import HamalronActorSheet from "./base-actor-sheet.mjs" import { SYSTEM } from "../../config/system.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, createDeal: HamalronPersonnageSheet.#onCreateDeal, createMalefica: HamalronPersonnageSheet.#onCreateMalefica, createRitual: HamalronPersonnageSheet.#onCreateRitual, createPerk: HamalronPersonnageSheet.#onCreatePerk, modifyAmmo: HamalronPersonnageSheet.#onModifyAmmo, modifyResistance: HamalronPersonnageSheet.#onModifyResistance, discardCard: HamalronPersonnageSheet.#onDiscardCard, rollCompetence: HamalronPersonnageSheet.#onRollCompetence, 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 || [] // Sort the sortileges by system.domain and then by the system.level context.sortileges.sort((a, b) => { if (a.system.domain === b.system.domain) { return a.system.level.localeCompare(b.system.level) } return a.system.domain.localeCompare(b.system.domain) }) break case "equipment": context.tab = context.tabs.equipment context.weapons = doc.itemTypes.weapon || [] context.weapons.sort((a, b) => a.name.localeCompare(b.name)) context.armors = doc.itemTypes.armor || [] context.armors.sort((a, b) => a.name.localeCompare(b.name)) context.equipments = doc.itemTypes.equipment || [] 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 < 8 // Marquer les cartes de succès et enrichir les descriptions for (const card of context.tarotCards) { card.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(card.system.description, { async: true }) // Vérifier si c'est une carte de succès 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 && card.system.valeur === successCardData.value.toString() }) } else { card.isSuccessCard = false } } break case "biography": context.tab = context.tabs.biography context.deals = doc.itemTypes.deal || [] context.enrichedBackstory = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.backstory, { async: true }) context.enrichedAppearance = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.appearance, { async: true }) context.enrichedScars = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.scars, { async: true }) context.enrichedLikes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.likes, { async: true }) context.enrichedDislikes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.dislikes, { async: true }) context.enrichedFears = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.fears, { async: true }) context.enrichedVices = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.vices, { async: true }) context.enrichedGoals = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.goals, { async: true }) context.enrichedAmbitions = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.ambitions, { async: true }) context.enrichedValues = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.values, { async: true }) context.enrichedBonds = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.bonds, { async: true }) context.enrichedNotes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.notes, { async: true }) break } return context } static #onCreateEquipment(event, target) { this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("HAMALRON.Label.newEquipment"), type: "equipment" }]) } static #onCreateDeal(event, target) { this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("HAMALRON.Label.newDeal"), type: "deal" }]) } static #onCreateMalefica(event, target) { this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("HAMALRON.Label.newMalefica"), type: "malefica" }]) } static #onCreateRitual(event, target) { this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("HAMALRON.Label.newRitual"), type: "ritual" }]) } static #onCreateWeapon(event, target) { this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("HAMALRON.Label.newWeapon"), type: "weapon" }]) } static #onCreateArmor(event, target) { this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("HAMALRON.Label.newArmor"), type: "armor" }]) } static #onCreatePerk(event, target) { this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("HAMALRON.Label.newPerk"), type: "perk" }]) } static async #onModifyAmmo(event, target) { const quantity = parseInt($(event.target).data("quantity")) const itemId = $(event.target).data("item-id") const item = this.document.items.get(itemId) if (!item) { console.warn(`HamalronCharacterSheet: Item with ID ${itemId} not found`) return } const currentAmmo = item.system.ammoQuantity || 0 const newAmmo = Math.max(0, currentAmmo + quantity) // Ensure ammo doesn't go below 0 await item.update({ "system.ammoQuantity": newAmmo }) } 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)) await this.document.update({ [`system.resistances.${symbole}.value`]: newValue }) } 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 = game.items.get(newCard.id) 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")) } 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 { // Trouver l'item original pour le dupliquer const originalItem = game.items.get(newCard.id) if (originalItem) { await this.document.createEmbeddedDocuments("Item", [originalItem.toObject()]) ui.notifications.info(game.i18n.localize("HAMALRON.Notifications.newCardDrawn") + newCard.name) } } deckManager.saveDeckState() deckManager.render() } } 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}`) } 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 // Check if we already have 8 cards (hard limit) if (currentTarotCount >= 8) { ui.notifications.warn(game.i18n.localize("HAMALRON.Notifications.tarotHandFull")) return } } if (item.type === "species-trait") { // Check if the item is already in the actor const existingTrait = this.document.items.find(i => i.type === "species-trait" && i.name === item.name) if (existingTrait) { await existingTrait.delete() // Display info message ui.notifications.info(game.i18n.localize("HAMALRON.Notifications.speciesTraitDeleted") + existingTrait.name) } } return super._onDropItem(item) } } }