300419cbad
Décompter uniquemlent si le jet n'est pas une particulière Sur attaque particulière, décompter si le choix n'est pas une attaque en rapidité
1287 lines
50 KiB
JavaScript
1287 lines
50 KiB
JavaScript
import { ChatUtility } from "./chat-utility.js";
|
||
import { RdDItemArme } from "./item-arme.js";
|
||
import { RdDItemCompetence } from "./item-competence.js";
|
||
import { RdDItemCompetenceCreature } from "./item-competencecreature.js";
|
||
import { Misc } from "./misc.js";
|
||
import { RdDBonus } from "./rdd-bonus.js";
|
||
import { RdDResolutionTable } from "./rdd-resolution-table.js";
|
||
import { RdDRoll } from "./rdd-roll.js";
|
||
import { RdDRollTables } from "./rdd-rolltables.js";
|
||
import { ReglesOptionelles } from "./regles-optionelles.js";
|
||
|
||
/* -------------------------------------------- */
|
||
const premierRoundInit = [
|
||
{ pattern: 'hast', init: 3.90 },
|
||
{ pattern: 'lance', init: 3.85 },
|
||
{ pattern: 'baton', init: 3.80 },
|
||
{ pattern: 'doubledragonne', init: 3.75 },
|
||
{ pattern: 'esparlongue', init: 3.70 },
|
||
{ pattern: 'epeedragonne', init: 3.65 },
|
||
{ pattern: 'epeebatarde', init: 3.60 },
|
||
{ pattern: 'epeecyane', init: 3.55 },
|
||
{ pattern: 'epeesorde', init: 3.50 },
|
||
{ pattern: 'grandehache', init: 3.45 },
|
||
{ pattern: 'bataille', init: 3.40 },
|
||
{ pattern: 'epeegnome', init: 3.35 },
|
||
{ pattern: 'masse', init: 3.30 },
|
||
{ pattern: 'gourdin', init: 3.25 },
|
||
{ pattern: 'fléau', init: 3.20 },
|
||
{ pattern: 'dague', init: 3.15 },
|
||
{ pattern: 'autre', init: 3.10 },
|
||
];
|
||
|
||
/* -------------------------------------------- */
|
||
export class RdDCombatManager extends Combat {
|
||
|
||
static init() {
|
||
/* -------------------------------------------- */
|
||
Hooks.on("getCombatTrackerEntryContext", (html, options) => {
|
||
RdDCombatManager.pushInitiativeOptions(html, options);
|
||
});
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
cleanItemUse() {
|
||
for (let turn of this.turns) {
|
||
turn.actor.resetItemUse()
|
||
}
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
cleanSonne() {
|
||
for (let combatant of this.data.combatants) {
|
||
if (combatant.actor) {
|
||
combatant.actor.verifierSonneRound(this.current.round);
|
||
}
|
||
else {
|
||
ui.notifications.warn(`Le combatant ${combatant.name} n'est pas associé à un acteur!`)
|
||
}
|
||
}
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
async nextRound() {
|
||
//console.log('New round !');s
|
||
this.cleanItemUse();
|
||
this.cleanSonne();
|
||
return super.nextRound();
|
||
}
|
||
|
||
/************************************************************************************/
|
||
async rollInitiative(ids, formula = undefined, messageOptions = {}) {
|
||
console.log(`${game.data.system.data.title} | Combat.rollInitiative()`, ids, formula, messageOptions);
|
||
// Structure input data
|
||
ids = typeof ids === "string" ? [ids] : ids;
|
||
const currentId = this.combatant._id;
|
||
// calculate initiative
|
||
for (let cId = 0; cId < ids.length; cId++) {
|
||
const c = this.getCombatant(ids[cId]);
|
||
//if (!c) return results;
|
||
|
||
let rollFormula = formula; // Init per default
|
||
if (!rollFormula) {
|
||
let armeCombat, competence;
|
||
if (c.actor.data.type == 'creature' || c.actor.data.type == 'entite') {
|
||
for (const competenceItem of c.actor.data.items) {
|
||
if (competenceItem.data.iscombat) {
|
||
competence = duplicate(competenceItem);
|
||
}
|
||
}
|
||
rollFormula = "2+( (" + RdDCombatManager.calculInitiative(competence.data.niveau, competence.data.carac_value) + ")/100)";
|
||
} else {
|
||
for (const item of c.actor.data.items) {
|
||
if (item.type == "arme" && item.data.equipe) {
|
||
armeCombat = duplicate(item);
|
||
}
|
||
}
|
||
let compName = (armeCombat == undefined) ? "Corps à corps" : armeCombat.data.competence;
|
||
competence = RdDItemCompetence.findCompetence(c.actor.data.items, compName);
|
||
let bonusEcaille = (armeCombat && armeCombat.data.magique) ? armeCombat.data.ecaille_efficacite : 0;
|
||
rollFormula = "2+( (" + RdDCombatManager.calculInitiative(competence.data.niveau, c.actor.data.data.carac[competence.data.defaut_carac].value, bonusEcaille) + ")/100)";
|
||
}
|
||
}
|
||
//console.log("Combatat", c);
|
||
const roll = super._getInitiativeRoll(c, rollFormula);
|
||
if (roll.total <= 0) roll.total = 0.00;
|
||
console.log("Compute init for", rollFormula, roll.total);
|
||
await this.updateEmbeddedEntity("Combatant", { _id: c._id, initiative: roll.total });
|
||
|
||
// Send a chat message
|
||
let rollMode = messageOptions.rollMode || game.settings.get("core", "rollMode");
|
||
let messageData = mergeObject(
|
||
{
|
||
speaker: {
|
||
scene: canvas.scene._id,
|
||
actor: c.actor ? c.actor._id : null,
|
||
token: c.token._id,
|
||
alias: c.token.name,
|
||
sound: CONFIG.sounds.dice,
|
||
},
|
||
flavor: `${c.token.name} a fait son jet d'Initiative (${messageOptions.initInfo})
|
||
<br>
|
||
`,
|
||
},
|
||
messageOptions
|
||
);
|
||
roll.toMessage(messageData, { rollMode, create: true });
|
||
|
||
RdDCombatManager.processPremierRoundInit();
|
||
}
|
||
return this;
|
||
};
|
||
|
||
/* -------------------------------------------- */
|
||
static calculInitiative(niveau, caracValue, bonusEcaille = 0) {
|
||
let base = niveau + Math.floor(caracValue / 2);
|
||
base += bonusEcaille;
|
||
return "1d6" + (base >= 0 ? "+" : "") + base;
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
/** Retourne une liste triée d'armes avec le split arme1 main / arme 2 main */
|
||
static finalizeArmeList(armes, competences, carac) {
|
||
// Gestion des armes 1/2 mains
|
||
let armesEquipe = [];
|
||
for (const arme of armes) {
|
||
if (arme.data.equipe) {
|
||
armesEquipe.push(arme);
|
||
let comp = competences.find(c => c.name == arme.data.competence);
|
||
arme.data.initiative = RdDCombatManager.calculInitiative(arme.data.niveau, carac[comp.data.defaut_carac].value);
|
||
// Dupliquer les armes pouvant être à 1 main et 2 mains en patchant la compétence
|
||
if (arme.data.unemain && !arme.data.deuxmains) {
|
||
arme.data.mainInfo = "(1m)";
|
||
} else if (!arme.data.unemain && arme.data.deuxmains) {
|
||
arme.data.mainInfo = "(2m)";
|
||
} else if (arme.data.unemain && arme.data.deuxmains) {
|
||
arme.data.mainInfo = "(1m)";
|
||
let arme2main = duplicate(arme);
|
||
arme2main.data.mainInfo = "(2m)";
|
||
arme2main.data.dommages = arme2main.data.dommages.split("/")[1]; // Existence temporaire uniquement dans la liste des armes, donc OK
|
||
arme2main.data.competence = arme2main.data.competence.replace(" 1 main", " 2 mains"); // Replace !
|
||
let comp = competences.find(c => c.name == arme2main.data.competence);
|
||
arme2main.data.niveau = comp.data.niveau;
|
||
arme2main.data.initiative = RdDCombatManager.calculInitiative(arme2main.data.niveau, carac[comp.data.defaut_carac].value);
|
||
armesEquipe.push(arme2main);
|
||
}
|
||
}
|
||
}
|
||
return armesEquipe.sort((a, b) => {
|
||
const nameA = a.name + (a.data.mainInfo ?? '');
|
||
const nameB = b.name + (b.data.mainInfo ?? '');
|
||
if (nameA > nameB) return 1;
|
||
if (nameA < nameB) return -1;
|
||
return 0;
|
||
});
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
static buildListeActionsCombat(combatant) {
|
||
if (combatant.actor == undefined) {
|
||
ui.notifications.warn(`Le combatant ${combatant.name} n'est pas associé à un acteur, impossible de déterminer ses actions de combat!`)
|
||
return [];
|
||
}
|
||
let items = combatant.actor.data.items;
|
||
let actions = []
|
||
if (combatant.actor.isCreature()) {
|
||
actions = actions.concat(items.filter(it => RdDItemCompetenceCreature.isCompetenceAttaque(it))
|
||
.map(competence => RdDItemCompetenceCreature.toArme(competence)));
|
||
} else {
|
||
// Recupération des items 'arme'
|
||
let armes = items.filter(it => RdDItemArme.isArmeUtilisable(it))
|
||
.map(arme => duplicate(arme)) /* pas de changements aux armes d'origine */
|
||
.concat(RdDItemArme.mainsNues());
|
||
|
||
let competences = items.filter(it => it.type == 'competence');
|
||
actions = actions.concat(RdDCombatManager.finalizeArmeList(armes, competences, combatant.actor.data.data.carac));
|
||
|
||
actions.push({ name: "Draconic", data: { initOnly: true, competence: "Draconic" } });
|
||
}
|
||
|
||
actions.push({ name: "Autre action", data: { initOnly: true, competence: "Autre action" } });
|
||
for (let index = 0; index < actions.length; index++) {
|
||
actions[index].index = index;
|
||
}
|
||
return actions;
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
static processPremierRoundInit() {
|
||
// Check if we have the whole init !
|
||
if (game.user.isGM && game.combat.current.round == 1) {
|
||
let initMissing = game.combat.data.combatants.find(it => !it.initiative);
|
||
if (!initMissing) { // Premier round !
|
||
for (let combatant of game.combat.data.combatants) {
|
||
let arme = combatant.initiativeData?.arme;
|
||
//console.log("Parsed !!!", combatant, initDone, game.combat.current, arme);
|
||
if (arme && arme.type == "arme") {
|
||
for (let initData of premierRoundInit) {
|
||
if (arme.data.initpremierround.toLowerCase().includes(initData.pattern)) {
|
||
let msg = `<h4>L'initiative de ${combatant.actor.name} a été modifiée !</h4>
|
||
<hr>
|
||
<div>
|
||
Etant donné son ${arme.name}, son initative pour ce premier round est désormais de ${initData.init}.
|
||
</div>`
|
||
ChatMessage.create({ content: msg });
|
||
game.combat.setInitiative(combatant._id, initData.init);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
static incDecInit(combatantId, incDecValue) {
|
||
const combatant = game.combat.getCombatant(combatantId);
|
||
let initValue = combatant.initiative + incDecValue;
|
||
game.combat.setInitiative(combatantId, initValue);
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
static pushInitiativeOptions(html, options) {
|
||
for (let i = 0; i < options.length; i++) {
|
||
let option = options[i];
|
||
if (option.name == 'COMBAT.CombatantReroll') { // Replace !
|
||
option.name = "Sélectionner l'initiative...";
|
||
option.condition = true;
|
||
option.icon = '<i class="far fa-question-circle"></i>';
|
||
option.callback = target => {
|
||
RdDCombatManager.displayInitiativeMenu(html, target.data('combatant-id'));
|
||
}
|
||
}
|
||
}
|
||
options = [
|
||
{ name: "Incrémenter initiative", condition: true, icon: '<i class="fas fa-plus"></i>', callback: target => { RdDCombatManager.incDecInit(target.data('combatant-id'), +0.01); } },
|
||
{ name: "Décrémenter initiative", condition: true, icon: '<i class="fas fa-minus"></i>', callback: target => { RdDCombatManager.incDecInit(target.data('combatant-id'), -0.01); } }
|
||
].concat(options);
|
||
}
|
||
/* -------------------------------------------- */
|
||
static rollInitiativeCompetence(combatantId, arme) {
|
||
const combatant = game.combat.getCombatant(combatantId);
|
||
if (combatant.actor == undefined) {
|
||
ui.notifications.warn(`Le combatant ${combatant.name} n'est pas associé à un acteur, impossible de déterminer ses actions de combat!`)
|
||
return [];
|
||
}
|
||
|
||
let initInfo = "";
|
||
let initOffset = 0;
|
||
let caracForInit = 0;
|
||
let compNiveau = 0;
|
||
let competence = { name: "Aucune" };
|
||
if (combatant.actor.getSurprise() == "totale") {
|
||
initOffset = -1; // To force 0
|
||
initInfo = "Surprise Totale"
|
||
} else if (combatant.actor.getSurprise() == "demi") {
|
||
initOffset = 0;
|
||
initInfo = "Demi Surprise"
|
||
} else if (arme.name == "Autre action") {
|
||
initOffset = 2;
|
||
initInfo = "Autre Action"
|
||
} else if (arme.name == "Draconic") {
|
||
initOffset = 7;
|
||
initInfo = "Draconic"
|
||
} else {
|
||
initOffset = 3; // Melée = 3.XX
|
||
competence = RdDItemCompetence.findCompetence(combatant.actor.data.items, arme.data.competence);
|
||
compNiveau = competence.data.niveau;
|
||
initInfo = arme.name + " / " + arme.data.competence;
|
||
|
||
if (combatant.actor.data.type == 'creature' || combatant.actor.data.type == 'entite') {
|
||
caracForInit = competence.data.carac_value;
|
||
if (competence.data.categorie == "lancer") {
|
||
initOffset = 5;
|
||
}
|
||
} else {
|
||
caracForInit = combatant.actor.data.data.carac[competence.data.defaut_carac].value;
|
||
if (competence.data.categorie == "lancer") { // Offset de principe pour les armes de jet
|
||
initOffset = 4;
|
||
}
|
||
if (competence.data.categorie == "tir") { // Offset de principe pour les armes de jet
|
||
initOffset = 5;
|
||
}
|
||
if (competence.data.categorie == "melee") { // Offset de principe pour les armes de jet
|
||
initOffset = 3;
|
||
}
|
||
}
|
||
}
|
||
let malus = combatant.actor.getEtatGeneral(); // Prise en compte état général
|
||
// Cas des créatures et entités vs personnages
|
||
let rollFormula = initOffset + "+ ( (" + RdDCombatManager.calculInitiative(compNiveau, caracForInit) + " + " + malus + ") /100)";
|
||
// Garder la trace de l'arme/compétence utilisée pour l'iniative
|
||
combatant.initiativeData = { arme: arme } // pour reclasser l'init au round 0
|
||
game.combat.rollInitiative(combatantId, rollFormula, { initInfo: initInfo });
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
static displayInitiativeMenu(html, combatantId) {
|
||
console.log("Combatant ; ", combatantId);
|
||
const combatant = game.combat.getCombatant(combatantId);
|
||
let armesList = RdDCombatManager.buildListeActionsCombat(combatant);
|
||
|
||
// Build the relevant submenu
|
||
if (armesList) {
|
||
let menuItems = [];
|
||
for (let arme of armesList) {
|
||
menuItems.push({
|
||
name: arme.data.competence,
|
||
icon: "<i class='fas fa-dice-d6'></i>",
|
||
callback: target => { RdDCombatManager.rollInitiativeCompetence(combatantId, arme) }
|
||
});
|
||
}
|
||
new ContextMenu(html, ".directory-list", menuItems).render();
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
export class RdDCombat {
|
||
|
||
static init() {
|
||
this.initStorePasseArmes();
|
||
Hooks.on("updateCombat", (combat, data) => { RdDCombat.onUpdateCombat(combat, data) });
|
||
Hooks.on("preDeleteCombat", (combat, options) => { RdDCombat.onPreDeleteCombat(combat, options); });
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
static initStorePasseArmes() {
|
||
game.system.rdd.combatStore = {
|
||
attaques: {},
|
||
defenses: {}
|
||
};
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
static onSocketMessage(sockmsg) {
|
||
switch (sockmsg.msg) {
|
||
case "msg_encaisser":
|
||
return RdDCombat.onMsgEncaisser(sockmsg.data);
|
||
case "msg_defense":
|
||
return RdDCombat.onMsgDefense(sockmsg.data);
|
||
}
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
static onUpdateCombat(combat, data) {
|
||
if (combat.data.round != 0 && combat.turns && combat.data.active) {
|
||
RdDCombat.combatNouveauTour(combat);
|
||
}
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
static onPreDeleteCombat(combat, options) {
|
||
if (game.user.isGM) {
|
||
combat.cleanItemUse();
|
||
ChatUtility.removeChatMessageContaining(`<div data-combatid="${combat.id}" data-combatmessage="actor-turn-summary">`)
|
||
/*
|
||
* TODO: support de plusieurs combats parallèles
|
||
* il faudrait avoir un id de combat en plus de celui de passe d'armes
|
||
*/
|
||
for (const key in game.system.rdd.combatStore.attaques) {
|
||
const attackerRoll = game.system.rdd.combatStore.attaques[key];
|
||
ChatUtility.removeChatMessageContaining(`<div data-passearme="${attackerRoll.passeArme}">`);
|
||
}
|
||
for (const key in game.system.rdd.combatStore.defenses) {
|
||
const defenderRoll = game.system.rdd.combatStore.defenses[key];
|
||
ChatUtility.removeChatMessageContaining(`<div data-passearme="${defenderRoll.passeArme}">`);
|
||
}
|
||
RdDCombat.initStorePasseArmes();
|
||
}
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
static combatNouveauTour(combat) {
|
||
let turn = combat.turns.find(t => t.tokenId == combat.current.tokenId);
|
||
if (game.user.isGM) {
|
||
// seul le GM notifie le status
|
||
this.displayActorCombatStatus(combat, turn.actor);
|
||
// TODO Playaudio for player??
|
||
}
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
static isActive() {
|
||
return true;
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
static createUsingTarget(attacker) {
|
||
const target = RdDCombat.getTarget();
|
||
if (target == undefined) {
|
||
ui.notifications.warn((game.user.targets?.size ?? 0) > 1
|
||
? "Vous devez choisir <strong>une seule</strong> cible à attaquer!"
|
||
: "Vous devez choisir une cible à attaquer!");
|
||
}
|
||
const defender = target?.actor;
|
||
const defenderTokenId = target?.data._id;
|
||
return this.create(attacker, defender, defenderTokenId, target)
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
static getTarget() {
|
||
if (game.user.targets && game.user.targets.size == 1) {
|
||
for (let target of game.user.targets) {
|
||
return target;
|
||
}
|
||
}
|
||
return undefined;
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
static _storeAttaque(attackerId, attackerRoll) {
|
||
game.system.rdd.combatStore.attaques[attackerId] = duplicate(attackerRoll);
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
static _getAttaque(attackerId) {
|
||
return game.system.rdd.combatStore.attaques[attackerId];
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
static _deleteAttaque(attackerId) {
|
||
delete game.system.rdd.combatStore.attaques[attackerId];
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
static _storeDefense(defenderRoll) {
|
||
game.system.rdd.combatStore.defenses[defenderRoll.passeArme] = duplicate(defenderRoll);
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
static _getDefense(passeArme) {
|
||
return game.system.rdd.combatStore.defenses[passeArme];
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
static _deleteDefense(passeArme) {
|
||
delete game.system.rdd.combatStore.defenses[passeArme];
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
static create(attacker, defender, defenderTokenId, target = undefined) {
|
||
return new RdDCombat(attacker, defender, defenderTokenId, target)
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
static createForAttackerAndDefender(attackerId, defenderTokenId) {
|
||
const attacker = game.actors.get(attackerId);
|
||
if (defenderTokenId) {
|
||
const defenderToken = canvas.tokens.get(defenderTokenId);
|
||
const defender = defenderToken.actor;
|
||
|
||
return RdDCombat.create(attacker, defender, defenderTokenId);
|
||
}
|
||
return RdDCombat.createUsingTarget(attacker)
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
static onMsgEncaisser(data) {
|
||
let attackerRoll = RdDCombat._getAttaque(data.attackerId); // Retrieve the rolldata from the store
|
||
|
||
if (game.user.id === data.gmId) { // Seul le GM effectue l'encaissement sur la fiche
|
||
let attacker = data.attackerId ? game.actors.get(data.attackerId) : null;
|
||
let defender = canvas.tokens.get(data.defenderTokenId).actor;
|
||
|
||
defender.encaisserDommages(attackerRoll, attacker);
|
||
}
|
||
|
||
RdDCombat._deleteDefense(attackerRoll.passeArme);
|
||
RdDCombat._deleteAttaque(data.attackerId);
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
static onMsgDefense(msg) {
|
||
let defenderToken = canvas.tokens.get(msg.defenderTokenId);
|
||
if (defenderToken) {
|
||
if (!game.user.isGM && !game.user.character) { // vérification / sanity check
|
||
ui.notifications.error("Le joueur " + game.user.name + " n'est connecté à aucun personnage. Impossible de continuer.");
|
||
return;
|
||
}
|
||
if ((game.user.isGM && !defenderToken.actor.hasPlayerOwner) || (defenderToken.actor.hasPlayerOwner && (game.user.character._id == defenderToken.actor.data._id))) {
|
||
const rddCombat = RdDCombat.createForAttackerAndDefender(msg.attackerId, msg.defenderTokenId);
|
||
const defenderRoll = msg.defenderRoll;
|
||
RdDCombat._storeAttaque(msg.attackerId, defenderRoll.attackerRoll);
|
||
RdDCombat._storeDefense(defenderRoll);
|
||
rddCombat.removeChatMessageActionsPasseArme(defenderRoll.passeArme);
|
||
rddCombat._chatMessageDefense(msg.paramChatDefense);
|
||
}
|
||
}
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
static _callJetDeVie(event) {
|
||
let actorId = event.currentTarget.attributes['data-actorId'].value;
|
||
let actor = game.actors.get(actorId);
|
||
actor.jetVie();
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
static registerChatCallbacks(html) {
|
||
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 => {
|
||
const rddCombat = RdDCombat.createForAttackerAndDefender(
|
||
event.currentTarget.attributes['data-attackerId']?.value,
|
||
event.currentTarget.attributes['data-defenderTokenId']?.value);
|
||
|
||
rddCombat.onEvent(button, event);
|
||
event.preventDefault();
|
||
});
|
||
}
|
||
html.on("click", '#chat-jet-vie', event => {
|
||
event.preventDefault();
|
||
RdDCombat._callJetDeVie(event);
|
||
});
|
||
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
constructor(attacker, defender, defenderTokenId, target) {
|
||
this.attacker = attacker;
|
||
this.defender = defender;
|
||
this.target = target;
|
||
this.attackerId = this.attacker.data._id;
|
||
this.defenderId = this.defender.data._id;
|
||
this.defenderTokenId = defenderTokenId;
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
async onEvent(button, event) {
|
||
const attackerRoll = RdDCombat._getAttaque(this.attackerId);
|
||
if (!attackerRoll) {
|
||
ui.notifications.warn("Action automatisée impossible, le jet de l'attaquant a été perdu (suite à un raffraichissement?)")
|
||
return;
|
||
}
|
||
const defenderRoll = game.system.rdd.combatStore.defenses[attackerRoll.passeArme];
|
||
const defenderTokenId = event.currentTarget.attributes['data-defenderTokenId']?.value;
|
||
|
||
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': 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, defenderRoll, { defenseChance: true }));
|
||
case '#appel-destinee-attaque': return this.attacker.appelDestinee(
|
||
() => this.attaqueSignificative(attackerRoll),
|
||
() => { });
|
||
case '#appel-destinee-defense': return this.defender.appelDestinee(
|
||
() => this.defenseDestinee(attackerRoll),
|
||
() => { });
|
||
}
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
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.significativeRequise(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, defenderRoll, attackerRoll.essais);
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
defenseDestinee(attackerRoll) {
|
||
let defenderRoll = RdDCombat._getDefense(attackerRoll.passeArme);
|
||
if (defenderRoll) {
|
||
ui.notifications.info('Défense significative grâce à la destinée')
|
||
RdDResolutionTable.significativeRequise(defenderRoll.rolled);
|
||
this.removeChatMessageActionsPasseArme(defenderRoll.passeArme);
|
||
if (defenderRoll.arme) {
|
||
this._onParadeNormale(defenderRoll);
|
||
}
|
||
else {
|
||
this._onEsquiveNormale(defenderRoll);
|
||
}
|
||
}
|
||
else {
|
||
ui.notifications.warn("Appel à la destinée impossible, la passe d'armes est déjà terminée!")
|
||
}
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
afficherOptionsDefense(attackerRoll, defenderRoll, essais) {
|
||
ui.notifications.info("La chance n'est pas avec vous");
|
||
this._sendMessageDefense(attackerRoll, defenderRoll, essais);
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
removeChatMessageActionsPasseArme(passeArme) {
|
||
if (game.settings.get("foundryvtt-reve-de-dragon", "supprimer-dialogues-combat-chat")) {
|
||
ChatUtility.removeChatMessageContaining(`<div data-passearme="${passeArme}">`);
|
||
}
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
static isEchec(rollData) {
|
||
switch (rollData.ajustements.surprise.used) {
|
||
case 'totale': return true;
|
||
}
|
||
return rollData.rolled.isEchec;
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
static isEchecTotal(rollData) {
|
||
if (!rollData.attackerRoll && rollData.ajustements.surprise.used) {
|
||
return rollData.rolled.isEchec;
|
||
}
|
||
return rollData.rolled.isETotal;
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
static isParticuliere(rollData) {
|
||
if (!rollData.attackerRoll && rollData.ajustements.surprise.used) {
|
||
return false;
|
||
}
|
||
return rollData.rolled.isPart;
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
static isReussite(rollData) {
|
||
switch (rollData.ajustements.surprise.used) {
|
||
case 'totale': return false;
|
||
}
|
||
return rollData.rolled.isSuccess;
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
async attaque(competence, arme = undefined) {
|
||
if (!await this.accorderEntite('avant-attaque')) {
|
||
return;
|
||
}
|
||
|
||
let rollData = this._prepareAttaque(competence, arme);
|
||
console.log("RdDCombat.attaque >>>", rollData);
|
||
if (arme) {
|
||
this.attacker.verifierForceMin(arme);
|
||
}
|
||
|
||
const dialog = await RdDRoll.create(this.attacker, rollData,
|
||
{
|
||
html: 'systems/foundryvtt-reve-de-dragon/templates/dialog-competence.html',
|
||
options: { height: 540 }
|
||
}, {
|
||
name: 'jet-attaque',
|
||
label: 'Attaque: ' + (arme?.name ?? competence.name),
|
||
callbacks: [
|
||
this.attacker.createCallbackExperience(),
|
||
{ action: r => this.removeChatMessageActionsPasseArme(r.passeArme) },
|
||
{ condition: r => arme && !RdDCombat.isParticuliere(r), action: r => this.attacker.incDecItemUse(arme._id) },
|
||
{ 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) },
|
||
{ condition: RdDCombat.isEchecTotal, action: r => this._onAttaqueEchecTotal(r) },
|
||
]
|
||
});
|
||
dialog.render(true);
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
_prepareAttaque(competence, arme) {
|
||
let rollData = {
|
||
passeArme: randomID(16),
|
||
coupsNonMortels: false,
|
||
competence: competence,
|
||
surprise: this.attacker.getSurprise(true),
|
||
surpriseDefenseur: this.defender.getSurprise(true),
|
||
essais: {}
|
||
};
|
||
rollData.diviseurSignificative = this._getDiviseurSignificative(rollData);
|
||
|
||
if (this.attacker.isCreature()) {
|
||
RdDItemCompetenceCreature.setRollDataCreature(rollData);
|
||
}
|
||
else if (arme) {
|
||
// Usual competence
|
||
rollData.arme = RdDItemArme.armeUneOuDeuxMains(arme, RdDItemCompetence.isArmeUneMain(competence));
|
||
}
|
||
else {
|
||
// sans armes: à mains nues
|
||
rollData.arme = RdDItemArme.mainsNues({ niveau: competence.data.niveau });
|
||
}
|
||
return rollData;
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
async _onAttaqueParticuliere(rollData) {
|
||
RdDCombat._storeAttaque(this.attackerId, rollData);
|
||
|
||
// 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({
|
||
alias: this.attacker.name,
|
||
whisper: ChatUtility.getWhisperRecipientsAndGMs(this.attacker.name),
|
||
content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-demande-attaque-particuliere.html', {
|
||
alias: this.attacker.name,
|
||
attackerId: this.attackerId,
|
||
defenderTokenId: this.defenderTokenId,
|
||
isFinesse: isMeleeDiffNegative,
|
||
isRapide: isMeleeDiffNegative && rollData.arme.data.rapide,
|
||
passeArme: rollData.passeArme
|
||
})
|
||
});
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
async _onAttaqueNormale(attackerRoll) {
|
||
console.log("RdDCombat.onAttaqueNormale >>>", attackerRoll);
|
||
|
||
attackerRoll.dmg = RdDBonus.dmg(attackerRoll, this.attacker.getBonusDegat(), this.defender.isEntiteCauchemar());
|
||
let defenderRoll = { attackerRoll: attackerRoll, passeArme: attackerRoll.passeArme, show: {} }
|
||
// Save rollData for defender
|
||
RdDCombat._storeAttaque(this.attackerId, attackerRoll);
|
||
RdDCombat._storeDefense(defenderRoll)
|
||
|
||
attackerRoll.show = {
|
||
cible: this.target ? this.defender.data.name : 'la cible',
|
||
isRecul: (attackerRoll.particuliere == 'force' || attackerRoll.tactique == 'charge')
|
||
}
|
||
await RdDResolutionTable.displayRollData(attackerRoll, this.attacker, 'chat-resultat-attaque.html');
|
||
|
||
if (!await this.accorderEntite('avant-defense')) {
|
||
return;
|
||
}
|
||
|
||
if (this.target) {
|
||
await this._sendMessageDefense(attackerRoll, defenderRoll);
|
||
}
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
async _sendMessageDefense(attackerRoll, defenderRoll, essaisPrecedents = undefined) {
|
||
console.log("RdDCombat._sendMessageDefense", attackerRoll, defenderRoll, essaisPrecedents, " / ", this.attacker, this.target, this.attackerId, attackerRoll.competence.data.categorie);
|
||
|
||
this.removeChatMessageActionsPasseArme(attackerRoll.passeArme);
|
||
if (essaisPrecedents) {
|
||
mergeObject(attackerRoll.essais, essaisPrecedents, { overwrite: true });
|
||
}
|
||
|
||
// # utilisation esquive
|
||
let esquiveUsage = 0;
|
||
let esquive = this.defender.getCompetence("esquive");
|
||
if (esquive) {
|
||
esquiveUsage = this.defender.getItemUse(esquive._id);
|
||
}
|
||
|
||
const paramChatDefense = {
|
||
passeArme: attackerRoll.passeArme,
|
||
essais: attackerRoll.essais,
|
||
defender: this.defender,
|
||
attacker: this.attacker,
|
||
attackerId: this.attackerId,
|
||
esquiveUsage: esquiveUsage,
|
||
defenderTokenId: this.defenderTokenId,
|
||
mainsNues: attackerRoll.dmg.mortalite != 'mortel' && this.defender.getCompetence("Corps à corps"),
|
||
armes: this._filterArmesParade(this.defender, attackerRoll.competence, attackerRoll.arme),
|
||
diffLibre: attackerRoll.ajustements?.diffLibre?.value ?? 0,
|
||
attaqueParticuliere: attackerRoll.particuliere,
|
||
attaqueCategorie: attackerRoll.competence.data.categorie,
|
||
attaqueArme: attackerRoll.arme,
|
||
surprise: this.defender.getSurprise(true),
|
||
dmg: attackerRoll.dmg,
|
||
};
|
||
|
||
const selfMessage = essaisPrecedents != undefined;
|
||
if (!selfMessage && (!game.user.isGM || this.defender.hasPlayerOwner)) {
|
||
this._socketSendMessageDefense(paramChatDefense, defenderRoll);
|
||
}
|
||
else {
|
||
await this._chatMessageDefense(paramChatDefense);
|
||
}
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
async _chatMessageDefense(paramDemandeDefense) {
|
||
ChatMessage.create({
|
||
// message privé: du défenseur à lui même (et aux GMs)
|
||
speaker: ChatMessage.getSpeaker(this.defender, canvas.tokens.get(this.defenderTokenId)),
|
||
alias: this.attacker.name,
|
||
whisper: ChatUtility.getWhisperRecipientsAndGMs(this.defender.name),
|
||
content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-demande-defense.html', paramDemandeDefense),
|
||
});
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
_socketSendMessageDefense(paramChatDefense, defenderRoll) {
|
||
// envoyer le message au destinataire
|
||
game.socket.emit("system.foundryvtt-reve-de-dragon", {
|
||
msg: "msg_defense", data: {
|
||
attackerId: this.attacker?.data._id,
|
||
defenderId: this.defender?.data._id,
|
||
defenderTokenId: this.defenderTokenId,
|
||
defenderRoll: duplicate(defenderRoll),
|
||
paramChatDefense: paramChatDefense,
|
||
rollMode: true
|
||
}
|
||
});
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
_filterArmesParade(defender, competence) {
|
||
let items = defender.data.items;
|
||
items = items.filter(item => RdDItemArme.isArmeUtilisable(item) || RdDItemCompetenceCreature.isCompetenceParade(item));
|
||
for (let item of items) {
|
||
item.data.nbUsage = defender.getItemUse(item._id); // Ajout du # d'utilisation ce round
|
||
}
|
||
switch (competence.data.categorie) {
|
||
case 'tir':
|
||
case 'lancer':
|
||
return items.filter(item => RdDItemArme.getCategorieParade(item) == 'boucliers')
|
||
default:
|
||
// Le fléau ne peut être paré qu’au bouclier p115
|
||
if (competence.name == "Fléau") {
|
||
return items.filter(item => RdDItemArme.getCategorieParade(item) == 'boucliers')
|
||
}
|
||
return items.filter(item => RdDItemArme.getCategorieParade(item));
|
||
}
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
async _onAttaqueEchecTotal(attackerRoll) {
|
||
|
||
RdDCombat._storeAttaque(this.attackerId, attackerRoll);
|
||
|
||
ChatMessage.create({
|
||
whisper: ChatUtility.getWhisperRecipientsAndGMs(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.createChatWithRollMode(this.defender.name, {
|
||
content: `<strong>Maladresse à ${action}!</strong> ` + await RdDRollTables.getMaladresse({ arme: avecArme })
|
||
});
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
async _onAttaqueEchec(rollData) {
|
||
console.log("RdDCombat.onAttaqueEchec >>>", rollData);
|
||
await RdDResolutionTable.displayRollData(rollData, this.attacker, 'chat-resultat-attaque.html');
|
||
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
async choixParticuliere(rollData, choix) {
|
||
console.log("RdDCombat.choixParticuliere >>>", rollData, choix);
|
||
|
||
if (choix != "rapidite") {
|
||
this.attacker.incDecItemUse(rollData.arme._id);
|
||
}
|
||
|
||
this.removeChatMessageActionsPasseArme(rollData.passeArme);
|
||
rollData.particuliere = choix;
|
||
await this._onAttaqueNormale(rollData);
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
async parade(attackerRoll, armeParadeId) {
|
||
let arme = this.defender.getArmeParade(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',
|
||
options: { height: 540 }
|
||
}, {
|
||
name: 'jet-parade',
|
||
label: 'Parade: ' + (arme ? arme.name : rollData.competence.name),
|
||
callbacks: [
|
||
this.defender.createCallbackExperience(),
|
||
{ action: r => this.removeChatMessageActionsPasseArme(r.passeArme) },
|
||
{ condition: r => !RdDCombat.isParticuliere(r), action: r => this.defender.incDecItemUse(armeParadeId) },
|
||
{ condition: RdDCombat.isReussite, action: r => this._onParadeNormale(r) },
|
||
{ condition: RdDCombat.isParticuliere, action: r => this._onParadeParticuliere(r) },
|
||
{ condition: RdDCombat.isEchec, action: r => this._onParadeEchec(r) },
|
||
]
|
||
});
|
||
dialog.render(true);
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
_prepareParade(attackerRoll, armeParade) {
|
||
const compName = armeParade.data.competence;
|
||
const armeAttaque = attackerRoll.arme;
|
||
|
||
let defenderRoll = {
|
||
passeArme: attackerRoll.passeArme,
|
||
diffLibre: attackerRoll.diffLibre,
|
||
attackerRoll: attackerRoll,
|
||
competence: this.defender.getCompetence(compName),
|
||
arme: armeParade,
|
||
surprise: this.defender.getSurprise(true),
|
||
needParadeSignificative: ReglesOptionelles.isUsing('categorieParade') && RdDItemArme.needParadeSignificative(armeAttaque, armeParade),
|
||
needResist: RdDItemArme.needArmeResist(armeAttaque, armeParade),
|
||
carac: this.defender.data.data.carac,
|
||
show: {}
|
||
};
|
||
defenderRoll.diviseurSignificative = this._getDiviseurSignificative(defenderRoll);
|
||
|
||
if (this.defender.isCreature()) {
|
||
RdDItemCompetenceCreature.setRollDataCreature(defenderRoll);
|
||
}
|
||
|
||
return defenderRoll;
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
_getDiviseurSignificative(defenderRoll) {
|
||
let facteurSign = 1;
|
||
if (defenderRoll.surprise == 'demi') {
|
||
facteurSign *= 2;
|
||
}
|
||
if (defenderRoll.needParadeSignificative) {
|
||
facteurSign *= 2;
|
||
}
|
||
if (RdDBonus.isDefenseAttaqueFinesse(defenderRoll)) {
|
||
facteurSign *= 2;
|
||
}
|
||
if (!ReglesOptionelles.isUsing('tripleSignificative')) {
|
||
facteurSign = Math.min(facteurSign, 4);
|
||
}
|
||
return facteurSign;
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
_onParadeParticuliere(defenderRoll) {
|
||
console.log("RdDCombat._onParadeParticuliere >>>", defenderRoll);
|
||
if (!defenderRoll.attackerRoll.isPart) {
|
||
// TODO: attaquant doit jouer résistance et peut être désarmé p132
|
||
ChatUtility.createChatWithRollMode(this.defender.name, {
|
||
content: `(à gérer) L'attaquant doit jouer résistance et peut être désarmé (p132)`
|
||
});
|
||
}
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
async _onParadeNormale(defenderRoll) {
|
||
console.log("RdDCombat._onParadeNormale >>>", defenderRoll);
|
||
|
||
await this.computeRecul(defenderRoll);
|
||
await this.computeDeteriorationArme(defenderRoll);
|
||
await RdDResolutionTable.displayRollData(defenderRoll, this.defender, 'chat-resultat-parade.html');
|
||
|
||
RdDCombat._deleteDefense(defenderRoll.passeArme);
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
async _onParadeEchec(defenderRoll) {
|
||
console.log("RdDCombat._onParadeEchec >>>", defenderRoll);
|
||
|
||
await RdDResolutionTable.displayRollData(defenderRoll, this.defender, 'chat-resultat-parade.html');
|
||
|
||
this.removeChatMessageActionsPasseArme(defenderRoll.passeArme);
|
||
this._sendMessageDefense(defenderRoll.attackerRoll, defenderRoll, { defense: true });
|
||
RdDCombat._storeDefense(defenderRoll);
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
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: r => !RdDCombat.isParticuliere(r), action: r => this.defender.incDecItemUse(esquive._id) },
|
||
{ 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) },
|
||
]
|
||
});
|
||
dialog.render(true);
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
_prepareEsquive(attackerRoll, competence) {
|
||
let rollData = {
|
||
passeArme: attackerRoll.passeArme,
|
||
diffLibre: attackerRoll.diffLibre,
|
||
attackerRoll: attackerRoll,
|
||
competence: competence,
|
||
surprise: this.defender.getSurprise(true),
|
||
surpriseDefenseur: this.defender.getSurprise(true),
|
||
carac: this.defender.data.data.carac,
|
||
show: {}
|
||
};
|
||
rollData.diviseurSignificative = this._getDiviseurSignificative(rollData);
|
||
|
||
if (this.defender.isCreature()) {
|
||
RdDItemCompetenceCreature.setRollDataCreature(rollData);
|
||
}
|
||
return rollData;
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
_onEsquiveParticuliere(rollData) {
|
||
console.log("RdDCombat._onEsquiveParticuliere >>>", rollData);
|
||
ChatUtility.createChatWithRollMode(this.defender.name, {
|
||
content: "<strong>Vous pouvez esquiver une deuxième fois!</strong>"
|
||
});
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
async _onEsquiveNormale(defenderRoll) {
|
||
console.log("RdDCombat._onEsquiveNormal >>>", defenderRoll);
|
||
await RdDResolutionTable.displayRollData(defenderRoll, this.defender, 'chat-resultat-esquive.html');
|
||
RdDCombat._deleteDefense(defenderRoll.passeArme);
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
async _onEsquiveEchec(defenderRoll) {
|
||
console.log("RdDCombat._onEsquiveEchec >>>", defenderRoll);
|
||
|
||
await RdDResolutionTable.displayRollData(defenderRoll, this.defender, 'chat-resultat-esquive.html');
|
||
|
||
this.removeChatMessageActionsPasseArme(defenderRoll.passeArme);
|
||
this._sendMessageDefense(defenderRoll.attackerRoll, defenderRoll, { defense: true })
|
||
RdDCombat._storeDefense(defenderRoll);
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
async computeDeteriorationArme(defenderRoll) {
|
||
if (!ReglesOptionelles.isUsing('resistanceArmeParade')) {
|
||
return;
|
||
}
|
||
const attackerRoll = defenderRoll.attackerRoll;
|
||
// Est-ce une parade normale?
|
||
if (defenderRoll.arme && attackerRoll && !defenderRoll.rolled.isPart) {
|
||
// Est-ce que l'attaque est une particulière en force ou une charge
|
||
if (defenderRoll.needResist || this._isForceOuCharge(attackerRoll)) {
|
||
|
||
defenderRoll.show = defenderRoll.show || {}
|
||
|
||
const dmg = attackerRoll.dmg.dmgArme + attackerRoll.dmg.dmgActor;
|
||
let arme = defenderRoll.arme;
|
||
let msg = "";
|
||
if (arme.data.magique) {
|
||
defenderRoll.show.deteriorationArme = 'resiste'; // Par défaut
|
||
if (arme.data.resistance_magique == undefined) arme.data.resistance_magique = 0; // Quick fix
|
||
if (dmg > arme.data.resistance_magique) { // Jet uniquement si dommages supérieur à résistance magique (cf. 274)
|
||
let resistance = Misc.toInt(arme.data.resistance);
|
||
// Jet de résistance de l'arme de parade (p.132)
|
||
let resistRoll = await RdDResolutionTable.rollData({
|
||
caracValue: resistance,
|
||
finalLevel: - dmg,
|
||
showDice: false
|
||
});
|
||
if (!resistRoll.rolled.isSuccess) {
|
||
let perteResistance = (dmg - arme.data.resistance_magique)
|
||
resistance -= perteResistance;
|
||
defenderRoll.show.deteriorationArme = resistance <= 0 ? 'brise' : 'perte';
|
||
defenderRoll.show.perteResistance = perteResistance;
|
||
this.defender.updateEmbeddedEntity("OwnedItem", { _id: defenderRoll.arme._id, 'data.resistance': resistance });
|
||
}
|
||
}
|
||
} else {
|
||
let resistance = Misc.toInt(arme.data.resistance);
|
||
// Jet de résistance de l'arme de parade (p.132)
|
||
let resistRoll = await RdDResolutionTable.rollData({
|
||
caracValue: resistance,
|
||
finalLevel: - dmg,
|
||
showDice: false
|
||
});
|
||
if (resistRoll.rolled.isSuccess) { // Perte de résistance
|
||
defenderRoll.show.deteriorationArme = 'resiste';
|
||
} else {
|
||
resistance -= dmg;
|
||
defenderRoll.show.deteriorationArme = resistance <= 0 ? 'brise' : 'perte';
|
||
defenderRoll.show.perteResistance = dmg;
|
||
this.defender.updateEmbeddedEntity("OwnedItem", { _id: defenderRoll.arme._id, 'data.resistance': resistance });
|
||
}
|
||
}
|
||
// Si l'arme de parade n'est pas un bouclier, jet de désarmement (p.132)
|
||
if (ReglesOptionelles.isUsing('defenseurDesarme') && resistance > 0 && RdDItemArme.getCategorieParade(defenderRoll.arme) != 'boucliers') {
|
||
let desarme = await RdDResolutionTable.rollData({
|
||
caracValue: this.defender.getForce(),
|
||
finalLevel: Misc.toInt(defenderRoll.competence.data.niveau) - dmg,
|
||
showDice: false
|
||
});
|
||
defenderRoll.show.desarme = desarme.rolled.isEchec;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
async computeRecul(defenderRoll) { // Calcul du recul (p. 132)
|
||
const attackerRoll = defenderRoll.attackerRoll;
|
||
if (ReglesOptionelles.isUsing('recul') && this._isForceOuCharge(attackerRoll)) {
|
||
const impact = this._computeImpactRecul(attackerRoll);
|
||
const rollRecul = await RdDResolutionTable.rollData({ caracValue: 10, finalLevel: impact });
|
||
if (rollRecul.rolled.isSuccess) {
|
||
defenderRoll.show.recul = 'encaisse';
|
||
} else if (rollRecul.rolled.isETotal || this._isReculCauseChute(impact)) {
|
||
defenderRoll.show.recul = 'chute';
|
||
await this.defender.addStatusEffectById('prone');
|
||
}
|
||
else {
|
||
defenderRoll.show.recul = 'recul';
|
||
}
|
||
}
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
async _isReculCauseChute(impact) {
|
||
const agilite = this.defender.getAgilite();
|
||
const chute = await RdDResolutionTable.rollData({ caracValue: agilite, finalLevel: impact });
|
||
return chute.rolled.isEchec;
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
_isForceOuCharge(attaque) {
|
||
return attaque.particuliere == 'force' || attaque.tactique == 'charge';
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
_computeImpactRecul(attaque) {
|
||
const taille = this.defender.getTaille();
|
||
const force = this.attacker.getForce();
|
||
const dommages = attaque.arme.data.dommagesReels;
|
||
return taille - (force + dommages);
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
async encaisser(attackerRoll, defenderTokenId) {
|
||
defenderTokenId = defenderTokenId || this.defenderTokenId;
|
||
console.log("RdDCombat.encaisser >>>", attackerRoll, defenderTokenId);
|
||
|
||
let defenderRoll = RdDCombat._getDefense(attackerRoll.passeArme);
|
||
if (!defenderRoll) {
|
||
ui.notifications.warn("Cette passe d'arme est déjà terminée!")
|
||
return;
|
||
}
|
||
if (defenderRoll?.rolled && RdDCombat.isEchecTotal(defenderRoll)) {
|
||
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, defenderRoll);
|
||
} else { // envoi à un GM: les joueurs n'ont pas le droit de modifier les personnages qu'ils ne possèdent pas
|
||
game.socket.emit("system.foundryvtt-reve-de-dragon", {
|
||
msg: "msg_encaisser",
|
||
data: {
|
||
attackerId: this.attackerId,
|
||
defenderTokenId: defenderTokenId,
|
||
attackerRoll: attackerRoll,
|
||
gmId: game.users.entities.find(u => u.isGM)?.id,
|
||
}
|
||
});
|
||
}
|
||
RdDCombat._deleteDefense(attackerRoll.passeArme);
|
||
this.removeChatMessageActionsPasseArme(attackerRoll.passeArme);
|
||
}
|
||
|
||
/* -------------------------------------------- */
|
||
/* 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 async displayActorCombatStatus(combat, actor) {
|
||
let data = {
|
||
combatId: combat._id,
|
||
alias: actor.name,
|
||
etatGeneral: actor.getEtatGeneral(),
|
||
isSonne: actor.getSonne(),
|
||
blessuresStatus: actor.computeResumeBlessure(),
|
||
SConst: actor.getSConst(),
|
||
actorId: actor.data._id,
|
||
isGrave: false,
|
||
isCritique: false
|
||
}
|
||
if (actor.countBlessuresByName("critiques") > 0) { // Pour éviter le cumul grave + critique
|
||
data.isCritique = true;
|
||
} else if (actor.countBlessuresByName("graves") > 0) {
|
||
data.isGrave = true;
|
||
}
|
||
|
||
ChatUtility.createChatWithRollMode(actor.name, {
|
||
content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-actor-turn-summary.html`, data)
|
||
});
|
||
}
|
||
}
|