diff --git a/module/actor.js b/module/actor.js index d28bb0e2..17f3432e 100644 --- a/module/actor.js +++ b/module/actor.js @@ -1107,68 +1107,6 @@ export class RdDActor extends Actor { await this.update( { 'data.blessures': bList } ); } - /* -------------------------------------------- */ - manageBlessures( blessuresData ) - { - // Fast exit - if ( this.data.type == 'entite') return; // Une entité n'a pas de blessures - if ( blessuresData.legeres + blessuresData.graves + blessuresData.critiques == 0 ) return; - - let workData = duplicate(blessuresData); - let blessures = duplicate(this.data.data.blessures); - // Manage blessures - if ( workData.legeres > 0 ) { - for (let k=0; k 0 ) { - workData.graves += 1; - blessuresData.graves += 1; - } - - if ( workData.graves > 0) { - for (let k=0; k 0 ) { - workData.critiques = 1; - blessuresData.critiques = 1; - workData.from3Graves = true; - } - - if ( workData.critiques > 0 ) { - if ( blessures.critiques.liste[0].active ) { - ChatMessage.create(`${game.user.name} vient de succomber à une seconde blessure critique ! Que les Dragons gardent son Archétype en paix !`); - } else { - if ( workData.from3Graves) { // Si la blessure critique provient d'une quatrième grave - this.santeIncDec("endurance", -this.data.data.sante.endurance.value); // Endurance à 0; - this.santeIncDec("vie", -4); // Vie à -4; - } - blessures.critiques.liste[0].active = true; - blessures.critiques.liste[0].loc = workData.locName; - } - } - - this.update( { "data.blessures": blessures } ); - } - /* -------------------------------------------- */ async jetDeMoral(situation) { let jetMoral = new Roll("1d20").roll(); @@ -1820,7 +1758,7 @@ export class RdDActor extends Actor { } /* -------------------------------------------- */ - async rollAppelChance( ) + async rollAppelChance(onSuccess = () => {}, onEchec = () => {}) { let rollData = { selectedCarac: this.getCaracByName('chance-actuelle'), surprise: '' }; const dialog = await RdDRoll.create(this, rollData, @@ -1830,19 +1768,23 @@ export class RdDActor extends Actor { label: 'Appel à la chance', callbacks: [ this.createCallbackExperience(), - { action: r => this._appelChanceResult(r) } + { 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(); + } } /* -------------------------------------------- */ @@ -1851,6 +1793,21 @@ export class RdDActor extends Actor { chance.value = Math.max(chance.value + value, 0); await this.update( {"data.compteurs.chance": chance } ); } + + /* -------------------------------------------- */ + async appelDestinee( onSuccess = () => {}, onEchec= ()=>{}) { + if (this.data.data.compteurs.destinee?.value ?? 0 >0 ) { + ChatMessage.create({content: `${this.name} a fait appel à la Destinée !` }); + let destinee = duplicate(this.data.data.compteurs.destinee); + destinee.value = destinee.value - 1; + await this.update( {"data.compteurs.destinee": destinee } ); + onSuccess(); + } + else { + onEchec(); + } + } + /* -------------------------------------------- */ ajustementAstrologique() { @@ -2086,7 +2043,7 @@ export class RdDActor extends Actor { dmg = 0; // Reset it if ( update.data.deterioration >= 10) { update.data.deterioration = 0; - let res = /\d+^/.exec(update.data.protection); + let res = /\d+/.exec(update.data.protection); if ( res ) update.data.protection = "1d"+update.data.protection; // if ( update.data.protection.toString().length == 1 ) @@ -2132,28 +2089,24 @@ export class RdDActor extends Actor { } console.log("encaisserDommages", attackerRoll ) - const armure = this.computeArmure( attackerRoll ); - const rollEncaissement = new Roll("2d10 + @dmg - @armure",{ - dmg: attackerRoll.dmg.total, - armure: armure - }).roll(); - RdDDice.show(rollEncaissement); - let result = RdDUtility.computeBlessuresSante(rollEncaissement.total, attackerRoll.dmg.mortalite, attackerRoll.dmg.loc); - result.endurance = Math.max(result.endurance, -Number(this.data.data.sante.endurance.value)); - await this.santeIncDec("vie", result.vie); - await this.santeIncDec("endurance", result.endurance, (result.critiques > 0)); + let encaissement = this.jetEncaissement(attackerRoll); + + this.ajouterBlessure(encaissement); // Will upate the result table + + await this.santeIncDec("vie", encaissement.vie); + await this.santeIncDec("endurance", encaissement.endurance, (encaissement.critiques > 0)); + + const blessureLegere = (encaissement.legeres > 0 ? "une blessure légère" : ""); + const blessureGrave = (encaissement.graves > 0 ? "une blessure grave" : ""); + const blessureCritique = (encaissement.critiques > 0 ? "une blessure critique" : ""); - this.manageBlessures(result); // Will upate the result table - const blessureLegere = (result.legeres > 0 ? "une blessure légère" : ""); - const blessureGrave = (result.graves > 0 ? "une blessure grave" : ""); - const blessureCritique = (result.critiques > 0 ? "une blessure critique" : ""); let commonMsg = { title: "Blessures !", - roll: rollEncaissement , - content: this.data.name + " a encaissé " + blessureLegere + blessureGrave + blessureCritique + roll: encaissement.roll, + content: `${this.data.name} a encaissé ${blessureLegere}${blessureGrave}${blessureCritique} (${encaissement.locName})` } - let addonMsg = "
Et a perdu :
" + result.endurance + " Endurance et " + result.vie + " Points de Vie"; + let addonMsg = "
Et a perdu :
" + encaissement.endurance + " Endurance et " + encaissement.vie + " Points de Vie"; if ( this.hasPlayerOwner ) { commonMsg.content += addonMsg; // Message pour tout le monde ChatMessage.create( commonMsg ); @@ -2169,6 +2122,92 @@ export class RdDActor extends Actor { this.sheet.render(false); } + /* -------------------------------------------- */ + jetEncaissement(rollData) { + + rollData.dmg.loc = rollData.dmg.loc?? RdDUtility.getLocalisation(); + + const roll = new Roll("2d10 + @dmg - @armure", { + dmg: rollData.dmg.total, + armure: this.computeArmure( rollData ) + }).roll(); + RdDDice.show(roll, game.settings.get("core", "rollMode")); + + let encaissement = RdDUtility.selectEncaissement(roll.total, rollData.dmg.mortalite) + let over20 = Math.max(roll.total - 20, 0); + encaissement.roll = roll; + encaissement.vie = - RdDUtility._evaluatePerte(encaissement.vie, over20); + encaissement.endurance = - RdDUtility._evaluatePerte(encaissement.endurance, over20); + encaissement.loc = rollData.dmg.loc; + encaissement.locName = rollData.dmg.loc.label ?? "Corps"; + return encaissement; + } + + /* -------------------------------------------- */ + ajouterBlessure( encaissement ) + { + // Fast exit + if ( this.data.type == 'entite') return; // Une entité n'a pas de blessures + if ( encaissement.legeres + encaissement.graves + encaissement.critiques == 0 ) return; + + const endActuelle = Number(this.data.data.sante.endurance.value); + let blessures = duplicate(this.data.data.blessures); + + let count = encaissement.legeres; + // Manage blessures + while (count > 0) { + let legere = blessures.legeres.liste.find(it => !it.active); + if (legere) { + this._setBlessure(legere, encaissement); + count--; + } + else { + encaissement.graves += count; + encaissement.legeres -= count; + break; + } + } + + count = encaissement.graves; + while (count > 0) { + let grave = blessures.graves.liste.find(it => !it.active); + if (grave) { + this._setBlessure(grave, encaissement); + count--; + } + else{ + encaissement.critiques += count; + encaissement.graves -= count; + encaissement.endurance = -endActuelle; + encaissement.vie = -4; + break; + } + } + + count = encaissement.critique; + while( count > 0 ) { + let critique = blessures.critiques.liste[0]; + if (!critique.active ) { + this._setBlessure(critique, encaissement); + count--; + } else { + // TODO: status effect dead + ChatMessage.create({ content: `${this.name} vient de succomber à une seconde blessure critique ! Que les Dragons gardent son Archétype en paix !`}); + encaissement.critique -= count; + break; + } + } + + encaissement.endurance = Math.max(encaissement.endurance, -endActuelle); + this.update( { "data.blessures": blessures } ); + } + + /* -------------------------------------------- */ + _setBlessure(blessure, encaissement) { + blessure.active = true; + blessure.loc = encaissement.locName; + } + /* -------------------------------------------- */ /** @override */ getRollData() { diff --git a/module/chat-utility.js b/module/chat-utility.js index e1bd0728..6535d1f1 100644 --- a/module/chat-utility.js +++ b/module/chat-utility.js @@ -3,6 +3,12 @@ * Class providing helper methods to get the list of users, and */ 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)); + toDelete.forEach(it => it.delete()); + } + /* -------------------------------------------- */ static chatWithRollMode(chatOptions, name) { @@ -11,7 +17,7 @@ export class ChatUtility { } /* -------------------------------------------- */ - static createChatMessage( chatOptions, rollMode, name) { + static createChatMessage(chatOptions, rollMode, name) { switch (rollMode) { case "blindroll": // GM only if (!game.user.isGM) { @@ -28,12 +34,12 @@ export class ChatUtility { chatOptions.whisper = ChatUtility.getWhisperRecipients(rollMode, name); break; } - chatOptions.alias = chatOptions.alias||name; + chatOptions.alias = chatOptions.alias || name; ChatMessage.create(chatOptions); } /* -------------------------------------------- */ - static prepareChatMessage( rollMode, name) { + static prepareChatMessage(rollMode, name) { return { user: game.user._id, whisper: ChatUtility.getWhisperRecipients(rollMode, name) @@ -41,7 +47,7 @@ export class ChatUtility { } /* -------------------------------------------- */ - static getWhisperRecipients( rollMode, name) { + static getWhisperRecipients(rollMode, name) { switch (rollMode) { case "blindroll": return ChatUtility.getUsers(user => user.isGM); case "gmroll": return ChatUtility.getWhisperRecipientsAndGMs(name); diff --git a/module/rdd-bonus.js b/module/rdd-bonus.js index e2c5d622..3a8806fd 100644 --- a/module/rdd-bonus.js +++ b/module/rdd-bonus.js @@ -1,5 +1,4 @@ import { RdDCarac } from "./rdd-carac.js"; -import { RdDUtility } from "./rdd-utility.js"; const conditionsTactiques = [ { type: '', descr: '', dmg: 0, attaque: 0, parade: 0, esquive: true }, @@ -18,7 +17,7 @@ export class RdDBonus { return conditionsTactiques.find(e => e.type == condition) || conditionsTactiques.find(e => e.type == 'pret'); } - + static isAjustementAstrologique(rollData) { return RdDCarac.isChance(rollData.selectedCarac) || rollData.selectedSort?.data.isrituel; @@ -27,10 +26,10 @@ export class RdDBonus { static isDefenseAttaqueFinesse(rollData) { return rollData.attackerRoll?.particuliere == 'finesse'; } - + /* -------------------------------------------- */ static dmg(rollData, dmgActor, isCauchemar = false) { - let dmg = { total: 0, loc: RdDUtility.getLocalisation() }; + let dmg = { total: 0 }; if (rollData.arme && rollData.arme.name.toLowerCase() == "esquive") { // Specific case management ui.notifications.warn("Calcul de bonus dégats sur eswquive"); @@ -55,26 +54,24 @@ export class RdDBonus { /* -------------------------------------------- */ static dmgBonus(condition) { return RdDBonus.find(condition).dmg; - } - + } + /* -------------------------------------------- */ static bonusAttaque(condition) { return RdDBonus.find(condition).attaque; - } + } /* -------------------------------------------- */ static _calculMortalite(rollData, isCauchemar) { - if (isCauchemar){ + if (isCauchemar) { return "cauchemar"; - }if (rollData.dmg && rollData.dmg.mortalite) { - return rollData.dmg.mortalite; } - if (rollData.arme && rollData.arme.data.mortalite) { - return rollData.arme.data.mortalite; - } - return "mortel"; + return isCauchemar ? "cauchemar" + : rollData.dmg?.mortalite + ?? rollData.arme?.data.mortalite + ?? "mortel"; } - + /* -------------------------------------------- */ static _dmgArme(rollData) { return parseInt(rollData.arme?.data.dommages ?? 0); @@ -83,7 +80,7 @@ export class RdDBonus { static _peneration(rollData) { return parseInt(rollData.arme?.data.penetration ?? 0); } - + /* -------------------------------------------- */ static _dmgPerso(dmgActor, categorie, dmgArme) { switch (categorie) { @@ -92,7 +89,7 @@ export class RdDBonus { } return dmgActor; } - + /* -------------------------------------------- */ static _dmgParticuliere(rollData) { return rollData.particuliere == 'force' ? 5 : 0; diff --git a/module/rdd-combat.js b/module/rdd-combat.js index e852c575..9616299a 100644 --- a/module/rdd-combat.js +++ b/module/rdd-combat.js @@ -94,7 +94,17 @@ export class RdDCombat { /* -------------------------------------------- */ static registerChatCallbacks(html) { - for (let button of ['#parer-button', '#esquiver-button', '#particuliere-attaque', '#encaisser-button']) { + for (let button of [ + '#parer-button', + '#esquiver-button', + '#particuliere-attaque', + '#encaisser-button', + '#appel-chance-defense', + '#appel-destinee-defense', + '#appel-chance-attaque', + '#appel-destinee-attaque', + '#echec-total-attaque', + ]) { html.on("click", button, event => { event.preventDefault(); RdDCombat.createForEvent(event).onEvent(button, event); @@ -119,19 +129,102 @@ export class RdDCombat { /* -------------------------------------------- */ async onEvent(button, event) { - let rollData = game.system.rdd.rollDataHandler[this.attackerId]; - if (!rollData) { + let attackerRoll = game.system.rdd.rollDataHandler.attaques[this.attackerId]; + if (!attackerRoll) { ui.notifications.warn("Action automatisée impossible, le jet de l'attaquant a été perdu (suite à un raffraichissement?)") return; } + const defenderTokenId = event.currentTarget.attributes['data-defenderTokenId'].value; + + let defenderRoll = this._getDefense(attackerRoll.passeArme); + const armeParadeId = event.currentTarget.attributes['data-armeid']?.value; + switch (button) { - case '#particuliere-attaque': return await this.choixParticuliere(rollData, event.currentTarget.attributes['data-mode'].value); - case '#parer-button': { - const armeId = event.currentTarget.attributes['data-armeid']; - return this.parade(rollData, armeId?.value); - } - case '#esquiver-button': return this.esquive(rollData); - case '#encaisser-button': return this.encaisser(rollData, event.currentTarget.attributes['data-defenderTokenId'].value); + case '#particuliere-attaque': return await this.choixParticuliere(attackerRoll, event.currentTarget.attributes['data-mode'].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.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 = 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; + } + + /* -------------------------------------------- */ + 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") + attackerRoll.essais.defenseChance = true; + attackerRoll.essais.defense = false; + this.removeChatMessageActionsPasseArme(attackerRoll.passeArme); + this._sendMessageDefense(attackerRoll); + } + + /* -------------------------------------------- */ + defenseDestinee(defenderRoll) { + ui.notifications.info('Défense significative grâce à la destinée') + RdDResolutionTable.forceSignificative(defenderRoll.rolled); + this.removeChatMessageActionsPasseArme(defenderRoll.passeArme); + if (defenderRoll.arme) { + this._onParadeNormale(defenderRoll); + } + else { + this._onEsquiveNormale(defenderRoll); + } + } + + /* -------------------------------------------- */ + afficherOptionsDefense(attackerRoll, essais) { + ui.notifications.info("La chance n'est pas avec vous"); + this._sendMessageDefense(attackerRoll, essais); + } + + /* -------------------------------------------- */ + removeChatMessageActionsPasseArme(passeArme) { + if (game.settings.get("foundryvtt-reve-de-dragon", "supprimer-dialogues-combat-chat")){ + ChatUtility.removeMyChatMessageContaining(`
`); } } @@ -185,6 +278,7 @@ export class RdDCombat { label: 'Attaque: ' + (arme?.name ?? competence.name), callbacks: [ this.attacker.createCallbackExperience(), + { action: r => this.removeChatMessageActionsPasseArme(r.passeArme) }, { condition: r => (RdDCombat.isReussite(r) && !RdDCombat.isParticuliere(r)), action: r => this._onAttaqueNormale(r) }, { condition: RdDCombat.isParticuliere, action: r => this._onAttaqueParticuliere(r) }, { condition: RdDCombat.isEchec, action: r => this._onAttaqueEchec(r) }, @@ -197,10 +291,12 @@ export class RdDCombat { /* -------------------------------------------- */ _prepareAttaque(competence, arme) { let rollData = { + passeArme: randomID(16), coupsNonMortels: false, competence: competence, surprise: this.attacker.getSurprise(), - surpriseDefenseur: this.defender.getSurprise() + surpriseDefenseur: this.defender.getSurprise(), + essais: { } }; if (this.attacker.isCreature()) { @@ -218,81 +314,67 @@ export class RdDCombat { } /* -------------------------------------------- */ - _onAttaqueParticuliere(rollData) { - console.log("RdDCombat.onAttaqueParticuliere >>>", rollData); + async _onAttaqueParticuliere(rollData) { + game.system.rdd.rollDataHandler.attaques[this.attackerId] = duplicate(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`; - } - game.system.rdd.rollDataHandler[this.attackerId] = rollData; - // TODO: use a dialog? - ChatMessage.create({ content: message, whisper: ChatMessage.getWhisperRecipients(this.attacker.name) }); + 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[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) { - this._sendMessageDefense(rollData); + await this._sendMessageDefense(attackerRoll); } } /* -------------------------------------------- */ - _sendMessageDefense(rollData) { - console.log("RdDCombat._sendMessageDefense", rollData, " / ", this.attacker, this.target, this.attackerId, rollData.competence.data.categorie); + async _sendMessageDefense(attackerRoll, essais = {}) { + console.log("RdDCombat._sendMessageDefense", attackerRoll, essais, " / ", this.attacker, this.target, this.attackerId, attackerRoll.competence.data.categorie); - let message = this._buildMessageDefense(rollData); - // encaisser - message += this._buildMessageEncaisser(rollData) + ""; + this.removeChatMessageActionsPasseArme(attackerRoll.passeArme); + mergeObject(attackerRoll.essais, essais, {overwrite: true}); + const paramDemandeDefense = { + passeArme: attackerRoll.passeArme, + 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, rollData); - } - - /* -------------------------------------------- */ - _buildMessageDefense(rollData) { - let message = "" + this.defender.name + " doit se défendre :"; - - if (this.defender.getSurprise() != 'totale') { - // parades - for (const arme of this._filterArmesParade(this.defender.data.items, rollData.competence, rollData.arme)) { - message += "
Parer avec " + arme.name + ""; - } - // corps à corps - if (rollData.dmg.mortalite != 'mortel' && this.defender.getCompetence("Corps à corps")) { - message += "
Parer à mains nues"; - } - // esquive - if (rollData.competence.data.categorie != 'tir') { - message += "
Esquiver"; - } - } - return message; - } - - /* -------------------------------------------- */ - _buildMessageEncaisser(rollData) { - return "
Encaisser à " + Misc.toSignedString(rollData.dmg.total) + " !"; + RdDCombat._sendRollMessage(this.attacker, this.defender, this.defenderTokenId, "msg_defense", message, attackerRoll); } /* -------------------------------------------- */ @@ -312,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) { @@ -334,6 +432,7 @@ export class RdDCombat { /* -------------------------------------------- */ async choixParticuliere(rollData, choix) { console.log("RdDCombat.choixParticuliere >>>", rollData, choix); + // TODO rollData.particuliere = choix; await this._onAttaqueNormale(rollData); } @@ -355,10 +454,10 @@ export class RdDCombat { label: 'Parade: ' + (arme ? arme.name : rollData.competence.name), callbacks: [ this.defender.createCallbackExperience(), + { action: r => this.removeChatMessageActionsPasseArme(r.passeArme) }, { 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); @@ -370,6 +469,7 @@ export class RdDCombat { const armeAttaque = attackerRoll.arme; let rollData = { + passeArme: attackerRoll.passeArme, forceValue: this.defender.getForceValue(), diffLibre: attackerRoll.diffLibre, attackerRoll: attackerRoll, @@ -403,7 +503,7 @@ export class RdDCombat { if (!rollData.attackerRoll.isPart) { // TODO: attaquant doit jouer résistance et peut être désarmé p132 ChatUtility.chatWithRollMode({ - content: `L'attaquant doit jouer résistance et peut être désarmé (p132)` + content: `(à gérer) L'attaquant doit jouer résistance et peut être désarmé (p132)` }, this.defender.name) } } @@ -412,34 +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 this.computeRecul(rollData); - await RdDResolutionTable.displayRollData(rollData, this.defender, 'chat-resultat-parade.html'); - this._sendMessageEncaisser(rollData.attackerRoll); + this.removeChatMessageActionsPasseArme(rollData.passeArme); + this._sendMessageDefense(rollData.attackerRoll, { defense: true }); + this._storeDefense(rollData); } /* -------------------------------------------- */ @@ -458,10 +546,10 @@ export class RdDCombat { label: 'Esquiver', callbacks: [ this.defender.createCallbackExperience(), + { action: r => this.removeChatMessageActionsPasseArme(r.passeArme) }, { 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); @@ -470,6 +558,7 @@ export class RdDCombat { /* -------------------------------------------- */ _prepareEsquive(attackerRoll, competence) { let rollData = { + passeArme: attackerRoll.passeArme, forceValue: this.defender.getForceValue(), diffLibre: attackerRoll.diffLibre, attackerRoll: attackerRoll, @@ -499,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 this.computeRecul(rollData); - await RdDResolutionTable.displayRollData(rollData, this.defender, 'chat-resultat-esquive.html'); - this._sendMessageEncaisser(rollData.attackerRoll); + this.removeChatMessageActionsPasseArme(rollData.passeArme); + this._sendMessageDefense(rollData.attackerRoll, { defense: true }) + this._storeDefense(rollData); } /* -------------------------------------------- */ @@ -613,13 +691,21 @@ export class RdDCombat { } /* -------------------------------------------- */ - encaisser(attackerRoll, defenderTokenId) { + async encaisser(attackerRoll, defenderTokenId) { 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; + + await this.computeRecul(defenderRoll); this.defender.encaisserDommages(attackerRoll, this.attacker); } else { // Emit message for GM game.socket.emit("system.foundryvtt-reve-de-dragon", { diff --git a/module/rdd-main.js b/module/rdd-main.js index 2cf83ed1..95237c2d 100644 --- a/module/rdd-main.js +++ b/module/rdd-main.js @@ -21,6 +21,7 @@ import { RdDResolutionTable } from "./rdd-resolution-table.js"; import { RdDTokenHud } from "./rdd-token-hud.js"; import { RdDCommands } from "./rdd-commands.js"; import { RdDCombat } from "./rdd-combat.js"; +import { ChatUtility } from "./chat-utility.js"; /* -------------------------------------------- */ /* Foundry VTT Initialization */ @@ -104,9 +105,12 @@ Hooks.once("init", async function() { // Create useful storage space game.system.rdd = { - rollDataHandler: {}, + rollDataHandler: { + attaques: {}, + defenses: {} + }, TMRUtility: TMRUtility - } + } /* -------------------------------------------- */ game.settings.register("foundryvtt-reve-de-dragon", "accorder-entite-cauchemar", { @@ -157,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("","", ) /* -------------------------------------------- */ @@ -213,9 +227,7 @@ Hooks.once("init", async function() { /* -------------------------------------------- */ function messageDeBienvenue(){ - game.messages - .filter(it => it.user._id == game.user._id && it.data.content.match(/^
'); ChatMessage.create( { user: game.user._id, whisper: [game.user._id], diff --git a/module/rdd-resolution-table.js b/module/rdd-resolution-table.js index 17bf951a..83dff5b7 100644 --- a/module/rdd-resolution-table.js +++ b/module/rdd-resolution-table.js @@ -1,5 +1,4 @@ import { ChatUtility } from "./chat-utility.js"; -import { RollDataAjustements as RollDataAjustements } from "./rolldata-ajustements.js"; import { Misc } from "./misc.js"; import { RdDDice } from "./rdd-dice.js"; @@ -127,7 +126,7 @@ export class RdDResolutionTable { /* -------------------------------------------- */ static _updateChancesFactor(chances, diviseur) { if (diviseur && diviseur > 1) { - let newScore = Math.floor(Number(chances.score) / diviseur); + let newScore = Math.floor(chances.score / diviseur); mergeObject(chances, this._computeCell(null, newScore), { overwrite: true }); } } @@ -135,10 +134,14 @@ export class RdDResolutionTable { /* -------------------------------------------- */ static _updateChancesWithBonus(chances, bonus) { if (bonus) { - let newScore = Number(chances.score) + Number(bonus); + let newScore = chances.score + bonus; mergeObject(chances, this._computeCell(null, newScore), { overwrite: true }); } } + static forceSignificative(chances) { + chances.roll = Math.floor(chances.score /2); + mergeObject(chances, reussites.find(x => x.code == 'sign'), { overwrite: true }); + } /* -------------------------------------------- */ static async rollChances(chances) { @@ -146,7 +149,7 @@ export class RdDResolutionTable { myRoll.showDice = chances.showDice; await RdDDice.show(myRoll); chances.roll = myRoll.total; - mergeObject(chances, this._computeReussite(chances, chances.roll)); + mergeObject(chances, this._computeReussite(chances, chances.roll), { overwrite: true }); return chances; } diff --git a/module/rdd-roll.js b/module/rdd-roll.js index 66277ce4..2de31f6a 100644 --- a/module/rdd-roll.js +++ b/module/rdd-roll.js @@ -1,4 +1,4 @@ -import { referenceAjustements, RollDataAjustements } from "./rolldata-ajustements.js"; +import { RollDataAjustements } from "./rolldata-ajustements.js"; import { HtmlUtility } from "./html-utility.js"; import { RdDItemCompetence } from "./item-competence.js"; import { RdDItemMeditation } from "./item-meditation.js"; diff --git a/module/rdd-utility.js b/module/rdd-utility.js index d277ee83..bdc45ae2 100644 --- a/module/rdd-utility.js +++ b/module/rdd-utility.js @@ -2,10 +2,8 @@ import { RdDRollTables } from "./rdd-rolltables.js"; import { ChatUtility } from "./chat-utility.js"; -import { RdDItemCompetence } from "./item-competence.js"; import { RdDCombat } from "./rdd-combat.js"; import { RdDRollResolutionTable } from "./rdd-roll-resolution-table.js"; -import { RdDItem } from "./item.js"; import { RdDItemCompetenceCreature } from "./item-competencecreature.js"; import { RdDItemArme } from "./item-arme.js"; @@ -206,6 +204,9 @@ export class RdDUtility { 'systems/foundryvtt-reve-de-dragon/templates/hud-actor-attaque.html', // 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', @@ -626,6 +627,7 @@ export class RdDUtility { /* -------------------------------------------- */ static getLocalisation( ) { + // TODO: bouger dans une RollTable du compendium et chercher dans les RoolTable puis compendium pour permettre le changement? let result = new Roll("1d20").roll().total; let txt = "" if ( result <= 3 ) txt = "Jambe, genou, pied, jarret"; @@ -640,16 +642,6 @@ export class RdDUtility { return { result: result, label: txt }; } - /* -------------------------------------------- */ - static computeBlessuresSante( degats, mortalite, loc) { - let encaissement = RdDUtility.selectEncaissement(degats, mortalite) - let over20 = Math.max(degats - 20, 0); - encaissement.endurance = - RdDUtility._evaluatePerte(encaissement.endurance, over20); - encaissement.vie = - RdDUtility._evaluatePerte(encaissement.vie, over20); - encaissement.locName = loc ? loc.label : "Corps"; - return encaissement; - } - /* -------------------------------------------- */ static selectEncaissement( degats, mortalite ) { const table = definitionsEncaissement[mortalite] === undefined ? definitionsEncaissement["mortel"] : definitionsEncaissement[mortalite]; @@ -703,60 +695,6 @@ export class RdDUtility { return competences; } - /* -------------------------------------------- */ - static buildDefenseChatCard( attacker, target, rollData ) - { - console.log("Attacker.defense", attacker, target, target.actor.isToken, attacker.data._id, rollData.competence.data.categorie ); - let myTarget = target.actor; - let defenseMsg = { title: "Défense en combat", - content: ""+myTarget.name+" doit se défendre :
" + - "Encaisser !", - whisper: ChatMessage.getWhisperRecipients( myTarget.name ), - attackerId: attacker.data._id, - defenderTokenId: target.data._id, - rollMode: true - }; - - if ( rollData.competence.data.categorie == 'melee' || rollData.competence.data.categorie == 'competencecreature') { // Melee attack or creature - let defenderArmes = []; - for (const arme of myTarget.data.items) { - if (arme.type == "arme" && RdDItemCompetence.isCompetenceMelee(arme.data.competence)) { - defenderArmes.push( arme ); - defenseMsg.content += "
Parer avec " + arme.name + ""; - } - if (arme.type == "competencecreature" && arme.data.isparade) { - defenderArmes.push( arme ); - defenseMsg.content += "
Parer avec " + arme.name + ""; - } - } - defenseMsg.content += "
Esquiver"; - } - if ( rollData.competence.data.categorie == "tir" ) { - for (const arme of myTarget.data.items) { // Bouclier for parry - if ( arme.type == "arme" && arme.name.toLowerCase.match("bouclier") ) { - defenderArmes.push( arme ); - defenseMsg.content += "
Parer avec " + arme.name + ""; - } - } - } - if ( rollData.competence.data.categorie == "lancer" ) { - for (const arme of myTarget.data.items) { // Bouclier for parry Dodge/Esquive - if ( arme.type == "arme" && arme.name.toLowerCase.match("bouclier") ) { - defenderArmes.push( arme ); - defenseMsg.content += "
Parer avec " + arme.name + ""; - } - } - defenseMsg.content += "
Esquiver"; - } - - defenseMsg.toSocket = true; // True per default for all players - if (game.user.isGM) { // In GM case, only if target is a player - defenseMsg.toSocket = myTarget.hasPlayerOwner; - } - - return defenseMsg; - } - /* -------------------------------------------- */ static async responseNombreAstral( data ) { let actor = game.actors.get( data.id); @@ -793,7 +731,7 @@ export class RdDUtility { } if ((game.user.isGM && !defenderToken.actor.hasPlayerOwner) || (defenderToken.actor.hasPlayerOwner && (game.user.character.id == defenderToken.actor.data._id))) { //console.log("User is pushing message...", game.user.name); - game.system.rdd.rollDataHandler[data.attackerId] = duplicate(data.rollData); + game.system.rdd.rollDataHandler.attaques[data.attackerId] = duplicate(data.rollData); data.whisper = [game.user]; data.blind = true; data.rollMode = "blindroll"; @@ -895,9 +833,12 @@ export class RdDUtility { /* -------------------------------------------- */ static _handleMsgEncaisser(data) { if (game.user.isGM) { // Seul le GM effectue l'encaissement sur la fiche - let attackerRoll = game.system.rdd.rollDataHandler[data.attackerId]; // Retrieve the rolldata from the store - let defenderToken = canvas.tokens.get(data.defenderTokenId); - defenderToken.actor.encaisserDommages(attackerRoll); + let attackerRoll = game.system.rdd.rollDataHandler.attaques[data.attackerId]; // Retrieve the rolldata from the store + game.system.rdd.rollDataHandler.attaques[data.attackerId] = undefined; + game.system.rdd.rollDataHandler.defenses[attackerRoll.passeArme] = undefined; + + let defender = canvas.tokens.get(data.defenderTokenId).actor; + defender.encaisserDommages(attackerRoll); } } diff --git a/module/rolldata-ajustements.js b/module/rolldata-ajustements.js index 9a687a29..5139a5da 100644 --- a/module/rolldata-ajustements.js +++ b/module/rolldata-ajustements.js @@ -125,16 +125,6 @@ export class RollDataAjustements { } } - // /* -------------------------------------------- */ - // static calculListe(ajustements, rollData, actor) { - // let list = []; - // for (var key in referenceAjustements) { - // if (referenceAjustements[key].isUsed(rollData, actor)) { - // list.push(Ajustements._apply(referenceAjustements[key], rollData, actor)); - // } - // } - // return list; - // } /* -------------------------------------------- */ static sum(ajustements) { 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 new file mode 100644 index 00000000..2fdfdd8d --- /dev/null +++ b/templates/chat-demande-defense.html @@ -0,0 +1,62 @@ +
+ {{#if (eq surprise 'totale')}} + {{defender.name}} est totalement surpris + {{else if essais.defense}} + {{defender.name}} doit + {{else}} + {{defender.name}} doit se défendre + {{#if (eq surprise 'demi')}} avec une significative {{/if}} : + + {{/if}} + +
+ {{#unless (eq surprise 'totale')}} + {{#if essais.defense}} +
+ {{#unless essais.defenseChance}} + {{#if (eq defender.data.type 'personnage')}} + Faire appel à la chance + +
+ {{/if}} + {{/unless}} + {{#if (eq defender.data.type 'personnage')}} + {{#if (gt defender.data.data.compteurs.destinee.value 0)}} + Utiliser la destinée + +
+ {{/if}} + {{/if}} + {{else}} + {{#each armes as |arme key|}} + + Parer avec {{arme.name}} à {{../diffLibre }} + +
+ {{/each}} + {{#if mainsNues}} + + Parer à mains nues à {{diffLibre}} + +
+ {{/if}} + {{#if (ne attaqueCategorie 'tir')}} + + Esquiver à {{diffLibre}} + +
+ {{/if}} + {{/if}} + {{/unless}} + + Encaisser à {{#if (eq dmg.mortalite 'non-mortel')~}} + ({{numberFormat dmg.total decimals=0 sign=true}}) + {{~else~}} + {{numberFormat dmg.total decimals=0 sign=true}} + {{~/if}} ! + +
+
\ No newline at end of file diff --git a/templates/chat-resultat-attaque.html b/templates/chat-resultat-attaque.html index 5c5d8538..7802cce2 100644 --- a/templates/chat-resultat-attaque.html +++ b/templates/chat-resultat-attaque.html @@ -24,7 +24,6 @@ {{numberFormat dmg.total decimals=0 sign=true}} (entités de cauchemar) {{~/if}}. {{#if show.isRecul}}Si votre adversaire n'esquive pas, il devra résister à l'impact ou reculer sous le choc!{{/if}} - Le coup vise: {{dmg.loc.label}}.
{{#if (eq particuliere 'rapidite')}}