diff --git a/CHANGELOG.md b/CHANGELOG.md index bc44b46..fa4338e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,12 +29,20 @@ See [Issue Backlog](../../issues) and [Roadmap](../../milestones). - *Changed* Advantage UI notifications to only present to GM users. This is introduced to prevent token or actor names being revealed to players. This will not prevent names being revealed in chat log messages, such as for opposed test results. [[#87](https://github.com/Jagusti/fvtt-wfrp4e-gmtoolkit/issues/87)] - *Fixed* compatibility with Advanced Macros [[#85](https://github.com/Jagusti/fvtt-wfrp4e-gmtoolkit/issues/85)] - Add XP, Reset Fortune and Session Turnover macros no longer throw an error if the Advanced Macros module is also active. - - These macros will need to be manually re-imported from the compendium for changes to apply. + - These macros will need to be re-imported from the compendium for changes to apply. - *Changed* references to Global Illumination to Unrestricted Token Vision, to align with changes in Foundry scene configuration options. - - This affects the Toggle Scene Visibility and Light macro, which should be manually re-imported from the compendium for changes to apply. + - This affects the Toggle Scene Visibility and Light macro, which should be re-imported from the compendium for changes to apply. - *Changed* compendium pack definitions to use field 'type' instead of deprecated 'entity'. - *Added* Japanese language support, including including localisation improvements to Pull Everyone to Scene and GM Toolbox macros. Thanks @Yasnen for contributing these! - - The Pull Everyone to Scene and GM Toolbox macros will need to be manually re-imported from the compendium for localization and related improvements to apply. + - The Pull Everyone to Scene and GM Toolbox macros will need to be re-imported from the compendium for localization and related improvements to apply. +- *Added* new Maintenance option in module settings to re-import GM Toolkit macros and tables. It is **recommended** to use this option when upgrading to a new version to reset content. + - Content is imported from compendium packs into the GM Toolkit Macro Directory folder or RollTable folder, depending on document type being imported. + - Existing content in the folder is removed, so any customizations should be moved to a separate location first. + - The Maintenance dialog shows version numbers for world and compendium content, so you can more easily identify if an item has been updated for a release. + - The Maintenance dialog also shows the database ID of the item. This can be used to identify existing duplicate world content (such as customised table or macro in a different folder location) that prevents re-importing. +- *Added* notes footer to Advantage suite and Toggle Scene Visibility and Light macros + - This includes macro decsription, version number and date plus usage tips + - This change standardises the presentation of in-context macro documentation across the suite. ## Version 0.9.2 - *Fixed* duplicate results numbering in Dark Whispers table and localization omission [#79] (Thanks @Txus5012). diff --git a/apps/gm-toolkit-maintenance.js b/apps/gm-toolkit-maintenance.js new file mode 100644 index 0000000..ac668fc --- /dev/null +++ b/apps/gm-toolkit-maintenance.js @@ -0,0 +1,75 @@ +import GMToolkit from "../modules/gm-toolkit.mjs"; +import { refreshToolkitContent, strip } from "../modules/utility.mjs"; + +export default class GMToolkitMaintenanceWrapper extends FormApplication { + async render() { + let listToolkitContent = new Array() + listToolkitContent.macros = await buildLocalizedContent(game.macros) + listToolkitContent.tables = await buildLocalizedContent(game.tables) + + let html = await renderTemplate("modules/wfrp4e-gm-toolkit/templates/gm-toolkit-maintenance.html", listToolkitContent) + + new GMToolkitMaintenance("wfrp4e-gm-toolkit", `${GMToolkit.MODULE_NAME_FULL} Maintenance`,html).render(true); + } +} // end class GMToolkitMaintenanceWrapper + +class GMToolkitMaintenance extends Dialog { + + constructor(module = GMToolkit.MODULE_ID, title, html) { + super({ + title, + content: html, + module: game.modules.get(module), + buttons: { + macros: { + label: "Macros", + callback: async () => { + await refreshToolkitContent("Macro") + } + }, + tables: { + label: "Tables", + callback: async () => { + await refreshToolkitContent("RollTable") + } + } + } + }) + } + +} // end class GMToolkitMainenance + +async function buildLocalizedContent(documentType) { + GMToolkit.log(false, "Starting buildLocalizedContent") + let toolkitContent = (documentType.filter(m=>m.folder?.name==game.gmtoolkit.module.MODULE_NAME)).sort((a, b) => a.name.localeCompare(b.name)) + let contentArray = [] + let pack = [] + + // Set translationKey prefix + let translationKeyPrefix = "" + if (documentType === game.macros) { + translationKeyPrefix = "GMTOOLKIT.Macro" + pack = game.packs.get(`${game.gmtoolkit.module.MODULE_ID}.gm-toolkit-macros`); + } + if (documentType === game.tables) { + translationKeyPrefix = "GMTOOLKIT.Table" + pack = game.packs.get(`${game.gmtoolkit.module.MODULE_ID}.gm-toolkit-tables`); + } + + // Get Compendium documents + let documents = await pack.getDocuments(); + + // build localized array + for (var content of toolkitContent) { + content.translationKey = strip(content.name, translationKeyPrefix, ".") + content.compendiumVersion = documents.filter(d => d.id == content.id).map(i => i.data.flags["wfrp4e-gm-toolkit"].version) + + contentArray.push(content) + } + + GMToolkit.log(false, "contentArray : ", contentArray) + GMToolkit.log(false, "Ending buildLocalizedContent") + + return(contentArray) + +} // end function buildLocalizedContent() diff --git a/css/gmtoolkit.css b/css/gmtoolkit.css index 7bb9ab3..ebb734d 100644 --- a/css/gmtoolkit.css +++ b/css/gmtoolkit.css @@ -132,3 +132,23 @@ box-shadow: 0px 0px 10px inset black; background: #352516; } + +/* 3. DIALOG */ +/***************************/ + +/* 3a. List Data */ + +.gmtoolkit.list-metadata.id { + font-family: var(--actor-input-font-family); + color: #cbee8b75; +} + +.gmtoolkit.list-metadata.version { + color: #006188; + font-weight: bold; +} + +.gmtoolkit.list-thumbnail { + vertical-align: bottom; + max-height: 1.5em; +} \ No newline at end of file diff --git a/lang/en.json b/lang/en.json index 23436f3..a8b2091 100644 --- a/lang/en.json +++ b/lang/en.json @@ -212,6 +212,16 @@ "GMTOOLKIT.Macro.SetTokenVisionandLight": "Set Token Vision and Light", "GMTOOLKIT.Macro.PullEveryonetoScene": "Pull Everyone to Scene", "GMTOOLKIT.Macro.Simplyd100": "Simply d100", + "GMTOOLKIT.Macro.GMToolbox": "GM Toolbox", + + "GMTOOLKIT.Table.DarkWhispers": "Dark Whispers", + + "GMTOOLKIT.Dialog.Maintenance.Hint": "Remove all macros or tables in the 'GM Toolkit' Macro directory or RollTable folder, if that folder exists, and import the latest compendium versions.

Any customisations will be lost. Move these out of the GM Toolkit directory first if you want to preserve any changes you have made.", + "GMTOOLKIT.Dialog.Maintenance.Macros": "Macros", + "GMTOOLKIT.Dialog.Maintenance.Tables": "Tables", + "GMTOOLKIT.Settings.Maintenance.menu.name" : "Module Maintenance", + "GMTOOLKIT.Settings.Maintenance.menu.hint" : "Replace imported GM Toolkit macros and/or tables with latest compendium versions.", + "GMTOOLKIT.Settings.Maintenance.menu.label" : "Update GM Toolkit Content", "ROLL.AstoundingSuccess" : "Astounding Success. 'Yes, perfectly!'", "ROLL.ImpressiveSuccess" : "Impressive Success. 'Yes, and ...'", diff --git a/lang/fr.json b/lang/fr.json index 8010ffd..e65358d 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -212,6 +212,16 @@ "GMTOOLKIT.Macro.SetTokenVisionandLight": "Set Token Vision and Light", "GMTOOLKIT.Macro.PullEveryonetoScene": "Pull Everyone to Scene", "GMTOOLKIT.Macro.Simplyd100": "Simply d100", + "GMTOOLKIT.Macro.GMToolbox": "GM Toolbox", + + "GMTOOLKIT.Table.DarkWhispers": "Dark Whispers", + + "GMTOOLKIT.Dialog.Maintenance.Hint": "Remove all macros or tables in the 'GM Toolkit' Macro directory or RollTable folder, if that folder exists, and import the latest compendium versions.

Any customisations will be lost. Move these out of the GM Toolkit directory first if you want to preserve any changes you have made.", + "GMTOOLKIT.Dialog.Maintenance.Macros": "Macros", + "GMTOOLKIT.Dialog.Maintenance.Tables": "Tables", + "GMTOOLKIT.Settings.Maintenance.menu.name" : "Module Maintenance", + "GMTOOLKIT.Settings.Maintenance.menu.hint" : "Replace imported GM Toolkit macros and/or tables with latest compendium versions.", + "GMTOOLKIT.Settings.Maintenance.menu.label" : "Update GM Toolkit Content", "ROLL.AstoundingSuccess" : "Réussite Stupéfiante. \"Oui, absolument !\"", "ROLL.ImpressiveSuccess" : "Réussite Impressionnant. \"Oui, et...\"", diff --git a/lang/ja.json b/lang/ja.json index 6d15242..88c951b 100644 --- a/lang/ja.json +++ b/lang/ja.json @@ -212,6 +212,16 @@ "GMTOOLKIT.Macro.SetTokenVisionandLight": "トークンの視界と光源を設定", "GMTOOLKIT.Macro.PullEveryonetoScene": "全員をシーンに引き寄せる", "GMTOOLKIT.Macro.Simplyd100": "100面ダイス", + "GMTOOLKIT.Macro.GMToolbox": "GM Toolbox", + + "GMTOOLKIT.Table.DarkWhispers": "Dark Whispers", + + "GMTOOLKIT.Dialog.Maintenance.Hint": "Remove all macros or tables in the 'GM Toolkit' Macro directory or RollTable folder, if that folder exists, and import the latest compendium versions.

Any customisations will be lost. Move these out of the GM Toolkit directory first if you want to preserve any changes you have made.", + "GMTOOLKIT.Dialog.Maintenance.Macros": "Macros", + "GMTOOLKIT.Dialog.Maintenance.Tables": "Tables", + "GMTOOLKIT.Settings.Maintenance.menu.name" : "Module Maintenance", + "GMTOOLKIT.Settings.Maintenance.menu.hint" : "Replace imported GM Toolkit macros and/or tables with latest compendium versions.", + "GMTOOLKIT.Settings.Maintenance.menu.label" : "Update GM Toolkit Content", "ROLL.AstoundingSuccess" : "超大成功。「イエス、文句なしにな!」", "ROLL.ImpressiveSuccess" : "大成功。「イエス。しかも……」", diff --git a/modules/gm-toolkit-settings.mjs b/modules/gm-toolkit-settings.mjs index 0c91a9f..957058d 100644 --- a/modules/gm-toolkit-settings.mjs +++ b/modules/gm-toolkit-settings.mjs @@ -3,6 +3,7 @@ import GMToolkitAdvantageSettings from "../apps/gm-toolkit-advantage-settings.js import GMToolkitDarkWhispersSettings from "../apps/gm-toolkit-darkwhispers-settings.js"; import GMToolkitSessionManagementSettings from "../apps/gm-toolkit-session-management-settings.js"; import GMToolkitVisionSettings from "../apps/gm-toolkit-vision-settings.js"; +import GMToolkitMaintenanceWrapper from "../apps/gm-toolkit-maintenance.js"; export default class GMToolkitSettings { @@ -295,6 +296,16 @@ export default class GMToolkitSettings { type: Boolean, feature: "grouptests" }); + + // Menu for Module Content Management + game.settings.registerMenu(GMToolkit.MODULE_ID, "menuMaintenance", { + name: "GMTOOLKIT.Settings.Maintenance.menu.name", + label: "GMTOOLKIT.Settings.Maintenance.menu.label", + hint: "GMTOOLKIT.Settings.Maintenance.menu.hint", + icon: "fas fa-cog", + type: GMToolkitMaintenanceWrapper, + restricted: true + }); } diff --git a/modules/gm-toolkit.mjs b/modules/gm-toolkit.mjs index a34499e..270fd77 100644 --- a/modules/gm-toolkit.mjs +++ b/modules/gm-toolkit.mjs @@ -1,8 +1,9 @@ export default class GMToolkit { static MODULE_ID = "wfrp4e-gm-toolkit"; - static MODULE_NAME = "GM Toolkit (WFRP4e)"; static MODULE_ABBREV = "GMTOOLKIT"; + static MODULE_NAME = "GM Toolkit"; + static MODULE_NAME_FULL = "GM Toolkit (WFRP4e)"; /** * A small helper function which leverages developer mode flags to gate debug logs. diff --git a/modules/utility.mjs b/modules/utility.mjs index 33dbb45..9186751 100644 --- a/modules/utility.mjs +++ b/modules/utility.mjs @@ -173,4 +173,52 @@ export function inActiveCombat(character, notification = true) { } } return inActiveCombat -} \ No newline at end of file +} + +/** + * Delete and re-import GM Toolkit macros + * @param {String} documentType : Actor object of the character + **/ +export async function refreshToolkitContent(documentType) { + + let toolkitContent = [] + + switch (documentType) { + case "Macro" : + // delete macros + await Macro.deleteDocuments(game.macros.filter(m=>m.folder?.name==GMToolkit.MODULE_NAME).map(m=>m.id)) + // delete Macro folder + await Folder.deleteDocuments(game.folders.filter(f => f.name == GMToolkit.MODULE_NAME && f.type == "Macro").map(f => f.id)) + // import macros from compendium + toolkitContent = await game.packs.get(`${GMToolkit.MODULE_ID}.gm-toolkit-macros`).importAll({ + folderName: GMToolkit.MODULE_NAME, + options: {keepId : true} + }); + break; + case "RollTable" : + // delete tables within GM Toolkit directory + await RollTable.deleteDocuments(game.tables.filter(t=>t.folder?.name==GMToolkit.MODULE_NAME).map(t=>t.id)) + // delete RollTable folder + await Folder.deleteDocuments(game.folders.filter(f => f.name == GMToolkit.MODULE_NAME && f.type == "RollTable").map(f => f.id)) + // import tables from compendium + toolkitContent = await game.packs.get(`${GMToolkit.MODULE_ID}.gm-toolkit-tables`).importAll({ + folderName: GMToolkit.MODULE_NAME, + options: {keepId : true} + }); + break; + } + + GMToolkit.log(false, toolkitContent) + +} + +/** + * Remove all leading, trailing and internal whitespace from a string. + * Typical use is in constructing localization strings from, eg, macro names + * @param {String} originalText : string from which to remove all whitespace + * @param {String} prefix : optional prefix for building string + * @param {String} joiner : optional joining character to connect prefix and stripped string + **/ + export function strip(originalText, prefix = "", joiner = "") { + return (prefix + joiner + originalText.replace(/\s+/g, '')) +} diff --git a/packs/gm-toolkit-macros.db b/packs/gm-toolkit-macros.db index 28e9b62..202bf92 100644 --- a/packs/gm-toolkit-macros.db +++ b/packs/gm-toolkit-macros.db @@ -1,13 +1,13 @@ -{"_id":"2sefSFqqAbySw2nz","name":"Reduce Advantage","type":"script","author":"k5y1jEYMBqd9uLUx","img":"modules/wfrp4e-gm-toolkit/assets/icons/advantage-reduce.svg","scope":"global","command":"/* Reduces Advantage for the selected token by 1 (to minimum 0).\n*/\n\ngame.gmtoolkit.advantage.updateAdvantage(token,\"reduce\");","folder":null,"sort":0,"permission":{"default":0,"k5y1jEYMBqd9uLUx":3},"flags":{}} -{"_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","type":"script","author":"h21fB4dFvdDBRcFD","img":"modules/wfrp4e-gm-toolkit/assets/icons/toggle-scene-light.svg","scope":"global","command":"/* Toggles the Token Vision and Unrestricted Vision Range settings. \n * If Token Vision is set, Unrestricted Vision Range 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.UnrestrictedNotToken\", {sceneName: thisScene.name}, {});\n} else {\n thisScene.update({tokenVision: true, globalLight: false});\n uiNotice = game.i18n.format(\"GMTOOLKIT.Scene.TokenNotUnrestricted\", {sceneName: thisScene.name}, {});\n}\n\nui.notifications.notify(uiNotice);","folder":null,"sort":0,"permission":{"default":0,"h21fB4dFvdDBRcFD":3},"flags":{"furnace":{"runAsGM":false}}} -{"_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":"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) 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: async (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 await 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.1\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 let 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.9.3\n * UPDATED: 2022-01-22\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":[]} -{"_id":"iopoLXTz9kfDTfiX","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 label = (game.i18n.localize(\"GMTOOLKIT.Macro.\" + name.replace(/\\s+/g, ''))) == ((\"GMTOOLKIT.Macro.\" + name.replace(/\\s+/g, ''))) ? name : (game.i18n.localize(\"GMTOOLKIT.Macro.\" + name.replace(/\\s+/g, '')))\n buttons[name] = {\n label : label,\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.9.3\n* UPDATED: 2022-05-13\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":"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\tlet party = 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\tlet currentFortune = character.data.data.status.fortune.value\n\t\tlet maxFortune = 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.9.3\n * UPDATED: 2022-01-22\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":"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(game.i18n.localize(\"GMTOOLKIT.Dialog.DarkWhispers.Title\"))) ? 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.2\n* UPDATED: 2022-01-04\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 let awardees = []\n if (game.user.targets.size < 1) {\n // (1) all player characters if no tokens are targeted\n awardees = 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 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 let recipient = pc?.actor?.name || pc.name \n let XPTotal = pc?.details?.experience?.total || pc.actor.data.data.details.experience.total; \n let newXPTotal = Math.max(XPTotal + XP,0);\n let XPCurrent = pc?.details?.experience?.current || pc.actor.data.data.details.experience.current; \n let 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.9.3\n * UPDATED: 2022-01-24\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","type":"script","author":"k5y1jEYMBqd9uLUx","img":"modules/wfrp4e-gm-toolkit/assets/icons/pull-to-scene.svg","scope":"global","command":"pullEveryoneToScene();\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: (game.i18n.localize('GMTOOLKIT.Dialog.ScenePullActivate.ActivateScene')),\n callback: async () => pullToScene(true)\n },\n pull: {\n label: (game.i18n.localize('GMTOOLKIT.Dialog.ScenePullActivate.PullOnly')),\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};\n\n/* ==========\n* MACRO: Pull Everyone to Scene\n* VERSION: 0.9.3\n* UPDATED: 2022-05-11\n* DESCRIPTION: Yanks every player into the scene that the GM is on. \n* TIP: Optionally activate (or prompt to activate) the scene through Configure Session Options in module settings. \n========== */","folder":null,"sort":0,"permission":{"default":0,"k5y1jEYMBqd9uLUx":3},"flags":{}} -{"_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"}}} +{"_id":"2sefSFqqAbySw2nz","name":"Reduce Advantage","type":"script","author":"k5y1jEYMBqd9uLUx","img":"modules/wfrp4e-gm-toolkit/assets/icons/advantage-reduce.svg","scope":"global","command":"game.gmtoolkit.advantage.updateAdvantage(token,`reduce`); \n\n/* ==========\n* MACRO: Reduce Advantage\n* VERSION: 0.8.0\n* UPDATED: 2021-12-26\n* DESCRIPTION: Reduces Advantage for the selected token by 1 (to minimum 0).\n* TIP: Token must be added to an encounter in the Combat Tracker. \n========== */","folder":null,"sort":0,"permission":{"default":0,"k5y1jEYMBqd9uLUx":3},"flags":{"wfrp4e-gm-toolkit":{"version":"0.8.0"}}} +{"_id":"6EKiEQZTbmQN97Vr","name":"Clear Advantage","type":"script","author":"k5y1jEYMBqd9uLUx","img":"modules/wfrp4e-gm-toolkit/assets/icons/advantage-clear.svg","scope":"global","command":"game.gmtoolkit.advantage.updateAdvantage(token,`clear`);\n\n/* ==========\n* MACRO: Clear Advantage\n* VERSION: 0.8.0\n* UPDATED: 2021-12-26\n* DESCRIPTION: Resets Advantage for the selected token to 0.\n* TIP: Token does not have to be added to an encounter in the Combat Tracker. \n========== */","folder":null,"sort":0,"permission":{"default":0,"k5y1jEYMBqd9uLUx":3},"flags":{"wfrp4e-gm-toolkit":{"version":"0.8.0"}}} +{"_id":"AjUYYy7qAN55BERN","name":"Add Advantage","type":"script","author":"k5y1jEYMBqd9uLUx","img":"modules/wfrp4e-gm-toolkit/assets/icons/advantage-add.svg","scope":"global","command":"game.gmtoolkit.advantage.updateAdvantage(token,`increase`);\n\n/* ==========\n* MACRO: Add Advantage\n* VERSION: 0.8.0\n* UPDATED: 2021-12-26\n* DESCRIPTION: Increases Advantage for the selected token by 1.\n* TIP: Token must be added to an encounter in the Combat Tracker. \n* TIP: Caps at character's maximum advantage. \n========== */","folder":null,"sort":0,"permission":{"default":0,"k5y1jEYMBqd9uLUx":3},"flags":{"wfrp4e-gm-toolkit":{"version":"0.8.0"}}} +{"_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) 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: async (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 await 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.1\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"},"wfrp4e-gm-toolkit":{"version":"0.9.1"}}} +{"name":"Toggle Scene Visibility and Light","type":"script","author":"WpuDIfNQnefaTyuV","img":"modules/wfrp4e-gm-toolkit/assets/icons/toggle-scene-light.svg","scope":"global","command":"let 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.UnrestrictedNotToken\", {sceneName: thisScene.name}, {});\n} else {\n thisScene.update({tokenVision: true, globalLight: false});\n uiNotice = game.i18n.format(\"GMTOOLKIT.Scene.TokenNotUnrestricted\", {sceneName: thisScene.name}, {});\n}\n\nui.notifications.notify(uiNotice);\n\n/* ==========\n* MACRO: Toggle Scene Vision and Light\n* VERSION: 0.9.3\n* UPDATED: 2022-01-29\n* DESCRIPTION: Toggles the Token Vision and Unrestricted Vision Range settings. \n* TIP: If Token Vision is set, Unrestricted Vision Range is unset (and vice-versa).\n* TIP: Applies to the scene being viewed, which is not necessarily the active scene.\n========== */","folder":null,"sort":0,"permission":{"default":0,"WpuDIfNQnefaTyuV":3},"flags":{"furnace":{"runAsGM":false},"wfrp4e-gm-toolkit":{"version":"0.9.3"},"core":{"sourceId":"Compendium.wfrp4e-gm-toolkit.gm-toolkit-macros.6UwKY8nGZyXzAXCa"}},"_id":"OiQ0cS3QsmQadxqR"} +{"_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 let 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.9.3\n * UPDATED: 2022-01-22\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":{"wfrp4e-gm-toolkit":{"version":"0.9.3"}}} +{"_id":"ihMGjHFP3SdvYH2k","name":"Simply d100","type":"chat","author":"k5y1jEYMBqd9uLUx","img":"modules/wfrp4e-gm-toolkit/assets/icons/d100.svg","scope":"global","command":"/r 1d100","folder":null,"sort":0,"permission":{"default":0,"k5y1jEYMBqd9uLUx":3},"flags":{"wfrp4e-gm-toolkit":{"version":"0.9.3"}}} +{"_id":"iopoLXTz9kfDTfiX","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 label = (game.i18n.localize(game.gmtoolkit.utility.strip(name, \"GMTOOLKIT.Macro\",\".\"))) == ((game.gmtoolkit.utility.strip(name, \"GMTOOLKIT.Macro\",\".\"))) ? name : (game.i18n.localize(game.gmtoolkit.utility.strip(name, \"GMTOOLKIT.Macro\",\".\")));\n buttons[name] = {\n label : label,\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.9.3\n* UPDATED: 2022-05-20\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"},"wfrp4e-gm-toolkit":{"version":"0.9.3"}}} +{"_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\tlet party = 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\tlet currentFortune = character.data.data.status.fortune.value\n\t\tlet maxFortune = 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.9.3\n * UPDATED: 2022-01-22\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":{"wfrp4e-gm-toolkit":{"version":"0.9.3"}}} +{"_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(game.i18n.localize(\"GMTOOLKIT.Dialog.DarkWhispers.Title\"))) ? 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.2\n* UPDATED: 2022-01-04\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"},"wfrp4e-gm-toolkit":{"version":"0.9.2"}}} +{"_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 let awardees = []\n if (game.user.targets.size < 1) {\n // (1) all player characters if no tokens are targeted\n awardees = 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 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 let recipient = pc?.actor?.name || pc.name \n let XPTotal = pc?.details?.experience?.total || pc.actor.data.data.details.experience.total; \n let newXPTotal = Math.max(XPTotal + XP,0);\n let XPCurrent = pc?.details?.experience?.current || pc.actor.data.data.details.experience.current; \n let 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.9.3\n * UPDATED: 2022-01-24\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":{"wfrp4e-gm-toolkit":{"version":"0.9.3"}}} +{"_id":"tiKEfs1nB7zAMgYg","name":"Pull Everyone to Scene","type":"script","author":"k5y1jEYMBqd9uLUx","img":"modules/wfrp4e-gm-toolkit/assets/icons/pull-to-scene.svg","scope":"global","command":"pullEveryoneToScene();\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: (game.i18n.localize('GMTOOLKIT.Dialog.ScenePullActivate.ActivateScene')),\n callback: async () => pullToScene(true)\n },\n pull: {\n label: (game.i18n.localize('GMTOOLKIT.Dialog.ScenePullActivate.PullOnly')),\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};\n\n/* ==========\n* MACRO: Pull Everyone to Scene\n* VERSION: 0.9.3\n* UPDATED: 2022-05-11\n* DESCRIPTION: Yanks every player into the scene that the GM is on. \n* TIP: Optionally activate (or prompt to activate) the scene through Configure Session Options in module settings. \n========== */","folder":null,"sort":0,"permission":{"default":0,"k5y1jEYMBqd9uLUx":3},"flags":{"wfrp4e-gm-toolkit":{"version":"0.9.3"}}} +{"_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"},"wfrp4e-gm-toolkit":{"version":"0.8.0"}}} diff --git a/packs/gm-toolkit-tables.db b/packs/gm-toolkit-tables.db index dadc7d6..9264cf9 100644 --- a/packs/gm-toolkit-tables.db +++ b/packs/gm-toolkit-tables.db @@ -1 +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":[7,7],"drawn":false,"flags":{},"img":"icons/svg/d20-black.svg","collection":null,"resultId":null,"weight":null,"rangeL":7,"rangeH":7},{"_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":[8,8],"drawn":false,"flags":{},"img":"icons/svg/d20-black.svg","collection":null,"resultId":null,"weight":null,"rangeL":8,"rangeH":8},{"_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":[9,9],"drawn":false,"flags":{},"img":"icons/svg/d20-black.svg","collection":null,"resultId":null,"weight":null,"rangeL":9,"rangeH":9},{"_id":"gmfs74ty8wbm8eoy","type":0,"text":"Secrets are burdensome. Be generous and give them away.","range":[10,10],"drawn":false,"flags":{},"img":"icons/svg/d20-black.svg","collection":null,"resultId":null,"weight":null,"rangeL":10,"rangeH":10}],"formula":"1d10","replacement":true,"displayRoll":true,"folder":null,"sort":0,"permission":{"default":0,"Lngg3hbkX3DayZX9":3},"flags":{"wfrp4e":{"key":"darkwhispers"},"core":{"sourceId":"RollTable.9rXQv4uJcQoLBitt"}}} +{"_id":"9rXQv4uJcQoLBitt","name":"Dark Whispers","img":"modules/wfrp4e-gm-toolkit/assets/icons/dark-whispers.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":"modules/wfrp4e-gm-toolkit/assets/icons/dark-whispers.svg","rangeL":1,"rangeH":1},{"_id":"h3hvuahahukmiklq","type":0,"text":"'Accidentally' strike an ally.","range":[2,2],"drawn":false,"flags":{},"img":"modules/wfrp4e-gm-toolkit/assets/icons/dark-whispers.svg","rangeL":2,"rangeH":2},{"_id":"9ltv15jz9n4pkc7m","type":0,"text":"Fall asleep on watch.","range":[3,3],"drawn":false,"flags":{},"img":"modules/wfrp4e-gm-toolkit/assets/icons/dark-whispers.svg","rangeL":3,"rangeH":3},{"_id":"j1z9hqs65ife4pqx","type":0,"text":"Kneel before the Magister. Show your allegiance.","range":[4,4],"drawn":false,"flags":{},"img":"modules/wfrp4e-gm-toolkit/assets/icons/dark-whispers.svg","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":"modules/wfrp4e-gm-toolkit/assets/icons/dark-whispers.svg","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":"modules/wfrp4e-gm-toolkit/assets/icons/dark-whispers.svg","rangeL":6,"rangeH":6},{"_id":"2rq045im6k1pft53","type":0,"text":"Exert your authority. Make an example of the weakest or most vulnerable.","range":[7,7],"drawn":false,"flags":{},"img":"modules/wfrp4e-gm-toolkit/assets/icons/dark-whispers.svg","rangeL":7,"rangeH":7},{"_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":[8,8],"drawn":false,"flags":{},"img":"modules/wfrp4e-gm-toolkit/assets/icons/dark-whispers.svg","rangeL":8,"rangeH":8},{"_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":[9,9],"drawn":false,"flags":{},"img":"modules/wfrp4e-gm-toolkit/assets/icons/dark-whispers.svg","rangeL":9,"rangeH":9},{"_id":"gmfs74ty8wbm8eoy","type":0,"text":"Secrets are burdensome. Be generous and give them away.","range":[10,10],"drawn":false,"flags":{},"img":"modules/wfrp4e-gm-toolkit/assets/icons/dark-whispers.svg","rangeL":10,"rangeH":10}],"formula":"1d10","replacement":true,"displayRoll":true,"folder":null,"sort":0,"permission":{"default":0,"Lngg3hbkX3DayZX9":3},"flags":{"wfrp4e":{"key":"darkwhispers"},"wfrp4e-gm-toolkit":{"version":"0.9.2"},"core":{"sourceId":"RollTable.9rXQv4uJcQoLBitt"}}} diff --git a/scripts/macros/add-advantage.js b/scripts/macros/add-advantage.js index 429c876..32774f6 100644 --- a/scripts/macros/add-advantage.js +++ b/scripts/macros/add-advantage.js @@ -1,5 +1,10 @@ -/* Increases Advantage for the selected token by 1. - * Caps at character's maximum advantage. -*/ - game.gmtoolkit.advantage.updateAdvantage(token,`increase`); + +/* ========== +* MACRO: Add Advantage +* VERSION: 0.8.0 +* UPDATED: 2021-12-26 +* DESCRIPTION: Increases Advantage for the selected token by 1. +* TIP: Token must be added to an encounter in the Combat Tracker. +* TIP: Caps at character's maximum advantage. +========== */ diff --git a/scripts/macros/clear-advantage.js b/scripts/macros/clear-advantage.js index 71f6c95..7e472aa 100644 --- a/scripts/macros/clear-advantage.js +++ b/scripts/macros/clear-advantage.js @@ -1,3 +1,10 @@ -/* Resets Advantage for the selected token to 0 */ - game.gmtoolkit.advantage.updateAdvantage(token,`clear`); + +/* ========== +* MACRO: Clear Advantage +* VERSION: 0.8.0 +* UPDATED: 2021-12-26 +* DESCRIPTION: Resets Advantage for the selected token to 0. +* TIP: Token does not have to be added to an encounter in the Combat Tracker. +========== */ + diff --git a/scripts/macros/macro-toolbox.js b/scripts/macros/macro-toolbox.js index 4204084..2b73360 100644 --- a/scripts/macros/macro-toolbox.js +++ b/scripts/macros/macro-toolbox.js @@ -20,7 +20,7 @@ let content = `
`, @@ -37,7 +37,7 @@ /* ========== * MACRO: GM Toolbox * VERSION: 0.9.3 -* UPDATED: 2022-05-13 +* UPDATED: 2022-05-20 * DESCRIPTION: Adds a customisable floating dialog for quick access to frequently used Toolkit macros, freeing up hotbar spots * TIP: Add / remove macros from the 'macros' list to tailor it for your game. Names must exactly match those in the Macro Directory. * TIP: The macro dialog can be kept open for quick access and minimised to reduce space diff --git a/scripts/macros/reduce-advantage.js b/scripts/macros/reduce-advantage.js index 637120f..186b9fa 100644 --- a/scripts/macros/reduce-advantage.js +++ b/scripts/macros/reduce-advantage.js @@ -1,4 +1,9 @@ -/* Reduces Advantage for the selected token by 1 (to minimum 0). -*/ +game.gmtoolkit.advantage.updateAdvantage(token,`reduce`); -game.gmtoolkit.advantage.updateAdvantage(token,`reduce`); \ No newline at end of file +/* ========== +* MACRO: Reduce Advantage +* VERSION: 0.8.0 +* UPDATED: 2021-12-26 +* DESCRIPTION: Reduces Advantage for the selected token by 1 (to minimum 0). +* TIP: Token must be added to an encounter in the Combat Tracker. +========== */ diff --git a/scripts/macros/toggle-scene-vision-light.js b/scripts/macros/toggle-scene-vision-light.js index 1194e25..3492e91 100644 --- a/scripts/macros/toggle-scene-vision-light.js +++ b/scripts/macros/toggle-scene-vision-light.js @@ -1,9 +1,3 @@ -/* Toggles the Token Vision and Unrestricted Vision Range settings. - * If Token Vision is set, Unrestricted Vision Range is unset (and vice-versa). - * Applies to the scene being viewed, which is not necessarily the active scene. - */ - - let thisScene = game.scenes.viewed; let uiNotice = game.i18n.format("GMTOOLKIT.Message.UnexpectedNoChange", {}); @@ -16,3 +10,12 @@ if (thisScene.data.tokenVision) { } ui.notifications.notify(uiNotice); + +/* ========== +* MACRO: Toggle Scene Vision and Light +* VERSION: 0.9.3 +* UPDATED: 2022-01-29 +* DESCRIPTION: Toggles the Token Vision and Unrestricted Vision Range settings. +* TIP: If Token Vision is set, Unrestricted Vision Range is unset (and vice-versa). +* TIP: Applies to the scene being viewed, which is not necessarily the active scene. +========== */ \ No newline at end of file diff --git a/templates/gm-toolkit-maintenance.html b/templates/gm-toolkit-maintenance.html new file mode 100644 index 0000000..881787c --- /dev/null +++ b/templates/gm-toolkit-maintenance.html @@ -0,0 +1,19 @@ +{{log this}} + +
+ +

{{{ localize "GMTOOLKIT.Dialog.Maintenance.Hint"}}}

+ +

{{ localize "GMTOOLKIT.Dialog.Maintenance.Macros" }}

+ {{#each macros as |macro id|}} +   {{ localize macro.translationKey }}
+ {{/each}} +
+ +

{{ localize "GMTOOLKIT.Dialog.Maintenance.Tables" }}

+ {{#each tables as |table id|}} +   {{ localize table.translationKey }}
+ {{/each}} +
+ +
\ No newline at end of file