/* -------------------------------------------- */ import { DialogCreateSigneDraconique } from "./dialog-create-signedraconique.js"; import { DialogStress } from "./dialog-stress.js"; import { Grammar } from "./grammar.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 { TMRRencontres } from "./tmr-rencontres.js"; import { TMRUtility } from "./tmr-utility.js"; const rddRollNumeric = /^(\d+)\s*([\+\-]?\d+)?\s*(s)?/; /* -------------------------------------------- */ export class RdDCommands { static init() { if (!game.system.rdd.commands) { const rddCommands = new RdDCommands(); rddCommands.registerCommand({ path: ["/aide"], func: (content, msg, params) => rddCommands.help(msg), descr: "Affiche l'aide pour toutes les commandes" }); rddCommands.registerCommand({ path: ["/help"], func: (content, msg, params) => rddCommands.help(msg), descr: "Affiche l'aide pour toutes les commandes" }); rddCommands.registerCommand({ path: ["/table", "queues"], func: (content, msg, params) => RdDRollTables.getQueue(true), descr: "Tire une Queue de Dragon" }); rddCommands.registerCommand({ path: ["/table", "ideefixe"], func: (content, msg, params) => RdDRollTables.getIdeeFixe(true), descr: "Tire une Idée fixe" }); rddCommands.registerCommand({ path: ["/table", "desir"], func: (content, msg, params) => RdDRollTables.getDesirLancinant(true), descr: "Tire un Désir Lancinant" }); rddCommands.registerCommand({ path: ["/table", "ombre"], func: (content, msg, params) => RdDRollTables.getOmbre(true), descr: "Tire une Ombre de Dragon" }); rddCommands.registerCommand({ path: ["/table", "tetehr"], func: (content, msg, params) => RdDRollTables.getTeteHR(true), descr: "Tire une Tête de Dragon pour Hauts Revants" }); rddCommands.registerCommand({ path: ["/table", "tete"], func: (content, msg, params) => RdDRollTables.getTete(true), descr: "Tire une Tête de Dragon" }); rddCommands.registerCommand({ path: ["/table", "souffle"], func: (content, msg, params) => RdDRollTables.getSouffle(true), descr: " Tire un Souffle de Dragon" }); rddCommands.registerCommand({ path: ["/table", "comp"], func: (content, msg, params) => RdDRollTables.getCompetence(true), descr: "Tire une compétence au hasard" }); rddCommands.registerCommand({ path: ["/table", "tarot"], func: (content, msg, params) => RdDRollTables.getTarot(true), descr: "Tire une carte du Tarot Draconique" }); rddCommands.registerCommand({ path: ["/meteo"], func: (content, msg, params) => rddCommands.getMeteo(msg, params), descr: "Propose une météo marine" }); rddCommands.registerCommand({ path: ["/nom"], func: (content, msg, params) => RdDNameGen.getName(msg, params), descr: "Génère un nom aléatoire" }); rddCommands.registerCommand({ path: ["/tmra"], func: (content, msg, params) => rddCommands.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` }); rddCommands.registerCommand({ path: ["/tmr"], func: (content, msg, params) => rddCommands.findTMR(msg, params), descr: `Cherche où se trouve une case des Terres médianes
/tmr? sordide 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)` }); rddCommands.registerCommand({ path: ["/tmrr"], func: (content, msg, params) => rddCommands.getRencontreTMR(params), descr: `Détermine une rencontre dans un type de case
/tmrr foret lance un d100 et détermine la rencontre correspondante en 'forêt'
/tmrr forêt 47 détermine la rencontre en 'forêt' pour un jet de dé de 47` }); rddCommands.registerCommand({ path: ["/xp", "comp"], func: (content, msg, params) => rddCommands.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)` }); rddCommands.registerCommand({ path: ["/xp", "carac"], func: (content, msg, params) => rddCommands.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)` }); rddCommands.registerCommand({ path: ["/rdd"], func: (content, msg, params) => rddCommands.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 ` }); rddCommands.registerCommand({ path: ["/ddr"], func: (content, msg, params) => rddCommands.rollDeDraconique(msg), descr: "Lance un Dé Draconique" }); rddCommands.registerCommand({ path: ["/payer"], func: (content, msg, params) => RdDUtility.afficherDemandePayer(params[0], params[1]), descr: `Permet 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` }); rddCommands.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` }); rddCommands.registerCommand({ path: ["/signe", "+"], func: (content, msg, params) => rddCommands.creerSignesDraconiques(), descr: "Crée un signe draconique et l'ajoute aux haut-rêvants choisis." }); rddCommands.registerCommand({ path: ["/signe", "-"], func: (content, msg, params) => rddCommands.supprimerSignesDraconiquesEphemeres(), descr: "Supprime les signes draconiques éphémères" }); rddCommands.registerCommand({ path: ["/stress"], func: (content, msg, params) => rddCommands.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` }); game.system.rdd.commands = rddCommands; } } constructor() { this.commandsTable = {}; } /* -------------------------------------------- */ registerCommand(command) { this._addCommand(this.commandsTable, command.path, '', command); } /* -------------------------------------------- */ _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.commandsTable, command, params, content, msg); } _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 = 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 list = [] this._buildSubTableHelp(list, table || this.commandsTable); let html = await renderTemplate("systems/foundryvtt-reve-de-dragon/templates/settings/dialog-aide-commands.html", { commands: list }); let d = new Dialog( { title: "Commandes disponibles dans le tchat", content: html, buttons: {}, }, { width: 600, height: 500, }); 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 TMRRencontres.rollRencontre(params[0], params[1]) } else { 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]; const compName = length > 1 ? Misc.join(params.slice(1, length), ' ') : undefined; for (let actor of actors) { await actor.rollCaracCompetence(caracName, compName, 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); RdDCommands._chatAnswer(msg, await RdDResolutionTable.buildRollDataHtml(rollData)); } /* -------------------------------------------- */ async rollDeDraconique(msg) { let ddr = await RdDDice.rollTotal("1dr + 7"); 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)); RdDCommands._chatAnswer(msg, `Case aléatoire: ${tmr.coord} - ${tmr.label}`); } else { return false; } } async findTMR(msg, params) { 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); } /* -------------------------------------------- */ 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]); 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]); 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.filterItems(item => Misc.data(item).type = 'signedraconique' && Misc.data(item).data.ephemere) .map(item => item.id); if (ephemeres.length > 0) { actor.deleteEmbeddedDocuments("Item", ephemeres); } }); 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(); } }