diff --git a/module/actor.js b/module/actor.js index e0d09e07..17f3432e 100644 --- a/module/actor.js +++ b/module/actor.js @@ -1758,7 +1758,7 @@ export class RdDActor extends Actor { } /* -------------------------------------------- */ - async rollAppelChance( onSuccess = () => {}, onEchec= ()=>{}) + async rollAppelChance(onSuccess = () => {}, onEchec = () => {}) { let rollData = { selectedCarac: this.getCaracByName('chance-actuelle'), surprise: '' }; const dialog = await RdDRoll.create(this, rollData, @@ -1768,21 +1768,23 @@ export class RdDActor extends Actor { label: 'Appel à la chance', callbacks: [ this.createCallbackExperience(), - { action: r => this._appelChanceResult(r) }, - { condition: r=> r.rolled.isSuccess, action: r => onSuccess() }, - { condition: r=> r.rolled.isEchec, action: r => onEchec() } + { action: r => this._appelChanceResult(r, onSuccess, onEchec) }, ] } - ); - dialog.render(true); - } - - /* -------------------------------------------- */ - async _appelChanceResult(rollData) { - if (rollData.rolled.isSuccess) { - await this.chanceActuelleIncDec(-1) + ); + dialog.render(true); } - RdDResolutionTable.displayRollData(rollData, this, 'chat-resultat-appelchance.html') + + /* -------------------------------------------- */ + async _appelChanceResult(rollData, onSuccess = () => {}, onEchec= ()=>{}) { + await RdDResolutionTable.displayRollData(rollData, this, 'chat-resultat-appelchance.html') + if (rollData.rolled.isSuccess) { + await this.chanceActuelleIncDec(-1) + onSuccess(); + } + else { + onEchec(); + } } /* -------------------------------------------- */ diff --git a/module/chat-utility.js b/module/chat-utility.js index 39bd8f67..6535d1f1 100644 --- a/module/chat-utility.js +++ b/module/chat-utility.js @@ -5,8 +5,8 @@ export class ChatUtility { static removeMyChatMessageContaining(part) { const toDelete = game.messages.filter(it => it.user._id == game.user._id) - .filter(it => it.data.content.includes(part)) - .forEach(it => it.delete()); + .filter(it => it.data.content.includes(part)); + toDelete.forEach(it => it.delete()); } diff --git a/module/rdd-combat.js b/module/rdd-combat.js index 59aef090..9616299a 100644 --- a/module/rdd-combat.js +++ b/module/rdd-combat.js @@ -101,6 +101,9 @@ export class RdDCombat { '#encaisser-button', '#appel-chance-defense', '#appel-destinee-defense', + '#appel-chance-attaque', + '#appel-destinee-attaque', + '#echec-total-attaque', ]) { html.on("click", button, event => { event.preventDefault(); @@ -133,72 +136,96 @@ export class RdDCombat { } const defenderTokenId = event.currentTarget.attributes['data-defenderTokenId'].value; - let defenderRoll = this._consumeDefense(attackerRoll.passeArme); + let defenderRoll = this._getDefense(attackerRoll.passeArme); + const armeParadeId = event.currentTarget.attributes['data-armeid']?.value; switch (button) { case '#particuliere-attaque': return await this.choixParticuliere(attackerRoll, event.currentTarget.attributes['data-mode'].value); - case '#parer-button': { - const armeId = event.currentTarget.attributes['data-armeid']; - return this.parade(attackerRoll, armeId?.value); - } + case '#parer-button': return this.parade(attackerRoll, armeParadeId); case '#esquiver-button': return this.esquive(attackerRoll); case '#encaisser-button': return this.encaisser(attackerRoll, defenderTokenId); + case '#echec-total-attaque': return this._onEchecTotal(attackerRoll); + case '#appel-chance-attaque': return this.attacker.rollAppelChance( + () => this.attaqueChanceuse(attackerRoll), + () => this._onEchecTotal(attackerRoll)); case '#appel-chance-defense': return this.defender.rollAppelChance( - () => this.rejouerDefense(defenderRoll, { chance: true }), - () => this.afficherOptionsDefense(attackerRoll, { chance: true })); - - case '#appel-destinee-defense': return this.defender.appelDestinee( - () => this.defenseSignificative(defenderRoll), - () => this.afficherOptionsDefense(attackerRoll, { destinee: true })); + () => this.defenseChanceuse(attackerRoll, defenderRoll), + () => this.afficherOptionsDefense(attackerRoll, { defenseChance: true })); + case '#appel-destinee-attaque': return this.attacker.appelDestinee( + () => this.attaqueSignificative(attackerRoll), + () => { }); + case '#appel-destinee-defense': return this.defender.appelDestinee( + () => this.defenseDestinee(defenderRoll), + () => { }); } } + /* -------------------------------------------- */ _consumeDefense(passeArme) { - let defenderRoll = game.system.rdd.rollDataHandler.defenses[passeArme]; + let defenderRoll = this._getDefense(passeArme); game.system.rdd.rollDataHandler.defenses[passeArme] = undefined; return defenderRoll; } + /* -------------------------------------------- */ + _getDefense(passeArme) { + return game.system.rdd.rollDataHandler.defenses[passeArme]; + } + + /* -------------------------------------------- */ _storeDefense(defenderRoll) { game.system.rdd.rollDataHandler.defenses[defenderRoll.passeArme] = defenderRoll; } - rejouerDefense(defenderRoll, tentatives) { + /* -------------------------------------------- */ + attaqueChanceuse(attackerRoll) { + ui.notifications.info("L'attaque est rejouée grâce à la chance") + attackerRoll.essais.attaqueChance = true; + this.attaque(attackerRoll, attackerRoll.arme); + } + + /* -------------------------------------------- */ + attaqueDestinee(attackerRoll) { + ui.notifications.info('Attaque significative grâce à la destinée') + RdDResolutionTable.forceSignificative(attackerRoll.rolled); + this.removeChatMessageActionsPasseArme(attackerRoll.passeArme); + this._onAttaqueNormale(attackerRoll); + } + + /* -------------------------------------------- */ + defenseChanceuse(attackerRoll, defenderRoll) { ui.notifications.info("La défense est rejouée grâce à la chance") - const attackerRoll = defenderRoll.attackerRoll; + attackerRoll.essais.defenseChance = true; + attackerRoll.essais.defense = false; this.removeChatMessageActionsPasseArme(attackerRoll.passeArme); - this.addTentatives(attackerRoll, tentatives); - - if (defenderRoll.arme) { - this.parade(attackerRoll, defenderRoll.arme._id); - } - else{ - this.esquive(attackerRoll); - } + this._sendMessageDefense(attackerRoll); } - afficherOptionsDefense(attackerRoll, tentatives) { - ui.notifications.info("La chance n'est pas avec vous") - this._sendMessageDefense(attackerRoll, tentatives); - } - - defenseSignificative(defenderRoll){ - ui.notifications.info('defense significative grâce à la destinée') - const attackerRoll = defenderRoll.attackerRoll; + /* -------------------------------------------- */ + defenseDestinee(defenderRoll) { + ui.notifications.info('Défense significative grâce à la destinée') RdDResolutionTable.forceSignificative(defenderRoll.rolled); - this.removeChatMessageActionsPasseArme(attackerRoll.passeArme); + this.removeChatMessageActionsPasseArme(defenderRoll.passeArme); if (defenderRoll.arme) { this._onParadeNormale(defenderRoll); } - else{ + else { this._onEsquiveNormale(defenderRoll); } } + /* -------------------------------------------- */ + afficherOptionsDefense(attackerRoll, essais) { + ui.notifications.info("La chance n'est pas avec vous"); + this._sendMessageDefense(attackerRoll, essais); + } + /* -------------------------------------------- */ removeChatMessageActionsPasseArme(passeArme) { - ChatUtility.removeMyChatMessageContaining(`
`); + if (game.settings.get("foundryvtt-reve-de-dragon", "supprimer-dialogues-combat-chat")){ + ChatUtility.removeMyChatMessageContaining(`
`); + } } /* -------------------------------------------- */ @@ -269,7 +296,7 @@ export class RdDCombat { competence: competence, surprise: this.attacker.getSurprise(), surpriseDefenseur: this.defender.getSurprise(), - tentatives: { chance: false, defense: false } + essais: { } }; if (this.attacker.isCreature()) { @@ -287,73 +314,69 @@ export class RdDCombat { } /* -------------------------------------------- */ - _onAttaqueParticuliere(rollData) { - console.log("RdDCombat.onAttaqueParticuliere >>>", rollData); - // Finesse et Rapidité seulement en mêlée et si la difficulté libre est de -1 minimum - let message = '

Réussite particulière en attaque

'; - message += `
Attaquer en Force`; - if (rollData.selectedCarac.label == "Mêlée" && rollData.diffLibre < 0) { - if (rollData.arme.data.rapide) { - message += `
Attaquer en Rapidité`; - } - message += `
Attaquer en Finesse`; - } + async _onAttaqueParticuliere(rollData) { game.system.rdd.rollDataHandler.attaques[this.attackerId] = duplicate(rollData); - // TODO: use a dialog? - ChatMessage.create({ content: message, whisper: ChatMessage.getWhisperRecipients(this.attacker.name) }); + + // Finesse et Rapidité seulement en mêlée et si la difficulté libre est de -1 minimum + const isMeleeDiffNegative = rollData.selectedCarac.label == "Mêlée" && rollData.diffLibre < 0; + ChatMessage.create({ + whisper: ChatMessage.getWhisperRecipients(this.attacker.name), + content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-demande-defense.html', { + attackerId: this.attackerId, + defenderTokenId: this.defenderTokenId, + isFinesse: isMeleeDiffNegative, + isRapide: isMeleeDiffNegative && rollData.arme.data.rapide + }) + }); } /* -------------------------------------------- */ - async _onAttaqueNormale(rollData) { - console.log("RdDCombat.onAttaqueNormale >>>", rollData); + async _onAttaqueNormale(attackerRoll) { + console.log("RdDCombat.onAttaqueNormale >>>", attackerRoll); - rollData.dmg = RdDBonus.dmg(rollData, this.attacker.getBonusDegat(), this.defender.isEntiteCauchemar()); + attackerRoll.dmg = RdDBonus.dmg(attackerRoll, this.attacker.getBonusDegat(), this.defender.isEntiteCauchemar()); // Save rollData for defender - game.system.rdd.rollDataHandler.attaques[this.attackerId] = duplicate(rollData); + game.system.rdd.rollDataHandler.attaques[this.attackerId] = duplicate(attackerRoll); - rollData.show = { + attackerRoll.show = { cible: this.target ? this.defender.data.name : 'la cible', - isRecul: (rollData.particuliere == 'force' || rollData.tactique == 'charge') + isRecul: (attackerRoll.particuliere == 'force' || attackerRoll.tactique == 'charge') } - await RdDResolutionTable.displayRollData(rollData, this.attacker, 'chat-resultat-attaque.html'); + await RdDResolutionTable.displayRollData(attackerRoll, this.attacker, 'chat-resultat-attaque.html'); if (!await this.accorderEntite('avant-defense')) { return; } if (this.target) { - await this._sendMessageDefense(rollData); + await this._sendMessageDefense(attackerRoll); } } /* -------------------------------------------- */ - async _sendMessageDefense(attackerRoll, tentatives = {}) { - console.log("RdDCombat._sendMessageDefense", attackerRoll, tentatives, " / ", this.attacker, this.target, this.attackerId, attackerRoll.competence.data.categorie); + async _sendMessageDefense(attackerRoll, essais = {}) { + console.log("RdDCombat._sendMessageDefense", attackerRoll, essais, " / ", this.attacker, this.target, this.attackerId, attackerRoll.competence.data.categorie); this.removeChatMessageActionsPasseArme(attackerRoll.passeArme); - - this.addTentatives(attackerRoll, tentatives); - - let message = await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-demande-defense.html`, { + mergeObject(attackerRoll.essais, essais, {overwrite: true}); + const paramDemandeDefense = { passeArme: attackerRoll.passeArme, - tentatives: attackerRoll.tentatives, + essais: attackerRoll.essais, surprise: this.defender.getSurprise(), defender: this.defender, attackerId: this.attackerId, defenderTokenId: this.defenderTokenId, mainsNues: attackerRoll.dmg.mortalite != 'mortel' && this.defender.getCompetence("Corps à corps"), armes: this._filterArmesParade(this.defender.data.items, attackerRoll.competence, attackerRoll.arme), + diffLibre: attackerRoll.ajustements?.diffLibre?.value ?? 0, dmg: attackerRoll.dmg - }); + }; + let message = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-demande-defense.html', paramDemandeDefense); RdDCombat._sendRollMessage(this.attacker, this.defender, this.defenderTokenId, "msg_defense", message, attackerRoll); } - addTentatives(attackerRoll, tentatives) { - mergeObject(attackerRoll.tentatives, tentatives, { overwrite: true }); - } - /* -------------------------------------------- */ _filterArmesParade(items, competence) { items = items.filter(item => (item.type == 'arme' && item.data.equipe) || (item.type == 'competencecreature' && item.data.isparade)); @@ -371,17 +394,33 @@ export class RdDCombat { } /* -------------------------------------------- */ - async _onAttaqueEchecTotal(rollData) { - // TODO: remplacer par un chat message pour laisser le joueur choisir un appel à la chance _avant_ - // https://gitlab.com/LeRatierBretonnien/foundryvtt-reve-de-dragon/-/issues/85 - console.log("RdDCombat.onEchecTotal >>>", rollData); - let chatOptions = { - content: "Echec total à l'attaque! " - + await RdDRollTables.getMaladresse({ arme: rollData.arme && rollData.arme.data.categorie_parade != 'sans-armes' }) - } - ChatUtility.chatWithRollMode(chatOptions, this.attacker.name) + async _onAttaqueEchecTotal(attackerRoll) { + + game.system.rdd.rollDataHandler.attaques[this.attackerId] = duplicate(attackerRoll); + + // Finesse et Rapidité seulement en mêlée et si la difficulté libre est de -1 minimum + ChatMessage.create({ + whisper: ChatMessage.getWhisperRecipients(this.attacker.name), + content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-demande-attaque-etotal.html', { + attackerId: this.attackerId, + attacker: this.attacker, + defenderTokenId: this.defenderTokenId, + essais: attackerRoll.essais + }) + }); } + /* -------------------------------------------- */ + async _onEchecTotal(rollData) { + console.log("RdDCombat._onEchecTotal >>>", rollData); + + const arme = rollData.arme; + const avecArme = arme?.data.categorie_parade != 'sans-armes'; + const action = (rollData.attackerRoll ? (arme ? "la parade" : "l'esquive") : "l'attaque"); + ChatUtility.chatWithRollMode({ + content: `Echec total à ${action}! ` + await RdDRollTables.getMaladresse({ arme: avecArme }) + }, this.defender.name) + } /* -------------------------------------------- */ async _onAttaqueEchec(rollData) { @@ -393,6 +432,7 @@ export class RdDCombat { /* -------------------------------------------- */ async choixParticuliere(rollData, choix) { console.log("RdDCombat.choixParticuliere >>>", rollData, choix); + // TODO rollData.particuliere = choix; await this._onAttaqueNormale(rollData); } @@ -418,7 +458,6 @@ export class RdDCombat { { condition: RdDCombat.isReussite, action: r => this._onParadeNormale(r) }, { condition: RdDCombat.isParticuliere, action: r => this._onParadeParticuliere(r) }, { condition: RdDCombat.isEchec, action: r => this._onParadeEchec(r) }, - { condition: RdDCombat.isEchecTotal, action: r => this._onParadeEchecTotal(r) }, ] }); dialog.render(true); @@ -473,33 +512,22 @@ export class RdDCombat { async _onParadeNormale(rollData) { console.log("RdDCombat._onParadeNormale >>>", rollData); + this._consumeDefense(rollData.passeArme); await this.computeRecul(rollData); await this.computeDeteriorationArme(rollData); await RdDResolutionTable.displayRollData(rollData, this.defender, 'chat-resultat-parade.html'); } - /* -------------------------------------------- */ - async _onParadeEchecTotal(rollData) { - // TODO: remplacer par un chat message pour laisser le joueur choisir un appel à la chance _avant_ - // https://gitlab.com/LeRatierBretonnien/foundryvtt-reve-de-dragon/-/issues/85 - console.log("RdDCombat._onParadeEchecTotal >>>", rollData); - let chatOptions = { - content: "Echec total à la parade! " - + await RdDRollTables.getMaladresse({ arme: rollData.arme && rollData.arme.data.categorie_parade != 'sans-armes' }) - } - ChatUtility.chatWithRollMode(chatOptions, this.defender.name) - } - /* -------------------------------------------- */ async _onParadeEchec(rollData) { console.log("RdDCombat._onParadeEchec >>>", rollData); await RdDResolutionTable.displayRollData(rollData, this.defender, 'chat-resultat-parade.html'); - this._storeDefense(rollData); this.removeChatMessageActionsPasseArme(rollData.passeArme); this._sendMessageDefense(rollData.attackerRoll, { defense: true }); + this._storeDefense(rollData); } /* -------------------------------------------- */ @@ -522,7 +550,6 @@ export class RdDCombat { { condition: RdDCombat.isReussite, action: r => this._onEsquiveNormale(r) }, { condition: RdDCombat.isParticuliere, action: r => this._onEsquiveParticuliere(r) }, { condition: RdDCombat.isEchec, action: r => this._onEsquiveEchec(r) }, - { condition: RdDCombat.isEchecTotal, action: r => this._onEsquiveEchecTotal(r) }, ] }); dialog.render(true); @@ -561,30 +588,19 @@ export class RdDCombat { /* -------------------------------------------- */ async _onEsquiveNormale(rollData) { console.log("RdDCombat._onEsquiveNormal >>>", rollData); - + this._consumeDefense(rollData.passeArme); await RdDResolutionTable.displayRollData(rollData, this.defender, 'chat-resultat-esquive.html'); } - /* -------------------------------------------- */ - async _onEsquiveEchecTotal(rollData) { - // TODO: remplacer par un chat message pour laisser le joueur choisir un appel à la chance _avant_ - // https://gitlab.com/LeRatierBretonnien/foundryvtt-reve-de-dragon/-/issues/85 - console.log("RdDCombat._onEsquiveEchecTotal >>>", rollData); - let chatOptions = { - content: "Echec total à l'esquive! " - + await RdDRollTables.getMaladresse({ arme: false }) - } - ChatUtility.chatWithRollMode(chatOptions, this.defender.name) - } /* -------------------------------------------- */ async _onEsquiveEchec(rollData) { console.log("RdDCombat._onEsquiveEchec >>>", rollData); await RdDResolutionTable.displayRollData(rollData, this.defender, 'chat-resultat-esquive.html'); - this._storeDefense(rollData); this.removeChatMessageActionsPasseArme(rollData.passeArme); this._sendMessageDefense(rollData.attackerRoll, { defense: true }) + this._storeDefense(rollData); } /* -------------------------------------------- */ @@ -679,11 +695,16 @@ export class RdDCombat { defenderTokenId = defenderTokenId || this.defenderTokenId; console.log("RdDCombat.encaisser >>>", attackerRoll, defenderTokenId); + let defenderRoll = this._consumeDefense(attackerRoll.passeArme); + if (defenderRoll && RdDCombat.isEchecTotal(defenderRoll)) { + // TODO: echec total!!! + this._onEchecTotal(defenderRoll); + } + if (game.user.isGM) { // Current user is the GM -> direct access attackerRoll.attackerId = this.attackerId; attackerRoll.defenderTokenId = defenderTokenId; - let defenderRoll = this._consumeDefense(attackerRoll.passeArme); await this.computeRecul(defenderRoll); this.defender.encaisserDommages(attackerRoll, this.attacker); } else { // Emit message for GM diff --git a/module/rdd-main.js b/module/rdd-main.js index 6c9e7a44..95237c2d 100644 --- a/module/rdd-main.js +++ b/module/rdd-main.js @@ -161,6 +161,16 @@ Hooks.once("init", async function() { type: Boolean }); + /* -------------------------------------------- */ + game.settings.register("foundryvtt-reve-de-dragon", "supprimer-dialogues-combat-chat", { + name: "Supprimer les dialogues de combat", + hint: "Si désactivée, tous les dialogues de combat sont conservés dans la conversation", + scope: "world", + config: true, + default: true, + type: Boolean + }); + //game.settings.get("","") to retrieve it and game.settings.set("","", ) /* -------------------------------------------- */ diff --git a/module/rdd-utility.js b/module/rdd-utility.js index e80e4adb..bdc45ae2 100644 --- a/module/rdd-utility.js +++ b/module/rdd-utility.js @@ -205,6 +205,8 @@ export class RdDUtility { // messages tchat 'systems/foundryvtt-reve-de-dragon/templates/chat-infojet.html', 'systems/foundryvtt-reve-de-dragon/templates/chat-demande-defense.html', + 'systems/foundryvtt-reve-de-dragon/templates/chat-demande-attaque-particuliere.html', + 'systems/foundryvtt-reve-de-dragon/templates/chat-demande-attaque-etotal.html', 'systems/foundryvtt-reve-de-dragon/templates/chat-resultat-appelchance.html', 'systems/foundryvtt-reve-de-dragon/templates/chat-resultat-attaque.html', 'systems/foundryvtt-reve-de-dragon/templates/chat-resultat-parade.html', diff --git a/templates/chat-demande-attaque-etotal.html b/templates/chat-demande-attaque-etotal.html new file mode 100644 index 00000000..004af0d4 --- /dev/null +++ b/templates/chat-demande-attaque-etotal.html @@ -0,0 +1,22 @@ +
+

Echec total en attaque

+
+ {{#if (eq attacker.data.type 'personnage')}} + {{#unless essais.attaqueChance}} + Faire appel à la chance + +
+ {{/unless}} + {{#if (gt attacker.data.data.compteurs.destinee.value 0)}} + Utiliser la destinée + +
+ {{/if}} + {{/if}} + + Tirer l'échec total ! + +
\ No newline at end of file diff --git a/templates/chat-demande-attaque-particuliere.html b/templates/chat-demande-attaque-particuliere.html new file mode 100644 index 00000000..acca2da3 --- /dev/null +++ b/templates/chat-demande-attaque-particuliere.html @@ -0,0 +1,19 @@ +
+

Réussite particulière en attaque

+
+ + Attaquer en Force + + {{#if isRapide}} +
+ + Attaquer en Rapidité + + {{/if}} + {{#if isFinesse}} +
+ + Attaquer en Finesse + + {{/if}} +
\ No newline at end of file diff --git a/templates/chat-demande-defense.html b/templates/chat-demande-defense.html index 39958ef3..2fdfdd8d 100644 --- a/templates/chat-demande-defense.html +++ b/templates/chat-demande-defense.html @@ -1,7 +1,7 @@
{{#if (eq surprise 'totale')}} {{defender.name}} est totalement surpris - {{else if tentatives.defense}} + {{else if essais.defense}} {{defender.name}} doit {{else}} {{defender.name}} doit se défendre @@ -11,9 +11,9 @@
{{#unless (eq surprise 'totale')}} - {{#if tentatives.defense}} + {{#if essais.defense}}
- {{#unless tentatives.chance}} + {{#unless essais.defenseChance}} {{#if (eq defender.data.type 'personnage')}} Faire appel à la chance @@ -31,22 +31,20 @@ {{/if}} {{else}} {{#each armes as |arme key|}} - Parer avec {{arme.name}} + + Parer avec {{arme.name}} à {{../diffLibre }}
{{/each}} {{#if mainsNues}} - - Parer à mains nues + + Parer à mains nues à {{diffLibre}}
{{/if}} {{#if (ne attaqueCategorie 'tir')}} - - Esquiver + + Esquiver à {{diffLibre}}
{{/if}}