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 } blessuresASoigner() { return [] } getEtatGeneral(options = { ethylisme: false }) { return 0 } 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) { RdDEmpoignade.checkEmpoignadeEnCours(this) let selectedCarac = this.getCaracByName(caracName) 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 arme = RdDItemCompetenceCreature.armeCreature(competence) if (arme && options.tryTarget && Targets.hasTargets()) { Targets.selectOneToken(target => { if (arme.action == "possession") { RdDPossession.onAttaquePossession(target, this, competence) } else { RdDCombat.rddCombatTarget(target, this).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 = "competence") { 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

`, title: 'Ne pas utiliser les automatisation de combat', buttonLabel: "Pas d'automatisation", onAction: async () => { this.rollCompetence(compToUse, { tryTarget: false, arme: arme }) } }); return } Targets.selectOneToken(target => { if (Targets.isTargetEntite(target)) { ui.notifications.warn(`Vous ne pouvez pas attaquer une entité non incarnée avec votre ${arme.name}!!!!`); return } const competence = this.getCompetence(compToUse) if (competence.isCompetencePossession()) { return RdDPossession.onAttaquePossession(target, this, competence); } RdDCombat.rddCombatTarget(target, this).attaque(competence, arme); }) } $getCompetenceArme(arme, competenceName) { return RdDItemArme.getCompetenceArme(arme, competenceName) } verifierForceMin(item) { } /* -------------------------------------------- */ async resetItemUse() { } async incDecItemUse(itemId, inc = 1) { } getItemUse(itemId) { return 0; } /* -------------------------------------------- */ async encaisser() { await RdDEncaisser.encaisser(this) } async encaisserDommages(rollData, attacker = undefined, show = undefined) { if (attacker && !await attacker.accorder(this, 'avant-encaissement')) { return; } const armure = await this.computeArmure(rollData); if (ReglesOptionnelles.isUsing('validation-encaissement-gr')){ await this.encaisserDommagesValidationGR(rollData, armure, attacker?.id, show); } else { const jet = await RdDUtility.jetEncaissement(rollData, armure, { showDice: SHOW_DICE }); await this.$onEncaissement(jet, show, attacker); } } async encaisserDommagesValidationGR(rollData, armure, attackerId, show) { if (!game.user.isGM) { RdDBaseActor.remoteActorCall({ tokenId: this.token?.id, actorId: this.id, method: 'encaisserDommagesValidationGR', args: [rollData, armure, attackerId, show] }); } else { const attacker = game.actors.get(attackerId); DialogValidationEncaissement.validerEncaissement(this, rollData, armure, jet => this.$onEncaissement(jet, show, attacker)); } } async $onEncaissement(jet, show, attacker) { await this.onAppliquerJetEncaissement(jet, attacker); await this.$afficherEncaissement(jet, show); } async onAppliquerJetEncaissement(encaissement, attacker) { } async $afficherEncaissement(encaissement, show) { foundry.utils.mergeObject(encaissement, { alias: this.name, hasPlayerOwner: this.hasPlayerOwner, show: show ?? {} }); await ChatUtility.createChatWithRollMode(this.name, { roll: encaissement.roll, content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-resultat-encaissement.html', encaissement) }); if (!encaissement.hasPlayerOwner && encaissement.endurance != 0) { encaissement = foundry.utils.duplicate(encaissement); encaissement.isGM = true; ChatMessage.create({ whisper: ChatMessage.getWhisperRecipients("GM"), content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-resultat-encaissement.html', encaissement) }); } } /* -------------------------------------------- */ async accorder(entite, when = 'avant-encaissement') { if (when != game.settings.get(SYSTEM_RDD, "accorder-entite-cauchemar") || entite == undefined || !entite.isEntite([ENTITE_INCARNE]) || entite.isEntiteAccordee(this)) { return true; } const rolled = await RdDResolutionTable.roll(this.getReveActuel(), - Number(entite.system.carac.niveau.value)); const rollData = { alias: this.name, rolled: rolled, entite: entite.name, selectedCarac: this.system.carac.reve }; if (rolled.isSuccess) { await entite.setEntiteReveAccordee(this); } await RdDResolutionTable.displayRollData(rollData, this, 'chat-resultat-accorder-cauchemar.html'); if (rolled.isPart) { await this.appliquerAjoutExperience(rollData, true); } return rolled.isSuccess; } isEntiteAccordee(attacker) { return true } async setEntiteReveAccordee(actor) { ui.notifications.error("Impossible de s'accorder à " + this.name + ": ce n'est pas une entité incarnée"); } }