/* Common useful functions shared between objects */ import { ChatUtility } from "./chat-utility.js"; import { RdDCombat } from "./rdd-combat.js"; import { Misc } from "./misc.js"; import { Grammar } from "./grammar.js"; import { TMRUtility } from "./tmr-utility.js"; import { DialogItemAchat } from "./achat-vente/dialog-item-achat.js"; import { ReglesOptionnelles } from "./settings/regles-optionnelles.js"; import { RdDDice } from "./rdd-dice.js"; import { RdDItem } from "./item.js"; import { RdDPossession } from "./rdd-possession.js"; import { RdDNameGen } from "./rdd-namegen.js"; import { RdDConfirm } from "./rdd-confirm.js"; import { RdDItemCompetence } from "./item-competence.js"; import { RdDResolutionTable } from "./rdd-resolution-table.js"; import { RdDTimestamp } from "./time/rdd-timestamp.js"; import { RdDRaretes } from "./item/raretes.js"; import { RdDEmpoignade } from "./rdd-empoignade.js"; import { ExperienceLog } from "./actor/experience-log.js"; import { RdDCoeur } from "./coeur/rdd-coeur.js"; import { APP_ASTROLOGIE_REFRESH } from "./sommeil/app-astrologie.js"; /* -------------------------------------------- */ // This table starts at 0 -> niveau -10 const carac_array = ["taille", "apparence", "constitution", "force", "agilite", "dexterite", "vue", "ouie", "odoratgout", "volonte", "intellect", "empathie", "reve", "chance", "melee", "tir", "lancer", "derobee"]; const difficultesLibres = Misc.intArray(0, -11); const ajustementsConditions = Misc.intArray(-10, 11); const ajustementsEncaissement = Misc.intArray(-10, 26); /* -------------------------------------------- */ function _buildAllSegmentsFatigue(max) { const cycle = [5, 2, 4, 1, 3, 0]; const fatigue = [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]; for (let i = 0; i <= max; i++) { const ligneFatigue = foundry.utils.duplicate(fatigue[i]); const caseIncrementee = cycle[i % 6]; ligneFatigue[caseIncrementee]++; ligneFatigue[caseIncrementee + 6]++; ligneFatigue.fatigueMax = 2 * (i + 1); fatigue[i + 1] = ligneFatigue; } return fatigue; } /* -------------------------------------------- */ function _cumulSegmentsFatigue(matrix) { let cumulMatrix = []; for (let line of matrix) { let cumul = foundry.utils.duplicate(line); for (let i = 1; i < 12; i++) { cumul[i] += cumul[i - 1]; } cumulMatrix.push(cumul); } return cumulMatrix; } /* -------------------------------------------- */ export const MAX_ENDURANCE_FATIGUE = 60; const fatigueMatrix = _buildAllSegmentsFatigue(MAX_ENDURANCE_FATIGUE); const cumulFatigueMatrix = _cumulSegmentsFatigue(fatigueMatrix); const fatigueMalus = [0, 0, 0, -1, -1, -1, -2, -3, -4, -5, -6, -7]; // Provides the malus for each segment of fatigue const fatigueLineSize = [3, 6, 7, 8, 9, 10, 11, 12]; const fatigueLineMalus = [0, -1, -2, -3, -4, -5, -6, -7]; /* -------------------------------------------- */ const nomEthylisme = ["Emeché", "Gris", "Pinté", "Pas frais", "Ivre", "Bu", "Complètement fait", "Ivre mort"]; /* -------------------------------------------- */ const definitionsEncaissement = { "mortel": [ { minimum: undefined, maximum: 0, endurance: "0", vie: "0", gravite: -1 }, { minimum: 1, maximum: 10, endurance: "1d4", vie: "0", gravite: 0 }, { minimum: 11, maximum: 15, endurance: "1d6", vie: "0", gravite: 2 }, { minimum: 16, maximum: 19, endurance: "2d6", vie: "2", gravite: 4 }, { minimum: 20, maximum: undefined, endurance: "100", vie: "4 + @over20", gravite: 6 }, ], "non-mortel": [ { minimum: undefined, maximum: 0, endurance: "0", vie: "0", gravite: -1 }, { minimum: 1, maximum: 10, endurance: "1d4", vie: "0", gravite: 0 }, { minimum: 11, maximum: 15, endurance: "1d6", vie: "0", gravite: 0 }, { minimum: 16, maximum: 19, endurance: "2d6", vie: "0", gravite: 2 }, { minimum: 20, maximum: undefined, endurance: "100", vie: "0", gravite: 2 }, ], "entiteincarnee": [ { minimum: undefined, maximum: 0, endurance: "0", vie: "0", gravite: -1 }, { minimum: 1, maximum: 10, endurance: "1d4", vie: "0", gravite: 0 }, { minimum: 11, maximum: 15, endurance: "1d6", vie: "0", gravite: 0 }, { minimum: 16, maximum: 19, endurance: "2d6", vie: "0", gravite: 0 }, { minimum: 20, maximum: undefined, endurance: "3d6 + @over20", vie: "0", gravite: 0 }, ] }; /* -------------------------------------------- */ export class RdDUtility { // persistent handling of conteneur show/hide static afficheContenu = {} /* -------------------------------------------- */ static async init() { Hooks.on("renderChatMessage", async (app, html, msg) => await ChatUtility.onRenderChatMessage(app, html, msg)) Hooks.on("createChatMessage", async (chatMessage, options, id) => await ChatUtility.onCreateChatMessage(chatMessage, options, id)) Hooks.on('renderChatLog', (log, html, chatLog) => RdDUtility.chatListeners(html)) } /* -------------------------------------------- */ static async preloadHandlebarsTemplates() { const templatePaths = [ //Character Sheets 'systems/foundryvtt-reve-de-dragon/templates/actor-sheet.html', 'systems/foundryvtt-reve-de-dragon/templates/actor-creature-sheet.html', 'systems/foundryvtt-reve-de-dragon/templates/actor-entite-sheet.html', 'systems/foundryvtt-reve-de-dragon/templates/actor-vehicule-sheet.html', // sous-parties de feuilles de personnages 'systems/foundryvtt-reve-de-dragon/templates/actor/header-buttons.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/header-etat.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/header-compteurs.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/header-compteurs-creature.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/header-compteurs-entitee.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/header-effects.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/header-hautreve.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/archetype.hbs', 'systems/foundryvtt-reve-de-dragon/templates/actor/vue-detaillee.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/carac-main.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/carac-derivee.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/carac-creature.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/carac-entitee.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/comp-creature.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/comp-possession.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/carac-total.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/competence.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/competence-categorie.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/xp-competences.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/combat.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/blessures.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/blessure.hbs', 'systems/foundryvtt-reve-de-dragon/templates/actor/maladies-poisons.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/possessions.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/resonances.hbs', 'systems/foundryvtt-reve-de-dragon/templates/actor/taches.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/taches.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/oeuvres.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/oeuvre.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/jeux.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/alchimie.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/astrologie.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/chirurgie.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/non-haut-revant.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/haut-revant.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/dragon-queues.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/dragon-queue.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/dragon-souffles.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/dragon-tetes.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/hr-signes-draconiques.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/hr-rencontres.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/hr-sorts.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/hr-sorts-reserve.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/hr-meditations.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/hr-casestmr.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/xp-journal.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/editor-notes-mj.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/inventaire.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/inventaire-item.html', "systems/foundryvtt-reve-de-dragon/templates/actor/inventaire-monnaie.html", 'systems/foundryvtt-reve-de-dragon/templates/actor/liens-animaux.hbs', 'systems/foundryvtt-reve-de-dragon/templates/actor/liens-suivants.hbs', 'systems/foundryvtt-reve-de-dragon/templates/actor/liens-vehicules.hbs', 'systems/foundryvtt-reve-de-dragon/templates/actor/commerce-inventaire.html', 'systems/foundryvtt-reve-de-dragon/templates/actor/commerce-inventaire-item.html', //Items 'systems/foundryvtt-reve-de-dragon/templates/scripts/autocomplete-script.hbs', 'systems/foundryvtt-reve-de-dragon/templates/scripts/autocomplete.hbs', 'systems/foundryvtt-reve-de-dragon/templates/item/boutons-comestible.html', 'systems/foundryvtt-reve-de-dragon/templates/item/icon-arme-broken.hbs', 'systems/foundryvtt-reve-de-dragon/templates/item/temporel.hbs', 'systems/foundryvtt-reve-de-dragon/templates/item/partial-inventaire.html', 'systems/foundryvtt-reve-de-dragon/templates/item/partial-environnement.html', 'systems/foundryvtt-reve-de-dragon/templates/item/partial-tab-environnement.html', 'systems/foundryvtt-reve-de-dragon/templates/item-queue-sheet.html', 'systems/foundryvtt-reve-de-dragon/templates/header-item.html', // partial enums 'systems/foundryvtt-reve-de-dragon/templates/enum-aspect-tarot.html', 'systems/foundryvtt-reve-de-dragon/templates/enum-base-competence.html', 'systems/foundryvtt-reve-de-dragon/templates/enum-caracteristiques.html', 'systems/foundryvtt-reve-de-dragon/templates/enum-categories.html', 'systems/foundryvtt-reve-de-dragon/templates/enum-categorie-ingredient.html', 'systems/foundryvtt-reve-de-dragon/templates/enum-categorie-parade.html', 'systems/foundryvtt-reve-de-dragon/templates/enum-categorie-potion.html', 'systems/foundryvtt-reve-de-dragon/templates/enum-categorie-queue.html', 'systems/foundryvtt-reve-de-dragon/templates/enum-categorie-vehicule.html', 'systems/foundryvtt-reve-de-dragon/templates/enum-competence.html', 'systems/foundryvtt-reve-de-dragon/templates/enum-draconic.html', 'systems/foundryvtt-reve-de-dragon/templates/enum-heures.html', 'systems/foundryvtt-reve-de-dragon/templates/enum-initpremierround.html', 'systems/foundryvtt-reve-de-dragon/templates/enum-mortalite.html', 'systems/foundryvtt-reve-de-dragon/templates/enum-niveau-ethylisme.html', 'systems/foundryvtt-reve-de-dragon/templates/enum-periode.html', 'systems/foundryvtt-reve-de-dragon/templates/enum-rarete.html', 'systems/foundryvtt-reve-de-dragon/templates/enum-tmr-effet.html', 'systems/foundryvtt-reve-de-dragon/templates/enum-tmr-type.html', // Partials 'systems/foundryvtt-reve-de-dragon/templates/coeur/chat-effet-tendre-moment.hbs', 'systems/foundryvtt-reve-de-dragon/templates/coeur/afficher-coeur.hbs', 'systems/foundryvtt-reve-de-dragon/templates/tirage/liste-resultats-recherche.hbs', 'systems/foundryvtt-reve-de-dragon/templates/time/horloge.hbs', 'systems/foundryvtt-reve-de-dragon/templates/voyage/fatigue-actor.hbs', 'systems/foundryvtt-reve-de-dragon/templates/voyage/option-vitesse-fatigue.hbs', 'systems/foundryvtt-reve-de-dragon/templates/common/timestamp.hbs', 'systems/foundryvtt-reve-de-dragon/templates/common/date-heure.hbs', 'systems/foundryvtt-reve-de-dragon/templates/common/periodicite.hbs', 'systems/foundryvtt-reve-de-dragon/templates/common/enum-duree.hbs', 'systems/foundryvtt-reve-de-dragon/templates/common/compendium-link.hbs', 'systems/foundryvtt-reve-de-dragon/templates/partial-description-overflow.html', 'systems/foundryvtt-reve-de-dragon/templates/partial-description-sort.html', 'systems/foundryvtt-reve-de-dragon/templates/partial-roll-ajustements.html', 'systems/foundryvtt-reve-de-dragon/templates/partial-roll-astrologique.hbs', 'systems/foundryvtt-reve-de-dragon/templates/partial-roll-coeur.hbs', 'systems/foundryvtt-reve-de-dragon/templates/partial-roll-competences.html', 'systems/foundryvtt-reve-de-dragon/templates/partial-roll-diffLibre.html', 'systems/foundryvtt-reve-de-dragon/templates/partial-roll-diffFixe.html', 'systems/foundryvtt-reve-de-dragon/templates/partial-roll-diffCondition.html', 'systems/foundryvtt-reve-de-dragon/templates/partial-roll-enctotal.html', 'systems/foundryvtt-reve-de-dragon/templates/partial-roll-forcer.html', 'systems/foundryvtt-reve-de-dragon/templates/partial-roll-moral.html', 'systems/foundryvtt-reve-de-dragon/templates/partial-roll-surenc.html', 'systems/foundryvtt-reve-de-dragon/templates/partial-select-carac.html', 'systems/foundryvtt-reve-de-dragon/templates/partial-item-hautrevant.html', 'systems/foundryvtt-reve-de-dragon/templates/partial-item-frequence.html', 'systems/foundryvtt-reve-de-dragon/templates/partial-item-description.html', 'systems/foundryvtt-reve-de-dragon/templates/roll/explain.hbs', 'systems/foundryvtt-reve-de-dragon/templates/resolution-table.html', // Dialogs 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-resolution.html', 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-competence.html', 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-carac.html', 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-sort.html', 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-encaisser.html', 'systems/foundryvtt-reve-de-dragon/templates/dialog-validation-encaissement.html', 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-meditation.html', 'systems/foundryvtt-reve-de-dragon/templates/dialog-tmr.html', 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-alchimie.html', 'systems/foundryvtt-reve-de-dragon/templates/sommeil/sommeil-actor-moral.hbs', 'systems/foundryvtt-reve-de-dragon/templates/sommeil/astrologie-gardien.hbs', 'systems/foundryvtt-reve-de-dragon/templates/sommeil/astrologie-joueur.hbs', 'systems/foundryvtt-reve-de-dragon/templates/sommeil/astrologie-theme.hbs', // HUD 'systems/foundryvtt-reve-de-dragon/templates/hud-actor-init.html', 'systems/foundryvtt-reve-de-dragon/templates/hud-actor-attaque.html', // messages tchat 'systems/foundryvtt-reve-de-dragon/templates/chat-infojet.html', 'systems/foundryvtt-reve-de-dragon/templates/chat-description.html', 'systems/foundryvtt-reve-de-dragon/templates/chat-info-appel-au-moral.html', 'systems/foundryvtt-reve-de-dragon/templates/chat-info-distance.html', 'systems/foundryvtt-reve-de-dragon/templates/chat-actor-turn-acteur.hbs', 'systems/foundryvtt-reve-de-dragon/templates/chat-actor-turn-sante.hbs', 'systems/foundryvtt-reve-de-dragon/templates/chat-actor-competence-xp.html', 'systems/foundryvtt-reve-de-dragon/templates/chat-actor-carac-xp.html', 'systems/foundryvtt-reve-de-dragon/templates/chat-potionenchantee-chateaudormant.html', 'systems/foundryvtt-reve-de-dragon/templates/chat-fabriquer-potion-base.html', 'systems/foundryvtt-reve-de-dragon/templates/chat-signe-draconique-actor.html' ]; Handlebars.registerHelper('either', (a, b) => a ?? b); Handlebars.registerHelper('computeResolutionScore', (row, col) => RdDResolutionTable.computePercentage(row, col)); Handlebars.registerHelper('computeResolutionChances', (row, col) => RdDResolutionTable.computeChances(row, col)); Handlebars.registerHelper('upperFirst', str => Misc.upperFirst(str ?? 'Null')); Handlebars.registerHelper('lowerFirst', str => Misc.lowerFirst(str ?? 'Null')); Handlebars.registerHelper('upper', str => str?.toUpperCase() ?? ''); Handlebars.registerHelper('lowercase', str => str?.toLowerCase() ?? ''); Handlebars.registerHelper('le', str => Grammar.articleDetermine(str)); Handlebars.registerHelper('apostrophe', (article, str) => Grammar.apostrophe(article, str)); Handlebars.registerHelper('un', str => Grammar.articleIndetermine(str)); Handlebars.registerHelper('accord', (genre, ...args) => Grammar.accord(genre, args)); Handlebars.registerHelper('buildLigneInventaire', (item, options) => { return new Handlebars.SafeString(RdDUtility.buildLigneInventaire(item, options)); }); Handlebars.registerHelper('buildInventaireConteneur', (actorId, itemId, options) => { return new Handlebars.SafeString(RdDUtility.buildInventaireConteneur(actorId, itemId, options)); }); Handlebars.registerHelper('buildContenuConteneur', (item, options) => { return new Handlebars.SafeString(RdDUtility.buildContenuConteneur(item, options)); }); Handlebars.registerHelper('calculerPrixCommercant', item => item.calculerPrixCommercant()); Handlebars.registerHelper('caseTmr-label', coord => TMRUtility.getTMRLabel(coord)); Handlebars.registerHelper('caseTmr-type', coord => TMRUtility.getTMRType(coord)); Handlebars.registerHelper('typeTmr-name', type => TMRUtility.typeTmrName(type)); Handlebars.registerHelper('effetRencontre-name', coord => TMRUtility.typeTmrName(coord)); Handlebars.registerHelper('timestamp-imgSigneHeure', (heure) => { return new Handlebars.SafeString(RdDTimestamp.imgSigneHeure(heure)) }); Handlebars.registerHelper('timestamp-imgSigne', (heure) => { return new Handlebars.SafeString(RdDTimestamp.imgSigne(heure)) }); Handlebars.registerHelper('timestamp-extract', timestamp => new RdDTimestamp(timestamp).toCalendrier()); Handlebars.registerHelper('timestamp-formulesDuree', () => RdDTimestamp.formulesDuree()); Handlebars.registerHelper('timestamp-formulesPeriode', () => RdDTimestamp.formulesPeriode()); Handlebars.registerHelper('array-includes', (array, value) => array.includes(value)); Handlebars.registerHelper('min', (...args) => Math.min(...args.slice(0, -1))); Handlebars.registerHelper('regle-optionnelle', (option) => ReglesOptionnelles.isUsing(option)); Handlebars.registerHelper('trier', list => list.sort((a, b) => a.name.localeCompare(b.name))); Handlebars.registerHelper('filtreTriCompetences', competences => RdDItemCompetence.triVisible(competences)); Handlebars.registerHelper('linkCompendium', (pack, id, name) => RdDUtility.linkCompendium(pack, id, name)); Handlebars.registerHelper('uniteQuantite', (itemId, actorId) => RdDUtility.getItem(itemId, actorId)?.getUniteQuantite()); Handlebars.registerHelper('isFieldInventaireModifiable', (type, field) => RdDItem.isFieldInventaireModifiable(type, field)); Handlebars.registerHelper('rarete-getChamp', (rarete, field) => RdDRaretes.getChamp(rarete, field)); Handlebars.registerHelper('plusMoins', diff => (diff > 0 ? '+' : '') + Math.round(diff)) Handlebars.registerHelper('experienceLog-topic', topic => ExperienceLog.labelTopic(topic)); // Handle v12 removal of this helper Handlebars.registerHelper('select', function (selected, options) { const escapedValue = RegExp.escape(Handlebars.escapeExpression(selected)); const rgx = new RegExp(' value=[\"\']' + escapedValue + '[\"\']'); const html = options.fn(this); return html.replace(rgx, "$& selected"); }); return loadTemplates(templatePaths); } static getItem(itemId, actorId = undefined) { return actorId ? game.actors.get(actorId)?.getItem(itemId) : game.items.get(itemId); } static linkCompendium(pack, id, name) { return `@Compendium[${pack}.${id}]{${name}}`; } /* -------------------------------------------- */ static buildListOptions(min, max) { let options = "" for (let i = min; i <= max; i++) { options += `` } return options; } /* -------------------------------------------- */ static arrayOrEmpty(items) { if (items?.length) { return items; } return []; } /* -------------------------------------------- */ static getNomEthylisme(niveauEthylisme) { return niveauEthylisme > 0 ? 'Aucun' : nomEthylisme[-niveauEthylisme] } /* -------------------------------------------- */ static toggleAfficheContenu(conteneurId) { RdDUtility.afficheContenu[conteneurId] = !RdDUtility.afficheContenu[conteneurId]; } /* -------------------------------------------- */ static getAfficheContenu(conteneurId) { if (conteneurId) return RdDUtility.afficheContenu[conteneurId]; return undefined; } /* -------------------------------------------- */ static buildArbreDeConteneurs(conteneurs, inventaires) { let objetVersConteneur = {}; // Attribution des objets aux conteneurs for (let conteneur of conteneurs) { conteneur.subItems = []; for (let id of conteneur.system.contenu ?? []) { let objet = inventaires.find(objet => (id == objet._id)); if (objet) { objet.estContenu = true; // Permet de filtrer ce qui est porté dans le template objetVersConteneur[id] = conteneur._id; conteneur.subItems.push(objet); } } } for (let conteneur of conteneurs) { conteneur.system.encTotal = RdDUtility.calculEncContenu(conteneur, inventaires); } return objetVersConteneur; } /* -------------------------------------------- */ static calculEncContenu(conteneur, inventaires) { const contenus = (conteneur.system.contenu ?? []).filter(id => id != undefined) .map(id => inventaires.find(it => (id == it.id))) .filter(it => it); let enc = Number(conteneur.system.encombrement ?? 0) * Number(conteneur.system.quantite ?? 1); for (let contenu of contenus) { if (contenu.type == 'conteneur') { enc += RdDUtility.calculEncContenu(contenu, inventaires); } else { enc += Number(contenu.system.encombrement ?? 0) * Number(contenu.system.quantite ?? 1) } } return enc } /* -------------------------------------------- */ // Construit la liste des conteneurs de niveau 1 (c'est à dire non contenu eux-même dans un conteneur) static conteneursRacine(conteneurs) { return conteneurs.filter((conteneur, index, arr) => !conteneur.estContenu); } static prepareOptionsArbreInventaire(item, optionsArbre) { if (!optionsArbre.profondeur) { optionsArbre.profondeur = 1 }; if (!optionsArbre.templateItem) { optionsArbre.templateItem = item.parent?.type == 'commerce' ? "systems/foundryvtt-reve-de-dragon/templates/actor/commerce-inventaire-item.html" : "systems/foundryvtt-reve-de-dragon/templates/actor/inventaire-item.html"; } item.niveau = optionsArbre.profondeur; } /* -------------------------------------------- */ /** * Construit la structure récursive des conteneurs, avec imbrication potentielle */ static buildLigneInventaire(item, options = {}, optionsArbre = { ouvert: false, profondeur: 1 }) { RdDUtility.prepareOptionsArbreInventaire(item, optionsArbre); const isConteneur = item.type == 'conteneur'; const inventaire = { item: item, vide: isConteneur && item.system.contenu.length == 0, ouvert: isConteneur && RdDUtility.getAfficheContenu(item._id), options: options }; optionsArbre.ouvert = inventaire.ouvert const ligneObjet = Handlebars.partials[optionsArbre.templateItem](inventaire); if (isConteneur) { return ligneObjet + RdDUtility.buildContenuConteneur(item, options, optionsArbre); } return ligneObjet; } static buildInventaireConteneur(actorId, itemId, options) { const actor = game.actors.get(actorId) const item = actor?.items.get(itemId) if (item) { return RdDUtility.buildContenuConteneur(item, options, { ouvert: true, profondeur: 1 }); } return ''; } /* -------------------------------------------- */ static buildContenuConteneur(conteneur, options = {}, optionsArbre = {}) { RdDUtility.prepareOptionsArbreInventaire(conteneur, optionsArbre); const display = optionsArbre.ouvert ? 'item-display-show' : 'item-display-hide'; const profondeur = optionsArbre.profondeur; optionsArbre.profondeur++; const lignesContenu = conteneur.subItems.sort(Misc.ascending(it => it.name)) .map(contenu => this.buildLigneInventaire(contenu, options, optionsArbre)); return `"; } /* -------------------------------------------- */ static getCaracArray() { return carac_array; } static getDifficultesLibres() { return difficultesLibres; } static getAjustementsConditions() { return ajustementsConditions; } static getAjustementsEncaissement() { return ajustementsEncaissement; } /* -------------------------------------------- */ static getSegmentsFatigue(maxEndurance) { return fatigueMatrix[Math.min(Math.max(maxEndurance, 1), fatigueMatrix.length)]; } /* -------------------------------------------- */ static calculMalusFatigue(fatigue, endurance) { endurance = Math.min(Math.max(endurance, 1), cumulFatigueMatrix.length); let segments = cumulFatigueMatrix[endurance]; for (let i = 0; i < segments.length; i++) { if (fatigue <= segments[i]) { return fatigueMalus[i] } } return -7; } /* -------------------------------------------- */ static calculFatigueHtml(fatigue, endurance) { return ReglesOptionnelles.isUsing("appliquer-fatigue") ? { malus: RdDUtility.calculMalusFatigue(fatigue, endurance), html: "" + RdDUtility.makeHTMLfatigueMatrix(fatigue, endurance).html() + "
" } : { malus: 0, html: '' }; } /* -------------------------------------------- */ // Build the nice (?) html table used to manage fatigue. // max should be the endurance max value static makeHTMLfatigueMatrix(fatigue, maxEndurance) { const segments = this.getSegmentsFatigue(maxEndurance); return this.makeHTMLfatigueMatrixForSegment(fatigue, segments); } /* -------------------------------------------- */ static makeHTMLfatigueMatrixForSegment(fatigue, segments) { fatigue = Math.max(fatigue, 0); fatigue = Math.min(fatigue, segments.fatigueMax); let table = $("").addClass('table-fatigue'); let segmentIdx = 0; let fatigueCount = 0; for (var line = 0; line < fatigueLineSize.length; line++) { let row = $(""); let segmentsPerLine = fatigueLineSize[line]; row.append(""); while (segmentIdx < segmentsPerLine) { let freeSize = segments[segmentIdx]; for (let col = 0; col < 5; col++) { if (col < freeSize) { if (fatigueCount < fatigue) row.append(""); else row.append("
" + fatigueLineMalus[line] + "X"); fatigueCount++; } else { row.append(""); } } row.append(""); segmentIdx = segmentIdx + 1; } table.append(row); } return table; } /* -------------------------------------------- */ static async getLocalisation(type = 'personnage') { let result = await RdDDice.rollTotal("1d20"); let txt = "" if (type == 'personnage') { if (result <= 3) txt = "Jambe, genou, pied, jarret"; else if (result <= 7) txt = "Hanche, cuisse, fesse"; else if (result <= 9) txt = "Ventre, reins"; else if (result <= 12) txt = "Poitrine, dos"; else if (result <= 14) txt = "Avant-bras, main, coude"; else if (result <= 18) txt = "Epaule, bras, omoplate"; else if (result == 19) txt = "Tête"; else if (result == 20) txt = "Tête (visage)"; } else { if (result <= 7) txt = "Jambes/Pattes"; else if (result <= 18) txt = "Corps"; else if (result <= 20) txt = "Tête"; } return { result: result, label: txt }; } /* -------------------------------------------- */ static async jetEncaissement(rollData, armure, options = { showDice: HIDE_DICE }) { const diff = Math.abs(rollData.diffLibre); let formula = RdDUtility.formuleEncaissement(diff, options) const roll = await RdDDice.roll(formula, options); RdDUtility.remplaceDeMinParDifficulte(roll, diff, options); return await RdDUtility.prepareEncaissement(rollData, roll, armure); } static remplaceDeMinParDifficulte(roll, diff, options) { if (!ReglesOptionnelles.isUsing('degat-minimum-malus-libre-simple')) { return } // 1 dé fait au minmum la difficulté libre const total = options.forceDiceResult?.total; if (total) { const reste = Math.max(total - diff, 1) roll.terms[0].number = reste + diff } else { if (roll.terms[0].results[0].result < diff) { roll.terms[0].results[0].result = diff; } else if (roll.terms[0].results[1].result < diff) { roll.terms[0].results[1].result = diff; } roll._total = roll.terms[0].results[0].result + roll.terms[0].results[1].result; } } static formuleEncaissement(diff, options) { // Chaque dé fait au minimum la difficulté libre if (ReglesOptionnelles.isUsing('degat-minimum-malus-libre')) { return `2d10min${diff}` } return '2d10' } /* -------------------------------------------- */ static async prepareEncaissement(rollData, roll, armure) { // La difficulté d'ataque s'ajoute aux dégâts const bonusDegatsDiffLibre = ReglesOptionnelles.isUsing('degat-ajout-malus-libre') ? Math.abs(rollData.diffLibre ?? 0) : 0 const jetTotal = roll.total + rollData.dmg.total - armure + bonusDegatsDiffLibre const encaissement = RdDUtility._selectEncaissement(jetTotal, rollData.dmg.mortalite); const over20 = Math.max(jetTotal - 20, 0); encaissement.dmg = rollData.dmg; encaissement.dmg.loc = rollData.dmg.loc ?? await RdDUtility.getLocalisation(this.type); encaissement.dmg.loc.label = encaissement.dmg.loc.label ?? 'Corps;'; encaissement.dmg.bonusDegatsDiffLibre = bonusDegatsDiffLibre encaissement.roll = roll; encaissement.armure = armure; encaissement.penetration = rollData.arme?.system.penetration ?? 0; encaissement.total = jetTotal; encaissement.vie = await RdDUtility._evaluatePerte(encaissement.vie, over20); encaissement.endurance = await RdDUtility._evaluatePerte(encaissement.endurance, over20); return encaissement; } /* -------------------------------------------- */ static _selectEncaissement(degats, mortalite) { const table = definitionsEncaissement[mortalite] === undefined ? definitionsEncaissement["mortel"] : definitionsEncaissement[mortalite]; for (let encaissement of table) { if ((encaissement.minimum === undefined || encaissement.minimum <= degats) && (encaissement.maximum === undefined || degats <= encaissement.maximum)) { return foundry.utils.duplicate(encaissement); } } return foundry.utils.duplicate(table[0]); } /* -------------------------------------------- */ static async _evaluatePerte(formula, over20) { let perte = new Roll(formula, { over20: over20 }); await perte.evaluate(); return perte.total; } /* -------------------------------------------- */ static onSocketMessage(sockmsg) { switch (sockmsg.msg) { case "msg_gm_chat_message": return ChatUtility.handleGMChatMessage(sockmsg.data); case "msg_app_astrologie_refresh": return Hooks.callAll(APP_ASTROLOGIE_REFRESH); case "msg_request_nombre_astral": return game.system.rdd.calendrier.requestNombreAstral(sockmsg.data); case "msg_tmr_move": let actor = game.actors.get(sockmsg.data.actorId); if (actor.isOwner || game.user.isGM) { actor.refreshTMRView(); } break; } } /* -------------------------------------------- */ static async chatListeners(html) { RdDCombat.registerChatCallbacks(html) RdDEmpoignade.registerChatCallbacks(html) RdDCoeur.registerChatCallbacks(html) // Gestion spécifique message passeurs html.on("click", '.tmr-passeur-coord a', event => { let coord = event.currentTarget.attributes['data-tmr-coord'].value; let actorId = event.currentTarget.attributes['data-actor-id'].value; let actor = game.actors.get(actorId); actor.tmrApp.positionnerDemiReve(coord); }); // Gestion spécifique des sorts en réserve multiples (ie têtes) html.on("click", '.declencher-sort-reserve', event => { let coord = event.currentTarget.attributes['data-tmr-coord'].value; let sortId = event.currentTarget.attributes['data-sort-id'].value; let actorId = event.currentTarget.attributes['data-actor-id'].value; let actor = game.actors.get(actorId); actor.tmrApp.lancerSortEnReserve(coord, sortId); // TODO: supprimer le message? }); // gestion bouton tchat Possession html.on("click", '.defense-possession', event => { let attackerId = event.currentTarget.attributes['data-attackerId'].value let defenderId = event.currentTarget.attributes['data-defenderId'].value let possessionId = event.currentTarget.attributes['data-possessionId'].value RdDPossession.onDefensePossession(attackerId, defenderId, possessionId) }); // gestion bouton tchat Acheter html.on("click", '.button-acheter', event => { const venteData = DialogItemAchat.preparerAchat(event.currentTarget); if (venteData) { DialogItemAchat.onAcheter(venteData); } }); html.on("click", '.button-creer-acteur', event => RdDNameGen.onCreerActeur(event)); // Gestion du bouton payer html.on("click", '.payer-button', event => { let sommeAPayer = Number(event.currentTarget.attributes['data-somme-a-payer']?.value ?? 0); let actor = RdDUtility.getSelectedActor("Pour effectuer le paiement:"); if (actor) { actor.payerSols(sommeAPayer); ChatUtility.removeChatMessageId(RdDUtility.findChatMessageId(event.currentTarget)); } }); html.on("click", '.rdd-world-content-link', async event => { const htmlElement = html.find(event.currentTarget); const id = htmlElement?.data("id"); const doctype = htmlElement?.data("doctype"); switch (doctype ?? 'Item') { case 'Actor': return game.actors.get(id)?.sheet.render(true); case 'Item': default: return game.items.get(id)?.sheet.render(true); } }); } static findChatMessageId(current) { return RdDUtility.getChatMessageId(RdDUtility.findChatMessage(current)); } static getChatMessageId(node) { return node?.attributes.getNamedItem('data-message-id')?.value; } static findChatMessage(current) { return RdDUtility.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 RdDUtility.findNodeMatching(current.parentElement, predicate); } return undefined; } static getSelectedActor(msgPlayer = undefined) { if (canvas.tokens.controlled.length == 1) { let token = canvas.tokens.controlled[0]; if (token.actor) { return token.actor; } if (msgPlayer != undefined) { msgPlayer += "
le token sélectionné doit être lié à un personnage"; } } if (game.user.character) { return game.user.character; } if (msgPlayer != undefined) { msgPlayer += "
vous pouvez sélectionner un seul token lié à un personnage"; msgPlayer += "
vous devez être connecté comme joueur avec un personnage sélectionné"; ui.notifications.warn(msgPlayer); ChatMessage.create({ content: msgPlayer, whisper: [game.user] }); } return undefined; } /* -------------------------------------------- */ static createMonnaie(name, cout, img = "", enc = 0.01) { let piece = { name: name, type: 'monnaie', img: img, _id: randomID(16), dasystemta: { quantite: 0, cout: cout, encombrement: enc, description: "" } } return piece; } /* -------------------------------------------- */ static afficherDemandePayer(som1, som2) { som1 = (som1) ? som1.toLowerCase() : "0d"; som2 = (som2) ? som2.toLowerCase() : "0d"; let regExp1 = /(\d+)(\w+)/g; let p1 = regExp1.exec(som1); let regExp2 = /(\d+)(\w+)/g; let p2 = regExp2.exec(som2); let deniers = 0; let sols = 0; if (p1[2] == 'd') deniers += Number(p1[1]); if (p1[2] == 's') sols += Number(p1[1]); if (p2[2] == 'd') deniers += Number(p2[1]); if (p2[2] == 's') sols += Number(p2[1]); let sommeAPayer = sols + deniers / 100; let msgPayer = `La somme de ${sols} Sols et ${deniers} Deniers est à payer
Payer` ChatMessage.create({ content: msgPayer }); } /* -------------------------------------------- */ 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 confirmSubActeurDelete(sheet, subActor, htmlToDelete, onSuppression = () => { }) { RdDConfirm.confirmer({ settingConfirmer: "confirmation-supprimer-lien-acteur", content: `

