diff --git a/packs/macros-5e.db b/packs/macros-5e.db index bf8ae86..ae715be 100644 --- a/packs/macros-5e.db +++ b/packs/macros-5e.db @@ -2,7 +2,7 @@ {"name":"Bless","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"// new build for bless macro by Penguin#0949 with help from Kotetsushin#7680\n// version beta 3.1.1 for workgroups\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 Blessd4 = '+1d4';\nlet bless = '';\nlet chatMsg = '';\nlet macroActor = actor;\nlet macroToken = token;\n\n//identify token\nif (macroToken === undefined || macroToken === null) {\n ui.notifications.warn(\"Please select a token first.\");\n} else {\n// grab curent global states\n\tlet mwak = JSON.parse(JSON.stringify(macroActor.data.data.bonuses.mwak.attack));\n\tlet rwak = JSON.parse(JSON.stringify(macroActor.data.data.bonuses.rwak.attack));\n\tlet msak = JSON.parse(JSON.stringify(macroActor.data.data.bonuses.msak.attack));\n\tlet rsak = JSON.parse(JSON.stringify(macroActor.data.data.bonuses.rsak.attack));\n\tlet abilities = JSON.parse(JSON.stringify(macroActor.data.data.bonuses.abilities.save));\n\tif(mwak.includes(Blessd4) && rwak.includes(Blessd4) && msak.includes(Blessd4) && rsak.includes(Blessd4) && abilities.includes(Blessd4)){\n\t\tbless = true;\n\t}\n// If not already bless\t\n\tif (bless == false || bless === null || bless === undefined || bless == \"\") {\t\n// toggle bless icon\n\t\tmacroToken.toggleEffect(blessIconPath); \n// anounce to chat\n\t\tchatMsg = `${macroActor.name} ${blessMsg}`;\n// add bless bonus\n\t\tconsole.log('adding bless modifiers to global bonuses');\n\t\tlet obj = {};\n\t\tobj['data.bonuses.mwak.attack'] = mwak + Blessd4;\n\t\tobj['data.bonuses.rwak.attack'] = rwak + Blessd4;\n\t\tobj['data.bonuses.msak.attack'] = msak + Blessd4;\n\t\tobj['data.bonuses.rsak.attack'] = rsak + Blessd4;\n\t\tobj['data.bonuses.abilities.save'] = abilities + Blessd4;\n\t\tmacroActor.update(obj);\n// if already bless\t\n\t}\telse if (bless == true) {\n// toggle bless icon\n\t\ttoken.toggleEffect(blessIconPath); \t\t\n// anounce to chat\n\t\tchatMsg = `${macroActor.name} ${endblessMsg}`;\n// remove bless bonus\n\t\tconsole.log('resetting global bonuses for bless');\n\t\tlet obj = {};\n\t\tvar tmp = JSON.parse(JSON.stringify(macroActor.data.data.bonuses.mwak.attack));\n\t\tvar tmpLength = tmp.indexOf(Blessd4);\n tmp = tmp.substring(0, tmpLength) + tmp.substring(tmpLength+4, tmp.length);\n\t\tobj['data.bonuses.mwak.attack'] = tmp;\n\t\ttmp = JSON.parse(JSON.stringify(macroActor.data.data.bonuses.rwak.attack));\n\t\ttmpLength = tmp.indexOf(Blessd4);\n tmp = tmp.substring(0, tmpLength) + tmp.substring(tmpLength+4, tmp.length);\n\t\tobj['data.bonuses.rwak.attack'] = tmp;\n\t\ttmp = JSON.parse(JSON.stringify(macroActor.data.data.bonuses.msak.attack));\n\t\ttmpLength = tmp.indexOf(Blessd4);\n tmp = tmp.substring(0, tmpLength) + tmp.substring(tmpLength+4, tmp.length);\n\t\tobj['data.bonuses.msak.attack'] = tmp;\n\t\ttmp = JSON.parse(JSON.stringify(macroActor.data.data.bonuses.rsak.attack));\n\t\ttmpLength = tmp.indexOf(Blessd4);\n tmp = tmp.substring(0, tmpLength) + tmp.substring(tmpLength+4, tmp.length);\n\t\tobj['data.bonuses.rsak.attack'] = tmp;\n\t\ttmp = JSON.parse(JSON.stringify(macroActor.data.data.bonuses.abilities.save));\n\t\ttmpLength = tmp.indexOf(Blessd4);\n tmp = tmp.substring(0, tmpLength) + tmp.substring(tmpLength+4, tmp.length);\n\t\tobj['data.bonuses.abilities.save'] = tmp;\n\t\tmacroActor.update(obj);\n\t}\n}\n \t\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","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[],"_id":"50p0yXEpxXa9aAYJ"} {"name":"Random Cutting Words","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"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 = () => {\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.entities.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 = table.roll();\n let result = roll.results[0];\n mockery = result.text;\n table.updateEmbeddedEntity(\"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}]].

`\n messageContent += `

${token.name} exclaims \"${mockery}\"

`\n messageContent += `
Cutting Words\n

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();\n","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[],"_id":"AbzZdXi97q8oHOUn"} {"name":"All Token’s Passive Perception","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"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);\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 if (count.actor.data.data.hasOwnProperty('skills')) {\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\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.entities.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 }\n","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[],"_id":"DBlk2wFOpxxhM9eC"} -{"name":"Guidance","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"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 3.1.1 for workgroups .1\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/windmill.svg';\nlet blessMsg = ' is guided!';\nlet endblessMsg = ' is no longer guided.';\n\n//fixed declarations DO NOT MODIFY\nlet Guidd4 = '+1d4';\nlet guided = '';\nlet chatMsg = '';\nlet macroActor = actor;\nlet macroToken = token;\n\n//identify token\nif (macroToken === undefined || macroToken === null) {\n ui.notifications.warn(\"Please select a token first.\");\n} else {\n// grab curent global states\n\tlet mwak = JSON.parse(JSON.stringify(macroActor.data.data.bonuses.abilities.check));\n\tif(mwak.includes(Guidd4)){\n\t\tguided = true;\n\t}\n// If not already guided\t\n\tif (guided == false || guided === null || guided === undefined || guided == \"\") {\t\n// toggle bless icon\n\t\tmacroToken.toggleEffect(blessIconPath); \n// anounce to chat\n\t\tchatMsg = `${macroActor.name} ${blessMsg}`;\n// add bless bonus\n\t\tconsole.log('adding bless modifiers to global bonuses');\n\t\tlet obj = {};\n\t\tobj['data.bonuses.abilities.check'] = mwak + Guidd4;\n\t\tmacroActor.update(obj);\n// if already guided\t\n\t}\telse if (guided == true) {\n// toggle bless icon\n\t\ttoken.toggleEffect(blessIconPath); \t\t\n// anounce to chat\n\t\tchatMsg = `${macroActor.name} ${endblessMsg}`;\n// remove bless bonus\n\t\tconsole.log('resetting global bonuses for bless');\n\t\tlet obj = {};\n\t\tvar tmp = JSON.parse(JSON.stringify(macroActor.data.data.bonuses.abilities.check));\n\t\tvar tmpLength = tmp.indexOf(Guidd4);\n tmp = tmp.substring(0, tmpLength) + tmp.substring(tmpLength+4, tmp.length);\n\t\tobj['data.bonuses.abilities.check'] = tmp;\n\t\tmacroActor.update(obj);\n\t}\n}\n \t\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","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[],"_id":"GlWj7z7p2V4JKXMx"} +{"_id":"GlWj7z7p2V4JKXMx","name":"Guidance","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"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// Edited by DrWiFi to roll guidance on skill checks as well, removed references to bless spell\n// version beta 3.1.1 for workgroups .2\n\n// user notes\n// this macro is intended for use by the recipient of the guidance spell in D&D 5e on Forge VTT\n// N.B. every recipient will need to use this macro independently on their own Actor/token.\n\n//user modifiable declarations CHANGE AT YOUR OWN RISK\nconst guidanceIconPath = 'icons/svg/windmill.svg';\nlet guidanceMsg = ' is guided!';\nlet endguidanceMsg = ' is no longer guided.';\n\n//fixed declarations DO NOT MODIFY\nlet Guidd4 = '+1d4';\nlet guided = '';\nlet chatMsg = '';\nlet macroActor = actor;\nlet macroToken = token;\n\n//identify token\nif (macroToken === undefined || macroToken === null) {\n ui.notifications.warn(\"Please select a token first.\");\n} else {\n// grab curent global states\n\tlet abilities = JSON.parse(JSON.stringify(macroActor.data.data.bonuses.abilities.check));\n\tlet skills = JSON.parse(JSON.stringify(macroActor.data.data.bonuses.abilities.skill));\n\tif(abilities.includes(Guidd4)){\n\t\tguided = true;\n\t}\n// If not already guided\t\n\tif (guided == false || guided === null || guided === undefined || guided == \"\") {\t\n// toggle guidance icon\n\t\tmacroToken.toggleEffect(guidanceIconPath); \n// anounce to chat\n\t\tchatMsg = `${macroActor.name} ${guidanceMsg}`;\n// add guidance bonus\n\t\tconsole.log('adding guidance modifiers to global bonuses');\n\t\tlet obj = {};\n\t\tobj['data.bonuses.abilities.check'] = abilities + Guidd4;\n\t\tobj['data.bonuses.abilities.skill'] = skills + Guidd4;\n\t\tmacroActor.update(obj);\n// if already guided\t\n\t}\telse if (guided == true) {\n// toggle guidance icon\n\t\ttoken.toggleEffect(guidanceIconPath); \t\t\n// anounce to chat\n\t\tchatMsg = `${macroActor.name} ${endguidanceMsg}`;\n// remove guidance bonus\n\t\tconsole.log('resetting global bonuses for guidance');\n\t\tlet obj = {};\n\t\tvar tmp = JSON.parse(JSON.stringify(macroActor.data.data.bonuses.abilities.check));\n\t\tvar tmpLength = tmp.indexOf(Guidd4);\n tmp = tmp.substring(0, tmpLength) + tmp.substring(tmpLength+4, tmp.length);\n\t\tobj['data.bonuses.abilities.check'] = tmp;\n\t\tvar tmp = JSON.parse(JSON.stringify(macroActor.data.data.bonuses.abilities.skill));\n\t\tvar tmpLength = tmp.indexOf(Guidd4);\n tmp = tmp.substring(0, tmpLength) + tmp.substring(tmpLength+4, tmp.length);\n\t\tobj['data.bonuses.abilities.skill'] = tmp;\n\t\tmacroActor.update(obj);\n\t}\n}\n \t\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":[]} {"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}`) !== undefined) {\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":"KWCb3y9I2P45LDWU"} {"_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":"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":[]} @@ -18,4 +18,4 @@ {"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"} {"name":"Tool Proficiency","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"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.entities.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
\n \n \n
\n
`;\n\n new Dialog({\n title: 'Which tool?',\n content: template,\n buttons: {\n ok: {\n icon: '',\n label: \"OK\",\n callback: async (html) => {\n let selection = html.find(\"#selectedTool\")[0].value;\n tool = toolsInInventory.find( item => item.name === selection )\n abilityDialog();\n }\n },\n cancel: {\n icon: '',\n label: 'Cancel'\n }\n },\n default: \"cancel\"\n }).render(true);\n })() \n}\n\nelse {\n new Dialog({\n title: 'No Tools!',\n content: '

You don\\'t seem to have any tool with you.

',\n buttons: {\n ok: {\n icon: '',\n label: \"OK\"\n }\n },\n default: \"ok\"\n }).render(true);\n}\n","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[],"_id":"vwfOm1Z1kY4EZS1G"} {"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\tchatMsg = `${macroActor.name} + rageMsg`;\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":[],"_id":"whXLG5afXGwYNXEq"} -{"_id":"GlWj7z7p2V4JKXMx","name":"Guidance","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"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// Edited by DrWiFi to roll guidance on skill checks as well, removed references to bless spell\n// version beta 3.1.1 for workgroups .2\n\n// user notes\n// this macro is intended for use by the recipient of the guidance spell in D&D 5e on Forge VTT\n// N.B. every recipient will need to use this macro independently on their own Actor/token.\n\n//user modifiable declarations CHANGE AT YOUR OWN RISK\nconst guidanceIconPath = 'icons/svg/windmill.svg';\nlet guidanceMsg = ' is guided!';\nlet endguidanceMsg = ' is no longer guided.';\n\n//fixed declarations DO NOT MODIFY\nlet Guidd4 = '+1d4';\nlet guided = '';\nlet chatMsg = '';\nlet macroActor = actor;\nlet macroToken = token;\n\n//identify token\nif (macroToken === undefined || macroToken === null) {\n ui.notifications.warn(\"Please select a token first.\");\n} else {\n// grab curent global states\n\tlet abilities = JSON.parse(JSON.stringify(macroActor.data.data.bonuses.abilities.check));\n\tlet skills = JSON.parse(JSON.stringify(macroActor.data.data.bonuses.abilities.skill));\n\tif(abilities.includes(Guidd4)){\n\t\tguided = true;\n\t}\n// If not already guided\t\n\tif (guided == false || guided === null || guided === undefined || guided == \"\") {\t\n// toggle guidance icon\n\t\tmacroToken.toggleEffect(guidanceIconPath); \n// anounce to chat\n\t\tchatMsg = `${macroActor.name} ${guidanceMsg}`;\n// add guidance bonus\n\t\tconsole.log('adding guidance modifiers to global bonuses');\n\t\tlet obj = {};\n\t\tobj['data.bonuses.abilities.check'] = abilities + Guidd4;\n\t\tobj['data.bonuses.abilities.skill'] = skills + Guidd4;\n\t\tmacroActor.update(obj);\n// if already guided\t\n\t}\telse if (guided == true) {\n// toggle guidance icon\n\t\ttoken.toggleEffect(guidanceIconPath); \t\t\n// anounce to chat\n\t\tchatMsg = `${macroActor.name} ${endguidanceMsg}`;\n// remove guidance bonus\n\t\tconsole.log('resetting global bonuses for guidance');\n\t\tlet obj = {};\n\t\tvar tmp = JSON.parse(JSON.stringify(macroActor.data.data.bonuses.abilities.check));\n\t\tvar tmpLength = tmp.indexOf(Guidd4);\n tmp = tmp.substring(0, tmpLength) + tmp.substring(tmpLength+4, tmp.length);\n\t\tobj['data.bonuses.abilities.check'] = tmp;\n\t\tvar tmp = JSON.parse(JSON.stringify(macroActor.data.data.bonuses.abilities.skill));\n\t\tvar tmpLength = tmp.indexOf(Guidd4);\n tmp = tmp.substring(0, tmpLength) + tmp.substring(tmpLength+4, tmp.length);\n\t\tobj['data.bonuses.abilities.skill'] = tmp;\n\t\tmacroActor.update(obj);\n\t}\n}\n \t\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":[]} +{"_id":"WhAe42txHIkeZk2s","name":"Lay On Hands","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"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\nlet confirmed = false;\nlet actorData = actor || canvas.tokens.controlled[0] || game.user.character;\nlet featData = actorData ? actorData.items.find(i => i.name===\"Lay on Hands\") : null;\n\nif(actorData == null || featData == null) \n ui.notifications.warn(`Selected hero must have \"Lay on Hands\" 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
\n \n \n
\n
\n \n \n
\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 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 new Roll(`${number}`).roll().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 targetActor.update({\"data.attributes.hp.value\" : targetActor.data.data.attributes.hp.value + number});\n }\n\n featUpdate.data.uses.value = featUpdate.data.uses.value - number;\n actorData.updateEmbeddedEntity(\"OwnedItem\", featUpdate);\n };\n }\n }\n }).render(true);\n}","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]} diff --git a/packs/macros-misc.db b/packs/macros-misc.db index e8ee7e6..b84560f 100644 --- a/packs/macros-misc.db +++ b/packs/macros-misc.db @@ -4,12 +4,14 @@ {"_id":"5XA28twwaRBUFEjC","name":"Breathing Lights","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"// Courtesy of @ohporter\n// Cause lightsources to \"breathe,\" expanding and contracting.\n(async () => {\n let min = 5;\n let max = 10;\n if (game.pulsatingLights) {\n game.pulsatingLights = false;\n } else {\n game.pulsatingLights = true;\n let glyphLights = [];\n let glyphColor = \"#5940b5\"\n let scene = game.scenes.active;\n \n canvas.lighting.placeables.forEach(l => { if (l.data.tintColor === glyphColor && l.scene === scene) glyphLights.push(l.id) })\n \n const updates = []\n \n let radius = min;\n let increment = true;\n let interval = setInterval(async () => {\n glyphLights.forEach(id => {\n updates.push({ _id: id, dim: radius, bright: radius/2});\n })\n await scene.updateEmbeddedEntity(\"AmbientLight\", updates);\n \n if (increment) {radius += 1} else {radius -= 1};\n if (radius === max) {increment = false};\n if (radius === min) {increment = true};\n if (!scene.active || !game.pulsatingLights) {\n // Reset to default glow\n glyphLights.forEach(id => {\n updates.push({ _id: id, dim: min, bright: 0});\n })\n await scene.updateEmbeddedEntity(\"AmbientLight\", updates);\n clearInterval(interval);\n }\n }, 200);\n }\n })()","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]} {"_id":"8iEx21HSVOmkbBFE","name":"Combat Tracker AC HP","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"// Adds the actor's AC to the combat tracker. Then toggles between HP and AC\nconst a = \"attributes.ac.value\";\nconst b = \"attributes.hp.value\";\n\nif (game.combat.settings.resource == a) {\n game.settings.set('core', 'combatTrackerConfig', {resource: b, skipDefeated: true});\n} else {\n game.settings.set('core', 'combatTrackerConfig', {resource: a, skipDefeated: true});\n}\nui.combat.updateTrackedResources();\n","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]} {"name":"Equip Unequip Shield","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"/**\n * Equips/unequips an item. Make sure you change the variables at the top (as required).\n * This script will also error check to make sure items exist and tokens are select. \n * Chat and token icon display options can be set as desired.\n * Author: Zapgun\n */\n\nlet itemName = 'Shield'; // <--- Change this to the *exact* item name (capitals count!)\nlet sendToChat = true; // <--- Change to 'true' or 'false' to display a chat message about equipping\nlet displayIcon = true; // <--- Change to 'true' or 'false' to display an effect icon when equipped\nconst effectIconPath = 'icons/svg/shield.svg'; // <--- Add the effect icon you want to appear when equipped\n\nlet toggleResult = false;\n\nif (!actor) {\n ui.notifications.warn('You need to select a token before using this macro!');\n} else {\n\n\tlet myItem = actor.items.find(i => i.name == itemName);\n\tif (myItem != null)\n\t{\n\t\tlet item = actor.getOwnedItem(myItem._id);\n\t\tlet attr = \"data.equipped\";\n\t\tlet equipped = getProperty(item.data, attr);\n\t\tif (sendToChat) {\t\t\t\n\t\t\tif (!equipped) {\n\t\t\t\tchatMessage(actor.name + ' equips their ' + ' ' + itemName+ '');\n\t\t\t} else {\n\t\t\t\tchatMessage(actor.name + ' un-equips their ' + ' ' + itemName + '');\t\t\t\n\t\t\t}\n\t\t}\n\t\titem.update({[attr]: !getProperty(item.data, attr)});\n\t\t\n\t\t// mark/unmark character's token with an effect icon when displayToken is true\n\t\t(async () => { \n\t\t\tif (displayIcon) {\n\t\t\t\ttoggleResult = await token.toggleEffect(effectIconPath);\n\t\t\t\tif (toggleResult == equipped) token.toggleEffect(effectIconPath); \n\t\t\t}\n\t\t})();\n\t\t\n\t} else {\n\t\tui.notifications.warn(\"No item named '\" + itemName + \"' found on character!\");\n\t}\n}\n\nfunction chatMessage(messageContent) {\n\t// create the message\n\tif (messageContent !== '') {\n\t\tlet chatData = {\n\t\t\tuser: game.user._id,\n\t\t\tspeaker: ChatMessage.getSpeaker(),\n\t\t\tcontent: messageContent,\n\t\t};\n\t\tChatMessage.create(chatData, {});\n\t}\n}","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[],"_id":"BqcD9aq88kWU67Uf"} +{"name":"Announce Round Number","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{"core":{"sourceId":"Macro.V1p6LWoKnv7croAd"}},"scope":"global","command":"let messageContent = `
ROUND ${game.combat.round}

`\nChatMessage.create({content: messageContent});","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[],"_id":"CVbowNiiiwqmADGY"} {"_id":"Cp6zQmvC9KSCEo1s","name":"Toggle Playlist","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"// get actual pl data from entries\nlet _playlistArray = game.playlists.entries;\nlet _applyChanges = false;\nlet _raw = `\n\n
\n
\n \n \n
\n
\n`\nlet _html = Handlebars.compile(_raw)\nlet d = new Dialog({\n title: \"Playlist Toggle\",\n content: _html(_playlistArray),\n buttons: {\n toggle: {\n icon: '',\n label: \"Toggle Selected Playlist\",\n callback: () => _applyChanges = true\n },\n },\n default: \"toggle\",\n close: html => {\n if (_applyChanges) {\n let _plName = html.find('[name=\"playlist-selection\"]')[0].value || \"none\";\n let _pl = game.playlists.getName(_plName);\n if (_pl.playing) {\n // turn off\n _pl.stopAll();\n } else {\n // turn on\n _pl.playAll();\n }\n }\n }\n}).render(true);\n","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]} {"_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 \n \n
\n
\n \n \n
\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":[]} {"_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\n
Compendium:
\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":"PJVvR31x5PNkdWFr","name":"Close All Doors","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"chat","flags":{},"scope":"global","command":"/**\n * Closes all doors on the canvas\n * Author: @Atropos#3814\n */\n \ncanvas.walls.updateMany(canvas.scene.data.walls.map(w => {\n return {_id: w._id, ds: w.ds === 1 ? 0 : w.ds};\n}));\n","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]} {"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 targets.push(user.id);\n }\n var messageText = html.find('[name=\"message\"]')[0].value\n }\n\n ChatMessage.create({\n content: messageText,\n whisper: targets\n });\n}\n","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[],"_id":"QTDeg4HOYCAmr4dK"} {"_id":"VXTRsUfomoFN9PVP","name":"Folder Permission","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"// Provides a prompt to set global 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
Permission:
\n \n
\n\n \n`;\n\nconst dialog = new Dialog({\n title: \"Set desired permission\",\n content: form,\n buttons: {\n use: {\n label: \"Apply permissions\",\n callback: applyPermissions\n }\n }\n}).render(true);\n\nfunction applyPermissions(html) {\n const folderName = html.find(`input#folderName`)[0].value;\n const permission = html.find(`select#desiredPermission`)[0].value;\n const recurse = html.find(`input#recurse`)[0].checked;\n \n const folders = game.folders.filter(f => f.name === folderName);\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 repermission(folders[0], permission, recurse);\n ui.notifications.notify(`Desired permissions were set successfully for '${folderName}'.`);\n }\n}\n\nfunction repermission(currentFolder, desiredPermission, recurse) {\n console.debug(\"Repermissioning: \", currentFolder.name);\n \n if (currentFolder.content) {\n currentFolder.content.map(item => {\n let newPermissions = duplicate(item.data.permission);\n newPermissions.default = desiredPermission;\n console.debug(\" Item:\", item.data.name);\n item.update({ permission: newPermissions });\n });\n }\n\n if (currentFolder.children && recurse) {\n currentFolder.children.map(({ data }) => {\n repermission(\n game.folders.entities.filter(f => f.data._id == data._id)[0],\n desiredPermission,\n recurse);\n });\n }\n}\n","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]} +{"name":"Lock and Unlock Players","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{"core":{"sourceId":"Macro.VhzAqG5Abes21JD9"}},"scope":"global","command":"async function updateRoles(from, to) {\n game.users.entities.filter(u => u.role === from).forEach(u => u.update({role:to}))\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)","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[],"_id":"Vhj3ctf5cAufQMnM"} {"name":"Scene Border Walls","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"for (let scene of game.scenes){\n //Goes through the array of scenes and finds the active one.\n if(scene.active == true){\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}\n","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[],"_id":"XIQjNhEFdzPA1mQY"} {"_id":"XNQSxkffgHApZf4A","name":"Import from Compendium","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"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
\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 entity = pack.entity;\n let folder = folderName ? findFolder(folderName, entity) : await createFolder(pack, entity);\n \n if (!folder) return ui.notifications.error(`Your world does not have any ${entity} folders named '${folderName}'.`);\n\n if (remove) removeDataFirst(folder.id, entity);\n if (folder) importPack(pack, entity, folder.id)\n}\n\nasync function importPack(pack, entity, folderId) {\n const entityClass = CONFIG[entity].entityClass;\n const content = await pack.getContent();\n\n const createData = content.map(c => {\n c.data.folder = folderId;\n return c.data;\n });\n entityClass.create(createData);\n}\n\nfunction removeDataFirst(folderId, entity) {\n let type = getEntityType(entity);\n const removeableData = game[type].filter(t => t.data.folder === folderId);\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 let folder = await Folder.create({ name, type, parent: null});\n return folder;\n}\n\nfunction findFolder(folderName, entity)\n{\n return game.folders.find(f => f.name === folderName && f.type === entity)\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":[]} {"name":"Tile XY Adjust","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"//Simple macro to loop through ALL SELECTED TILES and adjust their position by a set amount\n\n//Questions? Ask in Foundry VTT Discord #macro-polo channel. If absolutely needed, ping @Norc$5108\n\nasync function adjustTileXY(tile, xAdjust, yAdjust ) {\n await tile.update({\n x: tile.x + xAdjust,\n y: tile.y + yAdjust,\n });\n}\n\n//check to make sure at least one tile is selected\nif (canvas.tiles.controlled[0]) {\n //loop through all selected tiles\n for (let t of canvas.tiles.controlled) {\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.\n adjustTileXY(t, 1, 1);\n }\n} else {\n ui.notifications.notify(\"Please select at least one tile.\");\n}","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[],"_id":"bD3UuA4ikwbCwtKX"} @@ -22,7 +24,7 @@ {"name":"Scale Grid Size To Inches","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"/**\nAuthor: @stan#1549 (github.com/janssen-io)\n\nDescription:\nThe first time you click the macro, it will prompt you for your screen size (diagonal).\nClicking the macro a second time will automatically scale to 1\"/grid unit (square/hex).\nShift-click the macro to update this value.\n\nNote:\nThe screen size is saved per client. So opening the macro on another device for the first time, will make it prompt for the screen size again.\n */\n\nfunction showDialog(inches, resolve) {\n new Dialog({\n content: `Screen size (inches): `,\n default: 'scale',\n buttons: {\n scale: {\n label: 'Scale',\n callback: html => resolve(+html.find('input').val())\n }\n }\n }).render(true);\n}\n\nfunction scale(screenSizeInches) {\n const diagonal = Math.sqrt(screen.width ** 2 + screen.height ** 2);\n const ppi = diagonal / screenSizeInches;\n console.log(`PPI: ${screenSizeInches}\" screen | ${canvas.grid.size}px per grid unit | ${ppi}px per inch | Scaling to: ${ppi / canvas.grid.size}`);\n canvas.animatePan({ scale: ppi / canvas.grid.size });\n}\n\nconst key = 'stan#1549.scale.screenSize';\nconst storedScreenSize = localStorage.getItem(key);\nconst shouldUpdatePPI = !storedScreenSize || event.shiftKey;\n\nconst getScreenSize = new Promise(resolve => {\n if (shouldUpdatePPI) {\n showDialog(storedScreenSize || 42, resolve);\n } else {\n resolve(storedScreenSize);\n }\n});\n\ngetScreenSize.then(inches => {\n scale(inches);\n localStorage.setItem(key, inches);\n});\n\n\n","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[],"_id":"gq8UDK94ykfMViNT"} {"_id":"iLlJKBjrtOMNxlv6","name":"Tile Toggle Hidden Status","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"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\n\nif (canvas.tiles.controlled[0]) {\n canvas.tiles.controlled.forEach(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 tile.update({ hidden: v });\n });\n} else {\n ui.notifications.notify(\"Please select at least one tile.\");\n}\n","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]} {"_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
\n
\n` : '';\n\nnew Dialog({\n\ttitle: `Share Image via URL`,\n\tcontent: `\n\t\t
\n\t\t\t${selectOptions}\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t
\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":[]} -{"name":"Fine Tile Control","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"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\nlet amount = event.ctrlKey ? -0.5 : -1;\n\nif (canvas.tiles.controlled.length) {\n const updates = canvas.tiles.controlled\n .filter((tile) => !tile.data.locked)\n .map((tile) => ({\n _id: tile.id,\n y: tile.y + amount,\n }));\n canvas.tiles.updateMany(updates);\n} else {\n ui.notifications.notify(\"Please select at least one tile.\");\n}\n\n// Move Down\n// By @cole$9640\n\nlet amount = event.ctrlKey ? 0.5 : 1;\n\nif (canvas.tiles.controlled.length) {\n const updates = canvas.tiles.controlled\n .filter((tile) => !tile.data.locked)\n .map((tile) => ({\n _id: tile.id,\n y: tile.y + amount,\n }));\n canvas.tiles.updateMany(updates);\n} else {\n ui.notifications.notify(\"Please select at least one tile.\");\n}\n\n// Move Left\n// By @cole$9640\n\nlet amount = event.ctrlKey ? 0.5 : 1;\n\nif (canvas.tiles.controlled.length) {\n const updates = canvas.tiles.controlled\n .filter((tile) => !tile.data.locked)\n .map((tile) => ({\n _id: tile.id,\n x: tile.x + amount,\n }));\n canvas.tiles.updateMany(updates);\n} else {\n ui.notifications.notify(\"Please select at least one tile.\");\n}\n\n// Move Right\n// By @cole$9640\n\nlet amount = event.ctrlKey ? -0.5 : -1;\n\nif (canvas.tiles.controlled.length) {\n const updates = canvas.tiles.controlled\n .filter((tile) => !tile.data.locked)\n .map((tile) => ({\n _id: tile.id,\n x: tile.x + amount,\n }));\n canvas.tiles.updateMany(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\nlet amount = event.ctrlKey ? -0.5 : -1;\n\nif (canvas.tiles.controlled[0]) {\n canvas.tiles.controlled.forEach((ti) => {\n if (ti.data.locked) return;\n let r = ti.data.rotation;\n ti.update({ rotation: r + amount });\n });\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\nlet amount = event.ctrlKey ? 0.5 : 1;\n\nif (canvas.tiles.controlled[0]) {\n canvas.tiles.controlled.forEach((ti) => {\n if (ti.data.locked) return;\n let r = ti.data.rotation;\n ti.update({ rotation: r + amount });\n });\n} else {\n ui.notifications.notify(\"Please select at least one tile.\");\n}\n","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[],"_id":"mzkDNKmsOb9WwpIO"} +{"_id":"mzkDNKmsOb9WwpIO","name":"Fine Tile Control","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"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\nlet amount = event.ctrlKey ? -0.5 : -1;\n\nif (canvas.tiles.controlled.length) {\n const updates = canvas.tiles.controlled\n .filter((tile) => !tile.data.locked)\n .map((tile) => ({\n _id: tile.id,\n y: tile.y + amount,\n }));\n canvas.tiles.updateMany(updates);\n} else {\n ui.notifications.notify(\"Please select at least one tile.\");\n}\n\n// Move Down\n// By @cole$9640\n\nlet amount = event.ctrlKey ? 0.5 : 1;\n\nif (canvas.tiles.controlled.length) {\n const updates = canvas.tiles.controlled\n .filter((tile) => !tile.data.locked)\n .map((tile) => ({\n _id: tile.id,\n y: tile.y + amount,\n }));\n canvas.tiles.updateMany(updates);\n} else {\n ui.notifications.notify(\"Please select at least one tile.\");\n}\n\n// Move Right\n// By @cole$9640\n\nlet amount = event.ctrlKey ? 0.5 : 1;\n\nif (canvas.tiles.controlled.length) {\n const updates = canvas.tiles.controlled\n .filter((tile) => !tile.data.locked)\n .map((tile) => ({\n _id: tile.id,\n x: tile.x + amount,\n }));\n canvas.tiles.updateMany(updates);\n} else {\n ui.notifications.notify(\"Please select at least one tile.\");\n}\n\n// Move Left\n// By @cole$9640\n\nlet amount = event.ctrlKey ? -0.5 : -1;\n\nif (canvas.tiles.controlled.length) {\n const updates = canvas.tiles.controlled\n .filter((tile) => !tile.data.locked)\n .map((tile) => ({\n _id: tile.id,\n x: tile.x + amount,\n }));\n canvas.tiles.updateMany(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\nlet amount = event.ctrlKey ? -0.5 : -1;\n\nif (canvas.tiles.controlled[0]) {\n canvas.tiles.controlled.forEach((ti) => {\n if (ti.data.locked) return;\n let r = ti.data.rotation;\n ti.update({ rotation: r + amount });\n });\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\nlet amount = event.ctrlKey ? 0.5 : 1;\n\nif (canvas.tiles.controlled[0]) {\n canvas.tiles.controlled.forEach((ti) => {\n if (ti.data.locked) return;\n let r = ti.data.rotation;\n ti.update({ rotation: r + amount });\n });\n} else {\n ui.notifications.notify(\"Please select at least one tile.\");\n}","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]} {"_id":"qlx6N8QD6QliPQbu","name":"Create Ambient Light","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"// Create a (pre-configured) lightsource on the current scene. \n// This example is a blue light for \"activating a stargate.\"\n\nAmbientLight.create({\n t: \"l\", // l for local. The other option is g for global.\n x: 1100, // horizontal positioning\n y: 1150, // vertical positioning\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 rotation: 0, // the beam direction of the light in degrees (if its angle is less than 360 degrees.) \n // Oddly, degrees are counted from the 6 o'clock position.\n tintColor: \"#0080FF\", // Light coloring.\n tintAlpha: 0.5 // Light opacity (or \"brightness,\" depending on how you think about it.) \n});\n","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]} {"_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":[]} @@ -30,6 +32,4 @@ {"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":"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

Select your light.

\n
\n \n \n
\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: `

Edit your light.

\n

Emission Angle: ${lightSelectedAngle}

\n

Bright light distance: ${lightSelectedBright}

\n

Dim light distance: ${lightSelectedDim}

\n

Rotation CW from down: ${lightSelectedRotation}

\n

Tint Alpha: ${lightSelectedAngle}

\n

Tint Color HexCode: ${lightSelectedAngle}

`,\n buttons: {\n rot5cw: {\n icon: \"\",\n label: `Rotate 5* CW`,\n callback: () => { \n let rot = lights[lightSelected].data.rotation;\n lights[lightSelected].update({\"rotation\":rot+5});\n dialogEditor.render(true);\n }\n },\n rot15cw: {\n icon: \"\",\n label: `Rotate 15* CW`,\n callback: () => { \n let rot = lights[lightSelected].data.rotation;\n lights[lightSelected].update({\"rotation\":rot+15});\n dialogEditor.render(true);\n }\n },\n rot45cw: {\n icon: \"\",\n label: `Rotate 45* CW`,\n callback: () => { \n let rot = lights[lightSelected].data.rotation;\n lights[lightSelected].update({\"rotation\":rot+45});\n dialogEditor.render(true);\n }\n },\n rot5ccw: {\n icon: \"\",\n label: `Rotate 5* CCW`,\n callback: () => { \n let rot = lights[lightSelected].data.rotation;\n lights[lightSelected].update({\"rotation\":rot-5});\n dialogEditor.render(true);\n }\n },\n rot15ccw: {\n icon: \"\",\n label: `Rotate 15* CCW`,\n callback: () => { \n let rot = lights[lightSelected].data.rotation;\n lights[lightSelected].update({\"rotation\":rot-15});\n dialogEditor.render(true);\n }\n },\n rot45ccw: {\n icon: \"\",\n label: `Rotate 45* CCW`,\n callback: () => { \n let rot = lights[lightSelected].data.rotation;\n lights[lightSelected].update({\"rotation\":rot-45});\n dialogEditor.render(true);\n }\n },\n brightup: {\n icon: \"\",\n label: `Increase Bright by 5`,\n callback: () => { \n let bright = lights[lightSelected].data.bright;\n lights[lightSelected].update({\"bright\":bright+5});\n dialogEditor.render(true);\n }\n },\n brightdown: {\n icon: \"\",\n label: `Decrease Bright by 5`,\n callback: () => { \n let bright = lights[lightSelected].data.bright;\n lights[lightSelected].update({\"bright\":bright-5});\n dialogEditor.render(true);\n }\n },\n brightoff: {\n icon: \"\",\n label: `Remove Bright Light`,\n callback: () => { \n lights[lightSelected].update({\"bright\":0});\n dialogEditor.render(true);\n }\n },\n dimup: {\n icon: \"\",\n label: `Increase Dim by 5`,\n callback: () => { \n let dim = lights[lightSelected].data.dim;\n lights[lightSelected].update({\"dim\":dim+5});\n dialogEditor.render(true);\n }\n },\n dimdown: {\n icon: \"\",\n label: `Decrease Dim by 5`,\n callback: () => { \n let dim = lights[lightSelected].data.dim;\n lights[lightSelected].update({\"dim\":dim-5});\n dialogEditor.render(true);\n }\n },\n dimoff: {\n icon: \"\",\n label: `Remove Dim Light`,\n callback: () => { \n lights[lightSelected].update({\"dim\":0});\n dialogEditor.render(true);\n }\n },\n emit15: {\n icon: \"\",\n label: `Emission Angle 15*`,\n callback: () => { \n lights[lightSelected].update({\"angle\":15});\n dialogEditor.render(true);\n }\n },\n emit45: {\n icon: \"\",\n label: `Emission Angle 45*`,\n callback: () => { \n lights[lightSelected].update({\"angle\":45});\n dialogEditor.render(true);\n }\n },\n emit90: {\n icon: \"\",\n label: `Emission Angle 90*`,\n callback: () => { \n lights[lightSelected].update({\"angle\":90});\n dialogEditor.render(true);\n }\n },\n emit180: {\n icon: \"\",\n label: `Emission Angle 180*`,\n callback: () => { \n lights[lightSelected].update({\"angle\":180});\n dialogEditor.render(true);\n }\n },\n emit270: {\n icon: \"\",\n label: `Emission Angle 270*`,\n callback: () => { \n lights[lightSelected].update({\"angle\":270});\n dialogEditor.render(true);\n }\n },\n emit360: {\n icon: \"\",\n label: `Emission Angle 360*`,\n callback: () => { \n lights[lightSelected].update({\"angle\":360});\n dialogEditor.render(true);\n }\n },\n back: {\n icon: \"\",\n label: `Back`,\n callback: () => dialogSelector.render(true)\n },\n close: {\n icon: \"\",\n label: `Close`\n },\n },\n default: \"close\",\n close: () => {\n console.log(`${macroName} | Goodbye`);\n console.log(macroEndLog);\n }\n});\n\ndialogSelector.render(true);\n","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]} -{"_id":"mzkDNKmsOb9WwpIO","name":"Fine Tile Control","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"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\nlet amount = event.ctrlKey ? -0.5 : -1;\n\nif (canvas.tiles.controlled.length) {\n const updates = canvas.tiles.controlled\n .filter((tile) => !tile.data.locked)\n .map((tile) => ({\n _id: tile.id,\n y: tile.y + amount,\n }));\n canvas.tiles.updateMany(updates);\n} else {\n ui.notifications.notify(\"Please select at least one tile.\");\n}\n\n// Move Down\n// By @cole$9640\n\nlet amount = event.ctrlKey ? 0.5 : 1;\n\nif (canvas.tiles.controlled.length) {\n const updates = canvas.tiles.controlled\n .filter((tile) => !tile.data.locked)\n .map((tile) => ({\n _id: tile.id,\n y: tile.y + amount,\n }));\n canvas.tiles.updateMany(updates);\n} else {\n ui.notifications.notify(\"Please select at least one tile.\");\n}\n\n// Move Right\n// By @cole$9640\n\nlet amount = event.ctrlKey ? 0.5 : 1;\n\nif (canvas.tiles.controlled.length) {\n const updates = canvas.tiles.controlled\n .filter((tile) => !tile.data.locked)\n .map((tile) => ({\n _id: tile.id,\n x: tile.x + amount,\n }));\n canvas.tiles.updateMany(updates);\n} else {\n ui.notifications.notify(\"Please select at least one tile.\");\n}\n\n// Move Left\n// By @cole$9640\n\nlet amount = event.ctrlKey ? -0.5 : -1;\n\nif (canvas.tiles.controlled.length) {\n const updates = canvas.tiles.controlled\n .filter((tile) => !tile.data.locked)\n .map((tile) => ({\n _id: tile.id,\n x: tile.x + amount,\n }));\n canvas.tiles.updateMany(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\nlet amount = event.ctrlKey ? -0.5 : -1;\n\nif (canvas.tiles.controlled[0]) {\n canvas.tiles.controlled.forEach((ti) => {\n if (ti.data.locked) return;\n let r = ti.data.rotation;\n ti.update({ rotation: r + amount });\n });\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\nlet amount = event.ctrlKey ? 0.5 : 1;\n\nif (canvas.tiles.controlled[0]) {\n canvas.tiles.controlled.forEach((ti) => {\n if (ti.data.locked) return;\n let r = ti.data.rotation;\n ti.update({ rotation: r + amount });\n });\n} else {\n ui.notifications.notify(\"Please select at least one tile.\");\n}","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]} -{"name":"Announce Round Number","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{"core":{"sourceId":"Macro.V1p6LWoKnv7croAd"}},"scope":"global","command":"let messageContent = `
ROUND ${game.combat.round}

`\nChatMessage.create({content: messageContent});","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[],"_id":"CVbowNiiiwqmADGY"} -{"name":"Lock and Unlock Players","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{"core":{"sourceId":"Macro.VhzAqG5Abes21JD9"}},"scope":"global","command":"async function updateRoles(from, to) {\n game.users.entities.filter(u => u.role === from).forEach(u => u.update({role:to}))\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)","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[],"_id":"Vhj3ctf5cAufQMnM"} +{"name":"Format All Scene Notes","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{"core":{"sourceId":"Macro.yBeuLYd6wNuX6JHQ"}},"scope":"global","command":"let newNoteData = {};\n\nfor (let c of canvas.notes.children[0].children) {\n //newNoteData = duplicate(c.data);\n\n /* \n Define new note properties\n double right-click a map pin for a list of valid fonts, icons, etc. \n Remove the // in front of any line below to enable that change. \n */\n\n //newNoteData.fontFamily = \"Signika\";\n //newNoteData.fontSize = 8;\n \n /* \n replace the name of the icon, for example, the anchor would be \"icons/svg/anchor.svg\" \n */ \n //newNoteData.icon = \"icons/svg/book.svg\";\n //newNoteData.iconSize = 40;\n //newNoteData.iconTint = \"\";\n //newNoteData.text = \"test\";\n \n /* \n textAnchor controls the location of the text in relation to the icon. 0-4 are valid choices.\n 1 is the default, below. 0 hovers over the icon itself. 2 is above, 3 is left, 4 is right. \n */\n //newNoteData.textAnchor = 1;\n //newNoteData.textColor = \"#000000\";\n //newNoteData.x = 2250;\n //newNoteData.y = 2050;\n\n c.update(newNoteData);\n\n}\n\nasync () => {\n await canvas.draw()\n}","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[],"_id":"lPEdDL4uqxRFfW9x"} diff --git a/packs/macros-module-specific.db b/packs/macros-module-specific.db index 685d68d..a6e0d9a 100644 --- a/packs/macros-module-specific.db +++ b/packs/macros-module-specific.db @@ -1,7 +1,8 @@ {"_id":"OJGjAdWyYptiGeBa","name":"Token Vision Config - About Time","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"// A macro for the Foundry virtual tabletop that lets a user configure their token's vision and lighting settings, based on lighting sources from D&D 5e. Has a dependency on About Time by Tim Posney.\n\nif (canvas.tokens.controlled.length === 0)\n ui.notifications.error(\"Please select a token\");\n\nlet namedfields = (...fields) => {\n return (...arr) => {\n var obj = {};\n fields.forEach((field, index) => {\n obj[field] = arr[index];\n });\n return obj;\n };\n};\n\n// Very ugly automated construction below. DRY, but at what cost?\nlet VisionType = namedfields('name', 'dim', 'bright');\nvar visions = (() => {\n return [\n VisionType('Leave Unchanged', null, null),\n VisionType('Self', 5, 0),\n VisionType('Devil\\'s Sight', 0, 120)\n ].concat(...[...Array(6).keys()].map(x => (x+1)*30).map(n => {\n return VisionType(`Darkvision (${n} feet)`, n, 0);\n }));\n})();\n\nlet LightSource = namedfields('name', 'dim', 'bright', 'angle', 'lockRotation')\nvar lightSources = [\n LightSource('Leave Unchanged', null, null, null, null),\n LightSource('None', 0, 0, 360, null),\n LightSource('Candle', 10, 5, 360, null),\n LightSource('Torch / Light Cantrip', 40, 20, 360, null),\n LightSource('Lamp', 45, 15, 360, null),\n LightSource('Hooded Lantern', 60, 30, 360, null),\n LightSource('Hooded Lantern (Dim)', 5, 0, 360, null),\n LightSource('Bullseye Lantern', 120, 60, 52.5, false)\n];\n\nlet applyChanges = false;\nnew Dialog({\n title: `Token Vision Configuration`,\n content: `\n
\n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
\n`,\n buttons: {\n yes: {\n icon: \"\",\n label: `Apply Changes`,\n callback: () => applyChanges = true\n },\n no: {\n icon: \"\",\n label: `Cancel Changes`\n },\n },\n default: \"yes\",\n close: html => {\n if (applyChanges) {\n for ( let token of canvas.tokens.controlled ) {\n let visionIndex = parseInt(html.find('[name=\"vision-type\"]')[0].value) || 0;\n let lightIndex = parseInt(html.find('[name=\"light-source\"]')[0].value) || 0;\n let duration = parseInt(html.find('[name=\"duration\"]')[0].value) || 0;\n\n if (duration > 0) {\n if (game.modules.get(\"about-time\").active != true) {\n ui.notifications.error(\"About Time isn't loaded\");\n } else {\n ((backup) => {\n game.Gametime.doIn({minutes:Math.floor(3 * duration / 4)}, () => {\n ChatMessage.create({\n user: game.user._id,\n content: \"The fire burns low...\",\n speaker: speaker\n }, {});\n });\n })(Object.assign({}, token.data));\n ((backup) => {\n game.Gametime.doIn({minutes:duration}, () => {\n ChatMessage.create({\n user: game.user._id,\n content: \"The fire goes out, leaving you in darkness.\",\n speaker: speaker\n }, {});\n token.update({\n vision: true,\n dimSight: backup.dimSight,\n brightSight: backup.brightSight,\n dimLight: backup.dimLight,\n brightLight: backup.brightLight,\n lightAngle: backup.lightAngle,\n lockRotation: backup.lockRotation\n });\n });\n })(Object.assign({}, token.data));\n }\n }\n\n // Configure new token vision\n let dimSight = visions[visionIndex].dim ?? token.data.dimSight;\n let brightSight = visions[visionIndex].bright ?? token.data.brightSight;\n let dimLight = lightSources[lightIndex].dim ?? token.data.dimLight;\n let brightLight = lightSources[lightIndex].bright ?? token.data.brightLight;\n let lightAngle = lightSources[lightIndex].angle ?? token.data.lightAngle;\n let lockRotation = lightSources[lightIndex].lockRotation ?? token.data.lockRotation;\n\n // Update Token\n console.log(token);\n token.update({\n vision: true,\n dimSight: dimSight,\n brightSight: brightSight,\n dimLight: dimLight,\n brightLight: brightLight,\n lightAngle: lightAngle,\n lockRotation: lockRotation\n });\n }\n }\n }\n}).render(true);\n","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]} {"_id":"VZeBTY7FM8tERZGe","name":"Open Beyond Sheet Player","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"// Here's one for your players if you are using Virtual Tabletop Assets - D&D Beyond Integration:\n// Requires https://www.vttassets.com/modules/vtta-dndbeyond with character sheets linked!\n\nlet popup = () => {\n if (!game.user.character)\n return ui.notifications.error(\"You must first have a character assigned to your user!\");\n\n let char = game.user.character;\n\n if (!char.data.flags.vtta && !char.data.flags.vtta.dndbeyond && !char.data.flags.vtta.dndbeyond.url)\n return ui.notifications.error(\"Character must be linked with a D&D Beyond sheet!\");\n\n let url = char.data.flags.vtta.dndbeyond.url;\n let ratio = window.innerWidth / window.innerHeight;\n let width = Math.round(window.innerWidth * 0.5);\n let height = Math.round(window.innerWidth * 0.5 * ratio);\n const dndBeyondPopup = window.open(\n url,\n \"ddb_sheet_popup\",\n `resizeable,scrollbars,location=no,width=${width},height=${height},toolbar=1`\n );\n};\n\npopup();","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]} {"_id":"eAl2Dc1HLYp2sUeG","name":"Hit Die","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"// Requires furnace to work correctly.\n// args[0] === name of character\n// args[1] === \"add\",\"sub\", or \"use\" \n//\t\t\"add\" : adds 1 hit die if able to\n//\t\t\"sub\" : removes 1 hit die if able to\n//\t\t\"use\" : removes 1 hit die and heals actor for rolled amount\n\n(async () => {\n\tconst actor = game.actors.find(i => i.name === args[0]); \n if (!actor) return ui.notifications.warn(`No Actor by that name available.`);\n const classItems = actor.data.items.filter(it => it.type === \"class\")\n if (!classItems.length) return ui.notifications.warn(`Actor has no class!`);\n if (classItems.length > 1) return ui.notifications.warn(`Actor has multiple classes! This is not (yet) supported.`);\n const classItem = classItems[0];\n\t\n\tif(args[1] === \"add\")\n\t{\n\t\tif (classItem.data.hitDiceUsed <= 0) return ui.notifications.warn(`You are at maximum Hitdie!`);\n\n\t\tconst classItemUpdate = {\n\t\t\t_id: classItem._id,\n\t\t\tdata: {\n\t\t\t\thitDiceUsed: classItem.data.hitDiceUsed - 1,\n\t\t\t},\n\t\t};\n\n\t\tawait actor.updateEmbeddedEntity(\"OwnedItem\", classItemUpdate);\n\t}\n\t\n\tif(args[1] === \"sub\")\n\t{\n\t\tif (classItem.data.hitDiceUsed >= classItem.data.levels) return ui.notifications.warn(`You have no remaining hit dice to spend!`);\n\n\t\tconst classItemUpdate = {\n\t\t\t_id: classItem._id,\n\t\t\tdata: {\n\t\t\t\thitDiceUsed: classItem.data.hitDiceUsed + 1,\n\t\t\t},\n\t\t};\n\n\t\tawait actor.updateEmbeddedEntity(\"OwnedItem\", classItemUpdate);\n\t}\n\t\n\tif(args[1] === \"use\")\n\t{\n\t\tif (classItem.data.hitDiceUsed >= classItem.data.levels) return ui.notifications.warn(`You have no remaining hit dice to spend!`);\t\t\n\t\tconst classItemUpdate = {\n\t\t\t_id: classItem._id,\n\t\t\tdata: {\n\t\t\t\thitDiceUsed: classItem.data.hitDiceUsed + 1,\n\t\t\t},\n\t\t};\n\t\tawait actor.updateEmbeddedEntity(\"OwnedItem\", classItemUpdate);\n\t\t\n\t\tconst hitDieRoll = new Roll(`1${classItem.data.hitDice} + ${actor.data.data.abilities.con.mod}`);\n\t\thitDieRoll.roll();\n\t\thitDieRoll.toMessage({\n\t\t\tuser : game.user._id,\n\t\t\tspeaker : speaker,\n\t\t\tflavor : \"Roll Hit Dice\"\n\t\t});\n\t\t\n\t\tconst actorUpdate = {\n\t\t\tdata: {\n\t\t\t\tattributes: {\n\t\t\t\t\thp: {\n\t\t\t\t\t\tvalue: Math.clamped(\n\t\t\t\t\t\t\tactor.data.data.attributes.hp.value + hitDieRoll.total,\n\t\t\t\t\t\t\tactor.data.data.attributes.hp.min,\n\t\t\t\t\t\t\tactor.data.data.attributes.hp.max\n\t\t\t\t\t\t)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\t\tawait actor.update(actorUpdate);\n\t} \n})();\n","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]} +{"name":"Hunters Mark - Mark Attack Wrapper","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"//This marco is replacement for a rollItemMacro. Replace the name of the item\n//you wish to use to make the attack. This macro must be used with the\n//CastMark.json macro or it will just make a standard attack.\n\n// PUT ITEM MACRO HERE between quotes ****************\nconst itemName = \"Longbow\";\n// ***************************************************\n\n//parameters\n\nlet myToken = token;\nconst macroName = \"world\";\nconst markDmg = \" + 1d6\";\nconst target = game.user.targets.values().next().value;\nconst bonuses = myToken.actor.data.data.bonuses;\nconst actorId = myToken.actor._id + \"_mark\";\n\n//Check to see if the mark flag is set else make attack\n\nfunction checkMark() {\n const flag = myToken.getFlag(macroName, actorId);\n\n if (flag) {\n if (flag.targetId == target.data._id) {\n markAttack(flag);\n } else {\n baseAttack(flag);\n }\n } else {\n game.dnd5e.rollItemMacro(itemName);\n }\n}\n\n//check if the mark damag is set and if not increase\n//increase global damage by 1d6\n\nfunction markAttack(flag) {\n if (!flag.isSet) {\n let obj = {\n \"data.bonuses.mwak.damage\": flag.meleeAtk + markDmg,\n \"data.bonuses.rwak.damage\": flag.rangeAtk + markDmg,\n \"data.bonuses.msak.damage\": flag.meleeSpell + markDmg,\n \"data.bonuses.rsak.damage\": flag.rangeSpell + markDmg\n };\n updateActor(myToken, obj);\n flag.isSet = true;\n }\n game.dnd5e.rollItemMacro(itemName);\n token.setFlag(macroName, actorId, flag);\n}\n\n// check if the mark damage is set and if it is revert to base global damage\n\nfunction baseAttack(flag) {\n if (flag) {\n let obj = {\n \"data.bonuses.mwak.damage\": flag.meleeAtk,\n \"data.bonuses.rwak.damage\": flag.rangeAtk,\n \"data.bonuses.msak.damage\": flag.meleeSpell,\n \"data.bonuses.rsak.damage\": flag.rangeSpell\n };\n updateActor(myToken, obj);\n flag.isSet = false;\n game.dnd5e.rollItemMacro(itemName);\n token.setFlag(macroName, actorId, flag);\n } else {\n game.dnd5e.rollItemMacro(itemName);\n }\n}\n\nasync function updateActor(updateToken, obj) {\n await updateToken.actor.update(obj);\n}\n\n//Ensure target is set and then call check mark function\n\nif (!myToken) ui.notifications.error(\"Please select your token first.\");\nelse {\n checkMark();\n}\n","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[],"_id":"m2Dqm2gD0sUDNWbI"} {"_id":"pQ2VZCynMb4MIhky","name":"Resolve Surprise","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"// Resolve who is surprised between groups of players and enemies or individual players and enemies\n// This macro requires The Furnance module with \"Advanced Macros\" turned on.\n// ReadMe: https://gitlab.com/x.technocat/foundry-macros/-/blob/e67f9441b2d6d442258db1d51aa2be3ead97569d/surprise-macros/README.md\n\n// Get player character tokens\nconst playerActors = getPlayerActors();\n\n// Get hostile tokens\nconst enemyActors = getEnemyActors();\n\nlet warnMsg = \"\";\n\n// Main function\nconst isSuccessful = checkSurprise(playerActors, enemyActors);\n\nif (!isSuccessful) {\n return ui.notifications.warn(warnMsg);\n}\n\nfunction checkSurprise(playerActors, enemyActors) {\n if (!game.user.isGM) {\n warnMsg = \"You do not have permission to run this macro.\";\n return false;\n }\n if (!playerActors.length > 0) {\n warnMsg =\n \"Could not find player characters in the scene. Please add player characters to the scene.\";\n return false;\n }\n if (!enemyActors.length > 0) {\n warnMsg =\n \"Could not find hostile tokens. You need tokens with hostile dispositions in the scene to check for Surprise!\";\n return false;\n }\n\n // Displays a popup and handles the input. Thanks to PaperPunk for this.\n let calculateSurprise = false;\n\n new Dialog({\n title: `What Group is Sneaking?`,\n content: `\n
\n
\n

\n
\n \n
\n \n
\n
\n
\n
\n
\n `,\n buttons: {\n yes: {\n icon: \"\",\n label: `Calculate Surprise`,\n callback: () => (calculateSurprise = true), //if \"yes\" is selected, apply the selection.\n },\n no: {\n icon: \"\",\n label: `Cancel Surprise`,\n },\n },\n // Defaulting to yes, so that when someone selects something and hits enter, it doesn't cancel:\n default: \"yes\",\n\n // Handle the input\n close: (html) => {\n if (calculateSurprise) {\n // Determine which group is sneaking based on the user's selection\n const sneakyOptions = html.find('[name=\"sneaking-group\"]'); \n let sneakingGroup;\n for (const option of sneakyOptions) {\n if(option.checked) {\n sneakingGroup = option.value;\n break;\n }\n } \n\n let results = ``;\n let lowestStealthCheck = 0;\n\n switch (sneakingGroup) {\n case \"enemies\":\n lowestStealthCheck = rollStealth(enemyActors);\n results = calculateSurpriseResults(\n lowestStealthCheck,\n playerActors\n );\n break;\n\n case \"party\":\n lowestStealthCheck = rollStealth(playerActors);\n results = calculateSurpriseResults(\n lowestStealthCheck, \n enemyActors);\n break;\n }\n\n handleChatMessage(results, lowestStealthCheck);\n }\n },\n }).render(true); // display pop up window\n\n return true; // no issues and we're done here!\n}\n\nfunction handleChatMessage(results, lowestStealth) {\n let messageContent = ``;\n messageContent += `Lowest Stealth: [[${lowestStealth}]]

`;\n messageContent += results;\n\n const surpriseRules =\n \"
Surprise Rules
\" +\n \"Compare the Dexterity (Stealth) checks of anyone hiding with the passive Wisdom (Perception) score of each creature on the opposing side. Any character or monster that doesn't notice a threat is surprised at the start of the encounter.

\" +\n \"If you're surprised, you can't move or take an action on your first turn of the combat, and you can't take a reaction until that turn ends. A member of a group can be surprised even if the other members aren't.

PHB. 189\";\n\n messageContent += surpriseRules;\n\n let chatData = {\n user: game.user._id,\n speaker: game.user.name,\n content: messageContent,\n whisper: game.users.entities.filter((u) => u.isGM).map((u) => u._id),\n };\n\n ChatMessage.create(chatData, {});\n}\n\nfunction getPlayerActors() {\n // Get tokens in the scene that are player characters and not NPCs. Prioritize selected tokens.\n const controlledPlayerActors = canvas.tokens.controlled\n .filter((pc) => pc.actor.isPC && pc.actor.data.type === \"character\")\n .map((a) => a.actor);\n\n if (controlledPlayerActors.length > 0) {\n return controlledPlayerActors;\n }\n\n // If none are selected, use all the player characters in the scene\n return canvas.tokens.children[0].children\n .filter((pc) => pc.actor.isPC && pc.actor.data.type === \"character\")\n .map((a) => a.actor);\n}\n\nfunction getEnemyActors() {\n // Prioritize selected hostile enemies\n const controlledEnemyActors = canvas.tokens.controlled\n .filter((ec) => ec.actor.isPC === false && ec.data.disposition === -1)\n .map((a) => a.actor);\n\n if (controlledEnemyActors.length > 0) {\n return controlledEnemyActors;\n }\n\n // If none are selected, use all the enemies in the scene that are hostile\n return canvas.tokens.children[0].children\n .filter((ec) => ec.actor.isPC === false && ec.data.disposition === -1)\n .map((a) => a.actor);\n}\n\nfunction rollStealth(sneakyGroup) {\n // Roll stealth for the sneaking group\n let stealthResults = [];\n for (let actor of sneakyGroup) {\n let stealth =\n new Roll(\"1d20\").roll().total + actor.data.data.skills.ste.mod;\n stealthResults.push(stealth);\n }\n\n // Return the lowest stealth roll, it's all we need\n return Math.min(...stealthResults);\n}\n\nfunction calculateSurpriseResults(lowestStealth, perceptiveGroup) {\n let resultMsg = ``;\n\n const surprisedTxt = `[Surprised]`;\n const notSurprisedTxt = `[Not Surprised]`;\n const alertTxt = `Alert`;\n\n const lowestPerception = Math.min.apply(\n Math,\n perceptiveGroup.map(function (actor) {\n return actor.data.data.skills.prc.passive;\n })\n );\n\n if (lowestPerception >= lowestStealth) {\n resultMsg += `No one is Surprised!
`;\n } else {\n for (let actor of perceptiveGroup) {\n const name = actor.data.name;\n const prc = actor.data.data.skills.prc.passive;\n let msg = ``;\n\n // If Alert feat not surprised, else if win perception not surprised, else surprised\n const alertFeat = actor.data.items.find((p) => p.name === \"Alert\");\n if (alertFeat) {\n msg = `${name} (${prc}) -- ${notSurprisedTxt} ${alertTxt}`;\n } else if (prc >= lowestStealth) {\n msg = `${name} (${prc}) -- ${notSurprisedTxt}`;\n } else {\n msg = `${name} (${prc}) -- ${surprisedTxt}`;\n }\n\n // Append this actor's surprise result to chat message variable.\n resultMsg += `${msg}

`;\n }\n }\n\n return resultMsg;\n}\n","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]} {"name":"Hunters Mark - GM Conditions","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"//This macro must be called 'GMConditions' and be on the GM's\n//hot bar for the castMark.js macro to function correctly.\n//you must tick the option 'Excecute Macro As GM' to grant\n//players access to this macro.\n\n\nconst action = args[0]\nconst condition = args[1]\nconst targetId = args[2]\nconst target = canvas.tokens.get(targetId);\nif (action == \"apply\"){\n game.cub.applyCondition(condition, target, {warn: true});\n}\nelse if (action == \"remove\"){\n game.cub.removeCondition(condition, target, {warn: true});\n}\n","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[],"_id":"s7I9TcrGFMH2hQ0c"} {"name":"Hunters Mark - Cast Mark","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"//This marco is designed to be used in conjunction with MarkAttackWrapper.json\n//and GMConditions.js.\n\n//It is designed to replace the standard rollItemMacro for either the Hex\n//spell or the hunter's mark Spell on either the warlock or ranger, but\n//it will work on any class with hex or hunter's mark by changing the Localization\n//parameters. To use hex, replace warlockName variable below and if you\n//would like to use it with hunter's mark replace rangerName variable below.\n\n// CAUTION! if you change your global damage variables while this macro is\n//active the change will be erased. It should work fine with dynamic effects.\n\n//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n//This macro is designed to be used with the combat utility belt module.\n//and the furnace module. Once you have it installed, you will have to enable \n//enhanced conditions, and create a condition called 'Marked' as the Localization \n//parameter below. You just need to update that parameter to match whatever condition \n//name you want if you want to use a different name in condition lab.\n//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n//Localization\nconst rangerName = \"Ranger\";\nconst warlockName = \"Warlock\";\nconst rangerSpell = \"Hunter's Mark\";\nconst warlockSpell = \"Hex\";\nconst condition = \"Marked\";\nconst flagScope = \"world\";\n\n//parameters\nif (!token) ui.notifications.error(\"Please select your token first.\");\nlet myToken = token;\nconst markDmg = \" + 1d6\";\nif (!game.user.targets.values().next().value) {\n ui.notifications.error(\"Please select one target\");\n}\nconst target = game.user.targets.values().next().value;\n\nconst conditionList = target.data.effects;\nconst actorId = myToken.actor._id + \"_mark\";\nconst bonuses = myToken.actor.data.data.bonuses;\nlet className = \"\";\nlet spellName = \"\";\nlet gm_macro = null\n\n//Check to make sure that the GM has the GMConditions macro assigned.\ntry{\n gm_macro = game.macros.entities.find(mb => mb.name === \"GMConditions\");\n}\ncatch(err)\n{\n ui.notifications.error(\"GMConditions Macro was not found\")\n}\n\n//If Move flag and condition to a new selected target\nfunction move() {\n let swapTarget = myToken.getFlag(flagScope, actorId);\n const remCon = canvas.tokens.get(swapTarget.targetId);\n gm_macro.execute(\"remove\", condition, swapTarget.targetId);\n swapTarget.targetId = target.data._id;\n (async () => {\n await myToken.unsetFlag(flagScope, actorId);\n myToken.setFlag(flagScope, actorId, swapTarget);\n })();\n gm_macro.execute(\"apply\", condition, target.id);\n console.log();\n}\n\n//revert global damage to base, remove the condition and unset flag\n\nfunction remove() {\n const flagId = myToken.getFlag(flagScope, actorId);\n\n let obj = {\n \"data.bonuses.mwak.damage\": flagId.meleeAtk,\n \"data.bonuses.rwak.damage\": flagId.rangeAtk,\n \"data.bonuses.msak.damage\": flagId.meleeSpell,\n \"data.bonuses.rsak.damage\": flagId.rangeSpell\n };\n\n updateActor(myToken, obj);\n\n const remFlag = canvas.tokens.get(flagId.targetId);\n\n (async () => {\n await gm_macro.execute(\"remove\", condition, flagId.targetId);\n await myToken.unsetFlag(flagScope, actorId);\n })();\n}\n\n// User input to move or remove flag and condition\n\nfunction alterMark() {\n const d = new Dialog({\n title: \"Mark Enemy\",\n content: \"

Would you like to move or remove?

\",\n buttons: {\n one: {\n icon: '',\n label: \"Move\",\n callback: () => move()\n },\n two: {\n icon: '',\n label: \"Remove\",\n callback: () => remove()\n }\n },\n default: \"two\",\n close: () => console.log(\"Dialog closed\")\n }).render(true);\n}\n\n// cast the spell, apply the condition, create and set flag\n\nasync function castSpell() {\n try {\n await game.dnd5e.rollItemMacro(spellName);\n } catch (err) {\n return null;\n }\n gm_macro.execute(\"apply\", condition, target.id);\n\n let globalDmg = {\n targetId: target.data._id,\n meleeAtk: bonuses.mwak.damage,\n rangeAtk: bonuses.rwak.damage,\n meleeSpell: bonuses.msak.damage,\n rangeSpell: bonuses.rsak.damage,\n isSet: false\n };\n myToken.setFlag(flagScope, actorId, globalDmg);\n}\n\n//If the class is ranger and has the hunter's mark spell, set the spell name\n//or say the spell doesn't exist.\n\nfunction setRangerSpell() {\n className = rangerName;\n if (actor.items.find(i => i.name === `${rangerSpell}`)) {\n spellName = rangerSpell;\n castSpell();\n } else {\n ui.notifications.error(\n \"Selected actor does not have the \" + rangerSpell + \" spell.\"\n );\n console.log(\"Selected actor does not have the \" + rangerSpell + \" spell.\");\n return null;\n }\n}\n\n//If the class is warlock and has the hex spell set the spell name\n//if say the spell doesn't exist\n\nfunction setWarlockSpell() {\n className = warlockName;\n if (actor.items.find(i => i.name === `${warlockSpell}`)) {\n spellName = warlockSpell;\n castSpell();\n } else {\n ui.notifications.error(\n \"Selected actor does not have the \" + warlockSpell + \" spell.\"\n );\n console.log(\"Selected actor does not have the \" + warlockSpell + \" spell.\");\n return null;\n }\n}\n\n//check whether the token is a ranger or warlock\n\nfunction checkSpell() {\n if (actor.items.find(i => i.name === `${rangerName}`)) {\n setRangerSpell();\n } else if (actor.items.find(i => i.name === `${warlockName}`)) {\n setWarlockSpell();\n } else {\n ui.notifications.error(\n \"Please select a \" + rangerName + \" or \" + warlockName + \" token.\"\n );\n console.log(\n \"Please select a \" + rangerName + \" or \" + warlockName + \" token.\"\n );\n }\n}\n\nasync function updateActor(updateToken, obj) {\n await updateToken.actor.update(obj);\n}\n\n//If the flag exists call functions to move or remove if it doesn't exist\n// call function to cast the spell\n\nif (myToken.getFlag(flagScope, actorId)) {\n alterMark();\n} else {\n checkSpell();\n}\n","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[],"_id":"tch2n3sDUiZTxqGM"} -{"name":"Hunters Mark - Mark Attack Wrapper","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"//This marco is replacement for a rollItemMacro. Replace the name of the item\n//you wish to use to make the attack. This macro must be used with the\n//CastMark.json macro or it will just make a standard attack.\n\n// PUT ITEM MACRO HERE between quotes ****************\nconst itemName = \"Longbow\";\n// ***************************************************\n\n//parameters\n\nlet myToken = token;\nconst macroName = \"world\";\nconst markDmg = \" + 1d6\";\nconst target = game.user.targets.values().next().value;\nconst bonuses = myToken.actor.data.data.bonuses;\nconst actorId = myToken.actor._id + \"_mark\";\n\n//Check to see if the mark flag is set else make attack\n\nfunction checkMark() {\n const flag = myToken.getFlag(macroName, actorId);\n\n if (flag) {\n if (flag.targetId == target.data._id) {\n markAttack(flag);\n } else {\n baseAttack(flag);\n }\n } else {\n game.dnd5e.rollItemMacro(itemName);\n }\n}\n\n//check if the mark damag is set and if not increase\n//increase global damage by 1d6\n\nfunction markAttack(flag) {\n if (!flag.isSet) {\n let obj = {\n \"data.bonuses.mwak.damage\": flag.meleeAtk + markDmg,\n \"data.bonuses.rwak.damage\": flag.rangeAtk + markDmg,\n \"data.bonuses.msak.damage\": flag.meleeSpell + markDmg,\n \"data.bonuses.rsak.damage\": flag.rangeSpell + markDmg\n };\n updateActor(myToken, obj);\n flag.isSet = true;\n }\n game.dnd5e.rollItemMacro(itemName);\n token.setFlag(macroName, actorId, flag);\n}\n\n// check if the mark damage is set and if it is revert to base global damage\n\nfunction baseAttack(flag) {\n if (flag) {\n let obj = {\n \"data.bonuses.mwak.damage\": flag.meleeAtk,\n \"data.bonuses.rwak.damage\": flag.rangeAtk,\n \"data.bonuses.msak.damage\": flag.meleeSpell,\n \"data.bonuses.rsak.damage\": flag.rangeSpell\n };\n updateActor(myToken, obj);\n flag.isSet = false;\n game.dnd5e.rollItemMacro(itemName);\n token.setFlag(macroName, actorId, flag);\n } else {\n game.dnd5e.rollItemMacro(itemName);\n }\n}\n\nasync function updateActor(updateToken, obj) {\n await updateToken.actor.update(obj);\n}\n\n//Ensure target is set and then call check mark function\n\nif (!myToken) ui.notifications.error(\"Please select your token first.\");\nelse {\n checkMark();\n}\n","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[],"_id":"m2Dqm2gD0sUDNWbI"} +{"_id":"OJGjAdWyYptiGeBa","name":"Token Vision Config - About Time","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"// A macro for the Foundry virtual tabletop that lets a user configure their token's vision and lighting settings, based on lighting sources from D&D 5e. Has a dependency on About Time by Tim Posney.\n\nif (canvas.tokens.controlled.length === 0)\n ui.notifications.error(\"Please select a token\");\n\nlet namedfields = (...fields) => {\n return (...arr) => {\n var obj = {};\n fields.forEach((field, index) => {\n obj[field] = arr[index];\n });\n return obj;\n };\n};\n\n// Very ugly automated construction below. DRY, but at what cost?\nlet VisionType = namedfields('name', 'dim', 'bright');\nvar visions = (() => {\n return [\n VisionType('Leave Unchanged', null, null),\n VisionType('Self', 5, 0),\n VisionType('Devil\\'s Sight', 0, 120)\n ].concat(...[...Array(6).keys()].map(x => (x+1)*30).map(n => {\n return VisionType(`Darkvision (${n} feet)`, n, 0);\n }));\n})();\n\nlet LightSource = namedfields('name', 'dim', 'bright', 'angle', 'lockRotation')\nvar lightSources = [\n LightSource('Leave Unchanged', null, null, null, null),\n LightSource('None', 0, 0, 360, null),\n LightSource('Candle', 10, 5, 360, null),\n LightSource('Torch / Light Cantrip', 40, 20, 360, null),\n LightSource('Lamp', 45, 15, 360, null),\n LightSource('Hooded Lantern', 60, 30, 360, null),\n LightSource('Hooded Lantern (Dim)', 5, 0, 360, null),\n LightSource('Bullseye Lantern', 120, 60, 52.5, false)\n];\n\nlet applyChanges = false;\nnew Dialog({\n title: `Token Vision Configuration`,\n content: `\n
\n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
\n`,\n buttons: {\n yes: {\n icon: \"\",\n label: `Apply Changes`,\n callback: () => applyChanges = true\n },\n no: {\n icon: \"\",\n label: `Cancel Changes`\n },\n },\n default: \"yes\",\n close: html => {\n if (applyChanges) {\n for ( let token of canvas.tokens.controlled ) {\n let visionIndex = parseInt(html.find('[name=\"vision-type\"]')[0].value) || 0;\n let lightIndex = parseInt(html.find('[name=\"light-source\"]')[0].value) || 0;\n let duration = parseInt(html.find('[name=\"duration\"]')[0].value) || 0;\n\n if (duration > 0) {\n if (game.modules.get(\"about-time\").active != true) {\n ui.notifications.error(\"About Time isn't loaded\");\n } else {\n ((backup) => {\n game.Gametime.doIn({minutes:Math.floor(3 * duration / 4)}, () => {\n ChatMessage.create({\n user: game.user._id,\n content: \"The fire burns low...\",\n speaker: speaker\n }, {});\n });\n })(Object.assign({}, token.data));\n ((backup) => {\n game.Gametime.doIn({minutes:duration}, () => {\n ChatMessage.create({\n user: game.user._id,\n content: \"The fire goes out, leaving you in darkness.\",\n speaker: speaker\n }, {});\n token.update({\n vision: true,\n dimSight: backup.dimSight,\n brightSight: backup.brightSight,\n dimLight: backup.dimLight,\n brightLight: backup.brightLight,\n lightAngle: backup.lightAngle,\n lockRotation: backup.lockRotation\n });\n });\n })(Object.assign({}, token.data));\n }\n }\n\n // Configure new token vision\n let dimSight = visions[visionIndex].dim ?? token.data.dimSight;\n let brightSight = visions[visionIndex].bright ?? token.data.brightSight;\n let dimLight = lightSources[lightIndex].dim ?? token.data.dimLight;\n let brightLight = lightSources[lightIndex].bright ?? token.data.brightLight;\n let lightAngle = lightSources[lightIndex].angle ?? token.data.lightAngle;\n let lockRotation = lightSources[lightIndex].lockRotation ?? token.data.lockRotation;\n // Common settings for all 'torch-like' options\n // Feel free to change the values to your liking\n let lightAnimation = {type: \"torch\", speed: 2, intensity: 2};\n let lightColor = \"#f8c377\"; // Fire coloring.\n let lightAlpha = 0.15;\n\n // Update Token\n console.log(token);\n token.update({\n vision: true,\n dimSight: dimSight,\n brightSight: brightSight,\n dimLight: dimLight,\n brightLight: brightLight,\n lightAngle: lightAngle,\n lockRotation: lockRotation,\n lightAnimation: lightAnimation,\n lightColor: lightColor,\n lightAlpha: lightAlpha\n });\n }\n }\n }\n}).render(true);","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]} diff --git a/packs/macros-roll.db b/packs/macros-roll.db index fe758f1..4192f7c 100644 --- a/packs/macros-roll.db +++ b/packs/macros-roll.db @@ -1,11 +1,10 @@ {"_id":"DKxkQhiyOQMCCcGG","name":"Wandering Monsters","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"// setting variables\nlet tableName = \"Wandering Monsters\";\nlet msgContent = 'Wandering Monster roll was: ';\nlet result = '';\n\n// roll to check for wandering monster\nresult = new Roll(`1d20`).roll().total;\n\n// create the message\nif(result !== '') {\n let chatData = {\n content: msgContent + result,\n whisper: game.users.entities.filter(u => u.isGM).map(u => u._id)\n };\n ChatMessage.create(chatData, {});\n}\n\n// In this example, a roll between 17-20 will generate a roll from the Table. Tweak as needed!\nif (result >= 17) {\n const table = game.tables.entities.find(t => t.name === tableName);\n table.draw();\n}\n","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]} {"name":"Roll Ammunition Die","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"/*\nAuthor: stan#1549 (github.com/janssen-io)\n\nDescription:\nRoll cascading dice instead of keeping track of ammunition.\nExample:\n When you shoot ammunition, roll a d12. On a 1, it gets replaced with a d10.\n All the way down to a d4, after which you have a single shot left.\n On average this gives 12 + 10 + 8 + 6 + 4 + 1 = 41 shots (for a d12)\n If you need more than 61 shots (d20), then equip a second piece of the same\n ammunition and give it its own ammo die.\n*/\n\n(() => {\n if (!token) {\n ui.notifications.warn(\"No character selected!\");\n return;\n }\n\n const dieMap = {\n 20: 12,\n 12: 10,\n 10: 8,\n 8: 6,\n 6: 4,\n 4: 1,\n 1: 0\n }\n\n function rollDie(html, ammo) {\n const ammoId = html[0].querySelector('input:checked')?.value;\n if (!ammoId) {\n ui.notifications.error(\"No ammunition selected.\");\n return;\n }\n const diceInputs = Array.from(html[0].querySelectorAll('input[type=number]'));\n const dice = diceInputs.reduce((curr, input) => (curr[input.name] = input.value, curr), {});\n\n const die = dice[ammoId];\n const roll = new Roll(`1d${die}`);\n roll.roll();\n dice[ammoId] = roll.result == 1 ? (dieMap[die] || die - 1) : die;\n token.actor.unsetFlag('world', 'ammunition-dice')\n .then(entity => entity.setFlag('world', 'ammunition-dice', dice));\n roll.toMessage({\n flavor: `[Ammunition roll] ${token.name} fires a(n) ${ammo.find(a => a.id == ammoId).name}!`\n });\n }\n\n function updateDice(html) {\n const diceInputs = Array.from(html[0].querySelectorAll('input[type=number]'));\n const dice = diceInputs.reduce((curr, input) => (curr[input.name] = input.value, curr), {});\n\n token.actor.unsetFlag('world', 'ammunition-dice')\n .then(entity => entity.setFlag('world', 'ammunition-dice', dice));\n }\n\n function createForm(ammo, dice) {\n const options = ammo.map(item => `\n \n \n ${item.name}\n d\n \n `);\n return `${options.join('')}
AmmunitionDie
`;\n }\n\n function createDialog(token) {\n const dice = token.actor.getFlag('world', 'ammunition-dice') || {};\n const ammunition = token.actor.items.filter(i => i.type == \"consumable\" && i.data.data.consumableType == \"ammo\");\n\n if (ammunition.length === 0) {\n ui.notifications.error(\"You have no ammunition.\");\n return;\n }\n\n const form = createForm(ammunition, dice);\n\n return new Dialog({\n title: \"Roll ammunition die\",\n content: form,\n buttons: {\n yes: { label: \"Roll\", callback: html => rollDie(html, ammunition) },\n no: (game.user.isGM ? { label: \"Update\", callback: html => updateDice(html) } : { label: \"Cancel\" })\n },\n default: (game.user.isGM ? 'no' : 'yes')\n }).render(true);\n }\n\n createDialog(token);\n\n})();\n","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[],"_id":"GbfsxR8dVRUtQYG0"} {"_id":"GgFydAwlZwXQocph","name":"Roll Table","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"// Simple macro example to only roll from a table and whisper the result to the DM\n\nconst table = game.tables.entities.find(t => t.name === \"name of your table\");\nlet roll = table.roll();\n\nlet chatData = {\n user: game.user._id,\n speaker: ChatMessage.getSpeaker(),\n content: roll.results[0].text,\n whisper: game.users.entities.filter(u => u.isGM).map(u => u._id)\n};\nChatMessage.create(chatData, {});\n","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]} -{"_id":"ZaiiiR9KPBfzHvea","name":"Roll Initiatives","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"/**\n * Takes all selected tokens and adds them to the combat tracker. Then rolls initative for all NPC tokens.\n */\n\nasync function start() {\n for ( let token of canvas.tokens.controlled) { \n if (token.inCombat === false){\n // Change 'rollNPC' to 'rollAll' if you want to roll for your players as well.\n await token.toggleCombat().then(() => game.combat.rollNPC(null, {rollMode: 'gmroll'}));\n }\n }\n}\n\nstart();\n","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]} +{"_id":"ZaiiiR9KPBfzHvea","name":"Roll Initiatives","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"/**\n * Takes all selected tokens and adds them to the combat tracker. Then rolls initative for all NPC tokens.\n */\nasync function main() {\n await canvas.tokens.toggleCombat();\n game.combat.rollNPC({ messageOptions: { rollMode: CONST.DICE_ROLL_MODES.PRIVATE }})\n}\nmain();","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]} {"name":"Character Stat Roller","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"/**\n * Generates character stats and outputs the table result.\n * Author: @Kekilla#7036 & KrishMero1792\n */\n \n// Formula for rolling \nconst statString = '4d6kh3';\n\n// times to roll those stats\nconst numRolls = 6;\n\n\n//////////////////////////////////////////\n// Don't touch anything below this line //\n//////////////////////////////////////////\nconst stats = Array(numRolls).fill(0).map(e=>new Roll(statString).roll());\n\nconst {faces, rolls} = stats[0].parts[0];\nconst totalAverage = (faces/2 + 0.5) * rolls.filter(i=> i?.discarded !== true).length;\nconst totalDeviation = faces/2;\nconst totalLow = Math.ceil(totalAverage - totalDeviation);\nconst totalHigh = Math.ceil(totalAverage + totalDeviation);\n\nconst header = rolls.map((roll, index) => `D${index + 1}`).join('');\n\nlet tableRows = '';\nlet finalSum = 0;\nfor(let {parts, total} of stats) {\n tableRows += ``;\n tableRows += parts[0].rolls.map(({roll}) => `${roll}`).join('');\n tableRows += `${total}`;\n finalSum += total;\n}\n\nconst colspan = `colspan=\"${rolls.length + 1}\"`;\nconst center = `text-align:center;`;\n\nlet content = `\n \n \n \n \n \n ${header}\n \n \n ${tableRows}\n \n \n \n \n

New Ability Scores

\n
${statString} was rolled ${numRolls} times.
Total
Final Sum:${finalSum}
\n`;\n\n\nChatMessage.create({content});\n\nfunction colorSetter(number,low,high)\n{\n if(number <= low) return 'color:red';\n if(number >= high) return 'color:green';\n return '';\n}\n","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[],"_id":"cj0Sm0kWnHx11nFR"} +{"_id":"cktksne1wqrTiY7N","name":"Roll Die","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{"core":{"sourceId":"Macro.8zQiFBIWQcemEtIe"}},"scope":"global","command":"//Script to roll a die\n//Equivalently could be done by changing the macro to chat and entering /r 1d6\n//You can roll any number and any type of dice by changing Roll to ndx\n //n: number of dice\n //x: value (number of sides) on die\nconst roll = new Roll(`1d6`);\nroll.roll();\nroll.toMessage({\n flavor: \"Sneak Attack Damage\",\n});","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]} {"name":"Chartopia Roller","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"/**\n * Make a roll from chartopia and output the results in the chat window.\n * If you find yourself using this macro often, please support chartopia on patreon.\n */\n\n// chart id from url. IE 19449 is the chart id in https://chartopia.d12dev.com/chart/19449/\nlet chartId = 19449;\n// only let the gm see the results. false = everyone sees in chat. true = gm whispered results.\nlet gmOnly = false;\n\n\n//////////////////////////////////\n/// Don't edit past this point ///\n//////////////////////////////////\n\nvar rootUrl = \"https://chartopia.d12dev.com/test/\";\n\nfunction roll(id) {\n let request = new XMLHttpRequest();\n request.open('GET', rootUrl+'dice-roll-result?chart_id='+id, true);\n\n request.onload = function() {\n if (request.status >= 200 && request.status < 400) {\n // Success!\n let whisper = !!gmOnly ? game.users.entities.filter(u => u.isGM).map(u => u._id) : '';\n\n let chatData = {\n user: game.user._id,\n speaker: ChatMessage.getSpeaker(),\n content: request.responseText,\n whisper\n };\n\n ChatMessage.create(chatData, {});\n } else {\n // We reached our target server, but it returned an error\n console.log(\"Server error.\");\n }\n };\n\n request.onerror = function() {\n // There was a connection error of some sort\n console.log(\"Error getting result.\");\n };\n\n request.send();\n} \n\nroll(chartId);\n","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[],"_id":"fEzHEn3tGXXIRHpU"} {"_id":"sXxLpBqCRhCQeGLV","name":"Mass Roll Check","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"/*\n* Gets a list of selected tokens (or defaults to the user's character), provides a list of\n* skills, and then makes a roll for all the selected tokens with that skill. It then spits out\n* the poorly-formatted results to chat (or the GM if you uncomment the whisper line).\n*/\n\nlet targetActors = getTargetActors().filter(a => a != null);\nfunction checkForActors(){\n if (!targetActors.length > 0)\n throw new Error('You must designate at least one token as the roll target');\n};\ncheckForActors();\n\n// Choose roll type dialog\nlet rollTypeTemplate = `\n
\n
\n \n \n
\n
`;\n\nlet chooseCheckType = new Dialog({\n title: \"Choose check type\",\n content: rollTypeTemplate,\n buttons: {\n ok: {\n icon: '',\n label: \"OK\",\n callback: async (html) => {\n let checkType = html.find(\"#selectedType\")[0].value;\n selectedCheckDialog(checkType).render(true);\n }\n },\n cancel: {\n icon: '',\n label: 'Cancel'\n }\n },\n default: \"cancel\"\n});\n\n// Choose ability mod dialog\nfunction selectedCheckDialog(checkType) {\n\n let dialogTitle = getCheckDialogTitle(checkType);\n let dialogContent = getCheckTemplate(checkType);\n\n return new Dialog({\n title: dialogTitle,\n content: dialogContent,\n buttons: {\n ok: {\n icon: '',\n label: \"OK\",\n callback: async (html) => {\n let id = html.find(\"#selectedAbility\")[0].value;\n\n let messageContent = `

${checkType.toUpperCase()} Roll

`\n for (let a of targetActors) {\n let name = a.name;\n let mod = 0; \n switch (checkType) {\n case \"save\":\n mod = a.data.data.abilities[id].save;\n messageContent += `${name}: [[1d20+${mod}]] (${game.dnd5e.config.abilities[id]} saving throw)
`;\n break;\n case \"ability\":\n mod = a.data.data.abilities[id].mod + a.data.data.abilities[id].checkBonus;\n messageContent += `${name}: [[1d20+${mod}]] (${game.dnd5e.config.abilities[id]} check)
`;\n break;\n case \"skill\":\n mod = a.data.data.skills[id].total;\n messageContent += `${name}: [[1d20+${mod}]] (${game.dnd5e.config.skills[id]} (${a.data.data.skills[id].ability}) check)
`;\n break;\n default:\n objects = game.dnd5e.config.skills;\n break;\n }\n }\n \n let chatData = {\n user: game.user.id,\n speaker: game.user,\n content: messageContent,\n // Uncomment the following line if you want the results whispered to the GM.\n // whisper: game.users.entities.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 });\n}\n\n// Gets list of selected tokens, or if no tokens are selected then the user's character.\nfunction getTargetActors() {\n const character = game.user.character;\n const controlled = canvas.tokens.controlled;\n let actors = [];\n\n if (controlled.length === 0) return [character] || null;\n\n if (controlled.length > 0) {\n let actors = [];\n for (let i = 0; i < controlled.length; i++) {\n actors.push(controlled[i].actor);\n }\n\n return actors;\n}\nelse throw new Error('You must designate at least one token as the roll target');\n}\n\n\n// Gets a template of abilities or skills, based on the type of check chosen.\nfunction getCheckTemplate(checkType) {\n let objects = new Object();\n \n switch (checkType) {\n case \"save\":\n case \"ability\":\n objects = game.dnd5e.config.abilities;\n break;\n case \"skill\":\n objects = game.dnd5e.config.skills;\n break;\n default:\n objects = game.dnd5e.config.skills;\n break;\n }\n\n let template = `\n
\n
\n \n \n
\n
`;\n\n return template;\n}\n\nfunction getCheckDialogTitle(checkType) {\n switch (checkType) {\n case \"save\":\n return \"Saving Throw\"\n case \"ability\":\n return \"Ability Check\"\n case \"skill\":\n return \"Skill Check\"\n default:\n return \"Unknown Check\"\n }\n}\n\nchooseCheckType.render(true);\n","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]} {"name":"Token HP","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"/**\n * Roll/Reroll selected token HP\n * Author: Tielc#7191\n */\n\nconst tokens = canvas.tokens.controlled;\nlet choice = 0;\n\nif (tokens.length > 0){\n\ttokens.forEach(rollHP);\n} else {\n\tprintMessage(\"No Tokens were selected\");\n}\n\nfunction rollHP(token, index){\n\tlet actor = token.actor;\n\tlet formula = actor.data.data.attributes.hp.formula;\n\t\t\n\tif (actor.data.type != \"npc\" || !formula) return;\n\t\n\tlet hp = new Roll(formula).roll().total;\n\t\n\tactor.data.data.attributes.hp.value = hp;\n\tactor.data.data.attributes.hp.max = hp;\n\t\n\tprintMessage('

' + actor.data.name + '

HP: ' + actor.data.data.attributes.hp.value + '/' + actor.data.data.attributes.hp.max + '(' + token.data._id + ')');\n}\n\nfunction printMessage(message){\n\tlet chatData = {\n\t\tuser : game.user._id,\n\t\tcontent : message,\n\t\tblind: true,\n\t\twhisper : game.users.entities.filter(u => u.isGM).map(u => u._id)\n\t};\n\n\tChatMessage.create(chatData,{});\t\n}\n","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[],"_id":"xMJc8qLMu8rcTJ8z"} -{"name":"Roll Die","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"chat","flags":{"core":{"sourceId":"Macro.8zQiFBIWQcemEtIe"}},"scope":"global","command":"//Script to roll a die\n//Equivalently could be done by changing the macro to chat and entering /r 1d6\n//You can roll any number and any type of dice by changing Roll to ndx\n //n: number of dice\n //x: value (number of sides) on die\nconst roll = new Roll(`1d6`);\nroll.roll();\nroll.toMessage({\n flavor: \"Sneak Attack Damage\",\n});","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[],"_id":"cktksne1wqrTiY7N"} -{"_id":"cktksne1wqrTiY7N","name":"Roll Die","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{"core":{"sourceId":"Macro.8zQiFBIWQcemEtIe"}},"scope":"global","command":"//Script to roll a die\n//Equivalently could be done by changing the macro to chat and entering /r 1d6\n//You can roll any number and any type of dice by changing Roll to ndx\n //n: number of dice\n //x: value (number of sides) on die\nconst roll = new Roll(`1d6`);\nroll.roll();\nroll.toMessage({\n flavor: \"Sneak Attack Damage\",\n});","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]} -{"_id":"ZaiiiR9KPBfzHvea","name":"Roll Initiatives","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"/**\n * Takes all selected tokens and adds them to the combat tracker. Then rolls initative for all NPC tokens.\n */\nasync function main() {\n await canvas.tokens.toggleCombat();\n game.combat.rollNPC({ messageOptions: { rollMode: CONST.DICE_ROLL_MODES.PRIVATE }})\n}\nmain();","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]} +{"_id":"cj0Sm0kWnHx11nFR","name":"Character Stat Roller","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"/**\n * Generates character stats and outputs the table result.\n * Author: @Kekilla#7036 & KrishMero1792\n */\n \n// Formula for rolling \nconst statString = '4d6kh3';\n\n// times to roll those stats\nconst numRolls = 6;\n\n\n//////////////////////////////////////////\n// Don't touch anything below this line //\n//////////////////////////////////////////\nconst stats = Array(numRolls).fill(0).map(e=>new Roll(statString).roll());\n\nconst {faces, rolls} = stats[0].parts[0];\nconst totalAverage = (faces/2 + 0.5) * rolls.filter(i=> i?.discarded !== true).length;\nconst totalDeviation = faces/2;\nconst totalLow = Math.ceil(totalAverage - totalDeviation);\nconst totalHigh = Math.ceil(totalAverage + totalDeviation);\n\nconst header = rolls.map((roll, index) => `D${index + 1}`).join('');\n\nlet tableRows = '';\nlet finalSum = 0;\nfor(let {parts, total} of stats) {\n tableRows += ``;\n tableRows += parts[0].rolls.map(({result}) => `${result}`).join('');\n tableRows += `${total}`;\n finalSum += total;\n}\n\nconst colspan = `colspan=\"${rolls.length + 1}\"`;\nconst center = `text-align:center;`;\n\nlet content = `\n \n \n \n \n \n ${header}\n \n \n ${tableRows}\n \n \n \n \n

New Ability Scores

\n
${statString} was rolled ${numRolls} times.
Total
Final Sum:${finalSum}
\n`;\n\n\nChatMessage.create({content});\n\nfunction colorSetter(number,low,high)\n{\n if(number <= low) return 'color:red';\n if(number >= high) return 'color:green';\n return '';\n}","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]} diff --git a/packs/macros-token.db b/packs/macros-token.db index 0e3f33f..aa21765 100644 --- a/packs/macros-token.db +++ b/packs/macros-token.db @@ -1,3 +1,4 @@ +{"name":"Token Reiconize","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{"core":{"sourceId":"Macro.cRA2xgOGCm81mqfu"}},"scope":"global","command":"/*\nThe purpose of this macro is to trigger VTTA-Iconizer to update icons on tokens. This macro goes through\nevery actor in the game and changes the name of every item, feature, spell, etc. by adding a '~' to the end\nthen removing the '~' from the end. This does NOT update actors in a compendium.\n\nThis was developed primarily to update actors after updating a custom dictionary used by VTTA-Iconizer.\n\nA dialog will be displayed to allow you to go forward with the update or cancel. Once the update starts, a\nnew dialog will be shown with a cancel button. That cancel button is stupid and doesn't like to be clicked.\nYou'll have to click it a bunch to convince it to stop looping.\n*/\n\nlet canceled = false\nlet totalItemsProcessed = 0\nlet totalActorsProcessed = 0\n\nfunction CancelTouching() {\n canceled = true\n}\nDialogQuery()\n\nfunction DialogResults() {\n let dialog = new Dialog({\n content: `

Processing of ${totalActorsProcessed} actors and ${totalItemsProcessed} items complete

`,\n title: 'Reiconizer Done',\n buttons: {\n one: {\n label: \"Done\"\n },\n },\n })\n dialog.render(true)\n}\n\nasync function DialogQuery() {\n return new Dialog({\n title: `Reiconizer`,\n content: `

Is your world backed up? Do you wish to activate Iconizer?

`,\n buttons: {\n one: {\n icon: ``,\n label: \"Continue\",\n callback: async() => {\n await TouchActors()\n DialogResults()\n }\n },\n two: {\n icon: ``,\n label: \"Cancel\",\n callback: () => {}\n }\n },\n default: \"Cancel\",\n }).render(true);\n}\n\nfunction DialogWorking() {\n let dialog = new Dialog({\n title: `Reiconizer Working`,\n content: `

Task in progress

`,\n buttons: {\n one: {\n icon: ``,\n label: \"Cancel (You might have to click me a bunch)\",\n callback: () => {\n CancelTouching()\n }\n },\n },\n })\n\n dialog.position.height = 'Auto'\n dialog.render(true)\n return dialog\n}\n\nasync function TouchActors() {\n let working = DialogWorking()\n let remaining = game.actors.entries.length\n for (let _actor of game.actors.entries) {\n if (!canceled) {\n try {\n for (let item of _actor.items.entries) {\n if (!canceled) {\n working.data.content = `

Processing: ${_actor.name}

Remaining: ${remaining}

Item: ${item.name}

`\n working.render(true)\n let update = {\n _id: item._id,\n name: item.name + '~'\n }\n await _actor.updateEmbeddedEntity('OwnedItem', update)\n update.name = update.name.slice(0, -1)\n await _actor.updateEmbeddedEntity('OwnedItem', update)\n totalItemsProcessed++\n }\n }\n } catch (err) {\n console.log(`Error processing ${_actor.name}, ERROR: ${err}`)\n }\n remaining--\n totalActorsProcessed++\n }\n }\n\n working.close()\n}","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[],"_id":"ChZwQULAXvLnylbC"} {"_id":"Cro7DdjJpepCX22U","name":"Light Picker","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"function tokenUpdate(data) {\n canvas.tokens.controlled.map(token => token.update(data));\n}\n\nlet dialogEditor = new Dialog({\n title: `Token Light Picker`,\n content: `Pick the light source the selected token is holding.`,\n buttons: {\n none: {\n label: `None`,\n callback: () => {\n tokenUpdate({\"dimLight\": null, \"brightLight\": null, \"lightAngle\": 360,});\n dialogEditor.render(true);\n }\n },\n torch: {\n label: `Torch`,\n callback: () => {\n tokenUpdate({\"dimLight\": 40, \"brightLight\": 20, \"lightAngle\": 360,});\n dialogEditor.render(true);\n }\n },\n lamp: {\n label: `Lamp`,\n callback: () => {\n tokenUpdate({\"dimLight\": 45, \"brightLight\": 15, \"lightAngle\": 360,});\n dialogEditor.render(true);\n }\n },\n bullseye: {\n label: `Bullseye Lantern`,\n callback: () => {\n tokenUpdate({\"dimLight\": 120, \"brightLight\": 60, \"lightAngle\": 45,});\n dialogEditor.render(true);\n }\n },\n hoodedOpen: {\n label: `Hooded Lantern (Open)`,\n callback: () => {\n tokenUpdate({\"dimLight\": 60, \"brightLight\": 30, \"lightAngle\": 360,});\n dialogEditor.render(true);\n }\n },\n hoodedClosed: {\n label: `Hooded Lantern (Closed)`,\n callback: () => {\n tokenUpdate({\"dimLight\": 5, \"brightLight\": 0, \"lightAngle\": 360,});\n dialogEditor.render(true);\n }\n },\n close: {\n icon: \"\",\n label: `Close`\n },\n },\n default: \"close\",\n close: () => {}\n});\n\ndialogEditor.render(true)\n","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]} {"_id":"F4KqUssiXZNULUFU","name":"Mirror Token Image","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"// Flips the selected token image along the Y axis.\n// Change mirrorY to mirrorX to flip across the X axis\nfor ( let token of canvas.tokens.controlled ) {\n let flip = !token.data.mirrorY || false;\n token.update({mirrorY: flip});\n};\n","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]} {"name":"Remove Conditions","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"for ( let token of canvas.tokens.controlled ){\n await token.update({\"effects\": []});\n}\n","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[],"_id":"IKx2ap2YfZtAYLDv"} @@ -11,4 +12,5 @@ {"_id":"jBcKwGUwHS2V6nat","name":"Token Vision Configuration","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"chat","flags":{},"scope":"global","command":"// A macro for the Foundry virtual tabletop that lets a user configure their token's vision and lighting settings. This script is taken from Sky's foundry repo here: https://github.com/Sky-Captain-13/foundry/blob/master/scriptMacros/tokenVision.js.\n\nlet applyChanges = false;\nnew Dialog({\n title: `Token Vision Configuration`,\n content: `\n
\n
\n \n \n
\n
\n \n \n
\n
\n `,\n buttons: {\n yes: {\n icon: \"\",\n label: `Apply Changes`,\n callback: () => applyChanges = true\n },\n no: {\n icon: \"\",\n label: `Cancel Changes`\n },\n },\n default: \"yes\",\n close: html => {\n if (applyChanges) {\n for ( let token of canvas.tokens.controlled ) {\n let visionType = html.find('[name=\"vision-type\"]')[0].value || \"none\";\n let lightSource = html.find('[name=\"light-source\"]')[0].value || \"none\";\n let dimSight = 0;\n let brightSight = 0;\n let dimLight = 0;\n let brightLight = 0;\n let lightAngle = 360;\n let lockRotation = token.data.lockRotation;\n // Get Vision Type Values\n switch (visionType) {\n case \"dim0\":\n dimSight = 0;\n brightSight = 0;\n break;\n case \"dim30\":\n dimSight = 30;\n brightSight = 0;\n break;\n case \"dim60\":\n dimSight = 60;\n brightSight = 0;\n break;\n case \"dim90\":\n dimSight = 90;\n brightSight = 0;\n break;\n case \"dim120\":\n dimSight = 120;\n brightSight = 0;\n break;\n case \"dim150\":\n dimSight = 150;\n brightSight = 0;\n break;\n case \"dim180\":\n dimSight = 180;\n brightSight = 0;\n break;\n case \"bright120\":\n dimSight = 0;\n brightSight= 120;\n break;\n case \"nochange\":\n default:\n dimSight = token.data.dimSight;\n brightSight = token.data.brightSight;\n }\n // Get Light Source Values\n switch (lightSource) {\n case \"none\":\n dimLight = 0;\n brightLight = 0;\n break;\n case \"candle\":\n dimLight = 10;\n brightLight = 5;\n break;\n case \"lamp\":\n dimLight = 45;\n brightLight = 15;\n break;\n case \"bullseye\":\n dimLight = 120;\n brightLight = 60;\n lockRotation = false;\n lightAngle = 52.5;\n break;\n case \"hooded-dim\":\n dimLight = 5;\n brightLight = 0;\n break;\n case \"hooded-bright\":\n dimLight = 60;\n brightLight = 30;\n break;\n case \"light\":\n dimLight = 40;\n brightLight = 20;\n break;\n case \"torch\":\n dimLight = 40;\n brightLight = 20;\n break;\n case \"moon-touched\":\n dimLight = 30;\n brightLight = 15;\n break;\n case \"nochange\":\n default:\n dimLight = token.data.dimLight;\n brightLight = token.data.brightLight;\n lightAngle = token.data.lightAngle;\n lockRotation = token.data.lockRotation;\n }\n // Update Token\n console.log(token);\n token.update({\n vision: true,\n dimSight: dimSight,\n brightSight: brightSight,\n dimLight: dimLight,\n brightLight: brightLight,\n lightAngle: lightAngle,\n lockRotation: lockRotation\n });\n }\n }\n }\n}).render(true);\n","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]} {"_id":"p8daoiwkQPAnOJNm","name":"Token Multi Select","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"/*\nSwap the selected token with another of similar name via a\ndrop-down menu in a dialog box.\n\nTokens for each character should be named similarly but end with\n'_walking.png', '_fighting.png', and '_sneaking.png'. For example,\n'talion_walking.png', 'talion_fighting.png', and 'talion_sneaking.png'\n\nIf a token does not exist, mystery man will be automatically selected.\n*/\n\nif (actor !== undefined && actor !== null) {\n let d = new Dialog({\n title: 'Token Mogrifier',\n content: \"

Select a new token

\" +\n \"\",\n buttons: {\n ok: {\n icon: '',\n label: \"Do it!\",\n callback: () =>\n token.update({\n img: token.data.img.slice(0, token.data.img.lastIndexOf('_')) + document.getElementById(\"token\").value\n })\n },\n cancel: {\n icon: '',\n label: \"Nevermind\",\n callback: () => {}\n }\n }\n });\n d.render(true);\n} else {\n ui.notifications.warn(\"Please select a token.\");\n}\n","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]} {"name":"Change Disposition","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"let applyChanges = false;\nnew Dialog({\n title: `Token Disposition Changer`,\n content: `\n
\n
\n \n \n
\n
\n `,\n buttons: {\n yes: {\n icon: \"\",\n label: `Apply Changes`,\n callback: () => applyChanges = true\n },\n no: {\n icon: \"\",\n label: `Cancel Changes`\n },\n },\n default: \"yes\",\n close: html => {\n if (applyChanges) {\n for ( let token of canvas.tokens.controlled ) {\n let dispoType = html.find('[name=\"dispo-type\"]')[0].value || \"none\";\n switch (dispoType) {\n case \"hostile\":\n token.update({\"disposition\": -1});\n break;\n case \"friendly\":\n token.update({\"disposition\": 1});\n break;\n case \"neutral\":\n token.update({\"disposition\": 0});\n break;\n case \"nochange\":\n default:\n }\n }\n }\n }\n}).render(true);\n","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[],"_id":"soTXCSqJwGWNG9ga"} -{"name":"Token Reiconize","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{"core":{"sourceId":"Macro.cRA2xgOGCm81mqfu"}},"scope":"global","command":"/*\nThe purpose of this macro is to trigger VTTA-Iconizer to update icons on tokens. This macro goes through\nevery actor in the game and changes the name of every item, feature, spell, etc. by adding a '~' to the end\nthen removing the '~' from the end. This does NOT update actors in a compendium.\n\nThis was developed primarily to update actors after updating a custom dictionary used by VTTA-Iconizer.\n\nA dialog will be displayed to allow you to go forward with the update or cancel. Once the update starts, a\nnew dialog will be shown with a cancel button. That cancel button is stupid and doesn't like to be clicked.\nYou'll have to click it a bunch to convince it to stop looping.\n*/\n\nlet canceled = false\nlet totalItemsProcessed = 0\nlet totalActorsProcessed = 0\n\nfunction CancelTouching() {\n canceled = true\n}\nDialogQuery()\n\nfunction DialogResults() {\n let dialog = new Dialog({\n content: `

Processing of ${totalActorsProcessed} actors and ${totalItemsProcessed} items complete

`,\n title: 'Reiconizer Done',\n buttons: {\n one: {\n label: \"Done\"\n },\n },\n })\n dialog.render(true)\n}\n\nasync function DialogQuery() {\n return new Dialog({\n title: `Reiconizer`,\n content: `

Is your world backed up? Do you wish to activate Iconizer?

`,\n buttons: {\n one: {\n icon: ``,\n label: \"Continue\",\n callback: async() => {\n await TouchActors()\n DialogResults()\n }\n },\n two: {\n icon: ``,\n label: \"Cancel\",\n callback: () => {}\n }\n },\n default: \"Cancel\",\n }).render(true);\n}\n\nfunction DialogWorking() {\n let dialog = new Dialog({\n title: `Reiconizer Working`,\n content: `

Task in progress

`,\n buttons: {\n one: {\n icon: ``,\n label: \"Cancel (You might have to click me a bunch)\",\n callback: () => {\n CancelTouching()\n }\n },\n },\n })\n\n dialog.position.height = 'Auto'\n dialog.render(true)\n return dialog\n}\n\nasync function TouchActors() {\n let working = DialogWorking()\n let remaining = game.actors.entries.length\n for (let _actor of game.actors.entries) {\n if (!canceled) {\n try {\n for (let item of _actor.items.entries) {\n if (!canceled) {\n working.data.content = `

Processing: ${_actor.name}

Remaining: ${remaining}

Item: ${item.name}

`\n working.render(true)\n let update = {\n _id: item._id,\n name: item.name + '~'\n }\n await _actor.updateEmbeddedEntity('OwnedItem', update)\n update.name = update.name.slice(0, -1)\n await _actor.updateEmbeddedEntity('OwnedItem', update)\n totalItemsProcessed++\n }\n }\n } catch (err) {\n console.log(`Error processing ${_actor.name}, ERROR: ${err}`)\n }\n remaining--\n totalActorsProcessed++\n }\n }\n\n working.close()\n}","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[],"_id":"ChZwQULAXvLnylbC"} +{"_id":"Cro7DdjJpepCX22U","name":"Light Picker","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"function tokenUpdate(data) {\n canvas.tokens.controlled.map(token => token.update(data));\n}\n\nlet torchAnimation = {\"type\": \"torch\", \"speed\": 1, \"intensity\": 1};\n\nlet dialogEditor = new Dialog({\n title: `Token Light Picker`,\n content: `Pick the light source the selected token is holding.`,\n buttons: {\n none: {\n label: `None`,\n callback: () => {\n tokenUpdate({\"dimLight\": null, \"brightLight\": null, \"lightAngle\": 360,});\n dialogEditor.render(true);\n }\n },\n torch: {\n label: `Torch`,\n callback: () => {\n tokenUpdate({\"dimLight\": 40, \"brightLight\": 20, \"lightAngle\": 360, \"lightAnimation\": torchAnimation});\n dialogEditor.render(true);\n }\n },\n light: {\n label: `Light cantrip`,\n callback: () => {\n tokenUpdate({\"dimLight\": 40, \"brightLight\": 20, \"lightAngle\": 360, \"lightAnimation\": {\"type\": \"none\"}});\n dialogEditor.render(true);\n }\n },\n lamp: {\n label: `Lamp`,\n callback: () => {\n tokenUpdate({\"dimLight\": 45, \"brightLight\": 15, \"lightAngle\": 360, \"lightAnimation\": torchAnimation});\n dialogEditor.render(true);\n }\n },\n bullseye: {\n label: `Bullseye Lantern`,\n callback: () => {\n tokenUpdate({\"dimLight\": 120, \"brightLight\": 60, \"lightAngle\": 45, \"lightAnimation\": torchAnimation});\n dialogEditor.render(true);\n }\n },\n hoodedOpen: {\n label: `Hooded Lantern (Open)`,\n callback: () => {\n tokenUpdate({\"dimLight\": 60, \"brightLight\": 30, \"lightAngle\": 360, \"lightAnimation\": torchAnimation});\n dialogEditor.render(true);\n }\n },\n hoodedClosed: {\n label: `Hooded Lantern (Closed)`,\n callback: () => {\n tokenUpdate({\"dimLight\": 5, \"brightLight\": 0, \"lightAngle\": 360, \"lightAnimation\": torchAnimation});\n dialogEditor.render(true);\n }\n },\n close: {\n icon: \"\",\n label: `Close`\n },\n },\n default: \"close\",\n close: () => {}\n});\n\ndialogEditor.render(true)","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]} +{"_id":"jBcKwGUwHS2V6nat","name":"Token Vision Configuration","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"chat","flags":{},"scope":"global","command":"// A macro for the Foundry virtual tabletop that lets a user configure their token's vision and lighting settings. This script is taken from Sky's foundry repo here: https://github.com/Sky-Captain-13/foundry/blob/master/scriptMacros/tokenVision.js.\n\nlet applyChanges = false;\nnew Dialog({\n title: `Token Vision Configuration`,\n content: `\n
\n
\n \n \n
\n
\n \n \n
\n
\n `,\n buttons: {\n yes: {\n icon: \"\",\n label: `Apply Changes`,\n callback: () => applyChanges = true\n },\n no: {\n icon: \"\",\n label: `Cancel Changes`\n },\n },\n default: \"yes\",\n close: html => {\n if (applyChanges) {\n for ( let token of canvas.tokens.controlled ) {\n let visionType = html.find('[name=\"vision-type\"]')[0].value || \"none\";\n let lightSource = html.find('[name=\"light-source\"]')[0].value || \"none\";\n let dimSight = 0;\n let brightSight = 0;\n let dimLight = 0;\n let brightLight = 0;\n let lightAngle = 360;\n let lockRotation = token.data.lockRotation;\n let lightAnimation = token.data.lightAnimation;\n let lightAlpha = token.data.lightAlpha;\n let lightColor = token.data.lightColor;\n const colorFire = \"#f8c377\";\n const colorWhite = \"#ffffff\";\n const colorMoonGlow = \"#f4f1c9\";\n // Get Vision Type Values\n switch (visionType) {\n case \"dim0\":\n dimSight = 0;\n brightSight = 0;\n break;\n case \"dim30\":\n dimSight = 30;\n brightSight = 0;\n break;\n case \"dim60\":\n dimSight = 60;\n brightSight = 0;\n break;\n case \"dim90\":\n dimSight = 90;\n brightSight = 0;\n break;\n case \"dim120\":\n dimSight = 120;\n brightSight = 0;\n break;\n case \"dim150\":\n dimSight = 150;\n brightSight = 0;\n break;\n case \"dim180\":\n dimSight = 180;\n brightSight = 0;\n break;\n case \"bright120\":\n dimSight = 0;\n brightSight= 120;\n break;\n case \"nochange\":\n default:\n dimSight = token.data.dimSight;\n brightSight = token.data.brightSight;\n }\n // Get Light Source Values\n switch (lightSource) {\n case \"none\":\n dimLight = 0;\n brightLight = 0;\n lightAnimation = {type: \"none\"};\n break;\n case \"candle\":\n dimLight = 10;\n brightLight = 5;\n lightAnimation = {type: \"torch\", speed: 2, intensity: 2};\n lightColor = colorFire;\n lightAlpha = 0.15;\n break;\n case \"lamp\":\n dimLight = 45;\n brightLight = 15;\n lightAnimation = {type: \"torch\", speed: 2, intensity: 2};\n lightColor = colorFire;\n lightAlpha = 0.15;\n break;\n case \"bullseye\":\n dimLight = 120;\n brightLight = 60;\n lockRotation = false;\n lightAngle = 52.5;\n lightAnimation = {type: \"torch\", speed: 2, intensity: 2};\n lightColor = colorFire;\n lightAlpha = 0.15;\n break;\n case \"hooded-dim\":\n dimLight = 5;\n brightLight = 0;\n lightAnimation = {type: \"torch\", speed: 2, intensity: 2};\n lightColor = colorFire;\n lightAlpha = 0.15;\n break;\n case \"hooded-bright\":\n dimLight = 60;\n brightLight = 30;\n lightAnimation = {type: \"torch\", speed: 2, intensity: 2};\n lightColor = colorFire;\n lightAlpha = 0.15;\n break;\n case \"light\":\n dimLight = 40;\n brightLight = 20;\n lightAnimation = {type: \"none\"};\n lightColor = colorWhite;\n break;\n case \"torch\":\n dimLight = 40;\n brightLight = 20;\n lightAnimation = {type: \"torch\", speed: 2, intensity: 2};\n lightColor = colorFire;\n lightAlpha = 0.15;\n break;\n case \"moon-touched\":\n dimLight = 30;\n brightLight = 15;\n lightAnimation = {type: \"none\"};\n lightColor = colorMoonGlow;\n break;\n case \"nochange\":\n default:\n dimLight = token.data.dimLight;\n brightLight = token.data.brightLight;\n lightAngle = token.data.lightAngle;\n lockRotation = token.data.lockRotation;\n lightAnimation = token.data.lightAnimation;\n lightAlpha = token.data.lightAlpha;\n lightColor = token.data.lightColor;\n }\n // Update Token\n console.log(token);\n token.update({\n vision: true,\n dimSight: dimSight,\n brightSight: brightSight,\n dimLight: dimLight,\n brightLight: brightLight,\n lightAngle: lightAngle,\n lockRotation: lockRotation,\n lightAnimation: lightAnimation,\n lightAlpha: lightAlpha,\n lightColor: lightColor\n });\n }\n }\n }\n}).render(true);","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]}