cb4b255b35
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>
999 lines
36 KiB
JavaScript
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);
|
|
}
|
|
|
|
}
|