From a56dd1c659c794d09f131615be79e8c89ec4f187 Mon Sep 17 00:00:00 2001 From: Jag Goraya Date: Fri, 31 Dec 2021 16:29:57 +0000 Subject: [PATCH 1/5] Specify namespace for submenu settings to render (#71) --- modules/gm-toolkit-settings.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/gm-toolkit-settings.mjs b/modules/gm-toolkit-settings.mjs index b4f3603..4f26481 100644 --- a/modules/gm-toolkit-settings.mjs +++ b/modules/gm-toolkit-settings.mjs @@ -282,6 +282,6 @@ export function getDataSettings(data, feature) { s.isNumber = true; s.inputType = "number" } - s.value = game.settings.get(s.module, s.key); + s.value = game.settings.get(s.namespace, s.key); }); } From 9da98c31f62dc89b4287ccfc8585cc88e031d2e2 Mon Sep 17 00:00:00 2001 From: Jagusti Date: Sun, 2 Jan 2022 00:18:38 +0000 Subject: [PATCH 2/5] Add WFRP4e v5 compatibility for Send Dark Whispers - Added Dark Whispers RollTable and compendium. - Added prompt to import table in Send Dark Whispers dialog if not present. Addresses #69 --- CHANGELOG.md | 5 +++ lang/en.json | 1 + lang/fr.json | 1 + module.json | 32 ++++++++------ packs/gm-toolkit-macros.db | 2 +- packs/gm-toolkit-tables.db | 1 + scripts/macros/dark-whispers.js | 74 ++++++++++++++++++--------------- 7 files changed, 70 insertions(+), 46 deletions(-) create mode 100644 packs/gm-toolkit-tables.db diff --git a/CHANGELOG.md b/CHANGELOG.md index bef31df..d860b7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file. The format ## Unreleased See [Issue Backlog](../../issues) and [Roadmap](../../milestones). +- *Changed* Foundry compatibility to up to ###. WFRP4e system compatibility is up to ###. +- *Fixed* issue where module settings could not be accessed ([#70](https://github.com/Jagusti/fvtt-wfrp4e-gmtoolkit/issues/70)). +- *Changed* Send Dark Whispers macro to be compatible with WFRP4e v5.0.4 ([#69](https://github.com/Jagusti/fvtt-wfrp4e-gmtoolkit/issues/69)) +- *Added* Dark Whispers RollTable and compendium for use by Send Dark Whispers macro. Added prompt to import table in Send Dark Whispers dialog if not present. + ## Version 0.8.0 - *Changed* Advantage scripts to handle non-token characters. diff --git a/lang/en.json b/lang/en.json index af759e9..b48e84f 100644 --- a/lang/en.json +++ b/lang/en.json @@ -164,6 +164,7 @@ "GMTOOLKIT.Dialog.DarkWhispers.SendWhisper" : "Whisper", "GMTOOLKIT.Dialog.DarkWhispers.NoCorruption" : "No Corruption to spend on offer.", "GMTOOLKIT.Dialog.DarkWhispers.HasCorruption" : "Someone to bargain with.", + "GMTOOLKIT.Dialog.DarkWhispers.ImportTable" : "Import the Dark Whispers RollTable from the GM Toolkit compendium to see Dark Whisper prompts here.", "GMTOOLKIT.Message.DarkWhispers.Accept" : "Heed the Whisper", "GMTOOLKIT.Message.DarkWhispers.Reject" : "Deny the Dark Gods", diff --git a/lang/fr.json b/lang/fr.json index 0436085..de796f5 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -164,6 +164,7 @@ "GMTOOLKIT.Dialog.DarkWhispers.SendWhisper" : "Murmurer", "GMTOOLKIT.Dialog.DarkWhispers.NoCorruption" : "No Corruption to spend on offer.", "GMTOOLKIT.Dialog.DarkWhispers.HasCorruption" : "Someone to bargain with.", + "GMTOOLKIT.Dialog.DarkWhispers.ImportTable" : "Import the Dark Whispers RollTable from the GM Toolkit compendium to see Dark Whisper prompts here.", "GMTOOLKIT.Message.DarkWhispers.Accept" : "Obéir au Murmure", "GMTOOLKIT.Message.DarkWhispers.Reject" : "Ignorer les Dieux Noirs", diff --git a/module.json b/module.json index 55dc074..4dbbe49 100644 --- a/module.json +++ b/module.json @@ -2,15 +2,15 @@ "name": "wfrp4e-gm-toolkit", "title": "GM Toolkit (WFRP4e)", "description": "Utilities for WFRP4e GMs", - "version": "0.8.0", + "version": "0.9.0-dev", "minCoreVersion": "0.8.8", - "compatibleCoreVersion": "0.8.9", + "compatibleCoreVersion": "9", "author": "Jagusti", "authors" : [ { - "name": "Jagusti", - "discord": "Jagusti#3610", - "ko-fi": "jagusti" + "name": "Jagusti", + "discord": "Jagusti#3610", + "ko-fi": "jagusti" } ], "system": ["wfrp4e"], @@ -19,12 +19,20 @@ "styles": ["/css/gmtoolkit.css"], "packs": [ { - "name": "gm-toolkit-macros", - "label": "GM Toolkit", - "path": "packs/gm-toolkit-macros.db", - "entity": "Macro", - "module": "wfrp4e-gm-toolkit", - "system": "wfrp4e" + "name": "gm-toolkit-macros", + "label": "GM Toolkit", + "path": "packs/gm-toolkit-macros.db", + "entity": "Macro", + "module": "wfrp4e-gm-toolkit", + "system": "wfrp4e" + }, + { + "name": "gm-toolkit-tables", + "label": "GM Toolkit", + "path": "packs/gm-toolkit-tables.db", + "entity": "RollTable", + "module": "wfrp4e-gm-toolkit", + "system": "wfrp4e" } ], "languages": [ @@ -41,7 +49,7 @@ ], "url": "https://github.com/Jagusti/fvtt-wfrp4e-gmtoolkit", "manifest": "https://github.com/Jagusti/fvtt-wfrp4e-gmtoolkit/releases/latest/download/module.json", - "download": "https://github.com/Jagusti/fvtt-wfrp4e-gmtoolkit/releases/download/v0.8.0/wfrp4e-gm-toolkit.zip", + "download": "https://github.com/Jagusti/fvtt-wfrp4e-gmtoolkit/releases/download/v0.9.0/wfrp4e-gm-toolkit.zip", "bugs": "https://github.com/Jagusti/fvtt-wfrp4e-gmtoolkit/issues", "changelog": "https://github.com/Jagusti/fvtt-wfrp4e-gmtoolkit/releases", "readme": "https://github.com/Jagusti/fvtt-wfrp4e-gmtoolkit/wiki", diff --git a/packs/gm-toolkit-macros.db b/packs/gm-toolkit-macros.db index 922fa35..68cb93a 100644 --- a/packs/gm-toolkit-macros.db +++ b/packs/gm-toolkit-macros.db @@ -7,7 +7,7 @@ {"_id":"ihMGjHFP3SdvYH2k","name":"Simply d100","permission":{"default":0,"k5y1jEYMBqd9uLUx":3},"type":"chat","flags":{},"scope":"global","command":"/r 1d100","author":"k5y1jEYMBqd9uLUx","img":"modules/wfrp4e-gm-toolkit/assets/icons/d100.svg","actorIds":[]} {"name":"GM Toolbox","type":"script","author":"WpuDIfNQnefaTyuV","img":"modules/wfrp4e-gm-toolkit/assets/icons/macro-chest.svg","scope":"global","command":"(()=>{\n // Add and remove macros from the list as needed. \n const macros = [\n \"Add Advantage\",\n \"Clear Advantage\",\n \"Reduce Advantage\", \n \"Session Turnover\",\n \"Add XP\",\n \"Reset Fortune\",\n \"Make Secret Party Test\",\n \"Send Dark Whispers\",\n \"Toggle Scene Visibility and Light\",\n \"Set Token Vision and Light\",\n \"Pull Everyone to Scene\", \n \"Simply d100\"\n ];\n\n let buttons = {};\n let dialog;\n let content = `
{\n buttons[name] = {\n label : name,\n icon : ``,\n callback : () => {\n game.macros.getName(name).execute();\n dialog.render(true);\n }\n }\n });\n dialog = new Dialog({title : game.i18n.localize(\"GMTOOLKIT.Dialog.GMToolbox.Title\"), content, buttons}).render(true);\n})();\n \n \n/* ==========\n* MACRO: GM Toolbox\n* VERSION: 0.8.0\n* UPDATED: 2021-12-31\n* DESCRIPTION: Adds a customisable floating dialog for quick access to frequently used Toolkit macros, freeing up hotbar spots\n* TIP: Add / remove macros from the 'macros' list to tailor it for your game. Names must exactly match those in the Macro Directory.\n* TIP: The macro dialog can be kept open for quick access and minimised to reduce space\n========== */","folder":null,"sort":0,"permission":{"default":0,"WpuDIfNQnefaTyuV":3},"flags":{"core":{"sourceId":"Macro.bY0sF26Vj0OhXwQ5"}},"_id":"iopoLXTz9kfDTfiX"} {"_id":"pZmPtsEZHOpyJfnq","name":"Reset Fortune","type":"script","author":"k5y1jEYMBqd9uLUx","img":"modules/wfrp4e-gm-toolkit/assets/icons/reset-fortune.svg","scope":"global","command":"resetFortune();\n\nfunction resetFortune() {\n// setup: exit with notice if there are no player-owned characters\n\tparty = Array.from(game.actors).filter(pc => pc.hasPlayerOwner && pc.type == 'character'); \n\tif (party.length == 0) return ui.notifications.error(game.i18n.localize(\"GMTOOLKIT.ResetFortune.NoPlayerCharacters\")); \n\tlet chatContent = \"\"\n\n// cycle through player characters, updating Fortune and building a report message\n\tparty.forEach (character => {\n\t\tcurrentFortune = character.data.data.status.fortune.value\n\t\tmaxFortune = getMaxFortune(character);\n\t\tcharacter.update({'data.status.fortune.value': maxFortune})\n\t\tchatContent += `${character.name}: ${currentFortune} -> ${maxFortune}
`\n\t});\n\n// confirm changes made in chat\n\tlet chatData = {\n\t\tuser: game.user.id,\n\t\tcontent: chatContent, \n\t\tflavor: game.i18n.localize(\"GMTOOLKIT.ResetFortune.Restored\") \n\t};\n\tChatMessage.create(chatData, {}); \n}\n\n// Calculate the Fortune target to be restored, based on Fate and Luck talent advances\nfunction getMaxFortune(target) {\n let advLuck = 0;\n let item = target.items.find(i => i.name === game.i18n.localize(\"GMTOOLKIT.Talent.Luck\") )\n\tif(!(item == undefined || item.data.data.advances.value < 1)) { \n\t\tfor (let item of target.items)\n\t\t\t{\n\t\t\t\tif (item.type == \"talent\" && item.name == game.i18n.localize(\"GMTOOLKIT.Talent.Luck\"))\n\t\t\t\t\t{\n\t\t\t\t\t\tadvLuck += item.data.data.advances.value;\n\t\t\t\t\t}\n\t\t\t}\n\t\t} \n return target.status.fate.value + advLuck\n}\n\n/* ==========\n * MACRO: Reset Fortune\n * VERSION: 0.8.0\n * UPDATED: 2021-11-12\n * DESCRIPTION: Restores Fortune to the Fate level of player character(s). Applies any Luck talent bonus.\n * TIP: Characters must have a player assigned. \n ========== */","folder":null,"sort":0,"permission":{"default":0,"k5y1jEYMBqd9uLUx":3},"flags":{}} -{"_id":"roGO31Lo4pyL5kvg","name":"Send Dark Whispers","type":"script","author":"Zo7HSQ75uO8dWUkH","img":"modules/wfrp4e-gm-toolkit/assets/icons/dark-whispers.svg","scope":"global","command":"/* Open a dialog to send a Dark Whisper (WFRP p183) to selected player character(s).\n * Adapted from original macro developed by Vindico#9103. \n */\n\nformDarkWhispers(); // Set default user target filter in module settings. Override by adding parameter to 'all', 'absent' or 'present' party members. Clear parameter to revert to default. \n\nfunction formDarkWhispers(targets=String(game.settings.get(\"wfrp4e-gm-toolkit\", \"targetDarkWhispers\"))) {\n\n // Non-GMs are not permitted to send Dark Whispers\n if (!game.user.isGM) { \n return ui.notifications.error(game.i18n.localize('GMTOOLKIT.Message.DarkWhispers.NoPermission'));\n }\n\n //TODO: Switch to actor based selection\n // Only users who have an assigned player character are available to whisper to. \n let users = []\n switch (targets) { \n case 'all':\n users = game.users.filter(user => (user.character?.type == 'character'));\n break;\n case 'absent':\n users = game.users.filter(user => !user.active && (user.character?.type == 'character'));\n break;\n case 'present':\n default:\n users = game.users.filter(user => user.active && (user.character?.type == 'character'));\n break;\n }\n\n if (!users) { \n return ui.notifications.error(game.i18n.localize('GMTOOLKIT.Message.DarkWhispers.NoEligibleCharacters'));\n }\n \n // Build list of player / characters to select via dialog\n //TODO: Map individual Corruption values, rather than increment\n let corruptionAvailable = 0; // count to check there are characters with corruption to target\n let checkOptions = \"\";\n users.forEach(user => {\n let actorCorruption = {\n value: game.actors.get(user.character.id).data.data.status.corruption.value, \n max: game.actors.get(user.character.id).data.data.status.corruption.max\n };\n corruptionAvailable += actorCorruption.value;\n // Make unselectable if character has no Corruption to deal with\n let canWhisperTo = (actorCorruption.value) ? `enabled title=\"${game.i18n.localize('GMTOOLKIT.Dialog.DarkWhispers.HasCorruption')}\"` : `disabled title=\"${game.i18n.localize('GMTOOLKIT.Dialog.DarkWhispers.NoCorruption')}\"`;\n\n checkOptions+=`\n
\n \n \n \n
\n `\n });\n \n // abort if no characters have any Corruption\n if (corruptionAvailable == 0) { \n return ui.notifications.error(game.i18n.localize('GMTOOLKIT.Message.DarkWhispers.NoEligibleCharacters'));\n };\n\n // Construct and show form to write whisper message and select target player characters\n let dialogContent = `\n
\n \n
\n ${checkOptions} \n
\n \n
\n
\n \n
\n `\n new Dialog({\n title: game.i18n.localize('GMTOOLKIT.Dialog.DarkWhispers.Title'),\n content:dialogContent,\n buttons:{\n whisper:{ \n label: game.i18n.localize('GMTOOLKIT.Dialog.DarkWhispers.SendWhisper'),\n callback: (html) => sendDarkWhispers(html, users, \"private\")\n }\n }\n}).render(true);\n}\n\nfunction sendDarkWhispers(html, users, sendmode) {\n // check for whisper message \n var messageText = html.find('[name=\"message\"]')[0].value;\n \n // build list of selected players ids for whispers target\n var targets = [];\n for ( let user of users ) {\n if (html.find('[name=\"'+user.id+'\"]')[0].checked){\n targets.push(user.id);\n }\n }\n\n // abort if no whisper or character is selected \n if (targets.length == 0 || messageText.length == 0) {\n return ui.notifications.error(game.i18n.format('GMTOOLKIT.Message.DarkWhispers.WhisperAborted', {currentUser: game.users.current.name}));\n }\n\n // Construct and send message to whisper targets\n // Build the translation string based on the setting\n let messageTemplate = \"GMTOOLKIT.Settings.DarkWhispers.message\" + `.${game.settings.get(\"wfrp4e-gm-toolkit\", \"messageDarkWhispers\")}`\n // Parse the translated message\n let whisperMessage = `${game.i18n.format(messageTemplate, {message: messageText})}`\n \n // Add response buttons for chat card\n // data- attributes are used by listener\n let responseObjects = `\n \n ${game.i18n.localize('GMTOOLKIT.Message.DarkWhispers.Accept')}\n ${game.i18n.localize('GMTOOLKIT.Message.DarkWhispers.Reject')}\n \n ` \n\n ChatMessage.create({\n content: whisperMessage + responseObjects,\n whisper: targets\n });\n \n}","folder":null,"sort":0,"permission":{"default":0,"Zo7HSQ75uO8dWUkH":3},"flags":{"core":{"sourceId":"Macro.NsfnGgD7GMH7AMwI"}}} +{"_id":"roGO31Lo4pyL5kvg","name":"Send Dark Whispers","type":"script","author":"Zo7HSQ75uO8dWUkH","img":"modules/wfrp4e-gm-toolkit/assets/icons/dark-whispers.svg","scope":"global","command":"formDarkWhispers(); // Set default user target filter in module settings. Override by adding parameter to \"all\", \"absent\" or \"present\" party members. Clear parameter to revert to default. \n\nasync function formDarkWhispers(targets=String(game.settings.get(\"wfrp4e-gm-toolkit\", \"targetDarkWhispers\"))) {\n// Non-GMs are not permitted to send Dark Whispers\n if (!game.user.isGM) { \n return ui.notifications.error(game.i18n.localize(\"GMTOOLKIT.Message.DarkWhispers.NoPermission\"));\n }\n\n //TODO: Switch to actor based selection\n // Only users who have an assigned player character are available to whisper to. \n let users = []\n switch (targets) { \n case \"all\":\n users = game.users.filter(user => (user.character?.type == \"character\"));\n break;\n case \"absent\":\n users = game.users.filter(user => !user.active && (user.character?.type == \"character\"));\n break;\n case \"present\":\n default:\n users = game.users.filter(user => user.active && (user.character?.type == \"character\"));\n break;\n }\n\n if (!users) { \n return ui.notifications.error(game.i18n.localize(\"GMTOOLKIT.Message.DarkWhispers.NoEligibleCharacters\"));\n }\n \n // Build list of player / characters to select via dialog\n //TODO: Map individual Corruption values, rather than increment\n let corruptionAvailable = 0; // count to check there are characters with corruption to target\n let checkOptions = \"\";\n users.forEach(user => {\n let actorCorruption = {\n value: game.actors.get(user.character.id).data.data.status.corruption.value, \n max: game.actors.get(user.character.id).data.data.status.corruption.max\n };\n corruptionAvailable += actorCorruption.value;\n // Make unselectable if character has no Corruption to deal with\n let canWhisperTo = (actorCorruption.value) ? `enabled title=\"${game.i18n.localize('GMTOOLKIT.Dialog.DarkWhispers.HasCorruption')}\"` : `disabled title=\"${game.i18n.localize('GMTOOLKIT.Dialog.DarkWhispers.NoCorruption')}\"`;\n\n checkOptions+=`\n
\n \n \n \n
\n `\n });\n \n // abort if no characters have any Corruption\n if (corruptionAvailable == 0) { \n return ui.notifications.error(game.i18n.localize(\"GMTOOLKIT.Message.DarkWhispers.NoEligibleCharacters\"));\n };\n\n // Construct and show form to write whisper message and select target player characters\n let darkwhisper = (game.tables.getName(\"Dark Whispers\")) ? await game.wfrp4e.tables.rollTable(\"darkwhispers\") : game.i18n.format(\"GMTOOLKIT.Dialog.DarkWhispers.ImportTable\")\n\n let dialogContent = `\n
\n \n
\n ${checkOptions} \n
\n \n
\n
\n \n
\n `\n new Dialog({\n title: game.i18n.localize(\"GMTOOLKIT.Dialog.DarkWhispers.Title\"),\n content:dialogContent,\n buttons:{\n whisper:{ \n label: game.i18n.localize(\"GMTOOLKIT.Dialog.DarkWhispers.SendWhisper\"),\n callback: (html) => sendDarkWhispers(html, users, \"private\")\n }\n }\n}).render(true);\n}\n\nfunction sendDarkWhispers(html, users, sendmode) {\n // check for whisper message \n let messageText = html.find('[name=\"message\"]')[0].value;\n \n // build list of selected players ids for whispers target\n let targets = [];\n for ( let user of users ) {\n if (html.find(`[name=\"${user.id}\"]`)[0].checked){\n targets.push(user.id);\n }\n }\n\n // abort if no whisper or character is selected \n if (targets.length == 0 || messageText.length == 0) {\n return ui.notifications.error(game.i18n.format(\"GMTOOLKIT.Message.DarkWhispers.WhisperAborted\", {currentUser: game.users.current.name}));\n }\n\n // Construct and send message to whisper targets\n // Build the translation string based on the setting\n let messageTemplate = `GMTOOLKIT.Settings.DarkWhispers.message.${game.settings.get(\"wfrp4e-gm-toolkit\", \"messageDarkWhispers\")}`\n // Parse the translated message\n let whisperMessage = `${game.i18n.format(messageTemplate, {message: messageText})}`\n \n // Add response buttons for chat card\n // data- attributes are used by listener\n let responseObjects = `\n \n ${game.i18n.localize(\"GMTOOLKIT.Message.DarkWhispers.Accept\")}\n ${game.i18n.localize(\"GMTOOLKIT.Message.DarkWhispers.Reject\")}\n \n `\n\n ChatMessage.create({\n content: whisperMessage + responseObjects,\n whisper: targets\n });\n \n}\n\n\n/* ==========\n* MACRO: Send Dark Whispers\n* VERSION: 0.9.0\n* UPDATED: 2022-01-01\n* DESCRIPTION: Open a dialog to send a Dark Whisper (WFRP p183) to one or more selected player character(s).\n* TIP: Only player-assigned characters with Corruption can be sent a Dark Whisper. \n* TIP: The placeholder whisper is drawn from the Dark Whispers table. Change this for different random whispers. \n* TIP: The whisper can be edited in the dialog, regardless of what is pre-filled from the Dark Whispers table. \n========== */","folder":null,"sort":0,"permission":{"default":0,"Zo7HSQ75uO8dWUkH":3},"flags":{"core":{"sourceId":"Macro.NsfnGgD7GMH7AMwI"}}} {"_id":"rzKeTLKp0bOp5SK9","name":"Add XP","type":"script","author":"k5y1jEYMBqd9uLUx","img":"modules/wfrp4e-gm-toolkit/assets/icons/add-xp.svg","scope":"global","command":"addXP();\n\nasync function addXP() {\n\n // setup: determine group of actors to be awarded experience\n if (game.user.targets.size < 1) {\n // (1) all player characters if no tokens are selected\n awardees = Array.from(game.actors).filter(pc => pc.hasPlayerOwner && pc.type == \"character\");\n } else {\n // (2) otherwise, all targeted player character tokens\n awardees = Array.from(game.user.targets).filter(pc => pc.actor.hasPlayerOwner && pc.actor.type == \"character\");\n }\n\n // setup: exit with notice if there are no player-owned characters\n if (awardees.length < 1) return ui.notifications.error(game.i18n.localize(\"GMTOOLKIT.Token.TargetPCs\"), {});\n\n // Get session ID/date, default XP award and default reason\n let XP = Number(game.settings.get(\"wfrp4e-gm-toolkit\", \"addXPDefaultAmount\"))\n let reason = (game.settings.get(\"wfrp4e-gm-toolkit\", \"addXPDefaultReason\"))\n if (reason == \"null\") {\n reason = \"\"\n } else {\n reason = (game.settings.get(\"wfrp4e-gm-toolkit\", \"addXPDefaultReason\")) \n let session = game.gmtoolkit.utility.getSession()\n reason = reason.replace(\"%date%\", session.date)\n if (session.id != \"null\" ) reason = reason.replace(\"%session%\", session.id)\n }\n\n // Prompt for XP if option is set\n if (game.settings.get(\"wfrp4e-gm-toolkit\", \"addXPPrompt\")) {\n let awardeeList = \"
    \"\n awardees.forEach (pc => {\n awardeeList += `
  • ${pc?.actor?.name || pc.name}
  • ` \n })\n awardeeList += \"
\"\n const dialog = new Dialog({\n title: (game.i18n.localize('GMTOOLKIT.Dialog.AddXP.Title')),\n content: `
\n

${game.i18n.format(\"GMTOOLKIT.Dialog.AddXP.Recipients\", {recipients : awardeeList})}

\n
\n \n \n
\n
\n \n \n
\n
`,\n buttons: {\n yes: {\n icon: \"\",\n label: game.i18n.localize(\"GMTOOLKIT.Dialog.Apply\"),\n callback: (html) => {\n let XP = Math.round(html.find('#add-xp').val());\n if (isNaN(XP)) return ui.notifications.error(game.i18n.localize(\"GMTOOLKIT.Dialog.AddXP.InvalidXP\"))\n let reason = html.find('#xp-reason').val();\n updateXP(awardees, XP, reason);\n }\n },\n no: {\n icon: \"\",\n label: game.i18n.localize(\"GMTOOLKIT.Dialog.Cancel\"),\n },\n },\n default: \"yes\"\n }).render(true);\n } else {\n updateXP(awardees, XP, reason)\n }\n\n} // END: addXP\n\nfunction updateXP(awardees, XP, reason) {\n let chatContent = \"\"\n\n // cycle through player characters, gathering experience change data for report message\n awardees.forEach ( pc => {\n recipient = pc?.actor?.name || pc.name \n XPTotal = pc?.details?.experience?.total || pc.actor.data.data.details.experience.total; \n newXPTotal = Math.max(XPTotal + XP,0);\n XPCurrent = pc?.details?.experience?.current || pc.actor.data.data.details.experience.current; \n newXPCurrent = Math.max(XPCurrent + XP,0);\n\n // update token actor or actor\n pc?.actor ? pc.actor.awardExp(XP, reason) : pc.awardExp(XP, reason)\n\n // build report message \n chatContent += game.i18n.format(\"GMTOOLKIT.AddXP.Success\", {recipient, XPTotal, newXPTotal, XPCurrent, newXPCurrent} ) \n }); // end cycle\n \n// confirm changes made in whisper to GM\n let chatData = game.wfrp4e.utility.chatDataSetup(chatContent, \"gmroll\", false)\n chatData.flavor = game.i18n.format(\"GMTOOLKIT.AddXP.Flavor\", {XP, reason})\n ChatMessage.create(chatData, {}); \n console.log(chatContent)\n\n} // END: updateXP\n\n\n/* ==========\n * MACRO: Add XP\n * VERSION: 0.8.0\n * UPDATED: 2021-12-30\n * DESCRIPTION: Adds a set amount of XP to all or targeted player character(s). Adds XP update note to the Chat log.\n * TIP: Characters must have a player assigned. \n * TIP: Default XP amount and reason can be preset in module settings, along with option to bypass prompt for XP amount each time.\n * TIP: Non-whole numbers are rounded off. Negative numbers are subtracted. \n ========== */","folder":null,"sort":0,"permission":{"default":0,"k5y1jEYMBqd9uLUx":3},"flags":{}} {"_id":"tiKEfs1nB7zAMgYg","name":"Pull Everyone to Scene","permission":{"default":0,"k5y1jEYMBqd9uLUx":3},"type":"script","flags":{},"scope":"global","command":"/* Yanks every player into the scene that the GM is on. \n * Optionally activates the scene, depending on module setting. \n */\n\npullEveryoneToScene();\n\nasync function pullEveryoneToScene() {\n if (!game.user.isGM) {\n ui.notifications.error(game.i18n.localize('GMTOOLKIT.Message.ScenePullActivate.NoPermission'), {});\n }\n\n switch (game.settings.get(\"wfrp4e-gm-toolkit\", \"scenePullActivate\")) {\n case \"prompt\": \n const dialog = new Dialog({\n title: (game.i18n.localize('GMTOOLKIT.Dialog.ScenePullActivate.Title')),\n content: `
\n
\n \n
\n
`,\n buttons: {\n activate: {\n label: \"Activate Scene\",\n callback: async () => pullToScene(true)\n },\n pull: {\n label: \"Pull Only\",\n callback: async () => pullToScene(false)\n },\n },\n default: \"pull\"\n }).render(true);\n break;\n case \"always\": \n pullToScene(true)\n break;\n case \"never\":\n pullToScene(false)\n break;\n }\n\n function pullToScene(activateScene) {\n let thisScene = game.scenes.viewed\n if (activateScene) {\n thisScene.update({\"active\" : true})\n let sceneActiveState = thisScene.data.active\n ui.notifications.notify(game.i18n.format('GMTOOLKIT.Message.ScenePullActivate.Activated', {sceneName: thisScene.name}));\n } else { \n for ( let u of game.users.players ) {\n game.socket.emit(\"pullToScene\", thisScene.id, u.id);\n }\n let sceneActiveState = String()\n if (thisScene.data.active == true ) {\n sceneActiveState = game.i18n.localize('GMTOOLKIT.Scene.Active')\n } else {\n sceneActiveState = game.i18n.localize('GMTOOLKIT.Scene.NotActive');\n }\n ui.notifications.notify(game.i18n.format('GMTOOLKIT.Message.ScenePullActivate.Pulled', {sceneName: thisScene.name, sceneActiveState}))\n }\n }\n};","author":"k5y1jEYMBqd9uLUx","img":"modules/wfrp4e-gm-toolkit/assets/icons/pull-to-scene.svg","actorIds":[]} {"_id":"wN47JNwM2POBSUUm","name":"Make Secret Party Test","type":"script","author":"CIvnsqepNlj0qrKz","img":"modules/wfrp4e-gm-toolkit/assets/icons/make-secret-party-test.svg","scope":"global","command":"// === Set up test parameters\nlet targetSkill = \"Perception\"; // eg, basic skill\n// let targetSkill = \"Stealth (Rural)\"; // eg, grouped skill\n// let targetSkill = \"Play (Lute)\"; // eg, advanced skill\n\nlet silentTest = true; // true: to bypass roll dialog and post results directly to chat using the following default options\n// let silentTest = false; // false: to use the native Roll Dialog for control over talents and other modifiers/bonuses, which may vary by character; \n\n// === Set default options for silent tests (ignored for interactive tests)\nlet rollMode = \"blindroll\" // choose from \"gmroll\", \"blindroll\", \"selfroll\", \"public\"\nlet slBonus = 0\nlet successBonus = 0\nlet testModifier = +20 // game.wfrp4e.config.difficultyModifiers[(\"average\")]\n// --- testModifier can be numeric or reference built-in system difficultyModifiers \n// --- (eg, 'game.wfrp4e.config.difficultyModifiers[(\"average\")]' for +20)\n// --- accepted difficultyModifier values are listed in the REFERENCES section below\n\n// === Carry out the test if you are a GM\nif (game.user.isUniqueGM) \n {makeSecretPartyTest(targetSkill)} \nelse \n {ui.notifications.error(game.i18n.localize(\"GMTOOLKIT.Message.MakeSecretPartyTest.NoPermission\"), {})\n};\n\n// === Where the magic happens. \n\nasync function makeSecretPartyTest(targetSkill) {\n let party = Array.from(game.actors).filter(a => a.hasPlayerOwner && a.type == \"character\");\n for (let pc of party) {\n // stack tests rather than await: cancelling a non-silent test during an await(ed) call will abandon all subsequent tests\n let pcSkill = game.gmtoolkit.utility.hasSkill(pc, targetSkill, \"silent\")\n if (pcSkill != null) { \n // Roll against the skill if the actor has it ...\n pc.setupSkill(pcSkill, {bypass: silentTest, testModifier, slBonus, successBonus, rollMode, title : `${pcSkill.name} Test (${pc.name})`}).then(setupData => {pc.basicTest(setupData)});\n } else {\n // ... or fallback to underlying characteristic if they don't\n pcSkill = await game.wfrp4e.utility.findSkill(targetSkill)\n // TODO: optionally step-adjust the difficulty in case of fallback\n if (pcSkill.advanced.value == \"adv\" && !game.settings.get(\"wfrp4e-gm-toolkit\", \"fallbackAdvancedSkills\")) {\n ui.notifications.info(`${game.i18n.format(\"GMTOOLKIT.Message.MakeSecretPartyTest.AbortAdvancedSkillTest\", {character: pc.name, skill: targetSkill})}`)\n continue \n }\n let skillCharacteristic = game.wfrp4e.config.characteristics[pcSkill.characteristic.value]\n pc.setupCharacteristic(pcSkill.characteristic.value, {bypass: silentTest, testModifier, slBonus, successBonus, rollMode, title : `${skillCharacteristic} Test for ${pcSkill.name} (${pc.name})`}).then(setupData => {pc.basicTest(setupData)});\n } \n }\n}\n\n/* ==========\n * MACRO: Make Secret Party Test\n * VERSION: 0.8.0\n * UPDATED: 2021-12-31\n * DESCRIPTION: Macro template for GMs to run secret skill tests for their party. \n * TIP: Create copies of this macro and adjust the parameters (and title and/or icon) for different skill tests. Add them to your macro bar for one-click resolution. \n * TIP: By default, tests are rolled blind. Right-click a test result in the chat log to show the results to players.\n * TIP: You can use the results of a secret test in an opposed test as normal (using the double arrows in the chat log card.), such as secret Stealth v Perception tests\n ========== */\n\n/* === REFERENCES: \n* --- Accepted difficultyModifier values that can be used for testModifier\n* \"veasy\": \"Very Easy (+60)\",\n* \"easy\": \"Easy (+40)\",\n* \"average\": \"Average (+20)\",\n* \"challenging\": \"Challenging (+0)\",\n* \"difficult\": \"Difficult (-10)\",\n* \"hard\": \"Hard (-20)\",\n* \"vhard\": \"Very Hard (-30)\",\n* \"futile\": \"Futile (-40)\" // requires Enemy in Shadows module\n* \"impossible\": \"Impossible (-50)\" // requires Enemy in Shadows module\n*/","folder":null,"sort":0,"permission":{"default":0,"CIvnsqepNlj0qrKz":3},"flags":{"core":{"sourceId":"Macro.lQMBdQOkcPIIjfIe"}}} diff --git a/packs/gm-toolkit-tables.db b/packs/gm-toolkit-tables.db new file mode 100644 index 0000000..cc6385a --- /dev/null +++ b/packs/gm-toolkit-tables.db @@ -0,0 +1 @@ +{"_id":"9rXQv4uJcQoLBitt","name":"Dark Whispers","img":"icons/svg/d20-grey.svg","description":"Generic Dark Whispers that are used as placeholder prompts by the Send Dark Whispers macro.","results":[{"_id":"g9rsj2kp26oh6r3z","type":0,"text":"Let your quarry go.","range":[1,1],"drawn":false,"flags":{},"img":"icons/svg/d20-black.svg","collection":null,"resultId":null,"weight":null,"rangeL":1,"rangeH":1},{"_id":"h3hvuahahukmiklq","type":0,"text":"'Accidentally' strike an ally.","range":[2,2],"drawn":false,"flags":{},"img":"icons/svg/d20-black.svg","collection":null,"resultId":null,"weight":null,"rangeL":2,"rangeH":2},{"_id":"9ltv15jz9n4pkc7m","type":0,"text":"Fall asleep on watch.","range":[3,3],"drawn":false,"flags":{},"img":"icons/svg/d20-black.svg","collection":null,"resultId":null,"weight":null,"rangeL":3,"rangeH":3},{"_id":"j1z9hqs65ife4pqx","type":0,"text":"Kneel before the Magister. Show your allegiance.","range":[4,4],"drawn":false,"flags":{},"img":"icons/svg/d20-black.svg","collection":null,"resultId":null,"weight":null,"rangeL":4,"rangeH":4},{"_id":"ds7tn3xsonqyquvo","type":0,"text":"Nobody's that good or lucky. However popular or powerful they are. Call out the cheat!","range":[5,5],"drawn":false,"flags":{},"img":"icons/svg/d20-black.svg","collection":null,"resultId":null,"weight":null,"rangeL":5,"rangeH":5},{"_id":"3cx7xfwzxscq40p2","type":0,"text":"You're not as prepared as you thought you were.","range":[6,6],"drawn":false,"flags":{},"img":"icons/svg/d20-black.svg","collection":null,"resultId":null,"weight":null,"rangeL":6,"rangeH":6},{"_id":"2rq045im6k1pft53","type":0,"text":"Exert your authority. Make an example of the weakest or most vulnerable.","range":[6,6],"drawn":false,"flags":{},"img":"icons/svg/d20-black.svg","collection":null,"resultId":null,"weight":null,"rangeL":6,"rangeH":6},{"_id":"5iq16gdx32qx83yv","type":0,"text":"The big indestructible one over there is looking at you funny. Don't stand for it. Defend your honour. Publicly and on your own.","range":[7,7],"drawn":false,"flags":{},"img":"icons/svg/d20-black.svg","collection":null,"resultId":null,"weight":null,"rangeL":7,"rangeH":7},{"_id":"azxfuejsd1v28wym","type":0,"text":"You didn't come all this way to make friends. Sometimes you end up making enemies. This is one of those opportunities.","range":[8,8],"drawn":false,"flags":{},"img":"icons/svg/d20-black.svg","collection":null,"resultId":null,"weight":null,"rangeL":8,"rangeH":8},{"_id":"gmfs74ty8wbm8eoy","type":0,"text":"Secrets are burdensome. Be generous and give them away.","range":[9,9],"drawn":false,"flags":{},"img":"icons/svg/d20-black.svg","collection":null,"resultId":null,"weight":null,"rangeL":9,"rangeH":9}],"formula":"1d9","replacement":true,"displayRoll":true,"folder":null,"sort":0,"permission":{"default":0,"Lngg3hbkX3DayZX9":3},"flags":{"wfrp4e":{"key":"darkwhispers"},"core":{"sourceId":"RollTable.9rXQv4uJcQoLBitt"}}} diff --git a/scripts/macros/dark-whispers.js b/scripts/macros/dark-whispers.js index eb28c16..5ed3575 100644 --- a/scripts/macros/dark-whispers.js +++ b/scripts/macros/dark-whispers.js @@ -1,34 +1,29 @@ -/* Open a dialog to send a Dark Whisper (WFRP p183) to selected player character(s). - * Adapted from original macro developed by Vindico#9103. - */ +formDarkWhispers(); // Set default user target filter in module settings. Override by adding parameter to "all", "absent" or "present" party members. Clear parameter to revert to default. -formDarkWhispers(); // Set default user target filter in module settings. Override by adding parameter to 'all', 'absent' or 'present' party members. Clear parameter to revert to default. - -function formDarkWhispers(targets=String(game.settings.get("wfrp4e-gm-toolkit", "targetDarkWhispers"))) { - - // Non-GMs are not permitted to send Dark Whispers +async function formDarkWhispers(targets=String(game.settings.get("wfrp4e-gm-toolkit", "targetDarkWhispers"))) { +// Non-GMs are not permitted to send Dark Whispers if (!game.user.isGM) { - return ui.notifications.error(game.i18n.localize('GMTOOLKIT.Message.DarkWhispers.NoPermission')); + return ui.notifications.error(game.i18n.localize("GMTOOLKIT.Message.DarkWhispers.NoPermission")); } //TODO: Switch to actor based selection // Only users who have an assigned player character are available to whisper to. let users = [] switch (targets) { - case 'all': - users = game.users.filter(user => (user.character?.type == 'character')); + case "all": + users = game.users.filter(user => (user.character?.type == "character")); break; - case 'absent': - users = game.users.filter(user => !user.active && (user.character?.type == 'character')); + case "absent": + users = game.users.filter(user => !user.active && (user.character?.type == "character")); break; - case 'present': + case "present": default: - users = game.users.filter(user => user.active && (user.character?.type == 'character')); + users = game.users.filter(user => user.active && (user.character?.type == "character")); break; } if (!users) { - return ui.notifications.error(game.i18n.localize('GMTOOLKIT.Message.DarkWhispers.NoEligibleCharacters')); + return ui.notifications.error(game.i18n.localize("GMTOOLKIT.Message.DarkWhispers.NoEligibleCharacters")); } // Build list of player / characters to select via dialog @@ -48,35 +43,37 @@ function formDarkWhispers(targets=String(game.settings.get("wfrp4e-gm-toolkit",
- +
` }); // abort if no characters have any Corruption if (corruptionAvailable == 0) { - return ui.notifications.error(game.i18n.localize('GMTOOLKIT.Message.DarkWhispers.NoEligibleCharacters')); + return ui.notifications.error(game.i18n.localize("GMTOOLKIT.Message.DarkWhispers.NoEligibleCharacters")); }; - // Construct and show form to write whisper message and select target player characters + // Construct and show form to write whisper message and select target player characters + let darkwhisper = (game.tables.getName("Dark Whispers")) ? await game.wfrp4e.tables.rollTable("darkwhispers") : game.i18n.format("GMTOOLKIT.Dialog.DarkWhispers.ImportTable") + let dialogContent = `
- +
${checkOptions}
- +
- +
` new Dialog({ - title: game.i18n.localize('GMTOOLKIT.Dialog.DarkWhispers.Title'), + title: game.i18n.localize("GMTOOLKIT.Dialog.DarkWhispers.Title"), content:dialogContent, buttons:{ whisper:{ - label: game.i18n.localize('GMTOOLKIT.Dialog.DarkWhispers.SendWhisper'), + label: game.i18n.localize("GMTOOLKIT.Dialog.DarkWhispers.SendWhisper"), callback: (html) => sendDarkWhispers(html, users, "private") } } @@ -85,24 +82,24 @@ function formDarkWhispers(targets=String(game.settings.get("wfrp4e-gm-toolkit", function sendDarkWhispers(html, users, sendmode) { // check for whisper message - var messageText = html.find('[name="message"]')[0].value; + let messageText = html.find('[name="message"]')[0].value; // build list of selected players ids for whispers target - var targets = []; + let targets = []; for ( let user of users ) { - if (html.find('[name="'+user.id+'"]')[0].checked){ + if (html.find(`[name="${user.id}"]`)[0].checked){ targets.push(user.id); } } // abort if no whisper or character is selected if (targets.length == 0 || messageText.length == 0) { - return ui.notifications.error(game.i18n.format('GMTOOLKIT.Message.DarkWhispers.WhisperAborted', {currentUser: game.users.current.name})); + return ui.notifications.error(game.i18n.format("GMTOOLKIT.Message.DarkWhispers.WhisperAborted", {currentUser: game.users.current.name})); } // Construct and send message to whisper targets // Build the translation string based on the setting - let messageTemplate = "GMTOOLKIT.Settings.DarkWhispers.message" + `.${game.settings.get("wfrp4e-gm-toolkit", "messageDarkWhispers")}` + let messageTemplate = `GMTOOLKIT.Settings.DarkWhispers.message.${game.settings.get("wfrp4e-gm-toolkit", "messageDarkWhispers")}` // Parse the translated message let whisperMessage = `${game.i18n.format(messageTemplate, {message: messageText})}` @@ -110,14 +107,25 @@ function sendDarkWhispers(html, users, sendmode) { // data- attributes are used by listener let responseObjects = ` - ${game.i18n.localize('GMTOOLKIT.Message.DarkWhispers.Accept')} - ${game.i18n.localize('GMTOOLKIT.Message.DarkWhispers.Reject')} + ${game.i18n.localize("GMTOOLKIT.Message.DarkWhispers.Accept")} + ${game.i18n.localize("GMTOOLKIT.Message.DarkWhispers.Reject")} - ` + ` ChatMessage.create({ content: whisperMessage + responseObjects, whisper: targets }); -} \ No newline at end of file +} + + +/* ========== +* MACRO: Send Dark Whispers +* VERSION: 0.9.0 +* UPDATED: 2022-01-01 +* DESCRIPTION: Open a dialog to send a Dark Whisper (WFRP p183) to one or more selected player character(s). +* TIP: Only player-assigned characters with Corruption can be sent a Dark Whisper. +* TIP: The placeholder whisper is drawn from the Dark Whispers table. Change this for different random whispers. +* TIP: The whisper can be edited in the dialog, regardless of what is pre-filled from the Dark Whispers table. +========== */ \ No newline at end of file From c722336496774ad2b1db712b99cece740fa4eacb Mon Sep 17 00:00:00 2001 From: Jag Goraya Date: Sun, 2 Jan 2022 03:09:03 +0000 Subject: [PATCH 3/5] Add RollTable compatibility for Token Hud Extension shortcuts (#74) --- CHANGELOG.md | 15 +++++++++------ lang/en.json | 2 ++ lang/fr.json | 2 ++ modules/token-hud-extension.mjs | 31 +++++++++++++++++-------------- 4 files changed, 30 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4cc9d1e..004dc24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,12 +5,15 @@ All notable changes to this project will be documented in this file. The format ## Unreleased See [Issue Backlog](../../issues) and [Roadmap](../../milestones). -- *Changed* Foundry compatibility to up to ###. WFRP4e system compatibility is up to ###. -- *Fix* non-rendered html in Marginal Success roll description. -- *Fixed* issue where module settings could not be accessed ([#70](https://github.com/Jagusti/fvtt-wfrp4e-gmtoolkit/issues/70)). -- *Changed* Send Dark Whispers macro to be compatible with WFRP4e v5.0.4 ([#69](https://github.com/Jagusti/fvtt-wfrp4e-gmtoolkit/issues/69)) -- *Added* Dark Whispers RollTable and compendium for use by Send Dark Whispers macro. Added prompt to import table in Send Dark Whispers dialog if not present. - +- *Changed* Foundry compatibility to ###. WFRP4e system compatibility is ###. +- *Fixed* non-rendered html in Marginal Success roll description. +- *Fixed* issue where module settings could not be accessed [[#70](https://github.com/Jagusti/fvtt-wfrp4e-gmtoolkit/issues/70)]. +- *Added* RollTable compatibility to the Send Dark Whispers macro. This fixes issues with "undefined" Dark Whisper text in the Send Dark Whispers dialog [[#69](https://github.com/Jagusti/fvtt-wfrp4e-gmtoolkit/issues/69)]. +- *Added* Dark Whispers RollTable from compendium for use by Send Dark Whispers macro. +- *Added* prompt to import table in Send Dark Whispers dialog if not present. +- *Added* RollTable compatibility to Token Hud Extensions. This fixes issues with rolling for Mental Corruption, Physical Mutation and Wrath of the Gods using Token Hud Extension shortcuts. [[#69](https://github.com/Jagusti/fvtt-wfrp4e-gmtoolkit/issues/69)]. +- *Changed* Token Hud shortcut for Mental Corruption from `CTRL+SHIFT+double-click` to `SHIFT+double-click`. +- *Added* message prompt to install Mental Corruption, Physical Mutation and Wrath of the Gods Rolltables if not present. ## Version 0.8.0 - *Changed* Advantage scripts to handle non-token characters. diff --git a/lang/en.json b/lang/en.json index 0099321..7967841 100644 --- a/lang/en.json +++ b/lang/en.json @@ -22,6 +22,8 @@ "GMTOOLKIT.TokenHudExtension.StatusChanged" : "{targetStatus} changed from {originalStatus} to {newStatus} for {targetName}", "GMTOOLKIT.TokenHudExtension.StatusNotChanged" : "{targetStatus} is unchanged at {originalStatus} for {targetName}", "GMTOOLKIT.TokenHudExtension.LittlePrayerResult" : "

{actorName} offered a Little Prayer to the Gods ({littlePrayerResult}).

", + "GMTOOLKIT.TokenHudExtension.ImportTable" : "{table} table not found. Please import from the Compendium.", + "GMTOOLKIT.TokenHudExtension.Sufferance" : "{actor} suffers {plight}", "GMTOOLKIT.Dialog.Apply": "Apply Changes", "GMTOOLKIT.Dialog.Cancel" : "Cancel", diff --git a/lang/fr.json b/lang/fr.json index de796f5..f607a04 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -22,6 +22,8 @@ "GMTOOLKIT.TokenHudExtension.StatusChanged" : "{targetStatus} est passé de {originalStatus} à {newStatus} pour {targetName}", "GMTOOLKIT.TokenHudExtension.StatusNotChanged" : "{targetStatus} demeure à {originalStatus} pourr {targetName}", "GMTOOLKIT.TokenHudExtension.LittlePrayerResult" : "

{actorName} a offert une Modeste Prière aux Dieux ({littlePrayerResult}).

", + "GMTOOLKIT.TokenHudExtension.ImportTable" : "{table} table not found. Please import from the Compendium.", + "GMTOOLKIT.TokenHudExtension.Sufferance" : "{actor} suffers {plight}", "GMTOOLKIT.Dialog.Apply" : "Appliquer les changements", "GMTOOLKIT.Dialog.Cancel" : "Annuler", diff --git a/modules/token-hud-extension.mjs b/modules/token-hud-extension.mjs index 5c9a7f0..9cbe41b 100644 --- a/modules/token-hud-extension.mjs +++ b/modules/token-hud-extension.mjs @@ -351,14 +351,6 @@ export default class TokenHudExtension { }) hudCorruption.find("i").dblclick(async (ev) => { GMToolkit.log(false, `Corruption hud extension double-clicked.`) - if (ev.ctrlKey && ev.shiftKey && wfrp4eContent.core) { - let result = game.wfrp4e.tables.formatChatRoll("mutatemental"); - ChatMessage.create(game.wfrp4e.utility.chatDataSetup(result, "roll", true)); - GMToolkit.log(false, `${actor.name} spawned a mental mutation.`) - ev.preventDefault(); - ev.stopPropagation(); - return; - } if (ev.ctrlKey && ev.altKey) { let littlePrayer = new Roll("d100") littlePrayer.roll(); @@ -370,17 +362,28 @@ export default class TokenHudExtension { return; } if (ev.shiftKey && ev.altKey && wfrp4eContent.core) { - let result = game.wfrp4e.tables.formatChatRoll("wrath"); - ChatMessage.create(game.wfrp4e.utility.chatDataSetup(result, "roll", true)); - GMToolkit.log(false, `${actor.name} incurred the Wrath of the Gods.`) + let result = (game.tables.getName("Wrath of the Gods Table")) ? await game.wfrp4e.tables.rollTable("wrath") : `${game.i18n.format("GMTOOLKIT.TokenHudExtension.ImportTable", {table: "Wrath of the Gods"})}` + let chatData = game.wfrp4e.utility.chatDataSetup(result?.result || result, "gmroll", true) + chatData.flavor = game.i18n.format("GMTOOLKIT.TokenHudExtension.Sufferance", {actor: actor.name, plight: "the Wrath of the Gods"}) + ChatMessage.create(chatData, {}); ev.preventDefault(); ev.stopPropagation(); return; } if (ev.ctrlKey && wfrp4eContent.core) { - let result = game.wfrp4e.tables.formatChatRoll("mutatephys"); - ChatMessage.create(game.wfrp4e.utility.chatDataSetup(result, "roll", true)); - GMToolkit.log(false, `${actor.name} spawned a physical mutation.`) + let result = (game.tables.getName("Physical Mutation")) ? await game.wfrp4e.tables.rollTable("mutatephys") : `${game.i18n.format("GMTOOLKIT.TokenHudExtension.ImportTable", {table: "Physical Mutation"})}` + let chatData = game.wfrp4e.utility.chatDataSetup(result?.result || result, "gmroll", true) + chatData.flavor = game.i18n.format("GMTOOLKIT.TokenHudExtension.Sufferance", {actor: actor.name, plight: "Physical Mutation"}) + ChatMessage.create(chatData, {}); + ev.preventDefault(); + ev.stopPropagation(); + return; + } + if (ev.shiftKey && wfrp4eContent.core) { + let result = (game.tables.getName("Mental Corruption")) ? await game.wfrp4e.tables.rollTable("mutatemental") : `${game.i18n.format("GMTOOLKIT.TokenHudExtension.ImportTable", {table: "Mental Corruption"})}` + let chatData = game.wfrp4e.utility.chatDataSetup(result?.result || result, "gmroll", true) + chatData.flavor = game.i18n.format("GMTOOLKIT.TokenHudExtension.Sufferance", {actor: actor.name, plight: "Mental Corruption"}) + ChatMessage.create(chatData, {}); ev.preventDefault(); ev.stopPropagation(); return; From 410122616051d454428ab0103e94981ced246822 Mon Sep 17 00:00:00 2001 From: Jag Goraya Date: Sun, 2 Jan 2022 23:29:00 +0000 Subject: [PATCH 4/5] Add FVTT v9 compatibility for Set Token Vision and Light macro (#76) --- CHANGELOG.md | 7 ++ lang/en.json | 2 +- packs/gm-toolkit-macros.db | 2 +- scripts/macros/set-token-vision-light.js | 110 +++++++++++++---------- 4 files changed, 74 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 004dc24..54640dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,13 @@ See [Issue Backlog](../../issues) and [Roadmap](../../milestones). - *Added* RollTable compatibility to Token Hud Extensions. This fixes issues with rolling for Mental Corruption, Physical Mutation and Wrath of the Gods using Token Hud Extension shortcuts. [[#69](https://github.com/Jagusti/fvtt-wfrp4e-gmtoolkit/issues/69)]. - *Changed* Token Hud shortcut for Mental Corruption from `CTRL+SHIFT+double-click` to `SHIFT+double-click`. - *Added* message prompt to install Mental Corruption, Physical Mutation and Wrath of the Gods Rolltables if not present. +- *Changed* Set Token Vision and Light macro to be compatible with Foundry v9 canvas and lighting updates. This fixes issues with tokens not updating light and sight radius or animation when using the macro [[#68](https://github.com/Jagusti/fvtt-wfrp4e-gmtoolkit/issues/68)]. + - Sight changes may not be refreshed until tokens are moved or deselected and then reselected. This is due to an outstanding Foundry core bug [#6389]((https://gitlab.com/foundrynet/foundryvtt/-/issues/6389)). +- *Changed* listed radius for Soulcast (Miscast) from 15m to 15yds, to maintain consistency with Ablaze condition lighting details. French translation retains metric units. +- *Changed* Storm Lantern narrowbeam angle from 60 to 90 degrees, as per WFRP p309. Token lock rotation is no longer adjusted (previously the macro would ensure it was not locked.) +- *Changed* Night Vision to use sight angle of light source. +- *Fixed* issue with Night Vision multiplier being applied to Dim Sight on tokens that do not have Night Vision when multiple tokens are selected. +- *Removed* redundant "nochange" processing for light and sight, as this is not triggered. ## Version 0.8.0 - *Changed* Advantage scripts to handle non-token characters. diff --git a/lang/en.json b/lang/en.json index 7967841..afaa40f 100644 --- a/lang/en.json +++ b/lang/en.json @@ -50,7 +50,7 @@ "GMTOOLKIT.LightSource.GlowingSkin" : "Glowing Skin (Mutation) (10yd)", "GMTOOLKIT.LightSource.Ablaze" : "Ablaze (Condition) (15 yds)", "GMTOOLKIT.LightSource.Pha" : "Pha's Protection (WP Bonus yds)", - "GMTOOLKIT.LightSource.Soulfire" : "Soulfire (Miscast) (15 m)", + "GMTOOLKIT.LightSource.Soulfire" : "Soulfire (Miscast) (15 yds)", "GMTOOLKIT.Vision.Normal" : "Normal", "GMTOOLKIT.Vision.None" : "None", diff --git a/packs/gm-toolkit-macros.db b/packs/gm-toolkit-macros.db index 68cb93a..9f4fb14 100644 --- a/packs/gm-toolkit-macros.db +++ b/packs/gm-toolkit-macros.db @@ -2,7 +2,7 @@ {"_id":"6EKiEQZTbmQN97Vr","name":"Clear Advantage","type":"script","author":"k5y1jEYMBqd9uLUx","img":"modules/wfrp4e-gm-toolkit/assets/icons/advantage-clear.svg","scope":"global","command":"/* Resets Advantage for the selected token to 0 */\n\ngame.gmtoolkit.advantage.updateAdvantage(token,\"clear\");","folder":null,"sort":0,"permission":{"default":0,"k5y1jEYMBqd9uLUx":3},"flags":{}} {"_id":"6UwKY8nGZyXzAXCa","name":"Toggle Scene Visibility and Light","permission":{"default":0,"h21fB4dFvdDBRcFD":3},"type":"script","flags":{"furnace":{"runAsGM":false}},"scope":"global","command":"/* Toggles the Token Vision and Global Illumination settings. \n * If Token Vision is set, Global Illumination is unset (and vice-versa).\n * Applies to the scene being viewed, which is not necessarily the active scene.\n */\n\n\nlet thisScene = game.scenes.viewed;\nlet uiNotice = game.i18n.format(\"GMTOOLKIT.Message.UnexpectedNoChange\", {});\n\nif (thisScene.data.tokenVision) {\n thisScene.update({tokenVision: false, globalLight: true});\n uiNotice = game.i18n.format(\"GMTOOLKIT.Scene.GlobalNotToken\", {sceneName: thisScene.name}, {});\n} else {\n thisScene.update({tokenVision: true, globalLight: false});\n uiNotice = game.i18n.format(\"GMTOOLKIT.Scene.TokenNotGlobal\", {sceneName: thisScene.name}, {});\n}\n\nui.notifications.notify(uiNotice);","author":"h21fB4dFvdDBRcFD","img":"modules/wfrp4e-gm-toolkit/assets/icons/toggle-scene-light.svg","actorIds":[]} {"_id":"AjUYYy7qAN55BERN","name":"Add Advantage","type":"script","author":"k5y1jEYMBqd9uLUx","img":"modules/wfrp4e-gm-toolkit/assets/icons/advantage-add.svg","scope":"global","command":"/* Increases Advantage for the selected token by 1.\n * Caps at character's maximum advantage. \n*/\n\ngame.gmtoolkit.advantage.updateAdvantage(token,\"increase\");","folder":null,"sort":0,"permission":{"default":0,"k5y1jEYMBqd9uLUx":3},"flags":{}} -{"_id":"DGYdRmtbMZ81NmQ3","name":"Set Token Vision and Light","type":"script","author":"Zo7HSQ75uO8dWUkH","img":"modules/wfrp4e-gm-toolkit/assets/icons/set-token-vision-light.svg","scope":"global","command":"/* Open a dialog for quickly changing vision and lighting parameters of the selected token(s).\n * This macro is adapted for WFRP4e from Token Vision Configuration by @Sky#9453\n * https://github.com/Sky-Captain-13/foundry/tree/master/scriptMacros\n */\n\nsetTokenVisionLight();\n\nasync function setTokenVisionLight() { \n if (canvas.tokens.controlled.length < 1) \n return ui.notifications.error( game.i18n.localize(\"GMTOOLKIT.Token.Select\"),{} );\n\n let applyChanges = false;\n\n new Dialog({\n title: game.i18n.localize(\"GMTOOLKIT.Dialog.SetVisionLight.Title\"),\n content: `\n
\n
\n \n \n
\n
\n \n \n
\n
\n `,\n buttons: {\n yes: {\n icon: \"\",\n label: game.i18n.localize(\"GMTOOLKIT.Dialog.Apply\"),\n callback: () => applyChanges = true\n },\n no: {\n icon: \"\",\n label: game.i18n.localize(\"GMTOOLKIT.Dialog.Cancel\"),\n },\n },\n default: \"yes\",\n close: html => {\n if (applyChanges) {\n for ( let token of canvas.tokens.controlled ) {\n let visionType = html.find('[name=\"vision-type\"]')[0].value || \"nochange\"; // TODO: default vision option based on condition -> trait -> talent. Issue Log: https://github.com/Jagusti/fvtt-wfrp4e-gmtoolkit/issues/42\n let item; // used for finding whether token has Night Vision or Dark Vision \n let lightSource = html.find('[name=\"light-source\"]')[0].value || \"nochange\";\n let advNightVision = 0;\n let dimSight = 0;\n let brightSight = 0;\n let dimLight = 0;\n let brightLight = 0; // \n let lightAngle = 360;\n let lockRotation = token.data.lockRotation;\n let lightColor = \"\"; \n let lightColorIntensity = 0;\n let animationIntensity = 1;\n let animationSpeed = 1;\n let animationType = \"none\";\n \n // Get Light Source Values\n switch (lightSource) {\n case \"none\":\n case \"storm-shut\":\n dimLight = 0;\n break;\n case \"matches\":\n dimLight = 5;\n brightLight = 2;\n lightColor = \"#ffaa00\";\n lightColorIntensity = 0.3;\n animationIntensity = 8;\n animationSpeed = 8;\n animationType = \"torch\";\n break;\n case \"candle\":\n dimLight = 10;\n brightLight = 5;\n lightColor = \"#ffaa00\";\n lightColorIntensity = 0.3;\n animationIntensity = 8;\n animationSpeed = 8;\n animationType = \"torch\";\n break;\n case \"davrich-lamp\":\n dimLight = 10;\n brightLight = 5;\n lightColor = \"#ffaa00\";\n lightColorIntensity = 0.4;\n animationIntensity = 4;\n animationSpeed = 4;\n animationType = \"torch\";\n break;\n case \"torch\":\n dimLight = 15;\n brightLight = 7.5;\n lightColor = \"#ffaa00\";\n lightColorIntensity = 0.5;\n animationIntensity = 7;\n animationSpeed = 7;\n animationType = \"torch\"\n break;\n case \"lantern\":\n dimLight = 20;\n brightLight = 10;\n lightColor = \"#ffcc66\";\n lightColorIntensity = 0.7;\n animationIntensity = 3;\n animationSpeed = 3;\n animationType = \"torch\";\n break;\n case \"storm-broad\":\n dimLight = 20;\n brightLight = 10;\n lightColor = \"#ffcc66\";\n lightColorIntensity = 0.5;\n animationIntensity = 1;\n animationSpeed = 2;\n animationType = \"torch\";\n break;\n case \"storm-narrow\":\n dimLight = 30;\n brightLight = 20;\n lightColor = \"#ffcc66\";\n lightColorIntensity = 0.7;\n animationIntensity = 1;\n animationSpeed = 1;\n animationType = \"torch\";\n lockRotation = false;\n lightAngle = 60;\n break;\n case \"light\":\n dimLight = 15;\n brightLight = 7.5;\n lightColor = \"#99ffff\";\n lightColorIntensity = 0.5;\n animationIntensity = 3;\n animationSpeed = 2;\n animationType = \"pulse\"\n break;\n case \"witchlight\":\n dimLight = 20;\n brightLight = 10;\n lightColor = \"#99ffff\";\n lightColorIntensity = 0.7;\n animationIntensity = 6;\n animationSpeed = 2;\n animationType = \"chroma\"\n break;\n case \"glowing-skin\":\n dimLight = 10;\n brightLight = 3;\n lightColor = \"#ffbd80\";\n lightColorIntensity = 0.3;\n animationIntensity = 2;\n animationSpeed = 2;\n animationType = \"pulse\";\n break;\n case \"ablaze\":\n dimLight = 15;\n brightLight = 7.5;\n lightColor = \"#ff7733\";\n lightColorIntensity = 0.5;\n animationIntensity = 7;\n animationSpeed = 7;\n animationType = \"torch\"\n break;\n case \"pha\":\n dimLight = token.actor.data.data.characteristics.wp.bonus;\n brightLight = token.actor.data.data.characteristics.wp.bonus;\n lightColor = \"#ffddbb\";\n lightColorIntensity = 0.6;\n animationIntensity = 4;\n animationSpeed = 4;\n animationType = \"sunburst\"\n break;\n case \"soulfire\":\n dimLight = 15;\n brightLight = 7.5;\n lightColor = \"#ff7733\";\n lightColorIntensity = 0.5;\n animationIntensity = 7;\n animationSpeed = 7;\n animationType = \"fog\"\n break;\n case \"nochange\":\n default:\n dimLight = token.data.dimLight;\n brightLight = token.data.brightLight;\n lightAngle = token.data.lightAngle;\n lockRotation = token.data.lockRotation;\n lightColor = token.data.lightColor;\n }\n \n // Get Vision Type Values\n switch (visionType) {\n case \"blindedVision\":\n brightSight = 1;\n dimSight = 0;\n break;\n case \"noVision\":\n dimSight = 0;\n brightSight = 0;\n dimLight = 0;\n brightLight = 0;\n break;\n case \"darkVision\": \n item = token.actor.items.find(i => i.data.name.toLowerCase() == game.i18n.localize(\"GMTOOLKIT.Trait.DarkVision\").toLowerCase() );\n if(item == undefined) { \n (game.settings.get(\"wfrp4e-gm-toolkit\", \"overrideDarkVision\")) ? dimSight = Number(game.settings.get(\"wfrp4e-gm-toolkit\", \"rangeDarkVision\")) : dimSight = Number(game.settings.get(\"wfrp4e-gm-toolkit\", \"rangeNormalSight\")) ;\n } else {\n dimSight = Number(game.settings.get(\"wfrp4e-gm-toolkit\", \"rangeDarkVision\"));\n }\n brightSight = (dimSight / 2);\n break;\n case \"nightVision\":\n // Night Vision requires some minimal illumination to provide a benefit\n if (game.scenes.viewed.data.darkness < 1 | dimLight > 0 | game.scenes.viewed.globalLight) {\n item = token.actor.items.find(i => i.data.name.toLowerCase() == game.i18n.localize(\"GMTOOLKIT.Talent.NightVision\").toLowerCase() );\n if(item == undefined) { \n (game.settings.get(\"wfrp4e-gm-toolkit\", \"overrideNightVision\")) ? advNightVision = 1 : advNightVision = 0 ;\n } else { \n for (let item of token.actor.items)\n {\n if (item.name.toLowerCase() == game.i18n.localize(\"GMTOOLKIT.Talent.NightVision\").toLowerCase() ) {\n switch (item.type) {\n case \"trait\" :\n advNightVision = 1;\n break;\n case \"talent\" :\n advNightVision += item.data.data.advances.value;\n break;\n }\n }\n }\n }\n brightSight = (20 * advNightVision); \n dimSight = Math.max(brightSight + dimLight, Number(game.settings.get(\"wfrp4e-gm-toolkit\", \"rangeNormalSight\")));\n }\n break;\n case \"normalVision\":\n dimSight = Number(game.settings.get(\"wfrp4e-gm-toolkit\", \"rangeNormalSight\"));\n brightSight = 0;\n break;\n case \"nochange\":\n default:\n dimSight = token.data.dimSight;\n brightSight = token.data.brightSight;\n }\n \n // Update Token\n token.document.update({\n vision: true,\n visionType: visionType,\n lightSource: lightSource,\n dimLight: dimLight, \n brightLight: brightLight,\n lightAngle: lightAngle,\n lightColor: lightColor,\n lightAlpha: lightColorIntensity,\n lightAnimation: {\n intensity: animationIntensity,\n speed: animationSpeed,\n type: animationType\n },\n dimSight: dimSight,\n brightSight: brightSight,\n lockRotation: lockRotation,\n advNightVision: advNightVision\n });\n }\n }\n }\n }).render(true);\n};","folder":null,"sort":0,"permission":{"default":0,"Zo7HSQ75uO8dWUkH":3},"flags":{"core":{"sourceId":"Macro.issAGdpFxchQw2zp"}}} +{"_id":"DGYdRmtbMZ81NmQ3","name":"Set Token Vision and Light","type":"script","author":"Zo7HSQ75uO8dWUkH","img":"modules/wfrp4e-gm-toolkit/assets/icons/set-token-vision-light.svg","scope":"global","command":"setTokenVisionLight();\n\nasync function canvasTokensUpdate(data) {\n const updates = canvas.tokens.controlled.map(token => mergeObject({_id: token.id}, data));\n await canvas.scene.updateEmbeddedDocuments(\"Token\", updates)\n}\n\nasync function setTokenVisionLight() { \n if (canvas.tokens.controlled.length < 1) \n return ui.notifications.error( game.i18n.localize(\"GMTOOLKIT.Token.Select\"),{} );\n\n let applyChanges = false;\n\nnew Dialog({\n title: game.i18n.localize(\"GMTOOLKIT.Dialog.SetVisionLight.Title\"),\n content: `\n
\n
\n \n \n
\n
\n \n \n
\n
\n `,\n buttons: {\n yes: {\n icon: \"\",\n label: game.i18n.localize(\"GMTOOLKIT.Dialog.Apply\"),\n callback: () => applyChanges = true\n },\n no: {\n icon: \"\",\n label: game.i18n.localize(\"GMTOOLKIT.Dialog.Cancel\"),\n },\n },\n default: \"yes\",\n close: html => {\n if (applyChanges) {\n\n for ( let token of canvas.tokens.controlled ) {\n \n // Define a set of baseline values. Light Source and Vision choices will only change the properties that differ. Not all of the options available since Foundry v9 are used.\n // Baseline Vision Values\n let advNightVision = 0;\n let dimSight = 0;\n let brightSight = 0;\n let sightAngle = 360;\n // Baseline Light Source Values\n let dimLight = 0;\n let brightLight = 0; \n let lightAngle = 360;\n let lightColor = null; \n let lightColorIntensity = 0;\n let animationIntensity = 1;\n let animationSpeed = 1;\n let animationType = \"none\";\n\n let visionType = html.find('[name=\"vision-type\"]')[0].value; // TODO: default vision option based on condition -> trait -> talent. Issue Log: https://github.com/Jagusti/fvtt-wfrp4e-gmtoolkit/issues/42\n let item; // used for finding whether token has Night Vision or Dark Vision \n let lightSource = html.find('[name=\"light-source\"]')[0].value;\n \n // Get Light Source Values\n switch (lightSource) {\n case \"none\":\n case \"storm-shut\":\n dimLight = 0;\n break;\n case \"matches\":\n dimLight = 5;\n brightLight = 2;\n lightColor = \"#ffaa00\";\n lightColorIntensity = 0.3;\n animationIntensity = 8;\n animationSpeed = 8;\n animationType = \"torch\";\n break;\n case \"candle\":\n dimLight = 10;\n brightLight = 5;\n lightColor = \"#ffaa00\";\n lightColorIntensity = 0.3;\n animationIntensity = 8;\n animationSpeed = 8;\n animationType = \"torch\";\n break;\n case \"davrich-lamp\":\n dimLight = 10;\n brightLight = 5;\n lightColor = \"#ffaa00\";\n lightColorIntensity = 0.4;\n animationIntensity = 4;\n animationSpeed = 4;\n animationType = \"torch\";\n break;\n case \"torch\":\n dimLight = 15;\n brightLight = 7.5;\n lightColor = \"#ffaa00\";\n lightColorIntensity = 0.5;\n animationIntensity = 7;\n animationSpeed = 7;\n animationType = \"torch\"\n break;\n case \"lantern\":\n dimLight = 20;\n brightLight = 10;\n lightColor = \"#ffcc66\";\n lightColorIntensity = 0.7;\n animationIntensity = 3;\n animationSpeed = 3;\n animationType = \"torch\";\n break;\n case \"storm-broad\":\n dimLight = 20;\n brightLight = 10;\n lightColor = \"#ffcc66\";\n lightColorIntensity = 0.5;\n animationIntensity = 1;\n animationSpeed = 2;\n animationType = \"torch\";\n break;\n case \"storm-narrow\":\n dimLight = 30;\n brightLight = 20;\n lightColor = \"#ffcc66\";\n lightColorIntensity = 0.7;\n animationIntensity = 1;\n animationSpeed = 1;\n animationType = \"torch\";\n lightAngle = 90;\n break;\n case \"light\":\n dimLight = 15;\n brightLight = 7.5;\n lightColor = \"#99ffff\";\n lightColorIntensity = 0.5;\n animationIntensity = 3;\n animationSpeed = 2;\n animationType = \"pulse\"\n break;\n case \"witchlight\":\n dimLight = 20;\n brightLight = 10;\n lightColor = \"#99ffff\";\n lightColorIntensity = 0.7;\n animationIntensity = 6;\n animationSpeed = 2;\n animationType = \"chroma\"\n break;\n case \"glowing-skin\":\n dimLight = 10;\n brightLight = 3;\n lightColor = \"#ffbd80\";\n lightColorIntensity = 0.3;\n animationIntensity = 2;\n animationSpeed = 2;\n animationType = \"pulse\";\n break;\n case \"ablaze\":\n dimLight = 15;\n brightLight = 7.5;\n lightColor = \"#ff7733\";\n lightColorIntensity = 0.5;\n animationIntensity = 7;\n animationSpeed = 7;\n animationType = \"torch\"\n break;\n case \"pha\":\n dimLight = token.actor.data.data.characteristics.wp.bonus;\n brightLight = token.actor.data.data.characteristics.wp.bonus;\n lightColor = \"#ffddbb\";\n lightColorIntensity = 0.6;\n animationIntensity = 4;\n animationSpeed = 4;\n animationType = \"sunburst\"\n break;\n case \"soulfire\":\n dimLight = 15;\n brightLight = 7.5;\n lightColor = \"#ff7733\";\n lightColorIntensity = 0.5;\n animationIntensity = 7;\n animationSpeed = 7;\n animationType = \"fog\"\n break;\n default:\n dimLight = token.data.light.dim;\n brightLight = token.data.light.bright;\n lightAngle = token.data.light.angle;\n lightColor = token.data.light.color;\n }\n \n // Get Vision Type Values\n switch (visionType) {\n case \"blindedVision\":\n brightSight = 1;\n dimSight = 0;\n break;\n case \"noVision\":\n dimSight = 0;\n brightSight = 0;\n dimLight = 0;\n brightLight = 0;\n break;\n case \"darkVision\": \n item = token.actor.items.find(i => i.data.name.toLowerCase() == game.i18n.localize(\"GMTOOLKIT.Trait.DarkVision\").toLowerCase() );\n if(item != undefined) {\n dimSight = Number(game.settings.get(\"wfrp4e-gm-toolkit\", \"rangeDarkVision\"));\n } else { \n (game.settings.get(\"wfrp4e-gm-toolkit\", \"overrideDarkVision\")) ? dimSight = Number(game.settings.get(\"wfrp4e-gm-toolkit\", \"rangeDarkVision\")) : dimSight = Number(game.settings.get(\"wfrp4e-gm-toolkit\", \"rangeNormalSight\")) ;\n }\n brightSight = (dimSight / 2);\n break;\n case \"nightVision\":\n // Night Vision requires some minimal illumination to provide a benefit\n if (game.scenes.viewed.data.darkness < 1 | dimLight > 0 | game.scenes.viewed.data.globalLight) {\n item = token.actor.items.find(i => i.data.name.toLowerCase() == game.i18n.localize(\"GMTOOLKIT.Talent.NightVision\").toLowerCase() );\n if(item == undefined) { \n (game.settings.get(\"wfrp4e-gm-toolkit\", \"overrideNightVision\")) ? advNightVision = 1 : advNightVision = 0 ;\n } else { \n for (let item of token.actor.items)\n {\n if (item.name.toLowerCase() == game.i18n.localize(\"GMTOOLKIT.Talent.NightVision\").toLowerCase() ) {\n switch (item.type) {\n case \"trait\" :\n advNightVision = 1;\n break;\n case \"talent\" :\n advNightVision += item.data.data.advances.value;\n break;\n }\n }\n }\n }\n brightSight = (20 * advNightVision); \n dimSight = Math.max(brightSight + dimLight, Number(game.settings.get(\"wfrp4e-gm-toolkit\", \"rangeNormalSight\")));\n dimSight = advNightVision == 0 ? Number(game.settings.get(\"wfrp4e-gm-toolkit\", \"rangeNormalSight\")) : Math.max(brightSight + dimLight, Number(game.settings.get(\"wfrp4e-gm-toolkit\", \"rangeNormalSight\")));\n sightAngle = lightAngle;\n }\n console.log(`Night Vision Advances ${advNightVision}`)\n break;\n case \"normalVision\":\n dimSight = Number(game.settings.get(\"wfrp4e-gm-toolkit\", \"rangeNormalSight\"));\n brightSight = 0;\n break;\n default:\n dimSight = token.data.dimSight;\n brightSight = token.data.brightSight;\n }\n \n // Update Token\n token.document.update({\n \"vision\": true,\n \"light\": {\n \"dim\": dimLight, \n \"bright\": brightLight,\n \"angle\": lightAngle,\n \"color\": lightColor,\n \"alpha\": lightColorIntensity,\n },\n \"light.animation\": {\n \"intensity\": animationIntensity,\n \"speed\": animationSpeed,\n \"type\": animationType\n },\n \"dimSight\": dimSight,\n \"brightSight\": brightSight,\n \"sightAngle\": sightAngle\n // \"visionType\": visionType,\n // \"lightSource\": lightSource,\n // \"advNightVision\": advNightVision\n });\n token.refresh(true)\n }\n \n canvasTokensUpdate({\"vision\": true})\n\n }\n }\n }).render(true);\n};\n\n\n/* ==========\n * MACRO: Set Token Vision and Light\n * VERSION: 0.9.0\n * UPDATED: 2022-01-02\n * DESCRIPTION: Open a dialog for quickly changing vision and lighting parameters of the selected token(s).\n * TIP: Default sight range and Darkvision / Night Vision overrides can be configured in Configure Token Vision Settings under Module Settings.\n ========== */","folder":null,"sort":0,"permission":{"default":0,"Zo7HSQ75uO8dWUkH":3},"flags":{"core":{"sourceId":"Macro.issAGdpFxchQw2zp"}}} {"_id":"g9Wohpie7ODdbRKX","name":"Session Turnover","type":"script","author":"BsnP0LhXXtS4iz4M","img":"modules/wfrp4e-gm-toolkit/assets/icons/end-session.svg","scope":"global","command":"endSession();\n\nasync function endSession () {\n if (!game.user.isGM) {\n return ui.notifications.error(game.i18n.localize(\"GMTOOLKIT.Message.SessionEnd.NoPermission\"), {});\n }\n \n game.gmtoolkit.module.log(false, \"Processing Session Turnover.\")\n\n game.gmtoolkit.module.log(false, \"Pausing game.\")\n await game.togglePause(true);\n\n game.gmtoolkit.module.log(false, `Switching to holding scene.`)\n game.scenes.getName(game.settings.get(\"wfrp4e-gm-toolkit\", \"holdingScene\"))?.activate(true)\n \n game.gmtoolkit.module.log(false, \"Adding Experience.\")\n await game.macros.getName(\"Add XP\").execute();\n \n game.gmtoolkit.module.log(false, \"Resetting Fortune.\")\n await game.macros.getName(\"Reset Fortune\").execute();\n \n game.gmtoolkit.module.log(false, \"Exporting Chat.\")\n if (game.settings.get(\"wfrp4e-gm-toolkit\", \"exportChat\")) {\n await game.messages.export();\n }; \n \n if (game.settings.get(\"wfrp4e-gm-toolkit\", \"sessionID\") == \"null\") {\n game.gmtoolkit.module.log(false, `Not updating Session ID.`)\n } else {\n game.gmtoolkit.module.log(false, \"Updating Session ID.\")\n const thisSession = game.gmtoolkit.utility.getSession().id \n nextSession = Math.trunc(thisSession) == thisSession ? Number(thisSession) + 1 : thisSession;\n \n const dialog = new Dialog({\n title: (game.i18n.localize(\"GMTOOLKIT.Dialog.SessionTurnover.UpdateSessionID.Title\")),\n content: `
\n

${game.i18n.format(\"GMTOOLKIT.Dialog.SessionTurnover.UpdateSessionID.CurrentSession\", {thisSession})}

\n
\n

${game.i18n.localize(\"GMTOOLKIT.Dialog.SessionTurnover.UpdateSessionID.NextSession\")}

\n \n
\n
`,\n buttons: {\n yes: {\n icon: \"\",\n label: game.i18n.localize(\"GMTOOLKIT.Dialog.Apply\"),\n callback: (html) => {\n nextSession = html.find(\"#next-session\").val();\n game.settings.set(\"wfrp4e-gm-toolkit\", \"sessionID\", nextSession)\n game.gmtoolkit.module.log(true, `Previous Session ID was ${thisSession}. Next Session ID is ${nextSession}.`)\n }\n },\n no: {\n icon: \"\",\n label: game.i18n.localize(\"GMTOOLKIT.Dialog.Cancel\"),\n },\n },\n default: \"yes\"\n }).render(true);\n } // End Session ID Update\n\n game.gmtoolkit.module.log(false, `Completed Session Turnover tasks.`)\n};\n\n/* ==========\n * MACRO: Session Turnover\n * VERSION: 0.8.0\n * UPDATED: 2021-12-30\n * DESCRIPTION: Unified macro to run start and end of session admin, including awarding Experience Points, resetting Fortune, pausing the game and exporting the chat log. \n * TIP: Various default options can be defined in Session Management Settings under Module Settings.\n ========== */","folder":null,"sort":0,"permission":{"default":0,"BsnP0LhXXtS4iz4M":3},"flags":{}} {"_id":"ihMGjHFP3SdvYH2k","name":"Simply d100","permission":{"default":0,"k5y1jEYMBqd9uLUx":3},"type":"chat","flags":{},"scope":"global","command":"/r 1d100","author":"k5y1jEYMBqd9uLUx","img":"modules/wfrp4e-gm-toolkit/assets/icons/d100.svg","actorIds":[]} {"name":"GM Toolbox","type":"script","author":"WpuDIfNQnefaTyuV","img":"modules/wfrp4e-gm-toolkit/assets/icons/macro-chest.svg","scope":"global","command":"(()=>{\n // Add and remove macros from the list as needed. \n const macros = [\n \"Add Advantage\",\n \"Clear Advantage\",\n \"Reduce Advantage\", \n \"Session Turnover\",\n \"Add XP\",\n \"Reset Fortune\",\n \"Make Secret Party Test\",\n \"Send Dark Whispers\",\n \"Toggle Scene Visibility and Light\",\n \"Set Token Vision and Light\",\n \"Pull Everyone to Scene\", \n \"Simply d100\"\n ];\n\n let buttons = {};\n let dialog;\n let content = `
{\n buttons[name] = {\n label : name,\n icon : ``,\n callback : () => {\n game.macros.getName(name).execute();\n dialog.render(true);\n }\n }\n });\n dialog = new Dialog({title : game.i18n.localize(\"GMTOOLKIT.Dialog.GMToolbox.Title\"), content, buttons}).render(true);\n})();\n \n \n/* ==========\n* MACRO: GM Toolbox\n* VERSION: 0.8.0\n* UPDATED: 2021-12-31\n* DESCRIPTION: Adds a customisable floating dialog for quick access to frequently used Toolkit macros, freeing up hotbar spots\n* TIP: Add / remove macros from the 'macros' list to tailor it for your game. Names must exactly match those in the Macro Directory.\n* TIP: The macro dialog can be kept open for quick access and minimised to reduce space\n========== */","folder":null,"sort":0,"permission":{"default":0,"WpuDIfNQnefaTyuV":3},"flags":{"core":{"sourceId":"Macro.bY0sF26Vj0OhXwQ5"}},"_id":"iopoLXTz9kfDTfiX"} diff --git a/scripts/macros/set-token-vision-light.js b/scripts/macros/set-token-vision-light.js index 472d1f7..a44cf91 100644 --- a/scripts/macros/set-token-vision-light.js +++ b/scripts/macros/set-token-vision-light.js @@ -1,17 +1,17 @@ -/* Open a dialog for quickly changing vision and lighting parameters of the selected token(s). - * This macro is adapted for WFRP4e from Token Vision Configuration by @Sky#9453 - * https://github.com/Sky-Captain-13/foundry/tree/master/scriptMacros - */ - setTokenVisionLight(); +async function canvasTokensUpdate(data) { + const updates = canvas.tokens.controlled.map(token => mergeObject({_id: token.id}, data)); + await canvas.scene.updateEmbeddedDocuments("Token", updates) +} + async function setTokenVisionLight() { if (canvas.tokens.controlled.length < 1) return ui.notifications.error( game.i18n.localize("GMTOOLKIT.Token.Select"),{} ); let applyChanges = false; - new Dialog({ +new Dialog({ title: game.i18n.localize("GMTOOLKIT.Dialog.SetVisionLight.Title"), content: `
@@ -105,22 +105,28 @@ async function setTokenVisionLight() { default: "yes", close: html => { if (applyChanges) { + for ( let token of canvas.tokens.controlled ) { - let visionType = html.find('[name="vision-type"]')[0].value || "nochange"; // TODO: default vision option based on condition -> trait -> talent. Issue Log: https://github.com/Jagusti/fvtt-wfrp4e-gmtoolkit/issues/42 - let item; // used for finding whether token has Night Vision or Dark Vision - let lightSource = html.find('[name="light-source"]')[0].value || "nochange"; + + // Define a set of baseline values. Light Source and Vision choices will only change the properties that differ. Not all of the options available since Foundry v9 are used. + // Baseline Vision Values let advNightVision = 0; let dimSight = 0; let brightSight = 0; + let sightAngle = 360; + // Baseline Light Source Values let dimLight = 0; - let brightLight = 0; // + let brightLight = 0; let lightAngle = 360; - let lockRotation = token.data.lockRotation; - let lightColor = ""; + let lightColor = null; let lightColorIntensity = 0; let animationIntensity = 1; let animationSpeed = 1; let animationType = "none"; + + let visionType = html.find('[name="vision-type"]')[0].value; // TODO: default vision option based on condition -> trait -> talent. Issue Log: https://github.com/Jagusti/fvtt-wfrp4e-gmtoolkit/issues/42 + let item; // used for finding whether token has Night Vision or Dark Vision + let lightSource = html.find('[name="light-source"]')[0].value; // Get Light Source Values switch (lightSource) { @@ -190,8 +196,7 @@ async function setTokenVisionLight() { animationIntensity = 1; animationSpeed = 1; animationType = "torch"; - lockRotation = false; - lightAngle = 60; + lightAngle = 90; break; case "light": dimLight = 15; @@ -247,13 +252,11 @@ async function setTokenVisionLight() { animationSpeed = 7; animationType = "fog" break; - case "nochange": - default: - dimLight = token.data.dimLight; - brightLight = token.data.brightLight; - lightAngle = token.data.lightAngle; - lockRotation = token.data.lockRotation; - lightColor = token.data.lightColor; + default: + dimLight = token.data.light.dim; + brightLight = token.data.light.bright; + lightAngle = token.data.light.angle; + lightColor = token.data.light.color; } // Get Vision Type Values @@ -270,16 +273,16 @@ async function setTokenVisionLight() { break; case "darkVision": item = token.actor.items.find(i => i.data.name.toLowerCase() == game.i18n.localize("GMTOOLKIT.Trait.DarkVision").toLowerCase() ); - if(item == undefined) { - (game.settings.get("wfrp4e-gm-toolkit", "overrideDarkVision")) ? dimSight = Number(game.settings.get("wfrp4e-gm-toolkit", "rangeDarkVision")) : dimSight = Number(game.settings.get("wfrp4e-gm-toolkit", "rangeNormalSight")) ; - } else { + if(item != undefined) { dimSight = Number(game.settings.get("wfrp4e-gm-toolkit", "rangeDarkVision")); + } else { + (game.settings.get("wfrp4e-gm-toolkit", "overrideDarkVision")) ? dimSight = Number(game.settings.get("wfrp4e-gm-toolkit", "rangeDarkVision")) : dimSight = Number(game.settings.get("wfrp4e-gm-toolkit", "rangeNormalSight")) ; } brightSight = (dimSight / 2); break; case "nightVision": // Night Vision requires some minimal illumination to provide a benefit - if (game.scenes.viewed.data.darkness < 1 | dimLight > 0 | game.scenes.viewed.globalLight) { + if (game.scenes.viewed.data.darkness < 1 | dimLight > 0 | game.scenes.viewed.data.globalLight) { item = token.actor.items.find(i => i.data.name.toLowerCase() == game.i18n.localize("GMTOOLKIT.Talent.NightVision").toLowerCase() ); if(item == undefined) { (game.settings.get("wfrp4e-gm-toolkit", "overrideNightVision")) ? advNightVision = 1 : advNightVision = 0 ; @@ -300,40 +303,57 @@ async function setTokenVisionLight() { } brightSight = (20 * advNightVision); dimSight = Math.max(brightSight + dimLight, Number(game.settings.get("wfrp4e-gm-toolkit", "rangeNormalSight"))); + dimSight = advNightVision == 0 ? Number(game.settings.get("wfrp4e-gm-toolkit", "rangeNormalSight")) : Math.max(brightSight + dimLight, Number(game.settings.get("wfrp4e-gm-toolkit", "rangeNormalSight"))); + sightAngle = lightAngle; } + console.log(`Night Vision Advances ${advNightVision}`) break; case "normalVision": dimSight = Number(game.settings.get("wfrp4e-gm-toolkit", "rangeNormalSight")); brightSight = 0; break; - case "nochange": - default: + default: dimSight = token.data.dimSight; brightSight = token.data.brightSight; } // Update Token token.document.update({ - vision: true, - visionType: visionType, - lightSource: lightSource, - dimLight: dimLight, - brightLight: brightLight, - lightAngle: lightAngle, - lightColor: lightColor, - lightAlpha: lightColorIntensity, - lightAnimation: { - intensity: animationIntensity, - speed: animationSpeed, - type: animationType + "vision": true, + "light": { + "dim": dimLight, + "bright": brightLight, + "angle": lightAngle, + "color": lightColor, + "alpha": lightColorIntensity, + }, + "light.animation": { + "intensity": animationIntensity, + "speed": animationSpeed, + "type": animationType }, - dimSight: dimSight, - brightSight: brightSight, - lockRotation: lockRotation, - advNightVision: advNightVision + "dimSight": dimSight, + "brightSight": brightSight, + "sightAngle": sightAngle + // "visionType": visionType, + // "lightSource": lightSource, + // "advNightVision": advNightVision }); - } + token.refresh(true) + } + + canvasTokensUpdate({"vision": true}) + } } }).render(true); -}; \ No newline at end of file +}; + + +/* ========== + * MACRO: Set Token Vision and Light + * VERSION: 0.9.0 + * UPDATED: 2022-01-02 + * DESCRIPTION: Open a dialog for quickly changing vision and lighting parameters of the selected token(s). + * TIP: Default sight range and Darkvision / Night Vision overrides can be configured in Configure Token Vision Settings under Module Settings. + ========== */ \ No newline at end of file From 3188d0db19d1a146ceee51ff91e7e3c9a03fc14a Mon Sep 17 00:00:00 2001 From: Jagusti Date: Sun, 2 Jan 2022 23:43:59 +0000 Subject: [PATCH 5/5] Update manifest, changelog and readme for FVTT v9 compatibility --- CHANGELOG.md | 2 +- README.md | 13 ++++++------- module.json | 4 ++-- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54640dd..d0d8f97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format ## Unreleased See [Issue Backlog](../../issues) and [Roadmap](../../milestones). -- *Changed* Foundry compatibility to ###. WFRP4e system compatibility is ###. +- *Changed* Foundry compatibility to v9. WFRP4e system compatibility is v5. - *Fixed* non-rendered html in Marginal Success roll description. - *Fixed* issue where module settings could not be accessed [[#70](https://github.com/Jagusti/fvtt-wfrp4e-gmtoolkit/issues/70)]. - *Added* RollTable compatibility to the Send Dark Whispers macro. This fixes issues with "undefined" Dark Whisper text in the Send Dark Whispers dialog [[#69](https://github.com/Jagusti/fvtt-wfrp4e-gmtoolkit/issues/69)]. diff --git a/README.md b/README.md index 4074458..8c3a43f 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,11 @@ Utility module with tweaks, enhancements and macros to help GMs manage games of ## Compatibility -Version 0.8.0 requires -- Foundry VTT: 0.8.8 - 0.8.9 -- WFRP4e: 4.0.6 - 4.3.1 +Version 0.9.0 requires +- Foundry VTT: v9 +- WFRP4e: v5 -This version (including new enhancements) is partially compatible with WFRP4e 5.x and Foundry 9.x. Known conflicts are described in issue #69, and will be addressed in the next release. Future versions of the Toolkit may not be compatible with earlier versions of Foundry or the WFRP4e system. - -If you are using earlier versions of WFRP4e (3.4.1 - 3.6.2) and Foundry (0.7.10), then you should use [v0.6.3](https://github.com/Jagusti/fvtt-wfrp4e-gmtoolkit/releases/tag/v0.6.3) of the GM Toolkit. +If you are using earlier versions of WFRP4e (4.0.6 - 4.3.1) and Foundry (0.8.8 - 0.8.9), then you should use [v0.8.0](https://github.com/Jagusti/fvtt-wfrp4e-gmtoolkit/releases/tag/v0.8.0) of the GM Toolkit. ## Installation Instructions For full details, see the [Getting Started guide](../../wiki/getting-started) on the [wiki](../../wiki). @@ -23,7 +21,8 @@ For full details, see the [Getting Started guide](../../wiki/getting-started) on 3. [Import macros](https://github.com/Jagusti/fvtt-wfrp4e-gmtoolkit/wiki/getting-started#macros) to your Macro Directory via `Compendiums` > `Macro` > `GM Toolkit`. - Any imported macros will appear in the Macro Directory, next to the Macro Hotbar. - Open the Macro Directory and drag required macros onto a free space on the Marco Hotbar. -4. Revised macros will *not automatically* replace your existing GM Toolkit macros. You will need to manually re-import them from the Macro Compendium into your Macro Directory to take advantage of the improvements. + - Revised macros will *not automatically* replace your existing GM Toolkit macros. You will need to manually re-import them from the Macro Compendium into your Macro Directory to take advantage of the improvements. +4. [Import RolLTables](https://github.com/Jagusti/fvtt-wfrp4e-gmtoolkit/wiki/getting-started#macros) to your RollTable suite via `Compendiums` > `RollTable` > `GM Toolkit`. ## References diff --git a/module.json b/module.json index 4dbbe49..1445705 100644 --- a/module.json +++ b/module.json @@ -2,8 +2,8 @@ "name": "wfrp4e-gm-toolkit", "title": "GM Toolkit (WFRP4e)", "description": "Utilities for WFRP4e GMs", - "version": "0.9.0-dev", - "minCoreVersion": "0.8.8", + "version": "0.9.0", + "minCoreVersion": "9", "compatibleCoreVersion": "9", "author": "Jagusti", "authors" : [