#42 Tchat message fin

parade, esquive

 inclus deterioration arme de parade
 inclus recul sous le choc
This commit is contained in:
Vincent Vandemeulebrouck
2021-01-02 04:28:43 +01:00
parent cd36331702
commit 0a3f578bf3
19 changed files with 428 additions and 337 deletions

View File

@ -18,7 +18,9 @@ export class RdDCombat {
static createUsingTarget(attacker) {
const target = RdDCombat.getTarget();
if (target == undefined) {
ui.notifications.warn("Vous devez choisir une seule cible à attaquer!");
ui.notifications.warn((game.user.targets && game.user.targets.size > 1)
? "Vous devez choisir <strong>une seule</strong> cible à attaquer!"
: "Vous devez choisir une cible à attaquer!");
}
const defender = target ? target.actor : undefined;
const defenderTokenId = target ? target.data._id : undefined;
@ -124,7 +126,6 @@ export class RdDCombat {
/* -------------------------------------------- */
static isEchec(rollData) {
switch (rollData.surprise) {
case 'demi': return !rollData.rolled.isSign;
case 'totale': return true;
}
return rollData.rolled.isEchec;
@ -132,7 +133,7 @@ export class RdDCombat {
/* -------------------------------------------- */
static isEchecTotal(rollData) {
if (rollData.arme && rollData.surprise == 'demi') {
if (!rollData.attackerRoll && rollData.surprise) {
return rollData.rolled.isEchec;
}
return rollData.rolled.isETotal;
@ -140,7 +141,7 @@ export class RdDCombat {
/* -------------------------------------------- */
static isParticuliere(rollData) {
if (rollData.arme && rollData.surprise) {
if (!rollData.attackerRoll && rollData.surprise) {
return false;
}
return rollData.rolled.isPart;
@ -149,7 +150,6 @@ export class RdDCombat {
/* -------------------------------------------- */
static isReussite(rollData) {
switch (rollData.surprise) {
case 'demi': return rollData.rolled.isSign;
case 'totale': return false;
}
return rollData.rolled.isSuccess;
@ -216,7 +216,7 @@ export class RdDCombat {
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>
<br><a class='chat-card-button' id='particuliere-attaque' data-mode='finesse' data-attackerId='${this.attackerId}'>Attaquer en Finesse</a>";
<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;
@ -237,9 +237,7 @@ export class RdDCombat {
cible: this.target ? this.defender.data.name : 'la cible',
isRecul: (rollData.particuliereAttaque == 'force' || rollData.tactique == 'charge')
}
ChatUtility.chatWithRollMode(
{ content: await RdDResolutionTable.explainRollDataV2(rollData, 'chat-resultat-attaque.html') },
this.name)
await RdDResolutionTable.displayRollData(rollData, this.attacker.name, 'chat-resultat-attaque.html');
if (!await this.accorderEntite('avant-defense')) {
return;
@ -267,7 +265,7 @@ export class RdDCombat {
if (this.defender.getSurprise() != 'totale') {
// parades
for (const arme of this._filterArmesParade(this.defender.data.items, rollData.competence.data.categorie)) {
for (const arme of this._filterArmesParade(this.defender.data.items, rollData.competence, rollData.arme)) {
message += "<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>";
}
// corps à corps
@ -288,18 +286,24 @@ export class RdDCombat {
}
/* -------------------------------------------- */
_filterArmesParade(items, categorie) {
switch (categorie) {
_filterArmesParade(items, competence) {
switch (competence.data.categorie) {
case 'tir':
case 'lancer':
return items.filter(item => RdDItemArme.getCategorieParade(item) == 'boucliers')
default:
// Le fléau ne peut être paré quau bouclier p115
if (competence.name == "Fléau"){
return items.filter(item => RdDItemArme.getCategorieParade(item) == 'boucliers')
}
return items.filter(item => RdDItemArme.getCategorieParade(item));
}
}
/* -------------------------------------------- */
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: "<strong>Echec total à l'attaque!</strong> "
@ -308,15 +312,12 @@ export class RdDCombat {
ChatUtility.chatWithRollMode(chatOptions, this.attacker.name)
}
/* -------------------------------------------- */
_onAttaqueEchec(rollData) {
async _onAttaqueEchec(rollData) {
console.log("RdDCombat.onAttaqueEchec >>>", rollData);
let chatOptions = {
content: "<strong>Test : " + rollData.selectedCarac.label + " / " + rollData.competence.name + "</strong>"
+ RdDResolutionTable.explainRollData(rollData)
+ (this.target ? "<br><strong>Cible</strong> : " + this.defender.data.name : "")
}
ChatUtility.chatWithRollMode(chatOptions, this.attacker.name)
await RdDResolutionTable.displayRollData(rollData, this.attacker.name, 'chat-resultat-attaque.html');
}
/* -------------------------------------------- */
@ -328,8 +329,7 @@ export class RdDCombat {
/* -------------------------------------------- */
async parade(attackerRoll, armeParadeId) {
let arme = this._findArmeParade(armeParadeId);
let arme = RdDItemArme.getArmeData(armeParadeId ? this.defender.getOwnedItem(armeParadeId) : null);
console.log("RdDCombat.parade >>>", attackerRoll, armeParadeId, arme);
@ -353,32 +353,23 @@ export class RdDCombat {
dialog.render(true);
}
_findArmeParade(armeParadeId) {
return RdDItemArme.getArmeData(armeParadeId ? this.defender.getOwnedItem(armeParadeId) : null);
}
_prepareParade(attackerRoll, armeParade) {
const isCreature = this.defender.isCreature();
const compName = armeParade.data.competence;
const competence = this.defender.getCompetence(compName);
const armeAttaque = attackerRoll.arme;
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,
competence: this.defender.getCompetence(compName),
arme: armeParade,
surprise: this.defender.getSurprise(),
surpriseDefenseur: this.defender.getSurprise(),
needSignificative: this._needSignificative(attackerRoll) || RdDItemArme.needParadeSignificative(armeAttaque, armeParade),
needResist: this._needResist(armeAttaque, armeParade),
carac: this.defender.data.data.carac
needParadeSignificative: this.needParadeSignificative(attackerRoll, armeParade),
diviseur: this._getDiviseurSignificative(attackerRoll, armeParade),
needResist: this._needArmeResist(armeAttaque, armeParade),
carac: this.defender.data.data.carac,
show: {}
};
if (isCreature) {
RdDItemCompetence.setRollDataCreature(rollData);
@ -387,12 +378,29 @@ export class RdDCombat {
}
/* -------------------------------------------- */
_needSignificative(attackerRoll) {
return attackerRoll.particuliereAttaque == 'finesse';
_getDiviseurSignificative(attackerRoll, armeParade = undefined) {
let facteurSign = this.defender.getDiviseurSignificative();
if (RdDCombat.isAttaqueFinesse(attackerRoll)) {
facteurSign *= 2;
}
if (this.needParadeSignificative(attackerRoll, armeParade)) {
facteurSign *= 2;
}
return Math.min(4, facteurSign);
}
static isAttaqueFinesse(attackerRoll) {
return attackerRoll && attackerRoll.particuliereAttaque == 'finesse';
}
needParadeSignificative(attackerRoll, armeParade) {
return attackerRoll.arme && armeParade &&
RdDItemArme.needParadeSignificative(attackerRoll.arme, armeParade);
}
/* -------------------------------------------- */
_needResist(armeAttaque, armeParade) {
_needArmeResist(armeAttaque, armeParade) {
// Epées parant une arme de bois (cf. page 115 ), une résistance est nécessaire
let attCategory = RdDItemArme.getCategorieParade(armeAttaque);
let defCategory = RdDItemArme.getCategorieParade(armeParade);
@ -405,29 +413,27 @@ export class RdDCombat {
console.log("RdDCombat._onParadeParticuliere >>>", rollData);
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)`
}, this.defender.name)
}
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);
let chatOptions = {
content: "<strong>Test : " + rollData.selectedCarac.label + " / " + rollData.competence.name + "</strong>"
+ RdDResolutionTable.explainRollData(rollData)
+ "<br><strong>Attaque parée!</strong>"
}
ChatUtility.chatWithRollMode(chatOptions, this.defender.name)
await this.computeRecul(rollData, false);
await this.computeRecul(rollData);
await this.computeDeteriorationArme(rollData);
await RdDResolutionTable.displayRollData(rollData, this.defender.name, '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: "<strong>Echec total à la parade!</strong> "
@ -440,24 +446,11 @@ export class RdDCombat {
async _onParadeEchec(rollData) {
console.log("RdDCombat._onParadeEchec >>>", rollData);
let explications = "<br><strong>Parade échouée, encaissement !</strong> ";
explications += RdDBonus.description(rollData.surprise);
if (rollData.needSignificative) {
explications += " Significative nécessaire!";
}
await this.computeRecul(rollData);
let chatOptions = {
content: "<strong>Test : " + rollData.selectedCarac.label + " / " + rollData.competence.name + "</strong>"
+ RdDResolutionTable.explainRollData(rollData)
+ explications
}
await RdDResolutionTable.displayRollData(rollData, this.defender.name, 'chat-resultat-parade.html');
ChatUtility.chatWithRollMode(chatOptions, this.defender.name)
await this.computeRecul(rollData, true);
// TODO: gestion message pour chance/encaissement
this._sendMessageEncaisser(rollData.attackerRoll);
}
/* -------------------------------------------- */
@ -494,8 +487,9 @@ export class RdDCombat {
competence: competence,
surprise: this.defender.getSurprise(),
surpriseDefenseur: this.defender.getSurprise(),
needSignificative: this._needSignificative(attackerRoll),
carac: this.defender.data.data.carac
diviseur: this._getDiviseurSignificative(attackerRoll),
carac: this.defender.data.data.carac,
show: {}
};
if (this.defender.isCreature()) {
@ -508,24 +502,22 @@ export class RdDCombat {
_onEsquiveParticuliere(rollData) {
console.log("RdDCombat._onEsquiveParticuliere >>>", rollData);
let chatOptions = {
content: "<strong>Vous pouvez esquiver une deuxième attaque!</strong>"
content: "<strong>Vous pouvez esquiver une deuxième esquive!</strong>"
}
ChatUtility.chatWithRollMode(chatOptions, this.defender.name)
}
/* -------------------------------------------- */
_onEsquiveNormale(rollData) {
async _onEsquiveNormale(rollData) {
console.log("RdDCombat._onEsquiveNormal >>>", rollData);
let chatOptions = {
content: "<strong>Test : " + rollData.selectedCarac.label + " / " + rollData.competence.name + "</strong>"
+ RdDResolutionTable.explainRollData(rollData)
+ "<br><strong>Attaque esquivée!</strong>"
}
ChatUtility.chatWithRollMode(chatOptions, this.defender.name)
await RdDResolutionTable.displayRollData(rollData, this.defender.name, '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: "<strong>Echec total à l'esquive'!</strong> "
@ -537,32 +529,23 @@ export class RdDCombat {
async _onEsquiveEchec(rollData) {
console.log("RdDCombat._onEsquiveEchec >>>", rollData);
let explications = "<br><strong>Esquive échouée, encaissement !</strong> ";
explications += RdDBonus.description(rollData.surprise);
if (rollData.needSignificative) {
explications += " Significative nécessaire!";
}
await this.computeRecul(rollData);
let chatOptions = {
content: "<strong>Test : " + rollData.selectedCarac.label + " / " + rollData.competence.name + "</strong>"
+ RdDResolutionTable.explainRollData(rollData)
+ explications
}
await RdDResolutionTable.displayRollData(rollData, this.defender.name, 'chat-resultat-esquive.html');
ChatUtility.chatWithRollMode(chatOptions, this.defender.name)
await this.computeRecul(rollData, true);
this._sendMessageEncaisser(rollData.attackerRoll);
}
/* -------------------------------------------- */
async computeDeteriorationArme(rollData) {
const attackerRoll = rollData.attackerRoll;
if (rollData.arme && attackerRoll) { // C'est une parade
// Est-ce que l'attaque est une particulière, en force ou charge et que l'attaque n'en est pas une ?
if ((rollData.needResist || attackerRoll.particuliereAttaque == 'force' || attackerRoll.tactique == 'charge')
&& !rollData.rolled.isPart) {
// Est-ce une parade normale?
if (rollData.arme && attackerRoll && !rollData.rolled.isPart) {
// Est-ce que l'attaque est une particulière en force ou une charge
if (rollData.needResist || attackerRoll.particuliereAttaque == 'force' || attackerRoll.tactique == 'charge') {
rollData.show = rollData.show || {}
const dmg = attackerRoll.dmg.dmgArme + attackerRoll.dmg.dmgActor;
let resistance = Misc.toInt(rollData.arme.data.resistance);
let msg = "";
@ -572,72 +555,66 @@ export class RdDCombat {
finalLevel: - dmg,
showDice: false
});
if (resistRoll.isSuccess) { // Perte de résistance
msg = "Votre " + rollData.arme.name + " tient le choc de la parade. "
if (resistRoll.rolled.isSuccess) { // Perte de résistance
rollData.show.deteriorationArme = 'resiste';
} else {
resistance -= dmg;
if (resistance <= 0) {
this.defender.deleteEmbeddedEntity("OwnedItem", rollData.arme._id);
msg = "Sous la violence de la parade, votre " + rollData.arme.name + " s'est brisée sous le coup!";
rollData.show.deteriorationArme = 'brise';
} else {
this.defender.updateEmbeddedEntity("OwnedItem", { _id: rollData.arme._id, 'data.resistance': resistance });
msg = "En parant, vous endommagez votre " + rollData.arme.name + ", qui perd " + dmg + " de résistance. ";
rollData.show.deteriorationArme = 'perte';
rollData.show.perteResistance = dmg;
}
}
// Jet de désarmement
if (resistance > 0 && !rollData.arme.name.toLowerCase().includes('bouclier')) { // Si l'arme de parade n'est pas un bouclier, jet de désarmement (p.132)
// Si l'arme de parade n'est pas un bouclier, jet de désarmement (p.132)
if (resistance > 0 && !RdDItemArme.getCategorieParade(rollData.arme) == 'boucliers') {
let desarme = await RdDResolutionTable.rollData({
caracValue: this.defender.data.data.carac.force.value,
finalLevel: Misc.toInt(rollData.competence.data.niveau) - dmg,
showDice: false
});
if (desarme.isEchec) {
msg += "Vous ne parvenez pas à garder votre arme en main, elle tombe au sol à vos pieds";
rollData.show.desarme = desarme.rolled.isEchec;
if (desarme.rolled.isEchec) {
rollData.show.desarme = true;
}
}
ChatMessage.create({
content: msg,
user: game.user._id,
whisper: [game.user._id, ChatMessage.getWhisperRecipients("GM")]
});
}
}
}
/* -------------------------------------------- */
async computeRecul(rollData, encaisser = undefined) { // Calcul du recul (p. 132)
if (rollData.arme || encaisser) {
if ((rollData.attackerRoll.particuliereAttaque && rollData.attackerRoll.particuliereAttaque == 'force') || rollData.attackerRoll.tactique == 'charge') {
let reculNiveau = Misc.toInt(this.defender.data.data.carac.taille.value) - (rollData.attackerRoll.forceValue + rollData.attackerRoll.arme.data.dommagesReels);
let recul = await RdDResolutionTable.rollData({
caracValue: 10,
finalLevel: reculNiveau,
showDice: false
});
async computeRecul(rollData) { // Calcul du recul (p. 132)
const attaque = rollData.attackerRoll;
if (this._isAttaqueCauseRecul(attaque)) {
let msg = "";
if (recul.isSuccess) {
msg = " Vous ne reculez pas malgré la force du coup.";
} else {
let chute = await RdDResolutionTable.rollData({
caracValue: this.defender.data.data.carac.agilite.value,
finalLevel: reculNiveau,
showDice: false
});
if (!chute.isSuccess || recul.isETotal) {
msg = "Sous la violence du coup, vous reculez et chutez au sol ! Vous ne pouvez plus attaquer ce round.";
} else {
msg = "La violence du choc vous fait reculer de quelques mètres ! Vous ne pouvez plus attaquer ce round.";
}
}
ChatMessage.create({
content: msg,
user: game.user._id,
whisper: [game.user._id, ChatMessage.getWhisperRecipients("GM")]
});
let impactRecul = this._computeImpactRecul(attaque);
const agilite = this.defender.data.data.carac.agilite.value;
let rollRecul = await RdDResolutionTable.rollData({ caracValue: 10, finalLevel: impactRecul, showDice: false });
if (rollRecul.isSuccess) {
rollData.show.recul = 'encaisse';
} else if (rollRecul.isETotal) {
rollData.show.recul = 'chute';
}
else {
let chute = await RdDResolutionTable.rollData({ caracValue: agilite, finalLevel: impactRecul, showDice: false });
rollData.show.recul = (chute.isSuccess)
? 'recul'
: 'chute';
}
}
}
_isAttaqueCauseRecul(attaque) {
return attaque.particuliereAttaque == 'force' || attaque.tactique == 'charge';
}
_computeImpactRecul(attaque) {
return Misc.toInt(this.defender.data.data.carac.taille.value) - (attaque.forceValue + attaque.arme.data.dommagesReels);
}
/* -------------------------------------------- */
_sendMessageEncaisser(rollData) {
let message = "<strong>" + this.defender.name + "</strong> doit:" + this._buildMessageEncaisser(rollData);