From eafade24055c8c0278a4de068c643fe8fbb1d6d2 Mon Sep 17 00:00:00 2001 From: Jagusti <521096+Jagusti@users.noreply.github.com> Date: Fri, 19 Aug 2022 17:06:35 +0100 Subject: [PATCH] Update macro compendium --- CHANGELOG.md | 1 + packs/gm-toolkit-macros.db | 28 ++++++++++++++-------------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bb2d41..36cd13d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ See [Issue Backlog](../../issues) and [Roadmap](../../milestones). - v9 is no longer supported beyond GM Toolkit [v0.9.4.4](https://github.com/Jagusti/fvtt-wfrp4e-gmtoolkit/releases/tag/v0.9.4.4). - v10 the minimum version required for GM Toolkit. - Full details can be found in [#156](https://github.com/Jagusti/fvtt-wfrp4e-gmtoolkit/issues/156). + - Nearly all macros have been updated. These can be re-imported using the `Update GM Toolkit` module setting. - *Removed* extra console logging in maintenance and group test applications. [#75d6dd](https://github.com/Jagusti/fvtt-wfrp4e-gmtoolkit/commit/75d6dd540a7d8dbe6a3cb9d6be940a33552dbd03) - *Added* initial support for vision modes, as well as sight saturation and colour to be able to leverage v10 vision changes in Set Token Vision and Light. - *Changed* Advantage handling to deal with Tokens primarily, and Combatants for setting flags (which is used for Lose Momentum). Actor documents are no longer supported. diff --git a/packs/gm-toolkit-macros.db b/packs/gm-toolkit-macros.db index f56757a..08d3ca5 100644 --- a/packs/gm-toolkit-macros.db +++ b/packs/gm-toolkit-macros.db @@ -1,15 +1,15 @@ -{"_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.update(token, \"reduce\")\n\n/* ==========\n* MACRO: Reduce Advantage\n* VERSION: 0.9.5\n* UPDATED: 2022-08-04\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,"flags":{"wfrp4e-gm-toolkit":{"version":"0.9.5"}}} -{"_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.update(token, \"clear\")\n\n/* ==========\n* MACRO: Clear Advantage\n* VERSION: 0.9.5\n* UPDATED: 2022-08-04\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,"flags":{"wfrp4e-gm-toolkit":{"version":"0.9.5"}}} -{"_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.update(token, \"increase\")\n\n/* ==========\n* MACRO: Add Advantage\n* VERSION: 0.9.5\n* UPDATED: 2022-08-04\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,"flags":{"wfrp4e-gm-toolkit":{"version":"0.9.5"}}} -{"_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\n .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 no: {\n icon: \"\",\n label: game.i18n.localize(\"GMTOOLKIT.Dialog.Cancel\")\n },\n yes: {\n icon: \"\",\n label: game.i18n.localize(\"GMTOOLKIT.Dialog.Apply\"),\n callback: () => applyChanges = true\n }\n },\n default: \"yes\",\n close: async html => {\n if (applyChanges) {\n\n for ( const 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 const darkvision = game.i18n.localize(\"NAME.DarkVision\").toLowerCase()\n item = token.actor.items.find(\n i => i.data.name.toLowerCase() === darkvision\n )\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 const nightvision = game.i18n.localize(\"NAME.NightVision\").toLowerCase()\n // Night Vision requires some minimal illumination to provide a benefit\n if (\n game.scenes.viewed.data.darkness < 1\n | dimLight > 0\n | game.scenes.viewed.data.globalLight\n ) {\n item = token.actor.items.find(\n i => i.data.name.toLowerCase() === nightvision\n )\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 if (item.name.toLowerCase() === nightvision ) {\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.5\n * UPDATED: 2022-08-04\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,"flags":{"core":{"sourceId":"Macro.issAGdpFxchQw2zp"},"wfrp4e-gm-toolkit":{"version":"0.9.5"}}} -{"_id":"OiQ0cS3QsmQadxqR","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.5\n* UPDATED: 2022-08-04\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,"flags":{"furnace":{"runAsGM":false},"wfrp4e-gm-toolkit":{"version":"0.9.5"},"core":{"sourceId":"Compendium.wfrp4e-gm-toolkit.gm-toolkit-macros.6UwKY8nGZyXzAXCa"}}} -{"_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) === Number(thisSession)\n ? Number(thisSession) + 1\n : 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.5\n * UPDATED: 2022-08-04\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,"flags":{"wfrp4e-gm-toolkit":{"version":"0.9.5"}}} +{"_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.update(token, \"reduce\")\n\n/* ==========\n* MACRO: Reduce Advantage\n* VERSION: 6.0.0\n* UPDATED: 2022-08-04\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,"flags":{"wfrp4e-gm-toolkit":{"version":"6.0.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.update(token, \"clear\")\n\n/* ==========\n* MACRO: Clear Advantage\n* VERSION: 6.0.0\n* UPDATED: 2022-08-04\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,"flags":{"wfrp4e-gm-toolkit":{"version":"6.0.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.update(token, \"increase\")\n\n/* ==========\n* MACRO: Add Advantage\n* VERSION: 6.0.0\n* UPDATED: 2022-08-04\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,"flags":{"wfrp4e-gm-toolkit":{"version":"6.0.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\n .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 no: {\n icon: \"\",\n label: game.i18n.localize(\"GMTOOLKIT.Dialog.Cancel\")\n },\n yes: {\n icon: \"\",\n label: game.i18n.localize(\"GMTOOLKIT.Dialog.Apply\"),\n callback: () => applyChanges = true\n }\n },\n default: \"yes\",\n close: async html => {\n if (applyChanges) {\n\n for ( const 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 const darkvision = game.i18n.localize(\"NAME.DarkVision\").toLowerCase()\n item = token.actor.items.find(\n i => i.data.name.toLowerCase() === darkvision\n )\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 const nightvision = game.i18n.localize(\"NAME.NightVision\").toLowerCase()\n // Night Vision requires some minimal illumination to provide a benefit\n if (\n game.scenes.viewed.data.darkness < 1\n | dimLight > 0\n | game.scenes.viewed.data.globalLight\n ) {\n item = token.actor.items.find(\n i => i.data.name.toLowerCase() === nightvision\n )\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 if (item.name.toLowerCase() === nightvision ) {\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: 6.0.0\n * UPDATED: 2022-08-04\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,"flags":{"core":{"sourceId":"Macro.issAGdpFxchQw2zp"},"wfrp4e-gm-toolkit":{"version":"6.0.0"}}} +{"_id":"OiQ0cS3QsmQadxqR","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: 6.0.0\n* UPDATED: 2022-08-04\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,"flags":{"furnace":{"runAsGM":false},"wfrp4e-gm-toolkit":{"version":"6.0.0"},"core":{"sourceId":"Compendium.wfrp4e-gm-toolkit.gm-toolkit-macros.6UwKY8nGZyXzAXCa"}}} +{"_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) === Number(thisSession)\n ? Number(thisSession) + 1\n : 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: 6.0.0\n * UPDATED: 2022-08-04\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,"flags":{"wfrp4e-gm-toolkit":{"version":"6.0.0"}}} {"_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,"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 \"Check Conditions\",\n \"Session Turnover\",\n \"Add XP\",\n \"Reset Fortune\",\n \"Make Secret Group 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\", \".\"))\n === game.gmtoolkit.utility.strip(name, \"GMTOOLKIT.Macro\", \".\"))\n ? name\n : 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.5\n* UPDATED: 2022-08-04\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,"flags":{"core":{"sourceId":"Macro.bY0sF26Vj0OhXwQ5"},"wfrp4e-gm-toolkit":{"version":"0.9.5"}}} -{"_id":"nvqeTARBoSP89WT5","name":"Toggle Compendium Pack Visibility","type":"script","author":"WpuDIfNQnefaTyuV","img":"modules/wfrp4e-gm-toolkit/assets/icons/toggle-pack-visibility.svg","scope":"global","command":"let packsource = [] // Leave empty to include compendium packs from all sources\n// let packsouce = [\"wfrp4e-core\", \"wfrp4e-gm-toolkit\", \"wfrp4e-eis-maps\"] // Explicitly specify pack sources to only toggle their visiblity (eg, only show/hide packs from the wfrp4e-core module)\n\nlet packtypes = [] // Leave empty to include all types of compendium pack\n// let packtypes = [\"Actor\",\"Item\", \"JournalEntry\", \"Scene\", \"Macro\", \"Cards\"] // Explicitly specify types of pack to only toggle their visiblity (eg, only show/hide Actor and Item entries)\n\nlet packs = []\n\n// eslint-disable-next-line capitalized-comments\nlet forcePrivate = false // false: toggle current visibility setting for pack; true: force to not visible\n\nif (packsource.length === 0 && packtypes.length === 0 ) packs = game.packs.filter(p => p.metadata.system === \"wfrp4e\")\n\nif (packsource.length > 0 && packtypes.length === 0 ) {\n packs = game.packs.filter(p => {\n for (const source of packsource) {\n if (p.metadata.package === source) return true\n }\n return false\n })\n}\n\nif (packsource.length === 0 && packtypes.length > 0) {\n packs = game.packs.filter(p => p.metadata.system === \"wfrp4e\").filter(p => {\n for (const type of packtypes) {\n if (p.metadata.type === type) return true\n }\n return false\n })\n}\n\n\nif (packsource.length > 0 && packtypes.length > 0) {\n packs = game.packs.filter(p => {\n for (const compendium of packsource) {\n for (const type of packtypes) {\n if (p.metadata.package === compendium\n && p.metadata.type === type) return true\n }\n }\n return false\n })\n}\n\ngame.gmtoolkit.module.log(true, packs)\n\nfor (const pack of packs) {\n if (forcePrivate) {\n await pack.configure({ private: true })\n } else {\n await pack.configure({ private: !pack.private })\n }\n}\n\n/* ==========\n* MACRO: Toggle Compendium Pack Visibility\n* VERSION: 0.9.5\n* UPDATED: 2022-08-04\n* DESCRIPTION: Hides (or shows) compendium packs designated for the wfrp4e system\n* TIP: If no compendium source is specified, only modules declared for the \"wfrp4e\" system are included\n========== */","folder":null,"sort":0,"flags":{"core":{"sourceId":"Macro.4wqXemTyGRfnJoVW"},"wfrp4e-gm-toolkit":{"version":"0.9.5"}}} -{"_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 let party = game.gmtoolkit.utility.getGroup(game.settings.get(game.gmtoolkit.module.MODULE_ID, \"defaultPartySessionTurnover\")).filter(g => g.type === \"character\")\n if (party.length === 0) return ui.notifications.error(game.i18n.localize(\"GMTOOLKIT.ResetFortune.NoPlayerCharacters\"))\n let chatContent = \"\"\n\n // Cycle through player characters, updating Fortune and building a report message\n party.forEach(character => {\n let currentFortune = character.data.data.status.fortune.value\n let maxFortune = getMaxFortune(character)\n character.update({ \"data.status.fortune.value\": maxFortune })\n chatContent += `${character.name}: ${currentFortune} -> ${maxFortune}
`\n })\n\n // Confirm changes made in chat\n let chatData = {\n user: game.user.id,\n content: chatContent,\n flavor: game.i18n.localize(\"GMTOOLKIT.ResetFortune.Restored\")\n }\n ChatMessage.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(\"NAME.Luck\") )\n if (!(item === undefined || item.data.data.advances.value < 1)) {\n for (let item of target.items) {\n if (item.type === \"talent\" && item.name === game.i18n.localize(\"NAME.Luck\")) {\n advLuck += item.data.data.advances.value\n }\n }\n }\n return target.status.fate.value + advLuck\n}\n\n/* ==========\n * MACRO: Reset Fortune\n * VERSION: 0.9.5\n * UPDATED: 2022-08-04\n * DESCRIPTION: Restores Fortune to the Fate level of player character(s). Applies any Luck talent bonus.\n * TIP: Characters must be player assigned (if group setting is 'party') or player owned (if group setting is 'company').\n ========== */","folder":null,"sort":0,"flags":{"wfrp4e-gm-toolkit":{"version":"0.9.5"}}} -{"_id":"roGO31Lo4pyL5kvg","name":"Send Dark Whispers","type":"script","author":"Zo7HSQ75uO8dWUkH","img":"modules/wfrp4e-gm-toolkit/assets/icons/dark-whispers.svg","scope":"global","command":"formDarkWhispers()\n\nasync function formDarkWhispers () {\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 // Setup: determine group of actors to be whispered to\n const group = game.gmtoolkit.utility.getGroup(game.settings.get(\"wfrp4e-gm-toolkit\", \"defaultGroupDarkWhispers\")).filter(g => g.type === \"character\")\n const targeted = game.gmtoolkit.utility.getGroup(game.settings.get(\"wfrp4e-gm-toolkit\", \"defaultGroupDarkWhispers\"), { interaction: \"targeted\" }).filter(g => g.type === \"character\")\n // Setup: exit with notice if there are no player-assigned characters\n if (!group) {\n return ui.notifications.error(game.i18n.localize(\"GMTOOLKIT.Message.DarkWhispers.NoEligibleCharacters\"))\n }\n // Setup: exit with notice if there are no player-assigned characters with Corruption\n if (!group.some(g => g.data.data?.status?.corruption?.value > 0)) {\n return ui.notifications.error(game.i18n.localize(\"GMTOOLKIT.Message.DarkWhispers.NoEligibleCharacters\"))\n }\n\n // Setup dialog content\n // Build list of characters to select via dialog\n const characterList = []\n group.forEach(g => {\n characterList.push({\n actorId: g?.actor?.id || g.id,\n name: g?.actor?.name || g.name,\n corruption: {\n value: g.data.data?.status?.corruption?.value,\n max: g.data.data?.status?.corruption?.max\n },\n assignedUser: game.users.players.filter(p => p.character === g)[0],\n owners: game.users.players.filter(p => p.id in g.data.permission),\n targeted: targeted.includes(g)\n })\n })\n\n // Build dialog content\n let checkOptions = \"\"\n characterList.forEach(actor => {\n const canWhisperTo = (actor.corruption.value)\n ? `enabled title=\"${game.i18n.localize(\"GMTOOLKIT.Dialog.DarkWhispers.HasCorruption\")}\"`\n : `disabled title=\"${game.i18n.localize(\"GMTOOLKIT.Dialog.DarkWhispers.NoCorruption\")}\"`\n const checked = (actor.targeted && actor.corruption.value) ? \"checked\" : \"\"\n const playerOwners = actor.owners.map(m => m.name).join(\", \")\n checkOptions += `\n
\n \n \n \n
\n `\n })\n\n // Construct and show form\n const darkwhisper = (game.tables.getName(game.i18n.localize(\"GMTOOLKIT.Dialog.DarkWhispers.Title\")))\n ? await game.wfrp4e.tables.rollTable(\"darkwhispers\")\n : game.i18n.format(\"GMTOOLKIT.Dialog.DarkWhispers.ImportTable\")\n\n const dialogContent = `\n
\n \n
\n ${checkOptions} \n
\n \n
\n
\n \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 cancel: {\n label: game.i18n.localize(\"GMTOOLKIT.Dialog.Cancel\"),\n callback: () => abortWhisper()\n },\n whisper: {\n label: game.i18n.localize(\"GMTOOLKIT.Dialog.DarkWhispers.SendWhisper\"),\n callback: html => sendDarkWhispers(html, characterList, html.find('[name=\"sendToOwners\"]')[0].checked)\n }\n }\n }).render(true)\n}\n\nfunction sendDarkWhispers (html, characterList, sendToOwners) {\n // Build list of selected players ids for whispers target\n const characterTargets = []\n const playerRecipients = []\n\n for ( const character of characterList ) {\n if (html.find(`[name=\"${character.actorId}\"]`)[0].checked) {\n characterTargets.push(character.name)\n sendToOwners\n ? playerRecipients.push(...character.owners.map(m => m.id))\n : playerRecipients.push(character.assignedUser?.id)\n }\n }\n\n // Check for whisper message\n darkwhisper = html.find('[name=\"message\"]')[0].value\n // Abort if no whisper or character is selected\n if (playerRecipients.filter(p => p === undefined).length\n === playerRecipients.length\n || !playerRecipients.length || !darkwhisper\n ) return abortWhisper()\n\n // Construct and send message to whisper targets\n // Build the translation string based on the setting\n const messageTemplate = `GMTOOLKIT.Settings.DarkWhispers.message.${game.settings.get(\"wfrp4e-gm-toolkit\", \"messageDarkWhispers\")}`\n // Parse the translated message\n const whisperMessage = `${game.i18n.format(messageTemplate, { message: darkwhisper })}`\n // Add response buttons for chat card. data- attributes are used by listener.\n const responseButtons = `\n \n ${game.i18n.localize(\"GMTOOLKIT.Message.DarkWhispers.Accept\")}\n ${game.i18n.localize(\"GMTOOLKIT.Message.DarkWhispers.Reject\")}\n \n `\n // Post the message\n ChatMessage.create({\n content: whisperMessage + responseButtons,\n whisper: playerRecipients,\n flavor: characterTargets.join(\", \")\n })\n}\n\nfunction abortWhisper () {\n return ui.notifications.error(game.i18n.format(\"GMTOOLKIT.Message.DarkWhispers.WhisperAborted\", { currentUser: game.user.name }))\n}\n\n\n/* ==========\n* MACRO: Send Dark Whispers\n* VERSION: 0.9.5\n* UPDATED: 2022-08-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 or player-owned 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* TIP: Actor tokens that are targeted in a scene are pre-selecteed in the Send Dark Whisper dialog.\n========== */","folder":null,"sort":0,"flags":{"core":{"sourceId":"Macro.NsfnGgD7GMH7AMwI"},"wfrp4e-gm-toolkit":{"version":"0.9.5"}}} -{"_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 assigned player characters\n awardees = game.gmtoolkit.utility\n .getGroup(game.settings.get(\"wfrp4e-gm-toolkit\", \"defaultPartyGroupTest\"))\n .filter(g => g.type === \"character\")\n } else {\n // (2) all targeted tokens of awardee selection\n awardees = game.gmtoolkit.utility\n .getGroup(game.settings.get(\"wfrp4e-gm-toolkit\", \"defaultPartyGroupTest\"), { interaction: \"targeted\" })\n .filter(g => g.type === \"character\")\n }\n\n // Setup: exit with notice if there are no player-assigned 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 const XP = Number(game.settings.get(\"wfrp4e-gm-toolkit\", \"addXPDefaultAmount\"))\n let reason = (game.settings.get(\"wfrp4e-gm-toolkit\", \"addXPDefaultReason\") === \"null\")\n ? \"\"\n : game.settings.get(\"wfrp4e-gm-toolkit\", \"addXPDefaultReason\")\n if (reason) {\n reason = game.settings.get(\"wfrp4e-gm-toolkit\", \"addXPDefaultReason\")\n const session = game.gmtoolkit.utility.getSession()\n reason = (session.date)\n ? reason.replace(\"(%date%)\", `(${session.date})`)\n : reason.replace(\" (%date%)\", \"\")\n reason = (session.id !== \"null\" )\n ? reason.replace(\"%session%\", session.id)\n : reason = reason.replace(\"%session%\", \"\")\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 const XP = Math.round(html.find(\"#add-xp\").val())\n if (isNaN(XP)) return ui.notifications.error(game.i18n.localize(\"GMTOOLKIT.Dialog.AddXP.InvalidXP\"))\n const 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 const recipient = pc?.actor?.name || pc.name\n const XPTotal = pc?.details?.experience?.total\n const newXPTotal = Math.max(XPTotal + XP, 0)\n const XPCurrent = pc?.details?.experience?.current || 0\n const 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 const 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.5\n * UPDATED: 2022-08-04\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 (if default group is 'party') or be player-owned (if default group is 'company').\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,"flags":{"wfrp4e-gm-toolkit":{"version":"0.9.5"}}} -{"_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.5\n* UPDATED: 2022-08-04\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,"flags":{"wfrp4e-gm-toolkit":{"version":"0.9.5"}}} -{"_id":"vUzIl1uDkykO5DmG","name":"Check Conditions","type":"script","author":"WpuDIfNQnefaTyuV","img":"modules/wfrp4e-gm-toolkit/assets/icons/aura-conditions.svg","scope":"global","command":"checkConditions(endOfCombatRoundOnly = true, skipPCs = true)\n\nasync function checkConditions () {\n if (!game.user.isGM) return\n\n if (endOfCombatRoundOnly && !isEndOfRound()) return ui.notifications.error(game.i18n.localize(\"GMTOOLKIT.Message.CheckConditions.WaitForEndOfRound\"))\n\n const tokens = game.gmtoolkit.utility.getGroup(\"tokens\").filter(g => g.actor.conditions.length !== 0)\n const party = game.gmtoolkit.utility.getGroup(\"party\").filter(g => g.conditions.length !== 0)\n\n const testOptions = {\n absolute: { difficulty: \"challenging\" },\n rollMode: \"gmroll\"\n }\n\n const removedConditions = []\n\n for await (const tokenActor of tokens) {\n if (skipPCs && party.includes(tokenActor.actor)) continue\n testOptions.appendTitle = game.i18n.format(\"GMTOOLKIT.Dialog.CheckConditions.For\", { name: tokenActor.actor.name })\n\n for await (cond of tokenActor.actor.conditions) {\n await processConditionTest(\n tokenActor, testOptions, cond.conditionId, removedConditions\n )\n }\n }\n\n if (removedConditions.length !== 0) ChatMessage.create({ content: removedConditions.join(\"
\") })\n\n return removedConditions // END OF FUNCTION\n\n function isEndOfRound () {\n const combat = game.combat\n if (!combat) return false\n if (combat.data.round !== 0 && combat.turns && combat.data.active) {\n return (\n combat.current.turn > -1\n && combat.current.turn === combat.turns.length - 1\n )\n }\n }\n\n async function processConditionTest (\n tokenActor, testOptions, condition, removedConditions\n ) {\n if (tokenActor.actor.type === \"vehicle\" && condition !== \"ablaze\") return // Vehicles can burn, but not make condition tests\n const title = `${game.i18n.localize(game.wfrp4e.config.conditions[condition])}`\n const conditionCount = tokenActor.actor\n .hasCondition(condition)\n .conditionValue\n let conditionRemoved = \"\"\n let setupData = {}\n let conditionTest = {}\n let skill = {}\n // Show the roll dialog for broken condition tests, so the difficulty can be correctly set\n // note: cancelling the roll dialog cancels any outstanding condition checks\n condition === \"broken\" ? testOptions.bypass = false : testOptions.bypass = true\n\n switch (condition) {\n case \"surprised\": // Lose condition\n tokenActor.actor.removeCondition(condition)\n conditionRemoved = condition\n break\n case \"ablaze\": // Lose 1d10-TB-AP+(condition-1) (min 1) Wounds\n // doesn't consider the least protected hit location, but does show how much damage is absorbed by AP for manual adjustment if desired\n const ablazeDamage = await new Roll(`1d10 + ${conditionCount} - 1`).evaluate()\n tokenActor.actor.applyBasicDamage(ablazeDamage.total, {})\n break\n case \"stunned\": // Challenging Endurance: remove 1+SL (min 1) condition\n // fall through:\n case \"poisoned\": // Challenging Endurance: remove 1+SL (min 1) condition\n skill = game.gmtoolkit.utility.hasSkill(tokenActor.actor, game.i18n.localize(\"NAME.Endurance\"), \"silent\")\n // Setup test data\n if (skill !== undefined) { // Prefer Endurance test\n testOptions.title = game.i18n.format(\"GMTOOLKIT.Dialog.CheckConditions.Skill\", { title, skill: game.i18n.localize(\"NAME.Endurance\") })\n setupData = await tokenActor.actor.setupSkill(skill, testOptions)\n }\n if (skill === undefined) { // Fallback to Toughness if no Endurance skill\n testOptions.title = game.i18n.format(\"GMTOOLKIT.Dialog.CheckConditions.Fallback\", { title, char: game.i18n.localize(\"CHAR.T\") })\n setupData = await tokenActor.actor.setupCharacteristic(\"t\", testOptions)\n }\n // Process test\n await processConditionTest()\n break\n case \"broken\": // Challenging Cool: remove 1+SL (min 1) condition\n skill = game.gmtoolkit.utility.hasSkill(tokenActor.actor, game.i18n.localize(\"NAME.Cool\"), \"silent\")\n // Setup test data\n if (skill !== undefined) { // Prefer Cool test\n testOptions.title = game.i18n.format(\"GMTOOLKIT.Dialog.CheckConditions.Skill\", { title, skill: game.i18n.localize(\"NAME.Cool\") })\n setupData = await tokenActor.actor.setupSkill(skill, testOptions)\n }\n if (skill === undefined) { // Fallback to Willpower if no Cool skill\n testOptions.title = game.i18n.format(\"GMTOOLKIT.Dialog.CheckConditions.Fallback\", { title, char: game.i18n.localize(\"CHAR.WP\") })\n setupData = await tokenActor.actor.setupCharacteristic(\"t\", testOptions)\n }\n // Process test\n await processConditionTest()\n break\n default:\n break\n }\n // Collect removed conditions\n if (conditionRemoved !== \"\") {\n removedConditions.push(\n game.i18n.format(\"CHAT.RemovedConditions\", {\n condition: title,\n name: tokenActor.actor.name\n })\n )\n }\n\n async function processConditionTest () {\n conditionTest = await tokenActor.actor.basicTest(setupData)\n if (conditionTest.succeeded) {\n tokenActor.actor.removeCondition(\n condition,\n Math.min(Number(conditionTest.result.SL) + 1, conditionCount)\n )\n conditionRemoved = condition\n }\n return conditionTest\n }\n } // End processConditionTest\n}\n\n\n/* ==========\n* MACRO: Check Conditions\n* VERSION: 0.9.5\n* UPDATED: 2022-08-04\n* DESCRIPTION: Process end of round condition checks. Automatically handle removal of Surprised condition, tests to remove Poisoned, Stunned and Broken conditions, and Ablaze damage (including to vehicles).\n* TIP: Set `skipPCs = false` to automatically make condition checks for player-assigned characters.\n* TIP: Set `endOfCombatRoundsOnly = false` to use the macro in any combat round, or even outside combat.\n========== */","folder":null,"sort":0,"flags":{"core":{"sourceId":"Macro.YSvf5jsBKPTkkULa"},"wfrp4e-gm-toolkit":{"version":"0.9.5"}}} -{"_id":"wN47JNwM2POBSUUm","name":"Make Secret Group Test","type":"script","author":"CIvnsqepNlj0qrKz","img":"modules/wfrp4e-gm-toolkit/assets/icons/make-secret-group-test.svg","scope":"global","command":"makeGroupTest()\n\nfunction makeGroupTest () {\n\n /* === Set Target Group === */\n const groupOptions = {\n // eslint-disable-next-line capitalized-comments\n // type: \"company\" // options: \"party\" (default: player-assigned characters), \"company\" (PCs plus player-owned characters, eg henchmen)\n }\n\n /* === Set Test Parameters === */\n const testParameters = {\n // eslint-disable-next-line capitalized-comments\n // testSkill: \"Stealth (Rural)\", // eg, \"Perception\", \"Evaluate\", \"Lore (Reikland)\".\n // rollMode : \"public\", // choose from \"blindroll\" (default), \"gmroll\", \"selfroll\", \"public\"\n // testModifier: -10, // any +/- integer (0: default)\n // difficulty: \"difficult\", // options: \"default\" (default) or system difficultyModifiers (see reference at end)\n // bypass: false, // options: true (default), false\n // fallback: false, // options: true (default), false\n }\n\n /* === Interactive or Silent Test === */\n const interactive = true // Set false to bypass Set up Group Test user interface (must set {testParameters.testSkill})\n\n /* === Guard === */\n // Exit with notice if not a GM\n if (!game.user.isGM) return ui.notifications.error(game.i18n.localize(\"GMTOOLKIT.Message.MakeSecretGroupTest.NoPermission\"))\n\n /* === Run Group Test === */\n // RUN SILENT TEST\n if (!interactive) return game.gmtoolkit.grouptest\n .silent(groupOptions, testParameters)\n // LAUNCH USER INTERFACE\n new game.gmtoolkit.grouptest.launch({ groupOptions, testParameters })\n .render(interactive)\n}\n\n\n/* ==========\n * MACRO: Make Secret Group Test\n * VERSION: 0.9.5\n * UPDATED: 2022-08-04\n * DESCRIPTION: Quckly roll and report group skill tests for player and non-player characters.\n * TIP: Default options for Quick Test and custom skills can be set in module settings.\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 (such as secret Stealth v Perception tests) as normal. Use the double arrows in the chat log card, or roll a group test when Opposing with Targets.\n * TIP: Set interactive = false to bypass the Set up Group Test user interface and use defaults in module settings\n ========== */\n\n/* === REFERENCE: DIFFICULTY MODIFIER\n--- difficulty can be \"default\" or reference built-in system difficultyModifiers, eg, \"average\", etc\n * \"default\" (in quotes) leaves the group test setup option blank, and uses the system setting for determining default difficulty\n--- Accepted difficultyModifiers (replacing \"default\") and their standard interpretation are\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--- Modifier values may vary if using homebrew settings, such as MooMan's symmetric Difficulty Options\n*/","folder":null,"sort":0,"flags":{"core":{"sourceId":"Macro.lQMBdQOkcPIIjfIe"},"wfrp4e-gm-toolkit":{"version":"0.9.5"}}} +{"_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 \"Check Conditions\",\n \"Session Turnover\",\n \"Add XP\",\n \"Reset Fortune\",\n \"Make Secret Group 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\", \".\"))\n === game.gmtoolkit.utility.strip(name, \"GMTOOLKIT.Macro\", \".\"))\n ? name\n : 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: 6.0.0\n* UPDATED: 2022-08-04\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,"flags":{"core":{"sourceId":"Macro.bY0sF26Vj0OhXwQ5"},"wfrp4e-gm-toolkit":{"version":"6.0.0"}}} +{"_id":"nvqeTARBoSP89WT5","name":"Toggle Compendium Pack Visibility","type":"script","author":"WpuDIfNQnefaTyuV","img":"modules/wfrp4e-gm-toolkit/assets/icons/toggle-pack-visibility.svg","scope":"global","command":"let packsource = [] // Leave empty to include compendium packs from all sources\n// let packsouce = [\"wfrp4e-core\", \"wfrp4e-gm-toolkit\", \"wfrp4e-eis-maps\"] // Explicitly specify pack sources to only toggle their visiblity (eg, only show/hide packs from the wfrp4e-core module)\n\nlet packtypes = [] // Leave empty to include all types of compendium pack\n// let packtypes = [\"Actor\",\"Item\", \"JournalEntry\", \"Scene\", \"Macro\", \"Cards\"] // Explicitly specify types of pack to only toggle their visiblity (eg, only show/hide Actor and Item entries)\n\nlet packs = []\n\n// eslint-disable-next-line capitalized-comments\nlet forcePrivate = false // false: toggle current visibility setting for pack; true: force to not visible\n\nif (packsource.length === 0 && packtypes.length === 0 ) packs = game.packs.filter(p => p.metadata.system === \"wfrp4e\")\n\nif (packsource.length > 0 && packtypes.length === 0 ) {\n packs = game.packs.filter(p => {\n for (const source of packsource) {\n if (p.metadata.package === source) return true\n }\n return false\n })\n}\n\nif (packsource.length === 0 && packtypes.length > 0) {\n packs = game.packs.filter(p => p.metadata.system === \"wfrp4e\").filter(p => {\n for (const type of packtypes) {\n if (p.metadata.type === type) return true\n }\n return false\n })\n}\n\n\nif (packsource.length > 0 && packtypes.length > 0) {\n packs = game.packs.filter(p => {\n for (const compendium of packsource) {\n for (const type of packtypes) {\n if (p.metadata.package === compendium\n && p.metadata.type === type) return true\n }\n }\n return false\n })\n}\n\ngame.gmtoolkit.module.log(true, packs)\n\nfor (const pack of packs) {\n if (forcePrivate) {\n await pack.configure({ private: true })\n } else {\n await pack.configure({ private: !pack.private })\n }\n}\n\n/* ==========\n* MACRO: Toggle Compendium Pack Visibility\n* VERSION: 6.0.0\n* UPDATED: 2022-08-04\n* DESCRIPTION: Hides (or shows) compendium packs designated for the wfrp4e system\n* TIP: If no compendium source is specified, only modules declared for the \"wfrp4e\" system are included\n========== */","folder":null,"sort":0,"flags":{"core":{"sourceId":"Macro.4wqXemTyGRfnJoVW"},"wfrp4e-gm-toolkit":{"version":"6.0.0"}}} +{"_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 let party = game.gmtoolkit.utility.getGroup(game.settings.get(game.gmtoolkit.module.MODULE_ID, \"defaultPartySessionTurnover\")).filter(g => g.type === \"character\")\n if (party.length === 0) return ui.notifications.error(game.i18n.localize(\"GMTOOLKIT.ResetFortune.NoPlayerCharacters\"))\n let chatContent = \"\"\n\n // Cycle through player characters, updating Fortune and building a report message\n party.forEach(character => {\n let currentFortune = character.data.data.status.fortune.value\n let maxFortune = getMaxFortune(character)\n character.update({ \"data.status.fortune.value\": maxFortune })\n chatContent += `${character.name}: ${currentFortune} -> ${maxFortune}
`\n })\n\n // Confirm changes made in chat\n let chatData = {\n user: game.user.id,\n content: chatContent,\n flavor: game.i18n.localize(\"GMTOOLKIT.ResetFortune.Restored\")\n }\n ChatMessage.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(\"NAME.Luck\") )\n if (!(item === undefined || item.data.data.advances.value < 1)) {\n for (let item of target.items) {\n if (item.type === \"talent\" && item.name === game.i18n.localize(\"NAME.Luck\")) {\n advLuck += item.data.data.advances.value\n }\n }\n }\n return target.status.fate.value + advLuck\n}\n\n/* ==========\n * MACRO: Reset Fortune\n * VERSION: 6.0.0\n * UPDATED: 2022-08-04\n * DESCRIPTION: Restores Fortune to the Fate level of player character(s). Applies any Luck talent bonus.\n * TIP: Characters must be player assigned (if group setting is 'party') or player owned (if group setting is 'company').\n ========== */","folder":null,"sort":0,"flags":{"wfrp4e-gm-toolkit":{"version":"6.0.0"}}} +{"_id":"roGO31Lo4pyL5kvg","name":"Send Dark Whispers","type":"script","author":"Zo7HSQ75uO8dWUkH","img":"modules/wfrp4e-gm-toolkit/assets/icons/dark-whispers.svg","scope":"global","command":"formDarkWhispers()\n\nasync function formDarkWhispers () {\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 // Setup: determine group of actors to be whispered to\n const group = game.gmtoolkit.utility.getGroup(game.settings.get(\"wfrp4e-gm-toolkit\", \"defaultGroupDarkWhispers\")).filter(g => g.type === \"character\")\n const targeted = game.gmtoolkit.utility.getGroup(game.settings.get(\"wfrp4e-gm-toolkit\", \"defaultGroupDarkWhispers\"), { interaction: \"targeted\" }).filter(g => g.type === \"character\")\n // Setup: exit with notice if there are no player-assigned characters\n if (!group) {\n return ui.notifications.error(game.i18n.localize(\"GMTOOLKIT.Message.DarkWhispers.NoEligibleCharacters\"))\n }\n // Setup: exit with notice if there are no player-assigned characters with Corruption\n if (!group.some(g => g.data.data?.status?.corruption?.value > 0)) {\n return ui.notifications.error(game.i18n.localize(\"GMTOOLKIT.Message.DarkWhispers.NoEligibleCharacters\"))\n }\n\n // Setup dialog content\n // Build list of characters to select via dialog\n const characterList = []\n group.forEach(g => {\n characterList.push({\n actorId: g?.actor?.id || g.id,\n name: g?.actor?.name || g.name,\n corruption: {\n value: g.data.data?.status?.corruption?.value,\n max: g.data.data?.status?.corruption?.max\n },\n assignedUser: game.users.players.filter(p => p.character === g)[0],\n owners: game.users.players.filter(p => p.id in g.data.permission),\n targeted: targeted.includes(g)\n })\n })\n\n // Build dialog content\n let checkOptions = \"\"\n characterList.forEach(actor => {\n const canWhisperTo = (actor.corruption.value)\n ? `enabled title=\"${game.i18n.localize(\"GMTOOLKIT.Dialog.DarkWhispers.HasCorruption\")}\"`\n : `disabled title=\"${game.i18n.localize(\"GMTOOLKIT.Dialog.DarkWhispers.NoCorruption\")}\"`\n const checked = (actor.targeted && actor.corruption.value) ? \"checked\" : \"\"\n const playerOwners = actor.owners.map(m => m.name).join(\", \")\n checkOptions += `\n
\n \n \n \n
\n `\n })\n\n // Construct and show form\n const darkwhisper = (game.tables.getName(game.i18n.localize(\"GMTOOLKIT.Dialog.DarkWhispers.Title\")))\n ? await game.wfrp4e.tables.rollTable(\"darkwhispers\")\n : game.i18n.format(\"GMTOOLKIT.Dialog.DarkWhispers.ImportTable\")\n\n const dialogContent = `\n
\n \n
\n ${checkOptions} \n
\n \n
\n
\n \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 cancel: {\n label: game.i18n.localize(\"GMTOOLKIT.Dialog.Cancel\"),\n callback: () => abortWhisper()\n },\n whisper: {\n label: game.i18n.localize(\"GMTOOLKIT.Dialog.DarkWhispers.SendWhisper\"),\n callback: html => sendDarkWhispers(html, characterList, html.find('[name=\"sendToOwners\"]')[0].checked)\n }\n }\n }).render(true)\n}\n\nfunction sendDarkWhispers (html, characterList, sendToOwners) {\n // Build list of selected players ids for whispers target\n const characterTargets = []\n const playerRecipients = []\n\n for ( const character of characterList ) {\n if (html.find(`[name=\"${character.actorId}\"]`)[0].checked) {\n characterTargets.push(character.name)\n sendToOwners\n ? playerRecipients.push(...character.owners.map(m => m.id))\n : playerRecipients.push(character.assignedUser?.id)\n }\n }\n\n // Check for whisper message\n darkwhisper = html.find('[name=\"message\"]')[0].value\n // Abort if no whisper or character is selected\n if (playerRecipients.filter(p => p === undefined).length\n === playerRecipients.length\n || !playerRecipients.length || !darkwhisper\n ) return abortWhisper()\n\n // Construct and send message to whisper targets\n // Build the translation string based on the setting\n const messageTemplate = `GMTOOLKIT.Settings.DarkWhispers.message.${game.settings.get(\"wfrp4e-gm-toolkit\", \"messageDarkWhispers\")}`\n // Parse the translated message\n const whisperMessage = `${game.i18n.format(messageTemplate, { message: darkwhisper })}`\n // Add response buttons for chat card. data- attributes are used by listener.\n const responseButtons = `\n \n ${game.i18n.localize(\"GMTOOLKIT.Message.DarkWhispers.Accept\")}\n ${game.i18n.localize(\"GMTOOLKIT.Message.DarkWhispers.Reject\")}\n \n `\n // Post the message\n ChatMessage.create({\n content: whisperMessage + responseButtons,\n whisper: playerRecipients,\n flavor: characterTargets.join(\", \")\n })\n}\n\nfunction abortWhisper () {\n return ui.notifications.error(game.i18n.format(\"GMTOOLKIT.Message.DarkWhispers.WhisperAborted\", { currentUser: game.user.name }))\n}\n\n\n/* ==========\n* MACRO: Send Dark Whispers\n* VERSION: 6.0.0\n* UPDATED: 2022-08-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 or player-owned 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* TIP: Actor tokens that are targeted in a scene are pre-selecteed in the Send Dark Whisper dialog.\n========== */","folder":null,"sort":0,"flags":{"core":{"sourceId":"Macro.NsfnGgD7GMH7AMwI"},"wfrp4e-gm-toolkit":{"version":"6.0.0"}}} +{"_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 assigned player characters\n awardees = game.gmtoolkit.utility\n .getGroup(game.settings.get(\"wfrp4e-gm-toolkit\", \"defaultPartyGroupTest\"))\n .filter(g => g.type === \"character\")\n } else {\n // (2) all targeted tokens of awardee selection\n awardees = game.gmtoolkit.utility\n .getGroup(game.settings.get(\"wfrp4e-gm-toolkit\", \"defaultPartyGroupTest\"), { interaction: \"targeted\" })\n .filter(g => g.type === \"character\")\n }\n\n // Setup: exit with notice if there are no player-assigned 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 const XP = Number(game.settings.get(\"wfrp4e-gm-toolkit\", \"addXPDefaultAmount\"))\n let reason = (game.settings.get(\"wfrp4e-gm-toolkit\", \"addXPDefaultReason\") === \"null\")\n ? \"\"\n : game.settings.get(\"wfrp4e-gm-toolkit\", \"addXPDefaultReason\")\n if (reason) {\n reason = game.settings.get(\"wfrp4e-gm-toolkit\", \"addXPDefaultReason\")\n const session = game.gmtoolkit.utility.getSession()\n reason = (session.date)\n ? reason.replace(\"(%date%)\", `(${session.date})`)\n : reason.replace(\" (%date%)\", \"\")\n reason = (session.id !== \"null\" )\n ? reason.replace(\"%session%\", session.id)\n : reason = reason.replace(\"%session%\", \"\")\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 const XP = Math.round(html.find(\"#add-xp\").val())\n if (isNaN(XP)) return ui.notifications.error(game.i18n.localize(\"GMTOOLKIT.Dialog.AddXP.InvalidXP\"))\n const 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 const recipient = pc?.actor?.name || pc.name\n const XPTotal = pc?.details?.experience?.total\n const newXPTotal = Math.max(XPTotal + XP, 0)\n const XPCurrent = pc?.details?.experience?.current || 0\n const 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 const 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: 6.0.0\n * UPDATED: 2022-08-04\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 (if default group is 'party') or be player-owned (if default group is 'company').\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,"flags":{"wfrp4e-gm-toolkit":{"version":"6.0.0"}}} +{"_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: 6.0.0\n* UPDATED: 2022-08-04\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,"flags":{"wfrp4e-gm-toolkit":{"version":"6.0.0"}}} +{"_id":"vUzIl1uDkykO5DmG","name":"Check Conditions","type":"script","author":"WpuDIfNQnefaTyuV","img":"modules/wfrp4e-gm-toolkit/assets/icons/aura-conditions.svg","scope":"global","command":"checkConditions(endOfCombatRoundOnly = true, skipPCs = true)\n\nasync function checkConditions () {\n if (!game.user.isGM) return\n\n if (endOfCombatRoundOnly && !isEndOfRound()) return ui.notifications.error(game.i18n.localize(\"GMTOOLKIT.Message.CheckConditions.WaitForEndOfRound\"))\n\n const tokens = game.gmtoolkit.utility.getGroup(\"tokens\").filter(g => g.actor.conditions.length !== 0)\n const party = game.gmtoolkit.utility.getGroup(\"party\").filter(g => g.conditions.length !== 0)\n\n const testOptions = {\n absolute: { difficulty: \"challenging\" },\n rollMode: \"gmroll\"\n }\n\n const removedConditions = []\n\n for await (const tokenActor of tokens) {\n if (skipPCs && party.includes(tokenActor.actor)) continue\n testOptions.appendTitle = game.i18n.format(\"GMTOOLKIT.Dialog.CheckConditions.For\", { name: tokenActor.actor.name })\n\n for await (cond of tokenActor.actor.conditions) {\n await processConditionTest(\n tokenActor, testOptions, cond.conditionId, removedConditions\n )\n }\n }\n\n if (removedConditions.length !== 0) ChatMessage.create({ content: removedConditions.join(\"
\") })\n\n return removedConditions // END OF FUNCTION\n\n function isEndOfRound () {\n const combat = game.combat\n if (!combat) return false\n if (combat.data.round !== 0 && combat.turns && combat.data.active) {\n return (\n combat.current.turn > -1\n && combat.current.turn === combat.turns.length - 1\n )\n }\n }\n\n async function processConditionTest (\n tokenActor, testOptions, condition, removedConditions\n ) {\n if (tokenActor.actor.type === \"vehicle\" && condition !== \"ablaze\") return // Vehicles can burn, but not make condition tests\n const title = `${game.i18n.localize(game.wfrp4e.config.conditions[condition])}`\n const conditionCount = tokenActor.actor\n .hasCondition(condition)\n .conditionValue\n let conditionRemoved = \"\"\n let setupData = {}\n let conditionTest = {}\n let skill = {}\n // Show the roll dialog for broken condition tests, so the difficulty can be correctly set\n // note: cancelling the roll dialog cancels any outstanding condition checks\n condition === \"broken\" ? testOptions.bypass = false : testOptions.bypass = true\n\n switch (condition) {\n case \"surprised\": // Lose condition\n tokenActor.actor.removeCondition(condition)\n conditionRemoved = condition\n break\n case \"ablaze\": // Lose 1d10-TB-AP+(condition-1) (min 1) Wounds\n // doesn't consider the least protected hit location, but does show how much damage is absorbed by AP for manual adjustment if desired\n const ablazeDamage = await new Roll(`1d10 + ${conditionCount} - 1`).evaluate()\n tokenActor.actor.applyBasicDamage(ablazeDamage.total, {})\n break\n case \"stunned\": // Challenging Endurance: remove 1+SL (min 1) condition\n // fall through:\n case \"poisoned\": // Challenging Endurance: remove 1+SL (min 1) condition\n skill = game.gmtoolkit.utility.hasSkill(tokenActor.actor, game.i18n.localize(\"NAME.Endurance\"), \"silent\")\n // Setup test data\n if (skill !== undefined) { // Prefer Endurance test\n testOptions.title = game.i18n.format(\"GMTOOLKIT.Dialog.CheckConditions.Skill\", { title, skill: game.i18n.localize(\"NAME.Endurance\") })\n setupData = await tokenActor.actor.setupSkill(skill, testOptions)\n }\n if (skill === undefined) { // Fallback to Toughness if no Endurance skill\n testOptions.title = game.i18n.format(\"GMTOOLKIT.Dialog.CheckConditions.Fallback\", { title, char: game.i18n.localize(\"CHAR.T\") })\n setupData = await tokenActor.actor.setupCharacteristic(\"t\", testOptions)\n }\n // Process test\n await processConditionTest()\n break\n case \"broken\": // Challenging Cool: remove 1+SL (min 1) condition\n skill = game.gmtoolkit.utility.hasSkill(tokenActor.actor, game.i18n.localize(\"NAME.Cool\"), \"silent\")\n // Setup test data\n if (skill !== undefined) { // Prefer Cool test\n testOptions.title = game.i18n.format(\"GMTOOLKIT.Dialog.CheckConditions.Skill\", { title, skill: game.i18n.localize(\"NAME.Cool\") })\n setupData = await tokenActor.actor.setupSkill(skill, testOptions)\n }\n if (skill === undefined) { // Fallback to Willpower if no Cool skill\n testOptions.title = game.i18n.format(\"GMTOOLKIT.Dialog.CheckConditions.Fallback\", { title, char: game.i18n.localize(\"CHAR.WP\") })\n setupData = await tokenActor.actor.setupCharacteristic(\"t\", testOptions)\n }\n // Process test\n await processConditionTest()\n break\n default:\n break\n }\n // Collect removed conditions\n if (conditionRemoved !== \"\") {\n removedConditions.push(\n game.i18n.format(\"CHAT.RemovedConditions\", {\n condition: title,\n name: tokenActor.actor.name\n })\n )\n }\n\n async function processConditionTest () {\n conditionTest = await tokenActor.actor.basicTest(setupData)\n if (conditionTest.succeeded) {\n tokenActor.actor.removeCondition(\n condition,\n Math.min(Number(conditionTest.result.SL) + 1, conditionCount)\n )\n conditionRemoved = condition\n }\n return conditionTest\n }\n } // End processConditionTest\n}\n\n\n/* ==========\n* MACRO: Check Conditions\n* VERSION: 6.0.0\n* UPDATED: 2022-08-04\n* DESCRIPTION: Process end of round condition checks. Automatically handle removal of Surprised condition, tests to remove Poisoned, Stunned and Broken conditions, and Ablaze damage (including to vehicles).\n* TIP: Set `skipPCs = false` to automatically make condition checks for player-assigned characters.\n* TIP: Set `endOfCombatRoundsOnly = false` to use the macro in any combat round, or even outside combat.\n========== */","folder":null,"sort":0,"flags":{"core":{"sourceId":"Macro.YSvf5jsBKPTkkULa"},"wfrp4e-gm-toolkit":{"version":"6.0.0"}}} +{"_id":"wN47JNwM2POBSUUm","name":"Make Secret Group Test","type":"script","author":"CIvnsqepNlj0qrKz","img":"modules/wfrp4e-gm-toolkit/assets/icons/make-secret-group-test.svg","scope":"global","command":"makeGroupTest()\n\nfunction makeGroupTest () {\n\n /* === Set Target Group === */\n const groupOptions = {\n // eslint-disable-next-line capitalized-comments\n // type: \"company\" // options: \"party\" (default: player-assigned characters), \"company\" (PCs plus player-owned characters, eg henchmen)\n }\n\n /* === Set Test Parameters === */\n const testParameters = {\n // eslint-disable-next-line capitalized-comments\n // testSkill: \"Stealth (Rural)\", // eg, \"Perception\", \"Evaluate\", \"Lore (Reikland)\".\n // rollMode : \"public\", // choose from \"blindroll\" (default), \"gmroll\", \"selfroll\", \"public\"\n // testModifier: -10, // any +/- integer (0: default)\n // difficulty: \"difficult\", // options: \"default\" (default) or system difficultyModifiers (see reference at end)\n // bypass: false, // options: true (default), false\n // fallback: false, // options: true (default), false\n }\n\n /* === Interactive or Silent Test === */\n const interactive = true // Set false to bypass Set up Group Test user interface (must set {testParameters.testSkill})\n\n /* === Guard === */\n // Exit with notice if not a GM\n if (!game.user.isGM) return ui.notifications.error(game.i18n.localize(\"GMTOOLKIT.Message.MakeSecretGroupTest.NoPermission\"))\n\n /* === Run Group Test === */\n // RUN SILENT TEST\n if (!interactive) return game.gmtoolkit.grouptest\n .silent(groupOptions, testParameters)\n // LAUNCH USER INTERFACE\n new game.gmtoolkit.grouptest.launch({ groupOptions, testParameters })\n .render(interactive)\n}\n\n\n/* ==========\n * MACRO: Make Secret Group Test\n * VERSION: 6.0.0\n * UPDATED: 2022-08-04\n * DESCRIPTION: Quckly roll and report group skill tests for player and non-player characters.\n * TIP: Default options for Quick Test and custom skills can be set in module settings.\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 (such as secret Stealth v Perception tests) as normal. Use the double arrows in the chat log card, or roll a group test when Opposing with Targets.\n * TIP: Set interactive = false to bypass the Set up Group Test user interface and use defaults in module settings\n ========== */\n\n/* === REFERENCE: DIFFICULTY MODIFIER\n--- difficulty can be \"default\" or reference built-in system difficultyModifiers, eg, \"average\", etc\n * \"default\" (in quotes) leaves the group test setup option blank, and uses the system setting for determining default difficulty\n--- Accepted difficultyModifiers (replacing \"default\") and their standard interpretation are\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--- Modifier values may vary if using homebrew settings, such as MooMan's symmetric Difficulty Options\n*/","folder":null,"sort":0,"flags":{"core":{"sourceId":"Macro.lQMBdQOkcPIIjfIe"},"wfrp4e-gm-toolkit":{"version":"6.0.0"}}}