import PrismRPGActorSheet from "./base-actor-sheet.mjs" import PrismRPGRoll from "../../documents/roll.mjs" import { SYSTEM } from "../../config/system.mjs" export default class PrismRPGCharacterSheet extends PrismRPGActorSheet { /** @override */ static DEFAULT_OPTIONS = { classes: ["character"], position: { width: 780, height: 780, }, window: { contentClasses: ["character-content"], }, actions: { createEquipment: PrismRPGCharacterSheet.#onCreateEquipment, rollInitiative: PrismRPGCharacterSheet.#onRollInitiative, armorHitPointsPlus: PrismRPGCharacterSheet.#onArmorHitPointsPlus, armorHitPointsMinus: PrismRPGCharacterSheet.#onArmorHitPointsMinus, armorPointsPlus: PrismRPGCharacterSheet.#onArmorPointsPlus, armorPointsMinus: PrismRPGCharacterSheet.#onArmorPointsMinus, actionPointsPlus: PrismRPGCharacterSheet.#onActionPointsPlus, actionPointsMinus: PrismRPGCharacterSheet.#onActionPointsMinus, manaPointsPlus: PrismRPGCharacterSheet.#onManaPointsPlus, manaPointsMinus: PrismRPGCharacterSheet.#onManaPointsMinus, hpPlus: PrismRPGCharacterSheet.#onHpPlus, hpMinus: PrismRPGCharacterSheet.#onHpMinus, hpTempPlus: PrismRPGCharacterSheet.#onHpTempPlus, hpTempMinus: PrismRPGCharacterSheet.#onHpTempMinus, postItemToChat: PrismRPGCharacterSheet.#onPostItemToChat, }, } /** @override */ static PARTS = { main: { template: "systems/fvtt-prism-rpg/templates/character-main.hbs", }, tabs: { template: "templates/generic/tab-navigation.hbs", }, skills: { template: "systems/fvtt-prism-rpg/templates/character-skills.hbs", }, subattributes: { template: "systems/fvtt-prism-rpg/templates/character-subattributes.hbs", }, combat: { template: "systems/fvtt-prism-rpg/templates/character-combat.hbs", }, equipment: { template: "systems/fvtt-prism-rpg/templates/character-equipment.hbs", }, spells: { template: "systems/fvtt-prism-rpg/templates/character-spells.hbs", }, biography: { template: "systems/fvtt-prism-rpg/templates/character-biography.hbs", }, } /** @override */ tabGroups = { sheet: "skills", } /** * Prepare an array of form header tabs. * @returns {Record>} */ #getTabs() { let tabs = { skills: { id: "skills", group: "sheet", icon: "fa-solid fa-shapes", label: "PRISMRPG.Label.skills" }, subattributes: { id: "subattributes", group: "sheet", icon: "fa-solid fa-diagram-project", label: "PRISMRPG.Label.subattributes" }, combat: { id: "combat", group: "sheet", icon: "fa-solid fa-swords", label: "PRISMRPG.Label.combat" }, equipment: { id: "equipment", group: "sheet", icon: "fa-solid fa-backpack", label: "PRISMRPG.Label.equipment" }, biography: { id: "biography", group: "sheet", icon: "fa-solid fa-book", label: "PRISMRPG.Label.biography" }, } if (this.actor.system.biodata.magicUser) { tabs.spells = { id: "spells", group: "sheet", icon: "fa-sharp-duotone fa-solid fa-wand-magic-sparkles", label: "PRISMRPG.Label.spells" } } for (const v of Object.values(tabs)) { v.active = this.tabGroups[v.group] === v.id v.cssClass = v.active ? "active" : "" } return tabs } /** @override */ async _prepareContext() { const context = await super._prepareContext() context.tabs = this.#getTabs() context.config = SYSTEM return context } /** @override */ async _preparePartContext(partId, context) { const doc = this.document switch (partId) { case "main": context.race = doc.itemTypes.race?.[0] || null const classes = doc.itemTypes.class || [] // Create 3 class slots context.classSlots = [ classes[0] || null, classes[1] || null, classes[2] || null ] break case "skills": context.tab = context.tabs.skills context.skills = doc.itemTypes.skill context.racialAbilities = doc.itemTypes["racial-ability"] context.abilities = doc.itemTypes["ability"] context.vulnerabilities = doc.itemTypes.vulnerability break case "subattributes": context.tab = context.tabs.subattributes break case "spells": context.tab = context.tabs.spells context.spells = doc.itemTypes.spell context.hasSpells = context.spells.length > 0 break case "combat": context.tab = context.tabs.combat context.weapons = doc.itemTypes.weapon context.armors = doc.itemTypes.armor context.shields = doc.itemTypes.shield break case "equipment": context.tab = context.tabs.equipment context.equipments = doc.itemTypes.equipment break case "biography": context.tab = context.tabs.biography context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(doc.system.description, { async: true }) context.enrichedNotes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(doc.system.notes, { async: true }) break } return context } // #region Drag-and-Drop Workflow /** * Callback actions which occur when a dragged element is dropped on a target. * @param {DragEvent} event The originating DragEvent * @protected */ async _onDrop(event) { if (!this.isEditable || !this.isEditMode) return const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event) // Handle different data types if (data.type === "Item") { const item = await fromUuid(data.uuid) return this._onDropItem(item) } } static async #onRollInitiative(event, target) { await this.document.system.rollInitiative() } static async #onArmorHitPointsPlus(event, target) { let armorHP = this.actor.system.combat.armorHitPoints armorHP += 1 this.actor.update({ "system.combat.armorHitPoints": armorHP }) } static async #onArmorHitPointsMinus(event, target) { let armorHP = this.actor.system.combat.armorHitPoints armorHP -= 1 this.actor.update({ "system.combat.armorHitPoints": Math.max(armorHP, 0) }) } static async #onManaPointsPlus(event, target) { let mana = this.actor.system.manaPoints.value mana += 1 this.actor.update({ "system.manaPoints.value": Math.min(mana, this.actor.system.manaPoints.max) }) } static async #onManaPointsMinus(event, target) { let mana = this.actor.system.manaPoints.value mana -= 1 this.actor.update({ "system.manaPoints.value": Math.max(mana, 0) }) } static async #onArmorPointsPlus(event, target) { let armor = this.actor.system.armorPoints.value armor += 1 this.actor.update({ "system.armorPoints.value": Math.min(armor, this.actor.system.armorPoints.max) }) } static async #onArmorPointsMinus(event, target) { let armor = this.actor.system.armorPoints.value armor -= 1 this.actor.update({ "system.armorPoints.value": Math.max(armor, 0) }) } static async#onActionPointsPlus(event, target) { let actionPoints = this.actor.system.actionPoints.value actionPoints += 1 this.actor.update({ "system.actionPoints.value": Math.min(actionPoints, this.actor.system.actionPoints.max) }) } static async#onActionPointsMinus(event, target) { let actionPoints = this.actor.system.actionPoints.value actionPoints -= 1 this.actor.update({ "system.actionPoints.value": Math.max(actionPoints, 0) }) } static async#onHpPlus(event, target) { let hp = this.actor.system.hp.value hp += 1 this.actor.update({ "system.hp.value": Math.min(hp, this.actor.system.hp.max) }) } static async#onHpMinus(event, target) { let hp = this.actor.system.hp.value hp -= 1 this.actor.update({ "system.hp.value": Math.max(hp, 0) }) } static async#onHpTempPlus(event, target) { const temp = this.actor.system.hp.temp this.actor.update({ "system.hp.temp": temp + 1 }) } static async#onHpTempMinus(event, target) { const temp = this.actor.system.hp.temp this.actor.update({ "system.hp.temp": Math.max(temp - 1, 0) }) } static async #onCreateEquipment(event, target) { } static async #onPostItemToChat(event, target) { console.log("PRISM RPG | PostItemToChat action triggered", { event: event, target: target }) // Try to find the item element from the clicked target or its parents let itemElement = null // First try with the target (the actual clicked element) if (event.target) { itemElement = event.target.closest('[data-item-id]') } // If not found, try with currentTarget (the element with the action) if (!itemElement && event.currentTarget) { itemElement = event.currentTarget.closest('[data-item-id]') } // If still not found, try with the target parameter if (!itemElement && target) { itemElement = target.closest('[data-item-id]') } console.log("PRISM RPG | Found item element", { itemElement: itemElement }) if (!itemElement) { console.warn("PRISM RPG | Could not find item element for posting to chat") return } const itemId = itemElement.dataset.itemId if (!itemId) { console.warn("PRISM RPG | Item ID not found for posting to chat") return } const item = this.actor.items.get(itemId) if (!item) { console.warn("PRISM RPG | Item not found for posting to chat", { itemId: itemId }) return } // Create a chat message with the item data const speaker = ChatMessage.getSpeaker({ actor: this.actor }) const content = await this.formatItemForChat(item) await ChatMessage.create({ content: content, speaker: speaker, }) } async formatItemForChat(item) { // Format the item data for chat display const itemTypeClass = `${item.type}-item` let htmlContent = `

${item.name}

${game.i18n.localize(`TYPES.Item.${item.type}`) || item.type}
` // Add item image if available if (item.img && !item.img.includes('icons/svg/mystery-man.svg')) { htmlContent += `
${item.name}
` } // Add item description if available if (item.system.description && item.system.description.trim() !== '') { const enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(item.system.description, { async: true }) htmlContent += `
${enrichedDescription}
` } // Add specific item data based on item type htmlContent += `
` switch (item.type) { case 'weapon': htmlContent += `
Type: ${item.system.weaponType || 'Unknown'}
Damage: ${item.system.damage || 'N/A'}
APC: ${item.system.apc || 0}
` if (item.system.damageType) { const damageTypes = [] if (item.system.damageType.piercing) damageTypes.push('Piercing') if (item.system.damageType.bludgeoning) damageTypes.push('Bludgeoning') if (item.system.damageType.slashing) damageTypes.push('Slashing') if (damageTypes.length > 0) { htmlContent += `
Damage Type: ${damageTypes.join('/')}
` } } break case 'armor': htmlContent += `
Armor Type: ${item.system.armorType || 'Unknown'}
Armor Points: ${item.system.armorPoints || 0}
APC: ${item.system.apc || 0}
` break case 'shield': htmlContent += `
Shield Type: ${item.system.shieldType || 'Unknown'}
Armor Points: ${item.system.armorPoints || 0}
APC: ${item.system.apc || 0}
` break case 'skill': htmlContent += `
Modifier: ${item.system.modifier || 0}
Core Skill: ${item.system.isCoreSkill ? 'Yes' : 'No'}
` break case 'spell': htmlContent += `
Level: ${item.system.level || 'Unknown'}
Mana Cost: ${item.system.manaCost || 0}
Casting Time: ${item.system.castingTime || 'N/A'}
` break case 'miracle': htmlContent += `
Prayer Time: ${item.system.prayerTime || 'N/A'}
` break case 'equipment': htmlContent += `
Weight: ${item.system.weight || 'N/A'}
` break default: // For other item types, just show basic info htmlContent += `
Item Type: ${item.type}
` } htmlContent += `
` return htmlContent } _onRender(context, options) { // Inputs with class `item-quantity` const woundDescription = this.element.querySelectorAll('.wound-data') for (const input of woundDescription) { input.addEventListener("change", (e) => { e.preventDefault(); e.stopImmediatePropagation(); const newValue = e.currentTarget.value const index = e.currentTarget.dataset.index const fieldName = e.currentTarget.dataset.name let tab = foundry.utils.duplicate(this.actor.system.hp.wounds) tab[index][fieldName] = newValue console.log(tab, index, fieldName, newValue) this.actor.update({ "system.hp.wounds": tab }); }) } super._onRender(); } /** * Handles the roll action triggered by user interaction. * * @param {PointerEvent} event The event object representing the user interaction. * @param {HTMLElement} target The target element that triggered the roll. * * @returns {Promise} A promise that resolves when the roll action is complete. * * @throws {Error} Throws an error if the roll type is not recognized. * * @description This method checks the current mode (edit or not) and determines the type of roll * (save, resource, or damage) based on the target element's data attributes. It retrieves the * corresponding value from the document's system and performs the roll. */ async _onRoll(event, target) { if (this.isEditMode) return // Use closest to find the rollable element in case user clicked on a child const rollableElement = event.target.closest('.rollable') || event.target const rollType = rollableElement.dataset.rollType let rollKey = rollableElement.dataset.rollKey; let rollDice = rollableElement.dataset?.rollDice; this.actor.prepareRoll(rollType, rollKey, rollDice) } // #endregion }