Files
fvtt-mournblade-cyd-2-0/modules/mournblade-cyd2-utility.js
T
uberwald cb4b255b35 Fix: Remove remaining static method from inside init() function
The getItemValueSC static method was still incorrectly placed inside the
init() method, causing 'Unexpected strict mode reserved word' error at line 103.
This moves it to the class level with calculateItemValueSC.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-07 21:36:14 +02:00

999 lines
36 KiB
JavaScript

/* -------------------------------------------- */
import { MournbladeCYD2Combat } from "./mournblade-cyd2-combat.js";
import { MournbladeCYD2Commands } from "./mournblade-cyd2-commands.js";
/* -------------------------------------------- */
const __distanceDifficulte = { "porteecourte": 5, "porteemoyenne": 9, "porteelongue": 14 }
const __tireurDeplacement = { immobile: 0, lent: 3, rapide: 5 }
const __cibleCouvert = { aucun: 0, leger: 5, complet: 10 }
const __tailleCible = { normal: 0, main: 10, enfant: 3, maison: -10 }
/* -------------------------------------------- */
export class MournbladeCYD2Utility {
/* -------------------------------------------- */
// Helper pour calculer la valeur totale d'un item en SC (Sous de Cuivre / Pièces de Bronze)
// Conversion selon le lore Mournblade :
// 1 SA (Sou d'Argent / Pièce d'Argent) = 10 PB (Pièces de Bronze / Sous de Cuivre)
// 1 PO (Pièce d'Or) = 10 SA = 100 PB
// Donc : 1 PA (Pièce d'Argent) = 10 SC, 1 PO (Pièce d'Or) = 100 SC
static calculateItemValueSC(prixpo, prixca, prixsc) {
const po = parseInt(prixpo) || 0;
const ca = parseInt(prixca) || 0;
const sc = parseInt(prixsc) || 0;
return po * 100 + ca * 10 + sc;
}
// Helper pour calculer la valeur SC d'un item avec quantité
static getItemValueSC(item) {
const value = this.calculateItemValueSC(item.system?.prixpo, item.system?.prixca, item.system?.prixsc);
const quantity = item.system?.quantite || 1;
return value * quantity;
}
/* -------------------------------------------- */
static async init() {
Hooks.on('renderChatLog', (log, html, data) => MournbladeCYD2Utility.chatListeners(html))
Hooks.on("getChatMessageContextOptions", (html, options) => MournbladeCYD2Utility.chatRollMenu(html, options))
Hooks.on("getCombatTrackerEntryContext", (html, options) => {
MournbladeCYD2Utility.pushInitiativeOptions(html, options);
})
this.rollDataStore = {}
this.defenderStore = {}
MournbladeCYD2Commands.init()
Handlebars.registerHelper('count', function (list) {
return list.length;
})
Handlebars.registerHelper('includes', function (array, val) {
return array.includes(val);
})
Handlebars.registerHelper('upper', function (text) {
return text.toUpperCase();
})
Handlebars.registerHelper('lower', function (text) {
return text.toLowerCase()
})
Handlebars.registerHelper('upperFirst', function (text) {
if (typeof text !== 'string') return text
return text.charAt(0).toUpperCase() + text.slice(1)
})
Handlebars.registerHelper('notEmpty', function (list) {
return list.length > 0;
})
Handlebars.registerHelper('mul', function (a, b) {
return parseInt(a) * parseInt(b);
})
Handlebars.registerHelper('subtract', function (a, b) {
return parseInt(a) - parseInt(b);
})
// Helper Handlebars pour les templates
Handlebars.registerHelper('calculateItemValueSC', function (prixpo, prixca, prixsc) {
return MournbladeCYD2Utility.calculateItemValueSC(prixpo, prixca, prixsc);
});
// Helper pour localiser les valeurs d'allégeance
Handlebars.registerHelper('localizeAllegiance', function (value) {
if (!value) return value;
const allegianceMap = {
'tous': 'MNBL.all',
'chaos': 'MNBL.chaos',
'loi': 'MNBL.law',
'betes': 'MNBL.betes',
'elementaires': 'MNBL.elementaires',
'balance': 'MNBL.balance'
};
const key = allegianceMap[value?.toLowerCase?.()] || value;
return game.i18n?.localize?.(key) ?? value;
});
// Helper pour joindre les prédilections sans virgule superflue
Handlebars.registerHelper('joinPredilections', function (predilections) {
if (!predilections || !Array.isArray(predilections)) return '';
return predilections
.filter(pred => pred && pred.acquise && !pred.used)
.map(pred => pred.name)
.join(', ');
});
Handlebars.registerHelper('select', function(value, options) {
const html = options.fn(this);
const escaped = String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
return html.replace(new RegExp(`value="${escaped}"`, 'g'), `value="${value}" selected="selected"`);
})
game.settings.register("fvtt-mournblade-cyd-2-0", "mournblade-cyd2-pause-logo", {
name: "Logo de pause",
scope: "world",
config: true,
requiresReload: true,
default: "mournblade_logo_chaos",
type: String,
choices: { // If choices are defined, the resulting setting will be a select menu
"mournblade_logo_chaos": "Mournblade (Chaos)",
"mournblade_logo_texte": "Mournblade (Texte)"
},
})
// Initialise les listes de sélection dès le hook init (avant le rendu des fiches)
game.system.mournbladecyd2.config.listeNiveauSkill = this.createDirectOptionList(0, 10)
game.system.mournbladecyd2.config.listeNiveauCreature = this.createDirectOptionList(0, 35)
game.system.mournbladecyd2.config.pointsAmeOptions = this.createDirectOptionList(1, 20)
}
/* -------------------------------------------- */
static sortArrayObjectsByName(myArray) {
myArray.sort((a, b) => {
return a.name.localeCompare(b.name);
})
}
/* -------------------------------------------- */
static getModificateurOptions() {
let opt = []
for (let i = -15; i <= 15; i++) {
opt.push(`<option value="${i}">${i}</option>`)
}
return opt.concat("\n")
}
/* -------------------------------------------- */
static getPointAmeOptions() {
let opt = []
for (let i = 1; i <= 20; i++) {
opt.push(`<option value="${i}">${i}</option>`)
}
return opt.concat("\n")
}
/* -------------------------------------------- */
static getAttributs() {
return { adr: "Adresse", pui: "Puissance", cla: "Clairvoyance", pre: "Présence", tre: "Trempe" }
}
/* -------------------------------------------- */
static pushInitiativeOptions(html, options) {
}
/* -------------------------------------------- */
static getSkills() {
return this.skills
}
/* -------------------------------------------- */
static async ready() {
const skills = await MournbladeCYD2Utility.loadCompendium("fvtt-mournblade-cyd-2-0.skills")
this.skills = skills.map(i => i.toObject())
// Setup pause logo
let logoPause = "systems/fvtt-mournblade-cyd-2-0/assets/logos/" + game.settings.get("fvtt-mournblade-cyd-2-0", "mournblade-cyd2-pause-logo") + ".webp"
let logoImg = document.querySelector('#pause').children[0]
logoImg.setAttribute('style', `content: url(${logoPause})`)
}
/* -------------------------------------------- */
static createDirectOptionList(min, max) {
let options = {};
for (let i = min; i <= max; i++) {
options[`${i}`] = `${i}`;
}
return options;
}
static createArrayOptionList(min, max) {
let options = [];
for (let i = min; i <= max; i++) {
options.push({ key: `${i}`, label: `${i}` });
}
return options;
}
/* -------------------------------------------- */
static async loadCompendiumData(compendium) {
const pack = game.packs.get(compendium);
return await pack?.getDocuments() ?? [];
}
/* -------------------------------------------- */
static async loadCompendium(compendium, filter = item => true) {
let compendiumData = await MournbladeCYD2Utility.loadCompendiumData(compendium);
return compendiumData.filter(filter);
}
/* -------------------------------------------- */
static getOptionsStatusList() {
return this.optionsStatusList;
}
/* -------------------------------------------- */
static async chatListeners(html) {
$(html).on("click", '.predilection-reroll', async event => {
let predIdx = event.currentTarget.dataset.predilectionIndex
let messageId = MournbladeCYD2Utility.findChatMessageId(event.currentTarget)
let message = game.messages.get(messageId)
let rollData = message.getFlag("world", "mournblade-cyd2-roll")
let actor = MournbladeCYD2Utility.getActorFromRollData(rollData)
await actor.setPredilectionUsed(rollData.competence._id, predIdx)
rollData.competence = foundry.utils.duplicate(actor.getCompetence(rollData.competence._id))
MournbladeCYD2Utility.rollMournbladeCYD2(rollData)
})
$(html).on("click", '.roll-chat-degat', async event => {
let messageId = MournbladeCYD2Utility.findChatMessageId(event.currentTarget)
let message = game.messages.get(messageId)
let rollData = message.getFlag("world", "mournblade-cyd2-roll")
let actor = MournbladeCYD2Utility.getActorFromRollData(rollData)
actor.rollArmeDegats(rollData.arme._id, rollData.targetVigueur, rollData)
})
$(html).on("click", '.roll-chat-degat-devastateur', async event => {
let messageId = MournbladeCYD2Utility.findChatMessageId(event.currentTarget)
let message = game.messages.get(messageId)
let rollData = message.getFlag("world", "mournblade-cyd2-roll")
let actor = MournbladeCYD2Utility.getActorFromRollData(rollData)
rollData.applyCoupDevastateur = true
actor.rollArmeDegats(rollData.arme._id, rollData.targetVigueur, rollData)
})
}
/* -------------------------------------------- */
static async preloadHandlebarsTemplates() {
const templatePaths = [
'systems/fvtt-mournblade-cyd-2-0/templates/editor-notes-gm.hbs',
'systems/fvtt-mournblade-cyd-2-0/templates/partial-item-header.hbs',
'systems/fvtt-mournblade-cyd-2-0/templates/partial-item-description.hbs',
'systems/fvtt-mournblade-cyd-2-0/templates/partial-item-nav.hbs',
'systems/fvtt-mournblade-cyd-2-0/templates/partial-item-prix.hbs',
'systems/fvtt-mournblade-cyd-2-0/templates/partial-automation.hbs',
'systems/fvtt-mournblade-cyd-2-0/templates/partial-active-effects.hbs',
'systems/fvtt-mournblade-cyd-2-0/templates/partial-item-effects.hbs',
'systems/fvtt-mournblade-cyd-2-0/templates/hud-adversites.hbs',
]
return foundry.applications.handlebars.loadTemplates(templatePaths);
}
/* -------------------------------------------- */
static removeChatMessageId(messageId) {
if (messageId) {
game.messages.get(messageId)?.delete();
}
}
static findChatMessageId(current) {
return MournbladeCYD2Utility.getChatMessageId(MournbladeCYD2Utility.findChatMessage(current));
}
static getChatMessageId(node) {
return node?.attributes.getNamedItem('data-message-id')?.value;
}
static findChatMessage(current) {
return MournbladeCYD2Utility.findNodeMatching(current, it => it.classList.contains('chat-message') && it.attributes.getNamedItem('data-message-id'))
}
static findNodeMatching(current, predicate) {
if (current) {
if (predicate(current)) {
return current;
}
return MournbladeCYD2Utility.findNodeMatching(current.parentElement, predicate);
}
return undefined;
}
/* -------------------------------------------- */
static buildListOptions(min, max) {
let options = ""
for (let i = min; i <= max; i++) {
options += `<option value="${i}">${i}</option>`
}
return options;
}
/* -------------------------------------------- */
static getTarget() {
if (game.user.targets && game.user.targets.size == 1) {
for (let target of game.user.targets) {
return target;
}
}
return undefined;
}
/* -------------------------------------------- */
static getActorFromRollData(rollData) {
let actor = game.actors.get(rollData.actorId)
if (rollData.tokenId) {
let token = canvas.tokens.placeables.find(t => t.id == rollData.tokenId)
if (token) {
actor = token.actor
}
}
return actor
}
/* -------------------------------------------- */
static updateRollData(rollData) {
let id = rollData.rollId;
let oldRollData = this.rollDataStore[id] || {};
let newRollData = foundry.utils.mergeObject(oldRollData, rollData);
this.rollDataStore[id] = newRollData;
}
/* -------------------------------------------- */
static onSocketMessage(msg) {
if (msg.msg == "msg_apply_combativite") {
let defender = game.canvas.tokens.get(msg.data.defenderTokenId)?.actor
if (defender) {
defender.changeEtatCombativite(msg.data.value)
} else {
console.warn("MournbladeCYD2Utility.onSocketMessage : Impossible de trouver le token pour appliquer la combativité", msg.defenderTokenId)
}
}
}
/* -------------------------------------------- */
static chatDataSetup(content, modeOverride, isRoll = false, forceWhisper) {
let chatData = {
user: game.user.id,
rollMode: modeOverride || game.settings.get("core", "rollMode"),
content: content
};
if (["gmroll", "blindroll"].includes(chatData.rollMode)) chatData["whisper"] = ChatMessage.getWhisperRecipients("GM").map(u => u.id);
if (chatData.rollMode === "blindroll") chatData["blind"] = true;
else if (chatData.rollMode === "selfroll") chatData["whisper"] = [game.user];
if (forceWhisper) { // Final force !
chatData["speaker"] = ChatMessage.getSpeaker();
chatData["whisper"] = ChatMessage.getWhisperRecipients(forceWhisper);
}
return chatData;
}
/* -------------------------------------------- */
static async showDiceSoNice(roll, rollMode) {
if (game.modules.get("dice-so-nice")?.active) {
if (game.dice3d) {
let whisper = null;
let blind = false;
rollMode = rollMode ?? game.settings.get("core", "rollMode");
switch (rollMode) {
case "blindroll": //GM only
blind = true;
case "gmroll": // GM + rolling player
whisper = this.getUsers(user => user.isGM);
break;
case "roll": // everybody
whisper = this.getUsers(user => user.active);
break;
case "selfroll":
whisper = [game.user.id];
break;
}
await game.dice3d.showForRoll(roll, game.user, true, whisper, blind);
}
}
}
/* -------------------------------------------- */
static computeMonnaieDetails(valueSC) {
// Conversion selon le lore Mournblade :
// 1 PO = 100 SC, 1 CA (PA) = 10 SC
let po = Math.floor(valueSC / 100)
let pa = Math.floor((valueSC - (po * 100)) / 10)
let sc = valueSC - (po * 100) - (pa * 10)
return {
po, pa, sc, valueSC
}
}
/* -------------------------------------------- */
static computeResult(rollData) {
rollData.diceResult = rollData.roll?.terms?.[0]?.results?.[0]?.result ?? 0
if (rollData.mainDice.includes("d20")) {
let diceValue = rollData.roll?.terms?.[0]?.results?.[0]?.result ?? 0
if (diceValue % 2 == 1) {
rollData.isD20Impair = true
rollData.finalResult -= rollData.roll.terms[0].results[0].result // Substract value
if (diceValue == 1 || diceValue == 11) {
rollData.isDramatique = true
rollData.isSuccess = false
}
}
}
//console.log("Result : ", rollData
this.computeResultQuality(rollData)
}
/* -------------------------------------------- */
static computeResultQuality(rollData) {
if (rollData.difficulte > 0 && !rollData.isDramatique) {
rollData.isSuccess = (rollData.finalResult >= rollData.difficulte)
rollData.isHeroique = ((rollData.finalResult - rollData.difficulte) >= 10)
rollData.isDramatique = ((rollData.finalResult - rollData.difficulte) <= -10)
}
}
/* -------------------------------------------- */
static applyCombativite(rollData, value) {
if (game.user.isGM) {
const token = game.canvas.tokens.get(rollData.defenderTokenId)
const defender = token?.actor
if (!defender) {
console.warn("MournbladeCYD2Utility.applyCombativite : token défenseur introuvable", rollData.defenderTokenId)
return
}
defender.changeEtatCombativite(value)
} else {
game.socket.emit("system.fvtt-mournblade-cyd-2-0", { msg: "msg_apply_combativite", data: { defenderTokenId: rollData.defenderTokenId, value } });
}
}
/* -------------------------------------------- */
static async rollMournbladeCYD2(rollData) {
let actor = this.getActorFromRollData(rollData)
if (rollData.attrKey == "tochoose") { // No attr selected, force address
rollData.attrKey = "adr"
}
if (!rollData.attr) {
rollData.actionImg = "systems/fvtt-mournblade-cyd-2-0/assets/icons/" + actor.system.attributs[rollData.attrKey].labelnorm + ".webp"
rollData.attr = foundry.utils.duplicate(actor.system.attributs[rollData.attrKey])
}
if (rollData.attrKey2 != "none") {
rollData.attr2 = foundry.utils.duplicate(actor.system.attributs[rollData.attrKey2])
}
if (rollData.maitriseId != "none") {
rollData.selectedMaitrise = rollData.maitrises.find(p => p.id == rollData.maitriseId)
rollData.diceFormula = "2" + rollData.mainDice + "kh"
} else {
rollData.diceFormula = "1" + rollData.mainDice
}
//console.log("BEFORE COMP", rollData)
if (rollData.competence) {
rollData.predilections = foundry.utils.duplicate(rollData.competence.system.predilections || [])
let compmod = (rollData.competence.system.niveau == 0) ? -3 : 0
rollData.diceFormula += `+${rollData.attr.value}+${rollData.competence.system.niveau}+${rollData.modificateur}+${compmod}`
if (rollData.selectedTalents && rollData.selectedTalents.length > 0) {
for (let id of rollData.selectedTalents) {
let talent = rollData.talents.find(t => t._id == id)
let bonusOK = true
if (talent.system.baCost) {
bonusOK = actor.checkBonneAventure(talent.system.baCost)
if (bonusOK) {
actor.changeBonneAventure(-talent.system.baCost)
} else {
ui.notifications.warn("Vous n'avez pas assez de points de Bonne Aventure !")
}
}
if (bonusOK) {
rollData.diceFormula += `+${talent.system.bonus}`
}
}
}
rollData.diceFormula += `+${rollData.bonusMalusContext}`
} else if (rollData.attr2) {
rollData.diceFormula += `+${rollData.attr.value}+${rollData.attr2.value}+${rollData.modificateur}+${rollData.bonusMalusContext}`
} else {
const attrPart = rollData.multiplier > 1 ? `${rollData.attr.value}*${rollData.multiplier}` : `${rollData.attr.value}`
rollData.diceFormula += `+${attrPart}+${rollData.modificateur}+${rollData.bonusMalusContext}`
}
// Bonus arme naturelle en défense
if (rollData.bonusArmeNaturelle) {
rollData.diceFormula += `+${rollData.bonusArmeNaturelle}`
}
if (rollData.attaquantsMultiples) {
rollData.diceFormula += `+3`
}
if (rollData.hasAmbidextre) {
if (rollData.ambidextre1) {
rollData.diceFormula += `-3`
} else if (rollData.ambidextre2) {
rollData.diceFormula += `-6`
}
}
if (rollData.defenseurAuSol) {
rollData.diceFormula += `+3`
}
if (rollData.defenseurAveugle) {
rollData.diceFormula += `+10`
}
if (rollData.defenseurDeDos) {
rollData.diceFormula += `+5`
}
if (rollData.defenseurRestreint) {
rollData.diceFormula += `+3`
}
if (rollData.defenseurImmobilise) {
rollData.diceFormula += `+5`
}
if (rollData.soutiens > 0) { // 1 soutien = +3, 2 soutiens = +4, 3 soutiens = +5
rollData.diceFormula += `+${rollData.soutiens + 2}`
}
if (rollData.arme?.system.isDistance) {
rollData.difficulte = __distanceDifficulte[rollData.distanceTir]
rollData.difficulte += __tireurDeplacement[rollData.tireurDeplacement]
rollData.difficulte += __cibleCouvert[rollData.cibleCouvert]
rollData.difficulte += __tailleCible[rollData.tailleCible]
rollData.difficulte += rollData.cibleDeplace ? 3 : 0
rollData.difficulte += rollData.cibleCaC ? 3 : 0
rollData.difficulte += rollData.protectionDefenseur
}
if (rollData.attaqueDesarme) {
rollData.difficulte += 10
}
// Ajout adversités
rollData.diceFormula += `-${rollData.nbAdversites}`
if (rollData.arme && rollData.arme.type == "arme") {
rollData.diceFormula += `+${rollData.arme.system.bonusmaniementoff}`
}
// Gestion de la feinte éventuelle
rollData.nbCombativitePerdu = 1
let myRoll = await new Roll(rollData.diceFormula).roll()
await this.showDiceSoNice(myRoll, game.settings.get("core", "rollMode"))
rollData.roll = foundry.utils.duplicate(myRoll)
console.log(">>>> ", myRoll)
rollData.finalResult = myRoll.total
this.computeResult(rollData)
// Rune post-roll: calculate duration and apply soul cost
if (rollData.rune) {
rollData.runeduree = Math.ceil(rollData.runeame / 3)
if (rollData.runemode == "inscrire") {
rollData.runeduree *= 2
}
let subAme = rollData.runeame
if (!rollData.isSuccess && !rollData.isDramatique) {
subAme = Math.ceil(rollData.runeame / 2)
}
rollData.runeAmeCout = subAme
actor.subPointsAme(rollData.runemode, subAme)
}
if (rollData.isInit) {
actor.setFlag("world", "last-initiative", rollData.finalResult)
}
if (rollData.feinte) {
actor.changeBonneAventure(-1)
if (rollData.isHeroique) {
rollData.nbCombativitePerdu = "vaincu"
} else if (rollData.isSuccess) {
rollData.nbCombativitePerdu = 2
}
}
this.createChatWithRollMode(rollData.alias, {
content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-mournblade-cyd-2-0/templates/chat-generic-result.hbs`, rollData)
}, rollData)
if ((rollData.coupBas || rollData.arme) && rollData.isSuccess && rollData.defenderTokenId) {
this.applyCombativite(rollData, rollData.nbCombativitePerdu)
}
if (rollData.coupBas && rollData.isSuccess && rollData.defenderTokenId) {
const token = game.canvas.tokens.get(rollData.defenderTokenId)
const defender = token?.actor
if (defender) {
defender.incDecAdversite("bleue", -2)
}
}
}
/* -------------------------------------------- */
static getCombativiteList(nbActivite) {
let list = [{ value: String(0), label: "Combatif" }]
for (let i = 1; i < nbActivite - 2; i++) {
list.push({ value: String(i), label: "Eprouvé " + i })
}
list[nbActivite - 2] = { value: String(nbActivite - 2), label: "Affaibli" }
list[nbActivite - 1] = { value: String(nbActivite - 1), label: "Très Affaibli" }
list[nbActivite] = { value: String(nbActivite), label: "Vaincu" }
return list
}
/* -------------------------------------------- */
static getAmeList(nbAme, ameMin = null) {
// ameMin représente le meilleur état accessible (le minimum où l'âme peut remonter)
// Si ameMin = 3 (Stressé 3), la liste commence à Stressé 3 et va jusqu'à Brisé
// Si ameMin = 0 (Serein), tous les états sont disponibles
let minEffectif = ameMin !== null && ameMin !== undefined ? Math.min(ameMin, nbAme) : 0
let list = []
// Ajouter Serein seulement si ameMin <= 0
if (minEffectif <= 0) {
list.push({ value: String(0), label: "Serein" })
}
// Génération des états Stressé
let nbStresseTotal = Math.max(0, nbAme - 3)
for (let i = 1; i <= nbStresseTotal; i++) {
if (i >= minEffectif) {
list.push({ value: String(i), label: "Stressé " + i })
}
}
// Ajout des états finaux
let traumatiseValue = nbAme - 2
let tresTraumatiseValue = nbAme - 1
let briseValue = nbAme
if (traumatiseValue >= minEffectif) {
list.push({ value: String(traumatiseValue), label: "Traumatisé" })
}
if (tresTraumatiseValue >= minEffectif) {
list.push({ value: String(tresTraumatiseValue), label: "Très Traumatisé" })
}
if (briseValue >= minEffectif) {
list.push({ value: String(briseValue), label: "Brisé" })
}
return list
}
/* -------------------------------------------- */
static getAmeMaxList(nbAme) {
// Génère la liste complète des états d'âme pour le dropdown "Max"
let list = [{ value: String(0), label: "Serein" }]
// Génération des états Stressé (de 1 à nbAme-3)
let nbStresse = Math.max(0, nbAme - 3)
for (let i = 1; i <= nbStresse; i++) {
list.push({ value: String(i), label: "Stressé " + i })
}
// Ajout des états finaux
if (nbAme >= 3) {
list.push({ value: String(nbAme - 2), label: "Traumatisé" })
}
if (nbAme >= 2) {
list.push({ value: String(nbAme - 1), label: "Très Traumatisé" })
}
if (nbAme >= 1) {
list.push({ value: String(nbAme), label: "Brisé" })
}
return list
}
/* -------------------------------------------- */
static async bonusRollMournbladeCYD2(rollData) {
rollData.bonusFormula = rollData.addedBonus
console.log("Bonus Roll MournbladeCYD2", rollData.bonusFormula)
if (isNaN(Number(rollData.bonusFormula))) {
let bonusRoll = await new Roll(rollData.bonusFormula).roll()
await this.showDiceSoNice(bonusRoll, game.settings.get("core", "rollMode"));
rollData.bonusRoll = foundry.utils.duplicate(bonusRoll)
rollData.finalResult += Number(rollData.bonusRoll.total)
} else {
rollData.finalResult += Number(rollData.bonusFormula)
console.log("Bonus Roll MournbladeCYD2 2", rollData.finalResult)
}
this.computeResultQuality(rollData)
this.createChatWithRollMode(rollData.alias, {
content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-mournblade-cyd-2-0/templates/chat-generic-result.hbs`, rollData)
}, rollData)
}
/* -------------------------------------------- */
static getUsers(filter) {
return game.users.filter(filter).map(user => user._id);
}
/* -------------------------------------------- */
static getWhisperRecipients(rollMode, name) {
switch (rollMode) {
case "blindroll": return this.getUsers(user => user.isGM);
case "gmroll": return this.getWhisperRecipientsAndGMs(name);
case "selfroll": return [game.user.id];
}
return undefined;
}
/* -------------------------------------------- */
static getWhisperRecipientsAndGMs(name) {
let recep1 = ChatMessage.getWhisperRecipients(name) || [];
return recep1.concat(ChatMessage.getWhisperRecipients('GM'));
}
/* -------------------------------------------- */
static blindMessageToGM(chatOptions) {
let chatGM = foundry.utils.duplicate(chatOptions);
chatGM.whisper = this.getUsers(user => user.isGM);
chatGM.content = "Blinde message of " + game.user.name + "<br>" + chatOptions.content;
console.log("blindMessageToGM", chatGM);
game.socket.emit("system.fvtt-mournblade-cyd-2-0", { msg: "msg_gm_chat_message", data: chatGM });
}
/* -------------------------------------------- */
static async searchItem(dataItem) {
let item
if (dataItem.pack) {
let id = dataItem.id || dataItem._id
let items = await this.loadCompendium(dataItem.pack, item => item.id == id)
item = items[0] || undefined
} else {
item = game.items.get(dataItem.id)
}
return item
}
/* -------------------------------------------- */
static split3Columns(data) {
let array = [[], [], []];
if (data == undefined) return array;
let col = 0;
for (let key in data) {
let keyword = data[key];
keyword.key = key; // Self-reference
array[col].push(keyword);
col++;
if (col == 3) col = 0;
}
return array;
}
/* -------------------------------------------- */
static async createChatMessage(name, rollMode, chatOptions, rollData = undefined) {
switch (rollMode) {
case "blindroll": // GM only
if (!game.user.isGM) {
this.blindMessageToGM(chatOptions);
chatOptions.whisper = [game.user.id];
chatOptions.content = "Message only to the GM";
}
else {
chatOptions.whisper = this.getUsers(user => user.isGM);
}
break;
default:
chatOptions.whisper = this.getWhisperRecipients(rollMode, name);
break;
}
chatOptions.alias = chatOptions.alias || name
let msg = await ChatMessage.create(chatOptions)
console.log("=======>", rollData)
msg.setFlag("world", "mournblade-cyd2-roll", rollData)
}
/* -------------------------------------------- */
static getBasicRollData() {
let rollData = {
rollId: foundry.utils.randomID(16),
rollMode: game.settings.get("core", "rollMode"),
modificateursOptions: this.getModificateurOptions(),
pointAmeOptions: this.getPointAmeOptions(),
difficulte: 0,
modificateur: 0,
bonusMalusContext: 0,
bonusArmeNaturelle: 0,
defenseurAveugle: false,
defenseurDeDos: false,
defenseurAuSol: false,
defenseurRestreint: false,
defenseurImmobilise: false,
tailleCible: "normal",
tireurDeplacement: "immobile",
cibleCouvert: "aucun",
distanceTir: "porteemoyenne",
attaqueCharge: false,
attaqueDesarme: false,
attaqueAmbidextre1: false,
attaqueAmbidextre2: false,
chargeCavalerie: false,
contenir: false,
soutiens: 0
}
return rollData
}
/* -------------------------------------------- */
static updateWithTarget(rollData) {
let target = MournbladeCYD2Utility.getTarget()
if (target) {
rollData.defenderTokenId = target.id
let defender = game.canvas.tokens.get(rollData.defenderTokenId).actor
rollData.armeDefense = defender.getBestDefenseValue()
rollData.armeAttaqueDefenseur = defender.getBestAttackValue()
rollData.targetVigueur = defender.getVigueur()
rollData.protectionDefenseur = defender.getProtection()
if (rollData.immobiliser || rollData.repousser) {
let combatValues = defender.getCombatValues()
rollData.difficulte = combatValues.defenseTotal + (rollData.armeDefense && rollData.cibleconsciente ? 5 : 0)
} else if (rollData.coupBas) {
let combatValues = defender.getCombatValues()
rollData.difficulte = combatValues.defenseTotal
} else if (rollData.assomer) {
rollData.difficulte = 3 + (defender.system.attributs.tre.value * 2)
} else if (rollData.desengager) {
rollData.difficulte = rollData.armeAttaqueDefenseur?.system?.totalOffensif || 0;
} else if (rollData.armeDefense) {
rollData.difficulte = rollData.armeDefense.system.totalDefensif
if (!rollData.desengager && !rollData.arme.system.armenaturelle && !rollData.arme.system.armefortune) {
if (rollData.armeDefense.system.armenaturelle || rollData.armeDefense.system.armefortune) {
rollData.bonusArmeNaturelle = 3
}
}
} else {
ui.notifications.warn("Aucune arme de défense équipée, difficulté manuelle à positionner.")
}
}
}
/* -------------------------------------------- */
static createChatWithRollMode(name, chatOptions, rollData = undefined) {
this.createChatMessage(name, game.settings.get("core", "rollMode"), chatOptions, rollData)
}
/* -------------------------------------------- */
static getActorAlignment(actor) {
const loi = actor.system?.balance?.loi ?? 0
const chaos = actor.system?.balance?.chaos ?? 0
if (loi > chaos) return 'loyal'
if (chaos > loi) return 'chaotique'
return 'neutre'
}
/* -------------------------------------------- */
static applyBonneAventureRoll(li, changed, addedBonus) {
const el = li instanceof HTMLElement ? li : li[0];
let msgId = el.dataset.messageId ?? el.closest("[data-message-id]")?.dataset.messageId
let msg = game.messages.get(msgId)
if (msg) {
let rollData = msg.getFlag("world", "mournblade-cyd2-roll")
let actor = MournbladeCYD2Utility.getActorFromRollData(rollData)
actor.changeBonneAventure(changed)
rollData.isReroll = true
rollData.textBonus = "Bonus de Points d'Aventure"
if (addedBonus == "reroll") {
MournbladeCYD2Utility.rollMournbladeCYD2(rollData)
} else {
rollData.addedBonus = addedBonus
MournbladeCYD2Utility.bonusRollMournbladeCYD2(rollData)
}
}
}
/* -------------------------------------------- */
static applyEclatRoll(li, changed, addedBonus) {
const el = li instanceof HTMLElement ? li : li[0];
let msgId = el.dataset.messageId ?? el.closest("[data-message-id]")?.dataset.messageId
let msg = game.messages.get(msgId)
if (msg) {
let rollData = msg.getFlag("world", "mournblade-cyd2-roll")
let actor = MournbladeCYD2Utility.getActorFromRollData(rollData)
actor.changeEclat(changed)
rollData.isReroll = true
rollData.textBonus = "Bonus d'Eclat"
if (addedBonus == "reroll") {
MournbladeCYD2Utility.rollMournbladeCYD2(rollData)
} else {
rollData.addedBonus = addedBonus
MournbladeCYD2Utility.bonusRollMournbladeCYD2(rollData)
}
}
}
/* -------------------------------------------- */
static chatRollMenu(html, options) {
let canApply = li => {
const el = li instanceof HTMLElement ? li : li[0];
return canvas.tokens.controlled.length && el.querySelector(".mournblade-cyd2-roll");
}
let getActor = function (li) {
const el = li instanceof HTMLElement ? li : li[0];
let message = game.messages.get(el.dataset.messageId)
let rollData = message.getFlag("world", "mournblade-cyd2-roll")
return MournbladeCYD2Utility.getActorFromRollData(rollData)
}
let getRollData = function (li) {
const el = li instanceof HTMLElement ? li : li[0];
let message = game.messages.get(el.dataset.messageId)
return message.getFlag("world", "mournblade-cyd2-roll")
}
let canApplyBA = function (li) {
let rollData = getRollData(li)
let actor = MournbladeCYD2Utility.getActorFromRollData(rollData)
return (!rollData.isReroll && actor.getBonneAventure() > 0)
}
let canApplyPE = function (li) {
let rollData = getRollData(li)
let actor = MournbladeCYD2Utility.getActorFromRollData(rollData)
return (!rollData.isReroll && actor.getEclat() > 0)
}
let isLoyal = function (li) {
return MournbladeCYD2Utility.getActorAlignment(getActor(li)) === 'loyal'
}
let isChaotique = function (li) {
return MournbladeCYD2Utility.getActorAlignment(getActor(li)) === 'chaotique'
}
// Bonne Aventure — loyal : +3 fixe
options.push({
name: "Ajouter +3 (1 point de Bonne Aventure — Loyal)",
icon: "<i class='fas fa-balance-scale'></i>",
condition: li => canApply(li) && canApplyBA(li) && !isChaotique(li),
callback: li => MournbladeCYD2Utility.applyBonneAventureRoll(li, -1, "+3")
})
// Bonne Aventure — chaotique : +1d6
options.push({
name: "Ajouter +1d6 (1 point de Bonne Aventure — Chaotique)",
icon: "<i class='fas fa-dice'></i>",
condition: li => canApply(li) && canApplyBA(li) && !isLoyal(li),
callback: li => MournbladeCYD2Utility.applyBonneAventureRoll(li, -1, "+1d6")
})
// Éclat — loyal : +10
options.push({
name: "Ajouter +10 (1 point d'Éclat — Loyal)",
icon: "<i class='fas fa-star'></i>",
condition: li => canApply(li) && canApplyPE(li) && !isChaotique(li),
callback: li => MournbladeCYD2Utility.applyEclatRoll(li, -1, "+10")
})
// Éclat — chaotique : +1d20
options.push({
name: "Ajouter +1d20 (1 point d'Éclat — Chaotique)",
icon: "<i class='fas fa-dice-d20'></i>",
condition: li => canApply(li) && canApplyPE(li) && !isLoyal(li),
callback: li => MournbladeCYD2Utility.applyEclatRoll(li, -1, "+1d20")
})
// Éclat — relancer (tous)
options.push({
name: "Relancer le dé (1 point d'Éclat)",
icon: "<i class='fas fa-redo'></i>",
condition: li => canApply(li) && canApplyPE(li),
callback: li => MournbladeCYD2Utility.applyEclatRoll(li, -1, "reroll")
})
return options
}
/* -------------------------------------------- */
static async confirmDelete(actorSheet, li) {
let itemId = li.dataset?.itemId ?? li.dataset?.["item-id"];
let msgTxt = "<p>Etes vous certain de vouloir supprimer cet item ?";
let buttons = {
delete: {
icon: '<i class="fas fa-check"></i>',
label: "Oui !",
callback: () => {
actorSheet.actor.deleteEmbeddedDocuments("Item", [itemId]);
actorSheet.render(false);
}
},
cancel: {
icon: '<i class="fas fa-times"></i>',
label: "Non"
}
}
msgTxt += "</p>";
let d = new Dialog({
title: "Confirmer la suppression",
content: msgTxt,
buttons: buttons,
default: "cancel"
});
d.render(true);
}
}