From 4679c5843d5c26c916ed7934bd0e3c1cd804c549 Mon Sep 17 00:00:00 2001 From: Jagusti <521096+Jagusti@users.noreply.github.com> Date: Mon, 1 Aug 2022 01:02:21 +0100 Subject: [PATCH] Add default group option for Send Dark Whispers --- CHANGELOG.md | 3 +++ lang/de.json | 4 ++++ lang/en.json | 4 ++++ lang/fr.json | 4 ++++ lang/ja.json | 4 ++++ modules/gm-toolkit-settings.mjs | 13 +++++++++++++ packs/gm-toolkit-macros.db | 2 +- scripts/macros/dark-whispers.js | 16 ++++++++-------- 8 files changed, 41 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c08b21c..2d05a4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ See [Issue Backlog](../../issues) and [Roadmap](../../milestones). - *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 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. +- *Added* new option to set 'party' (player-assigned: default) or 'company' (player-owned) actors as default group type for Dark Whispers macros. [#151](https://github.com/Jagusti/fvtt-wfrp4e-gmtoolkit/issues/151) + - When using 'company' as the default group, it is possible to select unassigned players. In this case, no whisper is sent unless the 'Send to player owners' option is selected in the Send Dark Whispers dialog. +- *Added* new localisation keys to support additional dialog and settings options for default group selections. #151](https://github.com/Jagusti/fvtt-wfrp4e-gmtoolkit/issues/151) ## [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/lang/de.json b/lang/de.json index 731df40..af3f9ad 100644 --- a/lang/de.json +++ b/lang/de.json @@ -37,6 +37,8 @@ "GMTOOLKIT.Dialog.Apply": "Änderungen anwenden", "GMTOOLKIT.Dialog.Cancel" : "Abbrechen", + "GMTOOLKIT.Dialog.None" : "None", + "GMTOOLKIT.Dialog.NotAssigned" : "Not Assigned", "GMTOOLKIT.Token.Select": "Bitte wählen Sie zuerst ein oder mehrere Token aus.", "GMTOOLKIT.Token.SingleSelect": "Bitte wählen Sie zunächst einen einzelnen Token aus.", @@ -241,6 +243,8 @@ "GMTOOLKIT.Settings.DarkWhispers.message.hint" : "Wählen Sie den Ton von Dunkles Geflüster, der zu Ihrem Spiel passt.", "GMTOOLKIT.Settings.DarkWhispers.message.taunt" : "

Die dunklen Götter können sehen, dass du beunruhigt bist.

Für einen diskreten Gefallen können sie dich von 1 Verderbnis befreien.

{message}

Welchen Schaden kann es anrichten? Nehmen Sie das Angebot an und treten Sie vom Rande des Chaos zurück.

", "GMTOOLKIT.Settings.DarkWhispers.message.threat" : "

Dunkles Flüstern in deinen Ohren ...

Geben Sie auf keinen Fall preis, dass Du diese Nachricht erhalten hast.

{message}

Wenn du die Anweisung befolgst, wird deine Verderbnis insgesamt um 1 reduziert.

", + "GMTOOLKIT.Settings.DarkWhispers.DefaultGroup.name" : "Default Group Selection", + "GMTOOLKIT.Settings.DarkWhispers.DefaultGroup.hint" : "Choose 'party' (default) to only include characters assigned to players, or 'company' to also include player-owned characters (such as hirelings, henchmen and familiars). Individual characters can be de-selected in the Group Test dialog.", "GMTOOLKIT.Notification.NoActor" : "Kein Charakter ist ausgewählt oder verbunden mit {currentuser}.", "GMTOOLKIT.Notification.UserMustBePlayer" : "Nur Spielercharaktere können {action}.", diff --git a/lang/en.json b/lang/en.json index 1f6fc69..a6b75d6 100644 --- a/lang/en.json +++ b/lang/en.json @@ -37,6 +37,8 @@ "GMTOOLKIT.Dialog.Apply": "Apply Changes", "GMTOOLKIT.Dialog.Cancel" : "Cancel", + "GMTOOLKIT.Dialog.None" : "None", + "GMTOOLKIT.Dialog.NotAssigned" : "Not Assigned", "GMTOOLKIT.Token.Select": "Please select one or more tokens first.", "GMTOOLKIT.Token.SingleSelect": "Please select a single token first.", @@ -241,6 +243,8 @@ "GMTOOLKIT.Settings.DarkWhispers.message.hint" : "Choose the tone of Dark Whisper to suit your game.", "GMTOOLKIT.Settings.DarkWhispers.message.taunt" : "

The Dark Gods can see you are troubled.

For a discrete favour they can unburden you of 1 Corruption.

{message}

What harm can it do? Take the offer and step back from the brink of Chaos.

", "GMTOOLKIT.Settings.DarkWhispers.message.threat" : "

Dark Whispers Linger In Your Ears ...

Do not in any way reveal that you have received this message.

{message}

If you do as directed, your Corruption total will be reduced by 1.

", + "GMTOOLKIT.Settings.DarkWhispers.DefaultGroup.name" : "Default Group Selection", + "GMTOOLKIT.Settings.DarkWhispers.DefaultGroup.hint" : "Choose 'party' (default) to only include characters assigned to players, or 'company' to also include player-owned characters (such as hirelings, henchmen and familiars). Individual characters can be de-selected in the Group Test dialog.", "GMTOOLKIT.Notification.NoActor" : "No Character is selected or associated with {currentuser}.", "GMTOOLKIT.Notification.UserMustBePlayer" : "Only player characters can {action}.", diff --git a/lang/fr.json b/lang/fr.json index d74e5b4..62c3cab 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -37,6 +37,8 @@ "GMTOOLKIT.Dialog.Apply" : "Appliquer les changements", "GMTOOLKIT.Dialog.Cancel" : "Annuler", + "GMTOOLKIT.Dialog.None" : "None", + "GMTOOLKIT.Dialog.NotAssigned" : "Not Assigned", "GMTOOLKIT.Token.Select": "Veuillez selectionner 1 ou 2 Pions.", "GMTOOLKIT.Token.SingleSelect": "Veuillez sélectioner 1 Pion seulement.", @@ -241,6 +243,8 @@ "GMTOOLKIT.Settings.DarkWhispers.message.hint" : "Choisissez le ton des Sombres murmures correspondant à votre partie.", "GMTOOLKIT.Settings.DarkWhispers.message.taunt" : "

Les Dieux Noirs peuvent voir que vous êtes troublé.

Contre une petite faveur, ils peuvent vous soulager d'1 point de Corruption.

{message}

Quel mal cela pourrait-il faire ? Acceptez cette offre et éloignez-vous de l'abîme chaotique.

", "GMTOOLKIT.Settings.DarkWhispers.message.threat" : "

De Sombres murmures persistent dans vos oreilles...

Ne révélez d'aucune manière que vous avez reçu ce message.

{message}

Si vous faites tel qu'il vous a été enjoint de faire, votre total de points de Corruption sera réduit de 1.

", + "GMTOOLKIT.Settings.DarkWhispers.DefaultGroup.name" : "Default Group Selection", + "GMTOOLKIT.Settings.DarkWhispers.DefaultGroup.hint" : "Choose 'party' (default) to only include characters assigned to players, or 'company' to also include player-owned characters (such as hirelings, henchmen and familiars). Individual characters can be de-selected in the Group Test dialog.", "GMTOOLKIT.Notification.NoActor" : "Aucune Personnage n'est sélectionné ou n'est associé à {currentuser}.", "GMTOOLKIT.Notification.UserMustBePlayer" : "Seuls les Personnages Joueurs peuvent {action}.", diff --git a/lang/ja.json b/lang/ja.json index e0b7fff..09aa5d9 100644 --- a/lang/ja.json +++ b/lang/ja.json @@ -37,6 +37,8 @@ "GMTOOLKIT.Dialog.Apply": "変更を適用", "GMTOOLKIT.Dialog.Cancel" : "取消", + "GMTOOLKIT.Dialog.None" : "None", + "GMTOOLKIT.Dialog.NotAssigned" : "Not Assigned", "GMTOOLKIT.Token.Select": "最初にトークンを一つ以上選択してください。", "GMTOOLKIT.Token.SingleSelect": "最初にトークンを一つ選択してください。", @@ -241,6 +243,8 @@ "GMTOOLKIT.Settings.DarkWhispers.message.hint" : "ゲームに合わせて「暗黒のささやき」の音色を選んでください。", "GMTOOLKIT.Settings.DarkWhispers.message.taunt" : "

闇の神々はあなたが悩んでいるのを見ている。

些細な好意で、あなたの堕落を一つ解放してくれるだろう。

{message}

それにどんな害が有るのだろうか?この申し出を受けて、混沌の瀬戸際から一歩戻るのだ。

", "GMTOOLKIT.Settings.DarkWhispers.message.threat" : "

「暗黒のささやき」が耳にから離れない。。。

このメッセージを受け取ったことは、誰にも明かしてはいけない。

{message}

指示に従えば、君は堕落ポイントを1減らせるだろう。

", + "GMTOOLKIT.Settings.DarkWhispers.DefaultGroup.name" : "Default Group Selection", + "GMTOOLKIT.Settings.DarkWhispers.DefaultGroup.hint" : "Choose 'party' (default) to only include characters assigned to players, or 'company' to also include player-owned characters (such as hirelings, henchmen and familiars). Individual characters can be de-selected in the Group Test dialog.", "GMTOOLKIT.Notification.NoActor" : "キャラクターが選択されていないか、{currentuser}に割り当てられていない。", "GMTOOLKIT.Notification.UserMustBePlayer" : "「{action}」はプレイヤーだけが実行できる。", diff --git a/modules/gm-toolkit-settings.mjs b/modules/gm-toolkit-settings.mjs index 400fdc5..c397043 100644 --- a/modules/gm-toolkit-settings.mjs +++ b/modules/gm-toolkit-settings.mjs @@ -260,6 +260,19 @@ export class GMToolkitSettings { restricted: true }); // Settings for Send Dark Whispers Macro + game.settings.register(GMToolkit.MODULE_ID, "defaultGroupDarkWhispers", { + name: "GMTOOLKIT.Settings.DarkWhispers.DefaultGroup.name", + hint: "GMTOOLKIT.Settings.DarkWhispers.DefaultGroup.hint", + scope: "world", + config: false, + default: "party", + type: String, + choices: { + party: "GMTOOLKIT.Group.Type.party", + company: "GMTOOLKIT.Group.Type.company" + }, + feature: "darkwhispers" + }); game.settings.register(GMToolkit.MODULE_ID, "messageDarkWhispers", { name: "GMTOOLKIT.Settings.DarkWhispers.message.name", hint: "GMTOOLKIT.Settings.DarkWhispers.message.hint", diff --git a/packs/gm-toolkit-macros.db b/packs/gm-toolkit-macros.db index 9c911b2..a031c5e 100644 --- a/packs/gm-toolkit-macros.db +++ b/packs/gm-toolkit-macros.db @@ -8,7 +8,7 @@ {"_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\",\".\"))) == ((game.gmtoolkit.utility.strip(name, \"GMTOOLKIT.Macro\",\".\"))) ? name : (game.i18n.localize(game.gmtoolkit.utility.strip(name, \"GMTOOLKIT.Macro\",\".\")));\n buttons[name] = {\n label : label,\n icon : ``,\n callback : () => {\n game.macros.getName(name).execute();\n dialog.render(true);\n }\n }\n });\n dialog = new Dialog({title : game.i18n.localize(\"GMTOOLKIT.Dialog.GMToolbox.Title\"), content, buttons}).render(true);\n})();\n \n \n/* ==========\n* MACRO: GM Toolbox\n* VERSION: 0.9.4-1\n* UPDATED: 2022-07-22\n* DESCRIPTION: Adds a customisable floating dialog for quick access to frequently used Toolkit macros, freeing up hotbar spots\n* TIP: Add / remove macros from the 'macros' list to tailor it for your game. Names must exactly match those in the Macro Directory.\n* TIP: The macro dialog can be kept open for quick access and minimised to reduce space\n========== */","folder":null,"sort":0,"permission":{"default":0,"WpuDIfNQnefaTyuV":3},"flags":{"core":{"sourceId":"Macro.bY0sF26Vj0OhXwQ5"},"wfrp4e-gm-toolkit":{"version":"0.9.4-1"}}} {"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":"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 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.filter(p => p === undefined).length === playerRecipients.length || !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.3\n* UPDATED: 2022-07-31\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,"permission":{"default":0,"Zo7HSQ75uO8dWUkH":3},"flags":{"core":{"sourceId":"Macro.NsfnGgD7GMH7AMwI"},"wfrp4e-gm-toolkit":{"version":"0.9.4.3"}}} {"_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"}}} diff --git a/scripts/macros/dark-whispers.js b/scripts/macros/dark-whispers.js index f7838ec..1ba6b98 100644 --- a/scripts/macros/dark-whispers.js +++ b/scripts/macros/dark-whispers.js @@ -7,8 +7,8 @@ async function formDarkWhispers() { } // setup: determine group of actors to be whispered to - const group = game.gmtoolkit.utility.getGroup("party"); - const targeted = game.gmtoolkit.utility.getGroup("party", {interaction : "targeted"}); + const group = game.gmtoolkit.utility.getGroup(game.settings.get("wfrp4e-gm-toolkit", "defaultGroupDarkWhispers")).filter(g => g.type === "character") + const targeted = game.gmtoolkit.utility.getGroup(game.settings.get("wfrp4e-gm-toolkit", "defaultGroupDarkWhispers"), {interaction : "targeted"}).filter(g => g.type === "character"); // setup: exit with notice if there are no player-assigned characters if (!group) { return ui.notifications.error(game.i18n.localize("GMTOOLKIT.Message.DarkWhispers.NoEligibleCharacters")); @@ -44,7 +44,7 @@ async function formDarkWhispers() { checkOptions +=`
- +
` @@ -94,14 +94,14 @@ function sendDarkWhispers(html, characterList, sendToOwners) { for ( let character of characterList ) { if (html.find(`[name="${character.actorId}"]`)[0].checked) { characterTargets.push(character.name); - sendToOwners ? playerRecipients.push(...character.owners.map(m => m.id)) : playerRecipients.push(character.assignedUser.id) + sendToOwners ? playerRecipients.push(...character.owners.map(m => m.id)) : playerRecipients.push(character.assignedUser?.id) } } // check for whisper message darkwhisper = html.find('[name="message"]')[0].value // abort if no whisper or character is selected - if (!playerRecipients.length || !darkwhisper) return abortWhisper() + if (playerRecipients.filter(p => p === undefined).length === playerRecipients.length || !playerRecipients.length || !darkwhisper) return abortWhisper() // Construct and send message to whisper targets // Build the translation string based on the setting @@ -130,10 +130,10 @@ function abortWhisper() { /* ========== * MACRO: Send Dark Whispers -* VERSION: 0.9.4 -* UPDATED: 2022-07-04 +* VERSION: 0.9.4.3 +* UPDATED: 2022-07-31 * DESCRIPTION: Open a dialog to send a Dark Whisper (WFRP p183) to one or more selected player character(s). -* TIP: Only player-assigned characters with Corruption can be sent a Dark Whisper. +* TIP: Only player-assigned or player-owned characters with Corruption can be sent a Dark Whisper. * TIP: The placeholder whisper is drawn from the Dark Whispers table. Change this for different random whispers. * TIP: The whisper can be edited in the dialog, regardless of what is pre-filled from the Dark Whispers table. * TIP: Actor tokens that are targeted in a scene are pre-selecteed in the Send Dark Whisper dialog.