diff --git a/CHANGELOG.md b/CHANGELOG.md index df5add2..c08b21c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,8 @@ All notable changes to this project will be documented in this file. The format See [Issue Backlog](../../issues) and [Roadmap](../../milestones). - *Fixed* issue where players would see permission errors during advantage updates on other users' actors. [#150](https://github.com/Jagusti/fvtt-wfrp4e-gmtoolkit/issues/150) - *Added* new option to set 'party' (player-assigned: default) or 'company' (player-owned) actors as default group type for Session Turnover macros. [#151](https://github.com/Jagusti/fvtt-wfrp4e-gmtoolkit/issues/151) - - The Reset Fortune macro uses the Session Turnover Default Group Selection option. - - Actors who are not character type, but are player-owned (ie, NPC, vehicle and creature type actors) are ignored whe processing session turnover. macros + - The Reset Fortune and Add XP macros use the Session Turnover Default Group Selection option. + - Actors who are not character type, but are player-owned (ie, NPC, vehicle and creature type actors) are ignored whe processing Session Turnover macros. ## [Version 0.9.4.2](https://github.com/Jagusti/fvtt-wfrp4e-gmtoolkit/releases/tag/v0.9.4.2) (2022-07-29) - *Added* localization improvements for Condition Check and Secret Group Test macros. (Thanks @Txus5012!) [[#141](https://github.com/Jagusti/fvtt-wfrp4e-gmtoolkit/pull/141)] diff --git a/packs/gm-toolkit-macros.db b/packs/gm-toolkit-macros.db index f936cab..9c911b2 100644 --- a/packs/gm-toolkit-macros.db +++ b/packs/gm-toolkit-macros.db @@ -9,7 +9,7 @@ {"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\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 (var package of packsource) {\n if (p.metadata.package === package) return true;\n }\n })\n}\n\nif (packsource.length == 0 && packtypes.length > 0) {\n packs = game.packs.filter(p => p.metadata.system === \"wfrp4e\").filter(p => {\n for (var type of packtypes) {\n if (p.metadata.type === type) return true;\n }\n })\n}\n\n\nif (packsource.length > 0 && packtypes.length > 0) {\n packs = game.packs.filter(p => {\n for (var package of packsource) {\n for (var type of packtypes) {\n if (p.metadata.package === package && p.metadata.type === type) return true;\n }\n }\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.3\n* UPDATED: 2022-05-23\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,"permission":{"default":0,"WpuDIfNQnefaTyuV":3},"flags":{"core":{"sourceId":"Macro.4wqXemTyGRfnJoVW"},"wfrp4e-gm-toolkit":{"version":"0.9.3"}},"_id":"nvqeTARBoSP89WT5"} {"_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 = game.gmtoolkit.utility.getGroup(game.settings.get(game.gmtoolkit.module.MODULE_ID,\"defaultPartySessionTurnover\")).filter(g => g.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(\"NAME.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(\"NAME.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.4.3\n * UPDATED: 2022-07-31\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,"permission":{"default":0,"k5y1jEYMBqd9uLUx":3},"flags":{"wfrp4e-gm-toolkit":{"version":"0.9.4.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(); \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(\"party\");\n const targeted = game.gmtoolkit.utility.getGroup(\"party\", {interaction : \"targeted\"});\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 let canWhisperTo = (actor.corruption.value) ? `enabled title=\"${game.i18n.localize('GMTOOLKIT.Dialog.DarkWhispers.HasCorruption')}\"` : `disabled title=\"${game.i18n.localize('GMTOOLKIT.Dialog.DarkWhispers.NoCorruption')}\"`;\n let checked = (actor.targeted && actor.corruption.value) ? \"checked\" : \"\";\n let playerOwners = actor.owners.map(m => m.name).join(\", \")\n checkOptions +=`\n
\n \n \n \n
\n `\n });\n\n // Construct and show form\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 \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: (html) => 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 let characterTargets = []; \n let playerRecipients = []; \n\n for ( let character of characterList ) {\n if (html.find(`[name=\"${character.actorId}\"]`)[0].checked) {\n characterTargets.push(character.name);\n sendToOwners ? playerRecipients.push(...character.owners.map(m => m.id)) : 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.length || !darkwhisper) 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.4\n* UPDATED: 2022-07-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* TIP: Actor tokens that are targeted in a scene are pre-selecteed in the Send Dark Whisper dialog.\n========== */","folder":null,"sort":0,"permission":{"default":0,"Zo7HSQ75uO8dWUkH":3},"flags":{"core":{"sourceId":"Macro.NsfnGgD7GMH7AMwI"},"wfrp4e-gm-toolkit":{"version":"0.9.4"}}} -{"_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.getGroup(\"party\") \n } else {\n // (2) all targeted tokens of awardee selection\n awardees = game.gmtoolkit.utility.getGroup(\"party\", {interaction : \"targeted\"})\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 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; \n let newXPTotal = Math.max(XPTotal + XP,0);\n let XPCurrent = pc?.details?.experience?.current || 0; \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.4\n * UPDATED: 2022-07-03\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.4"}}} +{"_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.getGroup(game.settings.get(\"wfrp4e-gm-toolkit\", \"defaultPartyGroupTest\")).filter(g => g.type === \"character\")\n } else {\n // (2) all targeted tokens of awardee selection\n awardees = game.gmtoolkit.utility.getGroup( game.settings.get(\"wfrp4e-gm-toolkit\", \"defaultPartyGroupTest\"), {interaction : \"targeted\"}).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 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; \n let newXPTotal = Math.max(XPTotal + XP,0);\n let XPCurrent = pc?.details?.experience?.current || 0; \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.4.3\n * UPDATED: 2022-07-31\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,"permission":{"default":0,"k5y1jEYMBqd9uLUx":3},"flags":{"wfrp4e-gm-toolkit":{"version":"0.9.4.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":"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); console.log(tokens)\n const party = game.gmtoolkit.utility.getGroup(\"party\").filter(g => g.conditions.length !== 0); console.log(party)\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(tokenActor, testOptions, cond.conditionId, removedConditions)\n }\n }\n\n if (removedConditions.length !== 0) ChatMessage.create({ content: removedConditions.join(\"
\") })\n\n return // 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 (combat.current.turn > -1 && combat.current.turn == combat.turns.length - 1)\n }\n }\n\n async function processConditionTest(tokenActor, testOptions, condition, removedConditions) {\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.hasCondition(condition).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 console.log(ablazeDamage)\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: 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: 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: 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: 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(condition, Math.min(Number(conditionTest.result.SL) + 1, conditionCount));\n conditionRemoved = condition;\n }\n return conditionTest\n }\n } // end processConditionTest\n}\n\n\n/* ==========\n* MACRO: Check Conditions\n* VERSION: 0.9.4.2\n* UPDATED: 2022-07-25\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,"permission":{"default":0,"WpuDIfNQnefaTyuV":3},"flags":{"core":{"sourceId":"Macro.YSvf5jsBKPTkkULa"},"wfrp4e-gm-toolkit":{"version":"0.9.4-1"}}} {"_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":"/* === Set Target Group === */\nconst groupOptions = {\n // type: \"company\" // options: \"party\" (default: player-assigned characters), \"company\" (PCs plus player-owned characters, eg henchmen)\n }\n \n/* === Set Test Parameters === */\nconst testParameters = {\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 === */\nconst interactive = true // set false to bypass Set up Group Test user interface (must set {testParameters.testSkill})\n\n/* === Guard === */\n// Exit with notice if no actors in group\nif (!game.user.isGM) return ui.notifications.error(game.i18n.localize(\"GMTOOLKIT.Message.MakeSecretGroupTest.NoPermission\"))\n\n/* === Run Group Test === */\n// RUN SILENT TEST\nif (!interactive) return game.gmtoolkit.grouptest.silent(groupOptions, testParameters)\n// LAUNCH USER INTERFACE\nnew game.gmtoolkit.grouptest.launch({groupOptions, testParameters}).render(interactive);\n\n\n/* ==========\n * MACRO: Make Secret Group Test\n * VERSION: 0.9.4\n * UPDATED: 2022-07-21\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,"permission":{"default":0,"CIvnsqepNlj0qrKz":3},"flags":{"core":{"sourceId":"Macro.lQMBdQOkcPIIjfIe"},"wfrp4e-gm-toolkit":{"version":"0.9.4"}}} diff --git a/scripts/macros/add-xp.js b/scripts/macros/add-xp.js index e33852d..9153dfb 100644 --- a/scripts/macros/add-xp.js +++ b/scripts/macros/add-xp.js @@ -6,10 +6,10 @@ async function addXP() { let awardees = [] if (game.user.targets.size < 1) { // (1) all assigned player characters - awardees = game.gmtoolkit.utility.getGroup("party") + awardees = game.gmtoolkit.utility.getGroup(game.settings.get("wfrp4e-gm-toolkit", "defaultPartyGroupTest")).filter(g => g.type === "character") } else { // (2) all targeted tokens of awardee selection - awardees = game.gmtoolkit.utility.getGroup("party", {interaction : "targeted"}) + awardees = game.gmtoolkit.utility.getGroup( game.settings.get("wfrp4e-gm-toolkit", "defaultPartyGroupTest"), {interaction : "targeted"}).filter(g => g.type === "character") } // setup: exit with notice if there are no player-assigned characters @@ -100,10 +100,10 @@ function updateXP(awardees, XP, reason) { /* ========== * MACRO: Add XP - * VERSION: 0.9.4 - * UPDATED: 2022-07-03 + * VERSION: 0.9.4.3 + * UPDATED: 2022-07-31 * DESCRIPTION: Adds a set amount of XP to all or targeted player character(s). Adds XP update note to the chat log. - * TIP: Characters must have a player assigned. + * TIP: Characters must have a player assigned (if default group is 'party') or be player-owned (if default group is 'company'). * TIP: Default XP amount and reason can be preset in module settings, along with option to bypass prompt for XP amount each time. * TIP: Non-whole numbers are rounded off. Negative numbers are subtracted. ========== */ \ No newline at end of file