import { ChatUtility } from "../chat-utility.js"; import { DialogValidationEncaissement } from "../dialog-validation-encaissement.js"; import { Grammar } from "../grammar.js"; import { RdDItemCompetence } from "../item-competence.js"; import { Misc } from "../misc.js"; import { RdDEmpoignade } from "../rdd-empoignade.js"; import { RdDResolutionTable } from "../rdd-resolution-table.js"; import { RdDEncaisser } from "../rdd-roll-encaisser.js"; import { RdDRoll } from "../rdd-roll.js"; import { RdDUtility } from "../rdd-utility.js"; import { ReglesOptionnelles } from "../settings/regles-optionnelles.js"; import { RdDBaseActor } from "./base-actor.js"; import { RdDItemCompetenceCreature } from "../item-competencecreature.js"; import { StatusEffects } from "../settings/status-effects.js"; import { ITEM_TYPES } from "../item.js"; import { Targets } from "../targets.js"; import { RdDPossession } from "../rdd-possession.js"; import { RdDCombat } from "../rdd-combat.js"; import { RdDConfirm } from "../rdd-confirm.js"; import { ENTITE_INCARNE, SHOW_DICE, SYSTEM_RDD } from "../constants.js"; import { RdDItemArme } from "../item-arme.js"; const POSSESSION_SANS_DRACONIC = { img: 'systems/foundryvtt-reve-de-dragon/icons/entites/possession.webp', name: 'Sans draconic', system: { niveau: 0, defaut_carac: "reve-actuel", } }; /** * Classe de base pour les acteurs disposant de rêve (donc, pas des objets) * - Entités de rêve * - Créatures de "sang": créatures et humanoides */ export class RdDBaseActorReve extends RdDBaseActor { getCaracChanceActuelle() { return { label: 'Chance actuelle', value: this.getChanceActuel(), type: "number" }; } getCaracReveActuel() { return { label: 'Rêve actuel', value: this.getReveActuel(), type: "number" }; } getReveActuel() { return this.getReve() } getChanceActuel() { return this.getChance() } getReve() { return Number(this.system.carac.reve?.value ?? 0) } getForce() { return this.getReve() } getTaille() { return Number(this.system.carac.taille?.value ?? 0) } getAgilite() { return this.getForce() } getChance() { return this.getReve() } getMoralTotal() { return 0 } getBonusDegat() { return Number(this.system.attributs?.plusdom?.value ?? 0) } getProtectionNaturelle() { return Number(this.system.attributs?.protection?.value ?? 0) } getSConst() { return 0 } /* -------------------------------------------- */ getEncombrementMax() { return 0 } isSurenc() { return false } computeMalusSurEncombrement() { return 0 } ajustementAstrologique() { return 0 } getMalusArmure() { return 0 } getEnduranceActuelle() { return Number(this.system.sante?.endurance?.value ?? 0); } async jetEndurance(resteEndurance = undefined) { return { jetEndurance: 0, sonne: false } } isDead() { return false } isSonne() { return false } blessuresASoigner() { return [] } getEtatGeneral(options = { ethylisme: false }) { return 0 } isActorCombat() { return true } async computeArmure(attackerRoll) { return this.getProtectionNaturelle() } async remiseANeuf() { } async appliquerAjoutExperience(rollData, hideChatMessage = 'show') { } async santeIncDec(name, inc, isCritique = false) { } async finDeRound(options = { terminer: false }) { await this.$finDeRoundSuppressionEffetsTermines(options); await this.finDeRoundBlessures(); await this.$finDeRoundSupprimerObsoletes(); await this.$finDeRoundEmpoignade(); } async $finDeRoundSuppressionEffetsTermines(options) { for (let effect of this.getEffects()) { if (effect.duration.type !== 'none' && (effect.duration.remaining <= 0 || options.terminer)) { await effect.delete(); ChatMessage.create({ content: `${this.name} n'est plus ${Misc.lowerFirst(game.i18n.localize(effect.system.label))} !` }); } } } async finDeRoundBlessures() { } async $finDeRoundSupprimerObsoletes() { const obsoletes = [] .concat(this.itemTypes[ITEM_TYPES.empoignade].filter(it => it.system.pointsemp <= 0)) .concat(this.itemTypes[ITEM_TYPES.possession].filter(it => it.system.compteur < -2 || it.system.compteur > 2)) .map(it => it.id); await this.deleteEmbeddedDocuments('Item', obsoletes); } async $finDeRoundEmpoignade() { const immobilisations = this.itemTypes[ITEM_TYPES.empoignade].filter(it => it.system.pointsemp >= 2 && it.system.empoigneurid == this.id); immobilisations.forEach(emp => RdDEmpoignade.onImmobilisation(this, game.actors.get(emp.system.empoigneid), emp )) } async setSonne(sonne = true) { } /* -------------------------------------------- */ getCompetence(idOrName, options = {}) { if (idOrName instanceof Item) { return idOrName.isCompetence() ? idOrName : undefined } return RdDItemCompetence.findCompetence(this.items, idOrName, options) } getCompetences(name) { return RdDItemCompetence.findCompetences(this.items, name) } getCompetenceCorpsACorps(options = {}) { return this.getCompetence("Corps à corps", options) } getCompetencesEsquive() { return this.getCompetences("esquive") } getArmeParade(armeParadeId) { const item = armeParadeId ? this.getEmbeddedDocument('Item', armeParadeId) : undefined; return RdDItemArme.getArme(item); } getDraconicOuPossession() { return POSSESSION_SANS_DRACONIC } getPossession(possessionId) { return this.itemTypes[ITEM_TYPES.possession].find(it => it.system.possessionid == possessionId); } getPossessions() { return this.itemTypes[ITEM_TYPES.possession]; } getEmpoignades() { return this.itemTypes[ITEM_TYPES.empoignade]; } /* -------------------------------------------- */ async updateCreatureCompetence(idOrName, fieldName, value) { let competence = this.getCompetence(idOrName); if (competence) { function getFieldPath(fieldName) { switch (fieldName) { case "niveau": return 'system.niveau'; case "dommages": return 'system.dommages'; case "carac_value": return 'system.carac_value'; } return undefined } const path = getFieldPath(fieldName); if (path) { await competence.update({ [path]: value }); } } } /* -------------------------------------------- */ isEffectAllowed(effectId) { return false } getEffects(filter = e => true) { return this.getEmbeddedCollection("ActiveEffect").filter(filter); } getEffect(effectId) { return this.getEmbeddedCollection("ActiveEffect").find(it => it.statuses?.has(effectId)); } async setEffect(effectId, status) { if (this.isEffectAllowed(effectId)) { const effect = this.getEffect(effectId); if (!status && effect) { await this.deleteEmbeddedDocuments('ActiveEffect', [effect.id]); } if (status && !effect) { await this.createEmbeddedDocuments("ActiveEffect", [StatusEffects.prepareActiveEffect(effectId)]); } } } async removeEffect(id) { const effect = this.getEmbeddedCollection("ActiveEffect").find(it => it.id == id); if (effect) { await this.deleteEmbeddedDocuments('ActiveEffect', [id]); } } async removeEffects(filter = e => true) { if (game.user.isGM) { const ids = this.getEffects(filter).map(it => it.id); await this.deleteEmbeddedDocuments('ActiveEffect', ids); } } /* -------------------------------------------- */ getSurprise(isCombat = undefined) { let niveauSurprise = this.getEffects() .map(effect => StatusEffects.valeurSurprise(effect, isCombat)) .reduce(Misc.sum(), 0); if (niveauSurprise > 1) { return 'totale'; } if (niveauSurprise == 1) { return 'demi'; } return ''; } /* -------------------------------------------- */ async computeEtatGeneral() { // Par défaut, on ne calcule pas d'état général, seuls les personnages/créatures sont affectés this.system.compteurs.etat.value = 0; } /* -------------------------------------------- */ async openRollDialog({ name, label, template, rollData, callbackAction }) { const dialog = await RdDRoll.create(this, rollData, { html: template, close: async html => await this._onCloseRollDialog(html) }, { name: name, label: label, callbacks: [ this.createCallbackExperience(), this.createCallbackAppelAuMoral(), { action: callbackAction } ] }); dialog.render(true); return dialog } createEmptyCallback() { return { condition: r => false, action: r => { } }; } createCallbackExperience() { return this.createEmptyCallback(); } createCallbackAppelAuMoral() { return this.createEmptyCallback(); } async _onCloseRollDialog(html) { } /* -------------------------------------------- */ async roll() { RdDEmpoignade.checkEmpoignadeEnCours(this) const carac = this.getCarac() const selectedCaracName = ['apparence', 'perception', 'force', 'reve'].find(it => carac[it] != undefined) await this.openRollDialog({ name: `jet-${this.id}`, label: `Jet de ${this.name}`, template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll.html', rollData: { carac: carac, selectedCarac: carac[selectedCaracName], selectedCaracName: selectedCaracName, competences: this.itemTypes['competence'] }, callbackAction: r => this.$onRollCaracResult(r) }); } getCarac() { // TODO: le niveau d'une entité de cauchemar devrait être exclu... return foundry.utils.mergeObject(this.system.carac, { 'reve-actuel': this.getCaracReveActuel(), 'chance-actuelle': this.getCaracChanceActuelle() }, { inplace: false }) } /* -------------------------------------------- */ async rollCarac(caracName, jetResistance = undefined) { if (Grammar.equalsInsensitive(caracName, 'taille')) { return } RdDEmpoignade.checkEmpoignadeEnCours(this) let selectedCarac = this.getCaracByName(caracName) console.log("selectedCarac", selectedCarac) await this.openRollDialog({ name: 'jet-' + caracName, label: 'Jet ' + Grammar.apostrophe('de', selectedCarac.label), template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-carac.html', rollData: { selectedCarac: selectedCarac, competences: this.itemTypes['competence'], jetResistance: jetResistance ? caracName : undefined }, callbackAction: r => this.$onRollCaracResult(r) }); } /* -------------------------------------------- */ async $onRollCaracResult(rollData) { // Final chat message await RdDResolutionTable.displayRollData(rollData, this, 'chat-resultat-general.html'); } /* -------------------------------------------- */ async rollCompetence(idOrName, options = { tryTarget: true, arme: undefined }) { RdDEmpoignade.checkEmpoignadeEnCours(this) const competence = this.getCompetence(idOrName); let rollData = { carac: this.system.carac, competence: competence, arme: options.arme } if (competence.type == ITEM_TYPES.competencecreature) { const token = RdDUtility.getSelectedToken(this) const arme = RdDItemCompetenceCreature.armeCreature(competence) if (arme && options.tryTarget && Targets.hasTargets()) { Targets.selectOneTargetToken(target => { if (arme.action == "possession") { RdDPossession.onAttaquePossession(target, this, competence) } else { RdDCombat.rddCombatTarget(target, this, token).attaque(competence, arme) } }); return; } // Transformer la competence de créature RdDItemCompetenceCreature.setRollDataCreature(rollData) } await this.openRollDialog({ name: 'jet-competence', label: 'Jet ' + Grammar.apostrophe('de', competence.name), template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-competence.html', rollData: rollData, callbackAction: r => this.$onRollCompetence(r, options) }); } async $onRollCompetence(rollData, options) { await RdDResolutionTable.displayRollData(rollData, this, 'chat-resultat-competence.html') if (options?.onRollAutomate) { options.onRollAutomate(rollData); } } /** -------------------------------------------- * @param {*} arme item d'arme/compétence de créature * @param {*} categorieArme catégorie d'attaque à utiliser: competence (== melee), lancer, tir; naturelle, possession * @returns */ rollArme(arme, categorieArme, token) { token = token ?? RdDUtility.getSelectedToken(this) const compToUse = this.$getCompetenceArme(arme, categorieArme) if (!RdDItemArme.isArmeUtilisable(arme)) { ui.notifications.warn(`Arme inutilisable: ${arme.name} a une résistance de 0 ou moins`) return } if (!Targets.hasTargets()) { RdDConfirm.confirmer({ settingConfirmer: "confirmer-combat-sans-cible", content: `
Voulez vous faire un jet de ${compToUse} sans choisir de cible valide?
Tous les jets de combats devront être gérés à la main