/* -------------------------------------------- */ import { DialogChronologie } from "./dialog-chronologie.js"; import { DialogCreateSigneDraconique } from "./dialog-create-signedraconique.js"; import { DialogStress } from "./dialog-stress.js"; import { RdDItemCompetence } from "./item-competence.js"; import { Misc } from "./misc.js"; import { RdDCarac } from "./rdd-carac.js"; import { RdDDice } from "./rdd-dice.js"; import { RdDMeteo } from "./rdd-meteo.js"; import { RdDNameGen } from "./rdd-namegen.js"; import { RdDResolutionTable } from "./rdd-resolution-table.js"; import { RdDRollResolutionTable } from "./rdd-roll-resolution-table.js"; import { RdDRollTables } from "./rdd-rolltables.js"; import { RdDUtility } from "./rdd-utility.js"; import { TMRUtility } from "./tmr-utility.js"; const rddRollNumeric = /^(\d+)\s*([\+\-]?\d+)?\s*(s)?/; /* -------------------------------------------- */ export class RdDCommands { static init() { game.system.rdd.commands = new RdDCommands(); } constructor() { this.commandsTable = undefined; } _registerCommands() { this.commandsTable = {} this.registerCommand({ path: ["/aide"], func: (content, msg, params) => this.help(msg), descr: "Affiche l'aide pour toutes les commandes" }); this.registerCommand({ path: ["/help"], func: (content, msg, params) => this.help(msg), descr: "Affiche l'aide pour toutes les commandes" }); this.registerCommand({ path: ["/liste", "comp"], func: (content, msg, params) => RdDRollTables.getCompetence('liste'), descr: "Affiche la liste des compétences" }); this.registerCommand({ path: ["/table", "queue"], func: (content, msg, params) => RdDRollTables.getQueue('liste'), descr: "Affiche la table des Queues de Dragon" }); this.registerCommand({ path: ["/table", "ombre"], func: (content, msg, params) => RdDRollTables.getOmbre('liste'), descr: "Affiche la table des Ombres de Thanatos" }); this.registerCommand({ path: ["/table", "tetehr"], func: (content, msg, params) => RdDRollTables.getTeteHR('liste'), descr: "Affiche la table des Têtes de Dragon pour Hauts Revants" }); this.registerCommand({ path: ["/table", "tete"], func: (content, msg, params) => RdDRollTables.getTete('liste'), descr: "Affiche la table des Tête de Dragon pour tous" }); this.registerCommand({ path: ["/table", "souffle"], func: (content, msg, params) => RdDRollTables.getSouffle('liste'), descr: "Affiche la table des Souffles de Dragon" }); this.registerCommand({ path: ["/table", "tarot"], func: (content, msg, params) => RdDRollTables.getTarot('liste'), descr: "Affiche la table les cartes du Tarot Draconique" }); this.registerCommand({ path: ["/table", "ideefixe"], func: (content, msg, params) => RdDRollTables.getIdeeFixe('liste'), descr: "Affiche la table des Idées fixes" }); this.registerCommand({ path: ["/table", "desir"], func: (content, msg, params) => RdDRollTables.getDesirLancinant('liste'), descr: "Affiche la table des Désirs Lancinants" }); this.registerCommand({ path: ["/table", "rencontre"], func: (content, msg, params) => this.tableRencontres(msg, params), descr: `Affiche la table des Rencontres
/table rencontre deso affiche la table des rencontres en Désolation
/table rencontre mauvaise affiche la table des mauvaises rencontres` }); this.registerCommand({ path: ["/tirer", "comp"], func: (content, msg, params) => RdDRollTables.getCompetence('chat'), descr: "Tire une compétence au hasard" }); this.registerCommand({ path: ["/tirer", "queue"], func: (content, msg, params) => RdDRollTables.getQueue('chat'), descr: "Tire une Queue de Dragon" }); this.registerCommand({ path: ["/tirer", "ombre"], func: (content, msg, params) => RdDRollTables.getOmbre('chat'), descr: "Tire une Ombre de Thanatos" }); this.registerCommand({ path: ["/tirer", "tetehr"], func: (content, msg, params) => RdDRollTables.getTeteHR('chat'), descr: "Tire une Tête de Dragon pour Hauts Revants" }); this.registerCommand({ path: ["/tirer", "tete"], func: (content, msg, params) => RdDRollTables.getTete('chat'), descr: "Tire une Tête de Dragon" }); this.registerCommand({ path: ["/tirer", "souffle"], func: (content, msg, params) => RdDRollTables.getSouffle('chat'), descr: "Tire un Souffle de Dragon" }); this.registerCommand({ path: ["/tirer", "tarot"], func: (content, msg, params) => RdDRollTables.getTarot('chat'), descr: "Tire une carte du Tarot Draconique" }); this.registerCommand({ path: ["/tirer", "ideefixe"], func: (content, msg, params) => RdDRollTables.getIdeeFixe('chat'), descr: "Tire une Idée fixe" }); this.registerCommand({ path: ["/tirer", "desir"], func: (content, msg, params) => RdDRollTables.getDesirLancinant('chat'), descr: "Tire un Désir Lancinant" }); this.registerCommand({ path: ["/tirer", "rencontre"], func: (content, msg, params) => this.getRencontreTMR(params), descr: `Détermine une rencontre dans les TMR (synonyme de "/tmrr")` }); this.registerCommand({ path: ["/meteo"], func: (content, msg, params) => this.getMeteo(msg, params), descr: "Propose une météo marine" }); this.registerCommand({ path: ["/nom"], func: (content, msg, params) => RdDNameGen.getName(msg, params), descr: "Génère un nom aléatoire" }); this.registerCommand({ path: ["/tmr"], func: (content, msg, params) => this.findTMR(msg, params), descr: `Cherche où se trouve une case des Terres médianes
/tmr sord indique que la cité Sordide est en D13
/tmr foret donne la liste des TMR dont le nom contient "foret" (donc, toutes les forêts)` }); this.registerCommand({ path: ["/tmra"], func: (content, msg, params) => this.getTMRAleatoire(msg, params), descr: `Tire une case aléatoire des Terres médianes
/tmra forêt détermine une 'forêt' aléatoire
/tmra détermine une case aléatoire dans toutes les TMR` }); this.registerCommand({ path: ["/tmrr"], func: (content, msg, params) => this.getRencontreTMR(params), descr: `Détermine une rencontre dans les TMR
/tmrr forêt détermine une rencontre aléatoire en 'forêt'
/tmrr mauvaise détermine une mauvaise rencontre aléatoire
/tmrr for 47 détermine la rencontre en 'forêt' pour un jet de dé de 47` }); this.registerCommand({ path: ["/xp", "comp"], func: (content, msg, params) => this.getCoutXpComp(msg, params), descr: `Détermine le coût d'expérience pour augmenter une compétence. Exemples:
/xp comp -6 1: pour passer de -6 à +1
/xp comp +4: pour atteindre le niveau 4 (depuis +3)` }); this.registerCommand({ path: ["/xp", "carac"], func: (content, msg, params) => this.getCoutXpCarac(msg, params), descr: `Détermine le coût d'expérience pour augmenter une caractéristique. Exemples:
/xp carac 15: coût pour atteindre 15 (depuis 14)` }); this.registerCommand({ path: ["/rdd"], func: (content, msg, params) => this.rollRdd(msg, params), descr: `Effectue un jet de dés dans la table de résolution. Exemples:
/rdd ouvre la table de résolution
/rdd 10 3 effectue un jet 10 à +3
/rdd 15 -2 effectue un jet 15 à -2
/rdd 15 0 s effectue un jet 15 à 0, avec significative requise
/rdd Vue Vigilance -2 effectue un jet de Vue/Vigilance à -2 pour les tokens sélectionnés
/rdd vol déser +2 effectue un jet de Volonté/Survie en désert à +2 pour les tokens sélectionnés ` }); this.registerCommand({ path: ["/ddr"], func: (content, msg, params) => this.rollDeDraconique(msg), descr: "Lance un Dé Draconique" }); this.registerCommand({ path: ["/payer"], func: (content, msg, params) => RdDUtility.afficherDemandePayer(params[0], params[1]), descr: `Demande aux joueurs de payer un montant. Exemples:
/payer 5s 10d permet d'envoyer un message pour payer 5 sols et 10 deniers
/payer 10d permet d'envoyer un message pour payer 10 deniers` }); this.registerCommand({ path: ["/astro"], func: (content, msg, params) => RdDUtility.afficherHeuresChanceMalchance(Misc.join(params, ' ')), descr: `Affiche les heures de chance et de malchance selon l'heure de naissance donnée en argument. Exemples pour l'heure de la Lyre:
/astro 7
/astro Lyre
/astro Lyr` }); this.registerCommand({ path: ["/signe", "+"], func: (content, msg, params) => this.creerSignesDraconiques(), descr: "Crée un signe draconique et l'ajoute aux haut-rêvants choisis." }); this.registerCommand({ path: ["/signe", "-"], func: (content, msg, params) => this.supprimerSignesDraconiquesEphemeres(), descr: "Supprime les signes draconiques éphémères" }); this.registerCommand({ path: ["/stress"], func: (content, msg, params) => this.distribuerStress(params), descr: `Distribue du stress aux personnages. Exemples:
/stress : Ouvre une fenêtre pour donner du stress ou de l'expérience à un ensemble de personnages
/stress 6 : Distribue 6 points des Stress à tout les personnages joueurs, sans raison renseignée
/stress 6 Tigre : Distribue 6 points des Stress à tout les personnages joueurs, à cause d'un Tigre (Vert)
/stress 6 Glou Paulo : Distribue 6 points de Stress au personnage Paulon ou au personnage joueur Paulo, à cause d'un Glou` }); this.registerCommand({ path: ["/chrono"], func: (content, msg, params) => DialogChronologie.create(), descr: `Enregistre une entrée de chronologie dans un article de journal` }); } /* -------------------------------------------- */ registerCommand(command) { this._addCommand(this.getCommands(), command.path, '', command); } getCommands() { if (!this.commandsTable){ this._registerCommands(); } return this.commandsTable; } /* -------------------------------------------- */ _addCommand(targetTable, path, fullPath, command) { if (!this._validateCommand(targetTable, path, command)) { return; } const term = path[0]; fullPath = fullPath + term + ' ' if (path.length == 1) { command.descr = `${fullPath}: ${command.descr}`; targetTable[term] = command; } else { if (!targetTable[term]) { targetTable[term] = { subTable: {} }; } this._addCommand(targetTable[term].subTable, path.slice(1), fullPath, command) } } /* -------------------------------------------- */ _validateCommand(targetTable, path, command) { if (path.length > 0 && path[0] && command.descr && (path.length != 1 || targetTable[path[0]] == undefined)) { return true; } console.warn("RdDCommands._validateCommand failed ", targetTable, path, command); return false; } /* -------------------------------------------- */ /* Manage chat commands */ processChatCommand(commandLine, content = '', msg = {}) { // Setup new message's visibility let rollMode = game.settings.get("core", "rollMode"); if (["gmroll", "blindroll"].includes(rollMode)) msg["whisper"] = ChatMessage.getWhisperRecipients("GM"); if (rollMode === "blindroll") msg["blind"] = true; msg["type"] = 0; let command = commandLine[0].toLowerCase(); let params = commandLine.slice(1); return this.process(command, params, content, msg); } process(command, params, content, msg) { return this._processCommand(this.getCommands(), command, params, content, msg); } async _processCommand(commandsTable, name, params, content = '', msg = {}, path = "") { let command = commandsTable[name]; path = path + name + " "; if (command && command.subTable) { if (params[0]) { return this._processCommand(command.subTable, params[0], params.slice(1), content, msg, path) } else { this.help(msg, command.subTable); return true; } } if (command && command.func) { const result = await command.func(content, msg, params); if (result == false) { RdDCommands._chatAnswer(msg, command.descr); } return true; } return false; } /* -------------------------------------------- */ async help(msg) { this.help(msg, undefined); } async help(msg, table) { let commands = [] this._buildSubTableHelp(commands, table ?? this.getCommands()); let html = await renderTemplate("systems/foundryvtt-reve-de-dragon/templates/settings/dialog-aide-commands.html", { commands: commands }); let d = new Dialog( { title: "Commandes disponibles dans le tchat", content: html, buttons: {}, }, { width: 600, height: 600, }); d.render(true); } /* -------------------------------------------- */ static _chatAnswer(msg, content) { msg.whisper = [game.user.id]; msg.content = content; ChatMessage.create(msg); } /* -------------------------------------------- */ _buildSubTableHelp(list, table) { for (let [name, command] of Object.entries(table)) { if (command) { if (command.subTable) { this._buildSubTableHelp(list, command.subTable); } else { list.push(command.descr); } } } return list.sort(); } /* -------------------------------------------- */ async getRencontreTMR(params) { if (params.length == 1 || params.length == 2) { return game.system.rdd.rencontresTMR.rollRencontre(params[0], params[1]) } return false; } /* -------------------------------------------- */ async rollRdd(msg, params) { if (params.length == 0) { RdDRollResolutionTable.open(); } else { let flatParams = Misc.join(params, ' '); const numericParams = flatParams.match(rddRollNumeric); if (numericParams) { const carac = Misc.toInt(numericParams[1]); const diff = Misc.toInt(numericParams[2] || 0); const significative = numericParams[3] == 's' await this.rollRdDNumeric(msg, carac, diff, significative); return; } let actors = canvas.tokens.controlled.map(it => it.actor).filter(it => it); if (actors && actors.length > 0) { let length = params.length; let diff = Number(params[length - 1]); if (Number.isInteger(Number(diff))) { length--; } else { diff = 0; } const caracName = params[0]; let competence = length > 1 ? actors[0].getCompetence(Misc.join(params.slice(1, length), ' ')) : undefined; if (competence) { for (let actor of actors) { await actor.rollCaracCompetence(caracName, competence.name, diff); } } return; } else { ui.notifications.warn("Sélectionnez au moins un personnage pour lancer les dés") } } } /* -------------------------------------------- */ async rollRdDNumeric(msg, carac, diff, significative = false) { let rollData = { caracValue: carac, finalLevel: diff, diviseurSignificative: significative ? 2 : 1, show: { title: "Table de résolution" } }; await RdDResolutionTable.rollData(rollData); return RdDCommands._chatAnswer(msg, await RdDResolutionTable.buildRollDataHtml(rollData)); } /* -------------------------------------------- */ async rollDeDraconique(msg) { let ddr = await RdDDice.rollTotal("1dr + 7"); return RdDCommands._chatAnswer(msg, `Lancer d'un Dé draconique: ${ddr}`); } async getTMRAleatoire(msg, params) { if (params.length < 2) { let type = params[0]; const tmr = await TMRUtility.getTMRAleatoire(type ? (it => it.type == type) : (it => true)); return RdDCommands._chatAnswer(msg, `Case aléatoire: ${tmr.coord} - ${tmr.label}`); } else { return false; } } async findTMR(msg, params) { if (params && params.length > 0) { const search = Misc.join(params, ' '); const found = TMRUtility.findTMR(search); if (found?.length > 0) { return RdDCommands._chatAnswer(msg, `Les TMRs correspondant à '${search}' sont:` + Misc.join(found.map(it => `
${it.coord}: ${it.label}`))); } return RdDCommands._chatAnswer(msg, 'Aucune TMR correspondant à ' + search); } return false; } async tableRencontres(msg, params) { if (params && params.length > 0) { const search = Misc.join(params, ' '); const solvedTerrain = TMRUtility.findTMRLike(search); if (solvedTerrain == undefined) { return RdDCommands._chatAnswer(msg, 'Aucune TMR correspondant à ' + search); } return game.system.rdd.rencontresTMR.chatTable(solvedTerrain); } return false; } /* -------------------------------------------- */ getCoutXpComp(msg, params) { if (params && (params.length == 1 || params.length == 2)) { let to = params.length == 1 ? Number(params[0]) : Number(params[1]); let from = params.length == 1 ? to - 1 : Number(params[0]); return RdDCommands._chatAnswer(msg, `Coût pour passer une compétence de ${from} à ${to}: ${RdDItemCompetence.getDeltaXp(from, to)}`); } else { return false; } } /* -------------------------------------------- */ getCoutXpCarac(msg, params) { if (params && params.length == 1) { let to = Number(params[0]); return RdDCommands._chatAnswer(msg, `Coût pour passer une caractéristique de ${to - 1} à ${to}: ${RdDCarac.getCaracXp(to)}`); } else { return false; } } async creerSignesDraconiques() { DialogCreateSigneDraconique.createSigneForActors(); return true; } async supprimerSignesDraconiquesEphemeres() { game.actors.forEach(actor => { const ephemeres = actor.items.filter(item => item.type = 'signedraconique' && item.system.ephemere); if (ephemeres.length > 0) { actor.deleteEmbeddedDocuments("Item", ephemeres.map(item => item.id)); } }); return true; } async distribuerStress(params) { if (!game.user.isGM) { ui.notifications.warn("Seul le MJ est autorisé à utiliser la commande /stress"); return false; } if (params.length < 3) { DialogStress.distribuerStress(); } else { let stress = params[0] if (stress == undefined) { ui.notifications.warn("Pas de valeur de stress à distribuer!"); return; } let motif = params.slice(1, params.length - 2); let name = params[params.length - 1]; if (name == undefined) { for (let actor of game.actors) { actor.distribuerStress('stress', stress, motif); } } else { //console.log(stressValue, nomJoueur); let actor = Misc.findActor(name, game.actors.filter(it => it.hasPlayerOwner)) ?? Misc.findPlayer(name)?.character if (actor) { actor.distribuerStress('stress', stress, motif); } else { ui.notifications.warn(`Pas de personnage ou de joueur correspondant à ${name}!`); } } } return true; } async getMeteo(msg, params) { return await RdDMeteo.getMeteo(); } }