/** * Classe pour gérer les résultats du D30 dans Lethal Fantasy */ export default class D30Roll { /** * Table des résultats D30 chargée depuis le fichier JSON * @type {Object} */ static resultsTable = null /** * Définitions des conditions spéciales * @type {Object} */ static definitions = null /** * Types de jets supportés * @type {Object} */ static ROLL_TYPES = { MELEE_ATTACK: "melee_attack", RANGED_ATTACK: "ranged_attack", MELEE_DEFENSE: "melee_defense", ARCANE_SPELL_ATTACK: "arcane_spell_attack", ARCANE_SPELL_DEFENSE: "arcane_spell_defense", SKILL_ROLLS: "skill_rolls" } /** * Initialise la classe en chargeant la table des résultats * @returns {Promise} */ static async initialize() { try { const response = await fetch("systems/fvtt-lethal-fantasy/module/config/d30_results_tables.json") const data = await response.json() this.resultsTable = data.d30_dice_results this.definitions = data.definitions console.log("D30Roll | D30 results table loaded successfully") } catch (error) { console.error("D30Roll | Error loading D30 table:", error) ui.notifications.error("Unable to load D30 results table") } } /** * Récupère le résultat d'un jet de D30 * @param {number} diceValue La valeur du dé (1-30) * @param {string} rollType Le type de jet externe (ex: "weapon-attack", "spell-attack", etc.) * @param {Object} weapon L'arme ou l'objet utilisé (optionnel, nécessaire pour certains types) * @returns {string|null} Le résultat correspondant ou null si vide/non trouvé */ static getResult(diceValue, rollType, weapon = null) { if (!this.resultsTable) { console.warn("D30Roll | Results table is not initialized. Call D30Roll.initialize() first.") return null } // Validation des paramètres if (diceValue < 1 || diceValue > 30) { console.warn(`D30Roll | Invalid dice value: ${diceValue}. Must be between 1 and 30.`) return null } // Convert external rollType to internal rollType const internalType = this.convertToInternalType(rollType, weapon) if (!internalType) { console.warn(`D30Roll | Could not convert roll type: ${rollType}`) return null } if (!Object.values(this.ROLL_TYPES).includes(internalType)) { console.warn(`D30Roll | Invalid internal roll type: ${internalType}`) return null } const resultEntry = this.resultsTable[diceValue] if (!resultEntry) { console.warn(`D30Roll | No entry found for value ${diceValue}`) return null } const result = resultEntry[internalType] // Retourne null si le résultat est "empty" if (result === "empty" || !result) { return null } return result } /** * Convertit un rollType externe en rollType interne * @param {string} externalType Le type de jet externe (ex: "weapon-attack") * @param {Object} weapon L'arme ou l'objet utilisé (optionnel) * @returns {string|null} Le type interne correspondant ou null */ static convertToInternalType(externalType, weapon = null) { // Attack types - need weapon to determine if melee or ranged if (externalType === "weapon-attack") { if (!weapon) { console.warn("D30Roll | Weapon object required for weapon-attack type") return this.ROLL_TYPES.MELEE_ATTACK // Default to melee } return weapon.system?.weaponType === "ranged" ? this.ROLL_TYPES.RANGED_ATTACK : this.ROLL_TYPES.MELEE_ATTACK } // Monster attacks - default to melee if (externalType === "monster-attack") { // Check if weapon object has range information if (weapon?.system?.weaponType === "ranged") { return this.ROLL_TYPES.RANGED_ATTACK } return this.ROLL_TYPES.MELEE_ATTACK } // Defense types if (externalType === "weapon-defense" || externalType === "monster-defense") { return this.ROLL_TYPES.MELEE_DEFENSE } // Spell types if (externalType === "spell-attack" || externalType === "spell" || externalType === "spell-power") { return this.ROLL_TYPES.ARCANE_SPELL_ATTACK } // Skill types if (externalType === "skill" || externalType === "monster-skill" || externalType === "save" || externalType === "challenge") { return this.ROLL_TYPES.SKILL_ROLLS } // If no match, return null console.warn(`D30Roll | Unknown external roll type: ${externalType}`) return null } /** * Récupère toutes les informations pour une valeur de dé donnée * @param {number} diceValue La valeur du dé (1-30) * @returns {Object|null} Tous les résultats pour cette valeur ou null */ static getAllResultsForValue(diceValue) { if (!this.resultsTable) { console.warn("D30Roll | Results table is not initialized.") return null } if (diceValue < 1 || diceValue > 30) { console.warn(`D30Roll | Invalid dice value: ${diceValue}`) return null } return this.resultsTable[diceValue] } /** * Récupère la définition d'une condition spéciale * @param {string} definitionKey La clé de la définition (ex: "flash_of_pain") * @returns {string|null} La définition ou null */ static getDefinition(definitionKey) { if (!this.definitions) { console.warn("D30Roll | Definitions are not initialized.") return null } return this.definitions[definitionKey] || null } /** * Vérifie si un résultat est vide * @param {string} result Le résultat à vérifier * @returns {boolean} True si le résultat est vide */ static isEmptyResult(result) { return !result || result === "empty" } /** * Récupère un résultat formaté pour l'affichage * @param {number} diceValue La valeur du dé (1-30) * @param {string} rollType Le type de jet externe * @param {Object} weapon L'arme ou l'objet utilisé (optionnel) * @returns {Object} Un objet avec le résultat et des informations de formatage */ static getFormattedResult(diceValue, rollType, weapon = null) { const result = this.getResult(diceValue, rollType, weapon) const internalType = this.convertToInternalType(rollType, weapon) return { value: diceValue, rollType: rollType, internalType: internalType, result: result, isEmpty: this.isEmptyResult(result), hasResult: !this.isEmptyResult(result) } } /** * Vérifie si la table est chargée * @returns {boolean} True si la table est chargée */ static isInitialized() { return this.resultsTable !== null && this.definitions !== null } }