Vincent Vandemeulebrouck
9077c9f26b
après un raffraîchissement browser, le jet de dés de l'attaquant est perdu. ajout d'une notification pour informer l'utilisateur du soici.
629 lines
25 KiB
JavaScript
629 lines
25 KiB
JavaScript
import { RdDActor } from "./actor.js";
|
|
import { ChatUtility } from "./chat-utility.js";
|
|
import { RdDItemArme } from "./item-arme.js";
|
|
import { RdDItemCompetence } from "./item-competence.js";
|
|
import { Misc } from "./misc.js";
|
|
import { RdDResolutionTable } from "./rdd-resolution-table.js";
|
|
import { RdDRoll } from "./rdd-roll.js";
|
|
import { RdDUtility } from "./rdd-utility.js";
|
|
|
|
export class RdDCombat {
|
|
|
|
static isActive() {
|
|
return game.settings.get("foundryvtt-reve-de-dragon", "moteur-combat") == 'experimental';
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
static createUsingTarget(attacker) {
|
|
const target = RdDCombat.getTarget();
|
|
if (target == undefined) {
|
|
ui.notifications.warn("Vous devez choisir une seule cible à attaquer!");
|
|
}
|
|
return this.create(attacker, target ? target.actor : undefined, target)
|
|
}
|
|
|
|
static getTarget() {
|
|
if (game.user.targets && game.user.targets.size == 1) {
|
|
for (let target of game.user.targets) {
|
|
return target;
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
static create(attacker, defender, target = undefined) {
|
|
return new RdDCombat(attacker, defender, target)
|
|
}
|
|
|
|
static createForEvent(event) {
|
|
let attackerId = event.currentTarget.attributes['data-attackerId'].value;
|
|
let attacker = game.actors.get(attackerId);
|
|
|
|
const dataDefenderTokenId = event.currentTarget.attributes['data-defenderTokenId'];
|
|
if (dataDefenderTokenId) {
|
|
let defenderToken = canvas.tokens.get(dataDefenderTokenId.value);
|
|
let defender = defenderToken.actor;
|
|
|
|
return this.create(attacker, defender);
|
|
}
|
|
return this.createUsingTarget(attacker)
|
|
}
|
|
|
|
constructor(attacker, defender, target) {
|
|
this.attacker = attacker;
|
|
this.defender = defender;
|
|
this.target = target;
|
|
this.attackerId = this.attacker.data._id;
|
|
this.defenderId = this.defender.data._id;
|
|
this.defenderTokenId = target ? target.data._id : undefined;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
static registerChatCallbacks(html) {
|
|
for (let button of ['#parer-button', '#esquiver-button', '#particuliere-attaque', '#encaisser-button']) {
|
|
html.on("click", button, event => {
|
|
event.preventDefault();
|
|
RdDCombat.createForEvent(event).onEvent(button, event);
|
|
});
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async onEvent(button, event) {
|
|
if (!RdDCombat.isActive()) {
|
|
return;
|
|
}
|
|
let rollData = game.system.rdd.rollDataHandler[this.attackerId];
|
|
if (!rollData) {
|
|
ui.notifications.warn("Action automatisée impossible, le jet de l'attaquant a été perdu (suite à un raffraichissement?)")
|
|
return;
|
|
}
|
|
// TODO: enlever le ChatMessage?
|
|
switch (button) {
|
|
case '#particuliere-attaque': return await this.choixParticuliere(rollData, event.currentTarget.attributes['data-mode'].value);
|
|
case '#parer-button': return this.parade(rollData, event.currentTarget.attributes['data-armeid'].value);
|
|
case '#esquiver-button': return this.esquive(rollData);
|
|
case '#encaisser-button': return this.encaisser(rollData, event.currentTarget.attributes['data-defenderTokenId'].value);
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async attaque(competence, arme) {
|
|
if (!await this.accorderEntite('avant-attaque')) {
|
|
return;
|
|
}
|
|
|
|
let rollData = this._prepareAttaque(competence, arme);
|
|
console.log("RdDCombat.attaque >>>", rollData);
|
|
|
|
const dialog = await RdDRoll.create(this.attacker, rollData,
|
|
{ html: 'systems/foundryvtt-reve-de-dragon/templates/dialog-competence.html' }, {
|
|
name: 'jet-attaque',
|
|
label: 'Attaque: ' + (arme ? arme.name : competence.name),
|
|
callbacks: [
|
|
this.attacker.createCallbackExperience(),
|
|
{ condition: RdDResolutionTable.isParticuliere, action: r => this._onAttaqueParticuliere(r) },
|
|
{ condition: r => (RdDResolutionTable.isReussite(r) && !RdDResolutionTable.isParticuliere(r)), action: r => this._onAttaqueNormale(r) },
|
|
{ condition: RdDResolutionTable.isEchecTotal, action: r => this._onAttaqueEchecTotal(r) },
|
|
{ condition: RdDResolutionTable.isEchec, action: r => this._onAttaqueEchec(r) }
|
|
]
|
|
});
|
|
dialog.render(true);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
_prepareAttaque(competence, arme) {
|
|
let rollData = {
|
|
coupsNonMortels: false,
|
|
competence: competence,
|
|
surprise: this.attacker.getSurprise()
|
|
};
|
|
|
|
if (this.attacker.isCreature()) {
|
|
this._modifieRollDataCreature(rollData, competence);
|
|
}
|
|
else {
|
|
// Usual competence
|
|
rollData.arme = RdDItemArme.armeUneOuDeuxMains(arme, RdDItemCompetence.isArmeUneMain(competence));
|
|
}
|
|
return rollData;
|
|
}
|
|
|
|
_modifieRollDataCreature(rollData, competence) {
|
|
competence = duplicate(competence);
|
|
competence.data.defaut_carac = "carac_creature";
|
|
competence.data.categorie = "creature";
|
|
|
|
rollData.competence = competence;
|
|
rollData.carac = { "carac_creature": { label: competence.name, value: competence.data.carac_value } };
|
|
rollData.arme = {
|
|
name: competence.name, data: {
|
|
dommages: competence.data.dommages,
|
|
dommagesReels: competence.data.dommages
|
|
}
|
|
};
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
_onAttaqueParticuliere(rollData) {
|
|
console.log("RdDCombat.onAttaqueParticuliere >>>", rollData);
|
|
let message = "<strong>Réussite particulière en attaque</strong>";
|
|
message += "<br><a class='chat-card-button' id='particuliere-attaque' data-mode='force' data-attackerId='" + this.attackerId + "'>Attaquer en Force</a>";
|
|
// Finesse et Rapidité seulement en mêlée et si la difficulté libre est de -1 minimum
|
|
if (rollData.selectedCarac.label == "Mêlée" && rollData.diffLibre < 0) {
|
|
message += "<br><a class='chat-card-button' id='particuliere-attaque' data-mode='rapidite' data-attackerId='" + this.attackerId + "'>Attaquer en Rapidité</a>";
|
|
message += "<br><a class='chat-card-button' id='particuliere-attaque' data-mode='finesse' data-attackerId='" + this.attackerId + "'>Attaquer en Finesse</a>";
|
|
}
|
|
game.system.rdd.rollDataHandler[this.attackerId] = rollData;
|
|
// TODO: use a dialog?
|
|
ChatMessage.create({ content: message, whisper: ChatMessage.getWhisperRecipients(this.attacker.name) });
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async _onAttaqueNormale(rollData) {
|
|
console.log("RdDCombat.onAttaqueNormale >>>", rollData);
|
|
if (!await this.accorderEntite('avant-defense')) {
|
|
return;
|
|
}
|
|
|
|
let explications = "";
|
|
|
|
// Message spécial pour la rapidité, qui reste difficile à gérer automatiquement
|
|
if (rollData.particuliereAttaque == 'rapidite') {
|
|
ChatMessage.create({
|
|
content: "Vous avez attaqué en Rapidité. Vous pourrez faire une deuxième attaque, ou utiliser votre arme pour vous défendre.",
|
|
whisper: ChatMessage.getWhisperRecipients(this.attacker.name)
|
|
});
|
|
}
|
|
rollData.dmg = RdDCombat.calculBonusDegats(rollData, this.attacker);
|
|
|
|
if (this.target) {
|
|
rollData.mortalite = this._calculMortaliteEncaissement(rollData);
|
|
explications += "<br><strong>Cible</strong> : " + this.defender.data.name;
|
|
}
|
|
explications += "<br>Encaissement à "+ Misc.toSignedString(rollData.dmg.total)+ " (" + rollData.dmg.loc.label+")";
|
|
|
|
// Save rollData for defender
|
|
game.system.rdd.rollDataHandler[this.attackerId] = duplicate(rollData);
|
|
|
|
// Final chat message
|
|
let chatOptions = {
|
|
content: "<strong>Test : " + rollData.selectedCarac.label + " / " + rollData.competence.name + "</strong>"
|
|
+ "<br>Difficultés <strong>libre : " + rollData.diffLibre + "</strong> / conditions : " + Misc.toSignedString(rollData.diffConditions) + " / état : " + rollData.etat
|
|
+ RdDResolutionTable.explain(rollData.rolled)
|
|
+ explications
|
|
}
|
|
ChatUtility.chatWithRollMode(chatOptions, this.attacker.name)
|
|
if (this.target) {
|
|
this._messageDefenseur(rollData);
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
_messageDefenseur(rollData) {
|
|
console.log("RdDCombat._messageDefenseur", rollData, " / ", this.attacker, this.target, this.target.actor.isToken, this.attacker.data._id, rollData.competence.data.categorie);
|
|
|
|
let content = "<strong>" + this.defender.name + "</strong> doit se défendre :<span class='chat-card-button-area'>";
|
|
|
|
// parades
|
|
let filterArmesParade = this._getFilterArmesParade(rollData.competence.data.categorie);
|
|
for (const arme of this.defender.data.items.filter(filterArmesParade)) {
|
|
content += "<br><a class='chat-card-button' id='parer-button' data-attackerId='" + this.attackerId + "' data-defenderTokenId='" + this.defenderTokenId + "' data-armeid='" + arme._id + "'>Parer avec " + arme.name + "</a>";
|
|
}
|
|
|
|
// esquive
|
|
if (rollData.competence.data.categorie == 'melee' || rollData.competence.data.categorie == "lancer" || rollData.competence.data.categorie == 'competencecreature') {
|
|
content += "<br><a class='chat-card-button' id='esquiver-button' data-attackerId='" + this.attackerId + "' data-defenderTokenId='" + this.defenderTokenId + "'>Esquiver</a>";
|
|
}
|
|
|
|
// encaisser
|
|
content += "<br><a class='chat-card-button' id='encaisser-button' data-attackerId='" + this.attackerId + "' data-defenderTokenId='" + this.defenderTokenId + "'>Encaisser à " + Misc.toSignedString(rollData.dmg.total) + " !</a>";
|
|
content += "</span>"
|
|
|
|
let defense = {
|
|
title: "Défense en combat",
|
|
content: content,
|
|
whisper: ChatMessage.getWhisperRecipients(this.defender.name),
|
|
attackerId: this.attackerId,
|
|
defenderTokenId: this.defenderTokenId,
|
|
rollMode: true,
|
|
rollData: duplicate(rollData)
|
|
};
|
|
|
|
// envoyer le message de defense
|
|
if (!game.user.isGM || this.defender.hasPlayerOwner) {
|
|
game.socket.emit("system.foundryvtt-reve-de-dragon", { msg: "msg_defense", data: defense });
|
|
} else {
|
|
defense.whisper = [game.user];
|
|
}
|
|
|
|
if (game.user.isGM) { // Always push the message to the MJ
|
|
ChatMessage.create(defense);
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
_getFilterArmesParade(categorie) {
|
|
switch (categorie) {
|
|
case 'tir':
|
|
case 'lancer':
|
|
return arme => arme.type == "arme" && arme.data.competence.toLowerCase().match("bouclier");
|
|
default:
|
|
return arme => (arme.type == "arme" && RdDItemCompetence.isCompetenceMelee(arme.data.competence)) || (arme.type == "competencecreature" && arme.data.isparade)
|
|
}
|
|
}
|
|
/* -------------------------------------------- */
|
|
_calculMortaliteEncaissement(rollData) {
|
|
const mortalite = this.defender.isEntiteCauchemar() ? "cauchemar" : (rollData.mortalite ? rollData.mortalite : "mortel");
|
|
console.log("Mortalité : ", mortalite, this.defender.data.type);
|
|
return mortalite;
|
|
}
|
|
|
|
|
|
_onAttaqueEchecTotal(rollData) {
|
|
console.log("RdDCombat.onEchecTotal >>>", rollData);
|
|
// TODO: proposer un résultat d'échec total
|
|
let chatOptions = {
|
|
content: "<strong>Echec total à l'attaque!</strong>"
|
|
}
|
|
ChatUtility.chatWithRollMode(chatOptions, this.attacker.name)
|
|
}
|
|
|
|
_onAttaqueEchec(rollData) {
|
|
console.log("RdDCombat.onAttaqueEchec >>>", rollData);
|
|
let chatOptions = {
|
|
content: "<strong>Test : " + rollData.selectedCarac.label + " / " + rollData.competence.name + "</strong>"
|
|
+ "<br>Difficultés <strong>libre : " + rollData.diffLibre + "</strong> / conditions : " + Misc.toSignedString(rollData.diffConditions) + " / état : " + rollData.etat
|
|
+ RdDResolutionTable.explain(rollData.rolled)
|
|
+ (this.target ? "<br><strong>Cible</strong> : " + this.defender.data.name : "")
|
|
}
|
|
ChatUtility.chatWithRollMode(chatOptions, this.attacker.name)
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async choixParticuliere(rollData, choix) {
|
|
console.log("RdDCombat.choixParticuliere >>>", rollData, choix);
|
|
rollData.particuliereAttaque = choix;
|
|
await this._onAttaqueNormale(rollData);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async parade(attackerRoll, armeParadeId) {
|
|
let arme = this.defender.getOwnedItem(armeParadeId);
|
|
|
|
console.log("RdDCombat.parade >>>", attackerRoll, armeParadeId, arme);
|
|
|
|
let rollData = this._prepareParade(attackerRoll, arme);
|
|
|
|
const dialog = await RdDRoll.create(this.defender, rollData,
|
|
{ html: 'systems/foundryvtt-reve-de-dragon/templates/dialog-competence.html' }, {
|
|
name: 'jet-parade',
|
|
label: 'Parade: ' + (arme ? arme.name : rollData.competence.name),
|
|
callbacks: [
|
|
this.defender.createCallbackExperience(),
|
|
{ condition: RdDResolutionTable.isParticuliere, action: r => this._onParadeParticuliere(r) },
|
|
{ condition: RdDResolutionTable.isReussite, action: r => this._onParadeNormale(r) },
|
|
{ condition: RdDResolutionTable.isEchecTotal, action: r => this._onParadeEchecTotal(r) },
|
|
{ condition: RdDResolutionTable.isEchec, action: r => this._onParadeEchec(r) }
|
|
]
|
|
});
|
|
dialog.render(true);
|
|
}
|
|
|
|
_prepareParade(attackerRoll, arme) {
|
|
const isCreature = this.defender.isCreature();
|
|
const compName = isCreature ? arme.name : arme.data.data.competence;
|
|
const competence = this.defender.getCompetence(compName);
|
|
const armeAttaque = attackerRoll.arme;
|
|
const armeParade = arme.data;
|
|
|
|
if (compName != competence.name) {
|
|
// TODO: toujours utiliser competence.name ...
|
|
ui.notifications.warn("Différence entre compétence " + competence.name + " et compétence de l'arme " + compName);
|
|
}
|
|
|
|
let rollData = {
|
|
forceValue: this.defender.getForceValue(),
|
|
diffLibre: attackerRoll.diffLibre,
|
|
attackerRoll: attackerRoll,
|
|
competence: competence,
|
|
arme: arme.data,
|
|
surprise: this.defender.getSurprise(),
|
|
needSignificative: this._needSignificative(attackerRoll) || RdDItemArme.needParadeSignificative(armeAttaque, armeParade),
|
|
needResist: this._needResist(armeAttaque, armeParade),
|
|
carac: this.defender.data.data.carac
|
|
};
|
|
if (isCreature) {
|
|
this._modifieRollDataCreature(rollData, competence);
|
|
}
|
|
return rollData;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
_needSignificative(attackerRoll) {
|
|
return attackerRoll.particuliereAttaque == 'finesse';
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
_needResist(armeAttaque, armeParade) {
|
|
// Manage weapon categories when parrying (cf. page 115 )
|
|
let attCategory = RdDItemArme.getCategorieArme(armeAttaque);
|
|
let defCategory = RdDItemArme.getCategorieArme(armeParade);
|
|
|
|
return (attCategory.match("epee") && (defCategory == "hache" || defCategory == "lance"));
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
_onParadeParticuliere(rollData) {
|
|
console.log("RdDCombat._onParadeParticuliere >>>", rollData);
|
|
if (!rollData.attackerRoll.isPart) {
|
|
// TODO: attaquant doit jouer résistance et peut être désarmé p132
|
|
}
|
|
let chatOptions = {
|
|
content: "<strong>Vous pouvez utiliser votre arme pour une deuxième parade!</strong>"
|
|
}
|
|
ChatUtility.chatWithRollMode(chatOptions, this.defender.name)
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async _onParadeNormale(rollData) {
|
|
console.log("RdDCombat._onParadeNormale >>>", rollData);
|
|
if (rollData.needResist && !rollData.rolled.isPart) {
|
|
// TODO: déplacer la logique détérioration armure dans RdDCombat
|
|
this.defender.computeDeteriorationArme(rollData);
|
|
}
|
|
await this.defender.computeRecul(rollData, false);
|
|
|
|
let chatOptions = {
|
|
content: "<strong>Test : " + rollData.selectedCarac.label + " / " + rollData.competence.name + "</strong>"
|
|
+ "<br>Difficultés <strong>libre : " + rollData.diffLibre + "</strong> / conditions : " + Misc.toSignedString(rollData.diffConditions) + " / état : " + rollData.etat
|
|
+ RdDResolutionTable.explain(rollData.rolled)
|
|
+ "<br><strong>Attaque parée!</strong>"
|
|
}
|
|
|
|
ChatUtility.chatWithRollMode(chatOptions, this.defender.name)
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
_onParadeEchecTotal(rollData) {
|
|
console.log("RdDCombat._onParadeEchecTotal >>>", rollData);
|
|
// TODO: proposer un résultat d'échec total
|
|
let chatOptions = {
|
|
content: "<strong>Echec total à la parade!</strong>"
|
|
}
|
|
ChatUtility.chatWithRollMode(chatOptions, this.defender.name)
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async _onParadeEchec(rollData) {
|
|
console.log("RdDCombat._onParadeEchec >>>", rollData);
|
|
|
|
let explications = "<br><strong>Parade échouée, encaissement !</strong> ";
|
|
explications += this.descriptionSurprise(rollData.surprise);
|
|
if (rollData.needSignificative) {
|
|
explications += " Significative nécessaire!";
|
|
}
|
|
|
|
let chatOptions = {
|
|
content: "<strong>Test : " + rollData.selectedCarac.label + " / " + rollData.competence.name + "</strong>"
|
|
+ "<br>Difficultés <strong>libre : " + rollData.diffLibre + "</strong> / conditions : " + Misc.toSignedString(rollData.diffConditions) + " / état : " + rollData.etat
|
|
+ RdDResolutionTable.explain(rollData.rolled)
|
|
+ explications
|
|
}
|
|
|
|
ChatUtility.chatWithRollMode(chatOptions, this.defender.name)
|
|
|
|
await this.defender.computeRecul(rollData, true);
|
|
// TODO: gestion message pour chance/encaissement
|
|
this.encaisser(rollData.attackerRoll);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async esquive(attackerRoll) {
|
|
let esquive = this.defender.getCompetence("esquive");
|
|
if (esquive == undefined) {
|
|
ui.notifications.error(this.defender.name + " n'a pas de compétence 'esquive'");
|
|
return;
|
|
}
|
|
console.log("RdDCombat.esquive >>>", attackerRoll, esquive);
|
|
let rollData = this._prepareEsquive(attackerRoll, esquive);
|
|
|
|
const dialog = await RdDRoll.create(this.defender, rollData,
|
|
{ html: 'systems/foundryvtt-reve-de-dragon/templates/dialog-competence.html' }, {
|
|
name: 'jet-esquive',
|
|
label: 'Esquiver',
|
|
callbacks: [
|
|
this.defender.createCallbackExperience(),
|
|
{ condition: RdDResolutionTable.isParticuliere, action: r => this._onEsquiveParticuliere(r) },
|
|
{ condition: RdDResolutionTable.isReussite, action: r => this._onEsquiveNormale(r) },
|
|
{ condition: RdDResolutionTable.isEchecTotal, action: r => this._onEsquiveEchecTotal(r) },
|
|
{ condition: RdDResolutionTable.isEchec, action: r => this._onEsquiveEchec(r) },
|
|
]
|
|
});
|
|
dialog.render(true);
|
|
}
|
|
|
|
_prepareEsquive(attackerRoll, competence) {
|
|
let rollData = {
|
|
forceValue: this.defender.getForceValue(),
|
|
diffLibre: attackerRoll.diffLibre,
|
|
attackerRoll: attackerRoll,
|
|
competence: competence,
|
|
surprise: this.defender.getSurprise(),
|
|
needSignificative: this._needSignificative(attackerRoll),
|
|
carac: this.defender.data.data.carac
|
|
};
|
|
|
|
if (this.defender.isCreature()) {
|
|
this._modifieRollDataCreature(rollData, competence);
|
|
}
|
|
return rollData;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
_onEsquiveParticuliere(rollData) {
|
|
console.log("RdDCombat._onEsquiveParticuliere >>>", rollData);
|
|
let chatOptions = {
|
|
content: "<strong>Vous pouvez esquiver une deuxième attaque!</strong>"
|
|
}
|
|
ChatUtility.chatWithRollMode(chatOptions, this.defender.name)
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
_onEsquiveNormale(rollData) {
|
|
console.log("RdDCombat._onEsquiveNormal >>>", rollData);
|
|
let chatOptions = {
|
|
content: "<strong>Test : " + rollData.selectedCarac.label + " / " + rollData.competence.name + "</strong>"
|
|
+ "<br>Difficultés <strong>libre : " + rollData.diffLibre + "</strong> / conditions : " + Misc.toSignedString(rollData.diffConditions) + " / état : " + rollData.etat
|
|
+ RdDResolutionTable.explain(rollData.rolled)
|
|
+ "<br><strong>Attaque esquivée!</strong>"
|
|
}
|
|
|
|
ChatUtility.chatWithRollMode(chatOptions, this.defender.name)
|
|
}
|
|
/* -------------------------------------------- */
|
|
_onEsquiveEchecTotal(rollData) {
|
|
console.log("RdDCombat._onEsquiveEchecTotal >>>", rollData);
|
|
// TODO: proposer un résultat d'échec total
|
|
let chatOptions = {
|
|
content: "<strong>Echec total à l'esquive'!</strong>"
|
|
}
|
|
ChatUtility.chatWithRollMode(chatOptions, this.defender.name)
|
|
}
|
|
/* -------------------------------------------- */
|
|
async _onEsquiveEchec(rollData) {
|
|
console.log("RdDCombat._onEsquiveEchec >>>", rollData);
|
|
|
|
let explications = "<br><strong>Esquive échouée, encaissement !</strong> ";
|
|
explications += RdDCombat.descriptionSurprise(rollData.surprise);
|
|
if (rollData.needSignificative) {
|
|
explications += " Significative nécessaire!";
|
|
}
|
|
|
|
let chatOptions = {
|
|
content: "<strong>Test : " + rollData.selectedCarac.label + " / " + rollData.competence.name + "</strong>"
|
|
+ "<br>Difficultés <strong>libre : " + rollData.diffLibre + "</strong> / conditions : " + Misc.toSignedString(rollData.diffConditions) + " / état : " + rollData.etat
|
|
+ RdDResolutionTable.explain(rollData.rolled)
|
|
+ explications
|
|
}
|
|
|
|
ChatUtility.chatWithRollMode(chatOptions, this.defender.name)
|
|
|
|
await this.defender.computeRecul(rollData, true);
|
|
this.encaisser(rollData.attackerRoll);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
encaisser(attackerRoll) {
|
|
// TODO: gestion message pour chance/encaissement
|
|
this.encaisser(attackerRoll, this.defenderTokenId);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
encaisser(attackerRoll, defenderTokenId) {
|
|
defenderTokenId = defenderTokenId || this.defenderTokenId;
|
|
console.log("RdDCombat.encaisser >>>", attackerRoll, defenderTokenId);
|
|
|
|
if (game.user.isGM) { // Current user is the GM -> direct access
|
|
attackerRoll.attackerId = this.attackerId;
|
|
attackerRoll.defenderTokenId = defenderTokenId;
|
|
this.defender.encaisserDommages(attackerRoll, this.attacker);
|
|
} else { // Emit message for GM
|
|
game.socket.emit("system.foundryvtt-reve-de-dragon", {
|
|
msg: "msg_encaisser",
|
|
data: { attackerId: this.attackerId, defenderTokenId: defenderTokenId }
|
|
});
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
/* retourne true si on peut continuer, false si on ne peut pas continuer */
|
|
async accorderEntite(when = 'avant-encaissement') {
|
|
if (when != game.settings.get("foundryvtt-reve-de-dragon", "accorder-entite-cauchemar")
|
|
|| this.defender == undefined
|
|
|| !this.defender.isEntiteCauchemar()
|
|
|| this.defender.isEntiteCauchemarAccordee(this.attacker)) {
|
|
return true;
|
|
}
|
|
|
|
let rolled = await RdDResolutionTable.roll(this.attacker.getReveActuel(), - Number(this.defender.data.data.carac.niveau.value));
|
|
|
|
let message = {
|
|
content: "Jet de points actuels de rêve à " + rolled.finalLevel + RdDResolutionTable.explain(rolled) + "<br>",
|
|
whisper: ChatMessage.getWhisperRecipients(this.attacker.name)
|
|
};
|
|
|
|
if (rolled.isSuccess) {
|
|
await this.defender.setEntiteReveAccordee(this.attacker);
|
|
message.content += this.attacker.name + " s'est accordé avec " + this.defender.name;
|
|
}
|
|
else {
|
|
message.content += this.attacker.name + " n'est pas accordé avec " + this.defender.name;
|
|
}
|
|
|
|
ChatMessage.create(message);
|
|
return rolled.isSuccess;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
static calculBonusDegats(rollData, actor) {
|
|
let dmg = { total: 0, loc: RdDUtility.getLocalisation() };
|
|
if (rollData.arme.name.toLowerCase() == "esquive") {
|
|
// Specific case management
|
|
ui.notifications.warn("Calcul de bonus dégats sur eswquive")
|
|
return dmg;
|
|
}
|
|
dmg.dmgArme = RdDCombat._dmgArme(rollData);
|
|
dmg.ignoreArmure = 0; // TODO: calculer pour arcs et arbaletes, gérer pour lmes créatures
|
|
dmg.dmgTactique= RdDCombat._dmgTactique(rollData);
|
|
dmg.dmgParticuliere= RdDCombat._dmgParticuliere(rollData);
|
|
dmg.dmgSurprise= RdDCombat._dmgSurprise(rollData);
|
|
dmg.dmgActor = RdDCombat._dmgActor(actor.getBonusDegat(), rollData.selectedCarac.label, dmg.dmgArme);
|
|
dmg.total = dmg.dmgSurprise + dmg.dmgTactique + dmg.dmgArme + dmg.dmgActor + dmg.dmgParticuliere;
|
|
return dmg;
|
|
}
|
|
|
|
static _dmgArme(rollData) {
|
|
return parseInt(rollData.arme.data.dommages);
|
|
}
|
|
|
|
static _dmgActor(bonusDegat, categorie, dmgArme) {
|
|
switch (categorie) {
|
|
case "Tir": return 0;
|
|
case "Lancer": return Math.max(0, Math.min(dmgArme, bonusDegat));
|
|
}
|
|
return bonusDegat;
|
|
}
|
|
|
|
static _dmgTactique(rollData) {
|
|
return rollData.isCharge ? 2 : 0;
|
|
}
|
|
|
|
static _dmgParticuliere(rollData) {
|
|
return rollData.particuliereAttaque == 'force' ? 5 : 0;
|
|
}
|
|
|
|
|
|
static _dmgSurprise(rollData) {
|
|
if (rollData.surprise) {
|
|
switch (rollData.surprise) {
|
|
case 'demi': return 1;
|
|
case 'totale': return 10;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
static descriptionSurprise(surprise) {
|
|
if (surprise) {
|
|
switch (surprise) {
|
|
case 'demi': return 'demi-surprise';
|
|
case 'totale': return 'surprise totale';
|
|
}
|
|
}
|
|
return '';
|
|
}
|
|
|
|
|
|
} |