Initial import
This commit is contained in:
		
							
								
								
									
										4
									
								
								module/documents/_module.mjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								module/documents/_module.mjs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| export { default as LethalFantasyActor } from "./actor.mjs" | ||||
| export { default as LethalFantasyItem } from "./item.mjs" | ||||
| export { default as LethalFantasyRoll } from "./roll.mjs" | ||||
| export { default as LethalFantasyChatMessage } from "./chat-message.mjs" | ||||
							
								
								
									
										220
									
								
								module/documents/actor.mjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										220
									
								
								module/documents/actor.mjs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,220 @@ | ||||
| import { ROLL_TYPE } from "../config/system.mjs" | ||||
| export default class LethalFantasyActor extends Actor { | ||||
|   async _preCreate(data, options, user) { | ||||
|     await super._preCreate(data, options, user) | ||||
|  | ||||
|     // Configure prototype token settings | ||||
|     const prototypeToken = {} | ||||
|     if (this.type === "character") { | ||||
|       Object.assign(prototypeToken, { | ||||
|         sight: { enabled: true }, | ||||
|         actorLink: true, | ||||
|         disposition: CONST.TOKEN_DISPOSITIONS.FRIENDLY, | ||||
|       }) | ||||
|       this.updateSource({ prototypeToken }) | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Adds a path to the character. | ||||
|    * First path added is the major path, second path added is the minor path. | ||||
|    * Create all talents of the path and add them to the character. | ||||
|    * Add the path to the character. | ||||
|    * | ||||
|    * @param {Object} item The item to add as a path. | ||||
|    * @returns {Promise<void>} - A promise that resolves when the path is added. | ||||
|    */ | ||||
|   async addPath(item) { | ||||
|     if (this.type !== "character") return | ||||
|     let itemData = item.toObject() | ||||
|     if (this.system.hasVoieMajeure && this.system.hasVoieMineure) { | ||||
|       ui.notifications.warn(game.i18n.localize("TENEBRIS.Warning.dejaDeuxVoies")) | ||||
|       return | ||||
|     } | ||||
|  | ||||
|     // Voie mineure | ||||
|     if (this.system.hasVoieMajeure) { | ||||
|       if (this.system.voies.majeure.nom === item.name) { | ||||
|         ui.notifications.warn(game.i18n.localize("TENEBRIS.Warning.dejaVoieMajeure")) | ||||
|         return | ||||
|       } | ||||
|  | ||||
|       // Voie de base | ||||
|       const isBasePath = itemData.system.key !== "" && this.system.voies.majeure.key !== "" | ||||
|  | ||||
|       let dropNotification | ||||
|       let neufModifie | ||||
|       let onze | ||||
|       let neuf | ||||
|       if (isBasePath) { | ||||
|         onze = game.system.CONST.MINOR_PATH[itemData.system.key].onze | ||||
|         const labelOnze = game.i18n.localize(`TENEBRIS.Character.FIELDS.caracteristiques.${onze}.valeur.label`) | ||||
|         dropNotification = `La valeur de ${labelOnze} va être modifiée pour 11` | ||||
|         neuf = game.system.CONST.MINOR_PATH[itemData.system.key].neuf[this.system.voies.majeure.key] | ||||
|         if (neuf) { | ||||
|           neufModifie = true | ||||
|           const labelNeuf = game.i18n.localize(`TENEBRIS.Character.FIELDS.caracteristiques.${neuf}.valeur.label`) | ||||
|           dropNotification += `<br> La valeur de ${labelNeuf} va être modifiée pour 9` | ||||
|         } | ||||
|       } else { | ||||
|         dropNotification = "Vous devez modifier manuellement les caractéristiques selon la voie ajoutée" | ||||
|       } | ||||
|  | ||||
|       dropNotification += `<br>Vous pouvez renoncer à des biens de la voie majeure pour ceux de la voie mineure` | ||||
|       dropNotification += `<br> Vous pouvez renoncer à des langues de la voie majeure pour celles de la voie mineure` | ||||
|  | ||||
|       const proceed = await foundry.applications.api.DialogV2.confirm({ | ||||
|         window: { title: game.i18n.localize("TENEBRIS.Dialog.ajoutVoieMineureTitre") }, | ||||
|         content: dropNotification, | ||||
|         rejectClose: false, | ||||
|         modal: true, | ||||
|       }) | ||||
|       if (!proceed) return | ||||
|  | ||||
|       let voie | ||||
|  | ||||
|       // Création de la voie | ||||
|       voie = await this.createEmbeddedDocuments("Item", [itemData], { renderSheet: false }) | ||||
|  | ||||
|       if (isBasePath) { | ||||
|         await this.update({ | ||||
|           "system.voies.mineure.nom": item.name, | ||||
|           "system.voies.mineure.id": voie[0].id, | ||||
|           "system.voies.mineure.key": item.system.key, | ||||
|           [`system.caracteristiques.${onze}.valeur`]: 11, | ||||
|           "system.langues": `${this.system.langues} <br>${item.name} : ${item.system.langues}`, | ||||
|           "system.biens": `${this.system.biens} <br>${item.name} : ${item.system.biens}`, | ||||
|         }) | ||||
|         if (neufModifie) { | ||||
|           await this.update({ | ||||
|             [`system.caracteristiques.${neuf}.valeur`]: 9, | ||||
|           }) | ||||
|         } | ||||
|       } else { | ||||
|         await this.update({ | ||||
|           "system.voies.mineure.nom": item.name, | ||||
|           "system.voies.mineure.id": voie[0].id, | ||||
|           "system.voies.mineure.key": item.system.key, | ||||
|           "system.langues": `${this.system.langues} <br>${item.name} : ${item.system.langues}`, | ||||
|           "system.biens": `${this.system.biens} <br>${item.name} : ${item.system.biens}`, | ||||
|         }) | ||||
|       } | ||||
|  | ||||
|       // Création des talents | ||||
|       let newTalents = [] | ||||
|       for (const talent of itemData.system.talents) { | ||||
|         const talentItem = await fromUuid(talent) | ||||
|         if (talentItem) { | ||||
|           const newTalent = await this.createEmbeddedDocuments("Item", [talentItem.toObject()]) | ||||
|           // Modification de la voie du talent | ||||
|           await newTalent[0].update({ "system.path": voie[0].uuid }) | ||||
|           newTalents.push(newTalent[0].uuid) | ||||
|         } | ||||
|       } | ||||
|       // Mise à jour de la voie avec les nouveaux talents | ||||
|       await voie[0].update({ "system.talents": newTalents }) | ||||
|  | ||||
|       return ui.notifications.info(game.i18n.localize("TENEBRIS.Warning.voieMineureAjoutee")) | ||||
|     } | ||||
|  | ||||
|     // Voie majeure | ||||
|     else { | ||||
|       const proceed = await foundry.applications.api.DialogV2.confirm({ | ||||
|         window: { title: game.i18n.localize("TENEBRIS.Dialog.ajoutVoieMajeureTitre") }, | ||||
|         content: game.i18n.localize("TENEBRIS.Dialog.ajoutVoieMajeure"), | ||||
|         rejectClose: false, | ||||
|         modal: true, | ||||
|       }) | ||||
|       if (!proceed) return | ||||
|  | ||||
|       // Création de la voie | ||||
|       const voie = await this.createEmbeddedDocuments("Item", [itemData], { renderSheet: false }) | ||||
|  | ||||
|       // Création des talents | ||||
|       let newTalents = [] | ||||
|       for (const talent of itemData.system.talents) { | ||||
|         const talentItem = await fromUuid(talent) | ||||
|         if (talentItem) { | ||||
|           const newTalent = await this.createEmbeddedDocuments("Item", [talentItem.toObject()]) | ||||
|           // Modification de la voie du talent | ||||
|           await newTalent[0].update({ "system.path": voie[0].uuid }) | ||||
|           newTalents.push(newTalent[0].uuid) | ||||
|         } | ||||
|       } | ||||
|       // Mise à jour de la voie avec les nouveaux talents | ||||
|       await voie[0].update({ "system.talents": newTalents }) | ||||
|  | ||||
|       await this.update({ | ||||
|         "system.voies.majeure.id": voie[0].id, | ||||
|         "system.voies.majeure.nom": item.name, | ||||
|         "system.voies.majeure.key": item.system.key, | ||||
|         "system.caracteristiques.rob": item.system.caracteristiques.rob, | ||||
|         "system.caracteristiques.dex": item.system.caracteristiques.dex, | ||||
|         "system.caracteristiques.int": item.system.caracteristiques.int, | ||||
|         "system.caracteristiques.per": item.system.caracteristiques.per, | ||||
|         "system.caracteristiques.vol": item.system.caracteristiques.vol, | ||||
|         "system.ressources.san": item.system.ressources.san, | ||||
|         "system.ressources.oeil": item.system.ressources.oeil, | ||||
|         "system.ressources.verbe": item.system.ressources.verbe, | ||||
|         "system.ressources.bourse": item.system.ressources.bourse, | ||||
|         "system.ressources.magie": item.system.ressources.magie, | ||||
|         "system.dv": item.system.dv, | ||||
|         "system.dmax.valeur": item.system.dmax, | ||||
|         "system.langues": `${item.name} : ${item.system.langues}`, | ||||
|         "system.biens": `${item.name} : ${item.system.biens}`, | ||||
|       }) | ||||
|       return ui.notifications.info(game.i18n.localize("TENEBRIS.Warning.voieMajeureAjoutee")) | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Deletes a specified path and its associated talents. | ||||
|    * | ||||
|    * @param {Object} path The path object to be deleted. | ||||
|    * @param {boolean} isMajor Indicates if the path is a major path, elswise it is a minor path. | ||||
|    * @returns {Promise<void>} A promise that resolves when the deletion is complete. | ||||
|    * @throws {Error} Throws an error if the type is not "character". | ||||
|    */ | ||||
|   async deletePath(path, isMajor) { | ||||
|     if (this.type !== "character") return | ||||
|  | ||||
|     // Delete all talents linked to the path | ||||
|     let toDelete = path.system.talents.map((talent) => foundry.utils.parseUuid(talent).id) | ||||
|     toDelete.push(path.id) | ||||
|     await this.deleteEmbeddedDocuments("Item", toDelete) | ||||
|  | ||||
|     // Voie majeure | ||||
|     if (isMajor) { | ||||
|       await this.update({ "system.voies.majeure.nom": "", "system.voies.majeure.id": null, "system.voies.majeure.key": "" }) | ||||
|       ui.notifications.info(game.i18n.localize("TENEBRIS.Warning.voieMajeureSupprimee")) | ||||
|     } | ||||
|  | ||||
|     // Voie mineure | ||||
|     else { | ||||
|       await this.update({ "system.voies.mineure.nom": "", "system.voies.mineure.id": null, "system.voies.mineure.key": "" }) | ||||
|       ui.notifications.info(game.i18n.localize("TENEBRIS.Warning.voieMineureSupprimee")) | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Adds an attack to the actor if the actor type is "opponent". | ||||
|    * | ||||
|    * @param {Object} attack The attack object to be added. | ||||
|    * @returns {Promise<void>} A promise that resolves when the attack has been added. | ||||
|    */ | ||||
|   async addAttack(attack) { | ||||
|     if (this.type !== "opponent") return | ||||
|  | ||||
|     await this.createEmbeddedDocuments("Item", [attack]) | ||||
|   } | ||||
|  | ||||
|   async rollResource(resource) { | ||||
|     if (this.type !== "character") return | ||||
|     await this.system.roll(ROLL_TYPE.RESOURCE, resource) | ||||
|   } | ||||
|  | ||||
|   async rollSave(save, avantage) { | ||||
|     if (this.type !== "character") return | ||||
|     await this.system.roll(ROLL_TYPE.SAVE, save, avantage) | ||||
|   } | ||||
| } | ||||
							
								
								
									
										21
									
								
								module/documents/chat-message.mjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								module/documents/chat-message.mjs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| import LethalFantasyRoll from "./roll.mjs" | ||||
|  | ||||
| export default class LethalFantasyChatMessage extends ChatMessage { | ||||
|   async _renderRollContent(messageData) { | ||||
|     const data = messageData.message | ||||
|     if (this.rolls[0] instanceof LethalFantasyRoll) { | ||||
|       const isPrivate = !this.isContentVisible | ||||
|       // _renderRollHTML va appeler render sur tous les rolls | ||||
|       const rollHTML = await this._renderRollHTML(isPrivate) | ||||
|       if (isPrivate) { | ||||
|         data.flavor = game.i18n.format("CHAT.PrivateRollContent", { user: this.user.name }) | ||||
|         messageData.isWhisper = false | ||||
|         messageData.alias = this.user.name | ||||
|       } | ||||
|       data.content = `<section class="dice-rolls">${rollHTML}</section>` | ||||
|       return | ||||
|     } | ||||
|  | ||||
|     return super._renderRollContent(messageData) | ||||
|   } | ||||
| } | ||||
							
								
								
									
										1
									
								
								module/documents/item.mjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								module/documents/item.mjs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| export default class LethalFantasyItem extends Item {} | ||||
							
								
								
									
										592
									
								
								module/documents/roll.mjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										592
									
								
								module/documents/roll.mjs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,592 @@ | ||||
| import { ROLL_TYPE } from "../config/system.mjs" | ||||
| import LethalFantasyUtils from "../utils.mjs" | ||||
|  | ||||
| export default class LethalFantasyRoll extends Roll { | ||||
|   /** | ||||
|    * The HTML template path used to render dice checks of this type | ||||
|    * @type {string} | ||||
|    */ | ||||
|   static CHAT_TEMPLATE = "systems/fvtt-lethal-fantasy/templates/chat-message.hbs" | ||||
|  | ||||
|   get type() { | ||||
|     return this.options.type | ||||
|   } | ||||
|  | ||||
|   get isSave() { | ||||
|     return this.type === ROLL_TYPE.SAVE | ||||
|   } | ||||
|  | ||||
|   get isResource() { | ||||
|     return this.type === ROLL_TYPE.RESOURCE | ||||
|   } | ||||
|  | ||||
|   get isDamage() { | ||||
|     return this.type === ROLL_TYPE.DAMAGE | ||||
|   } | ||||
|  | ||||
|   get target() { | ||||
|     return this.options.target | ||||
|   } | ||||
|  | ||||
|   get value() { | ||||
|     return this.options.value | ||||
|   } | ||||
|  | ||||
|   get treshold() { | ||||
|     return this.options.treshold | ||||
|   } | ||||
|  | ||||
|   get actorId() { | ||||
|     return this.options.actorId | ||||
|   } | ||||
|  | ||||
|   get actorName() { | ||||
|     return this.options.actorName | ||||
|   } | ||||
|  | ||||
|   get actorImage() { | ||||
|     return this.options.actorImage | ||||
|   } | ||||
|  | ||||
|   get introText() { | ||||
|     return this.options.introText | ||||
|   } | ||||
|  | ||||
|   get introTextTooltip() { | ||||
|     return this.options.introTextTooltip | ||||
|   } | ||||
|  | ||||
|   get aide() { | ||||
|     return this.options.aide | ||||
|   } | ||||
|  | ||||
|   get gene() { | ||||
|     return this.options.gene | ||||
|   } | ||||
|  | ||||
|   get modificateur() { | ||||
|     return this.options.modificateur | ||||
|   } | ||||
|  | ||||
|   get avantages() { | ||||
|     return this.options.avantages | ||||
|   } | ||||
|  | ||||
|   get resultType() { | ||||
|     return this.options.resultType | ||||
|   } | ||||
|  | ||||
|   get isFailure() { | ||||
|     return this.resultType === "failure" | ||||
|   } | ||||
|  | ||||
|   get hasTarget() { | ||||
|     return this.options.hasTarget | ||||
|   } | ||||
|  | ||||
|   get targetName() { | ||||
|     return this.options.targetName | ||||
|   } | ||||
|  | ||||
|   get targetArmor() { | ||||
|     return this.options.targetArmor | ||||
|   } | ||||
|  | ||||
|   get targetMalus() { | ||||
|     return this.options.targetMalus | ||||
|   } | ||||
|  | ||||
|   get realDamage() { | ||||
|     return this.options.realDamage | ||||
|   } | ||||
|  | ||||
|   get rollAdvantage() { | ||||
|     return this.options.rollAdvantage | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Generates introductory text based on the roll type. | ||||
|    * | ||||
|    * @returns {string} The formatted introductory text for the roll. | ||||
|    */ | ||||
|   _createIntroText() { | ||||
|     let text | ||||
|  | ||||
|     switch (this.type) { | ||||
|       case ROLL_TYPE.SAVE: | ||||
|         const saveLabel = game.i18n.localize(`TENEBRIS.Character.FIELDS.caracteristiques.${this.target}.valeur.label`) | ||||
|         text = game.i18n.format("TENEBRIS.Roll.save", { save: saveLabel }) | ||||
|         text = text.concat("<br>").concat(`Seuil : ${this.treshold}`) | ||||
|         break | ||||
|       case ROLL_TYPE.RESOURCE: | ||||
|         const resourceLabel = game.i18n.localize(`TENEBRIS.Character.FIELDS.ressources.${this.target}.valeur.label`) | ||||
|         text = game.i18n.format("TENEBRIS.Roll.resource", { resource: resourceLabel }) | ||||
|         break | ||||
|       case ROLL_TYPE.DAMAGE: | ||||
|         const damageLabel = this.target | ||||
|         text = game.i18n.format("TENEBRIS.Roll.damage", { item: damageLabel }) | ||||
|         break | ||||
|       case ROLL_TYPE.ATTACK: | ||||
|         const attackLabel = this.target | ||||
|         text = game.i18n.format("TENEBRIS.Roll.attack", { item: attackLabel }) | ||||
|         break | ||||
|     } | ||||
|     return text | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Generates an introductory text tooltip with characteristics and modifiers. | ||||
|    * | ||||
|    * @returns {string} A formatted string containing the value, help, hindrance, and modifier. | ||||
|    */ | ||||
|   _createIntroTextTooltip() { | ||||
|     let tooltip = game.i18n.format("TENEBRIS.Tooltip.saveIntroTextTooltip", { value: this.value, aide: this.aide, gene: this.gene, modificateur: this.modificateur }) | ||||
|     if (this.hasTarget) { | ||||
|       tooltip = tooltip.concat(`<br>Cible : ${this.targetName}`) | ||||
|     } | ||||
|     return tooltip | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Prompt the user with a dialog to configure and execute a roll. | ||||
|    * | ||||
|    * @param {Object} options Configuration options for the roll. | ||||
|    * @param {string} options.rollType The type of roll being performed (e.g., RESOURCE, DAMAGE, ATTACK, SAVE). | ||||
|    * @param {string} options.rollValue The initial value or formula for the roll. | ||||
|    * @param {string} options.rollTarget The target of the roll. | ||||
|    * @param {"="|"+"|"++"|"-"|"--"} options.rollAdvantage If there is an avantage (+), a disadvantage (-), a double advantage (++), a double disadvantage (--) or a normal roll (=). | ||||
|    * @param {string} options.actorId The ID of the actor performing the roll. | ||||
|    * @param {string} options.actorName The name of the actor performing the roll. | ||||
|    * @param {string} options.actorImage The image of the actor performing the roll. | ||||
|    * @param {boolean} options.hasTarget Whether the roll has a target. | ||||
|    * @param {Object} options.target The target of the roll, if any. | ||||
|    * @param {Object} options.data Additional data for the roll. | ||||
|    * | ||||
|    * @returns {Promise<Object|null>} The roll result or null if the dialog was cancelled. | ||||
|    */ | ||||
|   static async prompt(options = {}) { | ||||
|     let formula = options.rollValue | ||||
|  | ||||
|     // Formula for a resource roll | ||||
|     if (options.rollType === ROLL_TYPE.RESOURCE) { | ||||
|       let ressource = game.i18n.localize(`TENEBRIS.Character.FIELDS.ressources.${options.rollTarget}.valeur.label`) | ||||
|       if (formula === "0" || formula === "") { | ||||
|         ui.notifications.warn(game.i18n.format("TENEBRIS.Warning.plusDeRessource", { ressource: ressource })) | ||||
|         return null | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     const rollModes = Object.fromEntries(Object.entries(CONFIG.Dice.rollModes).map(([key, value]) => [key, game.i18n.localize(value)])) | ||||
|     const fieldRollMode = new foundry.data.fields.StringField({ | ||||
|       choices: rollModes, | ||||
|       blank: false, | ||||
|       default: "public", | ||||
|     }) | ||||
|  | ||||
|     const choiceAide = foundry.utils.mergeObject({ 0: "0" }, options.rollValue <= 10 ? { 1: "1" } : { 1: "1", 2: "2" }) | ||||
|     const choiceGene = { | ||||
|       0: "0", | ||||
|       "-1": "-1", | ||||
|       "-2": "-2", | ||||
|       "-3": "-3", | ||||
|       "-4": "-4", | ||||
|       "-5": "-5", | ||||
|       "-6": "-6", | ||||
|       "-7": "-7", | ||||
|       "-8": "-8", | ||||
|       "-9": "-9", | ||||
|       "-10": "-10", | ||||
|     } | ||||
|     const choiceAvantage = { normal: "Normal", avantage: "Avantage", desavantage: "Désavantage", doubleAvantage: "Double avantage", doubleDesavantage: "Double désavantage" } | ||||
|     const choiceModificateur = { | ||||
|       0: "0", | ||||
|       "-1": "-1", | ||||
|       "-2": "-2", | ||||
|       "-3": "-3", | ||||
|       "-4": "-4", | ||||
|       "-5": "-5", | ||||
|       "-6": "-6", | ||||
|       "-7": "-7", | ||||
|       "-8": "-8", | ||||
|       "-9": "-9", | ||||
|       "-10": "-10", | ||||
|     } | ||||
|  | ||||
|     let damageDice | ||||
|     let damageDiceMax | ||||
|     let damageDiceFinal | ||||
|     let damageDiceLowered | ||||
|  | ||||
|     // Damage roll : check the roll is not above the maximum damage | ||||
|     if (options.rollType === ROLL_TYPE.DAMAGE) { | ||||
|       damageDice = options.rollValue | ||||
|       damageDiceMax = game.actors.get(options.actorId).system.dmax.valeur | ||||
|       damageDiceFinal = LethalFantasyUtils.maxDamage(damageDice, damageDiceMax) | ||||
|       damageDiceLowered = damageDiceFinal !== damageDice | ||||
|       // Récupération du nom de l'objet si c'est un jet depuis la fiche de l'acteur | ||||
|       // Si c'est via une macro le nom est connu | ||||
|       options.rollTarget = game.actors.get(options.actorId).items.get(options.rollTarget).name | ||||
|     } | ||||
|  | ||||
|     if (options.rollType === ROLL_TYPE.ATTACK) { | ||||
|       damageDice = options.rollValue | ||||
|     } | ||||
|  | ||||
|     let malus = "0" | ||||
|     let targetMalus = "0" | ||||
|     let targetName | ||||
|     let targetArmor | ||||
|     const displayOpponentMalus = game.settings.get("tenebris", "displayOpponentMalus") | ||||
|  | ||||
|     if (options.rollType === ROLL_TYPE.SAVE && options.hasTarget && options.target.document.actor.type === "opponent") { | ||||
|       targetName = options.target.document.actor.name | ||||
|       if (displayOpponentMalus) malus = options.target.document.actor.system.malus.toString() | ||||
|       else targetMalus = options.target.document.actor.system.malus.toString() | ||||
|     } | ||||
|  | ||||
|     if (options.rollType === ROLL_TYPE.DAMAGE && options.hasTarget && options.target.document.actor.type === "opponent") { | ||||
|       targetName = options.target.document.actor.name | ||||
|       targetArmor = options.target.document.actor.system.armure.toString() | ||||
|     } | ||||
|  | ||||
|     let dialogContext = { | ||||
|       isSave: options.rollType === ROLL_TYPE.SAVE, | ||||
|       isResource: options.rollType === ROLL_TYPE.RESOURCE, | ||||
|       isDamage: options.rollType === ROLL_TYPE.DAMAGE, | ||||
|       isAttack: options.rollType === ROLL_TYPE.ATTACK, | ||||
|       rollModes, | ||||
|       fieldRollMode, | ||||
|       choiceAide, | ||||
|       choiceGene, | ||||
|       choiceAvantage, | ||||
|       choiceModificateur, | ||||
|       damageDice, | ||||
|       damageDiceMax, | ||||
|       damageDiceFinal, | ||||
|       damageDiceLowered, | ||||
|       formula, | ||||
|       hasTarget: options.hasTarget, | ||||
|       malus, | ||||
|       targetName, | ||||
|       targetArmor, | ||||
|       rollAdvantage: this._convertAvantages(options.rollAdvantage), | ||||
|       rangeAdvantage: this._convertRollAdvantageToRange(options.rollAdvantage), | ||||
|     } | ||||
|     const content = await renderTemplate("systems/fvtt-lethal-fantasy/templates/roll-dialog.hbs", dialogContext) | ||||
|  | ||||
|     const title = LethalFantasyRoll.createTitle(options.rollType, options.rollTarget) | ||||
|     const label = game.i18n.localize("TENEBRIS.Roll.roll") | ||||
|     const rollContext = await foundry.applications.api.DialogV2.wait({ | ||||
|       window: { title: title }, | ||||
|       classes: ["tenebris"], | ||||
|       content, | ||||
|       buttons: [ | ||||
|         { | ||||
|           label: label, | ||||
|           callback: (event, button, dialog) => { | ||||
|             const output = Array.from(button.form.elements).reduce((obj, input) => { | ||||
|               if (input.name) obj[input.name] = input.value | ||||
|               return obj | ||||
|             }, {}) | ||||
|             // Avantages | ||||
|             switch (output.avantages) { | ||||
|               case "1": | ||||
|                 output.avantages = "doubleDesavantage" | ||||
|                 break | ||||
|               case "2": | ||||
|                 output.avantages = "desavantage" | ||||
|                 break | ||||
|               case "3": | ||||
|                 output.avantages = "normal" | ||||
|                 break | ||||
|               case "4": | ||||
|                 output.avantages = "avantage" | ||||
|                 break | ||||
|               case "5": | ||||
|                 output.avantages = "doubleAvantage" | ||||
|                 break | ||||
|             } | ||||
|             return output | ||||
|           }, | ||||
|         }, | ||||
|       ], | ||||
|       rejectClose: false, // Click on Close button will not launch an error | ||||
|       render: (event, dialog) => { | ||||
|         const rangeInput = dialog.querySelector('input[name="avantages"]') | ||||
|         if (rangeInput) { | ||||
|           rangeInput.addEventListener("change", (event) => { | ||||
|             event.preventDefault() | ||||
|             event.stopPropagation() | ||||
|             const readOnly = dialog.querySelector('input[name="selectAvantages"]') | ||||
|             readOnly.value = this._convertAvantages(event.target.value) | ||||
|           }) | ||||
|         } | ||||
|       }, | ||||
|     }) | ||||
|  | ||||
|     // If the user cancels the dialog, exit | ||||
|     if (rollContext === null) return | ||||
|  | ||||
|     let treshold | ||||
|  | ||||
|     if (options.rollType === ROLL_TYPE.SAVE) { | ||||
|       const aide = rollContext.aide === "" ? 0 : parseInt(rollContext.aide, 10) | ||||
|       const gene = rollContext.gene === "" ? 0 : parseInt(rollContext.gene, 10) | ||||
|       const modificateur = rollContext.modificateur === "" ? 0 : parseInt(rollContext.modificateur, 10) | ||||
|  | ||||
|       if (options.rollType === ROLL_TYPE.SAVE) { | ||||
|         let dice = "1d20" | ||||
|         switch (rollContext.avantages) { | ||||
|           case "avantage": | ||||
|             dice = "2d20kl" | ||||
|             break | ||||
|           case "desavantage": | ||||
|             dice = "2d20kh" | ||||
|             break | ||||
|           case "doubleAvantage": | ||||
|             dice = "3d20kl" | ||||
|             break | ||||
|           case "doubleDesavantage": | ||||
|             dice = "3d20kh" | ||||
|             break | ||||
|         } | ||||
|         formula = `${dice}` | ||||
|       } | ||||
|  | ||||
|       treshold = options.rollValue + aide + gene + modificateur | ||||
|     } | ||||
|  | ||||
|     // Formula for a damage roll | ||||
|     if (options.rollType === ROLL_TYPE.DAMAGE) { | ||||
|       formula = damageDiceFinal | ||||
|     } | ||||
|  | ||||
|     // Formula for an attack roll | ||||
|     if (options.rollType === ROLL_TYPE.ATTACK) { | ||||
|       formula = damageDice | ||||
|     } | ||||
|  | ||||
|     const rollData = { | ||||
|       type: options.rollType, | ||||
|       target: options.rollTarget, | ||||
|       value: options.rollValue, | ||||
|       treshold: treshold, | ||||
|       actorId: options.actorId, | ||||
|       actorName: options.actorName, | ||||
|       actorImage: options.actorImage, | ||||
|       rollMode: rollContext.visibility, | ||||
|       hasTarget: options.hasTarget, | ||||
|       targetName, | ||||
|       targetArmor, | ||||
|       targetMalus, | ||||
|       ...rollContext, | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * A hook event that fires before the roll is made. | ||||
|      * @function tenebris.preRoll | ||||
|      * @memberof hookEvents | ||||
|      * @param {Object} options          Options for the roll. | ||||
|      * @param {Object} rollData         All data related to the roll. | ||||
|      * @returns {boolean}               Explicitly return `false` to prevent roll to be made. | ||||
|      */ | ||||
|     if (Hooks.call("tenebris.preRoll", options, rollData) === false) return | ||||
|  | ||||
|     const roll = new this(formula, options.data, rollData) | ||||
|  | ||||
|     await roll.evaluate() | ||||
|  | ||||
|     let resultType | ||||
|     if (options.rollType === ROLL_TYPE.SAVE) { | ||||
|       resultType = roll.total <= treshold ? "success" : "failure" | ||||
|     } else if (options.rollType === ROLL_TYPE.RESOURCE) { | ||||
|       resultType = roll.total === 1 || roll.total === 2 ? "failure" : "success" | ||||
|     } | ||||
|  | ||||
|     let realDamage | ||||
|     if (options.rollType === ROLL_TYPE.DAMAGE) { | ||||
|       realDamage = Math.max(0, roll.total - parseInt(targetArmor, 10)) | ||||
|     } | ||||
|  | ||||
|     roll.options.resultType = resultType | ||||
|     roll.options.treshold = treshold | ||||
|     roll.options.introText = roll._createIntroText() | ||||
|     roll.options.introTextTooltip = roll._createIntroTextTooltip() | ||||
|     roll.options.realDamage = realDamage | ||||
|  | ||||
|     /** | ||||
|      * A hook event that fires after the roll has been made. | ||||
|      * @function tenebris.Roll | ||||
|      * @memberof hookEvents | ||||
|      * @param {Object} options          Options for the roll. | ||||
|      * @param {Object} rollData         All data related to the roll. | ||||
|       @param {LethalFantasyRoll} roll        The resulting roll. | ||||
|      * @returns {boolean}               Explicitly return `false` to prevent roll to be made. | ||||
|      */ | ||||
|     if (Hooks.call("tenebris.Roll", options, rollData, roll) === false) return | ||||
|  | ||||
|     return roll | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Creates a title based on the given type. | ||||
|    * | ||||
|    * @param {string} type The type of the roll. | ||||
|    * @param {string} target The target of the roll. | ||||
|    * @returns {string} The generated title. | ||||
|    */ | ||||
|   static createTitle(type, target) { | ||||
|     switch (type) { | ||||
|       case ROLL_TYPE.SAVE: | ||||
|         return `${game.i18n.localize("TENEBRIS.Dialog.titleSave")} : ${game.i18n.localize(`TENEBRIS.Manager.${target}`)}` | ||||
|       case ROLL_TYPE.RESOURCE: | ||||
|         return `${game.i18n.localize("TENEBRIS.Dialog.titleResource")} : ${game.i18n.localize(`TENEBRIS.Manager.${target}`)}` | ||||
|       case ROLL_TYPE.DAMAGE: | ||||
|         return `${game.i18n.localize("TENEBRIS.Dialog.titleDamage")} : ${target}` | ||||
|       case ROLL_TYPE.ATTACK: | ||||
|         return `${game.i18n.localize("TENEBRIS.Dialog.titleAttack")} : ${target}` | ||||
|       default: | ||||
|         return game.i18n.localize("TENEBRIS.Dialog.titleStandard") | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** @override */ | ||||
|   async render(chatOptions = {}) { | ||||
|     let chatData = await this._getChatCardData(chatOptions.isPrivate) | ||||
|     return await renderTemplate(this.constructor.CHAT_TEMPLATE, chatData) | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Generates the data required for rendering a roll chat card. | ||||
|    * | ||||
|    * @param {boolean} isPrivate Indicates if the chat card is private. | ||||
|    * @returns {Promise<Object>} A promise that resolves to an object containing the chat card data. | ||||
|    * @property {Array<string>} css - CSS classes for the chat card. | ||||
|    * @property {Object} data - The data associated with the roll. | ||||
|    * @property {number} diceTotal - The total value of the dice rolled. | ||||
|    * @property {boolean} isGM - Indicates if the user is a Game Master. | ||||
|    * @property {string} formula - The formula used for the roll. | ||||
|    * @property {number} total - The total result of the roll. | ||||
|    * @property {boolean} isSave - Indicates if the roll is a saving throw. | ||||
|    * @property {boolean} isResource - Indicates if the roll is related to a resource. | ||||
|    * @property {boolean} isDamage - Indicates if the roll is for damage. | ||||
|    * @property {boolean} isFailure - Indicates if the roll is a failure. | ||||
|    * @property {Array} avantages - Advantages associated with the roll. | ||||
|    * @property {string} actorId - The ID of the actor performing the roll. | ||||
|    * @property {string} actingCharName - The name of the character performing the roll. | ||||
|    * @property {string} actingCharImg - The image of the character performing the roll. | ||||
|    * @property {string} introText - Introductory text for the roll. | ||||
|    * @property {string} introTextTooltip - Tooltip for the introductory text. | ||||
|    * @property {string} resultType - The type of result (e.g., success, failure). | ||||
|    * @property {boolean} hasTarget - Indicates if the roll has a target. | ||||
|    * @property {string} targetName - The name of the target. | ||||
|    * @property {number} targetArmor - The armor value of the target. | ||||
|    * @property {number} realDamage - The real damage dealt. | ||||
|    * @property {boolean} isPrivate - Indicates if the chat card is private. | ||||
|    * @property {string} cssClass - The combined CSS classes as a single string. | ||||
|    * @property {string} tooltip - The tooltip text for the chat card. | ||||
|    */ | ||||
|   async _getChatCardData(isPrivate) { | ||||
|     const cardData = { | ||||
|       css: [SYSTEM.id, "dice-roll"], | ||||
|       data: this.data, | ||||
|       diceTotal: this.dice.reduce((t, d) => t + d.total, 0), | ||||
|       isGM: game.user.isGM, | ||||
|       formula: this.formula, | ||||
|       total: this.total, | ||||
|       isSave: this.isSave, | ||||
|       isResource: this.isResource, | ||||
|       isDamage: this.isDamage, | ||||
|       isFailure: this.isFailure, | ||||
|       avantages: this.avantages, | ||||
|       actorId: this.actorId, | ||||
|       actingCharName: this.actorName, | ||||
|       actingCharImg: this.actorImage, | ||||
|       introText: this.introText, | ||||
|       introTextTooltip: this.introTextTooltip, | ||||
|       resultType: this.resultType, | ||||
|       hasTarget: this.hasTarget, | ||||
|       targetName: this.targetName, | ||||
|       targetArmor: this.targetArmor, | ||||
|       realDamage: this.realDamage, | ||||
|       isPrivate: isPrivate, | ||||
|     } | ||||
|     cardData.cssClass = cardData.css.join(" ") | ||||
|     cardData.tooltip = isPrivate ? "" : await this.getTooltip() | ||||
|     return cardData | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Converts the roll result to a chat message. | ||||
|    * | ||||
|    * @param {Object} [messageData={}] Additional data to include in the message. | ||||
|    * @param {Object} options Options for message creation. | ||||
|    * @param {string} options.rollMode The mode of the roll (e.g., public, private). | ||||
|    * @param {boolean} [options.create=true] Whether to create the message. | ||||
|    * @returns {Promise} - A promise that resolves when the message is created. | ||||
|    */ | ||||
|   async toMessage(messageData = {}, { rollMode, create = true } = {}) { | ||||
|     super.toMessage( | ||||
|       { | ||||
|         isSave: this.isSave, | ||||
|         isResource: this.isResource, | ||||
|         isDamage: this.isDamage, | ||||
|         isFailure: this.resultType === "failure", | ||||
|         avantages: this.avantages, | ||||
|         introText: this.introText, | ||||
|         introTextTooltip: this.introTextTooltip, | ||||
|         actingCharName: this.actorName, | ||||
|         actingCharImg: this.actorImage, | ||||
|         hasTarget: this.hasTarget, | ||||
|         targetName: this.targetName, | ||||
|         targetArmor: this.targetArmor, | ||||
|         targetMalus: this.targetMalus, | ||||
|         realDamage: this.realDamage, | ||||
|         ...messageData, | ||||
|       }, | ||||
|       { rollMode: rollMode }, | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   // Used in the avantages select and with the rollAdvantage parameter: convert the selected value to the corresponding string | ||||
|   static _convertAvantages(value) { | ||||
|     switch (value) { | ||||
|       case "1": | ||||
|         return game.i18n.localize("TENEBRIS.Roll.doubleDesavantage") | ||||
|       case "2": | ||||
|         return game.i18n.localize("TENEBRIS.Roll.desavantage") | ||||
|       case "3": | ||||
|         return game.i18n.localize("TENEBRIS.Roll.normal") | ||||
|       case "4": | ||||
|         return game.i18n.localize("TENEBRIS.Roll.avantage") | ||||
|       case "5": | ||||
|         return game.i18n.localize("TENEBRIS.Roll.doubleAvantage") | ||||
|       case "--": | ||||
|         return game.i18n.localize("TENEBRIS.Roll.doubleDesavantage") | ||||
|       case "-": | ||||
|         return game.i18n.localize("TENEBRIS.Roll.desavantage") | ||||
|       case "=": | ||||
|         return game.i18n.localize("TENEBRIS.Roll.normal") | ||||
|       case "+": | ||||
|         return game.i18n.localize("TENEBRIS.Roll.avantage") | ||||
|       case "++": | ||||
|         return game.i18n.localize("TENEBRIS.Roll.doubleAvantage") | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // Used in the rollAdvantage parameter: convert the selected value to the corresponding range value | ||||
|   static _convertRollAdvantageToRange(value) { | ||||
|     switch (value) { | ||||
|       case "--": | ||||
|         return 1 | ||||
|       case "-": | ||||
|         return 2 | ||||
|       case "=": | ||||
|         return 3 | ||||
|       case "+": | ||||
|         return 4 | ||||
|       case "++": | ||||
|         return 5 | ||||
|     } | ||||
|   } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user