229 lines
7.9 KiB
JavaScript
229 lines
7.9 KiB
JavaScript
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
|
||
}
|
||
}
|
||
}
|
||
|
||
}
|