diff --git a/packs/macros-5e.db b/packs/macros-5e.db
index 90f6427..d4aa750 100644
--- a/packs/macros-5e.db
+++ b/packs/macros-5e.db
@@ -3,11 +3,10 @@
{"_id":"50p0yXEpxXa9aAYJ","name":"Bless","type":"script","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","scope":"global","command":"// new build for bless macro by Penguin#0949 with help from Kotetsushin#7680\n// version beta 4.2.0\n\n// user notes\n// this macro is inteded for use by the recipient of the bless spell in D&D 5e on Forge VTT\n// N.B. every recipient will need to use this macro independantly on their own Actor/token.\n\n//user modifiable declarations CHANGE AT YOUR OWN RISK\nconst blessIconPath = 'icons/svg/regen.svg';\nlet blessMsg = ' is Blessed!';\nlet endblessMsg = ' is no longer Blessed';\n\n//fixed declarations DO NOT MODIFY\nlet macroActor = token.actor;\nlet chatMsg = '';\nlet Blessd = macroActor.effects.find(i => i.data.label === \"Blessed\")\nlet bless = {\n changes: [\n {\n key: \"data.bonuses.mwak.attack\",\n mode: 2,\n priority: 20,\n value: \"+1d4\",\n },\n {\n key: \"data.bonuses.rwak.attack\",\n mode: 2,\n priority: 20,\n value: \"+1d4\",\n },\n\t\t{\n key: \"data.bonuses.msak.attack\",\n mode: 2,\n priority: 20,\n value: \"+1d4\",\n },\n\t\t{\n key: \"data.bonuses.rsak.attack\",\n mode: 2,\n priority: 20,\n value: \"+1d4\",\n },\n\t\t{\n key: \"data.bonuses.abilities.save\",\n mode: 2,\n priority: 20,\n value: \"+1d4\",\n },\n ],\n duration: {\n seconds: 60,\n },\n icon: blessIconPath,\n label: \"Blessed\"\n}\n//identify token\nif (macroActor === undefined || macroActor === null) {\n ui.notifications.warn(\"Please select a token first.\");\n} \nelse {\n// If already bless\t\nif (Blessd) {\n macroActor.deleteEmbeddedDocuments(\"ActiveEffect\", [Blessd.id])\n// anounce to chat\n\tchatMsg = `${macroActor.name} ${endblessMsg}`;\n}\n// if not already bless\t\nelse {\n macroActor.createEmbeddedDocuments(\"ActiveEffect\", [bless])\t\n// anounce to chat\n\t\tchatMsg = `${macroActor.name} ${blessMsg}`;\n}\n// write to chat if needed:\nif (chatMsg !== '') {\n\tlet chatData = {\n\t\tuser: game.user._id,\n\t\tspeaker: ChatMessage.getSpeaker(),\n\t\tcontent: chatMsg\n\t};\n\tChatMessage.create(chatData, {});\n}\n}","folder":null,"sort":0,"permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"flags":{}}
{"_id":"AbzZdXi97q8oHOUn","name":"Random Cutting Words","type":"script","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","scope":"global","command":"// Courtesy of @Zarek\n// Selected target receives a random cutting word from a table called \"Mockeries\" along with the roll reduction.\n// You can find a mockeries table in the community table module.\n\nlet cuttingWords = async () => {\n // Setup variables\n let tableName = \"mockeries\";\n let mockery = \"Now go away or I shall taunt you a second time-a!\"; // if table can't be found, use this.\n\n if (!actor) {\n ui.notifications.warn(\"You must have an actor selected.\");\n return\n }\n\n let actorLevels = actor.data.data.levels || 1;\n let table = game.tables.contents.find(t => t.name == tableName);\n // Get Targets name\n const targetId = game.user.targets.ids[0];\n const targetToken = canvas.tokens.get(targetId);\n if (!targetToken) {\n ui.notifications.warn(\"You must target a token.\");\n return\n }\n const targetName = targetToken.name;\n\n // Roll the result, and mark it drawn\n if (table) {\n if (checkTable(table)) {\n let roll = await table.roll();\n let result = roll.results[0];\n mockery = result.data.text;\n await table.updateEmbeddedDocuments(\"TableResult\", [{\n _id: result.id,\n drawn: true\n }]);\n }\n }\n\n function checkTable(table) {\n let results = 0;\n for (let data of table.data.results) {\n if (!data.drawn) {\n results++;\n }\n }\n if (results < 1) {\n table.reset();\n ui.notifications.notify(\"Table Reset\")\n return false\n }\n return true\n }\n\n let dieType = 'd6';\n if (actorLevels >= 15) {\n dieType = 'd12';\n } else if (actorLevels >= 10) {\n dieType = 'd10';\n } else if (actorLevels >= 5) {\n dieType = 'd8';\n }\n\n let messageContent = `
${targetName} Reduce your roll by: [[1${dieType}]].
When a creature that you can see within 60 feet of you makes an Attack roll, an ability check, or a damage roll, you can use your Reaction to expend one of your uses of Bardic Inspiration,\n rolling a Bardic Inspiration die and subtracting the number rolled from the creature’s roll.
\n
You can choose to use this feature after the creature makes its roll, but before the GM determines whether the Attack roll or ability check succeeds or fails, or before the creature deals its damage. \n The creature is immune if it can’t hear you or if it’s immune to being Charmed.
`\n\n // create the message\n if (messageContent !== '') {\n let chatData = {\n user: game.user.id,\n speaker: ChatMessage.getSpeaker(),\n content: messageContent,\n };\n ChatMessage.create(chatData, {});\n }\n};\n\ncuttingWords();","folder":null,"sort":0,"permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"flags":{}}
{"_id":"DBlk2wFOpxxhM9eC","name":"All Token’s Passive Perception","type":"script","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","scope":"global","command":"// Pull the passive perception of each token in the current scene and whisper the results to the GM.\n// Only tested with the 5e System in Foundry.\n// Author: @Drunemeton#7955. Based on the original macro by author @Erogroth#7134.\n\n// Initalize variables.\nlet pcArray = [];\nlet npcArray = [];\nlet messageContentPC = \"\";\nlet messageContentNPC = \"\";\nlet messageHeaderPC = \"PC Passive Perception \";\nlet messageHeaderNPC = \"NPC Passive Perception \";\n\n// Gather tokens in the current scene into an array.\nlet tokens = canvas.tokens.placeables.filter((token) => token.data && token.actor);\n\n// From the tokens array sort into PC and NPC arrays.\nfor (let count of tokens) {\n let tokenType = count.actor.data.type;\n let tokenName = count.data.name;\n let tokenPassive = count.actor.data.data.skills.prc.passive;\n \n if(tokenType === \"character\") {\n pcArray.push({ name: tokenName, passive: tokenPassive });\n } \n if(tokenType === \"npc\") {\n npcArray.push({ name: tokenName, passive: tokenPassive });\n }\n}\n\n// Sort each array.\nsortArray(pcArray);\nsortArray(npcArray);\n\n// Build chat message, with PCs first, then NPCs.\nfor (let numPC of pcArray) {\n messageContentPC += `${numPC.name}: ${numPC.passive} `;\n}\nfor (let numNPC of npcArray) {\n messageContentNPC += `${numNPC.name}: ${numNPC.passive} `;\n}\n\nlet chatMessage = (messageHeaderPC + messageContentPC + ` ` + messageHeaderNPC + messageContentNPC);\n\nlet chatData = {\n user: game.user._id,\n speaker: ChatMessage.getSpeaker(),\n content: chatMessage,\n whisper: game.users.filter((u) => u.isGM).map((u) => u._id),\n};\n\n// Display chat message.\nChatMessage.create(chatData, {});\n\n// Sort each array by Name.\n function sortArray(checkArray) {\n checkArray.sort(function (a, b) {\n var nameA = a.name.toUpperCase(); // ignore upper and lowercase\n var nameB = b.name.toUpperCase(); // ignore upper and lowercase\n if (nameA < nameB) {\n return -1;\n }\n if (nameA > nameB) {\n return 1;\n }\n // names must be equal\n return 0;\n });\n\n // Sort array by Passive Perception.\n checkArray.sort(function (a, b) {\n return b.passive - a.passive;\n });\n }","folder":null,"sort":0,"permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"flags":{}}
+{"name":"Apply Damage","type":"script","author":"wCsoNxA9I9elUaGF","img":"icons/svg/dice-target.svg","scope":"global","command":"// Displays a prompt which asks for an amount of damage to inflict\n// Inflicts the input damage amount to all selected tokens\n\nlet content = `\n `\n\nnew Dialog({\n title: 'How much damage should be applied (negative for healing)?',\n content: content,\n buttons:{\n yes: {\n icon: \"\",\n label: `Apply Damage`\n }\n },\n\n default:'yes',\n\n close: html => {\n let result = html.find('input[name=\\'inputField\\']');\n if (result.val() !== '') {\n let damage = result.val();\n let allSelected = canvas.tokens.controlled\n\n allSelected.forEach(selected => {\n let actor = selected.actor\n let hp = actor.data.data.attributes.hp.value\n let maxHp = actor.data.data.attributes.hp.max\n\n let updatedHp = damage > hp ? 0 : hp - damage\n\n actor.update({'data.attributes.hp.value': updatedHp > maxHp ? maxHp : updatedHp})\n\n console.log(actor)\n })\n }\n }\n}).render(true);\n\n(async () => {\nawait new Promise(resolve => setTimeout(resolve, 20));\nlet input = $('#damage-amount').focus();\n})();","folder":null,"sort":0,"permission":{"default":0,"wCsoNxA9I9elUaGF":3},"flags":{"core":{"sourceId":"Macro.7K3qCHAU5AW5TiP4"}},"_id":"Ey3vzQB2uvOrLgQ1"}
{"name":"Auto Sort Creatures by Type","type":"script","author":"wCsoNxA9I9elUaGF","img":"icons/svg/dice-target.svg","scope":"global","command":"/** ##################################################################################### *\n * This macro loops over the existing creature actors in the actor directory and creates *\n * folders corresponding to the creature type. It then sorts the creatures to the *\n * corresponding folder to make them quiet a bit more manageable, especially when you've *\n * created/imported a lot of creatures. *\n * I would recommend using the \"Compendium Folders\" module in combination to keep your *\n * initial load as fast as possible when you have a lot of actors. *\n * ##################################################################################### *\n * Credits to ZetaDracon#7558 and Freeze#2689 *\n * ##################################################################################### */\nconst folderData = {\n color: \"\",\n parent: \"\",\n sorting: \"a\",\n type: \"Actor\"\n};\n// lets make the folders.\nfor(let actor of game.actors) {\n const type = actor.data.data.details.type?.value;\n if(!type) continue; // so player characters get filtered out.\n const folder = game.folders.find(f => f.name.toLowerCase() === type && f.type === \"Actor\");\n if(!folder) await Folder.create(mergeObject({name: type}, folderData));\n}\n// lets update the actors.\nconst updates = game.actors.reduce((acc, a) => {\n const type = a.data.data.details.type?.value;\n if(!type) return acc; // so player characters get filtered out.\n let folderId = game.folders.find(f => f.name.toLowerCase() === type && f.type === \"Actor\").id;\n acc.push({_id: a.id, folder: folderId});\n return acc;\n}, []);\nawait Actor.updateDocuments(updates);","folder":null,"sort":0,"permission":{"default":0,"wCsoNxA9I9elUaGF":3},"flags":{"core":{"sourceId":"Macro.hQF9wAbhIcIzgMtU"}},"_id":"FpDlb3o6YENaz3tA"}
{"_id":"GlWj7z7p2V4JKXMx","name":"Guidance","type":"script","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","scope":"global","command":"// new build for guidance macro by Mr.White and Penguin#0949 and with no help all of Kotetsushin#7680 trust me \n// version beta 4.2.0\n\n// user notes\n// this macro is inteded for use by the recipient of the bless spell in D&D 5e on Forge VTT\n// N.B. every recipient will need to use this macro independantly on their own Actor/token.\n\n//user modifiable declarations CHANGE AT YOUR OWN RISK\nconst GuidIconPath = 'icons/svg/windmill.svg';\nlet GuideMsg = ' is guided!';\nlet endGuideMsg = ' is no longer guided.';\n\n//fixed declarations DO NOT MODIFY\nlet chatMsg = '';\nlet macroActor = token.actor;\nlet Guided = macroActor.effects.find(i => i.data.label === \"Guided\")\nlet Guide = {\n changes: [\n {\n key: \"data.bonuses.abilities.check\",\n mode: 2,\n priority: 20,\n value: \"+1d4\",\n },\n ],\n duration: {\n seconds: 60,\n },\n icon: GuidIconPath,\n label: \"Guided\"\n}\n//identify token\nif (macroActor === undefined || macroActor === null) {\n ui.notifications.warn(\"Please select a token first.\");\n}\nelse {\n// If already guided\t\n if (Guided) {\n macroActor.deleteEmbeddedDocuments(\"ActiveEffect\", [Guided.id]);\n // anounce to chat\n chatMsg = `${macroActor.name} ${endGuideMsg}`;\n }\n // if not already guided\t\n else {\n macroActor.createEmbeddedDocuments(\"ActiveEffect\", [Guide]);\n // anounce to chat\n chatMsg = `${macroActor.name} ${GuideMsg}`;\n }\n // write to chat if needed:\n if (chatMsg !== '') {\n let chatData = {\n user: game.user._id,\n speaker: ChatMessage.getSpeaker(),\n content: chatMsg\n };\n ChatMessage.create(chatData, {});\n }\n}","folder":null,"sort":0,"permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"flags":{}}
{"name":"Bardic Insperation","type":"script","author":"wCsoNxA9I9elUaGF","img":"icons/svg/dice-target.svg","scope":"global","command":"let bardinsp_data = null;\n\n//Check if have Bardic Insperation\n\nif (canvas.tokens.controlled.length == 1){\n\t//console.log(canvas.tokens.controlled);\n\tlet owner_actor = canvas.tokens.controlled[0].actor;\n\n\tfor (let item in owner_actor.data.items){\n\t\tif (item.name == \"Bardic Inspiration\"){\n\t\t\tbardinsp_data = item;\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\n//Get the Target of Bardic Insperation\nif (canvas.tokens._hover != null){\n\tlet bardinsp_token = canvas.tokens._hover;\n\n\n\tconst effect = bardinsp_token.actor.effects.entries;\n\n\tbardinsp_token.toggleEffect(\"systems/dnd5e/icons/skills/yellow_08.jpg\");\n}","folder":null,"sort":0,"permission":{"default":0,"wCsoNxA9I9elUaGF":3},"flags":{"core":{"sourceId":"Macro.4Sdc0USpaZSuNJP7"}},"_id":"GuLzJbqUoQl7Jwak"}
-{"_id":"KWCb3y9I2P45LDWU","name":"Sneak Attack","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"//\t\tDISCLAIMER:\t\tThis macro is heavily based on the original D&D 5e Rage Macro masterwork written by Felix#6196.\n//\t\t\t\t\t\tNorc#5108 created and is maintaining this macro.\n//\n//\t\t\t\t\t\tUpdates:\t1.\t2020/06/05: Initial version.\n\n//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n//!!!\tBonus Tip: Sneak Attack as a Condition \n//!!!\tIf you use the Combat Utility Belt module's Condition Lab, try adding a condition called \"Sneaky\" with the same icon \t\t\t \n//!!!\tas the optional sneak attack icon overlay, 'icons/svg/mystery-man-black.svg' by default. See EXPERIMENTAL MACRO ICON/NAME TOGGLE below.\n//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n//!!! OPTIONAL TOKEN ICON-\tOn by default. If a path to a sneak attack icon is defined, it displays like a condition on the sneaking rogue.\n//!!!\t\t\t\t\t\t\tTo use a different icon, manually change the filepath below or leave it empty ('') to disable the effect.\n//!!!\nconst sneakIconPath = 'icons/svg/mystery-man-black.svg';\n//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n//!!!\tEXPERIMENTAL MACRO ICON/NAME TOGGLE\t\tIf enabled, the macro icon and name toggles based on whether the rogue is currently sneaking. \n//!!!\t\t\t\t\t\t\t\t\t\t\tCAUTIONS: \t1. \tThis feature is off by default and is intended for ADVANCED USERS ONLY. \n//!!!\t\t\t\t\t\t\t\t\t\t\t\t\t\t2. \tRequires configuration using \"The Furnace\" module for a player to run!\n//!!!\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tThe GM needs to grant The Furnace's \"Run as GM\" permission for this macro.\n//!!!\t\t\t\t\t\t\t\t\t\t\t\t\t\t3. \tWorks best with only one rogue using this feature at a time.\n\n\t\t\t\t//To auto-toggle the macro's icon/name, override toggleMacro to true below.\n\t\t\t\tconst toggleMacro = false;\n\n\t\t\t\t//To use a different icon, manually change the filepath here\n\t\t\t\tconst stopSneakIconPath = 'icons/svg/cowled.svg';\n\n\t\t\t\t//You must update the following constant to this macro's exact name for the macro icon toggling to work.\n\t\t\t\tconst sneakMacroName = 'Sneak Attack';\n//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\nlet toggleResult = false;\nlet enabled = false;\nlet errorReason = '';\nlet sneakAttack = {};\nlet rogue = {};\nlet rogueLvls = 0;\nlet sneakDice = 0;\nlet chatMsg = '';\nlet oldMDmg = '';\nlet oldRDmg = '';\n\nlet macroActor = actor;\nlet macroToken = token;\n\n//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n//!!!\tBASIC LOCALIZATION SUPPORT\t\t\t\tSets names of D&D5E features as constants instead of hardcoding to allow easier translation.\n//!!!\t\t\t\t\t\t\t\t\t\t\tSets error messages as constants also for easier translation.\n\n\t\t\t\tconst rogueClassName = 'Rogue';\n\t\t\t\tconst sneakAttackFeatureName = 'Sneak Attack';\n\n\t\t\t\tconst errorSelectRogue = 'Please select a single rogue token.';\n\t\t\t\tconst warnMacroNotFound = ' is not a valid macro name, please fix. Sneak attack toggle successful but unable to alter macro.';\n\t\t\t\tconst errorSelectToken = 'Please select a token.';\n//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n\n//check to ensure token is selected and attempt to define the sneak attack feature\nif (macroActor !== null && macroActor !== undefined) {\n\tsneakAttack = macroActor.items.find(i => i.name == `${sneakAttackFeatureName}`);\n} else {\nerrorReason = `${errorSelectToken}`;\n}\n\n//check to ensure token is a rogue\nif (errorReason == '' && macroActor.items.find(i => i.name == `${rogueClassName}`) !== null) {\n\trogue = macroActor.items.find(i => i.name == `${rogueClassName}`);\n} else {\n\terrorReason = `${errorSelectRogue}`;\n}\n\nconsole.log(`Error reason is: ${errorReason}`);\n//main execution now that errors are caught\n\nif (errorReason == '') {\n\t\n\tchatMsg = '';\n\tlet enabled = false;\n\t// store the state of the sneak attack toggle in flags\n\tif (macroActor.data.flags.sneakMacro !== null && macroActor.data.flags.sneakMacro !== undefined) {\n\t\tenabled = true;\n\t}\n\t\n\t// if sneak attack is active, disable it\n\tif (enabled) {\n\t\tchatMsg = `${macroActor.name} is no longer sneak attacking.`;\n\t\t// ranged and melee weapon attack bonus\n\t\tlet obj = {};\n\t\tobj['flags.sneakMacro'] = null;\t\t\n\t\tobj['data.bonuses.mwak.damage'] = macroActor.data.flags.sneakMacro.oldMDmg;\t\t\t\n\t\tobj['data.bonuses.rwak.damage'] = macroActor.data.flags.sneakMacro.oldRDmg;\t\n\t\tmacroActor.update(obj);\n\t\t\n\t// if sneak attack is disabled, enable it\n\t} else {\t\t\n\t\tchatMsg = `${macroActor.name} starts sneak attacking!`;\n\t\t\n\t\tlet obj = {};\n\t\tobj['flags.sneakMacro.enabled'] = true;\n\n\t\t// Preserve old mwak damage bonus if there was one\n\t\tlet oldMDmg = macroActor.data.data.bonuses.mwak.damage;\n\t\tif (oldMDmg==null || oldMDmg == undefined || oldMDmg == '') oldMDmg = 0;\n\t\tobj['flags.sneakMacro.oldMDmg'] = JSON.parse(JSON.stringify(oldMDmg));\n\n\t\t// Preserve old rwak damage bonus if there was one\n\t\tlet oldRDmg = macroActor.data.data.bonuses.rwak.damage;\n\t\tif (oldRDmg==null || oldRDmg == undefined || oldRDmg == '') oldRDmg = 0;\n\t\tobj['flags.sneakMacro.oldRDmg'] = JSON.parse(JSON.stringify(oldRDmg));\n\n\t\t\n\t\t// Determining the rogue level\n\t\trogueLvls = rogue.data.data.levels;\n\n\t\t// Formula to determine the sneak attack damage depending on rogue level\t\n\t\tsneakDice = Math.ceil(rogueLvls/2);\n\t\n\t\t//actually add the bonus sneak attack damage to the previous bonus damage\n\t\t//respect roll formulas if present.\n\t\tif (oldMDmg==null || oldMDmg == undefined || oldMDmg == '' || oldMDmg == 0) {\n\t\t\tobj['data.bonuses.mwak.damage'] = `${sneakDice}d6`;\n\t\t} else {\n\t\t\tobj['data.bonuses.mwak.damage'] = `${oldMDmg} + ${sneakDice}d6`;\n\t\t}\n\n\t\tif (oldRDmg==null || oldRDmg == undefined || oldRDmg == '' || oldRDmg == 0) {\n\t\t\tobj['data.bonuses.rwak.damage'] = `${sneakDice}d6`;\n\t\t} else {\n\t\t\tobj['data.bonuses.rwak.damage'] = `${oldRDmg} + ${sneakDice}d6`;\n\t\t}\t\n\n\t\tmacroActor.update(obj);\n\n\t}\t\n\t\n\t//mark or unmark character's token with Sneaky effect icon, if sneakIconPath is defined\n\t(async () => { \n\t\ttoggleResult = await macroToken.toggleEffect(sneakIconPath);\n\t\tif (toggleResult == enabled) macroToken.toggleEffect(sneakIconPath); \n\t})();\n\n\t//toggle macro icon and name, if enabled\n\tif (toggleMacro) {\n//\t\tNorc's preferred icons, not sure if publicly available\n//\t\tsneakyMacroImgPath = 'systems/dnd5e/icons/skills/shadow_17.jpg';\n//\t\tstopSneakIconPath = 'systems/dnd5e/icons/skills/yellow_11.jpg';\n\t\tlet sneakMacro = game.macros.getName(sneakMacroName);\n\t\t\t//Also check for name of macro in its \"off\" form\n\t\t\tif (sneakMacro == null || sneakMacro == undefined) {\n\t\t\t\tsneakMacro = game.macros.getName('Stop ' + sneakMacroName);\n\t\t\t}\n\t\tlet obj = {};\n\t\tif ( (sneakMacro !== null && sneakMacro !== undefined) && \n\t\t\t\t+ (stopSneakIconPath !== null && stopSneakIconPath !== undefined && stopSneakIconPath !== '') ) {\n\t\t\tif (enabled) {\n\t\t\tobj['img'] = sneakIconPath;\n\t\t\tobj['name'] = sneakMacroName;\n\t\t\t} else {\n\t\t\tobj['img'] = stopSneakIconPath;\n\t\t\tobj['name'] = 'Stop ' + sneakMacroName;\n\t\t\t}\n\t\t\tsneakMacro.update(obj);\n\t\t} else {\n\t\tui.notifications.warn(`${sneakMacroName} ${warnMacroNotFound}`);\t\t\t\n\t\t}\n\t}\n\n} else {\nui.notifications.error(`${errorReason}`);\t\n}\nif (chatMsg !== '') {\n\tlet chatData = {\n\t\tuser: game.user._id,\n\t\tspeaker: ChatMessage.getSpeaker(),\n\t\tcontent: chatMsg\n\t};\n\tChatMessage.create(chatData, {});\n}","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]}
-{"_id":"REqPUjyUyd8xvnS5","name":"Reroll Bad D20 With Modifiers","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"/**\n * Rolls a d20. If the roll is below a 3, it rerolls the value. Adds perception total + 1 as well.\n */\n\nlet dice = new Roll('1d20 + @skills.prc.total + 1').roll();\nif (dice.total <= (4 + actor.data.data.skills.prc.total)) dice.reroll();\ndice.toMessage();\n","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]}
{"_id":"S5cgrEqeevt0hTVy","name":"Initiative With Disadvantage","type":"script","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","scope":"global","command":"// Initiative with Disadvantage by Nulmas#9462\n// Thanks to Freeze#2689, vance#1935 and u/Azzu for the help.\n\n// This macro allows GMs and players to roll for Initiative with disadvantage when playing D&D 5e. Hopefully it won't be needed for long and the option for it will be added \n// to the system in a future release.\n\n// The macro will roll for all the selected tokens and add them to the combat if they aren't in it already. It will also check if you are using Dex as a tiebreaker and roll\n// accordingly.\n\n// BEWARE: If a token has already rolled for initiative and you use this macro with it selected, the new initiative will replace the old one. I considered changing this, but\n// decided it's worth keeping it this way in case a player or GM rolls for initiative without disadvantage by mistake.\n\n(async () => {\n if (canvas.tokens.controlled.length === 0) return ui.notifications.error(\"Choose tokens to roll for\");\n await canvas.tokens.toggleCombat();\n let chosenTokens = canvas.tokens.controlled;\n let tieBreakerCheck = game.settings.get(\"dnd5e\", \"initiativeDexTiebreaker\") ? 1 : 0; //Checks if Dex tiebreaker is being used\n let initiatives = chosenTokens.map(t => {\n let chosenActor = t.actor;\n let advantage = chosenActor.getFlag(\"dnd5e\", \"initiativeAdv\") ? 1 : 0;\n let init = chosenActor.data.data.attributes.init.total;\n let tieBreaker = chosenActor.data.data.abilities.dex.value/100;\n let roll = new Roll(`${2 - advantage}d20kl + ${init} + ${tieBreaker * tieBreakerCheck}`).roll({async: false});\n roll.toMessage({speaker: ChatMessage.getSpeaker({token: t.document})});\n let combatantId = t.combatant.id;\n return{\n _id: combatantId,\n initiative: roll.total,\n };\n });\n await game.combat.updateEmbeddedDocuments(\"Combatant\", initiatives);\n})();","folder":null,"sort":0,"permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"flags":{"core":{"sourceId":"Macro.vvUdCHKq60JcksaX"}}}
{"_id":"Tbg9DhQqgQQZdMGZ","name":"Heavy Armor Feat Workaround","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"//Crude but effective way to simulate Heavy Armor Master.\n//Every time the player takes eligible damage, they can just click this macro with their token selected to \"get their 3HP back.\"\n//Questions? Ask in #macro-polo on Discord. If absolutely needed, please ping Norc#5108.\n\n//Known minor limitation: Does not take into account temp HP AT ALL.\n\nfunction modifyHP(token, amount) {\n let hp_cur = token.actor.data.data.attributes.hp.value;\n let hp_max = token.actor.data.data.attributes.hp.max;\n let hp_min = token.actor.data.data.attributes.hp.min;\n hp_cur = (hp_cur+amount > hp_max) ? hp_max : hp_cur+amount;\n hp_cur = (hp_cur < hp_min) ? hp_min : hp_cur;\n token.actor.update({'data.attributes.hp.value': parseInt(hp_cur)});\n return hp_cur;\n }\n\nif(token) {\n //Note: Just change the number after the comma to heal/receive other HP values. Negative numbers indicate damage.\n modifyHP(token,3);\n} else {\n ui.notifications.notify(\"Please select a token.\");\n}6","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]}
{"_id":"VI7m4TbGvbs99h3O","name":"Bane","type":"script","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","scope":"global","command":"// new build for Bane macro by Penguin#0949 with help from Kotetsushin#7680\n// version beta 4.2.0\n\n// user notes\n// this macro is inteded for use by the recipient of the Bane spell in D&D 5e on Forge VTT\n// N.B. every recipient will need to use this macro independantly on their own Actor/token.\n\n//user modifiable declarations CHANGE AT YOUR OWN RISK\nconst baneIconPath = 'icons/svg/degen.svg';\nlet baneMsg = ' is Baned!';\nlet endbaneMsg = ' is no longer Baned.';\n\n//fixed declarations DO NOT MODIFY\nlet macroActor = token.actor;\nlet chatMsg = '';\nlet Baned = macroActor.effects.find(i => i.data.label === \"Baned\")\nlet bane = {\n changes: [\n {\n key: \"data.bonuses.mwak.attack\",\n mode: 2,\n priority: 20,\n value: \"-1d4\",\n },\n {\n key: \"data.bonuses.rwak.attack\",\n mode: 2,\n priority: 20,\n value: \"-1d4\",\n },\n\t\t{\n key: \"data.bonuses.msak.attack\",\n mode: 2,\n priority: 20,\n value: \"-1d4\",\n },\n\t\t{\n key: \"mdata.bonuses.rsak.attack\",\n mode: 2,\n priority: 20,\n value: \"-1d4\",\n },\n\t\t{\n key: \"data.bonuses.abilities.save\",\n mode: 2,\n priority: 20,\n value: \"-1d4\",\n },\n ],\n duration: {\n seconds: 60,\n },\n icon: baneIconPath,\n label: \"Baned\"\n}\n//identify token\nif (macroActor === undefined || macroActor === null) {\n ui.notifications.warn(\"Please select a token first.\");\n} \nelse {\n// If already bless\t\nif (Baned) {\n macroActor.deleteEmbeddedDocuments(\"ActiveEffect\", [Baned.id])\n// anounce to chat\n\tchatMsg = `${macroActor.name} ${endbaneMsg}`;\n}\n// if not already bless\t\nelse {\n macroActor.createEmbeddedDocuments(\"ActiveEffect\", [bane])\t\n// anounce to chat\n\t\tchatMsg = `${macroActor.name} ${baneMsg}`;\n}\n// write to chat if needed:\nif (chatMsg !== '') {\n\tlet chatData = {\n\t\tuser: game.user._id,\n\t\tspeaker: ChatMessage.getSpeaker(),\n\t\tcontent: chatMsg\n\t};\n\tChatMessage.create(chatData, {});\n}\n}","folder":null,"sort":0,"permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"flags":{}}
@@ -15,19 +14,9 @@
{"_id":"WhAe42txHIkeZk2s","name":"Lay On Hands","type":"script","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","scope":"global","command":"/**\n * System: D&D5e\n * Apply lay-on-hands feat to a target character. Asks the player how many HP to heal and\n * verifies the entered value is within range before marking down usage counter. If the player\n * has OWNER permissions of target (such as GM or self-heal) the HP are applied automatically; \n * otherwise, a 'roll' message appears allowing the target character to right-click to apply healing.\n */\n\n(async () => {\n\nconst layName = \"Lay on Hands\";\nlet confirmed = false;\nlet actorData = actor || canvas.tokens.controlled[0] || game.user.character;\nlet featData = actorData ? actorData.items.find(i => i.name===layName) : null;\n\nif(actorData == null || featData == null) \n ui.notifications.warn(`Selected hero must have ${layName} feat.`);\nelse if (game.user.targets.size !== 1)\n ui.notifications.warn(`Please target one token.`);\nelse\n{\n let featUpdate = duplicate(featData);\n let targetActor = game.user.targets.values().next().value.actor;\n let maxHeal = Math.clamped(featUpdate.data.uses.value, 0, \n targetActor.data.data.attributes.hp.max - targetActor.data.data.attributes.hp.value);\n\n let content = `
${actorData.name} lays hands on ${targetActor.data.name}.
\n
How many HP do you want to restore to ${targetActor.data.name}?
\n `;\n new Dialog({\n title: \"Lay on Hands Healing\",\n content: content, \n buttons: {\n heal: { label: \"Heal!\", callback: () => confirmed = true },\n cancel: { label: \"Cancel\", callback: () => confirmed = false }\n },\n default: \"heal\",\n\n close: html => {\n (async () => {\n if (confirmed) \n {\n let number = Math.floor(Number(html.find('#num')[0].value));\n if (number < 1 || number > maxHeal)\n ui.notifications.warn(`Invalid number of charges entered = ${number}. Aborting action.`);\n else\n {\n let flavor = `${html.find('#flavor')[0].value} `;\n if (targetActor.permission !== CONST.ENTITY_PERMISSIONS.OWNER)\n // We need help applying the healing, so make a roll message for right-click convenience.\n await new Roll(`${number}`).toMessage({\n speaker: ChatMessage.getSpeaker(),\n flavor: `${actorData.name} lays hands on ${targetActor.data.name}. ${flavor}\n
Manually apply ${number} HP of healing to ${targetActor.data.name}
` });\n else {\n // We can apply healing automatically, so just show a normal chat message.\n ChatMessage.create({\n speaker: ChatMessage.getSpeaker(),\n content: `${actorData.name} lays hands on ${targetActor.data.name} for ${number} HP. ${flavor}`\n });\n await targetActor.update({\"data.attributes.hp.value\" : targetActor.data.data.attributes.hp.value + number});\n }\n \n //Update the value under \"Features\"\n featUpdate.data.uses.value = featUpdate.data.uses.value - number;\n await actorData.items.getName(layName).update({ \"data.uses.value\" : featUpdate.data.uses.value });\n\n //Update resource counter only if the \"Lay on Hands\" feature is set to consume it\n let resString = featUpdate.data.consume.target;\n if(resString.indexOf('resources') >= 0) {\n await actorData.update({\n data: { [featUpdate.data.consume.target] : featUpdate.data.uses.value }\n });\n }\n\n if (actorData.sheet.rendered) {\n // Update the actor sheet if it is currently open\n await actorData.render(true);\n }\n };\n }\n })();\n }\n }).render(true);\n}\n})();","folder":null,"sort":0,"permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"flags":{}}
{"_id":"XuT5kNbIerT7CGoA","name":"Random Inspiration","type":"script","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","scope":"global","command":"// Courtesy of @Zarek\n// Selected target receives a random inspiration from a table called \"inspirations\".\n// You can find a table called inspirations in the community tables module\n\n// Setup variables\nlet tableName = \"Inspirations\";\n\nlet bardicInspiration = async() => {\n if (!actor) {\n ui.notifications.warn(\"You must have an actor selected.\");\n return\n }\n\n // Get Targets name\n let actorLevels = actor.data.data.levels || 1;\n const targetId = game.user.targets.ids[0];\n const targetToken = canvas.tokens.get(targetId);\n if (!targetToken) {\n ui.notifications.warn(\"You must target a token.\");\n return\n }\n const targetName = targetToken.name;\n\n\n let table = game.tables.contents.find(t => t.name == tableName);\n\n //default inspiration if no table is found.\n //let inspiration = \"Cowards die many times before their deaths; the valiant never taste death but once.\";\n let inspiration = `I don't know what effect ${targetName} will have upon the enemy, but, by God, he terrifies me.`;\n \n // Roll the result, and mark it drawn\n if (table) {\n if (checkTable(table)) {\n // let result = table.roll()[1];\n let roll = await table.roll();\n let result = roll.results[0];\n inspiration = result.data.text;\n await table.updateEmbeddedDocuments(\"TableResult\", [{\n _id: result.id,\n drawn: true\n }]);\n }\n }\n\n function checkTable(table) {\n let results = 0;\n for (let data of table.data.results) {\n if (!data.drawn) {\n results++;\n }\n }\n if (results < 1) {\n table.reset();\n ui.notifications.notify(\"Table Reset\")\n return false\n }\n return true\n }\n\n let dieType = 'd6';\n if (actorLevels >= 15) {\n dieType = 'd12';\n } else if (actorLevels >= 10) {\n dieType = 'd10';\n } else if (actorLevels >= 5) {\n dieType = 'd8';\n }\n\n let messageContent = '';\n messageContent += `
${targetName} gains one Bardic Inspiration die, a ${dieType}. Once within the next 10 minutes, ${targetName} can roll the die and add the number rolled to one ability check, attack roll, or saving throw. ${targetName} can wait until after it rolls the d20 before deciding to use the Bardic Inspiration die, but must decide before the DM says whether the roll succeeds or fails. Once the Bardic Inspiration die is rolled, it is lost.
`\n\n // create the message\n if (messageContent !== '') {\n let chatData = {\n user: game.user.id,\n speaker: ChatMessage.getSpeaker(),\n content: messageContent,\n };\n ChatMessage.create(chatData, {});\n }\n};\nbardicInspiration();","folder":null,"sort":0,"permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"flags":{}}
{"_id":"bUEeDjvYU5xEtZAr","name":"Random Mockeries","type":"script","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","scope":"global","command":"// Courtesy of @Zarek\n// Selected target receives a random mockery from a table called \"mockeries\" along with the DC and damage.\n// You can find a table called mockeries in the community tables module.\n\n\nlet tableName = \"mockeries\";\n// default mockery if no table found.\nlet mockery = \"Now go away or I shall taunt you a second time-a!\";\n\nlet viciousMockeries = async () => {\n if (!actor) {\n ui.notifications.warn(\"You must have an actor selected.\");\n return\n }\n\n let actorLevels = actor.data.data.levels || 1;\n let table = game.tables.contents.find(t => t.name == tableName);\n\n // Get Targets name\n const targetId = game.user.targets.ids[0];\n const targetToken = canvas.tokens.get(targetId);\n if (!targetToken) {\n ui.notifications.warn(\"You must target a token.\");\n return\n }\n const targetName = targetToken.name;\n\n // Roll the result, and mark it drawn\n if (table) {\n if (checkTable(table)) {\n let roll = await table.roll();\n let result = roll.results[0];\n mockery = result.data.text;\n await table.updateEmbeddedDocuments(\"TableResult\", [{\n _id: result.id,\n drawn: true\n }]);\n }\n }\n\n function checkTable(table) {\n let results = 0;\n for (let data of table.data.results) {\n if (!data.drawn) {\n results++;\n }\n }\n if (results < 1) {\n table.reset();\n ui.notifications.notify(\"Table Reset\")\n return false\n }\n return true\n }\n\n // Add a message with damage roll\n let numDie = 1;\n if (actorLevels >= 17) {\n numDie = 4;\n } else if (actorLevels >= 11) {\n numDie = 3;\n } else if (actorLevels >= 5) {\n numDie = 2;\n }\n\n let messageContent = `
${targetName} Roll WIS save DC [[8+${actor.data.data.abilities.cha.mod}+@attributes.prof]] or take [[${numDie}d4]] damage and have disadvantage.
You unleash a string of insults laced with subtle enchantments at a creature you can see within range. If the target can hear you (though it need not understand you), it must succeed on a Wisdom saving throw or take 1d4 psychic damage and have disadvantage on the next attack roll it makes before the end of its next turn.
\n
This spell’s damage increases by 1d4 when you reach 5th level ([[/r 2d4]]), 11th level ([[/r 3d4]]), and 17th level ([[/r 4d4]]).
`\n\n // create the message\n if (messageContent !== '') {\n let chatData = {\n user: game.user.id,\n speaker: ChatMessage.getSpeaker(),\n content: messageContent,\n };\n ChatMessage.create(chatData, {});\n }\n};\n\nviciousMockeries();","folder":null,"sort":0,"permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"flags":{}}
-{"_id":"gKnoce2uTbOT9FBe","name":"Sharpshooter","type":"script","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","scope":"global","command":"if (!Boolean(actor)) return ui.notifications.warn(\"Please select a token.\");\nif (!Boolean(actor.items.find(i => i.name === 'Sharpshooter'))) return ui.notifications.warn(\"Please select a single token with the Sharpshooter feat.\");\n\nlet atkModifier = -5;\nlet dmgModifier = 10;\nconst isEnabled = Boolean(actor.data.flags.ssMacro?.isEnabled);\n\nconst disableSharpshooter = (item) => item.update({\n 'data.damage.parts': item.data.flags.ssMacro.oldDmg,\n 'data.attackBonus': item.data.flags.ssMacro.oldAtk ?? 0,\n 'flags.ssMacro': null\n});\n\nconst enableSharpshooter = (item) => {\n let oldDmg = JSON.parse(JSON.stringify(item.data.data.damage.parts));\n item.data.data.damage.parts[0][0] = `${item.data.data.damage.parts[0][0]} + ${dmgModifier}`;\n\n item.update({\n 'flags.ssMacro.oldDmg': oldDmg,\n 'flags.ssMacro.oldAtk': JSON.parse(JSON.stringify(item.data.data.attackBonus ?? 0)),\n 'data.damage.parts': item.data.data.damage.parts,\n 'data.attackBonus': `${+(item.data.data.attackBonus || 0) + atkModifier}`\n });\n}\n\nactor.update({ 'flags.ssMacro.isEnabled': !isEnabled });\nfor (let item of actor.items) {\n let isRangedWeapon = getProperty(item, 'data.data.actionType') === 'rwak' && getProperty(item, 'data.type') === 'weapon';\n\n if (isRangedWeapon && item.data.data.damage.parts.length > 0)\n isEnabled ? disableSharpshooter(item) : enableSharpshooter(item);\n}\n\nChatMessage.create({\n user: game.user._id,\n speaker: ChatMessage.getSpeaker(),\n content: isEnabled ? `${actor.name} is aiming normally now.` : `${actor.name} is aiming for vital areas.`,\n}, {});","folder":null,"sort":0,"permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"flags":{}}
-{"_id":"jm7QjDGeMrkhLrLQ","name":"Fighter Second Wind","type":"script","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","scope":"global","command":"/**\n * Macro for the Second Wind feature of Fighter.\n * Works with an active Fighter actor, prints a ui error otherwise.\n *\n * Requires a \"Second Wind\" resource, and will decrement it automatically.\n * Prints a chat message, rolls the heal amount, and updates the actor sheet appropriately.\n *\n * Inspired largely by the rage macro for 5e by Norc#5108\n */\n\n// TODO: Add additional configuration options and overrides.\n\nlet fighter = \"\";\nlet chatMsg = \"\";\nlet macroActor = actor;\nlet macroToken = token;\n\nconst fighterClassName = \"Fighter\";\nconst secondWindResourceName = \"Second Wind\";\n\nconst secondWindMessage = \"takes a deep breath and heals for\";\n\nconst errorSelectFighter = \"Please select a single fighter token\";\nconst errorNoSecondWind =\n \"does not have any second wind left, time for a rest!\";\n\n/**\n * Optional Resource Reduction\n */\nconst resourceDeduction = true;\nconst preventNegative = true;\n\nif (macroActor !== undefined && macroActor !== null) {\n fighter = macroActor.items.find((i) => i.name === `${fighterClassName}`);\n // Early error if not a fighter\n if (fighter === undefined) errorMsg(errorSelectFighter);\n\n // Logic if selected actor is a fighter\n if (fighter !== undefined && fighter !== null) {\n // Check for available second wind resource\n if (checkResource(macroActor)) {\n // Calculate string to roll based on fighter level\n let fighterLvl = fighter.data.data.levels;\n let healRoll = await new Roll(`1d10 + ${fighterLvl}`).roll();\n\n ChatMessage.create({\n user: game.user.id,\n speaker: ChatMessage.getSpeaker({\n token: actor,\n }),\n content: `${macroActor.name} ${secondWindMessage} ${healRoll.total}`,\n });\n\n updateHP(macroActor, healRoll.total);\n }\n }\n}\n\n/**\n * Updates the hp value of the actor sheet\n *\n * @param {Actor} actor active actor calling the macro\n * @param {Number} amt amount to add to current hp\n * @return {Number} actor sheet current hp value\n */\nfunction updateHP(actor, amt) {\n let { attributes } = actor.data.data;\n let cur_hp = attributes.hp.value;\n let max_hp = attributes.hp.max;\n let min_hp = attributes.hp.min;\n\n cur_hp = Math.min(cur_hp + amt, max_hp);\n cur_hp = Math.max(cur_hp, min_hp);\n actor.update({\n \"data.attributes.hp.value\": parseInt(cur_hp),\n });\n return cur_hp;\n}\n\n/**\n * Checks the resource of the actor sheet\n *\n * @param {Actor} actor active actor calling the macro\n * @return {Boolean} true if resource available or deduction disabled, false otherwise\n */\nfunction checkResource(actor) {\n if (resourceDeduction) {\n const { resources } = actor.data.data;\n let hasResource = false;\n let newResources = duplicate(resources);\n let obj = {};\n // Look for resources under core actor data\n let resourceKey = Object.keys(resources)\n .filter((key) => resources[key].label === `${secondWindResourceName}`)\n .shift();\n if ((resourceKey && resources[resourceKey].value > 0) || !preventNegative) {\n hasResource = true;\n newResources[resourceKey].value--;\n obj[\"data.resources\"] = newResources;\n actor.update(obj);\n return true;\n }\n if (!hasResource) {\n ui.notifications.error(`${actor.name} ${errorNoSecondWind}`);\n return false;\n }\n }\n return true;\n}","folder":null,"sort":0,"permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"flags":{}}
-{"name":"Speed Factor Initiative","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{"core":{"sourceId":"Macro.R3WnKSOQFZnuZrTf"}},"scope":"global","command":"/*\n* this macro rolls initiative by using the rules for the optional rule \"Speed factor initiative\", available in the Dungeon Master's Guide at page 270\n* select the tokens for which you want to roll initiative and click the macro. \n* Alternatively, if you have an actor assigned and no token selected, the macro will roll initiative for your assigned actor\n* the macro does not roll initiative for actors not in combat\n* requires DnD5e Game System\n* created by Gabro#4634, with the invaluable help of Freeze#2689\n*/\n\n\n//List of actions with their relative bonuses\nvar actions = {\n\t\"Medium Action (+0)\" : +0,\n\t\"Melee, Heavy Weapon (-2)\" : -2,\n\t\"Melee, Light or Finesse Weapon (+2)\" : +2,\n\t\"Melee, Two-Handed Weapon (-2)\" : -2,\n\t\"Ranged, Loading Weapon (-5)\" : -5,\n\t\"Spellcasting, 1st level (-1)\" : -1,\n\t\"Spellcasting, 2nd level (-2)\" : -2,\n\t\"Spellcasting, 3rd level (-3)\" : -3,\n\t\"Spellcasting, 4th level (-4)\" : -4,\n\t\"Spellcasting, 5th level (-5)\" : -5,\n\t\"Spellcasting, 6th level (-6)\" : -6,\n\t\"Spellcasting, 7th level (-7)\" : -7,\n\t\"Spellcasting, 8th level (-8)\" : -8,\n\t\"Spellcasting, 9th level (-9)\" : -9\n//optional rules as suggested by AngryDM (https://theangrygm.com/fine-i-wrote-about-speed-factor-initiative-in-dd-5e/), remove them if you don't want them\n ,\"Very Slow Action (-5)\" : -5,\n\t\"Slow Action (-2)\" : -2,\n\t\"Fast Action (+2)\" : +2,\n\t\"Very Fast Action (+5)\" : +5\n}\n\n// this option, as it is, disables the possibility to modify the box relative to the size and dex initiative bonus. If you want to modify them, change it to:\n// var disableTexts = ''\nvar disableTexts = 'disabled=\"disabled\"'\n\n\n// list of sizes present in 5e, optionally it's possible to modify the bonuses (but not the size names \"tiny\", \"sm\", etc)\nvar sizes = {\n \"tiny\" : +5,\n \"sm\" : +2,\n \"med\" : +0,\n \"lg\" : -2,\n \"huge\" : -5,\n \"grg\" : -8\n};\n\nvar options = new Array ()\nfor (var key in actions) {\noptions.push(key)\n}\n\nif (canvas.tokens.controlled.length >= 1) {\n\t(async ()=>{\n\t\tfor (let token of canvas.tokens.controlled) {\n\t\t\tlet combatant = game.combats.active.combatants.find(c => c.tokenId === token.id)\n\t\t\tif (combatant == null){\n\t\t\t\tconsole.log(token.name + \" is not in combat\")\n\t\t\t} else {\n\t\t\t\tlet data = [\n\t\t\t\t{type : `text`, label : `Base modifier : `, options : `${token.actor.data.data.attributes.init.total}` },\n\t\t\t\t{type : `text`, label : `Size modifier : `, options : `${sizes[token.actor.data.data.traits.size]}` },\n\t\t\t\t{type : `select`, label : `Action : `, options}\n\t\t\t];\n let rv = await quick_dialog({data}, combatant.name);\n\t\t\t\trollInit(token, rv)\n\t\t\t}\n\t\t}\n\t})();\n} else {\n\t(async ()=>{\n\t\tif (game.user.character == null){\n\t\t\tconsole.log(\"player has no player selected in player configuration menu\")\n\t\t} else {\n\t\t\tfor (let token of game.user.character.getActiveTokens()) {\n\t\t\t\tlet combatant = game.combats.active.combatants.find(c => c.tokenId === token.id)\n\t\t\t\tif (combatant == null){\n\t\t\t\t\tconsole.log(token.name + \" is not in combat\")\n\t\t\t\t} else {\n\t\t\t\t\tlet data = [\n\t\t\t\t\t{type : `text`, label : `Base modifier : `, options : `${token.actor.data.data.attributes.init.total}` },\n\t\t\t\t\t{type : `text`, label : `Size modifier : `, options : `${sizes[token.actor.data.data.traits.size]}` },\n\t\t\t\t\t{type : `select`, label : `Action : `, options}\n\t\t\t\t\t];\n\t\t\t\t\tlet rv = await quick_dialog({data}, combatant.name);\n\t\t\t\t\trollInit(token, rv)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t})();\n}\n\n\n\nasync function quick_dialog({data, title = `Select your action for `} = {}, name)\n{\n data = data instanceof Array ? data : [data];\n\n let value = await new Promise((resolve) => {\n let content = `\n
\n\t\t`\n\n\tfor (var i = 0; i < dicearray.length; i++) {\n\t\tif (dicearray[i].terms[0].results[0].result == 1) {\n\t\t\toutput += `
${dicearray[i].total}
`\n\t\t}\n\t\telse if (dicearray[i].terms[0].results[0].result == dicearray[i].dice[0].faces){\n\t\t\toutput += `
${dicearray[i].total}
`\n\t\t}\n\t\telse{\n\t\t\toutput += `
${dicearray[i].total}
`\n\t\t}\n\t}\n\n\toutput += `\n\t
\n
`\n\n\treturn output\n}\n\n\n\n\n\n//Launch Dialog message with a counter from 1-10 for the number of dice to roll\nnew Dialog({\n\ttitle: `Animate Objects (tiny)`,\n\tcontent: `\n\t\t\n\t`,\n\tbuttons: {\n\t\tyes: {\n\t\t\ticon: \"\",\n\t\t\tlabel: `Attack`,\n\t\t\tcallback: (html) => {\n\t\t\t\tlet count = html.find('#output-options').val();\n\t\t\t\tlet rolls = [];\n\t\t\t\tlet debugOutput = \"\";\n\n\t\t\t\t//Do attack Rolls\n\t\t\t\tfor (var i = 0; i < count; i++) {\n\t\t\t\t\tlet roll = new Roll(`1d20+8`);\n\n\t\t\t\t\trolls.push(roll.evaluate());\n\t\t\t\t\t//console.log(rolls);\n\t\t\t\t\t//console.log(rolls[rolls.length - 1].terms);\n\n\t\t\t\t\tdebugOutput = debugOutput.concat(rolls[rolls.length - 1].total);\n\t\t\t\t\tdebugOutput = debugOutput.concat(\" \");\n\t\t\t\t}\n\t\t\t\t//console.log(debugOutput);\n\t\t\t\tprintMessage(\"Animated Weapons (tiny) Attacks: \" + OutputManySameDice(rolls));\n\n\t\t\t}\n\t\t},\n\t\tno: {\n\t\t\ticon: \"\",\n\t\t\tlabel: `Cancel`\n\t\t},\n\t},\n\tdefault: \"yes\"\n}).render(true)","folder":null,"sort":0,"permission":{"default":0,"wCsoNxA9I9elUaGF":3},"flags":{"core":{"sourceId":"Macro.YnunhSHB2aQOFWYz"}},"_id":"jnZrWkjzZIIXnvON"}
{"_id":"mzvF9reAuonv5d4a","name":"Divine Smite","type":"script","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","scope":"global","command":"/*\n * The Smite macro emulates the Divine Smite feature of Paladins in DnD 5e. A spell slot level to use\n * can be selected, which increases the number of damage dice, and smiting a fiend or undead\n * will also increase the number of damage dice.\n * \n * If a token is not selected, the macro will default back to the default character for the Actor. \n * This allows for the GM to cast the macro on behalf a character that possesses it, \n * without requiring that a PC have their character selected.\n * To execute the macro a target MUST be specified and, unless configured otherwise, the character must have an available spell slot. \n * Make your regular attack and then if you choose to use Divine Smite, run this macro.\n */\n\n(() => {\n\n//Configurable variables\nlet maxSpellSlot = 5; // Highest spell-slot level that may be used.\nlet affectedCreatureTypes = [\"fiend\", \"undead\", \"undead (shapechanger)\"]; // Creature types that take extra damage.\n\n// Use token selected, or default character for the Actor if none is.\nlet s_actor = canvas.tokens.controlled[0]?.actor || game.user.character; \n\n// Flag for selected slot type\nlet pactSlot = false;\n\n// Verifies if the actor can smite.\nif (s_actor?.data.items.find(i => i.name === \"Divine Smite\") === undefined){\n return ui.notifications.error(`No valid actor selected that can use this macro.`);\n}\n\nlet confirmed = false;\nif (hasAvailableSlot(s_actor)) {\n\n // Get options for available slots\n let optionsText = \"\";\n let i = 1;\n for (; i < maxSpellSlot; i++) {\n const slots = getSpellSlots(s_actor, i, false);\n if (slots.value > 0) {\n const level = CONFIG.DND5E.spellLevels[i];\n const label = game.i18n.format('DND5E.SpellLevelSlot', {level: level, n: slots.value});\n optionsText += ``;\n }\n }\n\n // Check for Pact slot\n const slots = getSpellSlots(s_actor, 0, true);\n if(slots.value > 0) {\n i++;\n const level = CONFIG.DND5E.spellLevels[slots.level];\n const label = 'Pact: ' + game.i18n.format('DND5E.SpellLevelSlot', {level: level, n: slots.value});\n optionsText += ``;\n }\n\n // Create a dialogue box to select spell slot level to use when smiting.\n new Dialog({\n title: \"Divine Smite: Usage Configuration\",\n content: `\n \n `,\n buttons: {\n one: {\n icon: '',\n label: \"SMITE!\",\n callback: () => confirmed = true\n },\n two: {\n icon: '',\n label: \"Cancel\",\n callback: () => confirmed = false\n }\n },\n default: \"Cancel\",\n close: html => {\n if (confirmed) {\n let slotLevel = parseInt(html.find('[name=slot-level]')[0].value);\n if(slotLevel > maxSpellSlot) {\n slotLevel = actor.data.data.spells.pact.level;\n pactSlot = true;\n }\n const criticalHit = html.find('[name=criticalCheckbox]')[0].checked;\t\t\t\t\n const consumeSlot = html.find('[name=consumeCheckbox]')[0].checked;\n smite(s_actor, slotLevel, criticalHit, consumeSlot, pactSlot);\n }\n }\n }).render(true);\n\n} else {\n return ui.notifications.error(`No spell slots available to use this feature.`); \n}\n\n/**\n * Gives the spell slot information for a particular actor and spell slot level.\n * @param {Actor5e} actor - the actor to get slot information from.\n * @param {integer} level - the spell slot level to get information about. level 0 is deprecated.\n * @param {boolean} isPact - whether the spell slot is obtained through pact.\n * @returns {object} contains value (number of slots remaining), max, and override.\n */\nfunction getSpellSlots(actor, level, isPact) {\n if(isPact == false) {\n return actor.data.data.spells[`spell${level}`];\n }\n else {\n return actor.data.data.spells.pact;\n }\n}\n\n/**\n * Returns whether the actor has any spell slot left.\n * @param {Actor5e} actor - the actor to get slot information from.\n * @returns {boolean} True if any spell slots of any spell level are available to be used.\n */\n function hasAvailableSlot(actor) {\n for (let slot in actor.data.data.spells) {\n if (actor.data.data.spells[slot].value > 0) {\n return true;\n }\n }\n return false;\n }\n\n/**\n * Use the controlled token to smite the targeted token.\n * @param {Actor5e} actor - the actor that is performing the action.\n * @param {integer} slotLevel - the spell slot level to use when smiting.\n * @param {boolean} criticalHit - whether the hit is a critical hit.\n * @param {boolean} consume - whether to consume the spell slot.\n * @param {boolean} isPact - whether the spell slot used is obtained through pact.\n */\nasync function smite(actor, slotLevel, criticalHit, consume, isPact) {\n let targets = game.user.targets;\n\n let chosenSpellSlots = getSpellSlots(actor, slotLevel, isPact);\n\n if (chosenSpellSlots.value < 1) {\n ui.notifications.error(\"No spell slots of the required level available.\");\n return;\n }\n if (targets.size !== 1) {\n ui.notifications.error(\"You must target exactly one token to Smite.\");\n return;\n }\n\n const [target] = targets;\n let numDice = slotLevel + 1;\n let type = target.actor.data.data.details.type.value?.toLocaleLowerCase();\n if (affectedCreatureTypes.includes(type)) numDice += 1;\n if (criticalHit) numDice *= 2;\n const flavor = `Macro Divine Smite - ${game.i18n.localize(\"DND5E.DamageRoll\")} (${game.i18n.localize(\"DND5E.DamageRadiant\")})`;\n let damageRoll = new Roll(`${numDice}d8`);\n\n let targetActor = game.user.targets.values().next().value.actor;\n \n if (targetActor.permission !== CONST.ENTITY_PERMISSIONS.OWNER) {\n // We need help applying the damage, so make a roll message for right-click convenience.\n await damageRoll.toMessage({\n speaker: ChatMessage.getSpeaker(),\n flavor: `${actor.name} smited ${targetActor.data.name}. ${flavor}\n
Manually apply (or right-click) ${damageRoll.result} HP of damage to ${targetActor.data.name}
` });\n }\n else {\n // We can apply damage automatically, so just show a normal chat message.\n await damageRoll.toMessage({\n speaker: ChatMessage.getSpeaker(),\n flavor: `${actor.name} smited ${targetActor.data.name}. ${flavor}\n
${targetActor.data.name} has taken ${damageRoll.result} HP of damage.
` });\n targetActor.update({\"data.attributes.hp.value\" : targetActor.data.data.attributes.hp.value - damageRoll.result});\n }\n\n if (consume){\n let objUpdate = new Object();\n if(isPact == false) {\n objUpdate['data.spells.spell' + slotLevel + '.value'] = chosenSpellSlots.value - 1;\n }\n else {\n objUpdate['data.spells.pact.value'] = chosenSpellSlots.value - 1;\n }\n \n actor.update(objUpdate);\n }\n}\n\n})();","folder":null,"sort":0,"permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"flags":{}}
{"_id":"sjMYdsr3UapvE20H","name":"Show Token Actions","type":"script","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","scope":"global","command":"/*\n* Requires: DND5e.\n* Provides a dialog showing all action-triggered equipment, prepared and at-will spells, feats, and consumables,\n* as well as passive feats. Hopefully makes triggering actions easier without needing the character sheet open\n* all the time.\n* WARNING: Very ugly.\n* author/blame: ^ and stick#0520\n* with enormous help on the button events (and no blame to be attributed to): Skimble#8601\n*/\n\nclass ActionDialog extends Application {\n super(options){\n }\n\n activateListeners(html) {\n super.activateListeners(html);\n const buttons = html.find(\"button[class='show-action-button']\");\n\n if (buttons.length > 0)\n buttons.on(\"click\", event => {this.openActionTab(event, html);});\n }\n\n openActionTab(event, html) {\n // Declare all variables\n var i, tabcontent, tablinks;\n\n // Get all elements with class=\"tabcontent\" and hide them\n tabcontent = document.getElementsByClassName(\"show-action-category\");\n for (let t of tabcontent) {\n t.style.display = \"none\";\n }\n\n // Get all elements with class=\"tablinks\" and remove the class \"active\"\n tablinks = document.getElementsByClassName(\"show-action-button\");\n for (let t of tablinks) {\n t.className = t.className.replace(\" active\", \"\");\n }\n\n // Show the current tab, and add an \"active\" class to the button that opened the tab\n if (event.target.value == \"showActionAll\") {\n tabcontent = document.getElementsByClassName(\"show-action-category\");\n for (let t of tabcontent) {\n t.style.display= \"block\";\n }\n } else {\n if (document.getElementById(event.target.value) != null)\n document.getElementById(event.target.value).style.display = \"block\";\n }\n event.currentTarget.className += \" active\";\n }\n\n getData(){\n // Get user's character or the first token from the controlled list.\n function getTargetActor() {\n const character = game.user.character;\n if (character != null)\n return character;\n\n const controlled = canvas.tokens.controlled;\n\n if (controlled.length === 0) return character || null;\n\n if (controlled.length > 0 && controlled[0] != null) {\n return controlled[0].actor;\n }\n }\n\n function buildActionsList(targetActor) {\n let equipped = targetActor.data.items.filter(i => i.type !=\"consumable\" && getProperty(i.data, \"data.equipped\"));\n let activeEquipped = getActiveEquipment(equipped);\n let weapons = activeEquipped.filter(i => i.type == \"weapon\");\n let equipment = activeEquipped.filter(i => i.type == \"equipment\");\n\n let other = activeEquipped.filter(i => i.type != \"weapon\" && i.type != \"equipment\");\n let consumables = targetActor.data.items.filter(i => i.type == \"consumable\");\n let items = { \"weapons\": weapons, \"equipment\": equipment, \"other\": other, \"consumables\": consumables };\n\n let preparedSpells = targetActor.data.items.filter(i => i.type == \"spell\" && getProperty(i.data, \"data.preparation.prepared\"));\n let spells = categoriseSpells(preparedSpells);\n\n let allFeats = targetActor.data.items.filter(i => i.type == \"feat\");\n let activeFeats = getActiveFeats(allFeats);\n let passiveFeats = getPassiveFeats(allFeats);\n let feats = {\"active\": activeFeats, \"passive\": passiveFeats};\n\n\n return { \"equipment\": items,\"spells\": spells, \"feats\": feats };\n }\n\n function getActiveEquipment(equipment) {\n const activationTypes = Object.entries(game.dnd5e.config.abilityActivationTypes);\n\n let activeEquipment = equipment.filter(e => {\n if (getProperty(e.data, \"data.activation\") == undefined)\n return false;\n\n for (let [key, value] of activationTypes) {\n if (getProperty(e.data, \"data.activation.type\") == key)\n return true;\n }\n\n return false;\n });\n\n return activeEquipment;\n }\n\n function categoriseSpells(spells) {\n let powers = {};\n let book = {}\n\n book = spells.reduce(function (book, spell) {\n var level = getProperty(spell.data, \"data.level\");\n let prep = getProperty(spell.data, \"data.preparation.mode\");\n\n const prepTypes = game.dnd5e.config.spellPreparationModes;\n let prepType = prepTypes[prep];\n\n if (prep == \"pact\" || prep == \"atwill\" || prep == \"innate\") {\n if (!powers.hasOwnProperty(prepType)) {\n powers[prepTypes[prep]] = [];\n }\n\n powers[prepType].push(spell);\n } else {\n if (!book.hasOwnProperty(level)) {\n book[level] = [];\n }\n\n book[level].push(spell);\n }\n\n return book;\n }, {});\n\n return {\"book\": Object.entries(book), \"powers\": Object.entries(powers)};\n }\n\n function getActiveFeats(feats) {\n const activationTypes = Object.entries(game.dnd5e.config.abilityActivationTypes);\n let activeFeats = feats.filter(f => {\n if (getProperty(f.data, \"data.activation\") == undefined)\n return false;\n\n for (let [key, value] of activationTypes) {\n if (getProperty(f.data, \"data.activation.type\") == key)\n return true;\n }\n\n return false;\n });\n\n return Object.entries(activeFeats);\n }\n\n function getPassiveFeats(feats) {\n const activationTypes = Object.entries(game.dnd5e.config.abilityActivationTypes);\n let passiveFeats = feats.filter(f => {\n if (getProperty(f.data, \"data.activation\") == undefined)\n return false;\n\n for (let [key, value] of activationTypes) {\n if (getProperty(f.data, \"data.activation\") == key)\n return false;\n }\n\n return true;\n });\n\n return Object.entries(passiveFeats);\n }\n\n function getContentTemplate(actions) {\n let template = `\n
\n ${getCssStyle()}\n
\n
\n \n \n \n \n
\n
\n
\n
\n ${getItemsTemplate(actions.equipment)}\n
\n
\n ${getSpellsTemplate(actions.spells)}\n
\n ${getFeatsTemplate(actions.feats)}\n
\n
\n
\n
`;\n\n return template;\n }\n\n // Gets a template of abilities or skills, based on the type of check chosen.\n function getItemsTemplate(items) {\n if (items.weapons.length + items.equipment.length + items.other.length + items.consumables.length === 0)\n return \"\";\n\n let template = `
\n `;\n\n return template;\n }\n\n function getItemsCategoryTemplate(title, items) {\n if (items.length === 0)\n return \"\";\n\n let template = `
${title}
\n
`;\n for (let i of items) {\n template += ``;\n }\n\n template += `
`;\n\n return template;\n }\n\n function getSpellsCategoryTemplate(spells) {\n if (spells.length === 0)\n return \"\";\n\n let template = \"\";\n\n for (let [level, entries] of spells) {\n console.log(!isNaN(level.toString()));\n let subtitle = isNaN(level) ? level : (level.toString() === '0' ? `Cantrips` : `Level ${level}`);\n\n template += `
${subtitle}
\n
`;\n\n for (let s of entries) {\n template += ``;\n }\n\n template += `
`;\n }\n\n return template;\n }\n\n function getFeatsCategoryTemplate(subtitle, feats) {\n if (feats.length === 0)\n return \"\";\n\n let template = `
${subtitle}
\n
`\n\n for (let [index, f] of feats) {\n template += ``;\n }\n\n template += `
`\n\n\n return template;\n }\n\n function getCssStyle() {\n return `\n `\n }\n\n function getRollItemMacro(itemName) {\n return `game.dnd5e.rollItemMacro("${itemName}")`;\n }\n\n // set this to true if you want results whispered to the GM\n let targetActor = getTargetActor();\n let innerContent = \"\";\n\n if (targetActor != null || targetActor) {\n this.options.title = `${targetActor.name} actions`;\n let actionLists = buildActionsList(targetActor);\n innerContent = getContentTemplate(actionLists);\n } else {\n ui.notifications.error(\"No token selected or user character found.\");\n throw new Error(\"No token selected or character found\");\n }\n\n var content = `
${innerContent}
`;\n var contentsObject = {content:`${content}`}\n return contentsObject;\n }\n}\n\nlet opt=Dialog.defaultOptions;\nopt.resizable=true;\nopt.title=\"Choose action\";\nopt.minimizable=true;\nopt.width=600;\nvar viewer;\nviewer = new ActionDialog(opt);\nviewer.render(true);","folder":null,"sort":0,"permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"flags":{}}
{"_id":"tzvu5zxY5CiYYpZU","name":"Wild Magic","type":"script","author":"wCsoNxA9I9elUaGF","img":"icons/svg/dice-target.svg","scope":"global","command":"function printMessage(message){\n let chatData = {\n user : game.user.id,\n content : message,\n blind: true,\n whisper : game.users.filter(u => u.isGM).map(u => u.id)\n };\n\n ChatMessage.create(chatData,{}); \n}\n\n\nconst roll = new Roll(`1d20`);\nlet result = await roll.roll();\n\nif (result.total == 1) {\n printMessage('
Wild magic has been triggered.
');\n}\nelse{\n printMessage(\"Wild magic was not triggered on a \" + result.total);\n}","folder":null,"sort":0,"permission":{"default":0,"wCsoNxA9I9elUaGF":3},"flags":{"core":{"sourceId":"Macro.EQcDS69pG3R7vQhL"}}}
-{"name":"Great Weapon Master","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"/*\nCreated Monkan#8752 with guidance from the Rage macro in the FVTT Community Macros\n\nTips to make it work\n 1 - Have a feature called 'Great Weapon Master' for your character.\n 2 - Make sure you have your weapons with Heavy property filled out. \n 3 - if you make any changes to your damage or attack calculations, make sure you toggle it off.\n As it stores the old values to replace once you disable the feat. It could undo your changes.\n*/\n\nlet gwm='';\nlet chatMsg='';\n\n\nif (actor !== undefined && actor !== null) {\n // find the feat Great Weapon Master\n gwm = actor.items.find(i => i.name === 'Great Weapon Master');\n if (gwm == undefined) { \n ui.notifications.warn(\"Please select a single token with the Great Weapon Master feat.\"); \n }\n\n if (gwm !== undefined && gwm !== null) {\n\t\tchatMsg = '';\n\t\tlet enabled = false;\n\t\t// store the state of the GWM toggle in flags\n\t\tif (actor.data.flags.gwmMacro !== null && actor.data.flags.gwmMacro !== undefined) {\n\t\t\tenabled = true;\n\t\t}\n\t\t// if GWM is active, disable it\n\t\tif (enabled) {\n chatMsg = `${actor.name} is swinging Normally now.`;\n \n let obj = {};\n\t\t\tobj['flags.gwmMacro'] = null;\t\t\t\n\t\t\tactor.update(obj);\n\n\t\t\t// reset items\n\t\t\tfor (let item of actor.items) {\n\t\t\t\tif (item.data.flags.gwmMacro !== null && item.data.flags.gwmMacro !== undefined) {\n\t\t\t\t\t// restoring the old value from flags\n let oldDmg = item.data.flags.gwmMacro.oldDmg;\n let oldAtk = item.data.flags.gwmMacro.oldAtk;\n\t\t\t\t\tlet obj = {};\n obj['data.damage.parts'] = oldDmg;\n obj['data.attackBonus'] = oldAtk;\n\t\t\t\t\tobj['flags.gwmMacro'] = null;\n\t\t\t\t\titem.update(obj);\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t// if GWM is disabled, enable it\n\t\t} else {\n chatMsg = `${actor.name} is swinging Harder!`;\n \n let obj = {};\n\t\t\tobj['flags.gwmMacro.enabled'] = true;\n\t\t\tactor.update(obj);\n\n // update items\n let gwmAtk = -5;\n\t\t\tlet gwmDmg = 10;\n\t\t\tfor (let item of actor.items) {\n let isMelee = getProperty(item, 'data.data.actionType') === 'mwak';\n let isHeavy = getProperty(item, 'data.data.properties.hvy')\n\t\t\t\tif (isMelee && isHeavy && item.data.data.damage.parts.length > 0) {\n\t\t\t\t\tconsole.log('updating ' + item);\n let obj = {};\n let atk = item.data.data.attackBonus;\n let dmg = item.data.data.damage.parts;\n // Save old attack and damage values\n obj['flags.gwmMacro.oldDmg'] = JSON.parse(JSON.stringify(dmg));\n obj['flags.gwmMacro.oldAtk'] = JSON.parse(JSON.stringify(atk));\n // Set the new attack and damage values\n if (atk !== null) {\n atk += '' + gwmAtk;\n } else {\n atk = gwmAtk;\n }\n\t\t\t\t\tdmg[0][0] = `${dmg[0][0]} + ${gwmDmg}`;\n obj['data.damage.parts'] = dmg;\n obj['data.attackBonus'] = atk;\n\t\t\t\t\titem.update(obj);\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n }\n\n} else ui.notifications.warn(\"Please select a token.\");\n\n// write to chat if needed:\nif (chatMsg !== '') {\n\tlet chatData = {\n\t\tuser: game.user._id,\n\t\tspeaker: ChatMessage.getSpeaker(),\n\t\tcontent: chatMsg\n };\n\tChatMessage.create(chatData, {});\n}","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[],"_id":"vheIbl4B1rqHjyxJ"}
{"_id":"vwfOm1Z1kY4EZS1G","name":"Tool Proficiency","type":"script","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","scope":"global","command":"/**\n * Grab a list of tools in the selected player's inventory, then all the user to make a roll on the tool.\n * Will take into consideration if the player is proficient in using the tool.\n */\n\n// get the first entry from the array of currently selected tokens. Works best/exclusively with one selected token\nconst target = canvas.tokens.controlled[0].actor;\n// get the abilities of the selected token for ease of access later\nconst { abilities } = target.data.data;\n// Only items set as \"tools\" will be included!\n// get all held and equipped Tools/Kits/Supplies. Might want to replace with /[tT]ools|[kK]it|[sS]upplies|[sS]et$/ if gaming sets should be included\nconst toolsInInventory = target.items.filter( item => item.name.match(/[tT]ools|[kK]it|[sS]upplies$/) && item.data.data.hasOwnProperty(\"proficient\"));\n// const toolProficiencies = target.data.data.traits.toolProf; // Tools have proficiency mod in the object under .data.data.proficient. \nlet tool = undefined;\n\n// Choose ability mod dialog\nconst abilityDialog = (async () => {\n let template = `\n
\n
\n \n \n
\n
`\n\n\n new Dialog({\n title: tool.name,\n content: template,\n buttons: {\n ok: {\n icon: '',\n label: \"OK\",\n callback: async (html) => {\n const selection = html.find(\"#selectedAbility\")[0].value;\n console.log(tool, target);\n let prof = tool.data.data.proficient * target.data.data.attributes.prof; // target might be half or doubly proficient. This will make sure it is accounted for\n\n let messageContent = `${target.name} rolled a [[1d20+${abilities[selection].mod}(${abilities[selection].name})+${prof}(Proficiency)]] for the ${tool.name} check. `;\n let chatData = {\n user: game.user.id,\n speaker: ChatMessage.getSpeaker(),\n content: messageContent,\n // uncomment the line below to always whisper the roll to the GM\n // whisper: game.users.filter(u => u.isGM).map(u => u._id)\n };\n ChatMessage.create(chatData, {});\n }\n },\n cancel: {\n icon: '',\n label: 'Cancel'\n }\n },\n default: \"cancel\"\n }).render(true);\n})\n\n// Choose tool dialog\nif (toolsInInventory.length) {\n (async () => {\n let template = `\n
',\n buttons: {\n ok: {\n icon: '',\n label: \"OK\"\n }\n },\n default: \"ok\"\n }).render(true);\n}","folder":null,"sort":0,"permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"flags":{}}
{"_id":"whXLG5afXGwYNXEq","name":"Rage","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"//\t\tDISCLAIMER:\t\tThis macro is an evolved version of the original D&D 5e Rage Macro masterwork written by Felix#6196.\n//\t\t\t\t\t\tNorc#5108 is now maintaining this macro along with continued support from Felix.\n//\n//\n//\t\tUPDATES:\t\t1.\tFixed errors resulting from declarations of \"actor\" and \"token\" in a script macro. \n//\t\t\t\t\t\t\tAdded automatic Totem Spirit: Bear detection and resistance application \n//\t\t\t\t\t\t\tAdded error messages for trying to rage with no token or no barbarian selected\n//\t\t\t\t\t\t2.\t(Felix) Added resource/usage deduction and errors (re-added after accidentally overwriting the addition)\n//\t\t\t\t\t\t\tFixed rage damage at level 8\n//\t\t\t\t\t\t3.\t(2020/05/30) \"Version 2.0\" \t\n//\t\t\t\t\t\t\tImplemented Felix's idea to use global melee weapon attack bonus instead of modifying items\n//\t\t\t\t\t\t\tImproved Rage icon toggling to be more reliable\n//\t\t\t\t\t\t\tRemoved code from the resource management that created dependency on The Furnace Advanced Macros\n//\t\t\t\t\t\t\tImplemented Felix's fix for issue where new resistances and rage uses were not saving properly\n//\t\t\t\t\t\t\tFixed rage damage formula again...\n//\t\t\t\t\t\t\tAdded basic support for non-strength Based barbarians (Dex, Hexblade)\n//\t\t\t\t\t\t\tAdded optional ability to toggle the icon and name of the macro itself based on current raging state.\n//\t\t\t\t\t\t4.\t(2020/06/04) \n//\t\t\t\t\t\t\tFixed bug with experimental macro name/icon toggle only by renaming \"actor\" and \"token\"\n//\t\t\t\t\t\t\tAdded basic localization support to allow searching for translated class features\n//\t\t\t\t\t\t5.\t(2020/06/10)\n//\t\t\t\t\t\t\tRework to rage damage logic under the hood for edge case (other changes to bonus damage mid-combat) \n//\t\t\t\t\t\t\tRemoved logic that was causing multiple character sheets to open in some cases\n//\t\t\t\t\t\t\tEnhanced localization support\n\n//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n//!!! Bonus Tip 1: \t\tOptional Rage Resource Consumption\n//!!!\tTo automatically use and track Rage, you must have a resource exactly named \"Rage\" on your character sheet. This text can be changed\n//!!!\tby altering the value for \"rageResourceName\" in the LOCALIZATION SUPPORT section below).\n//!!!\tNote: \tImporting via VTTA Beyond Integration uses this text already. The macro can then automatically detect the Rage resource.\n//!!!\n//!!!\tBonus Tip 2: \t\tBear Totem Spirit Barbs\n//!!!\tIf you chose the Spirit Seeker Primal path, and at level 3 you chose the Bear Totem Spirit (resistance to all non-psychic damage), \n//!!!\tin your 5E character sheet, double-check that the name of your Totem Spirit feature to EXACTLY \"Totem Spirit: Bear\". This text can be\n//!!!\tchanged by altering the value for \"bearTotemFeatureName\" in the LOCALIZATION SUPPORT section below).\n//!!!\tNote: \tImporting via VTTA Beyond Integration uses this text already. The macro then automatically adds the extra \n//!!!\t\t\tBear Totem Spirit resistances.\n//!!!\n//!!!\tBonus Tip 3: \t\tThrown Weapons\n//!!!\tWhen a barb throws a weapon using strength, typically a javelin but also possibly a dagger, dart, sword, bar table etc, the rage bonus\n//!!!\tshould not be added because it is a ranged attack. However, D&D5E calls javelins and daggers Melee Weapons, because technically they\n//!!!\tare both. To solve this issue, if you always throw the weapon, click the weapon's details and change the attack type to \"Ranged Weapon\n//!!!\tAttack\" in the Action Type dropdown. If you want, you can add a second copy of the item (with no weight/quantity) to use for meleeing.\n//!!!\n//!!!\tBonus Tip 4: \t\tThe Rage Condition\n//!!!\tIf you use the Combat Utility Belt module's Condition Lab, try adding a condition called \"Raging\" with the same icon\n//!!!\tas the optional rage icon overlay, 'icons/svg/explosion.svg' by default. See EXPERIMENTAL MACRO ICON/NAME TOGGLE section below.\n//!!!\n//!!!\tBonus Tip 5: \t\tObsidian Sheet Compatibility\n//!!!\tIf using Obsidian module, try replacing \"Barbarian\" with \"brb\" as the barbClassName value in LOCALIZATION SUPPORT below.\n//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n//!!!\tOPTIONAL TOKEN ICON-\tOn by default. If a path to a rage icon is defined, it displays like a condition on the raging barbarian.\n//!!!\t\t\t\t\t\t\tTo use a different icon, manually change the filepath below or leave it empty ('') to disable the effect.\n//!!!\nconst rageIconPath = 'icons/svg/explosion.svg';\n//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n//!!!\tOPTIONAL RESOURCE DEDUCTION \tOn by default. First option automatically subtracts from the Rage Resource if enabled.\n//!!!\t\t\t\t\t\t\t\t\tSecond option prevents raging if no Rage resource is left. Set to false if you do not want this.\n\n\t\t\tconst deductResource = true;\n\t\t\tconst preventNegativeResource = true;\n//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n//!!!\tOPTIONAL NON-STRENGTH BARBARIAN SUPPORT\t\tONLY override to FALSE if your barbarian does not use Strength to make melee attacks\n//!!!\t\t\t\t\t\t\t\t\t\t\t\tand therefore does not get the Rage bonus to melee weapon attack damage.\n//!!!\n\t\t\tconst strAttacks = true;\n//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n//!!!\tEXPERIMENTAL MACRO ICON/NAME TOGGLE\t\tIf enabled, the macro icon and name toggles based on the barbarian's rage state.\n//!!!\t\t\t\t\t\t\t\t\t\t\tCAUTIONS: \t1. \tThis feature is off by default and is intended for ADVANCED USERS ONLY.\n//!!!\t\t\t\t\t\t\t\t\t\t\t\t\t\t2. \tRequires configuration using \"The Furnace\" module for a player to run!\n//!!!\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tThe GM needs to grant The Furnace's \"Run as GM\" permission for this macro.\n//!!!\t\t\t\t\t\t\t\t\t\t\t\t\t\t3. \tWorks best with only one barbarian using this feature at a time.\n\n\t\t\t//To auto-toggle the macro's icon/name, override toggleMacro to true below.\n\t\t\tconst toggleMacro = false;\n\n\t\t\t//To use a different icon, manually change the filepath here\n\t\t\tconst stopRageIconPath = 'icons/svg/unconscious.svg';\n\n\t\t\t//You must update the following constant to this macro's exact name for the macro icon toggling to work.\n\t\t\tconst rageMacroName = 'Rage';\n//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n//declarations\nlet barb = '';\nlet chatMsg = '';\nlet bear = '';\nlet noRage = false;\nlet rageDmgAdded = false;\nlet toggleResult = false;\nlet macroActor = actor;\nlet macroToken = token;\n\n//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n//!!!\tLOCALIZATION SUPPORT\t\t\t\tSets names of D&D5E features as constants instead of hardcoding to allow easier translation.\n//!!!\t\t\t\t\t\t\t\t\t\tSets error messages and flavor text as constants also for easier translation.\n//!!!\n\t\t\t//MUST MATCH VALUES IN CHARACTER SHEET (if present)\n\t\t\tconst barbClassName = 'Barbarian';\n\t\t\tconst rageResourceName = 'Rage';\n\t\t\tconst bearTotemFeatureName = 'Totem Spirit: Bear';\n\n\t\t\t//All remaining values may be changed freely\n\n\t\t\t//Rage chat message flavor text. Actor's name appears immediately before these two strings in the message.\n\t\t\tconst rageMsg = ' is RAAAAAGING!'\n\t\t\tconst endRageMsg = ' is no longer raging.';\n\n\t\t\t//error and warning messages\n\t\t\tconst errorSelectBarbarian = 'Please select a single barbarian token.';\n\t\t\tconst errorNoRage = ' does not have any rage left, time for a long rest!';\n\t\t\tconst warnMacroNotFound = ' is not a valid macro name, please fix. Rage toggle successful but unable to alter macro.';\n\t\t\tconst errorSelectToken = 'Please select a token.';\n\t\t\tconst errorFailRevert = 'Failed to revert global melee weapon attack bonus, please check manually.';\n//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n\n//main\n//check to see if Actor exists and is a barbarian\nif (macroActor !== undefined && macroActor !== null) {\n\n\t// get the barbarian class item\n\tbarb = macroActor.items.find(i => i.name === `${barbClassName}`);\n\tif (barb == undefined) {\n\t\tui.notifications.warn(`${errorSelectBarbarian}`);\n\t}\n\tif (barb !== undefined && barb !== null) {\n\t\tlet enabled = false;\n\t\t// Store the state of the rage toggle flags that indicate if rage is active or not\n\t\tif (macroActor.data.flags.rageMacro !== null && macroActor.data.flags.rageMacro !== undefined) {\n\t\t\tenabled = true;\n\t\t\t\t// Store whether there is also a rage damage bonus currently active\n\t\t\t\tif (macroActor.data.flags.rageMacro[\"rageDmgAdded\"] == true) {\n\t\t\t\t\trageDmgAdded = true;\n\t\t\t\t}\n\t\t}\n\n\t\t//Calculate rage value for use in damage reversion and application\n\t\t// Determining the barbarian level\n\t\tlet barblvl = barb.data.data.levels;\n\n\t\t// Formula to determine the rage bonus damage depending on barbarian level\n\t\tlet lvlCorrection = barblvl === 16 || barblvl === 17 ? 1 : 0;\n\t\tlet rageDmg = 2 + Math.floor(barblvl / 9) + lvlCorrection;\n\t\tlet dmg = JSON.parse(JSON.stringify(macroActor.data.data.bonuses.mwak.damage));\n\n\t\t// if rage is active, disable it\n\t\tif (enabled) {\n\t\t\tchatMsg = `${macroActor.name} ${endRageMsg}`;\n\t\t\t// reset resistances and melee weapon attack bonus\n\t\t\tlet obj = {};\n\t\t\tobj['flags.rageMacro'] = null;\n\t\t\t//revert damage resistances\n\t\t\tobj['data.traits.dr'] = macroActor.data.flags.rageMacro.oldResistances;\n\n\t\t\t//carefully revert rage global mwak damage bonus to original value, if that bonus is active\n\t\t\t//eventually want to add support so only last instance found is replaced.\n\t\t\tif(rageDmgAdded) {\n\t\t\t\tif (dmg == rageDmg || dmg == null || dmg == undefined || dmg == '' || dmg == 0){\n\t\t\t\t\tconsole.log('Removing simple rage damage');\n\t\t\t\t\tobj['data.bonuses.mwak.damage']='';\n\t\t\t\t} else {\n\t\t\t\t\tconsole.log('Removing complex rage damage');\n\t\t\t\t\tlet patt = `\\\\s\\\\+\\\\s${rageDmg}($|[^0123456789dkrxcm(@{])`;\n\t\t\t\t\tlet result = dmg.search(patt);\n\t\t\t\t\tif (result !== -1) {\n\t\t\t\t\t\tlet len = ('' + rageDmg).length;\n\t\t\t\t\t\tlet origDmg = duplicate(dmg);\n\t\t\t\t\t\tlet firstHalfDmg = duplicate(dmg).substring(0,result);\n\t\t\t\t\t\t//Test String: 2d6 + 2 + 2d6\n\t\t\t\t\t\tlet lastHalfDmg = duplicate(dmg).substring(result+3+len, origDmg.length);\n\t\t\t\t\t\tdmg = `${firstHalfDmg}${lastHalfDmg}`;\n\t\t\t\t\t\tobj['data.bonuses.mwak.damage']=dmg;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tui.notifications.error(`${errorFailRevert}`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tmacroActor.update(obj);\n\n\t\t// if rage is disabled, enable it\n\t\t} else {\n\t\t\tif (deductResource) {\n\t\t\t\tlet hasAvailableResource = false;\n\t\t\t\tlet newResources = duplicate(macroActor.data.data.resources)\n\t\t\t\tlet obj = {}\n\t\t\t\t// Look for Resources under the Core macroActor data\n\t\t\t\tlet resourceKey = Object.keys(macroActor.data.data.resources).filter(k => macroActor.data.data.resources[k].label === `${rageResourceName}`).shift();\n\t\t\t\tif (resourceKey && (macroActor.data.data.resources[resourceKey].value > 0 || !preventNegativeResource)) {\n\t\t\t\t\thasAvailableResource = true;\n\t\t\t\t\tnewResources[resourceKey].value--;\n\t\t\t\t\tobj['data.resources'] = newResources \n\t\t\t\t\tmacroActor.update(obj);\n\t\t\t\t}\n\t\t\t\tif (!hasAvailableResource) {\n\t\t\t\t\tui.notifications.error(`${macroActor.name} ${errorNoRage}`);\n\t\t\t\t\tnoRage=true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t//activate rage if there is rage available, or if it is okay to rage with 0 resources\n\t\t\tif (!noRage) {\n\t\t\t\tchatMsg = `${macroActor.name} ${rageMsg}`;\n\t\t\t\t// update resistance\n\t\t\t\tlet obj = {};\n\t\t\t\t// storing old resistances in flags to restore later\n\t\t\t\tobj['flags.rageMacro.enabled'] = true;\n\t\t\t\tobj['flags.rageMacro.oldResistances'] = JSON.parse(JSON.stringify(macroActor.data.data.traits.dr));\n\t\t\t\t// add bludgeoning, piercing and slashing resistance\n\t\t\t\tlet newResistance = duplicate(macroActor.data.data.traits.dr);\n\t\t\t\tif (newResistance.value.indexOf('bludgeoning') === -1) newResistance.value.push('bludgeoning');\n\t\t\t\tif (newResistance.value.indexOf('piercing') === -1) newResistance.value.push('piercing');\n\t\t\t\tif (newResistance.value.indexOf('slashing') === -1) newResistance.value.push('slashing');\n\t\t\t\t//If bear totem, add bear totem resistances.\n\t\t\t\tbear = macroActor.items.find(i => i.name === `${bearTotemFeatureName}`)\n\t\t\t\tif (bear !== undefined && bear!== null) {\n\t\t\t\t\tif (newResistance.value.indexOf('acid') === -1) newResistance.value.push('acid');\n\t\t\t\t\tif (newResistance.value.indexOf('cold') === -1) newResistance.value.push('cold');\n\t\t\t\t\tif (newResistance.value.indexOf('fire') === -1) newResistance.value.push('fire');\n\t\t\t\t\tif (newResistance.value.indexOf('force') === -1) newResistance.value.push('force');\n\t\t\t\t\tif (newResistance.value.indexOf('lightning') === -1) newResistance.value.push('lightning');\n\t\t\t\t\tif (newResistance.value.indexOf('necrotic') === -1) newResistance.value.push('necrotic');\n\t\t\t\t\tif (newResistance.value.indexOf('poison') === -1) newResistance.value.push('poison');\n\t\t\t\t\tif (newResistance.value.indexOf('radiant') === -1) newResistance.value.push('radiant');\n\t\t\t\t\tif (newResistance.value.indexOf('thunder') === -1) newResistance.value.push('thunder');\n\t\t\t\t}\n\t\t\t\tobj['data.traits.dr'] = newResistance;\n\t\t\t\tmacroActor.update(obj);\n\n\t\t\t\t// For Strength barbarians, update global melee weapon attack bonus to include rage bonus\n\t\t\t\tif (strAttacks) {\n\t\t\t\t\tobj['flags.rageMacro.rageDmgAdded'] = true;\n\t\t\t\t\t// Preserve old mwak damage bonus if there was one, just in case\n\t\t\t\t\tobj['flags.rageMacro.oldDmg'] = JSON.parse(JSON.stringify(dmg));\n\t\t\t\t\t//actually add the bonus rage damage to the previous bonus damage\n\t\t\t\t\t//respect roll formulas by doing string addition if value is already present.\n\t\t\t\t\tif (dmg == null || dmg == undefined || dmg == 0 || dmg == '') {\n\t\t\t\t\t\tconsole.log('Adding simple rage damage');\n\t\t\t\t\t\tobj['data.bonuses.mwak.damage'] = rageDmg;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconsole.log('Adding complex rage damage');\n\t\t\t\t\t\tobj['data.bonuses.mwak.damage'] = `${dmg} + ${rageDmg}`;\n\t\t\t\t\t}\n\t\t\t\t\tmacroActor.update(obj);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (!noRage) {\n\t\t\t// toggle rage icon, if rage path is defined above\n\t\t\t(async () => { \n\t\t\t\ttoggleResult = await macroToken.toggleEffect(rageIconPath);\n\t\t\t\tif (toggleResult == enabled) macroToken.toggleEffect(rageIconPath); \n\t\t\t})();\n\t\t\t\n\t\t\t//toggle macro icon and name, if macro name is correct and stop rage icon path is defined\n\t\t\tlet rageMacro = game.macros.getName(rageMacroName);\n\t\t\t\t//check for name of macro in its \"off\" form\n\t\t\t\tif (rageMacro == null || rageMacro == undefined) {\n\t\t\t\t\trageMacro = game.macros.getName('Stop ' + rageMacroName);\n\t\t\t\t}\n\t\t\tlet obj = {};\n\t\t\tif ( (rageMacro !== null && rageMacro !== undefined) && toggleMacro == true && \n\t\t\t\t\t+ (stopRageIconPath !== null && stopRageIconPath !== undefined && stopRageIconPath !== '') ) {\n\t\t\t\tif (enabled) {\n\t\t\t\t obj['img'] = rageIconPath;\n\t\t\t\t obj['name'] = rageMacroName;\n\t\t\t\t} else {\n\t\t\t\t obj['img'] = stopRageIconPath;\n\t\t\t\t obj['name'] = 'Stop ' + rageMacroName;\n\t\t\t\t}\n\t\t\t\trageMacro.update(obj);\n\t\t\t} else {\n\t\t\tif (toggleMacro == true) ui.notifications.warn(`${rageMacroName} ${warnMacroNotFound}`);\n\t\t\t}\n\t\t}\n\t}\n} else ui.notifications.warn(errorSelectToken);\n// write to chat if needed:\nif (chatMsg !== '') {\n\tlet chatData = {\n\t\tuser: game.user._id,\n\t\tspeaker: ChatMessage.getSpeaker(),\n\t\tcontent: chatMsg\n\t};\n\tChatMessage.create(chatData, {});\n}","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]}
-{"$$deleted":true,"_id":"jm7QjDGeMrkhLrLQ"}
-{"$$deleted":true,"_id":"vheIbl4B1rqHjyxJ"}
-{"$$deleted":true,"_id":"REqPUjyUyd8xvnS5"}
-{"$$deleted":true,"_id":"gKnoce2uTbOT9FBe"}
-{"$$deleted":true,"_id":"KWCb3y9I2P45LDWU"}
-{"$$deleted":true,"_id":"jmKgAR2r9Rfo60rU"}
diff --git a/packs/macros-misc.db b/packs/macros-misc.db
index 4538a12..d4bfce0 100644
--- a/packs/macros-misc.db
+++ b/packs/macros-misc.db
@@ -11,16 +11,13 @@
{"_id":"GhloAVkE0qJJGyoB","name":"Award Party XP","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"(function ()\n{\n function award_xp(type, amount)\n {\n let actors = game.actors.entities.filter(e => e.data.type === 'character' && e.isPC);\n let isShared = type == \"shared\";\n console.log(type + ' ' + amount);\n if (Number.isInteger(amount) && actors.length > 0)\n {\n let totalAmount = isShared ? amount : amount * actors.length;\n let individualAmount = isShared ? Math.floor(amount / actors.length) : amount\n\n let chatContent = `\n\t\t\t${totalAmount} Experience Awarded!\n\t\t\t ${individualAmount} added to:\n\t\t\t`;\n\n actors.forEach(actor =>\n {\n chatContent += ` ${actor.name}`;\n actor.update({\n \"data.details.xp.value\": actor.data.data.details.xp.value + individualAmount\n });\n });\n\n let chatData = {\n user: game.user._id,\n speaker: ChatMessage.getSpeaker(),\n content: chatContent,\n type: CONST.CHAT_MESSAGE_TYPES.OTHER\n };\n ChatMessage.create(chatData);\n }\n }\n\n new Dialog({\n title: \"Award Party XP\",\n content: `\n
Select a type and an amount. Individual xp will give or take a set amount to/from each party member, whereas shared will split an amount evenly.
\n \n `,\n buttons: {\n one: {\n icon: '',\n label: \"Confirm\",\n callback: (html) =>\n {\n let type = html.find('[id=xp-type]')[0].value;\n let amount = parseInt(html.find('[id=xp-amount]')[0].value);\n award_xp(type, amount);\n }\n },\n two: {\n icon: '',\n label: \"Cancel\",\n }\n },\n default: \"Cancel\"\n }).render(true);\n})();\n","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]}
{"name":"Convert Compendium To Table","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{"core":{"sourceId":"Macro.r1vixJ7Om2Al2yYb"}},"scope":"global","command":"/* \n * Macro: GeekDad's Compendium to Table Script\n * Version: 1\n * Updated: 02-12-2020\n * Description: A nice friendly UI that takes a compendium and appends it to a table.\n*/\n\nfunction getPackNames() {\n let packs = [];\n let keys = game.packs.keys();\n let done = false;\n while (!done) {\n let key = keys.next();\n done = key.done;\n if (!done) {\n let pack = game.packs.get(key.value);\n if (pack.metadata.entity === \"Item\") {\n packs.push({ key: key.value, name: pack.metadata.label });\n }\n }\n }\n return packs;\n}\n\nfunction getTableNames() {\n let tables = [];\n game.tables.entities.forEach(table => {\n tables.push({ key: table.id, name: table.name });\n });\n\n return tables;\n}\n\nasync function convertToTable(packKey, tableKey) {\n let pack = game.packs.get(packKey);\n let table = game.tables.get(tableKey);\n\n const entityType = \"Compendium\";\n await pack.getIndex();\n let range = 0;\n \n const results = pack.index.map(i => {\n range++;\n return {\n text: i.name,\n type: 2,\n collection: packKey,\n resultId: i._id,\n img: i.img,\n weight: 1,\n range: [range, range],\n drawn: false\n }\n });\n\n await table.createEmbeddedEntity(\"TableResult\", results);\n\n await table.update({formula: \"1d\" + results.length});\n}\n\nlet itemPacks = getPackNames();\nlet tables = getTableNames();\n\nlet content = ``\n\n new Dialog({\n title: `GeekDad's Compendium to Rolltable Converter`,\n content: content,\n buttons: {\n yes: {\n icon: \"\",\n label: \"Convert\",\n callback: (html) => {\n let packKey = html.find(\"select[name='output-targetPack']\").val();\n let tableKey = html.find(\"select[name='output-tableKey']\").val();\n convertToTable(packKey, tableKey);\n }\n },\n no: {\n icon: \"\",\n label: 'Cancel'\n }\n },\n default: \"yes\"\n }).render(true);","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[],"_id":"JUg8TRWdUaHkABMQ"}
{"_id":"JV1pRTUy2LuaAh0L","name":"Folder to Compendium","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"/**\n * Import folder into writable compendium. Locked compendiums will not show as an option.\n * Folder type is optional, however will help if you have the same folder name across multiple system types.\n * Also contains options to store subfolder contents, update existing records (or only add new), and delete duplicate records.\n * Author: KrishMero#1792\n */\n\nlet packOptions = game.packs.filter(pack => !pack.locked).map(pack => ``);\nlet entityType = COMPENDIUM_ENTITY_TYPES.map(type => ``);\nconst form = `\n
Folder:
\n \n \n\n
Folder Type:
\n \n \n ${entityType}\n \n \n\n
Compendium:
\n \n ${packOptions}\n \n \n\n \n \n\n \n \n\n \n`;\n\nconst dialog = new Dialog({\n title: \"Store folder in compendium\",\n content: form,\n buttons: {\n use: {\n label: \"Apply\",\n callback: storeFolder\n }\n }\n}).render(true);\n\nfunction storeFolder(html) {\n const folderName = html.find(`input#folderName`)[0].value;\n const folderType = html.find(`select#entityType`)[0].value;\n const destinationPack = html.find(`select#destinationPack`)[0].value;\n const recurse = html.find(`input#recurse`)[0].checked;\n const update = html.find(`input#update`)[0].checked;\n const deleteRecords = html.find(`input#delete`)[0].checked;\n \n let folders = game.folders.filter(f => f.name === folderName);\n if (folderType) {\n folders = folders.filter(f => f.type === folderType);\n }\n if (folders.length === 0) {\n ui.notifications.error(`Your world does not have any folders named '${folderName}'.`);\n }\n else if(folders.length > 1) {\n ui.notifications.error(`Your world has more than one folder named ${folderName}`) \n }\n else {\n console.log(`storing in ${destinationPack}`);\n let packObject = game.packs.get(destinationPack);\n storeRecursively(folders[0], packObject, recurse, update, deleteRecords);\n ui.notifications.notify(`'${folderName}' stored successfully in '${packObject.title}'.`);\n }\n}\n\nfunction storeRecursively(currentFolder, packObject, recurse, update, deleteRecords) {\n console.log('store recursively for ' + currentFolder.name);\n if (currentFolder.content) {\n currentFolder.content.map(item => {\n console.debug(\" Item:\", item.data.name);\n let existingRecords = packObject.index.filter(i => i.name === item.data.name);\n if (item.data.name === 'Augury') {\n console.log(existingRecords);\n console.log(existingRecords.length);\n }\n\n // Delete all but the first duplicate.\n if(existingRecords.length > 1) {\n if (deleteRecords) {\n console.log(existingRecords);\n existingRecords.shift();\n existingRecords.map(record => packObject.deleteEntity(record._id));\n } else {\n console.log(`Skipped: ${existingRecords[0].name}`)\n ui.notifications.error(`Can't store '${existingRecords[0].name}' as multiple records were found. Delete the extras or check 'Delete duplicates'. Logged to console.`);\n }\n }\n\n if (existingRecords.length === 1 && update) {\n packObject.updateEntity(existingRecords[0]);\n } else if (!existingRecords.length) {\n packObject.createEntity(item);\n }\n \n });\n }\n\n if (currentFolder.children && recurse) {\n currentFolder.children.map(({ data }) => {\n storeRecursively(\n game.folders.entities.filter(f => f.data._id == data._id)[0],\n packObject,\n recurse, \n update,\n deleteRecords\n );\n });\n }\n}\n","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]}
-{"_id":"L3ygrAlemRYn1WYW","name":"Create Merchant Stock","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{"core":{"sourceId":"Macro.vjZxzCJQRQ5004za"}},"scope":"global","command":"/*\n * Macro: Lars's Merchant generator based on \"GeekDad's Compendium to Table Script\"\n * Version: 1\n * Updated: 08-03-2021\n * Description: A nice friendly UI that takes a compendium and appends items for specific types to an actor.\n * Smith: will have ammo, equipment and weapons\n * General store: will have common items but not scroll and potions\n * Magic: will have all that is left of consumables and loot from the general store \n*/\n\nfunction getPackNames() {\n let packs = [];\n let keys = game.packs.keys();\n let done = false;\n while (!done) {\n let key = keys.next();\n done = key.done;\n if (!done) {\n let pack = game.packs.get(key.value);\n if (pack.metadata.entity === \"Item\") {\n packs.push({ key: key.value, name: pack.metadata.label });\n }\n }\n }\n return packs;\n}\n\nfunction getActorNames() {\n let actors = [];\n game.actors.entities.forEach(table => {\n actors.push({ key: table.id, name: table.name });\n });\n\n return actors;\n}\n\nasync function addItemToActor(itemPromise, actor) {\n let item = await itemPromise\n actor.createOwnedItem(item.data);\n}\n\nfunction getPriceForRarity(rarity) {\n if (rarity === \"Common\") {\n let r = new Roll(\"(1d6 + 1)\");\n r.evaluate();\n return r.result * 10\n } else if (rarity === \"Uncommon\") {\n let r = new Roll(\"1d6\");\n r.evaluate();\n return r.result * 100\n } else if (rarity === \"Rare\") {\n let r = new Roll(\"2d10\");\n r.evaluate();\n return r.result * 1000\n } else if (rarity === \"Very Rare\") {\n let r = new Roll(\"(1d4 + 1)\");\n r.evaluate();\n return r.result * 10000\n } else if (rarity === \"Legendary\") {\n let r = new Roll(\"2d6\");\n r.evaluate();\n return r.result * 25000\n }\n return 0;\n}\n\nfunction getItem(pack, id, itemType) {\n let item = pack.getEntity(id).then(function(result) {\n if (result.data.data.rarity === \"Common\") {\n result.data.data.quantity = 100\n }\n if (result.data.data.price === 0) {\n result.data.data.price = getPriceForRarity(result.data.data.rarity)\n }\n if (itemType === \"Smith\" && (result.data.type === \"ammunition\" || result.data.type === \"equipment\" || result.data.type === \"weapon\")) {\n return result\n }\n if ((result.data.type === \"consumable\" || result.data.type === \"loot\")) {\n if (result.data.data.rarity === \"Common\" && (result.data.type === \"consumable\" && result.data.data.consumableType !=='potion' && result.data.data.consumableType !== 'scroll') && itemType === \"GS\") {\n return result\n } else if (itemType === \"GS\" && result.data.data.consumableType !== 'ammunition') {\n } else if (itemType === \"MS\") {\n if (result.data.data.rarity === \"Common\" && ((result.data.type === \"consumable\" && result.data.data.consumableType != 'potion' && result.data.data.consumableType != 'scroll')\n || ((result.data.type === \"loot\")))) {\n console.log(\"Skip general store item: \" + result.name)\n } else {\n return result\n }\n }\n }\n return \"EMPTY\"\n })\n return item\n}\n\n\nfunction generateMagicItemsStock(roll, list, actor, rollFormula) {\n for (let i = 0; i < roll.result; i++) {\n let r = new Roll(rollFormula);\n r.evaluate();\n let item = list[Math.floor(Math.random() * list.length)]\n item.data.data.quantity = r.result\n addItemToActor(item, actor)\n }\n}\n\nasync function addToActor(packKey, actorKey, itemType,disableCommon,uncommon,rare,veryRare,legendary) {\n let pack = game.packs.get(packKey);\n let actor = game.actors.get(actorKey);\n\n await pack.getIndex();\n let items = [];\n for (var i = 0; i < pack.index.length; i++) {\n let item = await getItem(pack,pack.index[i]._id,itemType)\n if (item !== \"EMPTY\") {\n items.push(item)\n }\n\n }\n if ((disableCommon === undefined)) {\n console.log(\"add common items\")\n items.filter(item => item.data.data.rarity === \"Common\").forEach(item => addItemToActor(item, actor))\n }\n if (uncommon !== \"\") {\n let roll = new Roll(uncommon);\n roll.evaluate();\n let list = items.filter(item => item.data.data.rarity === \"Uncommon\");\n generateMagicItemsStock(roll, list, actor,uncommon);\n }\n if (rare !== \"\") {\n let roll = new Roll(rare);\n roll.evaluate();\n let list = items.filter(item => item.data.data.rarity === \"Rare\");\n generateMagicItemsStock(roll, list, actor,rare);\n }\n if (veryRare !== \"\") {\n let roll = new Roll(veryRare);\n roll.evaluate();\n let list = items.filter(item => item.data.data.rarity === \"Very Rare\");\n generateMagicItemsStock(roll, list, actor,veryRare);\n }\n if (legendary !== \"\") {\n let roll = new Roll(legendary);\n roll.evaluate();\n let list = items.filter(item => item.data.data.rarity === \"Legendary\");\n generateMagicItemsStock(roll, list, actor,legendary);\n }\n}\n\nlet itemPacks = getPackNames();\nlet actorNames = getActorNames();\n\nlet content = ``\n\nnew Dialog({\n title: `GeekDad's Compendium to Actor Generator`,\n content: content,\n buttons: {\n yes: {\n icon: \"\",\n label: \"Convert\",\n callback: (html) => {\n let packKey = html.find(\"select[name='output-targetPack']\").val();\n let actorKey = html.find(\"select[name='output-tableKey']\").val();\n let itemType = html.find(\"select[name='output-targetType']\").val();\n let disableCommon = html.find(\"input[name='disableCommon']:checked\").val();\n let uncommon = html.find(\"input[name='uncommon']\").val();\n let rare = html.find(\"input[name='rare']\").val();\n let veryRare = html.find(\"input[name='veryRare']\").val();\n let legendary = html.find(\"input[name='legendary']\").val();\n addToActor(packKey, actorKey,itemType,disableCommon,uncommon,rare,veryRare,legendary);\n }\n },\n no: {\n icon: \"\",\n label: 'Cancel'\n }\n },\n default: \"yes\"\n}).render(true);","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]}
{"_id":"PJVvR31x5PNkdWFr","name":"Close All Doors","type":"script","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","scope":"global","command":"/**\n * Closes all doors on the canvas\n * Author: @Atropos#3814\n */\n \ncanvas.scene.updateEmbeddedDocuments(\"Wall\", canvas.scene.data.walls.map(w => {\n return {_id: w.id, ds: w.data.ds === 1 ? 0 : w.data.ds};\n}));","folder":null,"sort":0,"permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"flags":{}}
{"_id":"QTDeg4HOYCAmr4dK","name":"Whisper Players","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"/** \n * Provides a dialog to whisper specific players. If you have tokens selected, it will automatically default to try and whisper those players.\n * @Author: Nelson#3570\n */\n\nlet applyChanges = false;\n\nlet users = game.users.filter(user => user.active);\nlet checkOptions = \"\"\nlet playerTokenIds = users.map(u => u.character?.id).filter(id => id !== undefined);\nlet selectedPlayerIds = canvas.tokens.controlled.map(token => {\n if (playerTokenIds.includes(token.actor.id)) return token.actor.id;\n});\n\n// Build checkbox list for all active players\nusers.forEach(user => {\n let checked = !!user.character && selectedPlayerIds.includes(user.character.id) && 'checked';\n checkOptions+=`\n \n \\n\n \n `\n});\n\nnew Dialog({\n title:\"Whisper\",\n content:`Whisper To: ${checkOptions} \n \n `,\n buttons:{\n whisper:{ \n label:\"Whisper\",\n callback: (html) => createMessage(html)\n }\n }\n}).render(true);\n\nfunction createMessage(html) {\n var targets = [];\n // build list of selected players ids for whispers target\n for ( let user of users ) {\n if (html.find('[name=\"'+user.id+'\"]')[0].checked){\n applyChanges=true;\n targets.push(user.id);\n }\n var messageText = html.find('[name=\"message\"]')[0].value\n }\nif(!applyChanges)return;\n ChatMessage.create({\n content: messageText,\n whisper: targets\n });\n}","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]}
{"_id":"Rd6O7mlOv0EIaviE","name":"Max NPC HP","type":"script","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","scope":"global","command":"// A simple macro for maximizing NPC HP\n\n// Choose one of the following to update by uncommenting one of the two lines below\n//const actors = game.actors; // update all actors in the sidebar\nconst actors = canvas.tokens.controlled.map((t) => t.actor); // update all selected tokens\n\nactors\n .filter((actor) => actor.type === \"npc\")\n .forEach(async (actor) => {\n const formula = actor.data.data?.attributes?.hp?.formula;\n if (!formula) return;\n\n const roll = await new Roll(formula).roll({ maximize: true });\n const data = {\n \"data.attributes.hp.value\": roll.total,\n \"data.attributes.hp.max\": roll.total,\n };\n await actor.update(data);\n });","folder":null,"sort":0,"permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"flags":{"core":{"sourceId":"Macro.rrLvfkbrPDDoaG7H"}}}
-{"_id":"VXTRsUfomoFN9PVP","name":"Folder Permission","type":"script","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","scope":"global","command":"// Provides a prompt to set default permissions to all items within a folder.\n// Prompts the user for the folder name (case sensitive) and the permission level.\n\nconst form = `\n
Folder:
\n \n \n\n
Folder Type:
\n \n \n\n
Permission:
\n \n \n \n \n \n \n \n\n \n`;\n\nconst dialog = new Dialog({\n\ttitle: 'Set desired permission',\n\tcontent: form,\n\tbuttons: {\n\t\tuse: {\n\t\t\tlabel: 'Apply permissions',\n\t\t\tcallback: applyPermissions,\n\t\t},\n\t},\n}).render(true);\n\n/**\n *\n * @param {jQuery} html\n */\nfunction applyPermissions(html) {\n\t// Get values from form\n\tconst folderType = html.find('select#folderType')[0].value;\n\tconst folderName = html.find(`input#folderName`)[0].value;\n\tconst permission = html.find(`select#desiredPermission`)[0].value;\n\tconst recurse = html.find(`input#recurse`)[0].checked;\n\n\t// Find folderName\n\tconst folders = game.folders.filter(\n\t\tf => f.type === folderType && f.name === folderName\n\t);\n\n\tif (folders.length === 0)\n\t\tui.notifications.error(\n\t\t\t`Your world does not have any folders named '${folderName}'.`\n\t\t);\n\telse if (folders.length > 1)\n\t\tui.notifications.error(\n\t\t\t`Your world has more than one folder named ${folderName}`\n\t\t);\n\telse {\n\t\trepermission(folders[0], permission, recurse);\n\t\tui.notifications.notify(\n\t\t\t`Desired permissions were set successfully for '${folderName}' of type '${folderType}'.`\n\t\t);\n\t}\n}\n\n/**\n *\n * @param {*} currentFolder\n * @param {String} desiredPermission\n * @param {Boolean} recurse\n * @returns {Boolean}\n */\nasync function repermission(currentFolder, desiredPermission, recurse) {\n\tconsole.info(`Repermissioning: ${currentFolder.name}`);\n\n\tif (currentFolder.content) {\n\t\tcurrentFolder.content.forEach(async doc => {\n\t\t\tconst newPerms = duplicate(doc.data.permission);\n\t\t\tnewPerms.default = Number(desiredPermission);\n\t\t\tawait doc.update({ permission: newPerms });\n\t\t});\n\t}\n\n\tif (recurse && currentFolder.children) {\n\t\tcurrentFolder.children.forEach(folder =>\n\t\t\trepermission(folder, desiredPermission, recurse)\n\t\t);\n\t}\n}","folder":null,"sort":0,"permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"flags":{}}
{"_id":"Vhj3ctf5cAufQMnM","name":"Lock and Unlock Players","type":"script","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","scope":"global","command":"async function updateRoles(fromRole, toRole) {\n const updates = game.users.filter(u => u.role === fromRole).map(u => ({_id: u.id, role: toRole}))\n await User.updateDocuments(updates)\n}\nnew Dialog({\n title: `Lock or unlock all players?`,\n default: 'cancel',\n buttons: {\n unlock: {\n icon: '',\n label: 'Unlock',\n callback: () => updateRoles(CONST.USER_ROLES.NONE, CONST.USER_ROLES.PLAYER)\n },\n lock: {\n icon: '',\n label: 'Lock',\n callback: () => updateRoles(CONST.USER_ROLES.PLAYER, CONST.USER_ROLES.NONE)\n },\n cancel: {\n icon: '',\n label: 'Cancel',\n callback: () => {}\n }\n }\n}).render(true)","folder":null,"sort":0,"permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"flags":{"core":{"sourceId":"Macro.VhzAqG5Abes21JD9"}}}
-{"_id":"XIQjNhEFdzPA1mQY","name":"Scene Border Walls","type":"script","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","scope":"global","command":"for (let scene of game.scenes){\n //Goes through the array of scenes and finds the active one.\n if (scene._view) {\n //Height, Width, and Padding offsets. \n //scene data.height and scene.data.width give image size.\n //canvas dimensions give size including padding\n //xf, yf = x and y offsets.\n let h = scene.data.height;\n let w = scene.data.width;\n let xf = (canvas.dimensions.width - w)*0.5;\n let yf = (canvas.dimensions.height - h)*0.5;\n //Walls need two vertices: X Point 1, Y Point 1, X Point 2, Y Point 2\n //top wall, right wall, bottom wall, left wall\n let tw = [xf, yf, w + xf, yf];\n let rw = [w + xf, yf, w + xf, h + yf];\n let bw = [w + xf, h + yf, xf, h + yf];\n let lw = [xf, h + yf, xf, yf];\n //Creates walls. There is probably a cleaner way to do this.\n Wall.create({\n c: tw\n });\n Wall.create({\n c: rw\n });\n Wall.create({\n c: bw\n });\n Wall.create({\n c: lw\n });\n }\n}","folder":null,"sort":0,"permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"flags":{}}
{"_id":"XNQSxkffgHApZf4A","name":"Import from Compendium","type":"script","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","scope":"global","command":"/** \n * Import all the entries from a compendium into the desired folder.\n * @Author: KrishMero#1702\n */\n \nlet packOptions = game.packs.map(pack => ``);\nconst form = `\n
Folder:
\n \n \n
leave blank to create a folder after the compendium name
\n \n\n
Compendium:
\n \n ${packOptions}\n \n \n\n \n`;\n\nconst dialog = new Dialog({\n title: \"Import data from compendium\",\n content: form,\n buttons: {\n use: {\n label: \"Apply\",\n callback: importCompendium\n }\n }\n}).render(true);\n\nasync function importCompendium(html) {\n const folderName = html.find(`input#folderName`)[0].value;\n const packName = html.find(`select#destinationPack`)[0].value;\n const remove = html.find(`input#delete`)[0].checked;\n\n const pack = game.packs.get(packName);\n const doc = pack.documentName;\n let folder = folderName ? findFolder(folderName, doc) : await createFolder(pack, doc);\n \n if (!folder) return ui.notifications.error(`Your world does not have any ${doc} folders named '${folderName}'.`);\n console.log(folder.id)\n if (remove) removeDataFirst(folder.id, doc);\n if (folder) importPack(pack, doc, folder.id)\n}\n\nasync function importPack(pack, doc, folderId) {\n const docClass = CONFIG[doc].documentClass;\n const content = await pack.getDocuments();\n const createData = content.map(c => {\n let data = c.toObject();\n data.folder = folderId;\n return data;\n });\n docClass.createDocuments(createData);\n}\n\nasync function removeDataFirst(folderId, doc) {\n let type = getDocType(doc);\n const removeableData = game[type].filter(t => t.data.folder === folderId);\n CONFIG[doc].documentClass.deleteDocuments(removeableData.map(e=>e.id));\n // if (typeof removeableData.delete !== \"undefined\") {\n // removeableData.delete();\n // } else {\n // removeableData.map(d => d.delete());\n // }\n}\n\nasync function createFolder(pack, type) {\n let name = pack.metadata.label;\n if(game.folders.getName(name)) return game.folders.getName(name);\n let folder = await Folder.createDocuments([{ name, type, parent: null}]);\n return folder[0];\n}\n\nfunction findFolder(folderName, doc)\n{\n return game.folders.find(f => f.name === folderName && f.type === doc)\n}\n\nfunction getDocType(doc) {\n switch (doc) {\n case 'JournalEntry': return 'journal';\n case 'RollTable': return 'tables';\n default: return doc.toLowerCase() + 's';\n }\n}","folder":null,"sort":0,"permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"flags":{}}
+{"name":"Actor to Combat","type":"script","author":"wCsoNxA9I9elUaGF","img":"icons/svg/dice-target.svg","scope":"global","command":"/*\nDisplay a prompt which allows you to select actors to add to the current combat encounter.\nThis is especially useful if you are running an encounter without a scene or tokens.\nYou can see it in action here: https://cdn.discordapp.com/attachments/960551198342139995/966856419787833384/Peek_2022-04-21_20-21.mp4\n*/\n\nDialog.confirm({\n title: \"Who would you like to add to the current combat?\",\n content: `
[]
`,\n render: html => {\n const section = html[0].querySelector(\"section\");\n const template = html[0].querySelector(\"template\");\n \n for (const actor of game.actors) {\n const item = document.createElement(\"div\");\n item.style.margin = \".5em 0\";\n \n const name = document.createElement(\"span\");\n name.slot = \"name\";\n name.textContent = actor.name;\n item.append(name);\n\n const id = document.createElement(\"code\");\n id.slot = \"id\";\n id.textContent = actor.id;\n item.append(id);\n\n const shadowRoot = item.attachShadow({ mode: \"open\" });\n shadowRoot.appendChild(template.content.cloneNode(true));\n \n section.append(item);\n }\n },\n yes: html => {\n const updates = [];\n html[0].querySelectorAll(\"template ~ section > div\").forEach(el => {\n const id = el.querySelector(\"code\").textContent;\n let quantity = el.shadowRoot.querySelector(\"input\").value;\n while (quantity > 0) {\n updates.push({ \"actorId\": id });\n quantity--;\n }\n });\n game.combat?.createEmbeddedDocuments(\"Combatant\", updates);\n },\n});","folder":null,"sort":0,"permission":{"default":0,"wCsoNxA9I9elUaGF":3},"flags":{"core":{"sourceId":"Macro.iAfz2Ld4b0IXhGRP"}},"_id":"a9TMIoWwDZ4u6vPa"}
{"_id":"bD3UuA4ikwbCwtKX","name":"Tile XY Adjust","type":"script","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","scope":"global","command":"//Simple macro to loop through ALL SELECTED TILES and adjust their position by a set amount\n//Questions? Ask in Foundry VTT Discord #macro-polo channel. If absolutely needed, ping @Norc$5108\n\nasync function adjustTilesXY(tiles, xAdjust, yAdjust ) {\n const updates = tiles.map(tile => ({\n _id: tile.id,\n x: tile.x + xAdjust,\n y: tile.y + yAdjust,\n }));\n await canvas.scene.updateEmbeddedDocuments(\"Tile\", updates)\n}\n\nconst tiles = canvas.background.controlled.length ? canvas.background.controlled : canvas.foreground.controlled;\nif(!tiles.length) return ui.notifications.info(\"No tiles selected.\")\n//loop through all selected tiles\n//REPLACE THE \"1\" VALUES BELOW AS NEEDED\n //The first number controls side-to-side position:\n //Positive values move tiles to the right\n //Negative values move tiles to the left\n //If you enter 0, tiles will not move side to side at all.\n //The second number controls up-and-down position:\n //Positive values move tiles down\n //Negative values move tiles up\n //If you enter 0, tiles will not move up or down at all.\nawait adjustTilesXY(tiles, 1, 1);","folder":null,"sort":0,"permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"flags":{}}
-{"_id":"bRBhm24NyQELbDti","name":"Create Chat Message","type":"script","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","scope":"global","command":"// Courtesy of @errational\n// Creates a chat message.\nlet controlledToken = canvas.tokens.controlled[0];\nconst content = `
Monster attacks ${controlledToken.name}
`;\n\nChatMessage.create({\n speaker: ChatMessage.getSpeaker(controlledToken),\n content: content,\n type: CONST.CHAT_MESSAGE_TYPES.OTHER\n});","folder":null,"sort":0,"permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"flags":{}}
{"_id":"cbvoXlwrSGOFFybV","name":"Delete All Templates","type":"script","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","scope":"global","command":"/**\n * Deletes all templates on the current scene\n */\n \n// no dialog. Just delete all templates.\ncanvas.scene.deleteEmbeddedDocuments(\"MeasuredTemplate\", canvas.templates.placeables.map(o =>o.id));\n\n// Get a dialog confirmation before deleting all templates on the scene:\n// canvas.templates.deleteAll()","folder":null,"sort":0,"permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"flags":{}}
{"_id":"dgSOPUXWUK1701PV","name":"Find Lights By Color","type":"script","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","scope":"global","command":"// Courtesy of @FloRad, Updated by scooper4711 \n// This macro is intended to perform a batch update\n// of all lights in the current scene e.g. after\n// importing from Unversal Battle Map importer,\n// allowing you to set e.g. all wall torches identically.\n(async () => {\n let foundLights = [];\n let markingColor = \"#eccd8b\"\n let newColor = \"#fec80a\"\n let scene = game.scenes.active;\n \n canvas.lighting.placeables.forEach(l => { if (l.data.tintColor === markingColor && l.scene === scene) foundLights.push(l.id) })\n \n const updates = []\n foundLights.forEach(id => {\n updates.push({ _id: id, \n tintColor: newColor, \n darkness: {min:0.2, max:1.0}, \n dim: 10, \n bright: 5, \n lightAnimation: {speed: 1, intensity: 3, type: \"torch\"} });\n })\n \n await scene.updateEmbeddedEntity(\"AmbientLight\", updates);\n \n console.log(foundLights)\n })()","folder":null,"sort":0,"permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"flags":{}}
{"_id":"e25oI8hASMY8bgu6","name":"Hex Crawler Helper","type":"script","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","scope":"global","command":"/*\nRequired Rollable Tables:\n*Wilderness Encounters*\n coast\n jungle1\n jungle2\n jungle3\n mountains\n rivers\n ruins\n swamp\n wasteland\n\n*Other Tables*\n weather\n directions\n\n cache\n deadexplorers\n\nCache and Deadexplorers are not mandatory, but if you don't want them search the file for // CACHE LINES or // DEAD EXPLORER LINES and comment out the 2 lines below the comments\n\nExplanation of those tables:\nIf you have an encounter table that has the word cache in it, the cache table will be rolled automatically.\n The party finds a cache: \nIf you have an encounter table that has DeadExplorers in it, the dead explorer table will be rolled automatically.\n The party finds: \n\n\nYou can have an automatic moving \"Actual Location\" Marker by creating a Token named \"Actual Location\" and placing it on your hex grid.\nThis will move if the players are \"Lost\". If the players are not lost it will not move.\n\n\n\n*/\n\n\n// Macro requires selecting a token to roll the survival check\n\nfunction hexCrawl() {\n if (canvas.tokens.controlled.length === 0)\n return ui.notifications.error(\"Please select the token of the Navigator!\");\n\n const playerMarker = canvas.scene.data.tokens.find(a => a.name === 'Player Location');\n const locationMarker = canvas.scene.data.tokens.find(a => a.name === 'Actual Location');\n\n const gridSize = canvas.grid.size;\n const vertical = gridSize * 0.866666;\n const diagVertical = gridSize * 0.433333;\n const diagHorizontal = gridSize * 0.75;\n\n // The option values below are the names of your rollable tables for each hex type. If these get changed here you will need to change them in the Survival Check DC section too!\n\n let pace = 'none';\n new Dialog({\n title: `Hex Crawl Helper`,\n content: `\n \n `,\n buttons: {\n slow: {\n icon: \"\",\n label: `Slow Pace`,\n callback: () => pace = 'slow'\n },\n average: {\n icon: \"\",\n label: `Average Pace`,\n callback: () => pace = 'average'\n },\n fast: {\n icon: \"\",\n label: `Fast Pace`,\n callback: () => pace = 'fast'\n }\n },\n default: \"average\",\n close: async (html) => {\n // set variables\n let hexType = html.find('[name=\"hex-type\"]')[0].value;\n let travelType = html.find('[name=\"travel-type\"]')[0].value;\n let playerDirection = html.find('[name=\"travel-direction\"]')[0].value;\n const weatherTable = game.tables.getName(\"weather\");\n const directionTable = game.tables.getName(\"directions\");\n const cacheTable = game.tables.getName(\"cache\");\n const deadExplorerTable = game.tables.getName(\"deadexplorers\");\n const encounterTable = game.tables.getName(hexType);\n let weatherRoll = (await weatherTable.roll()).results[0].data.text;\n let lostDirection = (await directionTable.roll()).results[0].data.text;\n let msgContent = 'Weather ' + weatherRoll + '
';\n let navigator = Actors.instance.get(canvas.tokens.controlled[0].data.actorId);\n let wis = navigator.data.data.abilities.wis.mod;\n let survival = (await new Roll(`1d20`).roll({async: true})).total + wis;\n let slowPace = (await new Roll(`1d4`).roll({async: true})).total;\n let fastPace = (await new Roll(`1d2`).roll({async: true})).total;\n let hexesMoved = 1;\n let encounter = '';\n let hexText = 'hexes';\n\n if (travelType === 'canoe') {\n hexesMoved++;\n }\n\n // build pace message and hex movement\n if (pace === 'slow') {\n if (slowPace === 1)\n hexesMoved--;\n if (hexesMoved === 1)\n hexText = 'hex';\n msgContent += 'Slow pace: Can hide from encounters or approach stealthily.
Party can move: ' + hexesMoved + ' ' + hexText + '.
';\n survival += 5;\n } else if (pace === 'average') {\n if (hexesMoved === 1)\n hexText = 'hex';\n msgContent += 'Average pace: For rivers, upstream and downstream have no effect, and waterfalls occur every 10 to 20 miles (requiring portage of canoes).
Party can move: ' + hexesMoved + ' ' + hexText + '.
';\n } else if (pace === 'fast') {\n if (fastPace === 1)\n hexesMoved++;\n if (hexesMoved === 1)\n hexText = 'hex';\n msgContent += 'Fast pace: -5 to passive Perception.
Party can move: ' + hexesMoved + ' ' + hexText + '.
';\n survival -= 5;\n } else {\n return;\n }\n\n // Survival Check DC for each hex type. If selected token rolls under DC the party is lost!\n if (((hexType === 'coast' || hexType === 'ruins') && survival < 10) || ((hexType === 'jungle1' || hexType === 'jungle2' || hexType === 'jungle3' || hexType === 'mountains' || hexType === 'rivers' || hexType === 'swamp' || hexType === 'wasteland') && survival < 15)) {\n msgContent += 'Party is Lost: Move actual location ' + hexesMoved + ' ' + hexText + ' to the ' + lostDirection + '
';\n // DEAD EXPLORER LINES comment out the next 2 lines if you don't want to use a dead explorer table!\n if (encounter.indexOf('DeadExplorers') > -1)\n msgContent += (await deadExplorerTable.roll()).results[0].data.text + '
Afternoon Encounter: ';\n }\n\n if ((await new Roll(`1d20`).roll({async: true})).total > 15) {\n encounter = (await encounterTable.roll()).results[0].data.text;\n msgContent += encounter;\n // CACHE LINES comment out the next 2 lines if you don't want to use a cache table!\n if (encounter.indexOf('cache') > -1)\n msgContent += (await cacheTable.roll()).results[0].data.text + '
';\n // DEAD EXPLORER LINES comment out the next 2 lines if you don't want to use a dead explorer table!\n if (encounter.indexOf('DeadExplorers') > -1)\n msgContent += (await deadExplorerTable.roll()).results[0].data.text + '
Evening Encounter: ';\n }\n\n if (new Roll(`1d20`).roll().total > 15) {\n encounter = (await encounterTable.roll()).results[0].data.text;\n msgContent += encounter;\n // CACHE LINES comment out the next 2 lines if you don't want to use a cache table!\n if (encounter.indexOf('cache') > -1)\n msgContent += (await cacheTable.roll()).results[0].data.text + '
';\n // DEAD EXPLORER LINES comment out the next 2 lines if you don't want to use a dead explorer table!\n if (encounter.indexOf('DeadExplorers') > -1)\n msgContent += (await deadExplorerTable.roll()).results[0].data.text + '
';\n } else {\n msgContent += 'None.';\n }\n\n // create the message\n let chatData = {\n content: msgContent,\n whisper: ChatMessage.getWhisperRecipients(\"GM\")\n };\n ChatMessage.create(chatData, {});\n }\n }).render(true);\n}\n\nhexCrawl();","folder":null,"sort":0,"permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"flags":{}}
@@ -30,22 +27,12 @@
{"_id":"iLlJKBjrtOMNxlv6","name":"Tile Toggle Hidden Status","type":"script","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","scope":"global","command":"// Simple macro to loop through ALL SELECTED TILES and toggle whether or not they are hidden.\n// Uncomment line 8 or 9 to change behavior to hide / show all tiles instead of toggle\nconst tiles = canvas.background.controlled.length ? canvas.background.controlled : canvas.foreground.controlled;\nconst updates = tiles.map(tile => {\n let v;\n v = !tile.data.hidden; // Toggle visibility for each tile\n // v = false; // Hide all selected tiles\n // v = true; // Show all selected tiles\n return{ _id: tile.id, hidden: v };\n});\ncanvas.scene.updateEmbeddedDocuments(\"Tile\", updates);","folder":null,"sort":0,"permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"flags":{}}
{"name":"Rebind Token Actors","type":"script","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","scope":"global","command":"// Rebinds the actor for all selected tokens.\n// Requirements: \n// - the actor must be present in the \"Actor Directory\"\n// - actor name can't contain either '/' or '.'\n\n// Any Token with an altered name and an img path attached \n// will look up the default actor name via provided URL.\n// Very useful when using eg. Plutonium in combination with\n// 5e.tools for importing creatures or other actors.\nlet tname, results, str, arr;\n\nconst dir = new ActorDirectory();\n\nfor (const token of canvas.tokens.controlled) {\n tname = token.name;\n results = dir.documents.filter(obj => {if(obj.data.name === tname){return obj;}})\n if(results.length === 0){\n if(token.data.img){\n// Possible optimization: regEx look-up for any word character pre '.' and post '/'\n str = token.data.img;\n arr = str.split('/');\n tname = arr[arr.length-1].split('.')[0];\n results = dir.documents.filter(obj => {if(obj.data.name === tname){return obj;}})\n }\n }\n if(results.length > 0){\n await token.update({'actorId':results[0].data._id});\n }\n}","folder":null,"sort":0,"permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"flags":{"core":{"sourceId":"Macro.T6rBYAF9cEQs1Tvj"}},"_id":"ie5zQi7onuxJezxN"}
{"_id":"lPEdDL4uqxRFfW9x","name":"Format All Scene Notes","type":"script","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","scope":"global","command":"const updates = canvas.notes.placeables.map(n => ({\n _id: n.id,\n \n /* READ THIS MESSAGE!**format_all_scene_notes.js**\nconst updates = canvas.notes.placeables.map(n => ({\n _id: n.id,\n \n /* READ THIS MESSAGE!\n Define new note properties\n double right-click a map pin for a list of valid fonts, icons, etc. \n remove the // from front of the line to allow that setting to be updated. \n */\n\n //fontFamily: \"Signika\", \n //fontSize: 48,\n //icon: \"icons/svg/anchor.svg\", // replace the name of the icon, for example, the anchor would be \"icons/svg/anchor.svg\" \n //iconSize: 40,\n //iconTint: null, // if not white hex code like: \"#AB8345\"\n //textAnchor: CONST.TEXT_ANCHOR_POINTS.CENTER, // textAnchor controls the location of the text in relation to the icon. other options are .BOTTOM, .TOP. LEFT . RIGHT\n //textColor: \"#FFFFFF\",\n //x: 2450, // absolute location on the canvas.\n //y: 1250, // absolute location on the canvas.\n}));\ncanvas.scene.updateEmbeddedDocuments(\"Note\", updates);\n Define new note properties\n double right-click a map pin for a list of valid fonts, icons, etc. \n remove the // from front of the line to allow that setting to be updated. \n */\n\n //fontFamily: \"Signika\", \n //fontSize: 48,\n //icon: \"icons/svg/anchor.svg\", // replace the name of the icon, for example, the anchor would be \"icons/svg/anchor.svg\" \n //iconSize: 40,\n //iconTint: null, // if not white hex code like: \"#AB8345\"\n //textAnchor: CONST.TEXT_ANCHOR_POINTS.CENTER, // textAnchor controls the location of the text in relation to the icon. other options are .BOTTOM, .TOP. LEFT . RIGHT\n //textColor: \"#FFFFFF\",\n //x: 2450, // absolute location on the canvas.\n //y: 1250, // absolute location on the canvas.\n}));\ncanvas.scene.updateEmbeddedDocuments(\"Note\", updates);","folder":null,"sort":0,"permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"flags":{"core":{"sourceId":"Macro.yBeuLYd6wNuX6JHQ"}}}
-{"_id":"mPIqhwFL3h5W9d2t","name":"Share Image Via URL","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"/**\n * Share an image to all players when you have an image URL\n * Author: @Krishmero#1792\n */\n\nlet imagePopup = (imageUrl) => {\n\t// Display the image popout and share it.\n\tconst ip = new ImagePopout(imageUrl);\n\tip.render(true);\n\tip.shareImage();\n};\n\nlet chatDialog = (imageUrl) => {\n\tChatMessage.create({\n\t\tuser: game.user._id,\n\t\tcontent: ``,\n\t\ttype: CONST.CHAT_MESSAGE_TYPES.OOC\n\t});\n};\n\nlet selectOptions = game.user.isGM ? `\n
\n\t\n\t\n\t\t\n\t\t\n\t\t\n\t\n
\n \n` : '';\n\nnew Dialog({\n\ttitle: `Share Image via URL`,\n\tcontent: `\n\t\t\n\t`,\n\tbuttons: {\n\t\tyes: {\n\t\t\ticon: \"\",\n\t\t\tlabel: `Share`,\n\t\t\tcallback: (html) => {\n\t\t\t\tlet imageUrl = html.find('#image-url').val();\n\t\t\t\tlet permission = html.find('select#output-options')[0]?.value || null;\n\t\t\t\tif (!imageUrl) {\n\t\t\t\t\treturn ui.notifications.info(\"You did not provide a valid image.\");\n\t\t\t\t}\n\t\t\t\tif (game.user.isGM && ['popup', 'both'].includes(permission)) {\n\t\t\t\t\timagePopup(imageUrl);\n\t\t\t\t}\n\t\t\t\tif (!game.user.isGM || ['chat', 'both'].includes(permission)) {\n\t\t\t\t\tchatDialog(imageUrl);\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tno: {\n\t\t\ticon: \"\",\n\t\t\tlabel: `Cancel`\n\t\t},\n\t},\n\tdefault: \"yes\"\n}).render(true)\n","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]}
{"_id":"mzkDNKmsOb9WwpIO","name":"Fine Tile Control","type":"script","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","scope":"global","command":"// Fine Tile Control //\n\n// This is a series of 6 macros that you must paste individually into a new\n// Hot Bar Macro Sheet and save. The Hot Bar number is used to activate the macro.\n// Hold down the Control key to halve the distance increment from 1 to 0.5.\n// CAUTION: If the ctrl key doesn‘t check your operating system and/or move the\n// macro to another cell.\n// NOTE: Any locked tiles are ignored.\n\n// Move Up\n// By @cole$9640\n\nconst amount = event.ctrlKey ? -0.5 : -1;\nconst tiles = canvas.background.controlled.length === 0 ? canvas.foreground.controlled : canvas.background.controlled;\nif (tiles.length) {\n const updates = tiles\n .filter((tile) => !tile.data.locked)\n .map((tile) => ({\n _id: tile.id,\n y: tile.y + amount,\n }));\n canvas.scene.updateEmbeddedDocuments(\"Tile\", updates);\n} else {\n ui.notifications.notify(\"Please select at least one tile.\");\n}\n\n// Move Down\n// By @cole$9640\n\nconst amount = event.ctrlKey ? 0.5 : 1;\nconst tiles = canvas.background.controlled.length === 0 ? canvas.foreground.controlled : canvas.background.controlled;\nif (tiles.length) {\n const updates = tiles\n .filter((tile) => !tile.data.locked)\n .map((tile) => ({\n _id: tile.id,\n y: tile.y + amount,\n }));\n canvas.scene.updateEmbeddedDocuments(\"Tile\", updates);\n} else {\n ui.notifications.notify(\"Please select at least one tile.\");\n}\n\n// Move Right\n// By @cole$9640\n\nconst amount = event.ctrlKey ? 0.5 : 1;\nconst tiles = canvas.background.controlled.length === 0 ? canvas.foreground.controlled : canvas.background.controlled;\nif (tiles.length) {\n const updates = tiles\n .filter((tile) => !tile.data.locked)\n .map((tile) => ({\n _id: tile.id,\n x: tile.x + amount,\n }));\n canvas.scene.updateEmbeddedDocuments(\"Tile\", updates);\n} else {\n ui.notifications.notify(\"Please select at least one tile.\");\n}\n\n// Move Left\n// By @cole$9640\n\nconst amount = event.ctrlKey ? -0.5 : -1;\nconst tiles = canvas.background.controlled.length === 0 ? canvas.foreground.controlled : canvas.background.controlled;\nif (tiles.length) {\n const updates = tiles\n .filter((tile) => !tile.data.locked)\n .map((tile) => ({\n _id: tile.id,\n x: tile.x + amount,\n }));\n canvas.scene.updateEmbeddedDocuments(\"Tile\", updates);\n} else {\n ui.notifications.notify(\"Please select at least one tile.\");\n}\n\n// Rotate Left\n// Original by @Norc$5108, updated and refined by @cole$9640 & @Drunemeton$7955\n\nconst amount = event.ctrlKey ? -0.5 : -1;\nconst tiles = canvas.background.controlled.length === 0 ? canvas.foreground.controlled : canvas.background.controlled;\nif (tiles.length) {\n const updates = tiles\n .filter((tile) => !tile.data.locked)\n .map((tile) => ({\n _id: tile.id, \n rotation: tile.data.rotation + amount \n }));\n canvas.scene.updateEmbeddedDocuments(\"Tile\", updates);\n} else {\n ui.notifications.notify(\"Please select at least one tile.\");\n}\n\n// Rotate Right\n// Original by @Norc$5108, updated and refined by @cole$9640 & @Drunemeton$7955\n\nconst amount = event.ctrlKey ? 0.5 : 1;\nconst tiles = canvas.background.controlled.length === 0 ? canvas.foreground.controlled : canvas.background.controlled;\nif (tiles.length) {\n const updates = tiles\n .filter((tile) => !tile.data.locked)\n .map((tile) => ({\n _id: tile.id, \n rotation: tile.data.rotation + amount \n }));\n canvas.scene.updateEmbeddedDocuments(\"Tile\", updates);\n} else {\n ui.notifications.notify(\"Please select at least one tile.\");\n}","folder":null,"sort":0,"permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"flags":{}}
{"_id":"qcWEVrOYfJAOa5Rr","name":"Rolltables","type":"script","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","scope":"global","command":"/* \n * Macro: GeekDad's Table Roller\n * Version: 2\n * Updated: 23-12-2021 by Freeze\n * Description: A nice friendly Better Tables compatible table roller that can draw mass quantities from tables much faster than the UI.\n*/\n\nfunction getTableNames() {\n let tables = [];\n game.tables.forEach(table => {\n tables.push({ key: table.id, name: table.name });\n });\n\n return tables;\n}\n\nasync function rollOnTable(tableKey, numRolls, betterTableState) {\n let table = game.tables.get(tableKey);\n await table.reset();\n if (game.betterTables) {\n let tableType = table.getFlag(\"better-rolltables\", \"table-type\");\n let originalAmount = table.data[\"loot-rolls-amount-input\"];\n await table.update({\"loot-rolls-amount-input\": numRolls});\n switch (betterTableState) {\n case 1:\n await game.betterTables.generateLoot(table);\n break;\n case 2:\n await game.betterTables.addLootToSelectedToken(table);\n break;\n default:\n if (tableType === \"loot\") {\n await game.betterTables.generateChatLoot(table);\n } else {\n await game.betterTables.betterTableRoll(table);\n }\n break;\n }\n await table.update({\"loot-rolls-amount-input\": originalAmount});\n } else {\n table.drawMany(numRolls);\n }\n}\n\nlet tables = getTableNames();\n\nlet content = ``\n\n new Dialog({\n title: `GeekDad's Table Roller`,\n content: content,\n buttons: {\n yes: {\n icon: \"\",\n label: \"Roll it\",\n callback: (html) => {\n let tableKey = html.find(\"select[name='output-tableKey']\").val();\n let numRolls = html.find(\"input[name='output-numberRolls']\").val();\n let betterTableState = 0;\n if (html.find(\"input[name='output-addToActor']:checked\").length > 0) {\n let radioVal = html.find(\"input[name='output-addToActor']:checked\").val();\n betterTableState = radioVal == \"onlyToChat\" ? 0 : \"tableActor\" ? 1 : 2;\n }\n rollOnTable(tableKey, numRolls, betterTableState);\n }\n },\n no: {\n icon: \"\",\n label: 'Cancel'\n }\n },\n default: \"yes\"\n }).render(true);","folder":null,"sort":0,"permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"flags":{"core":{"sourceId":"Macro.u3zoCoIPIcBWb0wO"}}}
{"_id":"qlx6N8QD6QliPQbu","name":"Create Ambient Light","type":"script","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","scope":"global","command":"// Create a (pre-configured) lightsource on the current scene. \n// This example is a blue light for \"activating a stargate.\"\n\ncanvas.scene.createEmbeddedDocuments(\"AmbientLight\", [{\n t: \"l\", // l for local. The other option is g for global.\n x: 1500, // horizontal positioning\n y: 1150, // vertical positioning\n rotation: 0, // the beam direction of the light in degrees (if its angle is less than 360 degrees.) \n config: { \n dim: 20.50, // the total radius of the light, including where it is dim.\n bright: 19.00, // the bright radius of the light\n angle: 360, // the coverage of the light. (Try 30 for a \"spotlight\" effect.)\n \n // Oddly, degrees are counted from the 6 o'clock position.\n color: \"#0080FF\", // Light coloring.\n alpha: 0.5\n } // Light opacity (or \"brightness,\" depending on how you think about it.) \n}]);","folder":null,"sort":0,"permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"flags":{}}
{"_id":"r8G0g61ikT9mJJwF","name":"Move Walls","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"/* From: @(Busy) Gen Kitty (she/her)\nTo move each node on both axes, you need all 4 parameters listed. \nIn this case, he wanted to move all the walls up and to the left and \nthe foundry grid is sorta vertically flipped to what you'd expect, \nwhich is why all of the operators are \"-=\" If you wanted to move them \nin different directions it'd just be a matter of changing the operator \nnext to the equals sign.\n\nEach argument is a node's X or Y position, and each wall segment has two nodes. \n0 = Node 1 X \n1 = Node 1 Y \n2 = Node 2 X \n3 = Node 2 Y\n*/\n\nlet walls = canvas.scene.data.walls.map(w => {\n w = duplicate(w);\n w.c[0] -= 50;\n w.c[1] -= 50;\n w.c[2] -= 50;\n w.c[3] -= 50;\n return w;\n});\ncanvas.scene.update({walls: walls});\n","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]}
-{"_id":"ra2KPziISGyrVcK2","name":"Hide Video Boxes","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"// Hides the camera boxes.\n// Note: this has to be re-ran when the UI refreshes.\n\nlet cameras = document.getElementById(\"camera-views\");\ncameras.style.display = cameras.style.display === \"none\" ? \"flex\" : \"none\";\n","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]}
{"name":"Delete Items Not In Folders","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"/**\n * Clears the actors entity of any entries not in a folder.\n * Change 'actors' to another game entity such as tables, items, macros, etc... to clear items not in directory for those places.\n * Author: KrishMero#1792\n */\n \n game.actors.forEach(t => {\n if (!t.data.folder) {\n t.delete();\n }\n});","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[],"_id":"rhnCyrHldOsjdAQN"}
-{"name":"Journal Update ID With Name","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"/**\n * Replaces the reference id to other items/tables/journals/actors to use their name.\n * Useful for after importing journal records from a compendium that has references to actors/items/etc...\n * Author: @KrishMero#1792\n */\n \ngame.journal.forEach(entry => {\n let content = entry.data.content;\n let matches = content.match(/@\\w*\\[\\w*\\]/g);\n // now we have an array of things such as @Actor[5c8HWfrpvRV4XtZ1]\n let uniqueMatches = matches\n .filter((value, index, self) => self.indexOf(value) === index) //unique matches\n .forEach(str => {\n let arrayData = str.slice(1, -1).split('['); // cut off the @ and ] then make [0] the type and [1] the id.\n // since the reference may not match directly with the game entity type, lets look that up.\n let entityType = getEntityType(arrayData[0]);\n let id = arrayData[1];\n // with the id and our entity type, look up the name of the entry.\n let name = game[entityType].get(id)?.name;\n if (!name) {\n return ui.notifications.error(`Could not find any record for the entity type ${entityType} with the id of ${id}`);\n }\n\n // replace the ID with the name.\n console.log(`updating ${id} with ${name}`);\n\n let regEx = new RegExp(id, 'g');\n content.replace(regEx, name);\n }); \n entry.update({ content });\n});\n\nfunction getEntityType(entity) {\n switch (entity) {\n case 'JournalEntry': return 'journal';\n case 'RollTable': return 'tables';\n default: return entity.toLowerCase() + 's';\n }\n}\n","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[],"_id":"tGXa6Bd79cHibPtM"}
{"name":"Compendium Set Lock and Visibility","type":"script","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","scope":"global","command":"/* \n* Made by foundry discor user: Freeze#2689\n* this Macro is for people that want to change the visibility and/or lock on their Compendium,\n* or all of their Compendia at once.\n* As always when doing mass changes to your world. MAKE A BACKUP, you were warned.\n* That said I anticipate no issues, just put this code in a macro on your hotbar and click.\n*/\n\nasync function changeSetting(lock, priv, key){\n let value = game.settings.get(\"core\", \"compendiumConfiguration\");\n value[key] = {private: priv, locked: lock};\n await game.settings.set(\"core\", \"compendiumConfiguration\", value);\n}\nasync function changeAllSettings(lock, priv) {\n let value = {};\n for(let pack of game.packs) {\n value[pack.collection] = {private: priv, locked: lock};\n }\n await game.settings.set(\"core\", \"compendiumConfiguration\", value);\n}\n\n\n\nfunction onChange(html) {\n let pack = game.packs.get(html.value);\n // console.log(pack)\n if(pack.private){\n if (!$(\"#compendia-changer-dialog .private-checkbox\").prop(\"checked\")){\n $(\"#compendia-changer-dialog .private-checkbox\").prop(\"checked\", true);\n }\n }\n else {\n if ($(\"#compendia-changer-dialog .private-checkbox\").prop(\"checked\")){\n $(\"#compendia-changer-dialog .private-checkbox\").prop(\"checked\", false);\n }\n }\n if(pack.locked){\n if (!$(\"#compendia-changer-dialog .locked-checkbox\").prop(\"checked\")){\n $(\"#compendia-changer-dialog .locked-checkbox\").prop(\"checked\", true);\n }\n }\n else {\n if ($(\"#compendia-changer-dialog .locked-checkbox\").prop(\"checked\")){\n $(\"#compendia-changer-dialog .locked-checkbox\").prop(\"checked\", false);\n }\n }\n \n}\n\n\nlet packs = game.packs.contents;\nlet options = packs.reduce((acc, p) => acc += ``, ``);\nlet checkedPrivate = packs[0].private ? \"checked\" : \"\";\nlet checkedLocked = packs[0].locked ? \"checked\" : \"\";\nconst content = ``;\n\nnew Dialog({\n title: \"Set locked / private for Compendia\",\n content,\n buttons: {\n change: {\n label: \"Change!\",\n callback: (html) => {\n const lock = html.find(\"[name=locked-setting]\")[0].checked;\n const priv = html.find(\"[name=private-setting\")[0].checked;\n const key = html.find(\"[name=pack-key]\")[0].value;\n changeSetting(lock, priv, key);\n }\n },\n changeAll: {\n label: \"Change ALL!\",\n callback: (html) => {\n const lock = html.find(\"[name=locked-setting]\")[0].checked;\n const priv = html.find(\"[name=private-setting\")[0].checked;\n changeAllSettings(lock, priv);\n }\n },\n cancel: {\n label: \"Cancel\"\n }\n },\n default: \"cancel\"\n},\n{\n id: \"compendia-changer-dialog\"\n}).render(true);\n\nawait new Promise(resolve => {setTimeout(resolve, 150)});\n$(document).ready(function () {\n $(\"#compendia-changer-dialog .compendium-select\").change(function (){\n onChange(this);\n });\n});","folder":null,"sort":0,"permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"flags":{"core":{"sourceId":"Macro.jkc0erfMqxJAciN0"}},"_id":"tSqe0A9bUTbsJsgI"}
{"name":"Show Modules","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"/**\n * Show Modules - Shows currently installed modules in foundry. Added on behalf of @vance\n */\nlet mods = '';\ngame.modules.forEach(m => {\n let a = m.active ? 'Enabled' : 'Disabled';\n mods = mods.concat(`${m.id}: ${a}\\n`);\n});\n\nlet d = new Dialog({\n title: `Enabled Mods`,\n content: ``,\n buttons: {\n copy: {\n label: `Copy to clipboard`,\n callback: () => {\n $(\"#modslist\").select();\n document.execCommand('copy');\n }\n },\n close: {\n icon: \"\",\n label: `Close`\n },\n },\n default: \"close\",\n close: () => {}\n});\n\nd.render(true);","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[],"_id":"u71xIHwO8uVaLS8o"}
{"_id":"wosXzUFEMQLD84so","name":"Ambient Light Quick Edit","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"let macroName = \"AmbientLight QuickEditor\"\nlet macroEndLog = \"---------------------------------------------\"\n\nlet i=0;\nlet lights = canvas.lighting.objects.children;\nlet lightSelected = lights[0];\nlet selectOptions = \"\";\nlet lightSelectedAngle = 0;\nlet lightSelectedBright = 0;\nlet lightSelectedDim = 0;\nlet lightSelectedRotation = 0;\nlet lightSelectedTintAlpha = 1;\nlet lightSelectedTintColor = \"\";\n\nconsole.log(\"---------------------------------------------\");\nconsole.log(`${macroName} by PaperPunk`);\nconsole.log(\"---------------------------------------------\");\nconsole.log(`${macroName} | Start`);\n\nconst drawingDetails = {\n author: game.user._id,\n fillAlpha: 0,\n fillColor: \"#808080\",\n fillType: 1,\n fontFamily: \"FontAwesome\",\n fontSize: 24,\n height: 48,\n hidden: false,\n locked: false,\n rotation: 0,\n strokeAlpha: 1,\n strokeColor: \"#000000\",\n strokeWidth: 2,\n text: i,\n textAlpha: 1,\n textColor: \"#ffffff\",\n type: \"r\",\n width: 48,\n //x: 250,\n x: lightSelected.x-24,\n //y: 250\n y: lightSelected.y+25\n};\n\n//let d = Drawing.create(drawingDetails);\n//d.update({\"x\": lights[i].x-24, \"y\": lights[i].y+25, \"text\": i});\n\nfor (i= 0; i< lights.length; i++) {\n selectOptions += ``;\n}\n\nconst htmlLightSelection = `\n \n `;\n\nlet dialogSelector = new Dialog({\n title: `${macroName}`,\n content: htmlLightSelection,\n buttons: {\n confirm: {\n icon: \"\",\n label: `Confirm`,\n callback: htmlLightSelection => { \n lightSelected = (htmlLightSelection.find('[name=\"light-selector\"]')[0].value)\n lightSelectedAngle = lights[lightSelected].data.angle;\n lightSelectedBright = lights[lightSelected].data.bright;\n lightSelectedDim = lights[lightSelected].data.dim;\n lightSelectedRotation = lights[lightSelected].data.rotation;\n lightSelectedTintAlpha = lights[lightSelected].data.tintAlpha;\n lightSelectedTintColor = lights[lightSelected].data.tintColor;\n //console.log(`${macroName} | lightSelected = ${lightSelected}`);\n //console.log(`${macroName} | lightSelectedBright = ${lightSelectedBright}`);\n dialogEditor.render(true);\n }\n },\n cancel: {\n icon: \"\",\n label: `Cancel`,\n callback: () => {\n console.log(`${macroName} | Goodbye`);\n console.log(macroEndLog);\n }\n },\n },\n default: \"cancel\",\n //close: () => console.log(\"AmbientLight QuickEditor | Dialog Window Closed\")\n});\n\nlet dialogEditor = new Dialog({\n title: `${macroName}`,\n content: `