import { ChatUtility } from "../chat-utility.js"; import { HIDE_DICE, SYSTEM_RDD } from "../constants.js"; import { RdDItem } from "../item.js"; import { Misc } from "../misc.js"; import { RdDDice } from "../rdd-dice.js"; const COMPENDIUM_SETTING_PREFIX = 'compendium-'; const CONFIGURABLE_COMPENDIUMS = { 'tables-diverses': { label: "Tables aléatoires", type: "RollTable" }, 'competences': { label: "Compétences", type: "Item" }, 'extrait-poetique': { label: "Extraits poetiques", type: "Item" }, 'queues-de-dragon': { label: "Queues de dragon", type: "Item" }, 'ombres-de-thanatos': { label: "Ombres de Thanatos", type: "Item" }, 'souffles-de-dragon': { label: "Souffles de Dragon", type: "Item" }, 'tarot-draconique': { label: "Tarots draconiques", type: "Item" }, 'rencontres': { label: "Rencontres dans les TMR", type: "Item" }, 'tetes-de-dragon-pour-haut-revants': { label: "Têtes de dragons (haut-rêvant)", type: "Item" }, 'tetes-de-dragon-pour-tous-personnages': { label: "Têtes de dragons (tous)", type: "Item" }, 'faune-flore-mineraux': { label: "Herbes & plantes", type: "Item" }, 'equipement': { label: "Equipements", type: "Item" }, } /** * ======= Gestion des accès aux compendiums systèmes (ou surchargés) ======= */ export class SystemCompendiums extends FormApplication { static initSettings() { Object.keys(CONFIGURABLE_COMPENDIUMS).forEach(compendium => { const definition = CONFIGURABLE_COMPENDIUMS[compendium]; foundry.utils.mergeObject(definition, { compendium: compendium, default: SystemCompendiums._getDefaultCompendium(compendium), setting: SystemCompendiums._getSettingCompendium(compendium) }); game.settings.register(SYSTEM_RDD, definition.setting, { name: definition.label, default: definition.default, scope: "world", config: false, type: String }); }); game.settings.registerMenu(SYSTEM_RDD, "compendium-settings", { name: "Choisir les compendiums système", label: "Compendiums système", hint: "Ouvre la fenêtre de sélection des compendiums système", icon: "fas fa-bars", restricted: true, type: SystemCompendiums }) } static getPack(compendium) { const pack = game.packs.get(compendium); if (pack) { return pack; } return game.packs.get(SystemCompendiums.getCompendium(compendium)) ?? game.packs.get(SystemCompendiums._getDefaultCompendium(compendium)); } static async getPackContent(compendium, docType) { const pack = SystemCompendiums.getPack(compendium); if (pack?.metadata.type == docType) { return await pack.getDocuments(); } return []; } static async getCompetences(actorType) { switch (actorType ?? 'personnage') { case 'personnage': return await SystemCompendiums.getWorldOrCompendiumItems('competence', 'competences'); case 'creature': return await SystemCompendiums.getWorldOrCompendiumItems('competencecreature', 'competences-creatures'); case 'entite': return await SystemCompendiums.getWorldOrCompendiumItems('competencecreature', 'competences-entites'); case 'vehicule': return []; } } /* -------------------------------------------- */ static async getWorldOrCompendiumItems(itemType, compendium) { let items = game.items.filter(it => it.type == itemType); if (compendium) { const ids = items.map(it => it.id); const names = items.map(it => it.name.toLowerCase()); const compendiumItems = await SystemCompendiums.getItems(compendium); items = items.concat(compendiumItems .filter(it => it.type == itemType) .filter(it => !ids.includes(it.id)) .filter(it => !names.includes(it.name.toLowerCase()))); } return items; } static async loadDocument(document) { const pack = game.packs.get(document.pack); return await pack.getDocument(document.id ?? document._id); } static async getItems(compendium, itemType = undefined) { const items = await SystemCompendiums.getPackContent(compendium, 'Item'); return (itemType ? items.filter(it => it.type == itemType) : items); } static async getContent(compendium, type, filter, itemFrequence, sorting) { let elements = await SystemCompendiums.getPackContent(compendium, type); elements = elements.filter(filter).filter(it => itemFrequence(it) > 0); if (sorting) { elements = elements.sort(sorting); } return elements; } /* -------------------------------------------- */ static async loadCompendiumData(compendium) { const pack = game.packs.get(compendium); return await pack?.getDocuments() ?? []; } /* -------------------------------------------- */ static async loadCompendium(compendium, filter = item => true) { let compendiumData = await SystemCompendiums.loadCompendiumData(compendium); return compendiumData.filter(filter); } static async getDefaultItems(compendium) { const pack = game.packs.get(SystemCompendiums._getDefaultCompendium(compendium)); if (pack.metadata.type == 'Item') { return await pack.getDocuments(); } return []; } static getCompendium(compendium) { const setting = CONFIGURABLE_COMPENDIUMS[compendium]?.setting; return setting ? game.settings.get(SYSTEM_RDD, setting) : SystemCompendiums._getDefaultCompendium(compendium); } static _getSettingCompendium(compendium) { return COMPENDIUM_SETTING_PREFIX + compendium; } static _getDefaultCompendium(compendium) { return `${SYSTEM_RDD}.${compendium}`; } constructor(...args) { super(...args); } static get defaultOptions() { const options = super.defaultOptions; foundry.utils.mergeObject(options, { id: "system-compendiums", template: "systems/foundryvtt-reve-de-dragon/templates/settings/system-compendiums.html", height: 'fit-content', width: 600, minimizable: false, closeOnSubmit: true, title: "Compendiums système" }); return options; } getData() { const systemCompendiums = Object.values(CONFIGURABLE_COMPENDIUMS) .map(it => foundry.utils.mergeObject(it, { value: SystemCompendiums.getCompendium(it.compendium) }, { inplace: false })) const availableCompendiums = game.packs.map(pack => { return { name: pack.collection, path: pack.collection.replace('.', " / "), type: pack.metadata.type } }); return foundry.utils.mergeObject(super.getData(), { systemCompendiums: systemCompendiums, availableCompendiums: availableCompendiums }, { inplace: false }) } activateListeners(html) { html.find("select.system-compendium-setting").change((event) => { const compendium = $(event.currentTarget).data('compendium') const value = $(event.currentTarget).val(); const systemCompendium = CONFIGURABLE_COMPENDIUMS[compendium]; game.settings.set(SYSTEM_RDD, systemCompendium.setting, value); }); } } /** * ======= Gestion de jets dans une table correspondant à un compendium ======= */ export class CompendiumTable { constructor(compendium, type, subTypes = undefined, sorting = undefined) { this.compendium = compendium; this.type = type; this.subTypes = subTypes; this.sorting = sorting ?? Misc.ascending(it => it.name); } async getContent(itemFrequence = it => it.system.frequence, filter = it => true) { return await SystemCompendiums.getContent(this.compendium, this.type, it => (!this.subTypes || this.subTypes.includes(it.type)) && itemFrequence(it) > 0 && filter(it), itemFrequence, this.sorting); } async buildTable(itemFrequence = it => it.system.frequence, filter = it => true) { const elements = await this.getContent(itemFrequence, filter); return CompendiumTableHelpers.buildTable(elements, itemFrequence); } async getRandom(itemFrequence = it => it.system.frequence, filter = it => true, forcedRoll = undefined) { const table = await this.buildTable(itemFrequence, filter); return await CompendiumTableHelpers.getRandom(table, this.type, this.subTypes, forcedRoll, SystemCompendiums.getCompendium(compendium)); } async toChatMessage(itemFrequence = it => it.system.frequence, filter = it => true, typeName = undefined) { const table = await this.buildTable(itemFrequence, filter); await CompendiumTableHelpers.tableToChatMessage(table, this.type, this.subTypes, typeName); return true; } } /** * ======= Gestion de tables correspondant à un compendium ======= */ export class CompendiumTableHelpers { static buildTable(elements, itemFrequence) { let max = 0; const total = elements.map(it => itemFrequence(it)).reduce(Misc.sum(), 0); return elements.map(it => { const frequence = itemFrequence(it); let row = { document: it, frequence: frequence, min: max + 1, max: max + frequence, total: total }; max += frequence; return row; }); } static concatTables(...tables) { const rows = tables.reduce((a, b) => a.concat(b)); let max = 0; const total = rows.map(it => it.frequence).reduce(Misc.sum(), 0); return rows.map(row => { const frequence = row.frequence row.min = max + 1 row.max = max + frequence row.total = total max += frequence return row }) } static async getRandom(table, type, subTypes = ['objet'], forcedRoll = undefined, localisation = undefined) { if (table.length == 0) { const typeName = Misc.typeName(type, subTypes[0]); ui.notifications.warn(`Aucun ${typeName} trouvé dans ${localisation ?? ' les compendiums'}`); return undefined; } return await CompendiumTableHelpers.selectRow(table, forcedRoll); } /* -------------------------------------------- */ static async selectRow(table, forcedRoll = undefined) { if (table.length == 0) { return undefined } const total = table[0].total; const formula = `1d${total}`; if (forcedRoll != undefined && (forcedRoll > total || forcedRoll <= 0)) { ui.notifications.warn(`Jet forcé ${forcedRoll} en dehors de la table [1..${total}], le jet est relancé`); forcedRoll = undefined; } const roll = forcedRoll ? { total: forcedRoll, formula } : await RdDDice.roll(formula, { showDice: HIDE_DICE }); const row = table.find(it => it.min <= roll.total && roll.total <= it.max); row.roll = roll; return row; } /* -------------------------------------------- */ static async tableRowToChatMessage(row, type, options = {showSource: true}) { if (!row) { return; } const flavorContent = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-compendium-table-roll.hbs', { roll: row.roll, document: row.document, typeName: Misc.typeName(type, row.document?.type ?? 'objet'), isGM: game.user.isGM, options }); const messageData = { user: game.user.id, rolls: [row.roll], sound: CONFIG.sounds.dice, content: flavorContent }; await ChatUtility.createChatWithRollMode(messageData) } /* -------------------------------------------- */ static async tableToChatMessage(table, type, subTypes, typeName = undefined) { const flavorContent = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-compendium-table.hbs', { img: RdDItem.getDefaultImg(subTypes[0]), typeName: typeName ?? Misc.typeName(type, subTypes[0]), table, isGM: game.user.isGM, }); const messageData = { user: game.user.id, whisper: [game.user], content: flavorContent }; await ChatUtility.createChatWithRollMode(messageData) } }