Etes vous certain de vouloir supprimer le lien vers ${subActor.name} ?

`, title: 'Confirmer la suppression', buttonLabel: 'Supprimer le lien', onAction: onSuppression }) } /* -------------------------------------------- */ static async confirmActorItemDelete(sheet, item, htmlToDelete) { const itemId = item.id; const confirmationSuppression = { settingConfirmer: "confirmation-supprimer-" + item.getItemGroup(), content: `

Etes vous certain de vouloir supprimer: ${item.name}?

`, title: `Supprimer ${item.name}`, buttonLabel: "Supprimer", onAction: () => { console.log('Delete : ', itemId); sheet.actor.deleteEmbeddedDocuments('Item', [itemId], { renderSheet: false }); RdDUtility.slideOnDelete(sheet, htmlToDelete); } }; if (item.isConteneurNonVide()) { confirmationSuppression.content += `

Ce conteneur n'est pas vide. Que voulez vous supprimer?

`; confirmationSuppression.settingConfirmer = undefined; RdDConfirm.confirmer(confirmationSuppression, { 'deleteall': { icon: '', label: "Supprimer conteneur et contenu", callback: () => { console.log("Delete : ", itemId); sheet.actor.deleteAllConteneur(itemId, { renderSheet: false }); RdDUtility.slideOnDelete(sheet, htmlToDelete); } } }); } else { RdDConfirm.confirmer(confirmationSuppression) } } static slideOnDelete(sheet, htmlToDelete) { return htmlToDelete?.slideUp(200, () => sheet.render(false)); } /* -------------------------------------------- */ static afficherHeuresChanceMalchance(heureNaissance) { if (game.user.isGM) { const heure = RdDTimestamp.findHeure(heureNaissance - 1); if (heureNaissance && heure) { let ajustement = game.system.rdd.calendrier.getAjustementAstrologique(heureNaissance); const current = game.system.rdd.calendrier.heureCourante(); ChatMessage.create({ content: `A l'heure de ${current.label}, le modificateur de Chance/Malchance est de ${Misc.toSignedString(ajustement)} pour l'heure de naissance ${heure.label}.`, whisper: ChatMessage.getWhisperRecipients("GM") }); } else if (heureNaissance) { ui.notifications.warn(heureNaissance + " ne correspond pas à une heure de naissance"); } else { ui.notifications.warn("Pas d'heure de naissance selectionnée"); } } else { ui.notifications.warn("Vous n'avez pas accès à cette commande"); } } /*-------------------------------------------- */ static checkThanatosXP(compName) { if (compName.includes('Thanatos')) { let message = "Vous avez mis des points d'Expérience dans la Voie de Thanatos !
Vous devez réduire manuellement d'un même montant d'XP une autre compétence Draconique."; ChatMessage.create({ whisper: ChatMessage.getWhisperRecipients(game.user.name), content: message }); } } }