import HamalronActorSheet from "./base-actor-sheet.mjs" import { SYSTEM } from "../../config/system.mjs" import HamalronUtils from "../../utils.mjs" export default class HamalronPersonnageSheet extends HamalronActorSheet { /** @override */ static DEFAULT_OPTIONS = { classes: ["character"], position: { width: 860, height: 620, }, window: { contentClasses: ["character-content"], }, actions: { createEquipment: HamalronPersonnageSheet.#onCreateEquipment, createArmor: HamalronPersonnageSheet.#onCreateArmor, createWeapon: HamalronPersonnageSheet.#onCreateWeapon, createSortilege: HamalronPersonnageSheet.#onCreateSortilege, openRecovery: HamalronPersonnageSheet.#onOpenRecovery, modifyResistance: HamalronPersonnageSheet.#onModifyResistance, removeWound: HamalronPersonnageSheet.#onRemoveWound, discardCard: HamalronPersonnageSheet.#onDiscardCard, rollCompetence: HamalronPersonnageSheet.#onRollCompetence, castSortilege: HamalronPersonnageSheet.#onCastSortilege, drawCard: HamalronPersonnageSheet.#onDrawCard, selectCard: HamalronPersonnageSheet.#onSelectCard, discardAndDraw: HamalronPersonnageSheet.#onDiscardAndDraw, }, } /** @override */ static PARTS = { main: { template: "systems/fvtt-hamalron/templates/personnage-main.hbs", }, tabs: { template: "templates/generic/tab-navigation.hbs", }, competences: { template: "systems/fvtt-hamalron/templates/personnage-status.hbs", }, tarot: { template: "systems/fvtt-hamalron/templates/personnage-tarot.hbs", }, sortileges: { template: "systems/fvtt-hamalron/templates/personnage-sortileges.hbs", }, equipment: { template: "systems/fvtt-hamalron/templates/personnage-equipement.hbs", }, biography: { template: "systems/fvtt-hamalron/templates/personnage-biographie.hbs", }, } /** @override */ tabGroups = { sheet: "competences", } /** * Prepare an array of form header tabs. * @returns {Record>} */ #getTabs() { const tabs = { competences: { id: "competences", group: "sheet", icon: "fa-solid fa-list-check", label: "HAMALRON.Label.competences" }, tarot: { id: "tarot", group: "sheet", icon: "fa-solid fa-cards", label: "HAMALRON.Label.tarot" }, sortileges: { id: "sortileges", group: "sheet", icon: "fa-solid fa-wand-magic-sparkles", label: "HAMALRON.Label.sortileges" }, equipment: { id: "equipment", group: "sheet", icon: "fa-solid fa-shapes", label: "HAMALRON.Label.equipment" }, biography: { id: "biography", group: "sheet", icon: "fa-solid fa-book", label: "HAMALRON.Label.biography" }, } 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() const doc = this.document return context } /** @override */ async _preparePartContext(partId, context) { const doc = this.document context.systemFields = this.document.system.schema.fields switch (partId) { case "main": // Get peuple and faction items (only one of each allowed) context.peuple = doc.itemTypes.peuple?.[0] || null context.faction = doc.itemTypes.faction?.[0] || null context.isEditMode = this.isEditMode context.isPlayMode = this.isPlayMode break case "competences": context.tab = context.tabs.competences const allCompetences = doc.itemTypes.competence || [] // Ajouter les données de niveau enrichies à chaque compétence allCompetences.forEach(c => { const niveauData = SYSTEM.NIVEAU_COMPETENCES[c.system.niveau] c.niveauName = niveauData?.name || c.system.niveau c.niveauModificateur = niveauData?.modificateur ?? 0 }) // Grouper les compétences par type et trier par nom dans chaque groupe context.competencesCommunes = allCompetences.filter(c => c.system.type === 'commune').sort((a, b) => a.name.localeCompare(b.name)) context.competencesPeuple = allCompetences.filter(c => c.system.type === 'peuple').sort((a, b) => a.name.localeCompare(b.name)) context.competencesFaction = allCompetences.filter(c => c.system.type === 'faction').sort((a, b) => a.name.localeCompare(b.name)) break; case "sortileges": context.tab = context.tabs.sortileges context.sortileges = doc.itemTypes.sortilege || [] context.sortileges.sort((a, b) => a.name.localeCompare(b.name)) break case "equipment": context.tab = context.tabs.equipment context.weapons = doc.itemTypes.arme || [] context.weapons.sort((a, b) => a.name.localeCompare(b.name)) context.armors = doc.itemTypes.armure || [] context.armors.sort((a, b) => a.name.localeCompare(b.name)) context.equipments = doc.itemTypes.equipement || [] context.equipments.sort((a, b) => a.name.localeCompare(b.name)) break case "tarot": context.tab = context.tabs.tarot context.tarotCards = doc.itemTypes.tarot || [] context.tarotCards.sort((a, b) => a.name.localeCompare(b.name)) context.handLimit = 7 context.handSize = context.tarotCards.length context.isOverLimit = context.handSize > context.handLimit context.canAddCard = context.handSize < context.handLimit for (const card of context.tarotCards) { card.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(card.system.description, { async: true }) if (doc.system.cartesSucces && card.system.type !== "atout") { card.isSuccessCard = Object.keys(doc.system.cartesSucces).some(symbole => { const successCardData = doc.system.cartesSucces[symbole] return card.system.symbole === symbole && String(card.system.valeur) === String(successCardData.value) }) } else { card.isSuccessCard = false } } break case "biography": context.tab = context.tabs.biography context.enrichedHistorial = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.historial, { async: true }) break } return context } static #onCreateEquipment(event, target) { this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("HAMALRON.Label.newEquipment"), type: "equipement" }]) } static #onCreateWeapon(event, target) { this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("HAMALRON.Label.newWeapon"), type: "arme" }]) } static #onCreateArmor(event, target) { this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("HAMALRON.Label.newArmor"), type: "armure" }]) } static #onCreateSortilege(event, target) { this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("HAMALRON.Label.newSortilege"), type: "sortilege" }]) } static async #onOpenRecovery(event, target) { const { default: HamalronResistanceRecovery } = await import("../resistance-recovery.mjs") await HamalronResistanceRecovery.prompt(this.document) } static async #onModifyResistance(event, target) { const symbole = target.dataset.symbole const delta = Number.parseInt(target.dataset.delta) if (!symbole || isNaN(delta)) { console.warn(`HamalronPersonnageSheet: Invalid resistance modification parameters`) return } const resistance = this.document.system.resistances[symbole] if (!resistance) { console.warn(`HamalronPersonnageSheet: Resistance ${symbole} not found`) return } const newValue = Math.max(0, Math.min(resistance.max, resistance.value + delta)) // Blessure incapacitante pour Épée (règle p.159) if (symbole === "epee" && delta < 0 && newValue < resistance.value && !this.document.system.blessureIncapacitante) { const choice = await foundry.applications.api.DialogV2.wait({ window: { title: game.i18n.localize("HAMALRON.Wound.Title") }, content: `

${game.i18n.localize("HAMALRON.Wound.Question")}

`, buttons: [ { action: "wound", label: game.i18n.localize("HAMALRON.Wound.Take"), default: true, callback: () => "wound" }, { action: "resist", label: game.i18n.localize("HAMALRON.Wound.Resist"), callback: () => "resist" }, ], rejectClose: false, }) if (choice === "wound") { await this.document.update({ "system.blessureIncapacitante": true, "system.malusBlessure": -2, }) ui.notifications.info(game.i18n.localize("HAMALRON.Wound.Active")) return } } await this.document.update({ [`system.resistances.${symbole}.value`]: newValue }) } static async #onRemoveWound(event, target) { const confirmed = await foundry.applications.api.DialogV2.confirm({ window: { title: game.i18n.localize("HAMALRON.Wound.Remove") }, content: `

${game.i18n.localize("HAMALRON.Wound.RemoveConfirm")}

`, rejectClose: false, modal: true, }) if (!confirmed) return await this.document.update({ "system.blessureIncapacitante": false, "system.malusBlessure": 0, }) ui.notifications.info(game.i18n.localize("HAMALRON.Wound.Healed")) } static async #onDiscardCard(event, target) { const itemId = $(event.target).data("item-id") const item = this.document.items.get(itemId) if (!item || item.type !== "tarot") { console.warn(`HamalronPersonnageSheet: Tarot card with ID ${itemId} not found`) return } // Supprimer la carte du personnage await item.delete() ui.notifications.info(game.i18n.localize("HAMALRON.Notifications.cardDiscarded") + item.name) } static async #onSelectCard(event, target) { const cardWrapper = $(target).closest(".tarot-card-wrapper") const wasSelected = cardWrapper.hasClass("selected") // Désélectionner toutes les cartes $(".tarot-card-wrapper").removeClass("selected") // Sélectionner la carte cliquée si elle n'était pas déjà sélectionnée if (!wasSelected) { cardWrapper.addClass("selected") } } static async #onDiscardAndDraw(event, target) { const itemId = $(target).data("item-id") const item = this.document.items.get(itemId) if (!item || item.type !== "tarot") { console.warn(`HamalronPersonnageSheet: Tarot card with ID ${itemId} not found`) return } // Dialog de confirmation const confirmed = await foundry.applications.api.DialogV2.confirm({ window: { title: game.i18n.localize("HAMALRON.Dialog.discardCardTitle"), }, content: `

${game.i18n.format("HAMALRON.Dialog.discardCardContent", { cardName: item.name })}

`, rejectClose: false, modal: true, }) if (!confirmed) return // Récupérer le deck manager const module = await import("../tarot-deck-manager.mjs") const HamalronTarotDeckManager = module.default if (!HamalronTarotDeckManager._instance) { HamalronTarotDeckManager._instance = new HamalronTarotDeckManager() } const deckManager = HamalronTarotDeckManager._instance if (!deckManager) { ui.notifications.warn(game.i18n.localize("HAMALRON.Notifications.deckManagerNotAvailable")) return } // Ajouter la carte à la défausse deckManager.addToDiscard({ id: item.id, name: item.name, img: item.img, symbole: item.system.symbole, valeur: item.system.valeur, type: item.system.type, description: item.system.description, }) // Supprimer la carte du personnage await item.delete() // Tirer une nouvelle carte si le deck n'est pas vide if (deckManager.deck.length > 0) { const newCards = deckManager.drawCards(1) if (newCards.length > 0) { const newCard = newCards[0] // Cas spécial : Le Mat if (newCard.valeur === "lemat") { console.log("Le Mat drawn! Executing special rules...") const { default: HamalronTirageTarot } = await import("../../documents/tirage-tarot.mjs") await HamalronTirageTarot.handleLeMatEffect(this.document, deckManager) } else { const originalItem = await HamalronUtils.findTarotItem(newCard) if (originalItem) { await this.document.createEmbeddedDocuments("Item", [originalItem.toObject()]) ui.notifications.info(game.i18n.localize("HAMALRON.Notifications.cardDiscardedAndDrawn") + newCard.name) } } } } else { ui.notifications.info(game.i18n.localize("HAMALRON.Notifications.cardDiscardedNoNew")) } await deckManager.saveDeckState() deckManager.render() } static async #onDrawCard(event, target) { const currentTarotCount = this.document.items.filter(i => i.type === "tarot").length // Vérifier que le joueur a moins de 7 cartes if (currentTarotCount >= 7) { ui.notifications.warn(game.i18n.localize("HAMALRON.Notifications.cannotDrawCard")) return } // Récupérer le deck manager singleton const module = await import("../tarot-deck-manager.mjs") const HamalronTarotDeckManager = module.default // Vérifier si l'instance existe, sinon la créer if (!HamalronTarotDeckManager._instance) { HamalronTarotDeckManager._instance = new HamalronTarotDeckManager() } const deckManager = HamalronTarotDeckManager._instance if (!deckManager) { ui.notifications.warn(game.i18n.localize("HAMALRON.Notifications.deckManagerNotAvailable")) return } if (deckManager.deck.length === 0) { ui.notifications.warn(game.i18n.localize("HAMALRON.Notifications.deckEmpty")) return } // Tirer une nouvelle carte const newCards = deckManager.drawCards(1) if (newCards.length > 0) { const newCard = newCards[0] // Cas spécial : Le Mat if (newCard.valeur === "lemat") { console.log("Le Mat drawn! Executing special rules...") const { default: HamalronTirageTarot } = await import("../../documents/tirage-tarot.mjs") await HamalronTirageTarot.handleLeMatEffect(this.document, deckManager) } else { const originalItem = await HamalronUtils.findTarotItem(newCard) if (originalItem) { await this.document.createEmbeddedDocuments("Item", [originalItem.toObject()]) ui.notifications.info(game.i18n.localize("HAMALRON.Notifications.newCardDrawn") + newCard.name) } } await deckManager.saveDeckState() deckManager.render() } } static async #onCastSortilege(event, target) { const itemId = $(target).data("item-id") const item = this.document.items.get(itemId) if (!item || item.type !== "sortilege") { console.warn(`HamalronPersonnageSheet: Sortilege with ID ${itemId} not found`) return } const { default: HamalronSortilegeCasting } = await import("../sortilege-casting.mjs") await HamalronSortilegeCasting.prompt(item, this.document) } static async #onRollCompetence(event, target) { const itemId = $(target).data("item-id") const item = this.document.items.get(itemId) if (!item || item.type !== "competence") { console.warn(`HamalronPersonnageSheet: Competence with ID ${itemId} not found`) return } await this.document.system.roll("competence", item) } /** * 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) { const rollType = $(event.currentTarget).data("roll-type") let item let li let statKey switch (rollType) { case "stat": statKey = $(event.currentTarget).data("stat-id"); item = this.actor.system?.stats?.[statKey]; break case "weapon": case "damage": li = $(event.currentTarget).parents(".item"); item = this.actor.items.get(li.data("item-id")); break default: throw new Error(`Unknown roll type ${rollType}`) } if (item) await this.document.system.roll(rollType, item) } 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) if (item.type === "tarot") { // Check if a card with the same name already exists const existingCard = this.document.items.find(i => i.type === "tarot" && i.name === item.name) if (existingCard) { ui.notifications.warn(game.i18n.localize("HAMALRON.Notifications.tarotCardAlreadyExists") + item.name) return } const currentTarotCount = this.document.items.filter(i => i.type === "tarot").length if (currentTarotCount >= 7) { ui.notifications.warn(game.i18n.localize("HAMALRON.Notifications.tarotHandFull")) return } } return super._onDropItem(item) } } }