/** * NPC/Monster Sheet for Vagabond RPG * * Compact stat block format sheet for NPCs and monsters: * - Header with HD, HP, TL, Zone * - Combat stats (Armor, Morale, Speed) * - Immunities/Weaknesses/Resistances * - Actions list with attack buttons * - Abilities list * - GM notes * * @extends VagabondActorSheet */ import VagabondActorSheet from "./base-actor-sheet.mjs"; export default class VagabondNPCSheet extends VagabondActorSheet { /* -------------------------------------------- */ /* Static Properties */ /* -------------------------------------------- */ /** @override */ static DEFAULT_OPTIONS = foundry.utils.mergeObject( super.DEFAULT_OPTIONS, { classes: ["vagabond", "sheet", "actor", "npc"], position: { width: 520, height: 600, }, actions: { ...VagabondActorSheet.DEFAULT_OPTIONS.actions, rollMorale: VagabondNPCSheet.#onRollMorale, rollAction: VagabondNPCSheet.#onRollAction, addAction: VagabondNPCSheet.#onAddAction, deleteAction: VagabondNPCSheet.#onDeleteAction, addAbility: VagabondNPCSheet.#onAddAbility, deleteAbility: VagabondNPCSheet.#onDeleteAbility, }, }, { inplace: false } ); /** @override */ static PARTS = { header: { template: "systems/vagabond/templates/actor/npc-header.hbs", }, stats: { template: "systems/vagabond/templates/actor/npc-stats.hbs", }, actions: { template: "systems/vagabond/templates/actor/npc-actions.hbs", }, abilities: { template: "systems/vagabond/templates/actor/npc-abilities.hbs", }, notes: { template: "systems/vagabond/templates/actor/npc-notes.hbs", }, }; /* -------------------------------------------- */ /* Getters */ /* -------------------------------------------- */ /** @override */ get tabs() { // NPC sheets don't use tabs - all content on one page return []; } /* -------------------------------------------- */ /* Data Preparation */ /* -------------------------------------------- */ /** @override */ async _prepareTypeContext(context, _options) { const system = this.actor.system; // Core combat stats context.hd = system.hd; context.hp = { value: system.hp.value, max: system.hp.max, percent: Math.round((system.hp.value / system.hp.max) * 100) || 0, isHalf: system.hp.value <= Math.floor(system.hp.max / 2), isDead: system.hp.value <= 0, }; context.tl = system.tl; context.armor = system.armor; context.morale = system.morale; context.moraleStatus = system.moraleStatus; // Zone with behavior hint context.zone = system.zone; context.zoneBehavior = system.getZoneBehavior?.() || ""; context.zoneOptions = { frontline: "VAGABOND.ZoneFrontline", midline: "VAGABOND.ZoneMidline", backline: "VAGABOND.ZoneBackline", }; // Size and being type context.size = system.size; context.beingType = system.beingType; context.sizeOptions = CONFIG.VAGABOND?.sizes || {}; context.beingTypeOptions = CONFIG.VAGABOND?.beingTypes || {}; // Speed context.speed = { walk: system.speed.value, fly: system.speed.fly, swim: system.speed.swim, climb: system.speed.climb, hasSpecialMovement: system.speed.fly > 0 || system.speed.swim > 0 || system.speed.climb > 0, }; // Senses context.senses = system.senses; context.hasSenses = system.senses.darksight || system.senses.blindsight > 0 || system.senses.tremorsense > 0; // Damage modifiers context.immunities = system.immunities || []; context.weaknesses = system.weaknesses || []; context.resistances = system.resistances || []; context.hasDamageModifiers = context.immunities.length > 0 || context.weaknesses.length > 0 || context.resistances.length > 0; // Actions with index for editing context.actions = (system.actions || []).map((action, index) => ({ ...action, index, })); context.hasActions = context.actions.length > 0; // Abilities with index for editing context.abilities = (system.abilities || []).map((ability, index) => ({ ...ability, index, })); context.hasAbilities = context.abilities.length > 0; // Appearing (encounter numbers) context.appearing = system.appearing; // Loot and GM notes context.loot = system.loot; context.gmNotes = system.gmNotes; // Damage type options for actions context.damageTypeOptions = CONFIG.VAGABOND?.damageTypes || {}; context.attackTypeOptions = { melee: "VAGABOND.AttackMelee", ranged: "VAGABOND.AttackRanged", }; } /** @override */ _configureRenderOptions(options) { super._configureRenderOptions(options); // NPC sheets render all parts (no tabs) options.parts = ["header", "stats", "actions", "abilities", "notes"]; } /* -------------------------------------------- */ /* Action Handlers */ /* -------------------------------------------- */ /** * Handle morale roll. * @param {PointerEvent} event * @param {HTMLElement} target */ static async #onRollMorale(event, _target) { event.preventDefault(); await this.actor.rollMorale({ trigger: "manual" }); } /** * Handle action roll (attack). * @param {PointerEvent} event * @param {HTMLElement} target */ static async #onRollAction(event, target) { event.preventDefault(); const actionIndex = parseInt(target.dataset.actionIndex, 10); const action = this.actor.system.actions[actionIndex]; if (!action) return; // Roll the damage for this action const roll = await new Roll(action.damage).evaluate(); // Create chat message const content = `
${action.description}
` : ""}Damage: [[/r ${action.damage}]] ${action.damageType}
${action.range ? `Range: ${action.range}
` : ""}