From 53251997b36a39593d38dfd3018a701ba04dc3ae Mon Sep 17 00:00:00 2001 From: Mike Bloy Date: Tue, 30 Nov 2021 22:58:57 -0600 Subject: [PATCH] Add spell effect turn alert macros --- .gitignore | 1 + macros/ApplySpellEffect.js | 65 +++++++++++++++++++++++++++++++++++++ macros/CancelSpellEffect.js | 17 ++++++++++ macros/SpellEffectSample.js | 13 ++++++++ packs/macros.db | 4 +++ 5 files changed, 100 insertions(+) create mode 100644 .gitignore create mode 100644 macros/ApplySpellEffect.js create mode 100644 macros/CancelSpellEffect.js create mode 100644 macros/SpellEffectSample.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7fe7a7c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +swade-mb-shared.lock diff --git a/macros/ApplySpellEffect.js b/macros/ApplySpellEffect.js new file mode 100644 index 0000000..1ca9683 --- /dev/null +++ b/macros/ApplySpellEffect.js @@ -0,0 +1,65 @@ +let effect = args[0]; +let targets = args[1]; +let extra = args[2]; +if (!extra) { + extra = {}; +} +if (!extra.name) { extra.name = effect.label } +if (!extra.duration) { extra.duration = effect.duration.rounds } +if (!extra.startMessage) { + extra.startMessage = `${effect.label}'s effects start` +} +if (!extra.endMessage) { + extra.endMessage = `${effect.label}'s effects end` +} +if (!extra.fadeMessage) { + extra.fadeMessage = `${effect.label}'s effects start to fade` +} + +if (canvas.tokens.controlled.length == 0) { + ui.notifications.error("No tokens selected"); + return; +} +for (let tgt of targets) { + let actor = tgt.actor; + let active = actor.effects.find(i => i.data.label === effect.label); + let chatData = { + speaker: ChatMessage.getSpeaker(tgt), + type: CONST.CHAT_MESSAGE_TYPES.OTHER, + emote: true + } + if (extra.flavor) { + chatData.flavor = extra.flavor; + } + if (active) { + await tgt.toggleEffect(effect, { active: false }) + chatData.content = extra.endMessage; + } else { + await tgt.toggleEffect(effect, { active: true }) + chatData.content = extra.startMessage; + } + if (chatData.content) { + ChatMessage.create(chatData); + } +} +if (game.combat && extra.duration) { + let alertData = { + round: extra.duration-1, + roundAbsolute: false, + turnId: game.combat.combatant._id, + message: extra.fadeMessage + } + await TurnAlert.create(alertData); + + const targetIds = targets.map(t => t.id); + alertData = { + round: extra.duration-1, + roundAbsolute: false, + turnId: game.combat.combatant._id, + endOfTurn: true, + message: extra.endMessage, + macro: "CancelSpellEffect", + args: [effect, targetIds] + } + await TurnAlert.create(alertData); +} \ No newline at end of file diff --git a/macros/CancelSpellEffect.js b/macros/CancelSpellEffect.js new file mode 100644 index 0000000..362846f --- /dev/null +++ b/macros/CancelSpellEffect.js @@ -0,0 +1,17 @@ +const effect = args[0]; +const targetIds = args[1]; +const extra = args[2]; + +async function main() { + for (const tokenId of targetIds) { + let token = game.canvas.tokens.get(tokenId); + let actor = token?.actor; + if (!actor) continue; + const active = actor.effects.find(e => e.data.label === effect.label); + if (active) { + await token.toggleEffect(effect, { active: false }); + console.log("Removed active effect", effect.label, token.name, token); + } + } +} +main(); \ No newline at end of file diff --git a/macros/SpellEffectSample.js b/macros/SpellEffectSample.js new file mode 100644 index 0000000..f57ec1f --- /dev/null +++ b/macros/SpellEffectSample.js @@ -0,0 +1,13 @@ +let effect = { + changes: [ + ], + duration: { rounds: 5 }, + icon: 'icons/svg/holy-shield.svg', + label: 'Sanctuary', + id: 'sanctuary' +} +const targets = canvas.tokens.controlled; +const extra = { flavor: "Sanctuary!" } +const spellEffect = game.macros.getName("ApplySpellEffect"); +let value = await spellEffect.execute(effect, targets, extra); +return value; \ No newline at end of file diff --git a/packs/macros.db b/packs/macros.db index a264c87..c9e3897 100644 --- a/packs/macros.db +++ b/packs/macros.db @@ -1,5 +1,6 @@ {"_id":"2Kz94m5zinSq0pxq","name":"Toggle Entangled","permission":{"default":0,"g5E84yQWEXKWBl9L":3},"type":"script","flags":{"combat-utility-belt":{"macroTrigger":""},"core":{"sourceId":"Macro.0nnEBhT2P7XeoKl7"},"cf":{"id":"temp_pswasgs6ygg"}},"scope":"global","command":"main ()\n\nasync function main() {\n //Is a token selected\n if (canvas.tokens.controlled.length == 0) {\n ui.notifications.error(\"No tokens selected\");\n return;\n }\n\n let tokens = canvas.tokens.controlled.map(token => {return token});\n\n for (let token of tokens) {\n if (token.actor.status.isEntangled) {\n await token.actor.update({\"data.status.isEntangled\": false})\n await token.actor.update({\"data.status.isDistracted\": false})\n } else {\n await token.actor.update({\"data.status.isBound\": false})\n await token.actor.update({\"data.status.isDistracted\": true})\n await token.actor.update({\"data.status.isEntangled\": true})\n }\n } // end for\n} //End main","author":"g5E84yQWEXKWBl9L","img":"systems/swade/assets/icons/status/status_entangled.svg","actorIds":[]} {"_id":"5uYT6npGhh29j3Bq","name":"Toggle Holding","permission":{"default":0,"goVuB7uyVDPjAwfj":3},"type":"script","flags":{"combat-utility-belt":{"macroTrigger":""},"core":{"sourceId":"Macro.dsr95SKSNCDX70VO"},"cf":{"id":"temp_pswasgs6ygg"}},"scope":"global","command":"main ()\n\nasync function main() {\n\nconst effect = \"systems/swade/assets/icons/status/status_holding.svg\";\nconst effectName = \"Holding\";\n\n //Is a token selected\n if (canvas.tokens.controlled.length == 0) {\n ui.notifications.error(\"No tokens selected\");\n return;\n }\n\n let tokens = canvas.tokens.controlled.map(token => {return token});\n\n for (let token of tokens) {\n await token.toggleEffect(effect);\n } // end for\n} //End main","author":"goVuB7uyVDPjAwfj","img":"systems/swade/assets/icons/status/status_holding.svg","actorIds":[]} +{"name":"CancelSpellEffect","type":"script","author":"ygiRButlaf23fX9p","img":"icons/svg/cancel.svg","scope":"global","command":"const effect = args[0];\nconst targetIds = args[1];\nconst extra = args[2];\n\nasync function main() {\n for (const tokenId of targetIds) {\n let token = game.canvas.tokens.get(tokenId);\n let actor = token?.actor;\n if (!actor) continue;\n const active = actor.effects.find(e => e.data.label === effect.label);\n if (active) {\n await token.toggleEffect(effect, { active: false });\n console.log(\"Removed active effect\", effect.label, token.name, token);\n }\n }\n}\nmain();","folder":null,"sort":0,"permission":{"default":0,"ygiRButlaf23fX9p":3},"flags":{"combat-utility-belt":{"macroTrigger":""},"advanced-macros":{"runAsGM":false},"scene-packer":{"sourceId":"Macro.LHPOj1ppx03VgI1R"},"cf":{"id":"temp_p20tzfcm449","path":"Spell Effect Macros","color":"#000000"}},"_id":"92LxuCERE2PKwgWn"} {"_id":"AVI34dUpDYCEm9w5","name":"AE_Companion_Macro(NAMEOFFORMACTOR)","type":"script","author":"ygiRButlaf23fX9p","img":"icons/svg/dice-target.svg","scope":"global","command":"const macro = game.macros.getName(\"shapeshift_AE_form\");\nlet value = await macro.execute(args[0]);\nreturn value;","folder":null,"sort":0,"permission":{"default":0,"ygiRButlaf23fX9p":3},"flags":{"combat-utility-belt":{"macroTrigger":""},"advanced-macros":{"runAsGM":false},"core":{"sourceId":"Macro.BEcVfWAVnXW0QKTR"},"scene-packer":{"sourceId":"Macro.BEcVfWAVnXW0QKTR"},"cf":{"id":"temp_ocr9zgcmo7","color":"#000000"}}} {"_id":"B07BTxFEkIpxWK3p","name":"Adventure Card","permission":{"default":0,"mrhsZpAiXth4sLah":3},"type":"script","flags":{"core":{"sourceId":"Compendium.swade-macros-simple.SWADE-Macros.dj9ISCPKpZRu43ud"},"cf":{"id":"temp_natl1zonf8"}},"scope":"global","command":"/* Mini Tutorial\r\n1 - Import the cards to a rollable table (i recommend Card Deck Importer - follow the instructions there). Name the rollable table AdventureDeck or change below.\r\n2 - Create an item (gear) named Adventure Card. Give it to the characters that will use it.\r\n3 - Run the macro.\r\n*/\r\n\r\nvar rollTableName = \"AdventureDeck\"; /// name of the rolltable with adventure cards\r\nvar itemCard = \"Adventure Card\"; /// name of the item holding the adventure card\r\n\r\nlet chars = game.actors.entities.filter((t) => t.data.type === \"character\"); /// all the chars\r\nlet optionchars = \"\";\r\nvar allchars = [];\r\n\r\nfor (const char of chars) {\r\n let myitem = char.items.find((i) => i.name === itemCard);\r\n if (myitem !== null) {\r\n /// filters the ones that has the item\r\n optionchars += ``;\r\n allchars.push(char._id);\r\n }\r\n}\r\n\r\nif (!optionchars) {\r\n /// no chars\r\n ui.notifications.warn(`No character has the item ` + itemCard + `.`, {});\r\n}\r\n\r\nlet template =\r\n `

How many cards?

\r\n

For wich character?

`;\r\nnew Dialog({\r\n title: \"Give Adventure Cards\",\r\n content: template,\r\n buttons: {\r\n ok: {\r\n label: \"Give\",\r\n callback: function (html) {\r\n applyFormOptions(html);\r\n },\r\n },\r\n cancel: {\r\n label: \"Cancel\",\r\n },\r\n },\r\n}).render(true);\r\n\r\nfunction drawFromTable(tableName) {\r\n /// thanks to Forien for this. Check his modules https://foundryvtt.com/community/forien\r\n const table = game.tables.getName(tableName);\r\n if (!table) {\r\n ui.notifications.warn(`Table ${tableName} not found.`, {});\r\n return;\r\n }\r\n let results = table.roll().results;\r\n\r\n // if table is without replacemenets, mark results as drawn\r\n if (table.data.replacement === false) {\r\n results = results.map((r) => {\r\n r.drawn = true;\r\n return r;\r\n });\r\n\r\n table.updateEmbeddedEntity(\"TableResult\", results);\r\n }\r\n\r\n return results;\r\n}\r\n\r\nfunction applyFormOptions(html) {\r\n let qtde = html.find(\"#qtde\")[0].value;\r\n let selchar = html.find(\"#jogs\")[0].value;\r\n\r\n if (selchar === \"todos\") {\r\n for (let i = 0; i < allchars.length; i++) {\r\n giveCards(qtde, allchars[i]);\r\n }\r\n } else {\r\n giveCards(qtde, selchar);\r\n }\r\n\r\n let chatData = {\r\n user: game.user._id,\r\n speaker: ChatMessage.getSpeaker(),\r\n content: \"Adventure Cards given\",\r\n };\r\n ChatMessage.create(chatData, {});\r\n}\r\n\r\nfunction giveCards(howmany, actorId) {\r\n let char = game.actors.get(actorId);\r\n let myitem = char.items.find((i) => i.name === itemCard);\r\n let updatedesc = \"\";\r\n\r\n for (let i = 1; i <= howmany; i++) {\r\n let results = drawFromTable(rollTableName);\r\n updatedesc +=\r\n \"

@Compendium[\" + results[0].collection + \".\" + results[0].resultId + \"]{\" + results[0].text + \"}

\";\r\n }\r\n\r\n myitem.update({ [\"data.description\"]: updatedesc });\r\n}\r\n","author":"mrhsZpAiXth4sLah","img":"icons/svg/chest.svg","actorIds":[]} {"_id":"CZvzvDhyY6oUHdKN","name":"Toggle Flying","permission":{"default":0,"goVuB7uyVDPjAwfj":3},"type":"script","flags":{"combat-utility-belt":{"macroTrigger":""},"core":{"sourceId":"Macro.dsr95SKSNCDX70VO"},"cf":{"id":"temp_pswasgs6ygg"}},"scope":"global","command":"main ()\n\nasync function main() {\n\nconst effect = \"systems/swade/assets/icons/status/status_flying.svg\";\nconst effectName = \"Flying\";\n\n //Is a token selected\n if (canvas.tokens.controlled.length == 0) {\n ui.notifications.error(\"No tokens selected\");\n return;\n }\n\n let tokens = canvas.tokens.controlled.map(token => {return token});\n\n for (let token of tokens) {\n await token.toggleEffect(effect);\n } // end for\n} //End main","author":"goVuB7uyVDPjAwfj","img":"systems/swade/assets/icons/status/status_flying.svg","actorIds":[]} @@ -7,6 +8,7 @@ {"_id":"Fvw5ksJfaqV4sisJ","name":"Toggle Stunned","permission":{"default":0,"g5E84yQWEXKWBl9L":3},"type":"script","flags":{"combat-utility-belt":{"macroTrigger":""},"core":{"sourceId":"Macro.0nnEBhT2P7XeoKl7"},"cf":{"id":"temp_pswasgs6ygg"}},"scope":"global","command":"main ()\n\nasync function main() {\n //Is a token selected\n if (canvas.tokens.controlled.length == 0) {\n ui.notifications.error(\"No tokens selected\");\n return;\n }\n const proneEffectName = \"SWADE.Prone\";\n let proneEffect = CONFIG.statusEffects.find(s => s.label == proneEffectName);\n\n let tokens = canvas.tokens.controlled.map(token => {return token});\n\n for (let token of tokens) {\n if (token.actor.status.isStunned) {\n await token.actor.update({\"data.status.isStunned\": false})\n } else {\n // add stunned and vulnerable and distracted and prone\n await token.actor.update({\"data.status.isStunned\": true})\n await token.actor.update({\"data.status.isDistracted\": true})\n if (!token.actor.effects.find(e => e.data.icon == proneEffect.icon)) {\n await token.toggleEffect(proneEffect);\n }\n await token.actor.update({\"data.status.isVulnerable\": true})\n }\n } // end for\n} //End main","author":"g5E84yQWEXKWBl9L","img":"systems/swade/assets/icons/status/status_stunned.svg","actorIds":[]} {"_id":"GdDAPaUnymrqdVrM","name":"Toggle Prone","permission":{"default":0,"goVuB7uyVDPjAwfj":3},"type":"script","flags":{"combat-utility-belt":{"macroTrigger":""},"core":{"sourceId":"Macro.0nnEBhT2P7XeoKl7"},"cf":{"id":"temp_pswasgs6ygg"}},"scope":"global","command":"main ()\n\nasync function main() {\n //Is a token selected\n if (canvas.tokens.controlled.length == 0) {\n ui.notifications.error(\"No tokens selected\");\n return;\n }\n const effectName = \"SWADE.Prone\";\n let effect = CONFIG.statusEffects.find(s => s.label == effectName);\n\n let tokens = canvas.tokens.controlled.map(token => {return token});\n\n for (let token of tokens) {\n await token.toggleEffect(effect);\n } // end for\n} //End main","author":"goVuB7uyVDPjAwfj","img":"systems/swade/assets/icons/status/status_prone.svg","actorIds":[]} {"_id":"HRVgvCZuAPR2WCMH","name":"Toggle Bound","permission":{"default":0,"g5E84yQWEXKWBl9L":3},"type":"script","flags":{"combat-utility-belt":{"macroTrigger":""},"core":{"sourceId":"Macro.0nnEBhT2P7XeoKl7"},"cf":{"id":"temp_pswasgs6ygg"}},"scope":"global","command":"main ()\n\nasync function main() {\n //Is a token selected\n if (canvas.tokens.controlled.length == 0) {\n ui.notifications.error(\"No tokens selected\");\n return;\n }\n\n let tokens = canvas.tokens.controlled.map(token => {return token});\n\n for (let token of tokens) {\n if (token.actor.status.isBound) {\n await token.actor.update({\"data.status.isBound\": false})\n await token.actor.update({\"data.status.isDistracted\": false})\n await token.actor.update({\"data.status.isVulnerable\": false})\n } else {\n await token.actor.update({\"data.status.isBound\": true})\n await token.actor.update({\"data.status.isDistracted\": true})\n await token.actor.update({\"data.status.isEntangled\": false})\n await token.actor.update({\"data.status.isVulnerable\": true})\n }\n } // end for\n} //End main","author":"g5E84yQWEXKWBl9L","img":"systems/swade/assets/icons/status/status_bound.svg","actorIds":[]} +{"name":"#[CF_tempEntity]","type":"chat","author":"ygiRButlaf23fX9p","img":"icons/svg/dice-target.svg","scope":"global","command":"","folder":null,"sort":0,"permission":{"default":0,"ygiRButlaf23fX9p":3},"flags":{"cf":{"id":"temp_p20tzfcm449","path":"Spell Effect Macros","color":"#000000","name":"Spell Effect Macros","children":["lm51dm7e9yhTx4os","92LxuCERE2PKwgWn","yV3XNCfgHpGrREt0"],"folderPath":[]}},"_id":"Ih0MlJcIkTiSWsAm"} {"name":"Fear Table","permission":{"default":0,"goVuB7uyVDPjAwfj":3},"type":"script","flags":{"core":{"sourceId":"Macro.tt5wQLZWCHErlY8L"},"combat-utility-belt":{"macroTrigger":""}},"scope":"global","command":"// Ask for Fear Penalty\n// Roll On the Fear Table with the Penalty\n\nmain()\n\nasync function main(){\n let fearTable = await game.packs.get(\"swade-core-rules.swade-tables\").getEntity(game.packs.get(\"swade-core-rules.swade-tables\").index.find(el => el.name == \"Fear Table\")._id)\n new Dialog({\n title:\"Fear Table Modifier\",\n content: `\n
\n

Creature Fear Penalty (Positive Number):

\n \n
\n `,\n buttons: {\n roll: {\n label: \"Roll\",\n callback: (html) => {\n let mod = html.find(\"#fearPenalty\")[0].value;\n console.log(mod)\n fearTable.draw({roll:new Roll(`1d20 + ${mod}`)})\n }\n }, \n cancel: {\n label: \"Cancel\"\n }\n }\n }).render(true)\n}","author":"goVuB7uyVDPjAwfj","img":"systems/swade/assets/icons/status/status_frightened.svg","actorIds":[],"_id":"Ry6NLK24QaSVA1dM"} {"name":"#[CF_tempEntity]","permission":{"default":0,"goVuB7uyVDPjAwfj":3},"type":"chat","flags":{"cf":{"id":"temp_pswasgs6ygg","folderPath":[],"color":"#000000","fontColor":"#FFFFFF","name":"States","children":[],"icon":""}},"scope":"global","command":"","author":"goVuB7uyVDPjAwfj","img":"icons/svg/dice-target.svg","actorIds":[],"_id":"T7HZINkdw1Z6u1Fc"} {"name":"Raise Calculator (Dynamic)","permission":{"default":0,"g5E84yQWEXKWBl9L":3},"type":"script","flags":{"combat-utility-belt":{"macroTrigger":""},"furnace":{"runAsGM":false},"core":{"sourceId":"Macro.onkkfY2zBddVpiLr"},"exportSource":{"world":"swadetest","system":"swade","coreVersion":"0.7.9","systemVersion":"0.18.3"}},"scope":"global","command":"let text = `\"\" Your Raises will show here once you leave the Result field.`;\n\nnew Dialog({\n title: 'Raise Calculator',\n content: `\n
\n
\n \n \n
\n
\n \n \n
\n

${text}

\n
`,\n buttons: {},\n render: ([dialogContent]) => {\n dialogContent.querySelector(`input[name=\"target\"`).focus();\n dialogContent.querySelector(`input[name=\"result\"`).addEventListener(\"input\", (event) => {\n const textInput = event.target;\n const form = textInput.closest(\"form\")\n const calcResult = form.querySelector(\".calculation\");\n const target = form.querySelector('input[name=\"target\"]').value;\n const result = form.querySelector('input[name=\"result\"]').value;\n let raises = Math.floor((parseInt(result) - parseInt(target)) / 4);\n if (parseInt(target) > parseInt(result)) {\n calcResult.innerHTML = `\"\" Failure`;\n }\n else if (parseInt(target) <= parseInt(result) && raises < 1) {\n calcResult.innerHTML = `\"\" Success`;\n }\n else {\n calcResult.innerHTML = `\"\" ${raises} Raise(s)`;\n }\n });\n },\n}).render(true);\n\n// v.1.2.0 By SalieriC#8263, with help from Rawny#2166.","author":"g5E84yQWEXKWBl9L","img":"modules/swade-mb-shared/assets/icons/misc/rolling-dices.svg","actorIds":[],"_id":"UB86lMBB3woUkLcb"} @@ -15,3 +17,5 @@ {"_id":"ed7YiOqkaqkGx0CR","name":"shapeshift_AE_form","type":"script","author":"ygiRButlaf23fX9p","img":"icons/svg/dice-target.svg","scope":"global","command":"let summon = args[0].summon;\nlet duplicates = args[0].duplicates;\nlet assignedActor = args[0].assignedActor;\n\nlet data = {\n actor: {},\n token: {},\n embedded: {Item: {}}\n};\n\nconst name = `${assignedActor.data.token.name} (${summon.data.token.name})`;\n\nconst actorElements = ['wildcard', 'bennies', 'fatigue', 'wounds'];\nconst keptAttributes = ['smarts', 'spirit']\n\nfor (const elem of actorElements) {\n data.actor[`data.${elem}`] = assignedActor.data.data[elem];\n}\n\ndata.actor['name'] = name;\ndata.token['name'] = name;\n\nfor (const attr of keptAttributes) {\n let attrData = assignedActor.data.data.attributes[attr];\n data.actor[`data.attributes.${attr}`] = attrData;\n let skills = assignedActor.items.filter(\n i => i.type === 'skill' && i.data.data.attribute === attr)\n for(const skill of skills) {\n data.embedded['Item'][skill.name] = skill.data;\n }\n}\nconst otherItems = assignedActor.items.filter(\n i => i.type === 'edge' || i.type === 'hindrance')\nfor(const item of otherItems) {\n data.embedded['Item'][item.name] = item.data;\n}\n\nreturn data;","folder":null,"sort":0,"permission":{"default":0,"ygiRButlaf23fX9p":3},"flags":{"combat-utility-belt":{"macroTrigger":""},"advanced-macros":{"runAsGM":false},"core":{"sourceId":"Macro.WzTbhEUdD90hJLnX"},"scene-packer":{"sourceId":"Macro.WzTbhEUdD90hJLnX"},"cf":{"id":"temp_ocr9zgcmo7","color":"#000000"}}} {"name":"#[CF_tempEntity]","permission":{"default":0,"mrhsZpAiXth4sLah":3},"type":"chat","flags":{"cf":{"id":"temp_natl1zonf8","folderPath":[],"color":"#000000","fontColor":"#FFFFFF","name":"Cards","children":[],"icon":""}},"scope":"global","command":"","author":"mrhsZpAiXth4sLah","img":"icons/svg/dice-target.svg","actorIds":[],"_id":"hnicuEhZlfMF2upA"} {"name":"Sanctuary CUB","type":"script","author":"g5E84yQWEXKWBl9L","img":"icons/svg/holy-shield.svg","scope":"global","command":"const CONDITION = \"Sanctuary\";\n\n\nasync function tokenEffect(token) {\n console.log(token);\n let message = \"\" + token.data.name;\n if (game.cub.hasCondition(CONDITION, token)) {\n message += \" is no longer under Sanctuary.\";\n game.cub.removeCondition(CONDITION, token);\n } else {\n game.cub.addCondition(CONDITION, token);\n message += \" is under @Compendium[swpf-core-rules.swpf-powers.H82NWO4VNEYTqciG]{Sanctuary}.\";\n }\n ChatMessage.create({\n user: game.user._id,\n emote: true,\n flavor: \"Sanctuary!\",\n speaker: ChatMessage.getSpeaker({token: token}),\n content: message});\n}\n\nasync function main() {\n if (canvas.tokens.controlled.length == 0) {\n ui.notifications.error(\"No tokens selected\");\n return;\n }\n canvas.tokens.controlled.forEach(token => tokenEffect(token));\n}\n\nmain()","folder":null,"sort":0,"permission":{"default":0,"g5E84yQWEXKWBl9L":3},"flags":{"combat-utility-belt":{"macroTrigger":""},"advanced-macros":{"runAsGM":false},"core":{"sourceId":"Macro.T7GXMuVq2JNMne2g"}},"_id":"jAPMvsQemX2LnSWp"} +{"name":"ApplySpellEffect","type":"script","author":"ygiRButlaf23fX9p","img":"icons/svg/sun.svg","scope":"global","command":"let effect = args[0];\nlet targets = args[1];\nlet extra = args[2];\nif (!extra) {\n extra = {};\n}\nif (!extra.name) { extra.name = effect.label }\nif (!extra.duration) { extra.duration = effect.duration.rounds }\nif (!extra.startMessage) {\n extra.startMessage = `${effect.label}'s effects start`\n}\nif (!extra.endMessage) {\n extra.endMessage = `${effect.label}'s effects end`\n}\nif (!extra.fadeMessage) {\n extra.fadeMessage = `${effect.label}'s effects start to fade`\n}\n\nif (canvas.tokens.controlled.length == 0) {\n ui.notifications.error(\"No tokens selected\");\n return;\n}\nfor (let tgt of targets) {\n let actor = tgt.actor;\n let active = actor.effects.find(i => i.data.label === effect.label);\n let chatData = {\n speaker: ChatMessage.getSpeaker(tgt),\n type: CONST.CHAT_MESSAGE_TYPES.OTHER,\n emote: true\n }\n if (extra.flavor) {\n chatData.flavor = extra.flavor;\n }\n if (active) {\n await tgt.toggleEffect(effect, { active: false })\n chatData.content = extra.endMessage;\n } else {\n await tgt.toggleEffect(effect, { active: true })\n chatData.content = extra.startMessage;\n }\n if (chatData.content) {\n ChatMessage.create(chatData);\n }\n}\nif (game.combat && extra.duration) {\n let alertData = {\n round: extra.duration-1,\n roundAbsolute: false,\n turnId: game.combat.combatant._id,\n message: extra.fadeMessage\n }\n await TurnAlert.create(alertData);\n\n const targetIds = targets.map(t => t.id);\n alertData = {\n round: extra.duration-1,\n roundAbsolute: false,\n turnId: game.combat.combatant._id,\n endOfTurn: true,\n message: extra.endMessage,\n macro: \"CancelSpellEffect\",\n args: [effect, targetIds, extra]\n }\n await TurnAlert.create(alertData);\n}","folder":null,"sort":0,"permission":{"default":0,"ygiRButlaf23fX9p":3},"flags":{"combat-utility-belt":{"macroTrigger":""},"advanced-macros":{"runAsGM":false},"scene-packer":{"sourceId":"Macro.OB9FF0mpgWSYEryu"},"cf":{"id":"temp_p20tzfcm449","path":"Spell Effect Macros","color":"#000000"}},"_id":"lm51dm7e9yhTx4os"} +{"name":"Sanctuary","type":"script","author":"ygiRButlaf23fX9p","img":"icons/svg/holy-shield.svg","scope":"global","command":"let effect = {\n changes: [\n ],\n duration: {rounds: 5},\n icon: 'icons/svg/holy-shield.svg',\n label: 'Sanctuary',\n id: 'sanctuary'\n}\nconst targets = canvas.tokens.controlled;\nconst extra = { flavor: \"Sanctuary!\" }\nconst spellEffect = game.macros.getName(\"ApplySpellEffect\");\nlet value = await spellEffect.execute(effect, targets, extra);\nreturn value;","folder":null,"sort":0,"permission":{"default":0,"ygiRButlaf23fX9p":3},"flags":{"advanced-macros":{"runAsGM":false},"combat-utility-belt":{"macroTrigger":""},"core":{"sourceId":"Macro.01dTmSPBq0xJQJcm"},"scene-packer":{"sourceId":"Macro.01dTmSPBq0xJQJcm"},"cf":{"id":"temp_p20tzfcm449","path":"Spell Effect Macros","color":"#000000"}},"_id":"yV3XNCfgHpGrREt0"}