Files
foundryvtt-reve-de-dragon/module/dialog-lancer-recette.js
T

229 lines
7.9 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Grammar } from "./grammar.js"
import { ITEM_TYPES, renderTemplate } from "./constants.js"
import { Misc } from "./misc.js"
import { RdDAlchimie } from "./rdd-alchimie.js"
import { RdDCarac } from "./rdd-carac.js"
import { RdDRoll } from "./rdd-roll.js"
import { extraireEtapesAlchimie } from "./roll/extraire-etapes-alchimie.mjs"
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api
export class DialogLancerRecette extends HandlebarsApplicationMixin(ApplicationV2) {
static DEFAULT_OPTIONS = {
tag: "form",
classes: ["dialog-lancer-recette"],
form: { submitOnChange: false, closeOnSubmit: false },
position: { width: 650, height: "auto" },
window: { title: "Lancer une recette", icon: "fa-solid fa-flask" },
actions: {
lancerOperations: DialogLancerRecette.#onLancerOperations,
},
}
static PARTS = {
form: { template: "systems/foundryvtt-reve-de-dragon/templates/item/dialog-lancer-recette.hbs" },
}
static async create(item, actor) {
const dialog = new DialogLancerRecette({ item, actor })
dialog.render(true)
}
constructor(options) {
super(options)
this.item = options.item
this.actor = options.actor
this.etapes = []
this.enCours = false
this.termine = false
this.allReussi = false
this.consomme = false
}
/* -------------------------------------------- */
async _prepareContext() {
const ingredients = Misc.safeJsonParse(this.item.system.ingredients, [])
const inventoryItems = this.actor.items.filter(i =>
[ITEM_TYPES.herbe, ITEM_TYPES.plante, ITEM_TYPES.ingredient,
ITEM_TYPES.objet, ITEM_TYPES.nourritureboisson, ITEM_TYPES.faune,
ITEM_TYPES.potion].includes(i.type))
const ingredientRows = ingredients.map(ing => {
const matches = inventoryItems.filter(inv => Grammar.matchName(ing.nom, inv.name))
const foundQty = matches.reduce((sum, inv) => sum + (parseInt(inv.system.quantite) || 0), 0)
const requiredQty = parseInt(ing.quantite) || 1
return {
nom: ing.nom,
quantiteRequise: requiredQty,
quantiteTrouvee: foundQty,
unite: ing.unite,
consommable: ing.consommable,
suffisant: foundQty >= requiredQty,
}
})
if (!this.etapes.length) {
this.etapes = extraireEtapesAlchimie(this.item.system.manipulation || '')
for (const [i, e] of this.etapes.entries()) {
e.num = i + 1
e.status = 'pending'
e.caracValue = this.actor.system.carac[e.caracKey]?.value ?? 0
e.sansCristal = e.type === 'couleur' && !this.actor.items.some(it =>
it.type === 'objet' && Grammar.toLowerCaseNoAccent(it.name) === 'cristal alchimique' && it.system.quantite > 0)
e.diffConditions = e.sansCristal ? -4 : 0
}
}
const resultat = Misc.safeJsonParse(this.item.system.resultat, null)
return {
recette: this.item.name,
personnage: this.actor.name,
but: this.item.system.but,
ingredients: ingredientRows,
allIngredientsOk: ingredientRows.every(r => r.suffisant),
etapes: this.etapes,
enCours: this.enCours,
termine: this.termine,
allReussi: this.allReussi,
peutLancer: !this.enCours && !this.termine,
resultat,
}
}
/* -------------------------------------------- */
async _onRender(context, options) {
}
/* -------------------------------------------- */
static async #onLancerOperations(event, target) {
if (this.enCours) return
this.enCours = true
this.render(true)
for (let i = 0; i < this.etapes.length; i++) {
const etape = this.etapes[i]
etape.status = 'rolling'
this._updateEtapeRow(i)
const rolled = await this._effectuerEtape(etape)
etape.status = rolled.isSuccess ? 'success' : 'failure'
this._updateEtapeRow(i)
if (!rolled.isSuccess) break
}
this.enCours = false
this.termine = true
this.allReussi = this.etapes.every(e => e.status === 'success')
await this._consommerIngredients()
this.consomme = true
this.render(true)
if (this.allReussi) {
ui.notifications.info('Toutes les opérations alchimiques ont réussi!')
const resultat = Misc.safeJsonParse(this.item.system.resultat, null)
if (resultat && resultat.type) {
const msg = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-resultat-alchimie-reussite.hbs', {
recette: this.item.name,
resultat,
resultatJson: JSON.stringify(resultat),
actorId: this.actor.id,
recipeImg: this.item.img,
alias: this.actor.getAlias ? this.actor.getAlias() : this.actor.name,
})
await ChatMessage.create({
content: msg,
speaker: ChatMessage.getSpeaker({ actor: this.actor }),
})
}
} else {
ui.notifications.warn('La recette a échoué. Les ingrédients ont été consommés.')
}
}
/* -------------------------------------------- */
async _effectuerEtape(etape) {
const tacheAlchimie = etape.type
const texteTache = etape.termes
const sansCristal = etape.sansCristal
const caracTache = RdDAlchimie.getCaracTache(tacheAlchimie)
const alchimieData = this.actor.getCompetence('alchimie')
const rollData = {
recette: this.item,
carac: { [caracTache]: this.actor.system.carac[caracTache] },
selectedCarac: this.actor.system.carac[caracTache],
competence: alchimieData,
diffLibre: RdDAlchimie.getDifficulte(texteTache),
diffConditions: sansCristal ? -4 : 0,
alchimie: {
tache: Misc.upperFirst(tacheAlchimie),
texte: texteTache,
sansCristal,
},
}
if (rollData.competence) {
rollData.competence.system.defaut_carac = caracTache
}
return new Promise(resolve => {
const callbacks = []
if (this.actor.createCallbackExperience) callbacks.push(this.actor.createCallbackExperience())
if (this.actor.createCallbackAppelAuMoral) callbacks.push(this.actor.createCallbackAppelAuMoral())
callbacks.push({
action: async r => {
resolve(r.rolled)
if (this.actor._alchimieResult) await this.actor._alchimieResult(r)
},
})
RdDRoll.create(this.actor, rollData,
{ html: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-alchimie.hbs' },
{ name: 'tache-alchimique', label: 'Tâche Alchimique', callbacks }
).then(dialog => {
dialog.render(true)
dialog.bringToFront()
})
})
}
/* -------------------------------------------- */
_updateEtapeRow(index) {
const etape = this.etapes[index]
if (!etape) return
const row = this.element?.querySelector(`.etape-row[data-index="${index}"]`)
if (!row) return
row.className = `etape-row etape-${etape.status}`
const cell = row.querySelector('.etape-status')
if (!cell) return
const icons = {
pending: '<i class="fas fa-circle" style="color:#999"></i>',
rolling: '<i class="fas fa-spinner fa-pulse" style="color:#2196F3"></i>',
success: '<i class="fas fa-check-circle" style="color:#2a8a2a"></i>',
failure: '<i class="fas fa-times-circle" style="color:#c44"></i>',
}
cell.innerHTML = icons[etape.status] || ''
}
/* -------------------------------------------- */
async _consommerIngredients() {
const ingredients = Misc.safeJsonParse(this.item.system.ingredients, [])
for (const ing of ingredients) {
if (!ing.consommable) continue
const matches = this.actor.items.filter(inv => Grammar.matchName(ing.nom, inv.name))
let remaining = parseInt(ing.quantite) || 1
for (const item of matches) {
if (remaining <= 0) break
const qty = parseInt(item.system.quantite) || 0
if (qty <= 0) continue
const toTake = Math.min(qty, remaining)
await item.diminuerQuantite(toTake, { supprimerSiZero: true })
remaining -= toTake
}
}
}
}