const { HandlebarsApplicationMixin } = foundry.applications.api export default class HamalronTarotDeckManager extends HandlebarsApplicationMixin(foundry.applications.api.ApplicationV2) { constructor(options = {}) { super(options) this.deck = [] this.discard = [] this.loadDeckState() } /** @override */ static DEFAULT_OPTIONS = { id: "tarot-deck-manager", classes: ["fvtt-hamalron", "tarot-deck-manager"], tag: "div", window: { title: "HAMALRON.TarotDeck.Title", icon: "fas fa-cards", resizable: true, }, position: { width: 600, height: 700, }, actions: { resetDeck: HamalronTarotDeckManager.#onResetDeck, drawCard: HamalronTarotDeckManager.#onDrawCard, drawMultiple: HamalronTarotDeckManager.#onDrawMultiple, shuffleDiscard: HamalronTarotDeckManager.#onShuffleDiscard, collectHands: HamalronTarotDeckManager.#onCollectHands, distributeCards: HamalronTarotDeckManager.#onDistributeCards, drawTension: HamalronTarotDeckManager.#onDrawTension, }, } /** @override */ static PARTS = { main: { template: "systems/fvtt-hamalron/templates/tarot-deck-manager.hbs", }, } /** * Save the current deck state to settings */ async saveDeckState() { const state = { deck: this.deck, discard: this.discard, } await game.settings.set("fvtt-hamalron", "tarotDeckState", state) console.log("Deck state saved:", state) } /** * Load the deck state from settings */ loadDeckState() { const state = game.settings.get("fvtt-hamalron", "tarotDeckState") if (state && state.deck && state.deck.length > 0) { this.deck = state.deck this.discard = state.discard || [] console.log("Deck state loaded:", state) } else { // Si aucun état sauvegardé, initialiser un nouveau deck this.resetDeck() } } /** * Reset the deck to a full tarot deck */ resetDeck() { this.deck = [] this.discard = [] // Créer toutes les cartes du jeu de tarot const items = game.items.filter(i => i.type === "tarot") if (items.length > 0) { // Utiliser les items existants this.deck = items.map(item => ({ 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, })) } else { ui.notifications.warn("HAMALRON.TarotDeck.NoCardsFound", { localize: true }) } // Mélanger le deck this.shuffleDeck() this.saveDeckState() this.render() } /** * Shuffle the deck using Fisher-Yates algorithm */ shuffleDeck() { for (let i = this.deck.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [this.deck[i], this.deck[j]] = [this.deck[j], this.deck[i]] } } /** * Draw a card from the deck * @param {number} count - Number of cards to draw * @returns {Array} - Drawn cards */ drawCards(count = 1) { if (this.deck.length === 0) { ui.notifications.warn("HAMALRON.TarotDeck.DeckEmpty", { localize: true }) return [] } const drawn = [] for (let i = 0; i < count && this.deck.length > 0; i++) { const card = this.deck.shift() drawn.push(card) } return drawn } /** * Send drawn cards to chat * @param {Array} cards - Cards to send to chat */ async sendCardsToChat(cards) { if (cards.length === 0) return const speaker = ChatMessage.getSpeaker({ alias: "Maître du Jeu" }) for (const card of cards) { const content = await foundry.applications.handlebars.renderTemplate( "systems/fvtt-hamalron/templates/chat-tarot-draw.hbs", { card: card, symboleLabel: game.i18n.localize(`HAMALRON.Tarot.FIELDS.symbole.${card.symbole}`), valeurLabel: game.i18n.localize(`HAMALRON.Tarot.FIELDS.valeur.${card.valeur}`), typeLabel: game.i18n.localize(`HAMALRON.Tarot.FIELDS.type.${card.type}`), } ) await ChatMessage.create({ speaker: speaker, content: content, type: CONST.CHAT_MESSAGE_TYPES.OTHER, }) } } /** @override */ async _prepareContext(options) { const context = await super._prepareContext(options) context.deckCount = this.deck.length context.discardCount = this.discard.length context.totalCards = this.deck.length + this.discard.length context.hasCards = this.deck.length > 0 context.hasDiscard = this.discard.length > 0 return context } /** * Handle reset deck action * @param {Event} event * @param {HTMLElement} target */ static async #onResetDeck(event, target) { const confirmed = await foundry.applications.api.DialogV2.confirm({ window: { title: "HAMALRON.TarotDeck.ResetConfirm" }, content: `
${game.i18n.localize("HAMALRON.TarotDeck.ResetWarning")}
`, rejectClose: false, modal: true, }) if (confirmed) { this.resetDeck() ui.notifications.info("HAMALRON.TarotDeck.DeckReset", { localize: true }) } } /** * Handle draw single card action * @param {Event} event * @param {HTMLElement} target */ static async #onDrawCard(event, target) { const cards = this.drawCards(1) if (cards.length > 0) { await this.sendCardsToChat(cards) // Ajouter les cartes directement à la défausse cards.forEach(card => this.addToDiscard(card)) this.saveDeckState() this.render() } } /** * Handle draw multiple cards action * @param {Event} event * @param {HTMLElement} target */ static async #onDrawMultiple(event, target) { const count = parseInt(target.dataset.count || "1") const cards = this.drawCards(count) if (cards.length > 0) { await this.sendCardsToChat(cards) // Ajouter les cartes directement à la défausse cards.forEach(card => this.addToDiscard(card)) this.saveDeckState() this.render() } } /** * Add a card to the discard pile * @param {Object} card - Card to add to discard */ addToDiscard(card) { // Vérifier que la carte n'est pas déjà dans la défausse const cardExists = this.discard.some(c => c.id === card.id) if (!cardExists) { this.discard.push(card) this.saveDeckState() this.render() } } /** * Collect all tarot cards from player characters and return them to the deck * @param {Event} event * @param {HTMLElement} target */ static async #onCollectHands(event, target) { const confirmed = await foundry.applications.api.DialogV2.confirm({ window: { title: game.i18n.localize("HAMALRON.TarotDeck.CollectHandsConfirm") }, content: `${game.i18n.localize("HAMALRON.TarotDeck.CollectHandsWarning")}
`, rejectClose: false, modal: true, }) if (!confirmed) return let totalCollected = 0 // Récupérer tous les acteurs de type personnage (incluant tous, pas seulement ceux avec propriété joueur) const playerCharacters = game.actors.filter(a => a.type === "personnage") console.log("Found characters:", playerCharacters.length) // Collecter toutes les cartes de tarot de tous les personnages for (const actor of playerCharacters) { const tarotCards = actor.items.filter(i => i.type === "tarot") console.log(`Actor ${actor.name} has ${tarotCards.length} tarot cards`) if (tarotCards.length > 0) { // Supprimer les cartes de l'acteur const cardIds = tarotCards.map(c => c.id) console.log(`Deleting ${cardIds.length} cards from ${actor.name}`) await actor.deleteEmbeddedDocuments("Item", cardIds) console.log(`Deleted cards from ${actor.name}`) totalCollected += tarotCards.length } } // Réinitialiser le deck avec toutes les cartes de tarot disponibles this.deck = [] this.discard = [] const items = game.items.filter(i => i.type === "tarot") if (items.length > 0) { // Créer un Map pour éviter les doublons (utiliser l'ID unique) const uniqueCards = new Map() items.forEach(item => { const key = `${item.system.symbole}_${item.system.valeur}_${item.system.type}` if (!uniqueCards.has(key)) { uniqueCards.set(key, { 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, }) } }) this.deck = Array.from(uniqueCards.values()) } // Mélanger le deck this.shuffleDeck() this.saveDeckState() ui.notifications.info( game.i18n.format("HAMALRON.TarotDeck.HandsCollected", { collected: totalCollected, deckSize: this.deck.length }) ) this.render() } /** * Distribute 8 cards to all connected players * @param {Event} event * @param {HTMLElement} target */ static async #onDistributeCards(event, target) { if (this.deck.length < 8) { ui.notifications.warn(game.i18n.localize("HAMALRON.TarotDeck.NotEnoughCards")) return } const confirmed = await foundry.applications.api.DialogV2.confirm({ window: { title: game.i18n.localize("HAMALRON.TarotDeck.DistributeConfirm") }, content: `${game.i18n.localize("HAMALRON.TarotDeck.DistributeWarning")}
`, rejectClose: false, modal: true, }) if (!confirmed) return let distributedCount = 0 let playersCount = 0 // Récupérer tous les utilisateurs connectés (sauf le GM) const connectedPlayers = game.users.filter(u => u.active && !u.isGM) for (const user of connectedPlayers) { // Trouver le personnage du joueur (premier actor de type personnage dont il est propriétaire) const playerCharacter = game.actors.find(a => a.type === "personnage" && a.testUserPermission(user, "OWNER") ) if (playerCharacter) { // Tirer 8 cartes pour ce personnage const cardsToDistribute = [] for (let i = 0; i < 8 && this.deck.length > 0; i++) { const card = this.deck.shift() cardsToDistribute.push(card) } // Créer les items sur l'acteur for (const card of cardsToDistribute) { const originalItem = game.items.get(card.id) if (originalItem) { await playerCharacter.createEmbeddedDocuments("Item", [originalItem.toObject()]) distributedCount++ // Cas spécial : Le Mat if (card.valeur === "lemat") { console.log(`Le Mat distributed to ${playerCharacter.name}! Executing special rules...`) // Import de TirageTarot pour accéder à la fonction helper const { default: HamalronTirageTarot } = await import("../documents/tirage-tarot.mjs") await HamalronTirageTarot.handleLeMatEffect(playerCharacter, this) } } } playersCount++ } } this.saveDeckState() ui.notifications.info( game.i18n.format("HAMALRON.TarotDeck.CardsDistributed", { cards: distributedCount, players: playersCount }) ) this.render() } /** * Shuffle discard pile back into deck * @param {Event} event * @param {HTMLElement} target */ static async #onShuffleDiscard(event, target) { if (this.discard.length === 0) { ui.notifications.warn(game.i18n.localize("HAMALRON.TarotDeck.NoDiscard")) return } // Ajouter toutes les cartes de la défausse au deck this.deck.push(...this.discard) this.discard = [] // Mélanger le deck this.shuffleDeck() this.saveDeckState() ui.notifications.info(game.i18n.localize("HAMALRON.TarotDeck.DiscardShuffled")) this.render() } /** * Handle draw tension action * @param {Event} event * @param {HTMLElement} target */ static async #onDrawTension(event, target) { // Import system config const { DIFFICULTY_CHOICES } = await import('../config/system.mjs') // Créer les options de difficulté const difficulties = Object.entries(DIFFICULTY_CHOICES) .filter(([key]) => key !== 'soustension_oppose') .map(([key, value]) => ({ value: key, label: value.label })) // Créer le contenu HTML du dialogue const selectOptions = difficulties.map(d => `` ).join('') const content = `