From c39502b9732108643d64b390a27e5aa2946fc5ff Mon Sep 17 00:00:00 2001 From: Anthony Vadala Date: Mon, 1 Jun 2020 17:45:49 -0400 Subject: [PATCH] Updated with latest macros and changes --- packs/macros-actor.db | 1 + packs/macros-roll.db | 2 ++ 2 files changed, 3 insertions(+) diff --git a/packs/macros-actor.db b/packs/macros-actor.db index 33c428f..896129a 100644 --- a/packs/macros-actor.db +++ b/packs/macros-actor.db @@ -10,3 +10,4 @@ {"_id":"hW29VmM09DenDXNz","name":"Rage","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"// \tDISCLAIMER: This macro is a slightly modified version of the original masterwork written by Felix#6196.\n//\t\t\t\tOriginal version is on the wiki here: \n//\t\t\t\thttps://github.com/foundry-vtt-community/wiki/wiki/Script-Macros#rage-toggle-for-inhabited-character\n//\n// \tDifferences in my version (Norc#5108, 05/02/2020):\n//\n//\t\tBiggest change: Changed macro to work for selected token, NOT the user's official character (game.user.character)\n// \t \tThis eliminated an error I was getting as a GM that prevented me from using the script.\n//\n//\t\tOther changes: 1. Fixed Rage icon toggling, for me it was backwards.\n// \t \t2. Added error messages for trying to rage with no token or no barbarian selected\n//\t\t\t\t\t \t3. Added auto-bear totem detection, with a minor edit to character sheet required.\n//\n//\tChangelog: _6 05/15/2020: 1. Removed an explicit definition of Token that caused an error inside a script macro and added comments\n//\t\t\t\t explaining how \"token\" and \"actor\" work in script macros.\n//\t\t\t\t 2. Changed \"magic words\" of the Bear Totem Spirit feature from \"Totem Spirit (Bear)\"\n//\t\t\t\t to \"Totem Spirit: Bear\" to match VTTA importer's new support for the feature.\n\n//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n//!!!\tBonus Tip 1: !!!\n//!!!\tIf you chose the Spirit Seeker Primal path, and you chose the Bear totem spirit (resistance to all non-psychic damage), !!!\n//!!!\tin your 5E character sheet, edit the name of your Totem Spirit feature to EXACTLY \"Totem Spirit: Bear\"\t\t\t\t !!!\n//!!! if it isn't already named that by VTTA Beyond Importer Module. This allows you to automatically gain the extra Bear Totem Spirit !!!\n//!!! resistances. \t\t\t\t\t\t\t\t !!!\n//!!! !!!\n//!!! \tBonus Tip 2: !!!\n//!!!\tIf you use the Combat Utility Belt module's Condition Lab, add a condition called Raging with the same icon as the optional rage !!!\n//!!!\ticon overlay, 'icons/svg/explosion.svg' by default. See OPTIONAL RAGE ICON section below. !!!\n//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n\t//declarations\n\tlet barb='';\n\tlet chatMsg='';\n\tlet bear = '';\n\tlet rageIconPath = '';\n\n//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n//!!! \tOPTIONAL RAGE ICON - Adds this icon to your character when raging only. Comment out following line to disable (add // before) !!!\n\n\t\trageIconPath = 'icons/svg/explosion.svg'\n\n//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n\n\n//check to see if Actor exists and is a barbarian\n\n\t\t\t//Script macros automagically define \"token\" and \"actor\" for you. If you did need to manually\n\t\t\t//define it, this is how you would do so: \n\t\t\t//let actor = game.user.character;\n\nif (actor !== undefined && actor !== null) {\n // get the barbarian class item\n barb = actor.items.find(i => i.name === 'Barbarian');\n if (barb == undefined) { \n ui.notifications.warn(\"Please select a single barbarian token.\");\n }\n \n\tif (barb !== undefined && barb !== null) {\n\t\tchatMsg = '';\n\t\tlet enabled = false;\n\t\t// store the state of the rage toggle in flags\n\t\tif (actor.data.flags.rageMacro !== null && actor.data.flags.rageMacro !== undefined) {\n\t\t\tenabled = true;\n\t\t}\n\t\t// if rage is active, disable it\n\t\tif (enabled) {\n\t\t\tchatMsg = `${actor.name} is no longer raging.`;\n\n\t\t\t// reset resistances\n\t\t\tlet obj = {};\n\t\t\tobj['flags.rageMacro'] = null;\n\t\t\tobj['data.traits.dr'] = actor.data.flags.rageMacro.oldResistances;\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.rageMacro !== null && item.data.flags.rageMacro !== undefined) {\n\t\t\t\t\t// restoring the old value from flags\n\t\t\t\t\tlet oldDmg = item.data.flags.rageMacro.oldDmg;\n\t\t\t\t\tlet obj = {};\n\t\t\t\t\tobj['data.damage.parts'] = oldDmg;\n\t\t\t\t\tobj['flags.rageMacro'] = null;\n\t\t\t\t\titem.update(obj);\n\t\t\t\t}\n\t\t\t}\n\n\n\t\t// if rage is disabled, enable it\n\t\t} else {\n\t\t\tchatMsg = `${actor.name} is RAAAAAGING!`;\n\n\t\t\t// update resistance\n\t\t\tlet obj = {};\n\t\t\t// storing old resistances in flags to restore later\n\t\t\tobj['flags.rageMacro.enabled'] = true;\n\t\t\tobj['flags.rageMacro.oldResistances'] = JSON.parse(JSON.stringify(actor.data.data.traits.dr));\n\n\t\t\t// add bludgeoning, piercing and slashing resistance\n\t\t\tlet newResistance = actor.data.data.traits.dr;\n\t\t\tif (newResistance.value.indexOf('bludgeoning') === -1) newResistance.value.push('bludgeoning');\n\t\t\tif (newResistance.value.indexOf('piercing') === -1) newResistance.value.push('piercing');\n\t\t\tif (newResistance.value.indexOf('slashing') === -1) newResistance.value.push('slashing');\n\n\n\t\t\t\n\t\t\t//If bear totem, add bear totem resistances.\n\t\t\tbear = actor.items.find(i => i.name === \"Totem Spirit: Bear\")\n\t\t\tif (bear !== undefined && bear !== null ) {\n\t\t\t\tif (newResistance.value.indexOf('acid') === -1) newResistance.value.push('acid');\n\t\t\t\tif (newResistance.value.indexOf('cold') === -1) newResistance.value.push('cold');\n\t\t\t\tif (newResistance.value.indexOf('fire') === -1) newResistance.value.push('fire');\n\t\t\t\tif (newResistance.value.indexOf('force') === -1) newResistance.value.push('force');\n\t\t\t\tif (newResistance.value.indexOf('lightning') === -1) newResistance.value.push('lightning');\n\t\t\t\tif (newResistance.value.indexOf('necrotic') === -1) newResistance.value.push('necrotic');\n\t\t\t\tif (newResistance.value.indexOf('poison') === -1) newResistance.value.push('poison');\n\t\t\t\tif (newResistance.value.indexOf('radiant') === -1) newResistance.value.push('radiant');\n\t\t\t\tif (newResistance.value.indexOf('thunder') === -1) newResistance.value.push('thunder');\n\t\t\t}\n\t\t\t\n\t\t\tobj['data.traits.dr'] = newResistance;\n\t\t\tactor.update(obj);\n\n\t\t\t// update items\n\t\t\t// determining the barbarian level\n\t\t\tlet barblvl = barb.data.data.levels;\n\t\t\t// the formula to determin the rage bonus damage depending on barbarian level\n\t\t\tlet ragedmg = 2 + Math.floor(barblvl / 9) - (barblvl === 8 ? 1 : 0);\n\t\t\tfor (let item of actor.items) {\n\t\t\t\tlet isMelee = getProperty(item, 'data.data.actionType') === 'mwak';\n\t\t\t\tif (isMelee && item.data.data.damage.parts.length > 0) {\n\t\t\t\t\tconsole.log('updating ' + item);\n\t\t\t\t\tlet obj = {};\n\t\t\t\t\tlet dmg = item.data.data.damage.parts;\n\t\t\t\t\tobj['flags.rageMacro.oldDmg'] = JSON.parse(JSON.stringify(dmg));\n\t\t\t\t\tdmg[0][0] = `${dmg[0][0]} + ${ragedmg}`;\n\t\t\t\t\tobj['data.damage.parts'] = dmg;\n\t\t\t\t\titem.update(obj);\n\t\t\t\t}\n\t\t\t}\t\n\t\t}\n\t// toggle rage icon\n\t// - this is optional and requires you to set the path for the token icon you want to use for rage\n\t\t\t\t//Script macros automagically define \"token\" and \"actor\" for you. If you did need to manually\n\t\t\t\t//define it, this is how you would do so: \n\t\t\t\t//let token = canvas.tokens.controlled.find(t => t.actor.id === actor.id);\n\ttoken.toggleEffect(rageIconPath);\n\t}\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}\n","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]} {"_id":"p0KlDQ5Ab2IIsNJY","name":"Link Actors","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"const scene = game.scenes.active;\nconst unlinked = canvas.scene.data.tokens.map(t => {\n const actor = game.actors.entities.find(a => a.name === t.name);\n if (actor) {\n return {\n _id: t._id,\n actorId: actor.id\n }\n } else {\n console.log(t.name);\n return {\n _id: t._id,\n actorId: \"\"\n }\n }\n});\nconst updates = duplicate(unlinked);\n\nscene.updateEmbeddedEntity(\"Token\", updates);\n\nui.notifications.info('Tokens linked to actors.');\nconsole.log(updates);","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]} {"_id":"zKbQ7lPYQC96DlIS","name":"Monk Ki","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"/**\n * Monk Ki Point spender\n * \n * This macro will prompt which Feature you want to spend Ki points on.\n * \n * Flurry of Blows: Automatically cast two Unarmed Strike's\n * Stunning Strike: Automatically show the saving throw DC\n * Deflect Missiles: Automatically show the damage reduction\n */\n(async () => {\n const kiName = \"Ki Points\";\n const errNoMonkToken = \"Please select a single monk token.\";\n\n const sendChat = async (msg) => {\n let chatData = {\n user: game.user.id,\n speaker: ChatMessage.getSpeaker(),\n content: msg,\n };\n ChatMessage.create(chatData, {});\n }\n\n if (!actor) {\n ui.notifications.warn(errNoMonkToken);\n return\n }\n\n let monk = actor.items.find(i => i.name === 'Monk' && i.type === 'class');\n if (!monk) {\n ui.notifications.warn(errNoMonkToken);\n return\n }\n\n let monkLevels = monk.data.data.levels || 20;\n //let subClass = monk.data.data.subclass;\n\n if (monkLevels < 2) {\n ui.notifications.warn('You must have a least 2 Monk levels to use ki points.');\n return\n }\n\n class KiFeature {\n /**\n * @param {string} name\n * @param {string} fallbackText\n * @param {number} requireLevel\n * @param {function} action\n * @param {function} appendTemplate\n * @param {number} kiCost\n */\n constructor(name, fallbackText, requireLevel, action, appendTemplate, kiCost) {\n this.name = name;\n this.fallbackText = fallbackText;\n this.requireLevel = requireLevel;\n this.kiCost = kiCost || 1;\n if (action) {\n this.action = action;\n }\n if (appendTemplate) {\n this.appendTemplate = appendTemplate;\n }\n }\n\n render(allowHigher) {\n let entry = null;\n\n const pack = game.packs.get(\"dnd5e.classfeatures\");\n if (!pack) {\n console.warn('Could not find \"dnd5e.classfeatures\" compendium.');\n } else {\n entry = pack.index.find(e => e.name === this.name);\n }\n\n if (!allowHigher && this.requireLevel && monkLevels && this.requireLevel > monkLevels) {\n ui.notifications.warn(`You need to have ${this.requireLevel} monk levels, you only have ${monkLevels}.`)\n return\n }\n\n if (entry) {\n pack.getEntity(entry._id).then(o => {\n let template = `@Compendium[dnd5e.classfeatures.${entry._id}]{${this.name}}\n ${o.data.data.description.value}`;\n if (this.appendTemplate) {\n template += '\\n\\n' + this.appendTemplate();\n }\n sendChat(template);\n if (this.action) {\n this.action();\n }\n });\n } else {\n console.warn(`Could not find \"${this.name}\" entry in compendium.`);\n let template = this.fallbackText;\n if (this.appendTemplate) {\n template += '\\n\\n' + this.appendTemplate();\n }\n sendChat(template);\n if (this.action) {\n this.action();\n }\n }\n }\n }\n\n const openHand = !!actor.items.find(o => o.data.name === 'Open Hand Technique') ? `
In addition, you can impose one of the following: Saving throw DC ${10 + actor.data.data.abilities.wis.mod}` : \"\";\n\n const features = [\n new KiFeature(\"Ki: Flurry of Blows\",\n `Immediately after you take the Attack action on your turn, you can spend 1 ki point to make two unarmed strikes as a bonus action. ${openHand}`,\n 2,\n function () {\n // Automatically roll two Unarmed Strike attacks\n let strike = actor.items.find(o => o.data.name === 'Unarmed Strike' && o.data.labels.activation === '1 Action')\n if (strike) {\n strike.roll();\n strike.roll();\n }\n }),\n new KiFeature(\"Ki: Patient Defense\",\n \"You can spend 1 ki point to take the Dodge action as a bonus action on your turn.\",\n 2),\n new KiFeature(\"Ki: Step of the Wind\",\n \"You can spend 1 ki point to take the Disengage or Dash action as a bonus action on your turn, and your jump distance is doubled for the turn.\",\n 2),\n new KiFeature(\"Deflect Missiles\",\n `Starting at 3rd level, you can use your reaction to deflect or catch the missile when you are hit by a ranged weapon attack. When you do so, the damage you take from the attack is reduced by 1d10 + your Dexterity modifier + your monk level.
\n\n If you reduce the damage to 0, you can catch the missile if it is small enough for you to hold in one hand and you have at least one hand free. If you catch a missile in this way, you can spend 1 ki point to make a ranged attack with the weapon or piece of ammunition you just caught, as part of the same reaction. You make this attack with proficiency, regardless of your weapon proficiencies, and the missile counts as a monk weapon for the attack, which has a normal range of 20 feet and a long range of 60 feet.`,\n 3,\n null,\n function () {\n return `Damage reduction: [[/r 1d10+${actor.data.data.abilities.dex.mod}+${monkLevels}]]`;\n }),\n new KiFeature(\"Ki: Stunning Strike\",\n \"Starting at 5th level, you can interfere with the flow of ki in an opponent’s body. When you hit another creature with a melee weapon attack, you can spend 1 ki point to attempt a stunning strike. The target must succeed on a Constitution saving throw or be stunned until the end of your next turn.\",\n 5,\n null,\n function () {\n // Append the saving throw DC to the chat message\n return `CON saving throw (DC [[8+${actor.data.data.abilities.wis.mod}+@attributes.prof]])`;\n }),\n new KiFeature(\"Ki: Diamond Soul\",\n `Beginning at 14th level, your mastery of ki grants you proficiency in all saving throws.\n\n Additionally, whenever you make a saving throw and fail, you can spend 1 ki point to reroll it and take the second result.`,\n 14),\n new KiFeature(\"Ki: Empty Body\",\n `Beginning at 18th level, you can use your action to spend 4 ki points to become invisible for 1 minute. During that time, you also have resistance to all damage but force damage.\n\n Additionally, you can spend 8 ki points to cast the astral projection spell, without needing material components. When you do so, you can’t take any other creatures with you.`,\n 18,\n null,\n function () {\n return \"Note: 4 ki points have been spent. Adjust manually if casting astral projection spell.\";\n },\n 4),\n ];\n\n const consumeKi = (feature, allowNegative, allowHigher) => {\n let hasAvailableResource = false;\n let selected = features.find(o => o.name == feature);\n let kiCost = selected.kiCost || 1;\n\n // Look for Resources under the Core actor data\n let resourceKey = Object.keys(actor.data.data.resources).filter(k => actor.data.data.resources[k].label === kiName).shift();\n if (resourceKey && (actor.data.data.resources[resourceKey].value >= kiCost || allowNegative)) {\n hasAvailableResource = true;\n actor.data.data.resources[resourceKey].value -= kiCost;\n }\n\n // Look for Ki Points Feat that has uses\n actor.items.filter(i => i.data.name === kiName && i.data.hasUses && (i.data.data.uses.value >= kiCost || allowNegative)).forEach(i => {\n hasAvailableResource = true;\n i.data.data.uses.value -= kiCost\n })\n\n if (!hasAvailableResource) {\n ui.notifications.warn(`${actor.name} does not have any ${kiName} left!`);\n return false;\n }\n if (actor.sheet.rendered) {\n // Update the actor sheet if it is currently open\n actor.render(true);\n }\n\n if (selected) {\n selected.render(allowHigher);\n }\n\n return true;\n };\n\n (async () => {\n let template = `\n
\n
\n \n \n
\n
\n \n
\n
\n \n
\n
`;\n new Dialog({\n title: `Monk Ki Point Spender`,\n content: template,\n buttons: {\n yes: {\n icon: \"\",\n label: `Apply`,\n callback: (html) => {\n let feature = html.find('#feature')[0].value;\n let allowNegative = html.find('#allow-negative')[0].checked;\n let allowHigher = html.find('#allow-higher')[0].checked;\n consumeKi(feature, allowNegative, allowHigher);\n }\n },\n no: {\n icon: \"\",\n label: `Cancel`\n },\n },\n default: \"yes\"\n }).render(true);\n })();\n})()\n","author":"y5gmtwxmW3A5ZuOP","img":"icons/svg/dice-target.svg","actorIds":[]} +{"_id":"hW29VmM09DenDXNz","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\t2. \tAdded automatic Totem Spirit: Bear detection and resistance application \n//\t\t\t\t\t\t\tPLEASE NOTE: A minor update to the Totem Spirit item's name in the character sheet is needed if \n//\t\t\t\t\t\t\tthe VTTA Beyond Integration was not used to create sheet. See Bonus Tip 1 below\n//\t\t\t\t\t\t3. \tAdded error messages for trying to rage with no token or no barbarian selected\n//\t\t\t\t\t\t4. \t(Felix) added resource/usage deduction and errors (re-added after accidentally overwriting the addition) \n//\t\t\t\t\t\t5. \t(Felix, 2020/05/29) Fixed rage damage at level 8\n//\t\t\t\t\t\t6. \t(2020/05/30) Implemented Felix's idea to use global melee weapon attack bonus instead of modifying items\n//\t\t\t\t\t\t7. \t(2020/05/30) Improved Rage icon toggling to be more reliable\n//\t\t\t\t\t\t8. \t(2020/05/30) Removed code from the resource management that created dependency on The Furnace Advanced Macros\n//\t\t\t\t\t\t9. \t(2020/05/30) Implemented Felix's fix for issue where new resistances and rage uses were not saving properly\n//\t\t\t\t\t\t10.\t(2020/05/30) Fixed rage damage formula again...\n//\t\t\t\t\t\t11.\t(2020/05/30) Added basic support for non-strength Based barbarians (Dex, Hexblade)\n//\t\t\t\t\t\t12.\t(2020/05/30) Added optional ability to toggle the icon and name of the macro itself based on current raging state.\n\n//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n//!!! OPTIONAL 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//!!! OPTIONAL 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\t\tconst deductResource = true;\n\t\t\t\tconst preventNegativeResource = true;\n//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n//!!! OPTIONAL 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//!!!\t\t\n\t\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\t\t3. \tWorks best with only one barbarian 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 stopRageIconPath = 'icons/svg/unconscious.svg';\n\n\t\t\t\t//You must update the following constant to this macro's exact for the macro icon toggling to work.\n\t\t\t\tconst rageMacroName = 'Rage';\n//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n//!!!\tBonus Tip 1: Bear 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\". Note: Importing\n//!!!\tvia VTTA Beyond Integration uses this name already. The macro then automatically adds the extra Bear Totem Spirit resistances.\n//!!!\n//!!!\tBonus Tip 2: Thrown 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 3: The Rage Condition \n//!!!\tIf you use the Combat Utility Belt module's Condition Lab, try adding a condition called \"Raging\" with the same icon \t\t\t \n//!!!\tas the optional rage icon overlay, 'icons/svg/explosion.svg' by default. See OPTIONAL TOKEN/MACRO ICONS section above.\n//!!!\n//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n//declarations\nlet barb = '';\nlet chatMsg = '';\nlet bear = '';\nlet noRage = false;\nlet toggleResult = false;\n\n//main\n//check to see if Actor exists and is a barbarian\nif (actor !== undefined && actor !== null) {\n\t\t\t\t\n\t// get the barbarian class item\n\tbarb = actor.items.find(i => i.name === 'Barbarian');\n\tif (barb == undefined) {\n\t\tui.notifications.warn(\"Please select a single barbarian token.\");\n\t}\n\tif (barb !== undefined && barb !== null) {\n\t\tchatMsg = '';\n\t\tlet enabled = false;\n\t\t// store the state of the rage toggle in flags\n\t\tif (actor.data.flags.rageMacro !== null && actor.data.flags.rageMacro !== undefined) {\n\t\t\tenabled = true;\n\t\t}\n\t\t\n\t\t// if rage is active, disable it\n\t\tif (enabled) {\n\t\t\tchatMsg = `${actor.name} is no longer raging.`;\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\tobj['data.traits.dr'] = actor.data.flags.rageMacro.oldResistances;\n\t\t\tlet test=0;\t\t\t\n\t\t\tobj['data.bonuses.mwak.damage'] = actor.data.flags.rageMacro.oldDmg;\t\n\t\t\tactor.update(obj);\n\t\t\t\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(actor.data.data.resources)\t\t\t\t\n\t\t\t\tlet obj = {}\n\t\t\t\t// Look for Resources under the Core actor data\n\t\t\t\tlet resourceKey = Object.keys(actor.data.data.resources).filter(k => actor.data.data.resources[k].label === \"Rage\").shift();\n\t\t\t\tif (resourceKey && (actor.data.data.resources[resourceKey].value > 0 || !preventNegativeResource)) {\n\t\t\t\t\thasAvailableResource = true;\n\t\t\t\t\tnewResources[resourceKey].value--;\t\t\t\t\t\n\t\t\t\t\tobj['data.resources'] = newResources \n\t\t\t\t\tactor.update(obj);\n\t\t\t\t}\n\t\t\t\tif (!hasAvailableResource) {\n\t\t\t\t\tui.notifications.error(`${actor.name} does not have any rage left, time for a long rest!`);\n\t\t\t\t\tnoRage=true;\n\t\t\t\t}\n\t\t\t\tif (actor.sheet.rendered) {\n\t\t\t\t\t// Update the actor sheet if it is currently open\n\t\t\t\t\tactor.render(true);\n\t\t\t\t}\n\t\t\t}\n\t\t\t\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 = `${actor.name} is RAAAAAGING!`;\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(actor.data.data.traits.dr));\n\t\t\t\t// add bludgeoning, piercing and slashing resistance\n\t\t\t\tlet newResistance = duplicate(actor.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 = actor.items.find(i => i.name === \"Totem Spirit: Bear\")\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\tactor.update(obj);\n\t\t\t\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\t// Preserve old mwak damage bonus if there was one\n\t\t\t\t\tlet dmg = actor.data.data.bonuses.mwak.damage;\n\t\t\t\t\tif (dmg==null || dmg == undefined || dmg == '') dmg = 0;\n\t\t\t\t\tobj['flags.rageMacro.oldDmg'] = JSON.parse(JSON.stringify(dmg));\n\t\t\t\n\t\t\t\t\t// Determining the barbarian level\n\t\t\t\t\tlet barblvl = barb.data.data.levels;\n\t\t\t\n\t\t\t\t\t// Formula to determine the rage bonus damage depending on barbarian level\n\t\t\t\t\tlet lvlCorrection = barblvl === 16 || barblvl === 17 ? 1 : 0;\n\t\t\t\t\tlet rageDmg = 2 + Math.floor(barblvl / 9) + lvlCorrection;\n\t\t\t\t\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 if present.\n\t\t\t\t\tif (parseInt(dmg) == dmg) {\n\t\t\t\t\t\tobj['data.bonuses.mwak.damage'] = parseInt(dmg) + rageDmg;\n\t\t\t\t\t} else {\n\t\t\t\t\tobj['data.bonuses.mwak.damage'] = `${dmg} + ${rageDmg}`;\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tactor.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 token.toggleEffect(rageIconPath);\n\t\t\t\tif (toggleResult == enabled) token.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(\"Rage macro named \" + `${rageMacroName}` + \" not found. Rage toggle successful but unable to toggle macro icon.\");\n\t\t\t}\t\n\t\t}\n\t}\n} else ui.notifications.warn(\"Please select a token.\");\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":[]} diff --git a/packs/macros-roll.db b/packs/macros-roll.db index 888b0e5..a3319ac 100644 --- a/packs/macros-roll.db +++ b/packs/macros-roll.db @@ -2,3 +2,5 @@ {"_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":[]} {"_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":"9TsgblYH0WTlDE9S","name":"Stealth Check","permission":{"default":0,"y5gmtwxmW3A5ZuOP":3},"type":"script","flags":{},"scope":"global","command":"// Grabs selected tokens and rolls a stealth check against all other tokens passive perception on the map. Then returns the result.\n\n// getting all actors of selected tokens\nlet actors = canvas.tokens.controlled.map(({ actor }) => actor);\n\n// if there are no selected tokens, roll for the player's character.\nif (actors.length < 1) {\n actors = game.users.entities.map(entity => {\n if (entity.active && entity.character !== null) {\n return entity.character;\n }\n });\n}\nconst validActors = actors.filter(actor => actor != null);\n\nlet messageContent = 'pp = passive perception
';\n\n// roll for every actor\nfor (const selectedActor of validActors) {\n const stealthMod = selectedActor.data.data.skills.ste.total; // stealth roll\n const stealth = new Roll(`1d20+${stealthMod}`).roll().total; // rolling the formula\n messageContent += `

${selectedActor.name} stealth roll was a ${stealth}.

`; // creating the output string\n\n // grab a list of unique tokens then check their passive perception against the rolled stealth.\n const uniqueActor = {};\n const caughtBy = canvas.tokens.placeables\n .filter(token => !!token.actor)\n .filter(({ actor }) => { // filter out duplicate token names. ie: we assume all goblins have the same passive perception\n if (uniqueActor[actor.name]) {\n return false;\n }\n uniqueActor[actor.name] = true;\n return true;\n })\n .filter(({ actor }) => {\n return selectedActor.id !== actor.id; // Don't check to see if the token sees himself.\n })\n .filter(({ actor }) => actor.data.data.skills.prc.passive >= stealth); // check map tokens passives with roller stealth\n\n if (!caughtBy.length) {\n messageContent += 'Stealth successful!
';\n } else {\n messageContent += 'Stealth questionable:
';\n caughtBy.map(({ actor }) => {\n messageContent += `${actor.name} pp(${actor.data.data.skills.prc.passive}).
`;\n });\n }\n}\n\n// create the message\nconst chatData = {\n user: game.user._id,\n speaker: game.user,\n content: messageContent,\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":[]} +{"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"}