From 4d64e142d6763b89ca3c86cd6134d91e2ecbfaad Mon Sep 17 00:00:00 2001 From: Matt Kemp Date: Wed, 3 Aug 2022 21:08:54 -0500 Subject: [PATCH 01/31] Standardize on debounceCollate to help with state oscillation --- src/AAhelpers.js | 16 ++++--- src/AAhooks.js | 114 ++++++++++++++++++++++------------------------- src/aura.js | 13 +++--- 3 files changed, 71 insertions(+), 72 deletions(-) diff --git a/src/AAhelpers.js b/src/AAhelpers.js index 19802fa..d9489e0 100644 --- a/src/AAhelpers.js +++ b/src/AAhelpers.js @@ -161,8 +161,11 @@ class AAhelpers { if (removeToken?.actor?.effects.size > 0) { for (let testEffect of removeToken.actor.effects) { if (!EffectsArray.includes(testEffect.data.origin) && testEffect.data?.flags?.ActiveAuras?.applied) { - await removeToken.actor.deleteEmbeddedDocuments("ActiveEffect", [testEffect.id]) - console.log(game.i18n.format("ACTIVEAURAS.RemoveLog", { effectDataLabel: testEffect.data.label, tokenName: removeToken.name })) + try { + await removeToken.actor.deleteEmbeddedDocuments("ActiveEffect", [testEffect.id]); + } finally { + console.log(game.i18n.format("ACTIVEAURAS.RemoveLog", { effectDataLabel: testEffect.data.label, tokenName: removeToken.name })); + } } } } @@ -171,9 +174,12 @@ class AAhelpers { static async RemoveAllAppliedAuras() { for (let removeToken of canvas.tokens.placeables) { if (removeToken?.actor?.effects.size > 0) { - let effects = removeToken.actor.effects.reduce((a, v) => { if (v.data?.flags?.ActiveAuras?.applied) return a.concat(v.id) }, []) - await removeToken.actor.deleteEmbeddedDocuments("ActiveEffect", effects) - console.log(game.i18n.format("ACTIVEAURAS.RemoveLog", { tokenName: removeToken.name })) + let effects = removeToken.actor.effects.reduce((a, v) => { if (v.data?.flags?.ActiveAuras?.applied) return a.concat(v.id) }, []); + try { + await removeToken.actor.deleteEmbeddedDocuments("ActiveEffect", effects); + } finally { + console.log(game.i18n.format("ACTIVEAURAS.RemoveLog", { tokenName: removeToken.name })); + } } } diff --git a/src/AAhooks.js b/src/AAhooks.js index 0ca368e..a05644d 100644 --- a/src/AAhooks.js +++ b/src/AAhooks.js @@ -4,37 +4,36 @@ Hooks.once('ready', () => { }); let AAgm; -const debouncedCollate = debounce((a, b, c, d) => CollateAuras(a, b, c, d), 200) +const debouncedCollate = debounce((a, b, c, d) => CollateAuras(a, b, c, d), 200); Hooks.once("socketlib.ready", () => { AAsocket = socketlib.registerModule("ActiveAuras"); AAsocket.register("userCollate", CollateAuras); }); Hooks.on("init", () => { - libWrapper.register("ActiveAuras", "ActiveEffect.prototype.apply", AAhelpers.applyWrapper, "MIXED") - libWrapper.register("ActiveAuras", "ActiveEffect.prototype._displayScrollingStatus", AAhelpers.scrollingText, "WRAPPER") -}) + libWrapper.register("ActiveAuras", "ActiveEffect.prototype.apply", AAhelpers.applyWrapper, "MIXED"); + libWrapper.register("ActiveAuras", "ActiveEffect.prototype._displayScrollingStatus", AAhelpers.scrollingText, "WRAPPER"); +}); Hooks.on("ready", () => { if (canvas.scene === null) { if (AAdebug) { console.log("Active Auras disabled due to no canvas") } return } - AAgm = game.user === game.users.find((u) => u.isGM && u.active) - CollateAuras(canvas.id, true, false) - + AAgm = game.user === game.users.find((u) => u.isGM && u.active); + CollateAuras(canvas.id, true, false); if (game.settings.get("ActiveAuras", "debug")) AAdebug = true -}) +}); Hooks.on("createToken", (token) => { if (canvas.scene === null) { if (AAdebug) { console.log("Active Auras disabled due to no canvas") } return } if (!AAgm) return; try { - if (getProperty(token, "data.flags.multilevel-tokens")) return + if (getProperty(token, "data.flags.multilevel-tokens")) return; for (let effect of token.actor.effects?.contents) { if (effect.data.flags.ActiveAuras?.isAura) { - if (AAdebug) console.log("createToken, collate auras true false") - debouncedCollate(canvas.scene.id, true, false, "createToken") + if (AAdebug) console.log("createToken, collate auras true false"); + debouncedCollate(canvas.scene.id, true, false, "createToken"); break; } } @@ -47,26 +46,26 @@ Hooks.on("createToken", (token) => { Hooks.on("updateCombat", async (combat, changed, options, userId) => { if (canvas.scene === null) { if (AAdebug) { console.log("Active Auras disabled due to no canvas") } return } if (changed.round === 1) { - ActiveAuras.MainAura(undefined, "combat start", canvas.id) + ActiveAuras.MainAura(undefined, "combat start", canvas.id); return; } if (!("turn" in changed)) return; if (!AAgm) return; - let combatant = canvas.tokens.get(combat.current.tokenId) - let previousCombatant = canvas.tokens.get(combat.previous.tokenId) - await previousCombatant.document.update({ "flags.ActiveAuras": false }) - if (AAdebug) console.log("updateCombat, main aura") - await ActiveAuras.MainAura(combatant.data, "combat update", combatant.scene.id) + let combatant = canvas.tokens.get(combat.current.tokenId); + let previousCombatant = canvas.tokens.get(combat.previous.tokenId); + await previousCombatant.document.update({ "flags.ActiveAuras": false }); + if (AAdebug) console.log("updateCombat, main aura"); + await ActiveAuras.MainAura(combatant.data, "combat update", combatant.scene.id); }); Hooks.on("preDeleteToken", async (token) => { if (canvas.scene === null) { if (AAdebug) { console.log("Active Auras disabled due to no canvas") } return } if (!AAgm) return; if (AAhelpers.IsAuraToken(token.id, token.parent.id)) { - if (AAdebug) console.log("preDelete, collate auras false true") - AAhelpers.ExtractAuraById(token.id, token.parent.id) + if (AAdebug) console.log("preDelete, collate auras false true"); + AAhelpers.ExtractAuraById(token.id, token.parent.id); } - AAhelpers.removeAurasOnToken(token) + AAhelpers.removeAurasOnToken(token); }); /** @@ -78,20 +77,16 @@ Hooks.on("updateToken", async (token, update, _flags, _id) => { if (("y" in update || "x" in update || "elevation" in update)) { let MapObject = AuraMap.get(token.parent.id); if (!MapObject || MapObject?.effects.length < 1) return; - if (AAdebug) console.log("movement, main aura") + if (AAdebug) console.log("movement, main aura"); await ActiveAuras.MainAura(token, "movement update", token.parent.id) } else if ("hidden" in update && AAhelpers.IsAuraToken(token.id, token.parent.id)) { - setTimeout(() => { - if (AAdebug) console.log("hidden, collate auras true true") - CollateAuras(canvas.scene, true, true, "updateToken") - }, 20) + if (AAdebug) console.log("hidden, collate auras true true"); + debouncedCollate(canvas.scene, true, true, "updateToken"); } else if (AAhelpers.IsAuraToken(token.id, token.parent.id) && AAhelpers.HPCheck(token)) { - setTimeout(() => { - if (AAdebug) console.log("0hp, collate auras true true") - CollateAuras(canvas.scene.id, true, true, "updateToken, dead") - }, 50) + if (AAdebug) console.log("0hp, collate auras true true"); + debouncedCollate(canvas.scene.id, true, true, "updateToken, dead"); } }); @@ -102,10 +97,10 @@ Hooks.on("updateActiveEffect", (effect, _update) => { if (canvas.scene === null) { if (AAdebug) { console.log("Active Auras disabled due to no canvas") } return } if (!AAgm) return; if (effect.data.flags?.ActiveAuras?.isAura) { - if (AAdebug) console.log("updateAE, collate auras true true") - debouncedCollate(canvas.scene.id, true, true, "updateActiveEffect") + if (AAdebug) console.log("updateAE, collate auras true true"); + debouncedCollate(canvas.scene.id, true, true, "updateActiveEffect"); } -}) +}); /** * On removal of active effect from linked actor, if aura remove from canvas.tokens @@ -116,8 +111,8 @@ Hooks.on("deleteActiveEffect", (effect) => { let applyStatus = effect.data.flags?.ActiveAuras?.applied; let auraStatus = effect.data.flags?.ActiveAuras?.isAura; if (!applyStatus && auraStatus) { - if (AAdebug) console.log("deleteAE, collate auras true false") - debouncedCollate(canvas.scene.id, false, true, "deleteActiveEffect") + if (AAdebug) console.log("deleteAE, collate auras true false"); + debouncedCollate(canvas.scene.id, false, true, "deleteActiveEffect"); } }); @@ -128,51 +123,50 @@ Hooks.on("createActiveEffect", (effect) => { if (canvas.scene === null) { if (AAdebug) { console.log("Active Auras disabled due to no canvas") } return } if (!AAgm) return; if (!effect.data.flags?.ActiveAuras?.applied && effect.data.flags?.ActiveAuras?.isAura) { - if (AAdebug) console.log("deleteAE, collate auras true false") - debouncedCollate(canvas.scene.id, true, false, "createActiveEffect") - }; + if (AAdebug) console.log("deleteAE, collate auras true false"); + debouncedCollate(canvas.scene.id, true, false, "createActiveEffect"); + } }); Hooks.on("canvasReady", (canvas) => { if (!AAgm) return; if (canvas.scene === null) { if (AAdebug) { console.log("Active Auras disabled due to no canvas") } return } - if (AAdebug) console.log("canvasReady, collate auras true false") - debouncedCollate(canvas.scene.id, true, false, "ready") -}) + if (AAdebug) console.log("canvasReady, collate auras true false"); + debouncedCollate(canvas.scene.id, true, false, "ready"); +}); Hooks.on("preUpdateActor", (actor, update) => { if (canvas.scene === null) { if (AAdebug) { console.log("Active Auras disabled due to no canvas") } return } if (AAhelpers.HPCheck(actor)) { const activeTokens = actor.getActiveTokens(); if (activeTokens.length > 0 && AAhelpers.IsAuraToken(activeTokens[0].data._id, canvas.id)) { - if (AAdebug) console.log("0hp, collate auras true true") + if (AAdebug) console.log("0hp, collate auras true true"); Hooks.once("updateActor", (a, b) => { if (!AAgm) return; - debouncedCollate(canvas.scene.id, true, true, "updateActor, dead") + debouncedCollate(canvas.scene.id, true, true, "updateActor, dead"); }) } } if (actor.data.data?.attributes?.hp?.value === 0 && update?.data?.attributes?.hp?.value > 0) { Hooks.once("updateActor", (a, b) => { if (!AAgm) return; - debouncedCollate(canvas.scene.id, true, false, "updateActor, revived") + debouncedCollate(canvas.scene.id, true, false, "updateActor, revived"); }) } -}) +}); Hooks.on("updateMeasuredTemplate", (data, update) => { if (canvas.scene === null) { if (AAdebug) { console.log("Active Auras disabled due to no canvas") } return } if (!getProperty(data, "flags.ActiveAuras")) return; - ActiveAuras.MainAura(undefined, "template movement", data.parent.id) -}) + ActiveAuras.MainAura(undefined, "template movement", data.parent.id); +}); Hooks.on("deleteMeasuredTemplate", (doc) => { if (canvas.scene === null) { if (AAdebug) { console.log("Active Auras disabled due to no canvas") } return } //if (!getProperty(data, "flags.ActiveAuras")) return; - AAhelpers.ExtractAuraById(doc.id, doc.parent.id) + AAhelpers.ExtractAuraById(doc.id, doc.parent.id); //ActiveAuras.CollateAuras(scene._id, false, true, "template deletion") - -}) +}); Hooks.on("deleteCombat", (combat) => { if (!AAgm) return; @@ -180,7 +174,7 @@ Hooks.on("deleteCombat", (combat) => { if (game.settings.get("ActiveAuras", "combatOnly")) { AAhelpers.RemoveAllAppliedAuras() } -}) +}); Hooks.on("deleteCombatant", (combatant) => { if (!AAgm) return; @@ -197,25 +191,23 @@ Hooks.on("createCombatant", (combat, combatant) => { if (combatant.actor.effects?.entries) { for (let effect of combatant.actor.effects?.entries) { if (effect.getFlag('ActiveAuras', 'isAura')) { - setTimeout(() => { - if (AAdebug) console.log("createToken, collate auras true false") - CollateAuras(combat.scene.id, true, false, "add combatant") - }, 20) + if (AAdebug) console.log("createToken, collate auras true false"); + debouncedCollate(combat.scene.id, true, false, "add combatant"); break; } } } -}) +}); Hooks.on("createWall", () => { - if (AAdebug) console.log("createWall, collate auras false true") + if (AAdebug) console.log("createWall, collate auras false true"); debouncedCollate(canvas.scene.id, false, true, "Wall Created") -}) +}); Hooks.on("updateWall", () => { - if (AAdebug) console.log("updateWall, collate auras true true") + if (AAdebug) console.log("updateWall, collate auras true true"); debouncedCollate(canvas.scene.id, true, true, "Wall Updated") -}) +}); Hooks.on("deleteWall", () => { - if (AAdebug) console.log("updateWall, collate auras true false") + if (AAdebug) console.log("updateWall, collate auras true false"); debouncedCollate(canvas.scene.id, true, false, "Wall Deleted") -}) +}); diff --git a/src/aura.js b/src/aura.js index 6960be8..29c4b80 100644 --- a/src/aura.js +++ b/src/aura.js @@ -300,17 +300,18 @@ class ActiveAuras { } /** - * - * @param {Token} token - token instance to remove effect from + * @param {String} tokenID - token instance to remove effect from * @param {String} effectOrigin - origin of effect to remove */ static async RemoveActiveEffects(tokenID, effectOrigin) { - const token = canvas.tokens.get(tokenID) + const token = canvas.tokens.get(tokenID); for (const tokenEffects of token.actor.effects) { if (tokenEffects.data.origin === effectOrigin && tokenEffects.data.flags?.ActiveAuras?.applied === true) { - await token.actor.deleteEmbeddedDocuments("ActiveEffect", [tokenEffects.id]) - console.log(game.i18n.format("ACTIVEAURAS.RemoveLog", { effectDataLabel: effectOrigin, tokenName: token.name })) - + try { + await token.actor.deleteEmbeddedDocuments("ActiveEffect", [tokenEffects.id]); + } finally { + console.log(game.i18n.format("ACTIVEAURAS.RemoveLog", { effectDataLabel: effectOrigin, tokenName: token.name })); + } } } } From 9886e59e1977176848f0ce4d59a1f2491cbf3395 Mon Sep 17 00:00:00 2001 From: Paul Potsides Date: Sat, 3 Sep 2022 16:08:57 +0100 Subject: [PATCH 02/31] DrawingShape is a core class in v10 --- src/aura.js | 2 +- src/templateDetection.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aura.js b/src/aura.js index 6960be8..f9a6b2d 100644 --- a/src/aura.js +++ b/src/aura.js @@ -211,7 +211,7 @@ class ActiveAuras { if (!AAhelpers.CheckType(canvasToken, type)) continue } if (hostile && canvasToken.data._id !== game.combats.active.current.tokenId) return; - const shape = DrawingShape(auraEntity.data) + const shape = getDrawingShape(auraEntity.data) distance = AAmeasure.inAura(canvasToken, auraEntity, game.settings.get("ActiveAuras", "wall-block"), height, radius, shape) } break; diff --git a/src/templateDetection.js b/src/templateDetection.js index 4980212..c504705 100644 --- a/src/templateDetection.js +++ b/src/templateDetection.js @@ -53,7 +53,7 @@ function PixiFromRect(data) { return new PIXI.Rectangle(x, y, width, height) } -function DrawingShape(data) { +function getDrawingShape(data) { let shape; switch (data.type) { case CONST.DRAWING_TYPES.RECTANGLE : shape = PixiFromRect(data); From ebad20ceae67ebe0c017680d0c42c13104eecdcd Mon Sep 17 00:00:00 2001 From: Chris Seieroe Date: Sat, 3 Sep 2022 20:12:25 -0700 Subject: [PATCH 03/31] update manifest file for v10 --- module.json | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/module.json b/module.json index f38549c..7c0f0b0 100644 --- a/module.json +++ b/module.json @@ -1,15 +1,18 @@ { - "name": "ActiveAuras", + "id": "ActiveAuras", "title": "Active-Auras", "description": "Active-Auras", - "author": "Kandashi", - "authors": [], + "authors": [{ + "name": "Kandashi" + }], "url": "https://github.com/kandashi/Active-Auras", "bugs": "https://github.com/kandashi/Active-Auras/issues", "flags": {}, "version": "0.4.0", - "minimumCoreVersion": "9", - "compatibleCoreVersion": "9.242", + "compatibility": { + "minimum": 10, + "verified": 10 + }, "scripts": [ "src/aura.js", "src/AAhelpers.js", @@ -57,7 +60,7 @@ "name": "Active Auras Auras", "label": "Active Auras Auras", "path": "packs/AuraItems", - "entity": "Item", + "type": "Item", "module": "ActiveAuras", "private": false, "system" : "dnd5e" @@ -66,7 +69,7 @@ "name": "Active Auras Zone Actors", "label": "Active Auras Zone Actors", "path": "packs/ZoneActors", - "entity": "Actor", + "type": "Actor", "system": "dnd5e", "module": "ActiveAuras", "private": false @@ -75,20 +78,21 @@ "name": "Active Auras Macros", "label": "Active Auras Macros", "path": "packs/macros", - "entity": "Macro", + "type": "Macro", "module": "ActiveAuras", "private": false } ], - "dependencies" : [ - { - "name": "socketlib", - "manifest": "https://raw.githubusercontent.com/manuelVo/foundryvtt-socketlib/master/module.json" - }, - { - "name": "lib-wrapper" - } - ], + "relationships": { + "requires" : [ + { + "id": "socketlib" + }, + { + "id": "lib-wrapper" + } + ] + }, "socket": true, "manifest": "https://raw.githubusercontent.com/kandashi/Active-Auras/main/module.json", "download": "https://github.com/kandashi/Active-Auras/archive/main.zip", From 80942bc69523870071fb9eabedfff7153827fed3 Mon Sep 17 00:00:00 2001 From: Chris Seieroe Date: Sat, 3 Sep 2022 23:23:38 -0700 Subject: [PATCH 04/31] first pass removing data warnings remove most of the data uses or change them to document references where appropriate --- src/AAhelpers.js | 42 ++++++++++++++++++++-------------------- src/AAhooks.js | 20 +++++++++---------- src/AAhtml.js | 2 +- src/aura.js | 34 ++++++++++++++++---------------- src/collateAuras.js | 14 +++++++------- src/measureDistances.js | 36 +++++++++++++++++----------------- src/templateDetection.js | 18 ++++++++--------- 7 files changed, 83 insertions(+), 83 deletions(-) diff --git a/src/AAhelpers.js b/src/AAhelpers.js index 19802fa..a96d1a8 100644 --- a/src/AAhelpers.js +++ b/src/AAhelpers.js @@ -9,7 +9,7 @@ class AAhelpers { { const scopes = SetupConfiguration.getPackageScopes(); if (!scopes.includes(scope)) throw new Error(`Invalid scope`); - return getProperty(entity.data.flags, scope); + return getProperty(entity.flags, scope); } } @@ -52,10 +52,10 @@ class AAhelpers { } static typeCheck5e(canvasToken, type) { let tokenType; - switch (canvasToken.actor.data.type) { + switch (canvasToken.actor.type) { case "npc": { try { - tokenType = [canvasToken.actor?.data.data.details.type.value, canvasToken.actor?.data.data.details.type.custom]; + tokenType = [canvasToken.actor?.system.details.type.value, canvasToken.actor?.system.details.type.custom]; } catch (error) { console.error([`ActiveAuras: the token has an unreadable type`, canvasToken]) } @@ -63,10 +63,10 @@ class AAhelpers { break; case "character": { try { - if (game.system.data.name === "sw5e") { - tokenType = canvasToken.actor?.data.data.details.species.toLowerCase(); + if (game.system.id === "sw5e") { + tokenType = canvasToken.actor?.system.details.species.toLowerCase(); } - else tokenType = [canvasToken.actor?.data.data.details.race.toLowerCase().replace("-", " ").split(" ")]; + else tokenType = [canvasToken.actor?.system.details.race.toLowerCase().replace("-", " ").split(" ")]; } catch (error) { console.error([`ActiveAuras: the token has an unreadable type`, canvasToken]) } @@ -75,7 +75,7 @@ class AAhelpers { case "vehicle": return; }; let humanoidRaces; - if (game.system.data.name === "sw5e") { + if (game.system.id === "sw5e") { humanoidRaces = ["abyssin", "aingtii", "aleena", "anzellan", "aqualish", "arcona", "ardennian", "arkanian", "balosar", "barabel", "baragwin", "besalisk", "bith", "bothan", "cathar", "cerean", "chadrafan", "chagrian", "chevin", "chironian", "chiss", "clawdite", "codruji", "colicoid", "dashade", "defel", "devoronian", "draethos", "dug", "duros", "echani", "eshkha", "ewok", "falleen", "felucian", "fleshraider", "gamorrean", "gand", "geonosian", "givin", "gotal", "gran", "gungan", "halfhuman", "harch", "herglic", "ho’din", "human", "hutt", "iktotchi", "ithorian", "jawa", "kage", "kaleesh", "kaminoan", "karkarodon", "keldor", "killik", "klatooinian", "kubaz", "kushiban", "kyuzo", "lannik", "lasat", "lurmen", "miraluka", "mirialan", "moncalamari", "mustafarian", "muun", "nautolan", "neimoidian", "noghri", "ortolan", "patrolian", "pau’an", "pa’lowick", "pyke", "quarren", "rakata", "rattataki", "rishii", "rodian", "ryn", "selkath", "shistavanen", "sithpureblood", "squib", "ssiruu", "sullustan", "talz", "tarasin", "thisspiasian", "togorian", "togruta", "toydarian", "trandoshan", "tusken", "twi'lek", "ugnaught", "umbaran", "verpine", "voss", "vurk", "weequay", "wookie", "yevetha", "zabrak", "zeltron", "zygerrian"]; } else humanoidRaces = ["human", "orc", "elf", "tiefling", "gnome", "aaracokra", "dragonborn", "dwarf", "halfling", "leonin", "satyr", "genasi", "goliath", "aasimar", "bugbear", "firbolg", "goblin", "lizardfolk", "tabxi", "triton", "yuan-ti", "tortle", "changling", "kalashtar", "shifter", "warforged", "gith", "centaur", "loxodon", "minotaur", "simic hybrid", "vedalken", "verdan", "locathah", "grung"]; @@ -94,10 +94,10 @@ class AAhelpers { static typeCheckSWADE(canvasToken, type) { let tokenType; - switch (canvasToken.actor.data.type) { + switch (canvasToken.actor.type) { case "npc": { try { - tokenType = canvasToken.actor?.data.data.details.species.name.toLowerCase(); + tokenType = canvasToken.actor?.system.details.species.name.toLowerCase(); } catch (error) { console.error([`ActiveAuras: the token has an unreadable type`, canvasToken]) } @@ -105,7 +105,7 @@ class AAhelpers { break; case "character": { try { - tokenType = canvasToken.actor?.data.data.details.species.name.toLowerCase(); + tokenType = canvasToken.actor?.system.details.species.name.toLowerCase(); } catch (error) { console.error([`ActiveAuras: the token has an unreadable type`, canvasToken]) } @@ -130,12 +130,12 @@ class AAhelpers { switch (game.system.id) { case "dnd5e": ; case "sw5e": { - if (getProperty(actor, "data.data.attributes.hp.max") === 0) return true - if (getProperty(actor, "data.data.attributes.hp.value") <= 0) return false + if (getProperty(actor, "system.attributes.hp.max") === 0) return true + if (getProperty(actor, "system.attributes.hp.value") <= 0) return false else return true } case "swade": { - let { max, value, ignored } = actor.data.data.wounds + let { max, value, ignored } = actor.system.wounds if (value - ignored >= max) return false else return true } @@ -160,9 +160,9 @@ class AAhelpers { for (let removeToken of canvas.tokens.placeables) { if (removeToken?.actor?.effects.size > 0) { for (let testEffect of removeToken.actor.effects) { - if (!EffectsArray.includes(testEffect.data.origin) && testEffect.data?.flags?.ActiveAuras?.applied) { + if (!EffectsArray.includes(testEffect.origin) && testEffect?.flags?.ActiveAuras?.applied) { await removeToken.actor.deleteEmbeddedDocuments("ActiveEffect", [testEffect.id]) - console.log(game.i18n.format("ACTIVEAURAS.RemoveLog", { effectDataLabel: testEffect.data.label, tokenName: removeToken.name })) + console.log(game.i18n.format("ACTIVEAURAS.RemoveLog", { effectDataLabel: testEffect.label, tokenName: removeToken.name })) } } } @@ -171,7 +171,7 @@ class AAhelpers { static async RemoveAllAppliedAuras() { for (let removeToken of canvas.tokens.placeables) { if (removeToken?.actor?.effects.size > 0) { - let effects = removeToken.actor.effects.reduce((a, v) => { if (v.data?.flags?.ActiveAuras?.applied) return a.concat(v.id) }, []) + let effects = removeToken.actor.effects.reduce((a, v) => { if (v?.flags?.ActiveAuras?.applied) return a.concat(v.id) }, []) await removeToken.actor.deleteEmbeddedDocuments("ActiveEffect", effects) console.log(game.i18n.format("ACTIVEAURAS.RemoveLog", { tokenName: removeToken.name })) } @@ -191,8 +191,8 @@ class AAhelpers { static applyWrapper(wrapped, ...args) { let actor = args[0] let change = args[1] - if (change.effect.data.flags?.ActiveAuras?.ignoreSelf) { - console.log(game.i18n.format("ACTIVEAURAS.IgnoreSelfLog", { effectDataLabel: change.effect.data.label, changeKey: change.key, actorName: actor.name })); + if (change.effect.flags?.ActiveAuras?.ignoreSelf) { + console.log(game.i18n.format("ACTIVEAURAS.IgnoreSelfLog", { effectDataLabel: change.effect.label, changeKey: change.key, actorName: actor.name })); args[1] = {} return wrapped(...args); } @@ -201,7 +201,7 @@ class AAhelpers { static scrollingText(wrapped, ...args) { if (game.settings.get("ActiveAuras", "scrollingAura")) { - if (this.data.flags["ActiveAuras"]?.applied) { + if (this.flags["ActiveAuras"]?.applied) { Object.defineProperty(this, "isSuppressed", { get: () => { if (new Error('').stack.includes("ActiveEffect5e._displayScrollingStatus")){ @@ -250,8 +250,8 @@ class AAhelpers { } static async removeAurasOnToken(token){ - if(!token.data.actorLink) return - let auras = token.actor.effects.filter(i => i.data.flags?.["ActiveAuras"]?.applied).map(i => i.id) + if(!token.document.actorLink) return + let auras = token.actor.effects.filter(i => i.flags?.["ActiveAuras"]?.applied).map(i => i.id) if(!auras) return await token.actor.deleteEmbeddedDocuments("ActiveEffect", auras) } diff --git a/src/AAhooks.js b/src/AAhooks.js index 0ca368e..ce3e7d1 100644 --- a/src/AAhooks.js +++ b/src/AAhooks.js @@ -30,9 +30,9 @@ Hooks.on("createToken", (token) => { if (canvas.scene === null) { if (AAdebug) { console.log("Active Auras disabled due to no canvas") } return } if (!AAgm) return; try { - if (getProperty(token, "data.flags.multilevel-tokens")) return + if (getProperty(token, "flags.multilevel-tokens")) return for (let effect of token.actor.effects?.contents) { - if (effect.data.flags.ActiveAuras?.isAura) { + if (effect.flags.ActiveAuras?.isAura) { if (AAdebug) console.log("createToken, collate auras true false") debouncedCollate(canvas.scene.id, true, false, "createToken") break; @@ -101,7 +101,7 @@ Hooks.on("updateToken", async (token, update, _flags, _id) => { Hooks.on("updateActiveEffect", (effect, _update) => { if (canvas.scene === null) { if (AAdebug) { console.log("Active Auras disabled due to no canvas") } return } if (!AAgm) return; - if (effect.data.flags?.ActiveAuras?.isAura) { + if (effect.flags?.ActiveAuras?.isAura) { if (AAdebug) console.log("updateAE, collate auras true true") debouncedCollate(canvas.scene.id, true, true, "updateActiveEffect") } @@ -113,8 +113,8 @@ Hooks.on("updateActiveEffect", (effect, _update) => { Hooks.on("deleteActiveEffect", (effect) => { if (canvas.scene === null) { if (AAdebug) { console.log("Active Auras disabled due to no canvas") } return } if (!AAgm) return; - let applyStatus = effect.data.flags?.ActiveAuras?.applied; - let auraStatus = effect.data.flags?.ActiveAuras?.isAura; + let applyStatus = effect.flags?.ActiveAuras?.applied; + let auraStatus = effect.flags?.ActiveAuras?.isAura; if (!applyStatus && auraStatus) { if (AAdebug) console.log("deleteAE, collate auras true false") debouncedCollate(canvas.scene.id, false, true, "deleteActiveEffect") @@ -127,7 +127,7 @@ Hooks.on("deleteActiveEffect", (effect) => { Hooks.on("createActiveEffect", (effect) => { if (canvas.scene === null) { if (AAdebug) { console.log("Active Auras disabled due to no canvas") } return } if (!AAgm) return; - if (!effect.data.flags?.ActiveAuras?.applied && effect.data.flags?.ActiveAuras?.isAura) { + if (!effect.flags?.ActiveAuras?.applied && effect.flags?.ActiveAuras?.isAura) { if (AAdebug) console.log("deleteAE, collate auras true false") debouncedCollate(canvas.scene.id, true, false, "createActiveEffect") }; @@ -144,7 +144,7 @@ Hooks.on("preUpdateActor", (actor, update) => { if (canvas.scene === null) { if (AAdebug) { console.log("Active Auras disabled due to no canvas") } return } if (AAhelpers.HPCheck(actor)) { const activeTokens = actor.getActiveTokens(); - if (activeTokens.length > 0 && AAhelpers.IsAuraToken(activeTokens[0].data._id, canvas.id)) { + if (activeTokens.length > 0 && AAhelpers.IsAuraToken(activeTokens[0].id, canvas.id)) { if (AAdebug) console.log("0hp, collate auras true true") Hooks.once("updateActor", (a, b) => { if (!AAgm) return; @@ -152,7 +152,7 @@ Hooks.on("preUpdateActor", (actor, update) => { }) } } - if (actor.data.data?.attributes?.hp?.value === 0 && update?.data?.attributes?.hp?.value > 0) { + if (actor.system?.attributes?.hp?.value === 0 && update?.system?.attributes?.hp?.value > 0) { Hooks.once("updateActor", (a, b) => { if (!AAgm) return; debouncedCollate(canvas.scene.id, true, false, "updateActor, revived") @@ -184,8 +184,8 @@ Hooks.on("deleteCombat", (combat) => { Hooks.on("deleteCombatant", (combatant) => { if (!AAgm) return; - if (AAhelpers.IsAuraToken(combatant.data.tokenId, combatant.parent.scene.id)) { - AAhelpers.ExtractAuraById(combatant.data.tokenId, combatant.parent.scene.id) + if (AAhelpers.IsAuraToken(combatant.tokenId, combatant.parent.scene.id)) { + AAhelpers.ExtractAuraById(combatant.tokenId, combatant.parent.scene.id) } }); diff --git a/src/AAhtml.js b/src/AAhtml.js index 2871810..6caaa52 100644 --- a/src/AAhtml.js +++ b/src/AAhtml.js @@ -4,7 +4,7 @@ * Hooks onto effect sheet to add aura configuration */ Hooks.on("renderActiveEffectConfig", async (sheet, html) => { - const flags = sheet.object.data.flags ?? {}; + const flags = sheet.object.flags ?? {}; const FormIsAura = game.i18n.format("ACTIVEAURAS.FORM_IsAura"); const FormIgnoreSelf = game.i18n.format("ACTIVEAURAS.FORM_IgnoreSelf"); const FormHidden = game.i18n.format("ACTIVEAURAS.FORM_Hidden"); diff --git a/src/aura.js b/src/aura.js index 6960be8..63bc71d 100644 --- a/src/aura.js +++ b/src/aura.js @@ -30,7 +30,7 @@ class ActiveAuras { if (movedToken !== undefined) { if (AAhelpers.IsAuraToken(movedToken.id, sceneID)) { - auraTokenId = movedToken.data._id + auraTokenId = movedToken.id } else if (getProperty(movedToken, "flags.token-attacher")) { if (AAdebug) console.log("ActiveAuras: token attacher movement") @@ -120,13 +120,13 @@ class ActiveAuras { * @param {Token} canvasToken - single token to test */ static UpdateToken(map, canvasToken, tokenId) { - if (canvasToken.data.flags['multilevel-tokens']) return; + if (canvasToken.document.flags['multilevel-tokens']) return; if (canvasToken.actor === null) return; - if (canvasToken.actor.data.type == "vehicle") return + if (canvasToken.actor.type == "vehicle") return let tokenAlignment; if (game.system.id === "dnd5e" || game.system.id === "sw5e") { try { - tokenAlignment = canvasToken.actor?.data.data.details.alignment.toLowerCase(); + tokenAlignment = canvasToken.actor?.system.details.alignment.toLowerCase(); } catch (error) { console.error([`ActiveAuras: the token has an unreadable alignment`, canvasToken]) } @@ -175,11 +175,11 @@ class ActiveAuras { if (auraEntity.id === canvasToken.id) continue; - if (!AAhelpers.DispositionCheck(auraTargets, auraEntity.data.disposition, canvasToken.data.disposition)) continue; + if (!AAhelpers.DispositionCheck(auraTargets, auraEntity.document.disposition, canvasToken.document.disposition)) continue; if (type) { if (!AAhelpers.CheckType(canvasToken, type)) continue } - if (hostile && canvasToken.data._id !== game.combats.active?.current.tokenId) continue; + if (hostile && canvasToken.id !== game.combats.active?.current.tokenId) continue; if (game.system.id === "swade") { if (!AAhelpers.Wildcard(canvasToken, wildcard, extra)) continue @@ -194,9 +194,9 @@ class ActiveAuras { if (type) { if (!AAhelpers.CheckType(canvasToken, type)) continue } - if (hostile && canvasToken.data._id !== game.combats.active.current.tokenId) return; + if (hostile && canvasToken.id !== game.combats.active.current.tokenId) return; if (auraEffect.casterDisposition) { - if (!AAhelpers.DispositionCheck(auraTargets, auraEffect.casterDisposition, canvasToken.data.disposition)) continue; + if (!AAhelpers.DispositionCheck(auraTargets, auraEffect.casterDisposition, canvasToken.disposition)) continue; } const shape = getTemplateShape(auraEntity) let templateDetails = auraEntity @@ -210,7 +210,7 @@ class ActiveAuras { if (type) { if (!AAhelpers.CheckType(canvasToken, type)) continue } - if (hostile && canvasToken.data._id !== game.combats.active.current.tokenId) return; + if (hostile && canvasToken.id !== game.combats.active.current.tokenId) return; const shape = DrawingShape(auraEntity.data) distance = AAmeasure.inAura(canvasToken, auraEntity, game.settings.get("ActiveAuras", "wall-block"), height, radius, shape) } @@ -228,7 +228,7 @@ class ActiveAuras { map.set(MapKey, { add: true, token: canvasToken, effect: auraEffect }) } } - else if (!MapObject?.add && canvasToken.document.actor?.effects.contents.some(e => e.data.origin === auraEffect.data.origin && e.data.label === auraEffect.data.label)) { + else if (!MapObject?.add && canvasToken.document.actor?.effects.contents.some(e => e.origin === auraEffect.data.origin && e.label === auraEffect.data.label)) { if (MapObject) { MapObject.add = false } @@ -248,17 +248,17 @@ class ActiveAuras { static async CreateActiveEffect(tokenID, oldEffectData) { const token = canvas.tokens.get(tokenID) - const duplicateEffect = token.document.actor.effects.contents.find(e => e.data.origin === oldEffectData.origin && e.data.label === oldEffectData.label) - if (getProperty(duplicateEffect, "data.flags.ActiveAuras.isAura")) return; + const duplicateEffect = token.document.actor.effects.contents.find(e => e.origin === oldEffectData.origin && e.label === oldEffectData.label) + if (getProperty(duplicateEffect, "flags.ActiveAuras.isAura")) return; if (duplicateEffect) { - if (duplicateEffect.data.origin === oldEffectData.origin) return; - if (JSON.stringify(duplicateEffect.data.changes) === JSON.stringify(oldEffectData.changes)) return; + if (duplicateEffect.origin === oldEffectData.origin) return; + if (JSON.stringify(duplicateEffect.changes) === JSON.stringify(oldEffectData.changes)) return; else await ActiveAuras.RemoveActiveEffects(tokenID, oldEffectData.origin) } let effectData = duplicate(oldEffectData) if (effectData.flags.ActiveAuras.onlyOnce) { const AAID = oldEffectData.origin.replaceAll(".", "") - if (token.data.flags.ActiveAuras?.[AAID]) return; + if (token.document.flags.ActiveAuras?.[AAID]) return; else await token.document.setFlag("ActiveAuras", AAID, true) } if (effectData.flags.ActiveAuras?.isMacro) { @@ -276,7 +276,7 @@ class ActiveAuras { } else if (typeof val === "string" && val.includes("@token")) { let re = /([\s]*@token)/gms - return val.replaceAll(re, ` ${token.data._id}`) + return val.replaceAll(re, ` ${token.id}`) } return val; }); @@ -307,7 +307,7 @@ class ActiveAuras { static async RemoveActiveEffects(tokenID, effectOrigin) { const token = canvas.tokens.get(tokenID) for (const tokenEffects of token.actor.effects) { - if (tokenEffects.data.origin === effectOrigin && tokenEffects.data.flags?.ActiveAuras?.applied === true) { + if (tokenEffects.origin === effectOrigin && tokenEffects.flags?.ActiveAuras?.applied === true) { await token.actor.deleteEmbeddedDocuments("ActiveEffect", [tokenEffects.id]) console.log(game.i18n.format("ACTIVEAURAS.RemoveLog", { effectDataLabel: effectOrigin, tokenName: token.name })) diff --git a/src/collateAuras.js b/src/collateAuras.js index 5be6ee4..954d844 100644 --- a/src/collateAuras.js +++ b/src/collateAuras.js @@ -18,16 +18,16 @@ async function CollateAuras(sceneID, checkAuras, removeAuras, source) { //Skips over null actor tokens if (testToken.actor === null || testToken.actor === undefined) continue; //Skips over MLT coppied tokens - if (testToken.data.flags["multilevel-tokens"]) continue + if (testToken.flags["multilevel-tokens"]) continue if (!AAhelpers.HPCheck(testToken) && game.settings.get("ActiveAuras", "dead-aura")) { if (AAdebug) console.log(`Skipping ${testToken.name}, 0hp`) continue } for (let testEffect of testToken?.actor?.effects.contents) { - if (testEffect.data.flags?.ActiveAuras?.isAura) { - if (testEffect.data.disabled) continue; - let newEffect = { data: duplicate(testEffect.data), parentActorLink: testEffect.parent.data.token.actorLink, parentActorId: testEffect.parent.id, entityType: "token", entityId: testToken.id } + if (testEffect.flags?.ActiveAuras?.isAura) { + if (testEffect.disabled) continue; + let newEffect = { data: duplicate(testEffect), parentActorLink: testEffect.parent.prototypeToken.actorLink, parentActorId: testEffect.parent.id, entityType: "token", entityId: testToken.id } let re = /@[\w\.]+/g let rollData = testToken.actor.getRollData() @@ -51,7 +51,7 @@ async function CollateAuras(sceneID, checkAuras, removeAuras, source) { newEffect.data.flags.ActiveAuras.applied = true; newEffect.data.flags.ActiveAuras.isMacro = macro; newEffect.data.flags.ActiveAuras.ignoreSelf = false; - if (testEffect.data.flags.ActiveAuras?.hidden && testToken.data.hidden) newEffect.data.flags.ActiveAuras.Paused = true; + if (testEffect.flags.ActiveAuras?.hidden && testToken.hidden) newEffect.data.flags.ActiveAuras.Paused = true; else newEffect.data.flags.ActiveAuras.Paused = false; effectArray.push(newEffect) } @@ -78,7 +78,7 @@ async function CollateAuras(sceneID, checkAuras, removeAuras, source) { } function RetrieveTemplateAuras(effectArray) { - let auraTemplates = canvas.templates.placeables.filter(i => i.data.flags?.ActiveAuras?.IsAura !== undefined) + let auraTemplates = canvas.templates.placeables.filter(i => i.flags?.ActiveAuras?.IsAura !== undefined) for (let template of auraTemplates) { for (let testEffect of template.data.flags?.ActiveAuras?.IsAura) { @@ -113,7 +113,7 @@ function RetrieveTemplateAuras(effectArray) { function RetrieveDrawingAuras(effectArray) { if (!effectArray) effectArray = AuraMap.get(canvas.scene._id)?.effects; - let auraDrawings = canvas.drawings.placeables.filter(i => i.data.flags?.ActiveAuras?.IsAura !== undefined) + let auraDrawings = canvas.drawings.placeables.filter(i => i.flags?.ActiveAuras?.IsAura !== undefined) for (let drawing of auraDrawings) { for (let testEffect of drawing.data.flags?.ActiveAuras?.IsAura) { diff --git a/src/measureDistances.js b/src/measureDistances.js index 8e6682c..4b86824 100644 --- a/src/measureDistances.js +++ b/src/measureDistances.js @@ -21,24 +21,24 @@ class AAmeasure { aura.inAura = true } - let sourceCorners = source.data.height === 1 && source.data.width === 1 ? + let sourceCorners = source.document.height === 1 && source.document.width === 1 ? [{ x: source.center.x, y: source.center.y }] : [ { x: source.center.x, y: source.center.y }, - { x: source.data.x + g2, y: source.data.y + g2 }, - { x: source.data.x + (source.data.width * gs) - g2, y: source.data.y + g2 }, - { x: source.data.x + g2, y: source.data.y + (source.data.height * gs) - g2 }, - { x: source.data.x + (source.data.width * gs) - g2, y: source.data.y + (source.data.height * gs) - g2 } + { x: source.x + g2, y: source.y + g2 }, + { x: source.x + (source.document.width * gs) - g2, y: source.y + g2 }, + { x: source.x + g2, y: source.y + (source.document.height * gs) - g2 }, + { x: source.x + (source.document.width * gs) - g2, y: source.y + (source.document.height * gs) - g2 } ] - let targetCorners = target.data.height === 1 && target.data.width === 1 ? + let targetCorners = target.document.height === 1 && target.document.width === 1 ? [{ x: target.center.x, y: target.center.y, collides: false }] : [ { x: target.center.x, y: target.center.y, collides: false }, - { x: target.data.x + g2, y: target.data.y + g2, collides: false }, - { x: target.data.x + (target.data.width * gs) - g2, y: target.data.y + g2, collides: false }, - { x: target.data.x + g2, y: target.data.y + (target.data.height * gs) - g2, collides: false }, - { x: target.data.x + (target.data.width * gs) - g2, y: target.data.y + (target.data.height * gs) - g2, collides: false } + { x: target.x + g2, y: target.y + g2, collides: false }, + { x: target.x + (target.document.width * gs) - g2, y: target.y + g2, collides: false }, + { x: target.x + g2, y: target.y + (target.document.height * gs) - g2, collides: false }, + { x: target.x + (target.document.width * gs) - g2, y: target.y + (target.document.height * gs) - g2, collides: false } ] if (AAdebug) { canvas.foreground.children.filter(i => i.squares)?.forEach(i => i.destroy()) @@ -62,7 +62,7 @@ class AAmeasure { let collision; if (game.modules.get("levels")?.active) { - collision = _levels.testCollision({ x: t.x, y: t.y, z: target.data.elevation }, { x: s.x, y: s.y, z: source.data.elevation ?? source.data.flags?.levels?.elevation }, "collision") + collision = _levels.testCollision({ x: t.x, y: t.y, z: target.document.elevation }, { x: s.x, y: s.y, z: source.document.elevation ?? source.flags?.levels?.elevation }, "collision") } else { collision = canvas.walls.checkCollision(r) @@ -135,13 +135,13 @@ class AAmeasure { let distance; switch (game.settings.get("ActiveAuras", "vertical-euclidean")) { case true: { - distance = Math.abs(source.data.elevation - target.data.elevation) + distance = Math.abs(source.document.elevation - target.document.elevation) } break; case false: { let g = canvas.dimensions let a = r.distance / g.size * g.distance; - let b = (source.data.elevation - target.data.elevation) + let b = (source.document.elevation - target.document.elevation) let c = (a * a) + (b * b) distance = Math.sqrt(c) } @@ -159,10 +159,10 @@ class AAmeasure { static boundingCheck(t1, t2, radius) { let { size, distance } = canvas.dimensions let rad = (radius / distance) * size - const xMax = t2.data.x + rad + t2.w + (size * t1.data.width) - const xMin = t2.data.x - rad - (size * t1.data.width) - const yMax = t2.data.y + rad + t2.h + (size * t1.data.height) - const yMin = t2.data.y - rad - (size * t1.data.height) + const xMax = t2.x + rad + t2.w + (size * t1.document.width) + const xMin = t2.x - rad - (size * t1.document.width) + const yMax = t2.y + rad + t2.h + (size * t1.document.height) + const yMin = t2.y - rad - (size * t1.document.height) if (AAdebug) { canvas.foreground.children.find(i => i.boundingCheck)?.destroy() let g = new PIXI.Graphics() @@ -170,7 +170,7 @@ class AAmeasure { let check = canvas.foreground.addChild(g) check.boundingCheck = true } - return !(t1.data.x < xMin || t1.data.x > xMax || t1.data.y > yMax || t1.data.y < yMin); + return !(t1.x < xMin || t1.x > xMax || t1.y > yMax || t1.y < yMin); } diff --git a/src/templateDetection.js b/src/templateDetection.js index 4980212..df0ea98 100644 --- a/src/templateDetection.js +++ b/src/templateDetection.js @@ -3,12 +3,12 @@ function getTemplateShape(template) { let d = canvas.dimensions; // Extract and prepare data - let {direction, distance, angle, width} = template.data; + let {direction, distance, angle, width} = template.document; distance *= (d.size / d.distance); width *= (d.size / d.distance); direction = Math.toRadians(direction); let shape; - switch (template.data.t) { + switch (template.document.t) { case "circle": shape = template._getCircleShape( distance); break; @@ -21,8 +21,8 @@ function getTemplateShape(template) { case "ray": shape = template._getRayShape( direction, distance, width); } - shape.x = template.data.x - shape.y = template.data.y + shape.x = template.x + shape.y = template.y return shape } @@ -30,12 +30,12 @@ function getAuraShape(source, radius) { const gs = canvas.dimensions.size const gd = gs / canvas.dimensions.distance if(game.settings.get(game.system.id, "diagonalMovement") === "555") return new PIXI.Rectangle( - source.data.x - (radius * gd), - source.data.y - (radius * gd), - (radius * gd)*2 + source.data.width *gs, - (radius * gd)*2 + source.data.height *gs, + source.x - (radius * gd), + source.y - (radius * gd), + (radius * gd)*2 + source.document.width *gs, + (radius * gd)*2 + source.document.height *gs, ) - return new PIXI.Circle(source.center.x, source.center.y, ((radius * gd) + (source.data.width / 2 * gs))) + return new PIXI.Circle(source.center.x, source.center.y, ((radius * gd) + (source.document.width / 2 * gs))) } function PixiFromPolygon(data) { From 1cfc8b30e569a8d5895641187a4ab1283cc70cdb Mon Sep 17 00:00:00 2001 From: Chris Seieroe Date: Sun, 4 Sep 2022 00:06:25 -0700 Subject: [PATCH 05/31] migrate the compendiums run the dnd5e.migrations.migrateCompendium on the actor and item ones, do the normal pack.migrate on the macro one --- packs/AuraItems | 28 ++++++++++++++-------------- packs/ZoneActors | 2 +- packs/macros | 6 +++--- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/packs/AuraItems b/packs/AuraItems index e5466dd..be31c51 100644 --- a/packs/AuraItems +++ b/packs/AuraItems @@ -1,14 +1,14 @@ -{"_id":"1YbUFxCDhleZBlqH","name":"Black Tentacles","type":"spell","img":"icons/magic/nature/vines-thorned-curled-glow-teal-purple.webp","data":{"description":{"value":"

Squirming, ebony tentacles fill a 20-foot square on ground that you can see within range. For the Duration, these tentacles turn the ground in the area into difficult terrain.

\n

When a creature enters the affected area for the first time on a turn or starts its turn there, the creature must succeed on a Dexterity saving throw or take 3d6 bludgeoning damage and be @Compendium[dnd5e.rules.QRKWz3p6v9Rl1Tzh]{Restrained} by the tentacles until the spell ends. A creature that starts its turn in the area and is already @Compendium[dnd5e.rules.QRKWz3p6v9Rl1Tzh]{Restrained} by the tentacles takes 3d6 bludgeoning damage.

\n

A creature @Compendium[dnd5e.rules.QRKWz3p6v9Rl1Tzh]{Restrained} by the tentacles can use its action to make a Strength or D⁠exterity check (its choice) against your spell save DC. On a success, it frees itself.

","chat":"","unidentified":""},"source":"","activation":{"type":"action","cost":1,"condition":""},"duration":{"value":1,"units":"minute"},"target":{"value":20,"width":null,"units":"ft","type":"square"},"range":{"value":90,"long":0,"units":"ft"},"uses":{"value":0,"max":"0","per":""},"consume":{"type":"","target":"","amount":null},"ability":"","actionType":"save","attackBonus":0,"chatFlavor":"","critical":{"threshold":null,"damage":null},"damage":{"parts":[],"versatile":""},"formula":"","save":{"ability":"","dc":null,"scaling":"spell"},"level":4,"school":"con","components":{"value":"","vocal":true,"somatic":true,"material":true,"ritual":false,"concentration":true},"materials":{"value":"A piece of tentacle from a giant octopus or a giant squid","consumed":false,"cost":0,"supply":0},"preparation":{"mode":"prepared","prepared":false},"scaling":{"mode":"none","formula":""}},"effects":[{"_id":"ovktWr3CsAFw82Nd","flags":{"dae":{"stackable":"none","macroRepeat":"startEveryTurn","transfer":false,"specialDuration":[]},"ActiveAuras":{"isAura":true,"ignoreSelf":false,"hidden":false,"height":false,"alignment":"","type":"","aura":"All","radius":null,"save":"","savedc":null,"hostile":true,"onlyOnce":true,"time":"None","displayTemp":false},"core":{"statusId":""}},"changes":[{"key":"flags.midi-qol.OverTime","mode":0,"value":"turn=start, saveAbility=con, saveDC=@attributes.spelldc, saveDamage=noDamage, rollType=save, saveMagic=true, damageBeforeSave=false, damageRoll=3d6, damageType=bludgeoning","priority":"20"}],"disabled":false,"duration":{"startTime":null},"icon":"systems/dnd5e/icons/spells/vines-eerie-2.jpg","label":"Black Tentacles","tint":null,"transfer":false,"selectedKey":"flags.midi-qol.OverTime"}],"folder":null,"sort":0,"permission":{"default":0,"E4BVikjIkVl2lL2j":3},"flags":{"midi-qol":{"onUseMacroName":"[preActiveEffects]ItemMacro","effectActivation":false,"forceCEOn":false},"core":{"sourceId":"Compendium.ActiveAuras.Active Auras Auras.1YbUFxCDhleZBlqH"},"itemacro":{"macro":{"data":{"_id":null,"name":"Black Tentacles","type":"script","author":"jkdToQqzFl0mFXls","img":"icons/svg/dice-target.svg","scope":"global","command":"if (!game.modules.get(\"advanced-macros\")?.active) { ui.notifications.error(\"Advanced Macros is not enabled\"); return }\nif(args[0].tag === \"OnUse\"){AAhelpers.applyTemplate(args)\n}","folder":null,"sort":0,"permission":{"default":0},"flags":{}}}},"midiProperties":{"nodam":false,"fulldam":false,"halfdam":false,"rollOther":false,"critOther":false,"magicdam":false,"magiceffect":false,"concentration":false,"toggleEffect":false}}} -{"_id":"8dL7Y3BMCV0L4Zjq","name":"Cloudkill","type":"spell","img":"icons/magic/air/fog-gas-smoke-swirling-green.webp","data":{"description":{"value":"

You create a 20-foot-radius sphere of poisonous, yellow-green fog centered on a point you choose within range. The fog spreads around corners. It lasts for the duration or until strong wind disperses the fog, ending the spell. Its area is heavily obscured.

When a creature enters the spell's area for the first time on a turn or starts its turn there, that creature must make a Constitution saving throw. The creature takes 5d8 poison damage on a failed save, or half as much damage on a successful one. Creatures are affected even if they hold their breath or don't need to breathe.

The fog moves 10 feet away from you at the start of each of your turns, rolling along the surface of the ground. The vapors, being heavier than air, sink to the lowest level of the land, even pouring down openings.

Higher Levels. When you cast this spell using a spell slot of 6th level or higher, the damage increases by 1d8 for each slot level above 5th.

","chat":"","unidentified":""},"source":"PHB pg. 222","activation":{"type":"action","cost":1,"condition":""},"duration":{"value":10,"units":"minute"},"target":{"value":20,"width":null,"units":"ft","type":"sphere"},"range":{"value":120,"long":0,"units":"ft"},"uses":{"value":0,"max":"0","per":""},"consume":{"type":"","target":"","amount":null},"ability":"","actionType":"other","attackBonus":0,"chatFlavor":"","critical":{"threshold":null,"damage":null},"damage":{"parts":[],"versatile":""},"formula":"","save":{"ability":"","dc":null,"scaling":"spell"},"level":5,"school":"con","components":{"value":"","vocal":true,"somatic":true,"material":false,"ritual":false,"concentration":true},"materials":{"value":"","consumed":false,"cost":0,"supply":0},"preparation":{"mode":"prepared","prepared":false},"scaling":{"mode":"level","formula":"1d8"}},"effects":[{"_id":"Q0lPH0mCHeIxs0TH","flags":{"dae":{"stackable":"none","macroRepeat":"startEveryTurn","transfer":false,"specialDuration":[]},"ActiveAuras":{"isAura":true,"ignoreSelf":false,"hidden":false,"height":false,"alignment":"","type":"","aura":"All","radius":null,"save":"","savedc":null,"hostile":false,"onlyOnce":true,"time":"None","displayTemp":true},"core":{"statusId":""}},"changes":[{"key":"flags.midi-qol.OverTime","mode":2,"value":"turn=start, saveAbility=con, saveDC=@attributes.spelldc, saveDamage=halfdamage, rollType=save, saveMagic=true, damageBeforeSave=false, damageRoll=(@item.level)d8, damageType=poison","priority":"20"}],"disabled":false,"duration":{"startTime":null},"icon":"systems/dnd5e/icons/spells/fog-acid-3.jpg","label":"Cloudkill","tint":null,"transfer":false,"selectedKey":"flags.midi-qol.OverTime"}],"folder":null,"sort":0,"permission":{"default":0,"E4BVikjIkVl2lL2j":3},"flags":{"midi-qol":{"onUseMacroName":"[postActiveEffects]ItemMacro","effectActivation":false},"core":{"sourceId":"Compendium.ActiveAuras.Active Auras Auras.8dL7Y3BMCV0L4Zjq"},"itemacro":{"macro":{"data":{"_id":null,"name":"Cloudkill","type":"script","author":"jkdToQqzFl0mFXls","img":"icons/svg/dice-target.svg","scope":"global","command":"if (!game.modules.get(\"advanced-macros\")?.active) { ui.notifications.error(\"Advanced Macros is not enabled\"); return }\nif(args[0].tag === \"OnUse\"){\nAAhelpers.applyTemplate(args)\n}","folder":null,"sort":0,"permission":{"default":0},"flags":{}}}},"midiProperties":{"nodam":false,"fulldam":false,"halfdam":false,"rollOther":false,"critOther":false,"magicdam":false,"magiceffect":false,"concentration":false,"toggleEffect":false}}} -{"_id":"LFB03nEpH36szQ89","name":"Moonbeam","type":"spell","img":"icons/magic/light/beam-rays-blue-large.webp","data":{"description":{"value":"

A silvery beam of pale light shines down in a 5-foot-radius, 40-foot-high Cylinder centered on a point within range. Until the spell ends, dim light fills the cylind⁠er.

When a creature enters the spell’s area for the first time on a turn or starts its turn there, it is engulfed in ghostly flames that cause searing pain, and it must make a Constitution saving throw. It takes 2d10 radiant damage on a failed save, or half as much damage on a successful one.

A Shapechanger makes its saving throw with disadvantage. If it fails, it also instantly reverts to its original form and can’t assume a different form until it leaves the spell’s light.

On each of your turns after you cast this spell, you can use an action to move the beam up to 60 feet in any direction.

At Higher Levels. When you cast this spell using a spell slot of 3rd level or higher, the damage increases by 1d10 for each slot level above 2nd.

","chat":"","unidentified":""},"source":"PHB pg. 261","activation":{"type":"action","cost":1,"condition":""},"duration":{"value":1,"units":"minute"},"target":{"value":5,"width":null,"units":"ft","type":"cylinder"},"range":{"value":120,"long":0,"units":"ft"},"uses":{"value":0,"max":"0","per":""},"consume":{"type":"","target":"","amount":null},"ability":"","actionType":"other","attackBonus":0,"chatFlavor":"","critical":{"threshold":null,"damage":null},"damage":{"parts":[],"versatile":""},"formula":"","save":{"ability":"","dc":null,"scaling":"spell"},"level":2,"school":"evo","components":{"value":"","vocal":true,"somatic":true,"material":true,"ritual":false,"concentration":true},"materials":{"value":"Several seeds of any moonseed plant and a piece of opalescent feldspar","consumed":false,"cost":0,"supply":0},"preparation":{"mode":"prepared","prepared":false},"scaling":{"mode":"none","formula":""}},"effects":[{"_id":"ZaAwlOQfLgwyYiOn","flags":{"dae":{"stackable":"none","macroRepeat":"startEveryTurn","transfer":false,"specialDuration":[]},"ActiveAuras":{"isAura":true,"ignoreSelf":false,"hidden":false,"height":false,"alignment":"","type":"","aura":"All","radius":null,"save":"","savedc":null,"hostile":true,"onlyOnce":true,"time":"None","displayTemp":true},"core":{"statusId":""}},"changes":[{"key":"flags.midi-qol.OverTime","mode":0,"value":"turn=start, saveAbility=con, saveDC=@attributes.spelldc, saveDamage=halfdamage, rollType=save, saveMagic=true, damageBeforeSave=false, damageRoll=(@item.level)d10, damageType=radiant","priority":"20"}],"disabled":false,"duration":{"startTime":null},"icon":"systems/dnd5e/icons/spells/beam-blue-3.jpg","label":"Moonbeam","origin":"Actor.LClwU7mAyShYLmFU.OwnedItem.slhSBMhIsacM2lPh","tint":null,"transfer":false,"selectedKey":"flags.midi-qol.OverTime"}],"folder":null,"sort":0,"permission":{"default":0,"E4BVikjIkVl2lL2j":3},"flags":{"midi-qol":{"onUseMacroName":"[preActiveEffects]ItemMacro","effectActivation":false},"core":{"sourceId":"Item.slhSBMhIsacM2lPh"},"itemacro":{"macro":{"data":{"_id":null,"name":"Moonbeam","type":"script","author":"jkdToQqzFl0mFXls","img":"icons/svg/dice-target.svg","scope":"global","command":"if (!game.modules.get(\"advanced-macros\")?.active) { ui.notifications.error(\"Advanced Macros is not enabled\"); return }\nif (args[0].tag === \"OnUse\") {\nAAhelpers.applyTemplate(args) }","folder":null,"sort":0,"permission":{"default":0},"flags":{}}}},"midiProperties":{"nodam":false,"fulldam":false,"halfdam":false,"rollOther":false,"critOther":false,"magicdam":false,"magiceffect":false,"concentration":false,"toggleEffect":false}}} -{"_id":"LnGJt2o5UoLiXYTa","name":"Darkness","type":"spell","img":"icons/magic/perception/eye-ringed-glow-angry-red.webp","data":{"description":{"value":"

Magical darkness spreads from a point you choose within range to fill a 15-foot-radius sphere for the duration. The darkness spreads around corners. A creature with darkvision can't see through this darkness, and nonmagical light can't illuminate it.

If the point you choose is on an object you are holding or one that isn't being worn or carried, the darkness emanates from the object and moves with it. Completely covering the source of the darkness with an opaque object, such as a bowl or a helm, blocks the darkness.

If any of this spell's area overlaps with an area of light created by a spell of 2nd level or lower, the spell that created the light is dispelled.

","chat":"","unidentified":""},"source":"PHB pg. 230","activation":{"type":"action","cost":1,"condition":""},"duration":{"value":10,"units":"minute"},"target":{"value":15,"width":null,"units":"ft","type":"sphere"},"range":{"value":60,"long":0,"units":"ft"},"uses":{"value":0,"max":"0","per":""},"consume":{"type":"","target":"","amount":null},"ability":"","actionType":"util","attackBonus":0,"chatFlavor":"","critical":{"threshold":null,"damage":null},"damage":{"parts":[],"versatile":"","value":""},"formula":"","save":{"ability":"","dc":null,"scaling":"spell","value":""},"level":2,"school":"evo","components":{"value":"","vocal":true,"somatic":false,"material":true,"ritual":false,"concentration":true},"materials":{"value":"Bat fur and a drop of pitch or piece of coal","consumed":false,"cost":0,"supply":0},"preparation":{"mode":"prepared","prepared":false},"scaling":{"mode":"none","formula":""}},"effects":[{"_id":"G6rYwXfFameLG4DJ","flags":{"dae":{"stackable":"none","macroRepeat":"none","transfer":false,"specialDuration":[],"durationExpression":""},"ActiveAuras":{"isAura":true,"ignoreSelf":false,"hidden":false,"height":false,"alignment":"","type":"","aura":"All","radius":null,"save":"","savedc":null,"hostile":false,"onlyOnce":false,"time":"None","displayTemp":false},"core":{"statusId":""}},"changes":[{"key":"StatusEffect","mode":0,"value":"Convenient Effect: Blinded","priority":"20"}],"disabled":false,"duration":{"startTime":null},"icon":null,"label":"Darkness","tint":null,"transfer":false,"selectedKey":"StatusEffect"}],"folder":null,"sort":0,"permission":{"default":0,"E4BVikjIkVl2lL2j":3},"flags":{"midi-qol":{"onUseMacroName":"[preActiveEffects]ItemMacro","effectActivation":false},"core":{"sourceId":"Compendium.ActiveAuras.Active Auras Auras.LnGJt2o5UoLiXYTa"},"itemacro":{"macro":{"data":{"_id":null,"name":"Darkness","type":"script","author":"jkdToQqzFl0mFXls","img":"icons/svg/dice-target.svg","scope":"global","command":"if (!game.modules.get(\"advanced-macros\")?.active) { ui.notifications.error(\"Advanced Macros is not enabled\"); return }\nif(args[0].tag === \"OnUse\"){\nAAhelpers.applyTemplate(args)\n}","folder":null,"sort":0,"permission":{"default":0},"flags":{}}}},"midiProperties":{"nodam":false,"fulldam":false,"halfdam":false,"rollOther":false,"critOther":false,"magicdam":false,"magiceffect":false,"concentration":false,"toggleEffect":false}}} -{"_id":"O835BAsKUwUSb4QT","name":"Grease","type":"spell","img":"icons/magic/air/fog-gas-smoke-orange.webp","data":{"description":{"value":"

Slick grease covers the ground in a 10-foot square centered on a point within range and turns it into difficult terrain for the duration.

When the grease appears, each creature standing in its area must succeed on a Dexterity saving throw or fall prone. A creature that enters the area or ends its turn there must also succeed on a Dexterity saving throw or fall prone.

","chat":"","unidentified":""},"source":"PHB pg. 246","activation":{"type":"action","cost":1,"condition":""},"duration":{"value":1,"units":"minute"},"target":{"value":10,"width":null,"units":"ft","type":"square"},"range":{"value":60,"long":0,"units":"ft"},"uses":{"value":0,"max":"0","per":""},"consume":{"type":"","target":"","amount":null},"ability":"","actionType":"save","attackBonus":0,"chatFlavor":"","critical":{"threshold":null,"damage":null},"damage":{"parts":[],"versatile":"","value":""},"formula":"","save":{"ability":"dex","dc":20,"scaling":"spell"},"level":1,"school":"con","components":{"value":"","vocal":true,"somatic":true,"material":true,"ritual":false,"concentration":false},"materials":{"value":"A bit of pork rind or butter","consumed":false,"cost":0,"supply":0},"preparation":{"mode":"prepared","prepared":false},"scaling":{"mode":"none","formula":""}},"effects":[{"_id":"56HHrYL8EiufxQmp","flags":{"dae":{"stackable":"none","macroRepeat":"endEveryTurn","transfer":false,"specialDuration":[]},"ActiveAuras":{"isAura":true,"ignoreSelf":false,"hidden":false,"height":false,"alignment":"","type":"","aura":"All","radius":null,"save":"","savedc":null,"hostile":false,"onlyOnce":false,"time":"None","displayTemp":true},"core":{"statusId":""}},"changes":[{"key":"flags.midi-qol.OverTime","mode":0,"value":"turn=start, saveAbility=dex, saveDC=@attributes.spelldc, rollType=save, label=Fall prone on fail","priority":"20"}],"disabled":false,"duration":{"startTime":null},"icon":"systems/dnd5e/icons/spells/fog-orange-1.jpg","label":"Grease","origin":"Actor.LClwU7mAyShYLmFU.OwnedItem.c75EoQVhgTvlVbq1","tint":null,"transfer":false,"selectedKey":"flags.midi-qol.OverTime"}],"folder":null,"sort":0,"permission":{"default":0,"E4BVikjIkVl2lL2j":3},"flags":{"midi-qol":{"onUseMacroName":"[preActiveEffects]ItemMacro","effectActivation":false},"itemacro":{"macro":{"data":{"_id":null,"name":"Grease","type":"script","author":"jkdToQqzFl0mFXls","img":"icons/svg/dice-target.svg","scope":"global","command":"if (!game.modules.get(\"advanced-macros\")?.active) { ui.notifications.error(\"Advanced Macros is not enabled\"); return }\nif(args[0].tag === \"OnUse\"){\nAAhelpers.applyTemplate(args)\n\n}","folder":null,"sort":0,"permission":{"default":0},"flags":{}}}},"core":{"sourceId":"Item.c75EoQVhgTvlVbq1"},"midiProperties":{"nodam":false,"fulldam":false,"halfdam":false,"rollOther":false,"critOther":false,"magicdam":false,"magiceffect":false,"concentration":false,"toggleEffect":false}}} -{"_id":"RW2w0lgE6f6T9aYJ","name":"Incendiary Cloud","type":"spell","img":"icons/magic/air/fog-gas-smoke-swirling-orange.webp","data":{"description":{"value":"

A swirling cloud of smoke shot through with white-hot embers appears in a 20-foot-radius Sphere centered on a point within range. The cloud spreads around corners and is heavily obscured. It lasts for the Duration or until a wind of moderate or greater speed (at least 10 miles per hour) disperses it.

When the cloud appears, each creature in it must make a Dexterity saving throw. A creature takes 10d8 fire damage on a failed save, or half as much damage on a successful one. A creature must also make this saving throw when it enters the spell’s area for the first time on a turn or ends its turn there.

The cloud moves 10 feet directly away from you in a direction that you choose at the start of each of your turns.

","chat":"","unidentified":""},"source":"PHB pg. 253","activation":{"type":"action","cost":1,"condition":""},"duration":{"value":1,"units":"minute"},"target":{"value":20,"width":null,"units":"ft","type":"sphere"},"range":{"value":150,"long":0,"units":"ft"},"uses":{"value":0,"max":"0","per":""},"consume":{"type":"","target":"","amount":null},"ability":"","actionType":"save","attackBonus":0,"chatFlavor":"","critical":{"threshold":null,"damage":null},"damage":{"parts":[],"versatile":""},"formula":"","save":{"ability":"","dc":null,"scaling":"spell"},"level":8,"school":"con","components":{"value":"","vocal":true,"somatic":true,"material":false,"ritual":false,"concentration":true},"materials":{"value":"","consumed":false,"cost":0,"supply":0},"preparation":{"mode":"prepared","prepared":false},"scaling":{"mode":"none","formula":""}},"effects":[{"_id":"R631jf30058faRaj","flags":{"dae":{"stackable":"none","macroRepeat":"endEveryTurn","transfer":false,"specialDuration":[]},"ActiveAuras":{"isAura":true,"ignoreSelf":false,"hidden":false,"height":false,"alignment":"","type":"","aura":"All","radius":null,"save":"","savedc":null,"hostile":true,"onlyOnce":true,"time":"None","displayTemp":true},"core":{"statusId":""}},"changes":[{"key":"flags.midi-qol.OverTime","mode":0,"value":"turn=end, saveAbility=dex, saveDC=@attributes.spelldc, saveDamage=halfdamage, rollType=save, saveMagic=true, damageBeforeSave=false, damageRoll=10d8, damageType=fire","priority":"20"}],"disabled":false,"duration":{"startTime":null},"icon":"systems/dnd5e/icons/spells/fog-orange-3.jpg","label":"Incendiary Cloud","tint":null,"transfer":false,"selectedKey":"flags.midi-qol.OverTime"}],"folder":null,"sort":0,"permission":{"default":0,"E4BVikjIkVl2lL2j":3},"flags":{"midi-qol":{"onUseMacroName":"[preActiveEffects]ItemMacro","effectActivation":false},"core":{"sourceId":"Compendium.ActiveAuras.Active Auras Auras.RW2w0lgE6f6T9aYJ"},"itemacro":{"macro":{"data":{"_id":null,"name":"Incendiary Cloud","type":"script","author":"jkdToQqzFl0mFXls","img":"icons/svg/dice-target.svg","scope":"global","command":"if (!game.modules.get(\"advanced-macros\")?.active) { ui.notifications.error(\"Advanced Macros is not enabled\"); return }\nif(args[0].tag === \"OnUse\"){\nAAhelpers.applyTemplate(args)\n}","folder":null,"sort":0,"permission":{"default":0},"flags":{}}}},"midiProperties":{"nodam":false,"fulldam":false,"halfdam":false,"rollOther":false,"critOther":false,"magicdam":false,"magiceffect":false,"concentration":false,"toggleEffect":false}}} -{"_id":"RXACq8vCYUuSRpjc","name":"Spike Growth","type":"spell","img":"icons/magic/nature/vines-thorned-curled-glow-green.webp","data":{"description":{"value":"

The ground in a 20-foot radius centered on a point within range twists and sprouts hard spikes and thorns. The area becomes difficult terrain for the Duration. When a creature moves into or within the area, it takes 2d4 piercing damage for every 5 feet it travels.

The transformation of the ground is camouflaged to look natural. Any creature that can’t see the area at the time the spell is cast must make a Wisdom (Perception) check against your spell save DC to recognize the terrain as hazardous before entering it.

","chat":"","unidentified":""},"source":"PHB pg. 277","activation":{"type":"action","cost":1,"condition":""},"duration":{"value":10,"units":"minute"},"target":{"value":20,"width":null,"units":"ft","type":"radius"},"range":{"value":150,"long":0,"units":"ft"},"uses":{"value":0,"max":"0","per":""},"consume":{"type":"","target":"","amount":null},"ability":"","actionType":"other","attackBonus":0,"chatFlavor":"","critical":{"threshold":null,"damage":null},"damage":{"parts":[],"versatile":""},"formula":"","save":{"ability":"","dc":null,"scaling":"spell","value":""},"level":2,"school":"trs","components":{"value":"","vocal":true,"somatic":true,"material":true,"ritual":false,"concentration":true},"materials":{"value":"Seven sharp thorns or seven small twigs, each sharpened to a point","consumed":false,"cost":0,"supply":0},"preparation":{"mode":"prepared","prepared":false},"scaling":{"mode":"none","formula":""}},"effects":[{"_id":"f1Ab0wnqRPfoEsf1","flags":{"dae":{"stackable":false,"macroRepeat":"none","transfer":false,"specialDuration":["isDamaged"]},"ActiveAuras":{"isAura":true,"ignoreSelf":false,"hidden":false,"height":false,"alignment":"","type":"","aura":"All","radius":null,"save":"","savedc":null,"hostile":true,"onlyOnce":false,"time":"isDamaged"}},"changes":[{"key":"macro.itemMacro","mode":0,"value":"","priority":"20"}],"disabled":false,"duration":{"startTime":null},"icon":"systems/dnd5e/icons/spells/vines-acid-2.jpg","label":"Spike Growth","origin":"Actor.LClwU7mAyShYLmFU.OwnedItem.nkArQrnBQu4uN92X","tint":null,"transfer":false,"selectedKey":"macro.itemMacro"}],"folder":null,"sort":0,"permission":{"default":0,"E4BVikjIkVl2lL2j":3},"flags":{"midi-qol":{"onUseMacroName":"[preActiveEffects]ItemMacro","effectActivation":false},"core":{"sourceId":"Item.nkArQrnBQu4uN92X"},"itemacro":{"macro":{"data":{"_id":null,"name":"Spike Growth","type":"script","author":"jkdToQqzFl0mFXls","img":"icons/svg/dice-target.svg","scope":"global","command":"if (!game.modules.get(\"advanced-macros\")?.active) { ui.notifications.error(\"Advanced Macros is not enabled\"); return }\nif(args[0].tag === \"OnUse\"){\nAAhelpers.applyTemplate(args) }\nif (args[0] === \"on\" || args[0] === \"each\") {\n const lastArg = args[args.length - 1];\n let tactor;\n if (lastArg.tokenId) tactor = canvas.tokens.get(lastArg.tokenId).actor;\n else tactor = game.actors.get(lastArg.actorId);\n const target = canvas.tokens.get(lastArg.tokenId)\n let damageRoll = new Roll(`2d4[piercing]`).evaluate()\n damageRoll.toMessage({ flavor: \"Spike Growth Damage\" })\n let targets = new Set();\n let saves = new Set();\n targets.add(target);\n saves.add(target);\n await MidiQOL.applyTokenDamage([{ damage: damageRoll.total, type: \"piercing\" }], damageRoll.total, targets, null, saves);\n let effect = tactor.effects.find(i => i.data.label === \"Spike Growth\")\n await effect.delete()\n}","folder":null,"sort":0,"permission":{"default":0},"flags":{}}}},"midiProperties":{"nodam":false,"fulldam":false,"halfdam":false,"rollOther":false,"critOther":false,"magicdam":false,"magiceffect":false,"concentration":false,"toggleEffect":false}}} -{"_id":"SGwciGPjSKSM1PZ8","name":"Insect Plague","type":"spell","img":"icons/magic/nature/leaf-glow-maple-teal.webp","data":{"description":{"value":"

Swarming, biting locusts fill a 20-foot-radius Sphere centered on a point you choose within range. The sph⁠ere spreads around corners. The sphe⁠re remains for the Duration, and its area is lightly obscured. The sphere’s area is difficult terrain.

When the area appears, each creature in it must make a Constitution saving throw. A creature takes 4d10 piercing damage on a failed save, or half as much damage on a successful one. A creature must also make this saving throw when it enters the spell’s area for the first time on a turn or ends its turn there.

At Higher Levels. When you cast this spell using a spell slot of 6th level or higher, the damage increases by 1d10 for each slot level above 5th.

","chat":"","unidentified":""},"source":"PHB pg. 254","activation":{"type":"action","cost":1,"condition":""},"duration":{"value":10,"units":"minute"},"target":{"value":20,"width":null,"units":"ft","type":"sphere"},"range":{"value":300,"long":0,"units":"ft"},"uses":{"value":0,"max":"0","per":""},"consume":{"type":"","target":"","amount":null},"ability":"","actionType":"save","attackBonus":0,"chatFlavor":"","critical":{"threshold":null,"damage":null},"damage":{"parts":[],"versatile":""},"formula":"","save":{"ability":"","dc":null,"scaling":"spell"},"level":5,"school":"con","components":{"value":"","vocal":true,"somatic":true,"material":true,"ritual":false,"concentration":true},"materials":{"value":"A few grains of sugar, some kernels of grain, and a smear of fat","consumed":false,"cost":0,"supply":0},"preparation":{"mode":"prepared","prepared":false},"scaling":{"mode":"level","formula":"1d10"}},"effects":[{"_id":"V4qfaLuoUbtFQoLu","flags":{"dae":{"stackable":"none","macroRepeat":"endEveryTurn","transfer":false,"specialDuration":[]},"ActiveAuras":{"isAura":true,"ignoreSelf":false,"hidden":false,"height":false,"alignment":"","type":"","aura":"All","radius":null,"save":"","savedc":null,"hostile":true,"onlyOnce":true,"time":"None","displayTemp":true},"core":{"statusId":""}},"changes":[{"key":"flags.midi-qol.OverTime","mode":0,"value":"turn=end, saveAbility=con, saveDC=@attributes.spelldc, saveDamage=halfdamage, rollType=save, saveMagic=true, damageBeforeSave=false, damageRoll=(@item.level-1)d8, damageType=piercing","priority":"20"}],"disabled":false,"duration":{"startTime":null},"icon":"systems/dnd5e/icons/spells/leaf-jade-3.jpg","label":"Insect Plague","tint":null,"transfer":false,"selectedKey":"flags.midi-qol.OverTime"}],"folder":null,"sort":0,"permission":{"default":0,"E4BVikjIkVl2lL2j":3},"flags":{"midi-qol":{"onUseMacroName":"[preActiveEffects]ItemMacro","effectActivation":false},"core":{"sourceId":"Compendium.ActiveAuras.Active Auras Auras.SGwciGPjSKSM1PZ8"},"itemacro":{"macro":{"data":{"_id":null,"name":"Insect Plague","type":"script","author":"jkdToQqzFl0mFXls","img":"icons/svg/dice-target.svg","scope":"global","command":"if (!game.modules.get(\"advanced-macros\")?.active) { ui.notifications.error(\"Advanced Macros is not enabled\"); return }\nif(args[0].tag === \"OnUse\"){\nAAhelpers.applyTemplate(args)\n}","folder":null,"sort":0,"permission":{"default":0},"flags":{}}}},"midiProperties":{"nodam":false,"fulldam":false,"halfdam":false,"rollOther":false,"critOther":false,"magicdam":false,"magiceffect":false,"concentration":false,"toggleEffect":false}}} -{"_id":"TDLGLgoS3gShIzbp","name":"Spirit Guardians","type":"spell","img":"icons/magic/light/projectile-bolts-salvo-white.webp","data":{"description":{"value":"

You call forth spirits to protect you. They flit around you to a distance of 15 feet for the duration. If you are good or neutral, their spectral form appears angelic or fey (your choice). If you are evil, they appear fiendish.

When you cast this spell, you can designate any number of creatures you can see to be unaffected by it. An affected creature's speed is halved in the area, and when the creature enters the area for the first time on a turn or starts its turn there, it must make a Wisdom saving throw. On a failed save, the creature takes 3d8 radiant damage (if you are good or neutral) or 3d8 necrotic damage (if you are evil). On a successful save, the creature takes half as much damage.

At Higher Levels. When you cast this spell using a spell slot of 4th level or higher, the damage increases by 1d8 for each slot level above 3rd.

","chat":"","unidentified":""},"source":"PHB pg. 278","activation":{"type":"action","cost":1,"condition":""},"duration":{"value":10,"units":"minute"},"target":{"value":null,"width":null,"units":"","type":"self"},"range":{"value":null,"long":null,"units":"self"},"uses":{"value":0,"max":"0","per":""},"consume":{"type":"","target":"","amount":null},"ability":"","actionType":"save","attackBonus":0,"chatFlavor":"","critical":{"threshold":null,"damage":null},"damage":{"parts":[],"versatile":""},"formula":"","save":{"ability":"","dc":null,"scaling":"spell"},"level":3,"school":"con","components":{"value":"","vocal":true,"somatic":true,"material":true,"ritual":false,"concentration":true},"materials":{"value":"A holy symbol","consumed":false,"cost":0,"supply":0},"preparation":{"mode":"prepared","prepared":false},"scaling":{"mode":"level","formula":"1d8"}},"effects":[{"_id":"hUhljQolEu7to9m5","flags":{"dae":{"stackable":"none","macroRepeat":"startEveryTurn","transfer":false,"specialDuration":[]},"ActiveAuras":{"isAura":true,"ignoreSelf":true,"hidden":false,"height":false,"alignment":"","type":"","aura":"Enemy","radius":15,"save":"","savedc":null,"hostile":false,"onlyOnce":true,"time":"None","displayTemp":false},"core":{"statusId":""}},"changes":[{"key":"flags.midi-qol.OverTime","mode":0,"value":"turn=start, saveAbility=wis, saveDC=@attributes.spelldc, saveDamage=halfdamage, rollType=save, saveMagic=true, damageBeforeSave=false, damageRoll=(@item.level)d8, damageType=radiant","priority":"0"},{"key":"data.attributes.movement.all","mode":0,"value":"*0.5","priority":"20"}],"disabled":false,"duration":{"startTime":null},"icon":"systems/dnd5e/icons/spells/needles-sky-2.jpg","label":"Spirit Guardians","tint":null,"transfer":false,"selectedKey":["flags.midi-qol.OverTime","data.attributes.movement.all"]}],"folder":null,"sort":0,"permission":{"default":0,"E4BVikjIkVl2lL2j":3},"flags":{"midi-qol":{"onUseMacroName":"[preActiveEffects]ItemMacro","effectActivation":false,"forceCEOn":false},"itemacro":{"macro":{"data":{"_id":null,"name":"Spirit Guardians","type":"script","author":"jkdToQqzFl0mFXls","img":"icons/svg/dice-target.svg","scope":"global","command":"if (!game.modules.get(\"advanced-macros\")?.active) { ui.notifications.error(\"Advanced Macros is not enabled\"); return }\nif(args[0].tag === \"OnUse\"){\nAAhelpers.applyTemplate(args) }","folder":null,"sort":0,"permission":{"default":0},"flags":{}}}},"core":{"sourceId":"Compendium.ActiveAuras.Active Auras Auras.TDLGLgoS3gShIzbp"},"midiProperties":{"nodam":false,"fulldam":false,"halfdam":false,"rollOther":false,"critOther":false,"magicdam":false,"magiceffect":false,"concentration":false,"toggleEffect":false}}} -{"_id":"UmRAOUrM8itCMzmj","name":"Sleet Storm","type":"spell","img":"icons/magic/water/projectile-beams-salvo-blue.webp","data":{"description":{"value":"

Until the spell ends, freezing rain and sleet fall in a 20-foot-tall cylinder with a 40-foot radius centered on a point you choose within range. The area is heavily obscured, and exposed flames in the area are doused.

The ground in the area is covered with slick ice, making it difficult terrain. When a creature enters the spell's area for the first time on a turn or starts its turn there, it must make a Dexterity saving throw. On a failed save, it falls Prone.

If a creature is concentrating in the spell's area, the creature must make a successful Constitution saving throw against your spell save DC or lose concentration.

","chat":"","unidentified":""},"source":"PHB pg. 276","activation":{"type":"action","cost":1,"condition":""},"duration":{"value":1,"units":"minute"},"target":{"value":40,"width":null,"units":"ft","type":"cylinder"},"range":{"value":150,"long":0,"units":"ft"},"uses":{"value":0,"max":"0","per":""},"consume":{"type":"","target":"","amount":null},"ability":"","actionType":"other","attackBonus":0,"chatFlavor":"","critical":{"threshold":null,"damage":null},"damage":{"parts":[],"versatile":"","value":""},"formula":"","save":{"ability":"","dc":null,"scaling":"spell"},"level":3,"school":"con","components":{"value":"","vocal":true,"somatic":true,"material":true,"ritual":false,"concentration":true},"materials":{"value":"A pinch of dust and a few drops of water.","consumed":false,"cost":0,"supply":0},"preparation":{"mode":"prepared","prepared":false},"scaling":{"mode":"none","formula":""}},"effects":[{"_id":"UPkDhbLfoplgjQNd","flags":{"dae":{"stackable":"none","macroRepeat":"startEveryTurn","transfer":false,"specialDuration":[]},"ActiveAuras":{"isAura":true,"ignoreSelf":false,"hidden":false,"height":false,"alignment":"","type":"","aura":"All","radius":null,"save":"","savedc":null,"hostile":true,"onlyOnce":true,"time":"None","displayTemp":false},"core":{"statusId":""}},"changes":[{"key":"flags.midi-qol.OverTime","mode":0,"value":"turn=start, saveAbility=dex, saveDC=@attributes.spelldc, rollType=save, label=Fall prone on fail","priority":"20"}],"disabled":false,"duration":{"startTime":null},"icon":null,"label":"Sleet Storm","origin":"Actor.LClwU7mAyShYLmFU.OwnedItem.DMIfX1LiTZvWgY82","tint":null,"transfer":false,"selectedKey":"flags.midi-qol.OverTime"}],"folder":null,"sort":0,"permission":{"default":0,"E4BVikjIkVl2lL2j":3},"flags":{"midi-qol":{"onUseMacroName":"[preActiveEffects]ItemMacro","effectActivation":false},"core":{"sourceId":"Item.DMIfX1LiTZvWgY82"},"itemacro":{"macro":{"data":{"_id":null,"name":"Sleet Storm","type":"script","author":"jkdToQqzFl0mFXls","img":"icons/svg/dice-target.svg","scope":"global","command":"if (!game.modules.get(\"advanced-macros\")?.active) { ui.notifications.error(\"Advanced Macros is not enabled\"); return }\nif(args[0].tag === \"OnUse\"){\nAAhelpers.applyTemplate(args) }","folder":null,"sort":0,"permission":{"default":0},"flags":{}}}},"midiProperties":{"nodam":false,"fulldam":false,"halfdam":false,"rollOther":false,"critOther":false,"magicdam":false,"magiceffect":false,"concentration":false,"toggleEffect":false}}} -{"_id":"ao9RNllMa6erTIeG","name":"Stinking Cloud","type":"spell","img":"icons/magic/air/fog-gas-smoke-dense-green.webp","data":{"description":{"value":"

You create a 20-foot-radius sphere of yellow, nauseating gas centered on a point within range. The cloud spreads around corners, and its area is heavily obscured. The cloud lingers in the air for the duration.

Each creature that is completely within the cloud at the start of its turn must make a Constitution saving throw against poison. On a failed save, the creature spends its action that turn retching and reeling. Creatures that don't need to breathe or are immune to poison automatically succeed on this saving throw.

A moderate wind (at least 10 miles per hour) disperses the cloud after 4 rounds. A strong wind (at least 20 miles per hour) disperses it after 1 round.

","chat":"","unidentified":""},"source":"PHB pg. 278","activation":{"type":"action","cost":1,"condition":""},"duration":{"value":1,"units":"minute"},"target":{"value":20,"width":null,"units":"ft","type":"sphere"},"range":{"value":90,"long":0,"units":"ft"},"uses":{"value":0,"max":"0","per":""},"consume":{"type":"","target":"","amount":null},"ability":"","actionType":"save","attackBonus":0,"chatFlavor":"","critical":{"threshold":null,"damage":null},"damage":{"parts":[],"versatile":"","value":""},"formula":"","save":{"ability":"","dc":null,"scaling":"spell"},"level":3,"school":"con","components":{"value":"","vocal":true,"somatic":true,"material":true,"ritual":false,"concentration":true},"materials":{"value":"A rotten egg or several skunk cabbage leaves","consumed":false,"cost":0,"supply":0},"preparation":{"mode":"prepared","prepared":false},"scaling":{"mode":"none","formula":""}},"effects":[{"_id":"frtB4d41h4ZQehRG","flags":{"dae":{"stackable":"none","macroRepeat":"startEveryTurn","transfer":false,"specialDuration":[]},"ActiveAuras":{"isAura":false,"ignoreSelf":false,"hidden":false,"height":false,"alignment":"","type":"","aura":"None","radius":null,"save":"","savedc":null,"hostile":false,"onlyOnce":false,"time":"None","displayTemp":false},"core":{"statusId":""}},"changes":[{"key":"flags.midi-qol.OverTime","mode":0,"value":"turn=start, saveAbility=con, saveDC=@attributes.spelldc, label=On a failed save, the creature spends its action that turn retching and reeling. Creatures that don't need to breathe or are immune to poison automatically succeed on this saving throw.","priority":"20"}],"disabled":false,"duration":{"startTime":null},"icon":"systems/dnd5e/icons/spells/fog-acid-2.jpg","label":"Stinking Cloud","tint":null,"transfer":false,"selectedKey":"flags.midi-qol.OverTime"}],"folder":null,"sort":0,"permission":{"default":0,"E4BVikjIkVl2lL2j":3},"flags":{"midi-qol":{"onUseMacroName":"[preActiveEffects]ItemMacro","effectActivation":false},"core":{"sourceId":"Compendium.ActiveAuras.Active Auras Auras.ao9RNllMa6erTIeG"},"itemacro":{"macro":{"data":{"_id":null,"name":"Stinking Cloud","type":"script","author":"jkdToQqzFl0mFXls","img":"icons/svg/dice-target.svg","scope":"global","command":"if (!game.modules.get(\"advanced-macros\")?.active) { ui.notifications.error(\"Advanced Macros is not enabled\"); return }\nif(args[0].tag === \"OnUse\"){\nAAhelpers.applyTemplate(args) }","folder":null,"sort":0,"permission":{"default":0},"flags":{}}}},"midiProperties":{"nodam":false,"fulldam":false,"halfdam":false,"rollOther":false,"critOther":false,"magicdam":false,"magiceffect":false,"concentration":false,"toggleEffect":false}}} -{"_id":"jNVxMgavVtbIZqFy","name":"Silence","type":"spell","img":"icons/magic/symbols/runes-triangle-magenta.webp","data":{"description":{"value":"

For the Duration, no sound can be created within or pass through a 20-foot-radius s⁠phere centered on a point you choose within range. Any creature or object entirely inside the s⁠phere is immune to thunder damage, and creatures are Deafened while entirely inside it. Casting a Spell that includes a verbal component is impossible there.

","chat":"","unidentified":""},"source":"PHB pg. 275","activation":{"type":"action","cost":1,"condition":""},"duration":{"value":10,"units":"minute"},"target":{"value":20,"width":null,"units":"ft","type":"sphere"},"range":{"value":120,"long":0,"units":"ft"},"uses":{"value":0,"max":"0","per":""},"consume":{"type":"","target":"","amount":null},"ability":"","actionType":"util","attackBonus":0,"chatFlavor":"","critical":{"threshold":null,"damage":null},"damage":{"parts":[],"versatile":"","value":""},"formula":"","save":{"ability":"","dc":null,"scaling":"spell","value":""},"level":2,"school":"ill","components":{"value":"","vocal":true,"somatic":true,"material":false,"ritual":true,"concentration":true},"materials":{"value":"","consumed":false,"cost":0,"supply":0},"preparation":{"mode":"prepared","prepared":false},"scaling":{"mode":"none","formula":""}},"effects":[{"_id":"4gFVHoupB272LFGm","flags":{"dae":{"stackable":"none","macroRepeat":"none","transfer":false,"specialDuration":[],"durationExpression":""},"ActiveAuras":{"isAura":true,"ignoreSelf":false,"hidden":false,"height":false,"alignment":"","type":"","aura":"All","radius":null,"save":"","savedc":null,"hostile":false,"onlyOnce":false,"time":"None","displayTemp":true},"core":{"statusId":""}},"changes":[{"key":"","mode":0,"value":"Convenient Effect: Blinded","priority":"20"}],"disabled":false,"duration":{"startTime":null},"icon":"icons/magic/symbols/runes-triangle-magenta.webp","label":"Silence","tint":null,"transfer":false,"selectedKey":"StatusEffect"}],"folder":null,"sort":0,"permission":{"default":0,"E4BVikjIkVl2lL2j":3},"flags":{"midi-qol":{"onUseMacroName":"[preActiveEffects]ItemMacro","effectActivation":false},"core":{"sourceId":"Compendium.ActiveAuras.Active Auras Auras.jNVxMgavVtbIZqFy"},"itemacro":{"macro":{"data":{"_id":null,"name":"Silence","type":"script","author":"jkdToQqzFl0mFXls","img":"icons/svg/dice-target.svg","scope":"global","command":"if (!game.modules.get(\"advanced-macros\")?.active) { ui.notifications.error(\"Advanced Macros is not enabled\"); return }\nif(args[0].tag === \"OnUse\"){\nAAhelpers.applyTemplate(args) }","folder":null,"sort":0,"permission":{"default":0},"flags":{}}}},"midiProperties":{"nodam":false,"fulldam":false,"halfdam":false,"rollOther":false,"critOther":false,"magicdam":false,"magiceffect":false,"concentration":false,"toggleEffect":false}}} -{"_id":"jmFWbZtk4k12Is9y","name":"Aura of Courage","type":"feat","img":"icons/magic/light/explosion-beam-impact-silhouette.webp","data":{"description":{"value":"

Starting at 10th level, you and friendly creatures within 10 feet of you can't be frightened while you are conscious.

At 18th level, the range of this aura increases to 30 feet.

","chat":"","unidentified":""},"source":"PHB pg. 85","activation":{"type":"","cost":0,"condition":""},"duration":{"value":null,"units":""},"target":{"value":null,"width":null,"units":null,"type":"self"},"range":{"value":10,"long":30,"units":"ft"},"uses":{"value":0,"max":0,"per":"","type":""},"consume":{"type":"","target":null,"amount":null},"ability":"","actionType":"","attackBonus":0,"chatFlavor":"","critical":{"threshold":null,"damage":null},"damage":{"parts":[],"versatile":"","value":""},"formula":"","save":{"ability":"","dc":null,"scaling":"spell","value":""},"requirements":"Paladin 10","recharge":{"value":null,"charged":false}},"effects":[{"_id":"jqyEv4x6Yz5NcW9F","flags":{"ActiveAuras":{"isAura":true,"inactive":false,"hidden":false,"aura":"Allies","radius":10},"dae":{"stackable":false,"specialDuration":"None","transfer":true}},"changes":[{"key":"data.traits.ci.value","value":"frightened","mode":2,"priority":20}],"disabled":false,"duration":{"startTime":null},"icon":"systems/dnd5e/icons/skills/yellow_13.jpg","label":"Aura of Courage","tint":null,"transfer":true}],"folder":null,"sort":0,"permission":{"default":0,"E4BVikjIkVl2lL2j":3},"flags":{"dae":{"activeEquipped":false,"alwaysActive":true},"core":{"sourceId":"Compendium.ActiveAuras.Active Auras Auras.jmFWbZtk4k12Is9y"}}} -{"_id":"wRjkVOup7yqk2CKU","name":"Aura of Protection","type":"feat","img":"icons/magic/air/wind-tornado-wall-blue.webp","data":{"description":{"value":"

Starting at 6th level, whenever you or a friendly creature within 10 feet of you must make a saving throw, the creature gains a bonus to the saving throw equal to your Charisma modifier (with a minimum bonus of +1). You must be conscious to grant this bonus.

 At 18th level, the range of this aura increases to 30 feet.

","chat":"","unidentified":""},"source":"PHB pg. 85","activation":{"type":"","cost":0,"condition":""},"duration":{"value":null,"units":""},"target":{"value":null,"width":null,"units":null,"type":"self"},"range":{"value":10,"long":30,"units":"ft"},"uses":{"value":0,"max":0,"per":"","type":""},"consume":{"type":"","target":null,"amount":null},"ability":"","actionType":"","attackBonus":0,"chatFlavor":"","critical":{"threshold":null,"damage":null},"damage":{"parts":[],"versatile":"","value":""},"formula":"","save":{"ability":"","dc":null,"scaling":"spell","value":""},"requirements":"Paladin 6","recharge":{"value":null,"charged":false}},"effects":[{"_id":"YA6d5MuIdX6Bkc8O","flags":{"ActiveAuras":{"aura":"Allies","radius":10,"isAura":true,"inactive":false,"hidden":false,"ignoreSelf":false,"height":false,"alignment":"","type":"","save":"","savedc":null,"hostile":false,"onlyOnce":false,"time":"None","displayTemp":false},"dae":{"stackable":"none","specialDuration":["None"],"transfer":true,"macroRepeat":"none","durationExpression":""},"core":{"statusId":""}},"changes":[{"key":"data.bonuses.abilities.save","mode":2,"value":"+@abilities.cha.mod","priority":"20"}],"disabled":false,"duration":{"startTime":null},"icon":"systems/dnd5e/icons/skills/blue_13.jpg","label":"Aura of Protection","origin":"Item.8csQOq23Ht2m47Zg","tint":"","transfer":true,"selectedKey":"data.bonuses.abilities.save"}],"folder":null,"sort":0,"permission":{"default":0,"jkdToQqzFl0mFXls":3},"flags":{"dae":{"activeEquipped":false,"alwaysActive":true},"core":{"sourceId":"Compendium.ActiveAuras.Active Auras Auras.gDPFeakSXy0DgGhd"}}} +{"_id":"1YbUFxCDhleZBlqH","name":"Black Tentacles","type":"spell","img":"icons/magic/nature/vines-thorned-curled-glow-teal-purple.webp","effects":[{"_id":"ovktWr3CsAFw82Nd","flags":{"dae":{"stackable":"none","macroRepeat":"startEveryTurn","transfer":false,"specialDuration":[]},"ActiveAuras":{"isAura":true,"ignoreSelf":false,"hidden":false,"height":false,"alignment":"","type":"","aura":"All","radius":null,"save":"","savedc":null,"hostile":true,"onlyOnce":true,"time":"None","displayTemp":false},"core":{"statusId":""}},"changes":[{"key":"flags.midi-qol.OverTime","mode":0,"value":"turn=start, saveAbility=con, saveDC=@attributes.spelldc, saveDamage=noDamage, rollType=save, saveMagic=true, damageBeforeSave=false, damageRoll=3d6, damageType=bludgeoning","priority":20}],"disabled":false,"duration":{"startTime":null,"seconds":null,"combat":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"icons/magic/nature/vines-thorned-curled-glow-teal-purple.webp","label":"Black Tentacles","tint":null,"transfer":false,"origin":null}],"folder":null,"sort":0,"flags":{"midi-qol":{"onUseMacroName":"[preActiveEffects]ItemMacro","effectActivation":false,"forceCEOn":false},"core":{"sourceId":"Compendium.ActiveAuras.Active Auras Auras.1YbUFxCDhleZBlqH"},"itemacro":{"macro":{"data":{"_id":null,"name":"Black Tentacles","type":"script","author":"jkdToQqzFl0mFXls","img":"icons/svg/dice-target.svg","scope":"global","command":"if (!game.modules.get(\"advanced-macros\")?.active) { ui.notifications.error(\"Advanced Macros is not enabled\"); return }\nif(args[0].tag === \"OnUse\"){AAhelpers.applyTemplate(args)\n}","folder":null,"sort":0,"permission":{"default":0},"flags":{}}}},"midiProperties":{"nodam":false,"fulldam":false,"halfdam":false,"rollOther":false,"critOther":false,"magicdam":false,"magiceffect":false,"concentration":false,"toggleEffect":false}},"system":{"description":{"value":"

Squirming, ebony tentacles fill a 20-foot square on ground that you can see within range. For the Duration, these tentacles turn the ground in the area into difficult terrain.

\n

When a creature enters the affected area for the first time on a turn or starts its turn there, the creature must succeed on a Dexterity saving throw or take 3d6 bludgeoning damage and be @Compendium[dnd5e.rules.QRKWz3p6v9Rl1Tzh]{Restrained} by the tentacles until the spell ends. A creature that starts its turn in the area and is already @Compendium[dnd5e.rules.QRKWz3p6v9Rl1Tzh]{Restrained} by the tentacles takes 3d6 bludgeoning damage.

\n

A creature @Compendium[dnd5e.rules.QRKWz3p6v9Rl1Tzh]{Restrained} by the tentacles can use its action to make a Strength or D⁠exterity check (its choice) against your spell save DC. On a success, it frees itself.

","chat":"","unidentified":""},"source":"","activation":{"type":"action","cost":1,"condition":""},"duration":{"value":1,"units":"minute"},"target":{"value":20,"width":null,"units":"ft","type":"square"},"range":{"value":90,"long":0,"units":"ft"},"uses":{"value":0,"max":"0","per":""},"consume":{"type":"","target":"","amount":null},"ability":"","actionType":"save","attackBonus":0,"chatFlavor":"","critical":{"threshold":null,"damage":null},"damage":{"parts":[],"versatile":""},"formula":"","save":{"ability":"","dc":null,"scaling":"spell"},"level":4,"school":"con","components":{"value":"","vocal":true,"somatic":true,"material":true,"ritual":false,"concentration":true},"materials":{"value":"A piece of tentacle from a giant octopus or a giant squid","consumed":false,"cost":0,"supply":0},"preparation":{"mode":"prepared","prepared":false},"scaling":{"mode":"none","formula":""}},"ownership":{"default":0,"E4BVikjIkVl2lL2j":3},"_stats":{"systemId":"dnd5e","systemVersion":"2.0.1","coreVersion":"10.283","createdTime":1662274929942,"modifiedTime":1662274930018,"lastModifiedBy":"GrX6tFOozYFBnDHq"}} +{"_id":"8dL7Y3BMCV0L4Zjq","name":"Cloudkill","type":"spell","img":"icons/magic/air/fog-gas-smoke-swirling-green.webp","effects":[{"_id":"Q0lPH0mCHeIxs0TH","flags":{"dae":{"stackable":"none","macroRepeat":"startEveryTurn","transfer":false,"specialDuration":[]},"ActiveAuras":{"isAura":true,"ignoreSelf":false,"hidden":false,"height":false,"alignment":"","type":"","aura":"All","radius":null,"save":"","savedc":null,"hostile":false,"onlyOnce":true,"time":"None","displayTemp":true},"core":{"statusId":""}},"changes":[{"key":"flags.midi-qol.OverTime","mode":2,"value":"turn=start, saveAbility=con, saveDC=@attributes.spelldc, saveDamage=halfdamage, rollType=save, saveMagic=true, damageBeforeSave=false, damageRoll=(@item.level)d8, damageType=poison","priority":20}],"disabled":false,"duration":{"startTime":null,"seconds":null,"combat":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"icons/magic/air/fog-gas-smoke-swirling-green.webp","label":"Cloudkill","tint":null,"transfer":false,"origin":null}],"folder":null,"sort":0,"flags":{"midi-qol":{"onUseMacroName":"[postActiveEffects]ItemMacro","effectActivation":false},"core":{"sourceId":"Compendium.ActiveAuras.Active Auras Auras.8dL7Y3BMCV0L4Zjq"},"itemacro":{"macro":{"data":{"_id":null,"name":"Cloudkill","type":"script","author":"jkdToQqzFl0mFXls","img":"icons/svg/dice-target.svg","scope":"global","command":"if (!game.modules.get(\"advanced-macros\")?.active) { ui.notifications.error(\"Advanced Macros is not enabled\"); return }\nif(args[0].tag === \"OnUse\"){\nAAhelpers.applyTemplate(args)\n}","folder":null,"sort":0,"permission":{"default":0},"flags":{}}}},"midiProperties":{"nodam":false,"fulldam":false,"halfdam":false,"rollOther":false,"critOther":false,"magicdam":false,"magiceffect":false,"concentration":false,"toggleEffect":false}},"system":{"description":{"value":"

You create a 20-foot-radius sphere of poisonous, yellow-green fog centered on a point you choose within range. The fog spreads around corners. It lasts for the duration or until strong wind disperses the fog, ending the spell. Its area is heavily obscured.

When a creature enters the spell's area for the first time on a turn or starts its turn there, that creature must make a Constitution saving throw. The creature takes 5d8 poison damage on a failed save, or half as much damage on a successful one. Creatures are affected even if they hold their breath or don't need to breathe.

The fog moves 10 feet away from you at the start of each of your turns, rolling along the surface of the ground. The vapors, being heavier than air, sink to the lowest level of the land, even pouring down openings.

Higher Levels. When you cast this spell using a spell slot of 6th level or higher, the damage increases by 1d8 for each slot level above 5th.

","chat":"","unidentified":""},"source":"PHB pg. 222","activation":{"type":"action","cost":1,"condition":""},"duration":{"value":10,"units":"minute"},"target":{"value":20,"width":null,"units":"ft","type":"sphere"},"range":{"value":120,"long":0,"units":"ft"},"uses":{"value":0,"max":"0","per":""},"consume":{"type":"","target":"","amount":null},"ability":"","actionType":"other","attackBonus":0,"chatFlavor":"","critical":{"threshold":null,"damage":null},"damage":{"parts":[],"versatile":""},"formula":"","save":{"ability":"","dc":null,"scaling":"spell"},"level":5,"school":"con","components":{"value":"","vocal":true,"somatic":true,"material":false,"ritual":false,"concentration":true},"materials":{"value":"","consumed":false,"cost":0,"supply":0},"preparation":{"mode":"prepared","prepared":false},"scaling":{"mode":"level","formula":"1d8"}},"ownership":{"default":0,"E4BVikjIkVl2lL2j":3},"_stats":{"systemId":"dnd5e","systemVersion":"2.0.1","coreVersion":"10.283","createdTime":1662274929945,"modifiedTime":1662274930025,"lastModifiedBy":"GrX6tFOozYFBnDHq"}} +{"_id":"LFB03nEpH36szQ89","name":"Moonbeam","type":"spell","img":"icons/magic/light/beam-rays-blue-large.webp","effects":[{"_id":"ZaAwlOQfLgwyYiOn","flags":{"dae":{"stackable":"none","macroRepeat":"startEveryTurn","transfer":false,"specialDuration":[]},"ActiveAuras":{"isAura":true,"ignoreSelf":false,"hidden":false,"height":false,"alignment":"","type":"","aura":"All","radius":null,"save":"","savedc":null,"hostile":true,"onlyOnce":true,"time":"None","displayTemp":true},"core":{"statusId":""}},"changes":[{"key":"flags.midi-qol.OverTime","mode":0,"value":"turn=start, saveAbility=con, saveDC=@attributes.spelldc, saveDamage=halfdamage, rollType=save, saveMagic=true, damageBeforeSave=false, damageRoll=(@item.level)d10, damageType=radiant","priority":20}],"disabled":false,"duration":{"startTime":null,"seconds":null,"combat":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"icons/magic/light/beam-rays-blue-large.webp","label":"Moonbeam","origin":"Actor.LClwU7mAyShYLmFU.OwnedItem.slhSBMhIsacM2lPh","tint":null,"transfer":false}],"folder":null,"sort":0,"flags":{"midi-qol":{"onUseMacroName":"[preActiveEffects]ItemMacro","effectActivation":false},"core":{"sourceId":"Item.slhSBMhIsacM2lPh"},"itemacro":{"macro":{"data":{"_id":null,"name":"Moonbeam","type":"script","author":"jkdToQqzFl0mFXls","img":"icons/svg/dice-target.svg","scope":"global","command":"if (!game.modules.get(\"advanced-macros\")?.active) { ui.notifications.error(\"Advanced Macros is not enabled\"); return }\nif (args[0].tag === \"OnUse\") {\nAAhelpers.applyTemplate(args) }","folder":null,"sort":0,"permission":{"default":0},"flags":{}}}},"midiProperties":{"nodam":false,"fulldam":false,"halfdam":false,"rollOther":false,"critOther":false,"magicdam":false,"magiceffect":false,"concentration":false,"toggleEffect":false}},"system":{"description":{"value":"

A silvery beam of pale light shines down in a 5-foot-radius, 40-foot-high Cylinder centered on a point within range. Until the spell ends, dim light fills the cylind⁠er.

When a creature enters the spell’s area for the first time on a turn or starts its turn there, it is engulfed in ghostly flames that cause searing pain, and it must make a Constitution saving throw. It takes 2d10 radiant damage on a failed save, or half as much damage on a successful one.

A Shapechanger makes its saving throw with disadvantage. If it fails, it also instantly reverts to its original form and can’t assume a different form until it leaves the spell’s light.

On each of your turns after you cast this spell, you can use an action to move the beam up to 60 feet in any direction.

At Higher Levels. When you cast this spell using a spell slot of 3rd level or higher, the damage increases by 1d10 for each slot level above 2nd.

","chat":"","unidentified":""},"source":"PHB pg. 261","activation":{"type":"action","cost":1,"condition":""},"duration":{"value":1,"units":"minute"},"target":{"value":5,"width":null,"units":"ft","type":"cylinder"},"range":{"value":120,"long":0,"units":"ft"},"uses":{"value":0,"max":"0","per":""},"consume":{"type":"","target":"","amount":null},"ability":"","actionType":"other","attackBonus":0,"chatFlavor":"","critical":{"threshold":null,"damage":null},"damage":{"parts":[],"versatile":""},"formula":"","save":{"ability":"","dc":null,"scaling":"spell"},"level":2,"school":"evo","components":{"value":"","vocal":true,"somatic":true,"material":true,"ritual":false,"concentration":true},"materials":{"value":"Several seeds of any moonseed plant and a piece of opalescent feldspar","consumed":false,"cost":0,"supply":0},"preparation":{"mode":"prepared","prepared":false},"scaling":{"mode":"none","formula":""}},"ownership":{"default":0,"E4BVikjIkVl2lL2j":3},"_stats":{"systemId":"dnd5e","systemVersion":"2.0.1","coreVersion":"10.283","createdTime":1662274929948,"modifiedTime":1662274930036,"lastModifiedBy":"GrX6tFOozYFBnDHq"}} +{"_id":"LnGJt2o5UoLiXYTa","name":"Darkness","type":"spell","img":"icons/magic/perception/eye-ringed-glow-angry-red.webp","effects":[{"_id":"G6rYwXfFameLG4DJ","flags":{"dae":{"stackable":"none","macroRepeat":"none","transfer":false,"specialDuration":[],"durationExpression":""},"ActiveAuras":{"isAura":true,"ignoreSelf":false,"hidden":false,"height":false,"alignment":"","type":"","aura":"All","radius":null,"save":"","savedc":null,"hostile":false,"onlyOnce":false,"time":"None","displayTemp":false},"core":{"statusId":""}},"changes":[{"key":"StatusEffect","mode":0,"value":"Convenient Effect: Blinded","priority":20}],"disabled":false,"duration":{"startTime":null,"seconds":null,"combat":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":null,"label":"Darkness","tint":null,"transfer":false,"origin":null}],"folder":null,"sort":0,"flags":{"midi-qol":{"onUseMacroName":"[preActiveEffects]ItemMacro","effectActivation":false},"core":{"sourceId":"Compendium.ActiveAuras.Active Auras Auras.LnGJt2o5UoLiXYTa"},"itemacro":{"macro":{"data":{"_id":null,"name":"Darkness","type":"script","author":"jkdToQqzFl0mFXls","img":"icons/svg/dice-target.svg","scope":"global","command":"if (!game.modules.get(\"advanced-macros\")?.active) { ui.notifications.error(\"Advanced Macros is not enabled\"); return }\nif(args[0].tag === \"OnUse\"){\nAAhelpers.applyTemplate(args)\n}","folder":null,"sort":0,"permission":{"default":0},"flags":{}}}},"midiProperties":{"nodam":false,"fulldam":false,"halfdam":false,"rollOther":false,"critOther":false,"magicdam":false,"magiceffect":false,"concentration":false,"toggleEffect":false}},"system":{"description":{"value":"

Magical darkness spreads from a point you choose within range to fill a 15-foot-radius sphere for the duration. The darkness spreads around corners. A creature with darkvision can't see through this darkness, and nonmagical light can't illuminate it.

If the point you choose is on an object you are holding or one that isn't being worn or carried, the darkness emanates from the object and moves with it. Completely covering the source of the darkness with an opaque object, such as a bowl or a helm, blocks the darkness.

If any of this spell's area overlaps with an area of light created by a spell of 2nd level or lower, the spell that created the light is dispelled.

","chat":"","unidentified":""},"source":"PHB pg. 230","activation":{"type":"action","cost":1,"condition":""},"duration":{"value":10,"units":"minute"},"target":{"value":15,"width":null,"units":"ft","type":"sphere"},"range":{"value":60,"long":0,"units":"ft"},"uses":{"value":0,"max":"0","per":""},"consume":{"type":"","target":"","amount":null},"ability":"","actionType":"util","attackBonus":0,"chatFlavor":"","critical":{"threshold":null,"damage":null},"damage":{"parts":[],"versatile":"","value":""},"formula":"","save":{"ability":"","dc":null,"scaling":"spell","value":""},"level":2,"school":"evo","components":{"value":"","vocal":true,"somatic":false,"material":true,"ritual":false,"concentration":true},"materials":{"value":"Bat fur and a drop of pitch or piece of coal","consumed":false,"cost":0,"supply":0},"preparation":{"mode":"prepared","prepared":false},"scaling":{"mode":"none","formula":""}},"ownership":{"default":0,"E4BVikjIkVl2lL2j":3},"_stats":{"systemId":"dnd5e","systemVersion":"2.0.1","coreVersion":"10.283","createdTime":1662274929952,"modifiedTime":1662274929952,"lastModifiedBy":"GrX6tFOozYFBnDHq"}} +{"_id":"O835BAsKUwUSb4QT","name":"Grease","type":"spell","img":"icons/magic/air/fog-gas-smoke-orange.webp","effects":[{"_id":"56HHrYL8EiufxQmp","flags":{"dae":{"stackable":"none","macroRepeat":"endEveryTurn","transfer":false,"specialDuration":[]},"ActiveAuras":{"isAura":true,"ignoreSelf":false,"hidden":false,"height":false,"alignment":"","type":"","aura":"All","radius":null,"save":"","savedc":null,"hostile":false,"onlyOnce":false,"time":"None","displayTemp":true},"core":{"statusId":""}},"changes":[{"key":"flags.midi-qol.OverTime","mode":0,"value":"turn=start, saveAbility=dex, saveDC=@attributes.spelldc, rollType=save, label=Fall prone on fail","priority":20}],"disabled":false,"duration":{"startTime":null,"seconds":null,"combat":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"icons/magic/air/fog-gas-smoke-orange.webp","label":"Grease","origin":"Actor.LClwU7mAyShYLmFU.OwnedItem.c75EoQVhgTvlVbq1","tint":null,"transfer":false}],"folder":null,"sort":0,"flags":{"midi-qol":{"onUseMacroName":"[preActiveEffects]ItemMacro","effectActivation":false},"itemacro":{"macro":{"data":{"_id":null,"name":"Grease","type":"script","author":"jkdToQqzFl0mFXls","img":"icons/svg/dice-target.svg","scope":"global","command":"if (!game.modules.get(\"advanced-macros\")?.active) { ui.notifications.error(\"Advanced Macros is not enabled\"); return }\nif(args[0].tag === \"OnUse\"){\nAAhelpers.applyTemplate(args)\n\n}","folder":null,"sort":0,"permission":{"default":0},"flags":{}}}},"core":{"sourceId":"Item.c75EoQVhgTvlVbq1"},"midiProperties":{"nodam":false,"fulldam":false,"halfdam":false,"rollOther":false,"critOther":false,"magicdam":false,"magiceffect":false,"concentration":false,"toggleEffect":false}},"system":{"description":{"value":"

Slick grease covers the ground in a 10-foot square centered on a point within range and turns it into difficult terrain for the duration.

When the grease appears, each creature standing in its area must succeed on a Dexterity saving throw or fall prone. A creature that enters the area or ends its turn there must also succeed on a Dexterity saving throw or fall prone.

","chat":"","unidentified":""},"source":"PHB pg. 246","activation":{"type":"action","cost":1,"condition":""},"duration":{"value":1,"units":"minute"},"target":{"value":10,"width":null,"units":"ft","type":"square"},"range":{"value":60,"long":0,"units":"ft"},"uses":{"value":0,"max":"0","per":""},"consume":{"type":"","target":"","amount":null},"ability":"","actionType":"save","attackBonus":0,"chatFlavor":"","critical":{"threshold":null,"damage":null},"damage":{"parts":[],"versatile":"","value":""},"formula":"","save":{"ability":"dex","dc":20,"scaling":"spell"},"level":1,"school":"con","components":{"value":"","vocal":true,"somatic":true,"material":true,"ritual":false,"concentration":false},"materials":{"value":"A bit of pork rind or butter","consumed":false,"cost":0,"supply":0},"preparation":{"mode":"prepared","prepared":false},"scaling":{"mode":"none","formula":""}},"ownership":{"default":0,"E4BVikjIkVl2lL2j":3},"_stats":{"systemId":"dnd5e","systemVersion":"2.0.1","coreVersion":"10.283","createdTime":1662274929957,"modifiedTime":1662274930043,"lastModifiedBy":"GrX6tFOozYFBnDHq"}} +{"_id":"RW2w0lgE6f6T9aYJ","name":"Incendiary Cloud","type":"spell","img":"icons/magic/air/fog-gas-smoke-swirling-orange.webp","effects":[{"_id":"R631jf30058faRaj","flags":{"dae":{"stackable":"none","macroRepeat":"endEveryTurn","transfer":false,"specialDuration":[]},"ActiveAuras":{"isAura":true,"ignoreSelf":false,"hidden":false,"height":false,"alignment":"","type":"","aura":"All","radius":null,"save":"","savedc":null,"hostile":true,"onlyOnce":true,"time":"None","displayTemp":true},"core":{"statusId":""}},"changes":[{"key":"flags.midi-qol.OverTime","mode":0,"value":"turn=end, saveAbility=dex, saveDC=@attributes.spelldc, saveDamage=halfdamage, rollType=save, saveMagic=true, damageBeforeSave=false, damageRoll=10d8, damageType=fire","priority":20}],"disabled":false,"duration":{"startTime":null,"seconds":null,"combat":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"icons/magic/air/fog-gas-smoke-swirling-orange.webp","label":"Incendiary Cloud","tint":null,"transfer":false,"origin":null}],"folder":null,"sort":0,"flags":{"midi-qol":{"onUseMacroName":"[preActiveEffects]ItemMacro","effectActivation":false},"core":{"sourceId":"Compendium.ActiveAuras.Active Auras Auras.RW2w0lgE6f6T9aYJ"},"itemacro":{"macro":{"data":{"_id":null,"name":"Incendiary Cloud","type":"script","author":"jkdToQqzFl0mFXls","img":"icons/svg/dice-target.svg","scope":"global","command":"if (!game.modules.get(\"advanced-macros\")?.active) { ui.notifications.error(\"Advanced Macros is not enabled\"); return }\nif(args[0].tag === \"OnUse\"){\nAAhelpers.applyTemplate(args)\n}","folder":null,"sort":0,"permission":{"default":0},"flags":{}}}},"midiProperties":{"nodam":false,"fulldam":false,"halfdam":false,"rollOther":false,"critOther":false,"magicdam":false,"magiceffect":false,"concentration":false,"toggleEffect":false}},"system":{"description":{"value":"

A swirling cloud of smoke shot through with white-hot embers appears in a 20-foot-radius Sphere centered on a point within range. The cloud spreads around corners and is heavily obscured. It lasts for the Duration or until a wind of moderate or greater speed (at least 10 miles per hour) disperses it.

When the cloud appears, each creature in it must make a Dexterity saving throw. A creature takes 10d8 fire damage on a failed save, or half as much damage on a successful one. A creature must also make this saving throw when it enters the spell’s area for the first time on a turn or ends its turn there.

The cloud moves 10 feet directly away from you in a direction that you choose at the start of each of your turns.

","chat":"","unidentified":""},"source":"PHB pg. 253","activation":{"type":"action","cost":1,"condition":""},"duration":{"value":1,"units":"minute"},"target":{"value":20,"width":null,"units":"ft","type":"sphere"},"range":{"value":150,"long":0,"units":"ft"},"uses":{"value":0,"max":"0","per":""},"consume":{"type":"","target":"","amount":null},"ability":"","actionType":"save","attackBonus":0,"chatFlavor":"","critical":{"threshold":null,"damage":null},"damage":{"parts":[],"versatile":""},"formula":"","save":{"ability":"","dc":null,"scaling":"spell"},"level":8,"school":"con","components":{"value":"","vocal":true,"somatic":true,"material":false,"ritual":false,"concentration":true},"materials":{"value":"","consumed":false,"cost":0,"supply":0},"preparation":{"mode":"prepared","prepared":false},"scaling":{"mode":"none","formula":""}},"ownership":{"default":0,"E4BVikjIkVl2lL2j":3},"_stats":{"systemId":"dnd5e","systemVersion":"2.0.1","coreVersion":"10.283","createdTime":1662274929959,"modifiedTime":1662274930049,"lastModifiedBy":"GrX6tFOozYFBnDHq"}} +{"_id":"RXACq8vCYUuSRpjc","name":"Spike Growth","type":"spell","img":"icons/magic/nature/vines-thorned-curled-glow-green.webp","effects":[{"_id":"f1Ab0wnqRPfoEsf1","flags":{"dae":{"stackable":false,"macroRepeat":"none","transfer":false,"specialDuration":["isDamaged"]},"ActiveAuras":{"isAura":true,"ignoreSelf":false,"hidden":false,"height":false,"alignment":"","type":"","aura":"All","radius":null,"save":"","savedc":null,"hostile":true,"onlyOnce":false,"time":"isDamaged"}},"changes":[{"key":"macro.itemMacro","mode":0,"value":"","priority":20}],"disabled":false,"duration":{"startTime":null,"seconds":null,"combat":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"icons/magic/nature/vines-thorned-curled-glow-green.webp","label":"Spike Growth","origin":"Actor.LClwU7mAyShYLmFU.OwnedItem.nkArQrnBQu4uN92X","tint":null,"transfer":false}],"folder":null,"sort":0,"flags":{"midi-qol":{"onUseMacroName":"[preActiveEffects]ItemMacro","effectActivation":false},"core":{"sourceId":"Item.nkArQrnBQu4uN92X"},"itemacro":{"macro":{"data":{"_id":null,"name":"Spike Growth","type":"script","author":"jkdToQqzFl0mFXls","img":"icons/svg/dice-target.svg","scope":"global","command":"if (!game.modules.get(\"advanced-macros\")?.active) { ui.notifications.error(\"Advanced Macros is not enabled\"); return }\nif(args[0].tag === \"OnUse\"){\nAAhelpers.applyTemplate(args) }\nif (args[0] === \"on\" || args[0] === \"each\") {\n const lastArg = args[args.length - 1];\n let tactor;\n if (lastArg.tokenId) tactor = canvas.tokens.get(lastArg.tokenId).actor;\n else tactor = game.actors.get(lastArg.actorId);\n const target = canvas.tokens.get(lastArg.tokenId)\n let damageRoll = new Roll(`2d4[piercing]`).evaluate()\n damageRoll.toMessage({ flavor: \"Spike Growth Damage\" })\n let targets = new Set();\n let saves = new Set();\n targets.add(target);\n saves.add(target);\n await MidiQOL.applyTokenDamage([{ damage: damageRoll.total, type: \"piercing\" }], damageRoll.total, targets, null, saves);\n let effect = tactor.effects.find(i => i.data.label === \"Spike Growth\")\n await effect.delete()\n}","folder":null,"sort":0,"permission":{"default":0},"flags":{}}}},"midiProperties":{"nodam":false,"fulldam":false,"halfdam":false,"rollOther":false,"critOther":false,"magicdam":false,"magiceffect":false,"concentration":false,"toggleEffect":false}},"system":{"description":{"value":"

The ground in a 20-foot radius centered on a point within range twists and sprouts hard spikes and thorns. The area becomes difficult terrain for the Duration. When a creature moves into or within the area, it takes 2d4 piercing damage for every 5 feet it travels.

The transformation of the ground is camouflaged to look natural. Any creature that can’t see the area at the time the spell is cast must make a Wisdom (Perception) check against your spell save DC to recognize the terrain as hazardous before entering it.

","chat":"","unidentified":""},"source":"PHB pg. 277","activation":{"type":"action","cost":1,"condition":""},"duration":{"value":10,"units":"minute"},"target":{"value":20,"width":null,"units":"ft","type":"radius"},"range":{"value":150,"long":0,"units":"ft"},"uses":{"value":0,"max":"0","per":""},"consume":{"type":"","target":"","amount":null},"ability":"","actionType":"other","attackBonus":0,"chatFlavor":"","critical":{"threshold":null,"damage":null},"damage":{"parts":[],"versatile":""},"formula":"","save":{"ability":"","dc":null,"scaling":"spell","value":""},"level":2,"school":"trs","components":{"value":"","vocal":true,"somatic":true,"material":true,"ritual":false,"concentration":true},"materials":{"value":"Seven sharp thorns or seven small twigs, each sharpened to a point","consumed":false,"cost":0,"supply":0},"preparation":{"mode":"prepared","prepared":false},"scaling":{"mode":"none","formula":""}},"ownership":{"default":0,"E4BVikjIkVl2lL2j":3},"_stats":{"systemId":"dnd5e","systemVersion":"2.0.1","coreVersion":"10.283","createdTime":1662274929962,"modifiedTime":1662274930056,"lastModifiedBy":"GrX6tFOozYFBnDHq"}} +{"_id":"SGwciGPjSKSM1PZ8","name":"Insect Plague","type":"spell","img":"icons/magic/nature/leaf-glow-maple-teal.webp","effects":[{"_id":"V4qfaLuoUbtFQoLu","flags":{"dae":{"stackable":"none","macroRepeat":"endEveryTurn","transfer":false,"specialDuration":[]},"ActiveAuras":{"isAura":true,"ignoreSelf":false,"hidden":false,"height":false,"alignment":"","type":"","aura":"All","radius":null,"save":"","savedc":null,"hostile":true,"onlyOnce":true,"time":"None","displayTemp":true},"core":{"statusId":""}},"changes":[{"key":"flags.midi-qol.OverTime","mode":0,"value":"turn=end, saveAbility=con, saveDC=@attributes.spelldc, saveDamage=halfdamage, rollType=save, saveMagic=true, damageBeforeSave=false, damageRoll=(@item.level-1)d8, damageType=piercing","priority":20}],"disabled":false,"duration":{"startTime":null,"seconds":null,"combat":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"icons/magic/nature/leaf-glow-maple-teal.webp","label":"Insect Plague","tint":null,"transfer":false,"origin":null}],"folder":null,"sort":0,"flags":{"midi-qol":{"onUseMacroName":"[preActiveEffects]ItemMacro","effectActivation":false},"core":{"sourceId":"Compendium.ActiveAuras.Active Auras Auras.SGwciGPjSKSM1PZ8"},"itemacro":{"macro":{"data":{"_id":null,"name":"Insect Plague","type":"script","author":"jkdToQqzFl0mFXls","img":"icons/svg/dice-target.svg","scope":"global","command":"if (!game.modules.get(\"advanced-macros\")?.active) { ui.notifications.error(\"Advanced Macros is not enabled\"); return }\nif(args[0].tag === \"OnUse\"){\nAAhelpers.applyTemplate(args)\n}","folder":null,"sort":0,"permission":{"default":0},"flags":{}}}},"midiProperties":{"nodam":false,"fulldam":false,"halfdam":false,"rollOther":false,"critOther":false,"magicdam":false,"magiceffect":false,"concentration":false,"toggleEffect":false}},"system":{"description":{"value":"

Swarming, biting locusts fill a 20-foot-radius Sphere centered on a point you choose within range. The sph⁠ere spreads around corners. The sphe⁠re remains for the Duration, and its area is lightly obscured. The sphere’s area is difficult terrain.

When the area appears, each creature in it must make a Constitution saving throw. A creature takes 4d10 piercing damage on a failed save, or half as much damage on a successful one. A creature must also make this saving throw when it enters the spell’s area for the first time on a turn or ends its turn there.

At Higher Levels. When you cast this spell using a spell slot of 6th level or higher, the damage increases by 1d10 for each slot level above 5th.

","chat":"","unidentified":""},"source":"PHB pg. 254","activation":{"type":"action","cost":1,"condition":""},"duration":{"value":10,"units":"minute"},"target":{"value":20,"width":null,"units":"ft","type":"sphere"},"range":{"value":300,"long":0,"units":"ft"},"uses":{"value":0,"max":"0","per":""},"consume":{"type":"","target":"","amount":null},"ability":"","actionType":"save","attackBonus":0,"chatFlavor":"","critical":{"threshold":null,"damage":null},"damage":{"parts":[],"versatile":""},"formula":"","save":{"ability":"","dc":null,"scaling":"spell"},"level":5,"school":"con","components":{"value":"","vocal":true,"somatic":true,"material":true,"ritual":false,"concentration":true},"materials":{"value":"A few grains of sugar, some kernels of grain, and a smear of fat","consumed":false,"cost":0,"supply":0},"preparation":{"mode":"prepared","prepared":false},"scaling":{"mode":"level","formula":"1d10"}},"ownership":{"default":0,"E4BVikjIkVl2lL2j":3},"_stats":{"systemId":"dnd5e","systemVersion":"2.0.1","coreVersion":"10.283","createdTime":1662274929965,"modifiedTime":1662274930062,"lastModifiedBy":"GrX6tFOozYFBnDHq"}} +{"_id":"TDLGLgoS3gShIzbp","name":"Spirit Guardians","type":"spell","img":"icons/magic/light/projectile-bolts-salvo-white.webp","effects":[{"_id":"hUhljQolEu7to9m5","flags":{"dae":{"stackable":"none","macroRepeat":"startEveryTurn","transfer":false,"specialDuration":[]},"ActiveAuras":{"isAura":true,"ignoreSelf":true,"hidden":false,"height":false,"alignment":"","type":"","aura":"Enemy","radius":15,"save":"","savedc":null,"hostile":false,"onlyOnce":true,"time":"None","displayTemp":false},"core":{"statusId":""}},"changes":[{"key":"flags.midi-qol.OverTime","mode":0,"value":"turn=start, saveAbility=wis, saveDC=@attributes.spelldc, saveDamage=halfdamage, rollType=save, saveMagic=true, damageBeforeSave=false, damageRoll=(@item.level)d8, damageType=radiant","priority":0},{"key":"system.attributes.movement.all","mode":0,"value":"*0.5","priority":20}],"disabled":false,"duration":{"startTime":null,"seconds":null,"combat":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"icons/magic/light/projectile-bolts-salvo-white.webp","label":"Spirit Guardians","tint":null,"transfer":false,"origin":null}],"folder":null,"sort":0,"flags":{"midi-qol":{"onUseMacroName":"[preActiveEffects]ItemMacro","effectActivation":false,"forceCEOn":false},"itemacro":{"macro":{"data":{"_id":null,"name":"Spirit Guardians","type":"script","author":"jkdToQqzFl0mFXls","img":"icons/svg/dice-target.svg","scope":"global","command":"if (!game.modules.get(\"advanced-macros\")?.active) { ui.notifications.error(\"Advanced Macros is not enabled\"); return }\nif(args[0].tag === \"OnUse\"){\nAAhelpers.applyTemplate(args) }","folder":null,"sort":0,"permission":{"default":0},"flags":{}}}},"core":{"sourceId":"Compendium.ActiveAuras.Active Auras Auras.TDLGLgoS3gShIzbp"},"midiProperties":{"nodam":false,"fulldam":false,"halfdam":false,"rollOther":false,"critOther":false,"magicdam":false,"magiceffect":false,"concentration":false,"toggleEffect":false}},"system":{"description":{"value":"

You call forth spirits to protect you. They flit around you to a distance of 15 feet for the duration. If you are good or neutral, their spectral form appears angelic or fey (your choice). If you are evil, they appear fiendish.

When you cast this spell, you can designate any number of creatures you can see to be unaffected by it. An affected creature's speed is halved in the area, and when the creature enters the area for the first time on a turn or starts its turn there, it must make a Wisdom saving throw. On a failed save, the creature takes 3d8 radiant damage (if you are good or neutral) or 3d8 necrotic damage (if you are evil). On a successful save, the creature takes half as much damage.

At Higher Levels. When you cast this spell using a spell slot of 4th level or higher, the damage increases by 1d8 for each slot level above 3rd.

","chat":"","unidentified":""},"source":"PHB pg. 278","activation":{"type":"action","cost":1,"condition":""},"duration":{"value":10,"units":"minute"},"target":{"value":null,"width":null,"units":"","type":"self"},"range":{"value":null,"long":null,"units":"self"},"uses":{"value":0,"max":"0","per":""},"consume":{"type":"","target":"","amount":null},"ability":"","actionType":"save","attackBonus":0,"chatFlavor":"","critical":{"threshold":null,"damage":null},"damage":{"parts":[],"versatile":""},"formula":"","save":{"ability":"","dc":null,"scaling":"spell"},"level":3,"school":"con","components":{"value":"","vocal":true,"somatic":true,"material":true,"ritual":false,"concentration":true},"materials":{"value":"A holy symbol","consumed":false,"cost":0,"supply":0},"preparation":{"mode":"prepared","prepared":false},"scaling":{"mode":"level","formula":"1d8"}},"ownership":{"default":0,"E4BVikjIkVl2lL2j":3},"_stats":{"systemId":"dnd5e","systemVersion":"2.0.1","coreVersion":"10.283","createdTime":1662274929968,"modifiedTime":1662274930069,"lastModifiedBy":"GrX6tFOozYFBnDHq"}} +{"_id":"UmRAOUrM8itCMzmj","name":"Sleet Storm","type":"spell","img":"icons/magic/water/projectile-beams-salvo-blue.webp","effects":[{"_id":"UPkDhbLfoplgjQNd","flags":{"dae":{"stackable":"none","macroRepeat":"startEveryTurn","transfer":false,"specialDuration":[]},"ActiveAuras":{"isAura":true,"ignoreSelf":false,"hidden":false,"height":false,"alignment":"","type":"","aura":"All","radius":null,"save":"","savedc":null,"hostile":true,"onlyOnce":true,"time":"None","displayTemp":false},"core":{"statusId":""}},"changes":[{"key":"flags.midi-qol.OverTime","mode":0,"value":"turn=start, saveAbility=dex, saveDC=@attributes.spelldc, rollType=save, label=Fall prone on fail","priority":20}],"disabled":false,"duration":{"startTime":null,"seconds":null,"combat":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":null,"label":"Sleet Storm","origin":"Actor.LClwU7mAyShYLmFU.OwnedItem.DMIfX1LiTZvWgY82","tint":null,"transfer":false}],"folder":null,"sort":0,"flags":{"midi-qol":{"onUseMacroName":"[preActiveEffects]ItemMacro","effectActivation":false},"core":{"sourceId":"Item.DMIfX1LiTZvWgY82"},"itemacro":{"macro":{"data":{"_id":null,"name":"Sleet Storm","type":"script","author":"jkdToQqzFl0mFXls","img":"icons/svg/dice-target.svg","scope":"global","command":"if (!game.modules.get(\"advanced-macros\")?.active) { ui.notifications.error(\"Advanced Macros is not enabled\"); return }\nif(args[0].tag === \"OnUse\"){\nAAhelpers.applyTemplate(args) }","folder":null,"sort":0,"permission":{"default":0},"flags":{}}}},"midiProperties":{"nodam":false,"fulldam":false,"halfdam":false,"rollOther":false,"critOther":false,"magicdam":false,"magiceffect":false,"concentration":false,"toggleEffect":false}},"system":{"description":{"value":"

Until the spell ends, freezing rain and sleet fall in a 20-foot-tall cylinder with a 40-foot radius centered on a point you choose within range. The area is heavily obscured, and exposed flames in the area are doused.

The ground in the area is covered with slick ice, making it difficult terrain. When a creature enters the spell's area for the first time on a turn or starts its turn there, it must make a Dexterity saving throw. On a failed save, it falls Prone.

If a creature is concentrating in the spell's area, the creature must make a successful Constitution saving throw against your spell save DC or lose concentration.

","chat":"","unidentified":""},"source":"PHB pg. 276","activation":{"type":"action","cost":1,"condition":""},"duration":{"value":1,"units":"minute"},"target":{"value":40,"width":null,"units":"ft","type":"cylinder"},"range":{"value":150,"long":0,"units":"ft"},"uses":{"value":0,"max":"0","per":""},"consume":{"type":"","target":"","amount":null},"ability":"","actionType":"other","attackBonus":0,"chatFlavor":"","critical":{"threshold":null,"damage":null},"damage":{"parts":[],"versatile":"","value":""},"formula":"","save":{"ability":"","dc":null,"scaling":"spell"},"level":3,"school":"con","components":{"value":"","vocal":true,"somatic":true,"material":true,"ritual":false,"concentration":true},"materials":{"value":"A pinch of dust and a few drops of water.","consumed":false,"cost":0,"supply":0},"preparation":{"mode":"prepared","prepared":false},"scaling":{"mode":"none","formula":""}},"ownership":{"default":0,"E4BVikjIkVl2lL2j":3},"_stats":{"systemId":"dnd5e","systemVersion":"2.0.1","coreVersion":"10.283","createdTime":1662274929970,"modifiedTime":1662274929970,"lastModifiedBy":"GrX6tFOozYFBnDHq"}} +{"_id":"ao9RNllMa6erTIeG","name":"Stinking Cloud","type":"spell","img":"icons/magic/air/fog-gas-smoke-dense-green.webp","effects":[{"_id":"frtB4d41h4ZQehRG","flags":{"dae":{"stackable":"none","macroRepeat":"startEveryTurn","transfer":false,"specialDuration":[]},"ActiveAuras":{"isAura":false,"ignoreSelf":false,"hidden":false,"height":false,"alignment":"","type":"","aura":"None","radius":null,"save":"","savedc":null,"hostile":false,"onlyOnce":false,"time":"None","displayTemp":false},"core":{"statusId":""}},"changes":[{"key":"flags.midi-qol.OverTime","mode":0,"value":"turn=start, saveAbility=con, saveDC=@attributes.spelldc, label=On a failed save, the creature spends its action that turn retching and reeling. Creatures that don't need to breathe or are immune to poison automatically succeed on this saving throw.","priority":20}],"disabled":false,"duration":{"startTime":null,"seconds":null,"combat":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"icons/magic/air/fog-gas-smoke-dense-green.webp","label":"Stinking Cloud","tint":null,"transfer":false,"origin":null}],"folder":null,"sort":0,"flags":{"midi-qol":{"onUseMacroName":"[preActiveEffects]ItemMacro","effectActivation":false},"core":{"sourceId":"Compendium.ActiveAuras.Active Auras Auras.ao9RNllMa6erTIeG"},"itemacro":{"macro":{"data":{"_id":null,"name":"Stinking Cloud","type":"script","author":"jkdToQqzFl0mFXls","img":"icons/svg/dice-target.svg","scope":"global","command":"if (!game.modules.get(\"advanced-macros\")?.active) { ui.notifications.error(\"Advanced Macros is not enabled\"); return }\nif(args[0].tag === \"OnUse\"){\nAAhelpers.applyTemplate(args) }","folder":null,"sort":0,"permission":{"default":0},"flags":{}}}},"midiProperties":{"nodam":false,"fulldam":false,"halfdam":false,"rollOther":false,"critOther":false,"magicdam":false,"magiceffect":false,"concentration":false,"toggleEffect":false}},"system":{"description":{"value":"

You create a 20-foot-radius sphere of yellow, nauseating gas centered on a point within range. The cloud spreads around corners, and its area is heavily obscured. The cloud lingers in the air for the duration.

Each creature that is completely within the cloud at the start of its turn must make a Constitution saving throw against poison. On a failed save, the creature spends its action that turn retching and reeling. Creatures that don't need to breathe or are immune to poison automatically succeed on this saving throw.

A moderate wind (at least 10 miles per hour) disperses the cloud after 4 rounds. A strong wind (at least 20 miles per hour) disperses it after 1 round.

","chat":"","unidentified":""},"source":"PHB pg. 278","activation":{"type":"action","cost":1,"condition":""},"duration":{"value":1,"units":"minute"},"target":{"value":20,"width":null,"units":"ft","type":"sphere"},"range":{"value":90,"long":0,"units":"ft"},"uses":{"value":0,"max":"0","per":""},"consume":{"type":"","target":"","amount":null},"ability":"","actionType":"save","attackBonus":0,"chatFlavor":"","critical":{"threshold":null,"damage":null},"damage":{"parts":[],"versatile":"","value":""},"formula":"","save":{"ability":"","dc":null,"scaling":"spell"},"level":3,"school":"con","components":{"value":"","vocal":true,"somatic":true,"material":true,"ritual":false,"concentration":true},"materials":{"value":"A rotten egg or several skunk cabbage leaves","consumed":false,"cost":0,"supply":0},"preparation":{"mode":"prepared","prepared":false},"scaling":{"mode":"none","formula":""}},"ownership":{"default":0,"E4BVikjIkVl2lL2j":3},"_stats":{"systemId":"dnd5e","systemVersion":"2.0.1","coreVersion":"10.283","createdTime":1662274929972,"modifiedTime":1662274930076,"lastModifiedBy":"GrX6tFOozYFBnDHq"}} +{"_id":"jNVxMgavVtbIZqFy","name":"Silence","type":"spell","img":"icons/magic/symbols/runes-triangle-magenta.webp","effects":[{"_id":"4gFVHoupB272LFGm","flags":{"dae":{"stackable":"none","macroRepeat":"none","transfer":false,"specialDuration":[],"durationExpression":""},"ActiveAuras":{"isAura":true,"ignoreSelf":false,"hidden":false,"height":false,"alignment":"","type":"","aura":"All","radius":null,"save":"","savedc":null,"hostile":false,"onlyOnce":false,"time":"None","displayTemp":true},"core":{"statusId":""}},"changes":[{"key":"","mode":0,"value":"Convenient Effect: Blinded","priority":20}],"disabled":false,"duration":{"startTime":null,"seconds":null,"combat":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"icons/magic/symbols/runes-triangle-magenta.webp","label":"Silence","tint":null,"transfer":false,"origin":null}],"folder":null,"sort":0,"flags":{"midi-qol":{"onUseMacroName":"[preActiveEffects]ItemMacro","effectActivation":false},"core":{"sourceId":"Compendium.ActiveAuras.Active Auras Auras.jNVxMgavVtbIZqFy"},"itemacro":{"macro":{"data":{"_id":null,"name":"Silence","type":"script","author":"jkdToQqzFl0mFXls","img":"icons/svg/dice-target.svg","scope":"global","command":"if (!game.modules.get(\"advanced-macros\")?.active) { ui.notifications.error(\"Advanced Macros is not enabled\"); return }\nif(args[0].tag === \"OnUse\"){\nAAhelpers.applyTemplate(args) }","folder":null,"sort":0,"permission":{"default":0},"flags":{}}}},"midiProperties":{"nodam":false,"fulldam":false,"halfdam":false,"rollOther":false,"critOther":false,"magicdam":false,"magiceffect":false,"concentration":false,"toggleEffect":false}},"system":{"description":{"value":"

For the Duration, no sound can be created within or pass through a 20-foot-radius s⁠phere centered on a point you choose within range. Any creature or object entirely inside the s⁠phere is immune to thunder damage, and creatures are Deafened while entirely inside it. Casting a Spell that includes a verbal component is impossible there.

","chat":"","unidentified":""},"source":"PHB pg. 275","activation":{"type":"action","cost":1,"condition":""},"duration":{"value":10,"units":"minute"},"target":{"value":20,"width":null,"units":"ft","type":"sphere"},"range":{"value":120,"long":0,"units":"ft"},"uses":{"value":0,"max":"0","per":""},"consume":{"type":"","target":"","amount":null},"ability":"","actionType":"util","attackBonus":0,"chatFlavor":"","critical":{"threshold":null,"damage":null},"damage":{"parts":[],"versatile":"","value":""},"formula":"","save":{"ability":"","dc":null,"scaling":"spell","value":""},"level":2,"school":"ill","components":{"value":"","vocal":true,"somatic":true,"material":false,"ritual":true,"concentration":true},"materials":{"value":"","consumed":false,"cost":0,"supply":0},"preparation":{"mode":"prepared","prepared":false},"scaling":{"mode":"none","formula":""}},"ownership":{"default":0,"E4BVikjIkVl2lL2j":3},"_stats":{"systemId":"dnd5e","systemVersion":"2.0.1","coreVersion":"10.283","createdTime":1662274929974,"modifiedTime":1662274929974,"lastModifiedBy":"GrX6tFOozYFBnDHq"}} +{"_id":"jmFWbZtk4k12Is9y","name":"Aura of Courage","type":"feat","img":"icons/magic/light/explosion-beam-impact-silhouette.webp","effects":[{"_id":"jqyEv4x6Yz5NcW9F","flags":{"ActiveAuras":{"isAura":true,"inactive":false,"hidden":false,"aura":"Allies","radius":10},"dae":{"stackable":false,"specialDuration":"None","transfer":true}},"changes":[{"key":"system.traits.ci.value","value":"frightened","mode":2,"priority":20}],"disabled":false,"duration":{"startTime":null,"seconds":null,"combat":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"icons/magic/light/explosion-beam-impact-silhouette.webp","label":"Aura of Courage","tint":null,"transfer":true,"origin":null}],"folder":null,"sort":0,"flags":{"dae":{"activeEquipped":false,"alwaysActive":true},"core":{"sourceId":"Compendium.ActiveAuras.Active Auras Auras.jmFWbZtk4k12Is9y"}},"system":{"description":{"value":"

Starting at 10th level, you and friendly creatures within 10 feet of you can't be frightened while you are conscious.

At 18th level, the range of this aura increases to 30 feet.

","chat":"","unidentified":""},"source":"PHB pg. 85","activation":{"type":"","cost":0,"condition":""},"duration":{"value":null,"units":""},"target":{"value":null,"width":null,"units":null,"type":"self"},"range":{"value":10,"long":30,"units":"ft"},"uses":{"value":0,"max":0,"per":"","type":""},"consume":{"type":"","target":null,"amount":null},"ability":"","actionType":"","attackBonus":0,"chatFlavor":"","critical":{"threshold":null,"damage":null},"damage":{"parts":[],"versatile":"","value":""},"formula":"","save":{"ability":"","dc":null,"scaling":"spell","value":""},"requirements":"Paladin 10","recharge":{"value":null,"charged":false}},"ownership":{"default":0,"E4BVikjIkVl2lL2j":3},"_stats":{"systemId":"dnd5e","systemVersion":"2.0.1","coreVersion":"10.283","createdTime":1662274929977,"modifiedTime":1662274930084,"lastModifiedBy":"GrX6tFOozYFBnDHq"}} +{"_id":"wRjkVOup7yqk2CKU","name":"Aura of Protection","type":"feat","img":"icons/magic/air/wind-tornado-wall-blue.webp","effects":[{"_id":"YA6d5MuIdX6Bkc8O","flags":{"ActiveAuras":{"aura":"Allies","radius":10,"isAura":true,"inactive":false,"hidden":false,"ignoreSelf":false,"height":false,"alignment":"","type":"","save":"","savedc":null,"hostile":false,"onlyOnce":false,"time":"None","displayTemp":false},"dae":{"stackable":"none","specialDuration":["None"],"transfer":true,"macroRepeat":"none","durationExpression":""},"core":{"statusId":""}},"changes":[{"key":"system.bonuses.abilities.save","mode":2,"value":"+@abilities.cha.mod","priority":20}],"disabled":false,"duration":{"startTime":null,"seconds":null,"combat":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"icons/magic/air/wind-tornado-wall-blue.webp","label":"Aura of Protection","origin":"Item.8csQOq23Ht2m47Zg","tint":null,"transfer":true}],"folder":null,"sort":0,"flags":{"dae":{"activeEquipped":false,"alwaysActive":true},"core":{"sourceId":"Compendium.ActiveAuras.Active Auras Auras.gDPFeakSXy0DgGhd"}},"system":{"description":{"value":"

Starting at 6th level, whenever you or a friendly creature within 10 feet of you must make a saving throw, the creature gains a bonus to the saving throw equal to your Charisma modifier (with a minimum bonus of +1). You must be conscious to grant this bonus.

 At 18th level, the range of this aura increases to 30 feet.

","chat":"","unidentified":""},"source":"PHB pg. 85","activation":{"type":"","cost":0,"condition":""},"duration":{"value":null,"units":""},"target":{"value":null,"width":null,"units":null,"type":"self"},"range":{"value":10,"long":30,"units":"ft"},"uses":{"value":0,"max":0,"per":"","type":""},"consume":{"type":"","target":null,"amount":null},"ability":"","actionType":"","attackBonus":0,"chatFlavor":"","critical":{"threshold":null,"damage":null},"damage":{"parts":[],"versatile":"","value":""},"formula":"","save":{"ability":"","dc":null,"scaling":"spell","value":""},"requirements":"Paladin 6","recharge":{"value":null,"charged":false}},"ownership":{"default":0,"jkdToQqzFl0mFXls":3},"_stats":{"systemId":"dnd5e","systemVersion":"2.0.1","coreVersion":"10.283","createdTime":1662274929979,"modifiedTime":1662274930089,"lastModifiedBy":"GrX6tFOozYFBnDHq"}} diff --git a/packs/ZoneActors b/packs/ZoneActors index df1b5a1..3cfdc59 100644 --- a/packs/ZoneActors +++ b/packs/ZoneActors @@ -1 +1 @@ -{"_id":"blnOhH5q6LWOSgYC","name":"Zone Effects","permission":{"default":0,"E4BVikjIkVl2lL2j":3},"type":"character","data":{"abilities":{"str":{"value":10,"proficient":0},"dex":{"value":10,"proficient":0},"con":{"value":10,"proficient":0},"int":{"value":10,"proficient":0},"wis":{"value":10,"proficient":0},"cha":{"value":10,"proficient":0}},"attributes":{"ac":{"value":10},"hp":{"value":10,"min":0,"max":10,"temp":null,"tempmax":null},"init":{"value":0,"bonus":0},"movement":{"burrow":0,"climb":0,"fly":0,"swim":0,"walk":30,"units":"ft","hover":false},"senses":{"darkvision":0,"blindsight":0,"tremorsense":0,"truesight":0,"units":"ft","special":""},"spellcasting":"int","death":{"success":0,"failure":0},"encumbrance":{"value":null,"max":null},"exhaustion":0,"inspiration":false},"details":{"biography":{"value":"","public":""},"alignment":"","race":"","background":"","xp":{"value":0,"min":0,"max":300},"appearance":"","trait":"","ideal":"","bond":"","flaw":""},"traits":{"size":"med","di":{"value":[],"custom":""},"dr":{"value":[],"custom":""},"dv":{"value":[],"custom":""},"ci":{"value":[],"custom":""},"languages":{"value":[],"custom":""},"weaponProf":{"value":[],"custom":""},"armorProf":{"value":[],"custom":""},"toolProf":{"value":[],"custom":""}},"currency":{"pp":0,"gp":0,"ep":0,"sp":0,"cp":0},"skills":{"acr":{"value":0,"ability":"dex"},"ani":{"value":0,"ability":"wis"},"arc":{"value":0,"ability":"int"},"ath":{"value":0,"ability":"str"},"dec":{"value":0,"ability":"cha"},"his":{"value":0,"ability":"int"},"ins":{"value":0,"ability":"wis"},"itm":{"value":0,"ability":"cha"},"inv":{"value":0,"ability":"int"},"med":{"value":0,"ability":"wis"},"nat":{"value":0,"ability":"int"},"prc":{"value":0,"ability":"wis"},"prf":{"value":0,"ability":"cha"},"per":{"value":0,"ability":"cha"},"rel":{"value":0,"ability":"int"},"slt":{"value":0,"ability":"dex"},"ste":{"value":0,"ability":"dex"},"sur":{"value":0,"ability":"wis"}},"spells":{"spell1":{"value":0,"override":null},"spell2":{"value":0,"override":null},"spell3":{"value":0,"override":null},"spell4":{"value":0,"override":null},"spell5":{"value":0,"override":null},"spell6":{"value":0,"override":null},"spell7":{"value":0,"override":null},"spell8":{"value":0,"override":null},"spell9":{"value":0,"override":null},"pact":{"value":0,"override":null}},"bonuses":{"mwak":{"attack":"","damage":""},"rwak":{"attack":"","damage":""},"msak":{"attack":"","damage":""},"rsak":{"attack":"","damage":""},"abilities":{"check":"","save":"","skill":""},"spell":{"dc":""}},"resources":{"primary":{"value":null,"max":null,"sr":false,"lr":false,"label":""},"secondary":{"value":null,"max":null,"sr":false,"lr":false,"label":""},"tertiary":{"value":null,"max":null,"sr":false,"lr":false,"label":""}}},"sort":100001,"flags":{},"token":{"flags":{},"name":"Zone Effects","displayName":0,"img":"icons/svg/mystery-man.svg","tint":null,"width":1,"height":1,"scale":1,"lockRotation":false,"rotation":0,"vision":true,"dimSight":30,"brightSight":0,"dimLight":0,"brightLight":0,"sightAngle":360,"lightAngle":360,"lightAlpha":1,"lightAnimation":{"speed":5,"intensity":5},"actorId":"blnOhH5q6LWOSgYC","actorLink":true,"disposition":1,"displayBars":0,"bar1":{},"bar2":{},"randomImg":false},"items":[],"effects":[{"_id":"rmP4f6pWoK29nJ7F","flags":{"dae":{"macroRepeat":"none","specialDuration":["turnEnd"]},"ActiveAuras":{"isAura":true,"ignoreSelf":false,"hidden":false,"height":false,"alignment":"","type":"","aura":"None","radius":5,"save":"","savedc":null,"hostile":true,"onlyOnce":true,"time":"None"}},"changes":[{"key":"macro.execute","value":"\"AA DoT\" 1d6 fire","mode":0,"priority":20}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"icons/svg/aura.svg","label":"DAE Damge over time - edit effect value, requires DAE and Times Up","origin":"Actor.9n8trQy9SuvzNdMv","tint":""},{"_id":"sznrvl59zwvoQSPf","flags":{"dae":{"macroRepeat":"none"},"ActiveAuras":{"isAura":true,"ignoreSelf":false,"hidden":false,"height":false,"alignment":"","type":"","aura":"None","radius":5,"save":"","savedc":null,"hostile":false,"onlyOnce":false,"time":"None"}},"changes":[{"key":"data.attributes.movement.walk","value":0.5,"mode":1,"priority":20}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"icons/svg/aura.svg","label":"Slow Aura","origin":"Actor.9n8trQy9SuvzNdMv","tint":""},{"_id":"nrvryBcBA41UaSii","flags":{"dae":{"macroRepeat":"none","specialDuration":["turnEnd"]},"ActiveAuras":{"isAura":true,"ignoreSelf":false,"hidden":false,"height":false,"alignment":"","type":"","aura":"None","radius":5,"save":"","savedc":null,"hostile":true,"onlyOnce":true,"time":"None"}},"changes":[{"key":"macro.execute","value":"\"AA Cloudkill\" 15","mode":0,"priority":20}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"icons/svg/aura.svg","label":"Cloudkill (number in effect value is the DC) requires Times Up","origin":"Actor.9n8trQy9SuvzNdMv","tint":""}]} +{"_id":"blnOhH5q6LWOSgYC","name":"Zone Effects","type":"character","sort":100001,"flags":{},"items":[],"effects":[{"_id":"rmP4f6pWoK29nJ7F","flags":{"dae":{"macroRepeat":"none","specialDuration":["turnEnd"]},"ActiveAuras":{"isAura":true,"ignoreSelf":false,"hidden":false,"height":false,"alignment":"","type":"","aura":"None","radius":5,"save":"","savedc":null,"hostile":true,"onlyOnce":true,"time":"None"}},"changes":[{"key":"macro.execute","value":"\"AA DoT\" 1d6 fire","mode":0,"priority":20}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null,"combat":null},"icon":"icons/svg/aura.svg","label":"DAE Damge over time - edit effect value, requires DAE and Times Up","origin":"Actor.9n8trQy9SuvzNdMv","tint":null,"transfer":true},{"_id":"sznrvl59zwvoQSPf","flags":{"dae":{"macroRepeat":"none"},"ActiveAuras":{"isAura":true,"ignoreSelf":false,"hidden":false,"height":false,"alignment":"","type":"","aura":"None","radius":5,"save":"","savedc":null,"hostile":false,"onlyOnce":false,"time":"None"}},"changes":[{"key":"system.attributes.movement.walk","value":"0.5","mode":1,"priority":20}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null,"combat":null},"icon":"icons/svg/aura.svg","label":"Slow Aura","origin":"Actor.9n8trQy9SuvzNdMv","tint":null,"transfer":true},{"_id":"nrvryBcBA41UaSii","flags":{"dae":{"macroRepeat":"none","specialDuration":["turnEnd"]},"ActiveAuras":{"isAura":true,"ignoreSelf":false,"hidden":false,"height":false,"alignment":"","type":"","aura":"None","radius":5,"save":"","savedc":null,"hostile":true,"onlyOnce":true,"time":"None"}},"changes":[{"key":"macro.execute","value":"\"AA Cloudkill\" 15","mode":0,"priority":20}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null,"combat":null},"icon":"icons/svg/aura.svg","label":"Cloudkill (number in effect value is the DC) requires Times Up","origin":"Actor.9n8trQy9SuvzNdMv","tint":null,"transfer":true}],"system":{"abilities":{"str":{"value":10,"proficient":0,"bonuses":{"check":"","save":""}},"dex":{"value":10,"proficient":0,"bonuses":{"check":"","save":""}},"con":{"value":10,"proficient":0,"bonuses":{"check":"","save":""}},"int":{"value":10,"proficient":0,"bonuses":{"check":"","save":""}},"wis":{"value":10,"proficient":0,"bonuses":{"check":"","save":""}},"cha":{"value":10,"proficient":0,"bonuses":{"check":"","save":""}}},"attributes":{"ac":{"flat":10,"calc":"flat","formula":""},"hp":{"value":10,"min":0,"max":10,"temp":null,"tempmax":null},"init":{"value":0,"bonus":0},"movement":{"burrow":0,"climb":0,"fly":0,"swim":0,"walk":30,"units":"ft","hover":false},"attunement":{"max":3},"senses":{"darkvision":0,"blindsight":0,"tremorsense":0,"truesight":0,"units":"ft","special":""},"spellcasting":"int","death":{"success":0,"failure":0},"exhaustion":0,"inspiration":false,"encumbrance":{"value":null,"max":null}},"details":{"biography":{"value":"","public":""},"alignment":"","race":"","background":"","originalClass":"","xp":{"value":0,"min":0,"max":300},"appearance":"","trait":"","ideal":"","bond":"","flaw":""},"traits":{"size":"med","di":{"value":[],"custom":""},"dr":{"value":[],"custom":""},"dv":{"value":[],"custom":""},"ci":{"value":[],"custom":""},"languages":{"value":[],"custom":""},"weaponProf":{"value":[],"custom":""},"armorProf":{"value":[],"custom":""},"toolProf":{"value":[],"custom":""}},"currency":{"pp":0,"gp":0,"ep":0,"sp":0,"cp":0},"skills":{"acr":{"value":0,"ability":"dex","bonuses":{"check":"","passive":""}},"ani":{"value":0,"ability":"wis","bonuses":{"check":"","passive":""}},"arc":{"value":0,"ability":"int","bonuses":{"check":"","passive":""}},"ath":{"value":0,"ability":"str","bonuses":{"check":"","passive":""}},"dec":{"value":0,"ability":"cha","bonuses":{"check":"","passive":""}},"his":{"value":0,"ability":"int","bonuses":{"check":"","passive":""}},"ins":{"value":0,"ability":"wis","bonuses":{"check":"","passive":""}},"itm":{"value":0,"ability":"cha","bonuses":{"check":"","passive":""}},"inv":{"value":0,"ability":"int","bonuses":{"check":"","passive":""}},"med":{"value":0,"ability":"wis","bonuses":{"check":"","passive":""}},"nat":{"value":0,"ability":"int","bonuses":{"check":"","passive":""}},"prc":{"value":0,"ability":"wis","bonuses":{"check":"","passive":""}},"prf":{"value":0,"ability":"cha","bonuses":{"check":"","passive":""}},"per":{"value":0,"ability":"cha","bonuses":{"check":"","passive":""}},"rel":{"value":0,"ability":"int","bonuses":{"check":"","passive":""}},"slt":{"value":0,"ability":"dex","bonuses":{"check":"","passive":""}},"ste":{"value":0,"ability":"dex","bonuses":{"check":"","passive":""}},"sur":{"value":0,"ability":"wis","bonuses":{"check":"","passive":""}}},"spells":{"spell1":{"value":0,"override":null},"spell2":{"value":0,"override":null},"spell3":{"value":0,"override":null},"spell4":{"value":0,"override":null},"spell5":{"value":0,"override":null},"spell6":{"value":0,"override":null},"spell7":{"value":0,"override":null},"spell8":{"value":0,"override":null},"spell9":{"value":0,"override":null},"pact":{"value":0,"override":null}},"bonuses":{"mwak":{"attack":"","damage":""},"rwak":{"attack":"","damage":""},"msak":{"attack":"","damage":""},"rsak":{"attack":"","damage":""},"abilities":{"check":"","save":"","skill":""},"spell":{"dc":""}},"resources":{"primary":{"value":null,"max":null,"sr":false,"lr":false,"label":""},"secondary":{"value":null,"max":null,"sr":false,"lr":false,"label":""},"tertiary":{"value":null,"max":null,"sr":false,"lr":false,"label":""}}},"ownership":{"default":0,"E4BVikjIkVl2lL2j":3},"prototypeToken":{"flags":{},"name":"Zone Effects","displayName":0,"width":1,"height":1,"lockRotation":false,"rotation":0,"actorLink":true,"disposition":1,"displayBars":0,"bar1":{"attribute":"attributes.hp"},"bar2":{"attribute":null},"randomImg":false,"light":{"dim":0,"bright":0,"angle":360,"alpha":1,"animation":{"speed":5,"intensity":5,"type":null,"reverse":false},"color":null,"coloration":1,"attenuation":0.5,"luminosity":0.5,"saturation":0,"contrast":0,"shadows":0,"darkness":{"min":0,"max":1}},"texture":{"src":"icons/svg/mystery-man.svg","tint":null,"scaleX":1,"scaleY":1,"offsetX":0,"offsetY":0,"rotation":0},"sight":{"angle":360,"enabled":true,"range":30,"brightness":0,"visionMode":"basic","color":null,"attenuation":0.1,"saturation":0,"contrast":0},"alpha":1,"detectionModes":[]},"img":"icons/svg/mystery-man.svg","folder":null,"_stats":{"systemId":"dnd5e","systemVersion":"2.0.1","coreVersion":"10.283","createdTime":1662274930116,"modifiedTime":1662274930149,"lastModifiedBy":"GrX6tFOozYFBnDHq"}} diff --git a/packs/macros b/packs/macros index e1f42dc..8d2b0bd 100644 --- a/packs/macros +++ b/packs/macros @@ -1,3 +1,3 @@ -{"name":"AA DoT","permission":{"default":0,"E4BVikjIkVl2lL2j":3},"type":"script","flags":{"furnace":{"runAsGM":false},"core":{"sourceId":"Macro.nsfe0IvUwY7JP4ya"}},"scope":"global","command":"if(args[0] === \"on\") {\nlet damageRoll = new Roll(`${args[1]}`).roll().toMessage({flavor: `${args[2]}`})\n}","author":"E4BVikjIkVl2lL2j","img":"icons/svg/dice-target.svg","actorIds":[],"_id":"TWJ7K9Rw384oXFxV"} -{"_id":"UwyXiTHK3Q518uLm","name":"AA ApplyEffectsToTemplate","type":"script","author":"E4BVikjIkVl2lL2j","img":"icons/svg/dice-target.svg","scope":"global","command":"//On use macro to apply an item's active effects to the placed template instead of applying through the normal midi QoL workflow \n// requires midi qol, advanced macros and Active Auras \nAAhelpers.applyTemplate()","folder":null,"sort":0,"permission":{"default":0,"E4BVikjIkVl2lL2j":3},"flags":{"furnace":{"runAsGM":false},"combat-utility-belt":{"macroTrigger":""},"core":{"sourceId":"Macro.fHwT4N13IcbW4D3I"},"advanced-macros":{"runAsGM":false}}} -{"name":"AA Apply effect to drawing","type":"script","author":"jkdToQqzFl0mFXls","img":"icons/svg/dice-target.svg","scope":"global","command":"// Standard Hotbar macro to apply an active effect from a given actor (default Zone Effects) to a selected drawing\n// Change the actor name as required \n\n\nlet effectActor = game.actors.getName(\"\")\nlet effects = effectActor.data.effects.contents\nlet drawing = canvas.drawings.controlled[0]\nlet content\nfor ( let effect of effects) {\ncontent += ``;\n}\nnew Dialog({\n title: 'Example',\n content: `\n
\n
\n \n
\n
\n `,\n buttons: {\n apply: {\n label: \"Apply\",\n callback: async (html) => {\n let effectId = html.find('#effect').val();\n let effect = effects.find(i => i.id === effectId)\n effect.data.origin = `Actor.${effectActor.id}`\n await drawing.document.setFlag(\"ActiveAuras\", \"IsAura\", [effect.data])\n AAhelpers.UserCollateAuras(canvas.scene.id, true, false, \"drawingAdd\")\n }\n }\n }\n}).render(true)","folder":null,"sort":0,"permission":{"default":0,"E4BVikjIkVl2lL2j":3,"jkdToQqzFl0mFXls":3},"flags":{"furnace":{"runAsGM":false},"combat-utility-belt":{"macroTrigger":""},"core":{"sourceId":"Macro.RukVihRspPDZRSKM"},"advanced-macros":{"runAsGM":false}},"_id":"r8hqRcIOLk3ymtUW"} +{"name":"AA DoT","type":"script","flags":{"furnace":{"runAsGM":false},"core":{"sourceId":"Macro.nsfe0IvUwY7JP4ya"}},"scope":"global","command":"if(args[0] === \"on\") {\nlet damageRoll = new Roll(`${args[1]}`).roll().toMessage({flavor: `${args[2]}`})\n}","author":"E4BVikjIkVl2lL2j","img":"icons/svg/dice-target.svg","_id":"TWJ7K9Rw384oXFxV","ownership":{"default":0,"E4BVikjIkVl2lL2j":3},"folder":null,"sort":0,"_stats":{"systemId":"dnd5e","systemVersion":"2.0.1","coreVersion":"10.283","createdTime":1662274930162,"modifiedTime":1662274930162,"lastModifiedBy":"GrX6tFOozYFBnDHq"}} +{"_id":"UwyXiTHK3Q518uLm","name":"AA ApplyEffectsToTemplate","type":"script","author":"E4BVikjIkVl2lL2j","img":"icons/svg/dice-target.svg","scope":"global","command":"//On use macro to apply an item's active effects to the placed template instead of applying through the normal midi QoL workflow \n// requires midi qol, advanced macros and Active Auras \nAAhelpers.applyTemplate()","folder":null,"sort":0,"flags":{"furnace":{"runAsGM":false},"combat-utility-belt":{"macroTrigger":""},"core":{"sourceId":"Macro.fHwT4N13IcbW4D3I"},"advanced-macros":{"runAsGM":false}},"ownership":{"default":0,"E4BVikjIkVl2lL2j":3},"_stats":{"systemId":"dnd5e","systemVersion":"2.0.1","coreVersion":"10.283","createdTime":1662274930163,"modifiedTime":1662274930163,"lastModifiedBy":"GrX6tFOozYFBnDHq"}} +{"name":"AA Apply effect to drawing","type":"script","author":"jkdToQqzFl0mFXls","img":"icons/svg/dice-target.svg","scope":"global","command":"// Standard Hotbar macro to apply an active effect from a given actor (default Zone Effects) to a selected drawing\n// Change the actor name as required \n\n\nlet effectActor = game.actors.getName(\"\")\nlet effects = effectActor.data.effects.contents\nlet drawing = canvas.drawings.controlled[0]\nlet content\nfor ( let effect of effects) {\ncontent += ``;\n}\nnew Dialog({\n title: 'Example',\n content: `\n
\n
\n \n
\n
\n `,\n buttons: {\n apply: {\n label: \"Apply\",\n callback: async (html) => {\n let effectId = html.find('#effect').val();\n let effect = effects.find(i => i.id === effectId)\n effect.data.origin = `Actor.${effectActor.id}`\n await drawing.document.setFlag(\"ActiveAuras\", \"IsAura\", [effect.data])\n AAhelpers.UserCollateAuras(canvas.scene.id, true, false, \"drawingAdd\")\n }\n }\n }\n}).render(true)","folder":null,"sort":0,"flags":{"furnace":{"runAsGM":false},"combat-utility-belt":{"macroTrigger":""},"core":{"sourceId":"Macro.RukVihRspPDZRSKM"},"advanced-macros":{"runAsGM":false}},"_id":"r8hqRcIOLk3ymtUW","ownership":{"default":0,"E4BVikjIkVl2lL2j":3,"jkdToQqzFl0mFXls":3},"_stats":{"systemId":"dnd5e","systemVersion":"2.0.1","coreVersion":"10.283","createdTime":1662274930165,"modifiedTime":1662274930165,"lastModifiedBy":"GrX6tFOozYFBnDHq"}} From 97c32c84b00637e47f2a79c2b027786c15deaf93 Mon Sep 17 00:00:00 2001 From: Chris Seieroe Date: Sun, 4 Sep 2022 00:09:29 -0700 Subject: [PATCH 06/31] update Apply effect to drawing macro remove data uses in that macro --- packs/macros | 1 + 1 file changed, 1 insertion(+) diff --git a/packs/macros b/packs/macros index 8d2b0bd..7a29ab5 100644 --- a/packs/macros +++ b/packs/macros @@ -1,3 +1,4 @@ {"name":"AA DoT","type":"script","flags":{"furnace":{"runAsGM":false},"core":{"sourceId":"Macro.nsfe0IvUwY7JP4ya"}},"scope":"global","command":"if(args[0] === \"on\") {\nlet damageRoll = new Roll(`${args[1]}`).roll().toMessage({flavor: `${args[2]}`})\n}","author":"E4BVikjIkVl2lL2j","img":"icons/svg/dice-target.svg","_id":"TWJ7K9Rw384oXFxV","ownership":{"default":0,"E4BVikjIkVl2lL2j":3},"folder":null,"sort":0,"_stats":{"systemId":"dnd5e","systemVersion":"2.0.1","coreVersion":"10.283","createdTime":1662274930162,"modifiedTime":1662274930162,"lastModifiedBy":"GrX6tFOozYFBnDHq"}} {"_id":"UwyXiTHK3Q518uLm","name":"AA ApplyEffectsToTemplate","type":"script","author":"E4BVikjIkVl2lL2j","img":"icons/svg/dice-target.svg","scope":"global","command":"//On use macro to apply an item's active effects to the placed template instead of applying through the normal midi QoL workflow \n// requires midi qol, advanced macros and Active Auras \nAAhelpers.applyTemplate()","folder":null,"sort":0,"flags":{"furnace":{"runAsGM":false},"combat-utility-belt":{"macroTrigger":""},"core":{"sourceId":"Macro.fHwT4N13IcbW4D3I"},"advanced-macros":{"runAsGM":false}},"ownership":{"default":0,"E4BVikjIkVl2lL2j":3},"_stats":{"systemId":"dnd5e","systemVersion":"2.0.1","coreVersion":"10.283","createdTime":1662274930163,"modifiedTime":1662274930163,"lastModifiedBy":"GrX6tFOozYFBnDHq"}} {"name":"AA Apply effect to drawing","type":"script","author":"jkdToQqzFl0mFXls","img":"icons/svg/dice-target.svg","scope":"global","command":"// Standard Hotbar macro to apply an active effect from a given actor (default Zone Effects) to a selected drawing\n// Change the actor name as required \n\n\nlet effectActor = game.actors.getName(\"\")\nlet effects = effectActor.data.effects.contents\nlet drawing = canvas.drawings.controlled[0]\nlet content\nfor ( let effect of effects) {\ncontent += ``;\n}\nnew Dialog({\n title: 'Example',\n content: `\n
\n
\n \n
\n
\n `,\n buttons: {\n apply: {\n label: \"Apply\",\n callback: async (html) => {\n let effectId = html.find('#effect').val();\n let effect = effects.find(i => i.id === effectId)\n effect.data.origin = `Actor.${effectActor.id}`\n await drawing.document.setFlag(\"ActiveAuras\", \"IsAura\", [effect.data])\n AAhelpers.UserCollateAuras(canvas.scene.id, true, false, \"drawingAdd\")\n }\n }\n }\n}).render(true)","folder":null,"sort":0,"flags":{"furnace":{"runAsGM":false},"combat-utility-belt":{"macroTrigger":""},"core":{"sourceId":"Macro.RukVihRspPDZRSKM"},"advanced-macros":{"runAsGM":false}},"_id":"r8hqRcIOLk3ymtUW","ownership":{"default":0,"E4BVikjIkVl2lL2j":3,"jkdToQqzFl0mFXls":3},"_stats":{"systemId":"dnd5e","systemVersion":"2.0.1","coreVersion":"10.283","createdTime":1662274930165,"modifiedTime":1662274930165,"lastModifiedBy":"GrX6tFOozYFBnDHq"}} +{"name":"AA Apply effect to drawing","type":"script","author":"jkdToQqzFl0mFXls","img":"icons/svg/dice-target.svg","scope":"global","command":"// Standard Hotbar macro to apply an active effect from a given actor (default Zone Effects) to a selected drawing\n// Change the actor name as required \n\n\nlet effectActor = game.actors.getName(\"\")\nlet effects = effectActor.effects.contents\nlet drawing = canvas.drawings.controlled[0]\nlet content\nfor ( let effect of effects) {\ncontent += ``;\n}\nnew Dialog({\n title: 'Example',\n content: `\n
\n
\n \n
\n
\n `,\n buttons: {\n apply: {\n label: \"Apply\",\n callback: async (html) => {\n let effectId = html.find('#effect').val();\n let effect = effects.find(i => i.id === effectId)\n effect.origin = `Actor.${effectActor.id}`\n await drawing.document.setFlag(\"ActiveAuras\", \"IsAura\", [effect.data])\n AAhelpers.UserCollateAuras(canvas.scene.id, true, false, \"drawingAdd\")\n }\n }\n }\n}).render(true)","folder":null,"sort":0,"flags":{"furnace":{"runAsGM":false},"combat-utility-belt":{"macroTrigger":""},"core":{"sourceId":"Macro.RukVihRspPDZRSKM"},"advanced-macros":{"runAsGM":false}},"_id":"r8hqRcIOLk3ymtUW","ownership":{"default":0,"E4BVikjIkVl2lL2j":3,"jkdToQqzFl0mFXls":3},"_stats":{"systemId":"dnd5e","systemVersion":"2.0.1","coreVersion":"10.283","createdTime":1662274930165,"modifiedTime":1662275271741,"lastModifiedBy":"GrX6tFOozYFBnDHq"}} From 4beb33197fa1c1bbab95edb955e181da3f761327 Mon Sep 17 00:00:00 2001 From: Jack Holloway Date: Sun, 23 Oct 2022 18:43:53 +0100 Subject: [PATCH 07/31] v0.4.1 --- module.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/module.json b/module.json index 7c0f0b0..7ab2aee 100644 --- a/module.json +++ b/module.json @@ -8,7 +8,7 @@ "url": "https://github.com/kandashi/Active-Auras", "bugs": "https://github.com/kandashi/Active-Auras/issues", "flags": {}, - "version": "0.4.0", + "version": "0.4.1", "compatibility": { "minimum": 10, "verified": 10 @@ -98,4 +98,4 @@ "download": "https://github.com/kandashi/Active-Auras/archive/main.zip", "protected": false, "coreTranslation": false -} \ No newline at end of file +} From e0e1134fb3a6288b2d05f68fa6519db124488aab Mon Sep 17 00:00:00 2001 From: Jack Holloway Date: Sun, 23 Oct 2022 18:47:52 +0100 Subject: [PATCH 08/31] v0.4.14 --- module.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module.json b/module.json index 7ab2aee..ebcbf60 100644 --- a/module.json +++ b/module.json @@ -8,7 +8,7 @@ "url": "https://github.com/kandashi/Active-Auras", "bugs": "https://github.com/kandashi/Active-Auras/issues", "flags": {}, - "version": "0.4.1", + "version": "0.4.14", "compatibility": { "minimum": 10, "verified": 10 From e6c89482a51df17c7a199be404985c9da0c8ad29 Mon Sep 17 00:00:00 2001 From: Jack Holloway Date: Mon, 24 Oct 2022 23:25:51 +0100 Subject: [PATCH 09/31] Initial fixes for template effects --- src/AAhelpers.js | 123 ++++++++++++++++++++++------------------ src/collateAuras.js | 62 ++++++++++---------- src/measureDistances.js | 21 ++++--- 3 files changed, 108 insertions(+), 98 deletions(-) diff --git a/src/AAhelpers.js b/src/AAhelpers.js index f15304b..a8bbc21 100644 --- a/src/AAhelpers.js +++ b/src/AAhelpers.js @@ -26,18 +26,18 @@ class AAhelpers { for (let effect of MapObject.effects) { if (effect.entityId === tokenID) return true; } - return false + return false; } static DispositionCheck(auraTargets, auraDis, tokenDis) { switch (auraTargets) { case "Allies": { - if (auraDis !== tokenDis) return false - else return true + if (auraDis !== tokenDis) return false; + else return true; } case "Enemy": { - if (auraDis === tokenDis) return false - else return true + if (auraDis === tokenDis) return false; + else return true; } case "All": return true; } @@ -45,9 +45,11 @@ class AAhelpers { static CheckType(canvasToken, type) { switch (game.system.id) { - case ("dnd5e"): ; - case ("sw5e"): return AAhelpers.typeCheck5e(canvasToken, type) - case ("swade"): return AAhelpers.typeCheckSWADE(canvasToken, type); + case ("dnd5e"): + case ("sw5e"): + return AAhelpers.typeCheck5e(canvasToken, type); + case ("swade"): + return AAhelpers.typeCheckSWADE(canvasToken, type); } } static typeCheck5e(canvasToken, type) { @@ -57,7 +59,7 @@ class AAhelpers { try { tokenType = [canvasToken.actor?.system.details.type.value, canvasToken.actor?.system.details.type.custom]; } catch (error) { - console.error([`ActiveAuras: the token has an unreadable type`, canvasToken]) + console.error([`ActiveAuras: the token has an unreadable type`, canvasToken]); } } break; @@ -68,7 +70,7 @@ class AAhelpers { } else tokenType = [canvasToken.actor?.system.details.race.toLowerCase().replace("-", " ").split(" ")]; } catch (error) { - console.error([`ActiveAuras: the token has an unreadable type`, canvasToken]) + console.error([`ActiveAuras: the token has an unreadable type`, canvasToken]); } } break; @@ -80,16 +82,16 @@ class AAhelpers { } else humanoidRaces = ["human", "orc", "elf", "tiefling", "gnome", "aaracokra", "dragonborn", "dwarf", "halfling", "leonin", "satyr", "genasi", "goliath", "aasimar", "bugbear", "firbolg", "goblin", "lizardfolk", "tabxi", "triton", "yuan-ti", "tortle", "changling", "kalashtar", "shifter", "warforged", "gith", "centaur", "loxodon", "minotaur", "simic hybrid", "vedalken", "verdan", "locathah", "grung"]; - if (tokenType.includes(type)) return true + if (tokenType.includes(type)) return true; for (let x of tokenType) { if (humanoidRaces.includes(x)) { - tokenType = "humanoid" + tokenType = "humanoid"; continue; } } if (tokenType === type || tokenType === "any") return true; - return false + return false; } static typeCheckSWADE(canvasToken, type) { @@ -99,7 +101,7 @@ class AAhelpers { try { tokenType = canvasToken.actor?.system.details.species.name.toLowerCase(); } catch (error) { - console.error([`ActiveAuras: the token has an unreadable type`, canvasToken]) + console.error([`ActiveAuras: the token has an unreadable type`, canvasToken]); } } break; @@ -107,37 +109,37 @@ class AAhelpers { try { tokenType = canvasToken.actor?.system.details.species.name.toLowerCase(); } catch (error) { - console.error([`ActiveAuras: the token has an unreadable type`, canvasToken]) + console.error([`ActiveAuras: the token has an unreadable type`, canvasToken]); } } break; case "vehicle": return; } - return tokenType === type + return tokenType === type; } static Wildcard(canvasToken, wildcard, extra) { - if (game.system.id !== "swade") return true - let Wild = canvasToken.actor.isWildcard - if (Wild && wildcard) return true - else if (!Wild && extra) return true - else return false + if (game.system.id !== "swade") return true; + let Wild = canvasToken.actor.isWildcard; + if (Wild && wildcard) return true; + else if (!Wild && extra) return true; + else return false; } static HPCheck(entity) { - let actor = entity.actor - if(entity.collectionName === "actors") actor = entity + let actor = entity.actor; + if(entity.collectionName === "actors") actor = entity; switch (game.system.id) { case "dnd5e": ; case "sw5e": { - if (getProperty(actor, "system.attributes.hp.max") === 0) return true - if (getProperty(actor, "system.attributes.hp.value") <= 0) return false - else return true + if (getProperty(actor, "system.attributes.hp.max") === 0) return true; + if (getProperty(actor, "system.attributes.hp.value") <= 0) return false; + else return true; } case "swade": { - let { max, value, ignored } = actor.system.wounds - if (value - ignored >= max) return false - else return true + let { max, value, ignored } = actor.system.wounds; + if (value - ignored >= max) return false; + else return true; } } } @@ -147,15 +149,15 @@ class AAhelpers { let MapKey = sceneID; let MapObject = AuraMap.get(MapKey); let effectArray = MapObject.effects.filter(e => e.entityId !== entityId); - AuraMap.set(MapKey, { effects: effectArray }) - AAhelpers.RemoveAppliedAuras(canvas) + AuraMap.set(MapKey, { effects: effectArray }); + AAhelpers.RemoveAppliedAuras(canvas); } static async RemoveAppliedAuras() { let EffectsArray = []; - let MapKey = canvas.scene.id - let MapObject = AuraMap.get(MapKey) - MapObject.effects.forEach(i => EffectsArray.push(i.data.origin)) + let MapKey = canvas.scene.id; + let MapObject = AuraMap.get(MapKey); + MapObject.effects.forEach(i => EffectsArray.push(i.data.origin)); for (let removeToken of canvas.tokens.placeables) { if (removeToken?.actor?.effects.size > 0) { @@ -186,8 +188,8 @@ class AAhelpers { } static UserCollateAuras(sceneID, checkAuras, removeAuras, source) { - let AAGM = game.users.find((u) => u.isGM && u.active) - AAsocket.executeAsUser("userCollate", AAGM.id, sceneID, checkAuras, removeAuras, source) + let AAGM = game.users.find((u) => u.isGM && u.active); + AAsocket.executeAsUser("userCollate", AAGM.id, sceneID, checkAuras, removeAuras, source); } /** @@ -195,11 +197,11 @@ class AAhelpers { */ static applyWrapper(wrapped, ...args) { - let actor = args[0] - let change = args[1] + let actor = args[0]; + let change = args[1]; if (change.effect.flags?.ActiveAuras?.ignoreSelf) { console.log(game.i18n.format("ACTIVEAURAS.IgnoreSelfLog", { effectDataLabel: change.effect.label, changeKey: change.key, actorName: actor.name })); - args[1] = {} + args[1] = {}; return wrapped(...args); } return wrapped(...args) @@ -226,8 +228,8 @@ class AAhelpers { static async applyTemplate(args) { - let duration - const convertedDuration = globalThis.DAE.convertDuration(args[0].itemData.data.duration, true); + let duration; + const convertedDuration = globalThis.DAE.convertDuration(args[0].itemData.system.duration, true); if (convertedDuration?.type === "seconds") { duration = { seconds: convertedDuration.seconds, startTime: game.time.worldTime }; } @@ -239,26 +241,35 @@ class AAhelpers { startTurn: game.combat?.turn }; } - let template = canvas.templates.get(args[0].templateId) - let disposition = args[0].actor.token.disposition - let effects = args[0].item.effects - let templateEffectData = [] + let template = canvas.templates.get(args[0].templateId); + let disposition = args[0].actor.token?.disposition ?? args[0].actor.prototypeToken?.disposition; + let effects = args[0].item.effects; + let templateEffectData = []; for (let effect of effects) { - let data = { data: duplicate(effect), parentActorId: false, parentActorLink: false, entityType: "template", entityId: template.id, casterDisposition: disposition, castLevel: args[0].spellLevel } - if (effect.flags["ActiveAuras"].displayTemp) { data.data.duration = duration } - data.data.origin = `Actor.${args[0].actor._id}.Item.${args[0].item._id}` - templateEffectData.push(data) + let data = { + data: duplicate(effect), + parentActorId: false, + parentActorLink: false, + entityType: "template", + entityId: template.id, + casterDisposition: disposition, + castLevel: args[0].spellLevel + }; + if (effect.flags["ActiveAuras"].displayTemp) data.data.duration = duration; + data.data.origin = `Actor.${args[0].actor._id}.Item.${args[0].item._id}`; + templateEffectData.push(data); } - await template.document.setFlag("ActiveAuras", "IsAura", templateEffectData) - AAhelpers.UserCollateAuras(canvas.scene.id, true, false, "spellCast") - return { haltEffectsApplication: true } + console.log("Applying template effect", templateEffectData); + await template.document.setFlag("ActiveAuras", "IsAura", templateEffectData); + AAhelpers.UserCollateAuras(canvas.scene.id, true, false, "spellCast"); + return { haltEffectsApplication: true }; } static async removeAurasOnToken(token){ - if(!token.document.actorLink) return - let auras = token.actor.effects.filter(i => i.flags?.["ActiveAuras"]?.applied).map(i => i.id) - if(!auras) return - await token.actor.deleteEmbeddedDocuments("ActiveEffect", auras) + if(!token.document.actorLink) return; + let auras = token.actor.effects.filter(i => i.flags?.["ActiveAuras"]?.applied).map(i => i.id); + if(!auras) return; + await token.actor.deleteEmbeddedDocuments("ActiveEffect", auras); } } diff --git a/src/collateAuras.js b/src/collateAuras.js index 954d844..160d455 100644 --- a/src/collateAuras.js +++ b/src/collateAuras.js @@ -78,70 +78,70 @@ async function CollateAuras(sceneID, checkAuras, removeAuras, source) { } function RetrieveTemplateAuras(effectArray) { - let auraTemplates = canvas.templates.placeables.filter(i => i.flags?.ActiveAuras?.IsAura !== undefined) + let auraTemplates = canvas.templates.placeables.filter(i => i.document.flags?.ActiveAuras?.IsAura !== undefined); for (let template of auraTemplates) { - for (let testEffect of template.data.flags?.ActiveAuras?.IsAura) { + for (let testEffect of template.document.flags?.ActiveAuras?.IsAura) { if (testEffect.disabled) continue; - let newEffect = duplicate(testEffect) + let newEffect = duplicate(testEffect); const parts = testEffect.data.origin.split(".") const [entityName, entityId, embeddedName, embeddedId] = parts; - let actor = game.actors.get(entityId) - let rollData = actor.getRollData() - rollData["item.level"] = getProperty(testEffect, "castLevel") - Object.assign(rollData, { item: { level: testEffect.castLevel } }) - let re = /@[\w\.]+/g + let actor = game.actors.get(entityId); + let rollData = actor.getRollData(); + rollData["item.level"] = getProperty(testEffect, "castLevel"); + Object.assign(rollData, { item: { level: testEffect.castLevel } }); + let re = /@[\w\.]+/g; for (let change of newEffect.data.changes) { - if (typeof change.value !== "string") continue - let s = change.value - for (let match of s.match(re) || []) s = s.replace(match, getProperty(rollData, match.slice(1))) - change.value = s - if (change.key === "macro.execute" || change.key === "macro.itemMacro") newEffect.data.flags.ActiveAuras.isMacro = true + if (typeof change.value !== "string") continue; + let s = change.value; + for (let match of s.match(re) || []) s = s.replace(match, getProperty(rollData, match.slice(1))); + change.value = s; + if (change.key === "macro.execute" || change.key === "macro.itemMacro") newEffect.data.flags.ActiveAuras.isMacro = true; } - newEffect.disabled = false + newEffect.disabled = false; let macro = newEffect.data.flags.ActiveAuras.isMacro !== undefined ? newEffect.data.flags.ActiveAuras.isMacro : false; newEffect.data.flags.ActiveAuras.isAura = false; newEffect.data.flags.ActiveAuras.applied = true; newEffect.data.flags.ActiveAuras.isMacro = macro; newEffect.data.flags.ActiveAuras.ignoreSelf = false; - effectArray.push(newEffect) + effectArray.push(newEffect); } } - return effectArray + return effectArray; } function RetrieveDrawingAuras(effectArray) { if (!effectArray) effectArray = AuraMap.get(canvas.scene._id)?.effects; - let auraDrawings = canvas.drawings.placeables.filter(i => i.flags?.ActiveAuras?.IsAura !== undefined) + let auraDrawings = canvas.drawings.placeables.filter(i => i.document.flags?.ActiveAuras?.IsAura !== undefined); for (let drawing of auraDrawings) { for (let testEffect of drawing.data.flags?.ActiveAuras?.IsAura) { if (testEffect.disabled) continue; - let newEffect = { data: duplicate(testEffect), parentActorId: false, parentActorLink: false, entityType: "drawing", entityId: drawing.id, } - const parts = testEffect.origin.split(".") + let newEffect = { data: duplicate(testEffect), parentActorId: false, parentActorLink: false, entityType: "drawing", entityId: drawing.id, }; + const parts = testEffect.origin.split("."); const [entityName, entityId, embeddedName, embeddedId] = parts; - let actor = game.actors.get(entityId) + let actor = game.actors.get(entityId); if (!!actor) { - let rollData = actor.getRollData() + let rollData = actor.getRollData(); for (let change of newEffect.data.changes) { - if (typeof change.value !== "string") continue - let re = /@[\w\.]+/g - let s = change.value - for (let match of s.match(re) || []) s = s.replace(match, getProperty(rollData, match.slice(1))) - change.value = s - if (change.key === "macro.execute" || change.key === "macro.itemMacro") newEffect.data.flags.ActiveAuras.isMacro = true + if (typeof change.value !== "string") continue; + let re = /@[\w\.]+/g; + let s = change.value; + for (let match of s.match(re) || []) s = s.replace(match, getProperty(rollData, match.slice(1))); + change.value = s; + if (change.key === "macro.execute" || change.key === "macro.itemMacro") newEffect.data.flags.ActiveAuras.isMacro = true; } } - newEffect.disabled = false + newEffect.disabled = false; let macro = newEffect.data.flags.ActiveAuras.isMacro !== undefined ? newEffect.data.flags.ActiveAuras.isMacro : false; newEffect.data.flags.ActiveAuras.isAura = false; newEffect.data.flags.ActiveAuras.applied = true; newEffect.data.flags.ActiveAuras.isMacro = macro; newEffect.data.flags.ActiveAuras.ignoreSelf = false; - effectArray.push(newEffect) + effectArray.push(newEffect); } } - return effectArray -} \ No newline at end of file + return effectArray; +} diff --git a/src/measureDistances.js b/src/measureDistances.js index 4b86824..ec72b0b 100644 --- a/src/measureDistances.js +++ b/src/measureDistances.js @@ -79,19 +79,19 @@ class AAmeasure { } static isTokenInside(templateDetails, token, wallsBlockTargeting = false) { - const grid = canvas?.scene?.data.grid; + const grid = canvas?.scene?.grid; if (!grid) return false; const templatePos = { x: templateDetails.x, y: templateDetails.y }; // Check for center of each square the token uses. // e.g. for large tokens all 4 squares - const startX = token.data.width >= 1 ? 0.5 : (token.data.width / 2); - const startY = token.data.height >= 1 ? 0.5 : (token.data.height / 2); - for (let x = startX; x < token.data.width; x++) { - for (let y = startY; y < token.data.height; y++) { + const startX = token.width >= 1 ? 0.5 : (token.width / 2); + const startY = token.height >= 1 ? 0.5 : (token.height / 2); + for (let x = startX; x < token.width; x++) { + for (let y = startY; y < token.height; y++) { const currGrid = { - x: token.data.x + x * grid - templatePos.x, - y: token.data.y + y * grid - templatePos.y, + x: token.x + x * grid - templatePos.x, + y: token.y + y * grid - templatePos.y, }; let contains = templateDetails.shape?.contains(currGrid.x, currGrid.y); if (contains && wallsBlockTargeting) { @@ -121,12 +121,11 @@ class AAmeasure { //@ts-ignore }*/ - contains = !canvas?.walls?.checkCollision(r); + contains = !canvas?.walls?.checkCollision(r); } // Check the distance from origin. - if (contains) - return true; + if (contains) return true; } } return false; @@ -174,4 +173,4 @@ class AAmeasure { } -} \ No newline at end of file +} From 876beb971040515cfc648f1bf96ff9d2c63f1756 Mon Sep 17 00:00:00 2001 From: Jack Holloway Date: Tue, 25 Oct 2022 09:58:25 +0100 Subject: [PATCH 10/31] Data model changes from https://github.com/kandashi/Active-Auras/pull/241/commits/c9900447647babc1f4872ab42f465f50633faa5f --- src/AAhelpers.js | 2 +- src/collateAuras.js | 62 ++++++++++++++++++++--------------------- src/measureDistances.js | 14 +++++----- 3 files changed, 39 insertions(+), 39 deletions(-) diff --git a/src/AAhelpers.js b/src/AAhelpers.js index a8bbc21..3c578e5 100644 --- a/src/AAhelpers.js +++ b/src/AAhelpers.js @@ -259,7 +259,7 @@ class AAhelpers { data.data.origin = `Actor.${args[0].actor._id}.Item.${args[0].item._id}`; templateEffectData.push(data); } - console.log("Applying template effect", templateEffectData); + if (AAdebug) console.log("Applying template effect", templateEffectData); await template.document.setFlag("ActiveAuras", "IsAura", templateEffectData); AAhelpers.UserCollateAuras(canvas.scene.id, true, false, "spellCast"); return { haltEffectsApplication: true }; diff --git a/src/collateAuras.js b/src/collateAuras.js index 160d455..9494d38 100644 --- a/src/collateAuras.js +++ b/src/collateAuras.js @@ -10,11 +10,11 @@ async function CollateAuras(sceneID, checkAuras, removeAuras, source) { if (!AAgm) return; if (sceneID !== canvas.id) return ui.notifications.warn("Collate Auras called on a non viewed scene, auras will be updated when you return to that scene") if (AAdebug) console.log(source) - let MapKey = sceneID; - let MapObject = AuraMap.get(MapKey); - let effectArray = []; - for (let t of canvas.tokens.placeables) { - let testToken = t.document + const MapKey = sceneID; + const MapObject = AuraMap.get(MapKey); + const effectArray = []; + for (const t of canvas.tokens.placeables) { + const testToken = t.document //Skips over null actor tokens if (testToken.actor === null || testToken.actor === undefined) continue; //Skips over MLT coppied tokens @@ -24,17 +24,17 @@ async function CollateAuras(sceneID, checkAuras, removeAuras, source) { if (AAdebug) console.log(`Skipping ${testToken.name}, 0hp`) continue } - for (let testEffect of testToken?.actor?.effects.contents) { + for (const testEffect of testToken?.actor?.effects.contents) { if (testEffect.flags?.ActiveAuras?.isAura) { if (testEffect.disabled) continue; - let newEffect = { data: duplicate(testEffect), parentActorLink: testEffect.parent.prototypeToken.actorLink, parentActorId: testEffect.parent.id, entityType: "token", entityId: testToken.id } - let re = /@[\w\.]+/g - let rollData = testToken.actor.getRollData() + const newEffect = { data: duplicate(testEffect), parentActorLink: testEffect.parent.prototypeToken.actorLink, parentActorId: testEffect.parent.id, entityType: "token", entityId: testToken.id } + const re = /@[\w\.]+/g + const rollData = testToken.actor.getRollData() - for (let change of newEffect.data.changes) { + for (const change of newEffect.data.changes) { if (typeof change.value !== "string") continue - let s = change.value - for (let match of s.match(re) || []) { + const s = change.value + for (const match of s.match(re) || []) { if (s.includes("@@")) { s = s.replace(match, match.slice(1)) } @@ -46,7 +46,7 @@ async function CollateAuras(sceneID, checkAuras, removeAuras, source) { if (change.key === "macro.execute" || change.key === "macro.itemMacro") newEffect.data.flags.ActiveAuras.isMacro = true } newEffect.data.disabled = false - let macro = newEffect.data.flags.ActiveAuras.isMacro !== undefined ? newEffect.data.flags.ActiveAuras.isMacro : false; + const macro = newEffect.data.flags.ActiveAuras.isMacro !== undefined ? newEffect.data.flags.ActiveAuras.isMacro : false; newEffect.data.flags.ActiveAuras.isAura = false; newEffect.data.flags.ActiveAuras.applied = true; newEffect.data.flags.ActiveAuras.isMacro = macro; @@ -78,28 +78,28 @@ async function CollateAuras(sceneID, checkAuras, removeAuras, source) { } function RetrieveTemplateAuras(effectArray) { - let auraTemplates = canvas.templates.placeables.filter(i => i.document.flags?.ActiveAuras?.IsAura !== undefined); + const auraTemplates = canvas.templates.placeables.filter(i => i.document.flags?.ActiveAuras?.IsAura !== undefined); - for (let template of auraTemplates) { - for (let testEffect of template.document.flags?.ActiveAuras?.IsAura) { + for (const template of auraTemplates) { + for (const testEffect of template.document.flags?.ActiveAuras?.IsAura) { if (testEffect.disabled) continue; - let newEffect = duplicate(testEffect); + const newEffect = duplicate(testEffect); const parts = testEffect.data.origin.split(".") const [entityName, entityId, embeddedName, embeddedId] = parts; - let actor = game.actors.get(entityId); - let rollData = actor.getRollData(); + const actor = game.actors.get(entityId); + const rollData = actor.getRollData(); rollData["item.level"] = getProperty(testEffect, "castLevel"); Object.assign(rollData, { item: { level: testEffect.castLevel } }); - let re = /@[\w\.]+/g; - for (let change of newEffect.data.changes) { + const re = /@[\w\.]+/g; + for (const change of newEffect.data.changes) { if (typeof change.value !== "string") continue; - let s = change.value; - for (let match of s.match(re) || []) s = s.replace(match, getProperty(rollData, match.slice(1))); + const s = change.value; + for (const match of s.match(re) || []) s = s.replace(match, getProperty(rollData, match.slice(1))); change.value = s; if (change.key === "macro.execute" || change.key === "macro.itemMacro") newEffect.data.flags.ActiveAuras.isMacro = true; } newEffect.disabled = false; - let macro = newEffect.data.flags.ActiveAuras.isMacro !== undefined ? newEffect.data.flags.ActiveAuras.isMacro : false; + const macro = newEffect.data.flags.ActiveAuras.isMacro !== undefined ? newEffect.data.flags.ActiveAuras.isMacro : false; newEffect.data.flags.ActiveAuras.isAura = false; newEffect.data.flags.ActiveAuras.applied = true; @@ -113,21 +113,21 @@ function RetrieveTemplateAuras(effectArray) { function RetrieveDrawingAuras(effectArray) { if (!effectArray) effectArray = AuraMap.get(canvas.scene._id)?.effects; - let auraDrawings = canvas.drawings.placeables.filter(i => i.document.flags?.ActiveAuras?.IsAura !== undefined); + const auraDrawings = canvas.drawings.placeables.filter(i => i.document.flags?.ActiveAuras?.IsAura !== undefined); - for (let drawing of auraDrawings) { - for (let testEffect of drawing.data.flags?.ActiveAuras?.IsAura) { + for (const drawing of auraDrawings) { + for (const testEffect of drawing.document.flags?.ActiveAuras?.IsAura) { if (testEffect.disabled) continue; - let newEffect = { data: duplicate(testEffect), parentActorId: false, parentActorLink: false, entityType: "drawing", entityId: drawing.id, }; + const newEffect = { data: duplicate(testEffect), parentActorId: false, parentActorLink: false, entityType: "drawing", entityId: drawing.id, }; const parts = testEffect.origin.split("."); const [entityName, entityId, embeddedName, embeddedId] = parts; - let actor = game.actors.get(entityId); + const actor = game.actors.get(entityId); if (!!actor) { let rollData = actor.getRollData(); for (let change of newEffect.data.changes) { if (typeof change.value !== "string") continue; - let re = /@[\w\.]+/g; - let s = change.value; + const re = /@[\w\.]+/g; + const s = change.value; for (let match of s.match(re) || []) s = s.replace(match, getProperty(rollData, match.slice(1))); change.value = s; if (change.key === "macro.execute" || change.key === "macro.itemMacro") newEffect.data.flags.ActiveAuras.isMacro = true; diff --git a/src/measureDistances.js b/src/measureDistances.js index ec72b0b..3e5f153 100644 --- a/src/measureDistances.js +++ b/src/measureDistances.js @@ -79,19 +79,19 @@ class AAmeasure { } static isTokenInside(templateDetails, token, wallsBlockTargeting = false) { - const grid = canvas?.scene?.grid; + const grid = canvas?.scene?.grid?.size; if (!grid) return false; const templatePos = { x: templateDetails.x, y: templateDetails.y }; // Check for center of each square the token uses. // e.g. for large tokens all 4 squares - const startX = token.width >= 1 ? 0.5 : (token.width / 2); - const startY = token.height >= 1 ? 0.5 : (token.height / 2); - for (let x = startX; x < token.width; x++) { - for (let y = startY; y < token.height; y++) { + const startX = token.document.width >= 1 ? 0.5 : (token.document.width / 2); + const startY = token.document.height >= 1 ? 0.5 : (token.document.height / 2); + for (let x = startX; x < token.document.width; x++) { + for (let y = startY; y < token.document.height; y++) { const currGrid = { - x: token.x + x * grid - templatePos.x, - y: token.y + y * grid - templatePos.y, + x: token.document.x + x * grid - templatePos.x, + y: token.document.y + y * grid - templatePos.y, }; let contains = templateDetails.shape?.contains(currGrid.x, currGrid.y); if (contains && wallsBlockTargeting) { From 0b63282aab3a2f3cf0c79185c99588f9982ce431 Mon Sep 17 00:00:00 2001 From: Jack Holloway Date: Tue, 25 Oct 2022 09:59:00 +0100 Subject: [PATCH 11/31] v0.4.15 --- module.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module.json b/module.json index ebcbf60..dbe8c59 100644 --- a/module.json +++ b/module.json @@ -8,7 +8,7 @@ "url": "https://github.com/kandashi/Active-Auras", "bugs": "https://github.com/kandashi/Active-Auras/issues", "flags": {}, - "version": "0.4.14", + "version": "0.4.15", "compatibility": { "minimum": 10, "verified": 10 From c3bc37ff6b8159427b5962921158bd8bf08e4e6a Mon Sep 17 00:00:00 2001 From: Jack Holloway Date: Tue, 25 Oct 2022 10:55:45 +0100 Subject: [PATCH 12/31] fix assingment issue --- src/collateAuras.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/collateAuras.js b/src/collateAuras.js index 9494d38..768af92 100644 --- a/src/collateAuras.js +++ b/src/collateAuras.js @@ -33,7 +33,7 @@ async function CollateAuras(sceneID, checkAuras, removeAuras, source) { for (const change of newEffect.data.changes) { if (typeof change.value !== "string") continue - const s = change.value + let s = change.value for (const match of s.match(re) || []) { if (s.includes("@@")) { s = s.replace(match, match.slice(1)) From 3d13e2537264eb9a113d9a682f436b178c66e761 Mon Sep 17 00:00:00 2001 From: Jack Holloway Date: Tue, 25 Oct 2022 10:56:20 +0100 Subject: [PATCH 13/31] v0.4.16 --- module.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module.json b/module.json index dbe8c59..f6f6e86 100644 --- a/module.json +++ b/module.json @@ -8,7 +8,7 @@ "url": "https://github.com/kandashi/Active-Auras", "bugs": "https://github.com/kandashi/Active-Auras/issues", "flags": {}, - "version": "0.4.15", + "version": "0.4.16", "compatibility": { "minimum": 10, "verified": 10 From f9216a3dcea507aa025102006053ea3e1dd25f5b Mon Sep 17 00:00:00 2001 From: Jack Holloway Date: Tue, 25 Oct 2022 11:28:50 +0100 Subject: [PATCH 14/31] Fix bug when deleting tokens with synthetic actors --- src/AAhelpers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AAhelpers.js b/src/AAhelpers.js index 3c578e5..7f20719 100644 --- a/src/AAhelpers.js +++ b/src/AAhelpers.js @@ -267,7 +267,7 @@ class AAhelpers { } static async removeAurasOnToken(token){ - if(!token.document.actorLink) return; + if(!token.actorLink) return; let auras = token.actor.effects.filter(i => i.flags?.["ActiveAuras"]?.applied).map(i => i.id); if(!auras) return; await token.actor.deleteEmbeddedDocuments("ActiveEffect", auras); From 78cf41b9a96040f7844975e17e4b3524e59eb26e Mon Sep 17 00:00:00 2001 From: Jack Holloway Date: Tue, 25 Oct 2022 11:29:22 +0100 Subject: [PATCH 15/31] Fix up measuring debug functions --- src/measureDistances.js | 110 ++++++++++++++++++++-------------------- 1 file changed, 54 insertions(+), 56 deletions(-) diff --git a/src/measureDistances.js b/src/measureDistances.js index 3e5f153..1b50b61 100644 --- a/src/measureDistances.js +++ b/src/measureDistances.js @@ -1,4 +1,4 @@ -//bounding box check +// bounding box check // center collision check // edges collision check // draw pixi polygon @@ -6,76 +6,75 @@ class AAmeasure { - static inAura(target, source, wallblocking = false, auraHeight, radius, shape) { - const gs = canvas.dimensions.size + const gs = canvas.dimensions.size; const g2 = gs / 2; if (!AAmeasure.boundingCheck(target, source, radius)) return false; - const auraPoly = shape + const auraPoly = shape; if (AAdebug && shape) { - canvas.foreground.children.find(i => i.inAura)?.destroy() - let g = new PIXI.Graphics() - g.beginFill(0, 0.2).drawShape(shape) - let aura = canvas.foreground.addChild(g) - aura.inAura = true + canvas.layers.find((l) => l.name === "DrawingsLayer").children.find(i => i.inAura)?.destroy(); + const g = new PIXI.Graphics(); + g.beginFill(0, 0.2).drawShape(shape); + const aura = canvas.layers.find((l) => l.name === "DrawingsLayer").addChild(g); + aura.inAura = true; } - let sourceCorners = source.document.height === 1 && source.document.width === 1 ? - [{ x: source.center.x, y: source.center.y }] : - [ + let sourceCorners = source.document.height === 1 && source.document.width === 1 + ? [{ x: source.center.x, y: source.center.y }] + : [ { x: source.center.x, y: source.center.y }, { x: source.x + g2, y: source.y + g2 }, { x: source.x + (source.document.width * gs) - g2, y: source.y + g2 }, { x: source.x + g2, y: source.y + (source.document.height * gs) - g2 }, - { x: source.x + (source.document.width * gs) - g2, y: source.y + (source.document.height * gs) - g2 } - ] + { x: source.x + (source.document.width * gs) - g2, y: source.y + (source.document.height * gs) - g2 }, + ]; - let targetCorners = target.document.height === 1 && target.document.width === 1 ? - [{ x: target.center.x, y: target.center.y, collides: false }] : - [ + let targetCorners = target.document.height === 1 && target.document.width === 1 + ? [{ x: target.center.x, y: target.center.y, collides: false }] + : [ { x: target.center.x, y: target.center.y, collides: false }, { x: target.x + g2, y: target.y + g2, collides: false }, { x: target.x + (target.document.width * gs) - g2, y: target.y + g2, collides: false }, { x: target.x + g2, y: target.y + (target.document.height * gs) - g2, collides: false }, - { x: target.x + (target.document.width * gs) - g2, y: target.y + (target.document.height * gs) - g2, collides: false } - ] + { x: target.x + (target.document.width * gs) - g2, y: target.y + (target.document.height * gs) - g2, collides: false }, + ]; + if (AAdebug) { - canvas.foreground.children.filter(i => i.squares)?.forEach(i => i.destroy()) + canvas.layers.find((l) => l.name === "DrawingsLayer").children.filter(i => i.squares)?.forEach(i => i.destroy()); function drawSquare(point) { - let {x, y} = point - let g = new PIXI.Graphics() - g.beginFill(0xFF0000, 0.2).drawRect(x-5, y-5, 10, 10) - let aura = canvas.foreground.addChild(g) - aura.squares = true + const {x, y} = point; + const g = new PIXI.Graphics(); + g.beginFill(0xFF0000, 0.2).drawRect(x-5, y-5, 10, 10); + const aura = canvas.layers.find((l) => l.name === "DrawingsLayer").addChild(g); + aura.squares = true; } - sourceCorners.forEach(i => drawSquare(i)) - targetCorners.forEach(i => drawSquare(i)) - + sourceCorners.forEach(i => drawSquare(i)); + targetCorners.forEach(i => drawSquare(i)); } - for (let t of targetCorners) { + + for (const t of targetCorners) { if (!auraPoly.contains(t.x, t.y)) continue; // quick exit if not in the aura - for (let s of sourceCorners) { - let r = new Ray(t, s) + for (const s of sourceCorners) { + const r = new Ray(t, s) if (wallblocking) { let collision; if (game.modules.get("levels")?.active) { - - collision = _levels.testCollision({ x: t.x, y: t.y, z: target.document.elevation }, { x: s.x, y: s.y, z: source.document.elevation ?? source.flags?.levels?.elevation }, "collision") + collision = _levels.testCollision({ x: t.x, y: t.y, z: target.document.elevation }, { x: s.x, y: s.y, z: source.document.elevation ?? source.flags?.levels?.elevation }, "collision"); } else { - collision = canvas.walls.checkCollision(r) + collision = canvas.walls.checkCollision(r); } - if (collision) continue + if (collision) continue; } if (auraHeight) { - if (!AAmeasure.heightCheck(source, target, radius, r)) continue + if (!AAmeasure.heightCheck(source, target, radius, r)) continue; } - return true + return true; } } - return false + return false; } static isTokenInside(templateDetails, token, wallsBlockTargeting = false) { @@ -134,15 +133,15 @@ class AAmeasure { let distance; switch (game.settings.get("ActiveAuras", "vertical-euclidean")) { case true: { - distance = Math.abs(source.document.elevation - target.document.elevation) + distance = Math.abs(source.document.elevation - target.document.elevation); } break; case false: { - let g = canvas.dimensions - let a = r.distance / g.size * g.distance; - let b = (source.document.elevation - target.document.elevation) - let c = (a * a) + (b * b) - distance = Math.sqrt(c) + const g = canvas.dimensions; + const a = r.distance / g.size * g.distance; + const b = (source.document.elevation - target.document.elevation); + const c = (a * a) + (b * b); + distance = Math.sqrt(c); } } return distance <= radius; @@ -156,21 +155,20 @@ class AAmeasure { * @returns boolean */ static boundingCheck(t1, t2, radius) { - let { size, distance } = canvas.dimensions - let rad = (radius / distance) * size - const xMax = t2.x + rad + t2.w + (size * t1.document.width) - const xMin = t2.x - rad - (size * t1.document.width) - const yMax = t2.y + rad + t2.h + (size * t1.document.height) - const yMin = t2.y - rad - (size * t1.document.height) + const { size, distance } = canvas.dimensions; + const rad = (radius / distance) * size; + const xMax = t2.x + rad + t2.w + (size * t1.document.width); + const xMin = t2.x - rad - (size * t1.document.width); + const yMax = t2.y + rad + t2.h + (size * t1.document.height); + const yMin = t2.y - rad - (size * t1.document.height); if (AAdebug) { - canvas.foreground.children.find(i => i.boundingCheck)?.destroy() - let g = new PIXI.Graphics() - g.beginFill(0, 0.1).drawRect(xMin, yMin, (xMax - xMin), (yMax - yMin)) - let check = canvas.foreground.addChild(g) - check.boundingCheck = true + canvas.layers.find((l) => l.name === "DrawingsLayer").children.find(i => i.boundingCheck)?.destroy(); + let g = new PIXI.Graphics(); + g.beginFill(0, 0.1).drawRect(xMin, yMin, (xMax - xMin), (yMax - yMin)); + let check = canvas.layers.find((l) => l.name === "DrawingsLayer").addChild(g); + check.boundingCheck = true; } return !(t1.x < xMin || t1.x > xMax || t1.y > yMax || t1.y < yMin); } - } From b016145625fb049cedd28082f072c35444b87d84 Mon Sep 17 00:00:00 2001 From: Jack Holloway Date: Tue, 25 Oct 2022 11:33:17 +0100 Subject: [PATCH 16/31] v0.4.17 --- module.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/module.json b/module.json index f6f6e86..7103406 100644 --- a/module.json +++ b/module.json @@ -8,7 +8,7 @@ "url": "https://github.com/kandashi/Active-Auras", "bugs": "https://github.com/kandashi/Active-Auras/issues", "flags": {}, - "version": "0.4.16", + "version": "0.4.17", "compatibility": { "minimum": 10, "verified": 10 @@ -94,8 +94,8 @@ ] }, "socket": true, - "manifest": "https://raw.githubusercontent.com/kandashi/Active-Auras/main/module.json", - "download": "https://github.com/kandashi/Active-Auras/archive/main.zip", + "manifest": "https://raw.githubusercontent.com/MrPrimate/Active-Auras/main/module.json", + "download": "https://github.com/MrPrimate/Active-Auras/archive/main.zip", "protected": false, "coreTranslation": false } From 5fa129fe7d11e2017db2a8a002c613f1dc048174 Mon Sep 17 00:00:00 2001 From: Jack Holloway Date: Thu, 27 Oct 2022 14:53:23 +0100 Subject: [PATCH 17/31] Lagging aura problem Auras lagged a square behind when the system was using the animation when moving tokens. The solution now waits for the token to be in the correct location before continuing --- src/AAhooks.js | 19 ++++++-- src/aura.js | 124 ++++++++++++++++++++++++------------------------- 2 files changed, 75 insertions(+), 68 deletions(-) diff --git a/src/AAhooks.js b/src/AAhooks.js index 80c896c..b18e05c 100644 --- a/src/AAhooks.js +++ b/src/AAhooks.js @@ -55,7 +55,7 @@ Hooks.on("updateCombat", async (combat, changed, options, userId) => { let previousCombatant = canvas.tokens.get(combat.previous.tokenId); await previousCombatant.document.update({ "flags.ActiveAuras": false }); if (AAdebug) console.log("updateCombat, main aura"); - await ActiveAuras.MainAura(combatant.data, "combat update", combatant.scene.id); + await ActiveAuras.MainAura(combatant.document, "combat update", combatant.scene.id); }); Hooks.on("preDeleteToken", async (token) => { @@ -75,10 +75,19 @@ Hooks.on("updateToken", async (token, update, _flags, _id) => { if (canvas.scene === null) { if (AAdebug) { console.log("Active Auras disabled due to no canvas") } return } if (!AAgm) return; if (("y" in update || "x" in update || "elevation" in update)) { - let MapObject = AuraMap.get(token.parent.id); - if (!MapObject || MapObject?.effects.length < 1) return; - if (AAdebug) console.log("movement, main aura"); - await ActiveAuras.MainAura(token, "movement update", token.parent.id) + // we need to wait for the movement to finish due to the animation in v10, listen to refresh hook + const moveHookId = Hooks.on("refreshToken", async (rToken) => { + if (rToken.id !== token.id + || ("x" in update && rToken.x !== update.x) + || ("y" in update && rToken.y !== update.y) + || ("elevation" in update && rToken.document.elevation !== update.elevation) + ) return; + Hooks.off("refreshToken", moveHookId); + let MapObject = AuraMap.get(token.parent.id); + if (!MapObject || MapObject?.effects.length < 1) return; + if (AAdebug) console.log("movement, main aura"); + await ActiveAuras.MainAura(token, "movement update", token.parent.id); + }) } else if ("hidden" in update && AAhelpers.IsAuraToken(token.id, token.parent.id)) { if (AAdebug) console.log("hidden, collate auras true true"); diff --git a/src/aura.js b/src/aura.js index 74533ab..4d3b35c 100644 --- a/src/aura.js +++ b/src/aura.js @@ -14,23 +14,23 @@ class ActiveAuras { let perfStart; let perfEnd; if (AAdebug) { perfStart = performance.now() } - if (typeof movedToken?.documentName !== "string") movedToken = movedToken?.document ?? undefined + if (typeof movedToken?.documentName !== "string") movedToken = movedToken?.document ?? undefined; if (AAdebug) { console.log(source) } if (!AAgm) return; - const sceneCombat = game.combats.filter(c => c.scene?.id === sceneID) + const sceneCombat = game.combats.filter(c => c.scene?.id === sceneID); if (game.settings.get("ActiveAuras", "combatOnly") && !sceneCombat[0]?.started) { if (AAdebug) { console.warn("Active Auras not active when not in combat") } - return;w + return; } if (sceneID !== canvas.id) return ui.notifications.warn("An update was called on a non viewed scene, auras will be updated when you return to that scene") let map = new Map(); - let updateTokens = canvas.tokens.placeables + let updateTokens = canvas.tokens.placeables; let auraTokenId; if (movedToken !== undefined) { if (AAhelpers.IsAuraToken(movedToken.id, sceneID)) { - auraTokenId = movedToken.id + auraTokenId = movedToken.id; } else if (getProperty(movedToken, "flags.token-attacher")) { if (AAdebug) console.log("ActiveAuras: token attacher movement") @@ -39,21 +39,19 @@ class ActiveAuras { updateTokens = [canvas.tokens.get(movedToken.id)]; } } - map = ActiveAuras.UpdateAllTokens(map, updateTokens, auraTokenId) + map = ActiveAuras.UpdateAllTokens(map, updateTokens, auraTokenId); if (AAdebug) { - perfEnd = performance.now() - console.log(`Active Auras Find Auras took ${perfEnd - perfStart} ms, FPS:${Math.round(canvas.app.ticker.FPS)}`) + perfEnd = performance.now(); + console.log(`Active Auras Find Auras took ${perfEnd - perfStart} ms, FPS:${Math.round(canvas.app.ticker.FPS)}`); } for (const mapEffect of map) { - const MapKey = mapEffect[0] - map.set(MapKey, { add: mapEffect[1].add, token: mapEffect[1].token, effect: mapEffect[1].effect.data }) + const MapKey = mapEffect[0]; + map.set(MapKey, { add: mapEffect[1].add, token: mapEffect[1].token, effect: mapEffect[1].effect.data }); } if (AAdebug) console.log(map) - - - map.forEach(compareMap) + map.forEach(compareMap); /** * @@ -72,15 +70,15 @@ class ActiveAuras { for (let e = 0; e < m[1].effect.changes.length; e++) { if (typeof (parseInt(m[1].effect.changes[e].value)) !== "number") continue; const oldEffectValue = parseInt(value.effect.changes[e].value); - const newEffectValue = parseInt(m[1].effect.changes[e].value) + const newEffectValue = parseInt(m[1].effect.changes[e].value); if (oldEffectValue < newEffectValue) { - map1.delete(key) + map1.delete(key); } } } else if ((m[1].effect.label === value.effect.label) && (m[1].add === true || value.add === true) && (m[1].token.id === value.token.id)) { - if (value.add === false) map.delete(key) + if (value.add === false) map.delete(key); } } @@ -88,15 +86,15 @@ class ActiveAuras { for (const update of map) { if (update[1].add) { - await ActiveAuras.CreateActiveEffect(update[1].token.id, update[1].effect) + await ActiveAuras.CreateActiveEffect(update[1].token.id, update[1].effect); } else { - await ActiveAuras.RemoveActiveEffects(update[1].token.id, update[1].effect.origin) + await ActiveAuras.RemoveActiveEffects(update[1].token.id, update[1].effect.origin); } } if (AAdebug) { - perfEnd = performance.now() - console.log(`Active Auras Main Function took ${perfEnd - perfStart} ms, FPS:${Math.round(canvas.app.ticker.FPS)}`) + perfEnd = performance.now(); + console.log(`Active Auras Main Function took ${perfEnd - perfStart} ms, FPS:${Math.round(canvas.app.ticker.FPS)}`); } } @@ -108,9 +106,9 @@ class ActiveAuras { */ static UpdateAllTokens(map, tokens, tokenId) { for (const canvasToken of tokens) { - ActiveAuras.UpdateToken(map, canvasToken, tokenId) + ActiveAuras.UpdateToken(map, canvasToken, tokenId); } - return map + return map; } /** @@ -122,32 +120,32 @@ class ActiveAuras { static UpdateToken(map, canvasToken, tokenId) { if (canvasToken.document.flags['multilevel-tokens']) return; if (canvasToken.actor === null) return; - if (canvasToken.actor.type == "vehicle") return + if (canvasToken.actor.type == "vehicle") return; let tokenAlignment; if (game.system.id === "dnd5e" || game.system.id === "sw5e") { try { tokenAlignment = canvasToken.actor?.system.details.alignment.toLowerCase(); } catch (error) { - console.error([`ActiveAuras: the token has an unreadable alignment`, canvasToken]) + console.error([`ActiveAuras: the token has an unreadable alignment`, canvasToken]); } } const MapKey = canvasToken.scene.id; - let MapObject = AuraMap.get(MapKey) + let MapObject = AuraMap.get(MapKey); let checkEffects = MapObject.effects; //Check for other types of X aura if the aura token is moved if (tokenId && canvasToken.id !== tokenId) { - checkEffects = checkEffects.filter(i => i.entityId === tokenId) - let duplicateEffect = [] + checkEffects = checkEffects.filter(i => i.entityId === tokenId); + let duplicateEffect = []; checkEffects.forEach(e => duplicateEffect = (MapObject.effects.filter(i => (i.data?.label === e.data?.label) && i.entityId !== tokenId))); - checkEffects = checkEffects.concat(duplicateEffect) + checkEffects = checkEffects.concat(duplicateEffect); } for (const auraEffect of checkEffects) { - const auraTargets = auraEffect.data.flags?.ActiveAuras?.aura + const auraTargets = auraEffect.data.flags?.ActiveAuras?.aura; const { radius, height, hostile, wildcard, extra } = auraEffect.data.flags?.ActiveAuras; let { type, alignment } = auraEffect.data.flags?.ActiveAuras; - const { parentActorLink, parentActorId } = auraEffect + const { parentActorLink, parentActorId } = auraEffect; type = type !== undefined ? type.toLowerCase() : ""; alignment = alignment !== undefined ? alignment.toLowerCase() : ""; if (alignment && !tokenAlignment.includes(alignment) && !tokenAlignment.includes("any")) continue; // cleaned up alignment check and moved here. @@ -158,38 +156,38 @@ class ActiveAuras { let auraAlignment = auraEffect.data.flags?.ActiveAuras?.alignment !== undefined ? auraEffect.data.flags?.ActiveAuras?.alignment.toLowerCase() : ""; let hostileTurn = auraEffect.data.flags?.ActiveAuras?.hostile */ - const auraEntityType = auraEffect.entityType + const auraEntityType = auraEffect.entityType; switch (auraEntityType) { //{data: testEffect.data, parentActorLink :testEffect.parent.data.token.actorLink, parentActorId : testEffect.parent._id, tokenId: testToken.id, templateId: template._id, } case "token": { if (parentActorLink) { - const auraTokenArray = game.actors.get(parentActorId).getActiveTokens() + const auraTokenArray = game.actors.get(parentActorId).getActiveTokens(); if (auraTokenArray.length > 1) { - auraEntity = auraTokenArray[0] - console.error("AA: Duplicate Linked Tokens detected, defaulting to first token.") + auraEntity = auraTokenArray[0]; + console.error("AA: Duplicate Linked Tokens detected, defaulting to first token."); } - else auraEntity = auraTokenArray[0] + else auraEntity = auraTokenArray[0]; } - else auraEntity = canvas.tokens.get(auraEffect.entityId) + else auraEntity = canvas.tokens.get(auraEffect.entityId); if (auraEntity.id === canvasToken.id) continue; if (!AAhelpers.DispositionCheck(auraTargets, auraEntity.document.disposition, canvasToken.document.disposition)) continue; if (type) { - if (!AAhelpers.CheckType(canvasToken, type)) continue + if (!AAhelpers.CheckType(canvasToken, type)) continue; } if (hostile && canvasToken.id !== game.combats.active?.current.tokenId) continue; if (game.system.id === "swade") { - if (!AAhelpers.Wildcard(canvasToken, wildcard, extra)) continue + if (!AAhelpers.Wildcard(canvasToken, wildcard, extra)) continue; } - const shape = getAuraShape(auraEntity, radius) - distance = AAmeasure.inAura(canvasToken, auraEntity, game.settings.get("ActiveAuras", "wall-block"), height, radius, shape) + const shape = getAuraShape(auraEntity, radius); + distance = AAmeasure.inAura(canvasToken, auraEntity, game.settings.get("ActiveAuras", "wall-block"), height, radius, shape); } break; case "template": { - auraEntity = canvas.templates.get(auraEffect.entityId) + auraEntity = canvas.templates.get(auraEffect.entityId); if (type) { if (!AAhelpers.CheckType(canvasToken, type)) continue @@ -198,21 +196,21 @@ class ActiveAuras { if (auraEffect.casterDisposition) { if (!AAhelpers.DispositionCheck(auraTargets, auraEffect.casterDisposition, canvasToken.disposition)) continue; } - const shape = getTemplateShape(auraEntity) - let templateDetails = auraEntity + const shape = getTemplateShape(auraEntity); + let templateDetails = auraEntity; //templateDetails.shape = shape distance = AAmeasure.isTokenInside(templateDetails, canvasToken, game.settings.get("ActiveAuras", "wall-block")); } break; case "drawing": { - auraEntity = canvas.drawings.get(auraEffect.entityId) + auraEntity = canvas.drawings.get(auraEffect.entityId); if (type) { - if (!AAhelpers.CheckType(canvasToken, type)) continue + if (!AAhelpers.CheckType(canvasToken, type)) continue; } if (hostile && canvasToken.id !== game.combats.active.current.tokenId) return; const shape = getDrawingShape(auraEntity.data) - distance = AAmeasure.inAura(canvasToken, auraEntity, game.settings.get("ActiveAuras", "wall-block"), height, radius, shape) + distance = AAmeasure.inAura(canvasToken, auraEntity, game.settings.get("ActiveAuras", "wall-block"), height, radius, shape); } break; } @@ -222,22 +220,22 @@ class ActiveAuras { if (distance && !auraEffect.data.flags?.ActiveAuras?.Paused) { if (MapObject) { - MapObject.add = true + MapObject.add = true; } else { - map.set(MapKey, { add: true, token: canvasToken, effect: auraEffect }) + map.set(MapKey, { add: true, token: canvasToken, effect: auraEffect }); } } else if (!MapObject?.add && canvasToken.document.actor?.effects.contents.some(e => e.origin === auraEffect.data.origin && e.label === auraEffect.data.label)) { if (MapObject) { - MapObject.add = false + MapObject.add = false; } else { - map.set(MapKey, { add: false, token: canvasToken, effect: auraEffect }) + map.set(MapKey, { add: false, token: canvasToken, effect: auraEffect }); } } } - return map + return map; } /** @@ -246,9 +244,9 @@ class ActiveAuras { * @param {ActiveEffect} effectData - effect data to generate effect */ static async CreateActiveEffect(tokenID, oldEffectData) { - const token = canvas.tokens.get(tokenID) + const token = canvas.tokens.get(tokenID); - const duplicateEffect = token.document.actor.effects.contents.find(e => e.origin === oldEffectData.origin && e.label === oldEffectData.label) + const duplicateEffect = token.document.actor.effects.contents.find(e => e.origin === oldEffectData.origin && e.label === oldEffectData.label); if (getProperty(duplicateEffect, "flags.ActiveAuras.isAura")) return; if (duplicateEffect) { if (duplicateEffect.origin === oldEffectData.origin) return; @@ -257,9 +255,9 @@ class ActiveAuras { } let effectData = duplicate(oldEffectData) if (effectData.flags.ActiveAuras.onlyOnce) { - const AAID = oldEffectData.origin.replaceAll(".", "") + const AAID = oldEffectData.origin.replaceAll(".", ""); if (token.document.flags.ActiveAuras?.[AAID]) return; - else await token.document.setFlag("ActiveAuras", AAID, true) + else await token.document.setFlag("ActiveAuras", AAID, true); } if (effectData.flags.ActiveAuras?.isMacro) { for (let [changeIndex, change] of effectData.changes.entries()) { @@ -267,16 +265,16 @@ class ActiveAuras { if (change.key === "macro.execute" || change.key === "macro.itemMacro") { if (typeof newValue === "string") { - newValue = [newValue] + newValue = [newValue]; newValue = newValue.map(val => { if (typeof val === "string" && val.includes("@@token")) { - let re = /([\s]*@@token)/gms - return val.replaceAll(re, ` @token`) + let re = /([\s]*@@token)/gms; + return val.replaceAll(re, ` @token`); } else if (typeof val === "string" && val.includes("@token")) { - let re = /([\s]*@token)/gms - return val.replaceAll(re, ` ${token.id}`) + let re = /([\s]*@token)/gms; + return val.replaceAll(re, ` ${token.id}`); } return val; }); @@ -290,13 +288,13 @@ class ActiveAuras { } } } - ['ignoreSelf', 'hidden', 'height', 'alignment', 'type', 'aura', 'radius', 'isAura', 'height'].forEach(e => delete effectData.flags.ActiveAuras[e]) + ['ignoreSelf', 'hidden', 'height', 'alignment', 'type', 'aura', 'radius', 'isAura', 'height'].forEach(e => delete effectData.flags.ActiveAuras[e]); if (effectData.flags.ActiveAuras.time !== "None" && effectData.flags.ActiveAuras.time !== undefined && game.modules.get("dae")?.active) { - effectData.flags.dae?.specialDuration?.push(effectData.flags.ActiveAuras.time) + effectData.flags.dae?.specialDuration?.push(effectData.flags.ActiveAuras.time); } await token.actor.createEmbeddedDocuments("ActiveEffect", [effectData]); - console.log(game.i18n.format("ACTIVEAURAS.ApplyLog", { effectDataLabel: effectData.label, tokenName: token.name })) + console.log(game.i18n.format("ACTIVEAURAS.ApplyLog", { effectDataLabel: effectData.label, tokenName: token.name })); } /** From 3a212cf596c213417a9938cfcd1e27f5767a853e Mon Sep 17 00:00:00 2001 From: Jack Holloway Date: Thu, 27 Oct 2022 14:53:58 +0100 Subject: [PATCH 18/31] v0.4.18 --- module.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module.json b/module.json index 7103406..d44daac 100644 --- a/module.json +++ b/module.json @@ -8,7 +8,7 @@ "url": "https://github.com/kandashi/Active-Auras", "bugs": "https://github.com/kandashi/Active-Auras/issues", "flags": {}, - "version": "0.4.17", + "version": "0.4.18", "compatibility": { "minimum": 10, "verified": 10 From fa47b47ad98149bad9317a53283982e41b89d5a2 Mon Sep 17 00:00:00 2001 From: Jack Holloway Date: Thu, 27 Oct 2022 15:37:37 +0100 Subject: [PATCH 19/31] Remove aura lag if not using token animations# --- src/AAhooks.js | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/src/AAhooks.js b/src/AAhooks.js index b18e05c..f47d6f4 100644 --- a/src/AAhooks.js +++ b/src/AAhooks.js @@ -1,3 +1,12 @@ +// DEBUG function to disable animate on tokens +// Hooks.on("preUpdateToken", (tokenDoc, update, options) => { +// const x = foundry.utils.hasProperty(update, "x"); +// const y = foundry.utils.hasProperty(update, "y"); +// if ( !x && !y ) return; +// console.warn(options) +// foundry.utils.setProperty(options, "animate", false); +// }); + Hooks.once('ready', () => { if (!game.modules.get('lib-wrapper')?.active && game.user.isGM) ui.notifications.error("Module XYZ requires the 'libWrapper' module. Please install and activate it."); @@ -68,26 +77,38 @@ Hooks.on("preDeleteToken", async (token) => { AAhelpers.removeAurasOnToken(token); }); + /** * On token movement run MainAura */ Hooks.on("updateToken", async (token, update, _flags, _id) => { + if (AAdebug) console.warn("updateTokenHookArgs", {token, update, _flags, _id}); if (canvas.scene === null) { if (AAdebug) { console.log("Active Auras disabled due to no canvas") } return } if (!AAgm) return; if (("y" in update || "x" in update || "elevation" in update)) { // we need to wait for the movement to finish due to the animation in v10, listen to refresh hook - const moveHookId = Hooks.on("refreshToken", async (rToken) => { - if (rToken.id !== token.id - || ("x" in update && rToken.x !== update.x) - || ("y" in update && rToken.y !== update.y) - || ("elevation" in update && rToken.document.elevation !== update.elevation) - ) return; - Hooks.off("refreshToken", moveHookId); + + async function movementUpdate() { let MapObject = AuraMap.get(token.parent.id); if (!MapObject || MapObject?.effects.length < 1) return; if (AAdebug) console.log("movement, main aura"); await ActiveAuras.MainAura(token, "movement update", token.parent.id); - }) + }; + + if(_flags.animate === false) { + movementUpdate(); + } else { + const moveHookId = Hooks.on("refreshToken", async (rToken) => { + if (rToken.id !== token.id + || ("x" in update && rToken.x !== update.x) + || ("y" in update && rToken.y !== update.y) + || ("elevation" in update && rToken.document.elevation !== update.elevation) + ) return; + Hooks.off("refreshToken", moveHookId); + movementUpdate(); + }) + movementUpdate(); + } } else if ("hidden" in update && AAhelpers.IsAuraToken(token.id, token.parent.id)) { if (AAdebug) console.log("hidden, collate auras true true"); From db243e6d0e493629e25d6bb08ad282cf44b0dd5c Mon Sep 17 00:00:00 2001 From: Jack Holloway Date: Thu, 27 Oct 2022 15:38:10 +0100 Subject: [PATCH 20/31] v0.4.19 --- module.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module.json b/module.json index d44daac..d15f81a 100644 --- a/module.json +++ b/module.json @@ -8,7 +8,7 @@ "url": "https://github.com/kandashi/Active-Auras", "bugs": "https://github.com/kandashi/Active-Auras/issues", "flags": {}, - "version": "0.4.18", + "version": "0.4.19", "compatibility": { "minimum": 10, "verified": 10 From c464be08aa0d14a3c0e9803cba503a6182725c65 Mon Sep 17 00:00:00 2001 From: Jack Holloway Date: Thu, 27 Oct 2022 19:07:22 +0100 Subject: [PATCH 21/31] Walls now block auras again --- Changelog.md | 8 +++++++- src/measureDistances.js | 12 +++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/Changelog.md b/Changelog.md index deb1f67..facf6a2 100644 --- a/Changelog.md +++ b/Changelog.md @@ -78,4 +78,10 @@ Fixed/updated all compendium items so they contain the macro in an Item Macro, t ## 0.4.07 Fixed for non-circular templates -Fixes for duplicate DF CE effects as the name would not match the aura-name \ No newline at end of file +Fixes for duplicate DF CE effects as the name would not match the aura-name + +## 0.4.20 + +v10 Support +Walls will now block again +Standardize on debounceCollate to help with state oscillation diff --git a/src/measureDistances.js b/src/measureDistances.js index 1b50b61..fd8d7ea 100644 --- a/src/measureDistances.js +++ b/src/measureDistances.js @@ -57,14 +57,20 @@ class AAmeasure { if (!auraPoly.contains(t.x, t.y)) continue; // quick exit if not in the aura for (const s of sourceCorners) { - const r = new Ray(t, s) + const r = new Ray(t, s); if (wallblocking) { let collision; if (game.modules.get("levels")?.active) { - collision = _levels.testCollision({ x: t.x, y: t.y, z: target.document.elevation }, { x: s.x, y: s.y, z: source.document.elevation ?? source.flags?.levels?.elevation }, "collision"); + collision = _levels.testCollision( + { x: t.x, y: t.y, z: target.document.elevation }, + { x: s.x, y: s.y, z: source.document.elevation ?? source.flags?.levels?.elevation }, + "collision" + ); } else { - collision = canvas.walls.checkCollision(r); + // collision blocked by sight or movement + collision = canvas.walls.checkCollision(r, {mode: "any", type: "sight" }) + || canvas.walls.checkCollision(r, {mode: "any", type: "move" }); } if (collision) continue; } From a6c44de03777f610eede47518af86f3e7e72592a Mon Sep 17 00:00:00 2001 From: Jack Holloway Date: Thu, 27 Oct 2022 19:07:56 +0100 Subject: [PATCH 22/31] v0.4.20 --- module.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module.json b/module.json index d15f81a..1032073 100644 --- a/module.json +++ b/module.json @@ -8,7 +8,7 @@ "url": "https://github.com/kandashi/Active-Auras", "bugs": "https://github.com/kandashi/Active-Auras/issues", "flags": {}, - "version": "0.4.19", + "version": "0.4.20", "compatibility": { "minimum": 10, "verified": 10 From 693ba242273179d5f24051fbd48e31d8005e079f Mon Sep 17 00:00:00 2001 From: Jack Holloway Date: Sat, 29 Oct 2022 10:10:22 +0100 Subject: [PATCH 23/31] Hidden Actors: new setting to remove/retain aura effects on hidden actors --- lang/en.json | 4 +++- src/AAhelpers.js | 15 +++++++------ src/AAhooks.js | 19 ++++++++++++---- src/collateAuras.js | 53 +++++++++++++++++++++++++-------------------- src/settings.js | 10 ++++++++- 5 files changed, 64 insertions(+), 37 deletions(-) diff --git a/lang/en.json b/lang/en.json index a48c8b7..1c2b0a8 100644 --- a/lang/en.json +++ b/lang/en.json @@ -8,6 +8,8 @@ "ACTIVEAURAS.tabname": "Auras", "ACTIVEAURAS.removeDead": "Remove Auras on death", "ACTIVEAURAS.removeDeadHint": "On tokens reaching 0 hp, remove any auras they would provide, default on", + "ACTIVEAURAS.removeHiddenAuras": "Remove Auras on hidden tokens?", + "ACTIVEAURAS.removeHiddenAurasHint": "On tokens that are hidden, remove any auras they would provide, default on", "ACTIVEAURAS.combatOnly" : "Auras in combat", "ACTIVEAURAS.combatHint" : "Only check auras while a combat is active, this is for performance reasons and should be on unless you really need it off", "ACTIVEAURAS.scrollingAura": "Disable scrolling text for auras", @@ -46,4 +48,4 @@ "ACTIVEAURAS.RemoveLog":"ActiveAuras | Removed '{effectDataLabel}' from {tokenName}", "ACTIVEAURAS.IgnoreSelfLog":"ActiveAuras | Ignoring '{effectDataLabel}' change {changeKey} to {actorName}", "ACTIVEAURAS.SaveNotify": "${tokenName} passes the save against ${oldEffectDataLabel}" -} \ No newline at end of file +} diff --git a/src/AAhelpers.js b/src/AAhelpers.js index 7f20719..76cf836 100644 --- a/src/AAhelpers.js +++ b/src/AAhelpers.js @@ -146,18 +146,19 @@ class AAhelpers { static ExtractAuraById(entityId, sceneID) { if (!AAgm) return; - let MapKey = sceneID; - let MapObject = AuraMap.get(MapKey); - let effectArray = MapObject.effects.filter(e => e.entityId !== entityId); + const MapKey = sceneID; + const MapObject = AuraMap.get(MapKey); + const effectArray = MapObject.effects.filter(e => e.entityId !== entityId); AuraMap.set(MapKey, { effects: effectArray }); AAhelpers.RemoveAppliedAuras(canvas); } static async RemoveAppliedAuras() { - let EffectsArray = []; - let MapKey = canvas.scene.id; - let MapObject = AuraMap.get(MapKey); - MapObject.effects.forEach(i => EffectsArray.push(i.data.origin)); + const MapKey = canvas.scene.id; + const MapObject = AuraMap.get(MapKey); + const EffectsArray = MapObject.effects.map(i => i.data.origin); + + if (AAdebug) console.warn("RemoveAppliedAuras", { MapKey, MapObject, EffectsArray }) for (let removeToken of canvas.tokens.placeables) { if (removeToken?.actor?.effects.size > 0) { diff --git a/src/AAhooks.js b/src/AAhooks.js index f47d6f4..13c5551 100644 --- a/src/AAhooks.js +++ b/src/AAhooks.js @@ -13,7 +13,17 @@ Hooks.once('ready', () => { }); let AAgm; + +/** + * + * @param {String} sceneID Scene to check upon + * @param {Boolean} checkAuras Can apply auras + * @param {Boolean} removeAuras Can remove auras + * @param {String} source For console logging + * @returns + */ const debouncedCollate = debounce((a, b, c, d) => CollateAuras(a, b, c, d), 200); + Hooks.once("socketlib.ready", () => { AAsocket = socketlib.registerModule("ActiveAuras"); AAsocket.register("userCollate", CollateAuras); @@ -82,7 +92,7 @@ Hooks.on("preDeleteToken", async (token) => { * On token movement run MainAura */ Hooks.on("updateToken", async (token, update, _flags, _id) => { - if (AAdebug) console.warn("updateTokenHookArgs", {token, update, _flags, _id}); + if (AAdebug) console.log("updateTokenHookArgs", {token, update, _flags, _id}); if (canvas.scene === null) { if (AAdebug) { console.log("Active Auras disabled due to no canvas") } return } if (!AAgm) return; if (("y" in update || "x" in update || "elevation" in update)) { @@ -110,9 +120,10 @@ Hooks.on("updateToken", async (token, update, _flags, _id) => { movementUpdate(); } } - else if ("hidden" in update && AAhelpers.IsAuraToken(token.id, token.parent.id)) { - if (AAdebug) console.log("hidden, collate auras true true"); - debouncedCollate(canvas.scene, true, true, "updateToken"); + // in v10 invisible is now a thing, so hidden is considered "not on scene" + else if (hasProperty(update, "hidden") && (!update.hidden || AAhelpers.IsAuraToken(token.id, token.parent.id))) { + if (AAdebug) console.log(`hidden, collate auras ${!update.hidden} ${update.hidden}`); + debouncedCollate(canvas.scene.id, !update.hidden, update.hidden, "updateToken, hidden"); } else if (AAhelpers.IsAuraToken(token.id, token.parent.id) && AAhelpers.HPCheck(token)) { if (AAdebug) console.log("0hp, collate auras true true"); diff --git a/src/collateAuras.js b/src/collateAuras.js index 768af92..e0057c7 100644 --- a/src/collateAuras.js +++ b/src/collateAuras.js @@ -8,44 +8,50 @@ */ async function CollateAuras(sceneID, checkAuras, removeAuras, source) { if (!AAgm) return; - if (sceneID !== canvas.id) return ui.notifications.warn("Collate Auras called on a non viewed scene, auras will be updated when you return to that scene") - if (AAdebug) console.log(source) + if (sceneID !== canvas.id) return ui.notifications.warn("Collate Auras called on a non viewed scene, auras will be updated when you return to that scene"); + if (AAdebug) console.log(source); const MapKey = sceneID; const MapObject = AuraMap.get(MapKey); const effectArray = []; for (const t of canvas.tokens.placeables) { - const testToken = t.document + const testToken = t.document; //Skips over null actor tokens if (testToken.actor === null || testToken.actor === undefined) continue; //Skips over MLT coppied tokens - if (testToken.flags["multilevel-tokens"]) continue - + if (testToken.flags["multilevel-tokens"]) continue; + // applying auras on dead? if (!AAhelpers.HPCheck(testToken) && game.settings.get("ActiveAuras", "dead-aura")) { - if (AAdebug) console.log(`Skipping ${testToken.name}, 0hp`) - continue + if (AAdebug) console.log(`Skipping ${testToken.name}, 0hp`); + continue; + } + // applying auras on hidden? + if (testToken.hidden && game.settings.get("ActiveAuras", "remove-hidden-auras")) { + if (AAdebug) console.log(`Skipping ${testToken.name}, hidden`); + continue; } + // loop over effects for (const testEffect of testToken?.actor?.effects.contents) { if (testEffect.flags?.ActiveAuras?.isAura) { if (testEffect.disabled) continue; - const newEffect = { data: duplicate(testEffect), parentActorLink: testEffect.parent.prototypeToken.actorLink, parentActorId: testEffect.parent.id, entityType: "token", entityId: testToken.id } - const re = /@[\w\.]+/g - const rollData = testToken.actor.getRollData() + const newEffect = { data: duplicate(testEffect), parentActorLink: testEffect.parent.prototypeToken.actorLink, parentActorId: testEffect.parent.id, entityType: "token", entityId: testToken.id }; + const re = /@[\w\.]+/g; + const rollData = testToken.actor.getRollData(); for (const change of newEffect.data.changes) { - if (typeof change.value !== "string") continue - let s = change.value + if (typeof change.value !== "string") continue; + let s = change.value; for (const match of s.match(re) || []) { if (s.includes("@@")) { - s = s.replace(match, match.slice(1)) + s = s.replace(match, match.slice(1)); } else { - s = s.replace(match, getProperty(rollData, match.slice(1))) + s = s.replace(match, getProperty(rollData, match.slice(1))); } } - change.value = s - if (change.key === "macro.execute" || change.key === "macro.itemMacro") newEffect.data.flags.ActiveAuras.isMacro = true + change.value = s; + if (change.key === "macro.execute" || change.key === "macro.itemMacro") newEffect.data.flags.ActiveAuras.isMacro = true; } - newEffect.data.disabled = false + newEffect.data.disabled = false; const macro = newEffect.data.flags.ActiveAuras.isMacro !== undefined ? newEffect.data.flags.ActiveAuras.isMacro : false; newEffect.data.flags.ActiveAuras.isAura = false; newEffect.data.flags.ActiveAuras.applied = true; @@ -53,21 +59,20 @@ async function CollateAuras(sceneID, checkAuras, removeAuras, source) { newEffect.data.flags.ActiveAuras.ignoreSelf = false; if (testEffect.flags.ActiveAuras?.hidden && testToken.hidden) newEffect.data.flags.ActiveAuras.Paused = true; else newEffect.data.flags.ActiveAuras.Paused = false; - effectArray.push(newEffect) + effectArray.push(newEffect); } } } - await RetrieveDrawingAuras(effectArray) - await RetrieveTemplateAuras(effectArray) + await RetrieveDrawingAuras(effectArray); + await RetrieveTemplateAuras(effectArray); if (MapObject) { - MapObject.effects = effectArray + MapObject.effects = effectArray; } else { - AuraMap.set(MapKey, { effects: effectArray }) + AuraMap.set(MapKey, { effects: effectArray }); } - - if (AAdebug) console.log(AuraMap) + if (AAdebug) console.log("AuraMap", AuraMap); if (checkAuras) { ActiveAuras.MainAura(undefined, "Collate auras", canvas.id) diff --git a/src/settings.js b/src/settings.js index e78e226..d3d0b82 100644 --- a/src/settings.js +++ b/src/settings.js @@ -31,6 +31,14 @@ Hooks.on('init', () => { default: true, type: Boolean, }); + game.settings.register("ActiveAuras", "remove-hidden-auras", { + name: game.i18n.format("ACTIVEAURAS.removeHiddenAuras"), + hint: game.i18n.format("ACTIVEAURAS.removeHiddenAurasHint"), + scope: "world", + config: true, + default: true, + type: Boolean, + }); game.settings.register("ActiveAuras", "combatOnly", { name: game.i18n.format("ACTIVEAURAS.combatOnly"), hint: game.i18n.format("ACTIVEAURAS.combatHint"), @@ -59,4 +67,4 @@ Hooks.on('init', () => { default: false, type: Boolean, }); -}); \ No newline at end of file +}); From b688333af93012f04aa6392fee9d7bb6796e8552 Mon Sep 17 00:00:00 2001 From: Jack Holloway Date: Sat, 29 Oct 2022 10:11:38 +0100 Subject: [PATCH 24/31] v0.4.21 --- Changelog.md | 4 ++++ module.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index facf6a2..000fd69 100644 --- a/Changelog.md +++ b/Changelog.md @@ -85,3 +85,7 @@ Fixes for duplicate DF CE effects as the name would not match the aura-name v10 Support Walls will now block again Standardize on debounceCollate to help with state oscillation + +# 0.4.21 + +New settings to apply/remove aura effects from hidden actors (not invisible). Defaults to true. diff --git a/module.json b/module.json index 1032073..719fb9e 100644 --- a/module.json +++ b/module.json @@ -8,7 +8,7 @@ "url": "https://github.com/kandashi/Active-Auras", "bugs": "https://github.com/kandashi/Active-Auras/issues", "flags": {}, - "version": "0.4.20", + "version": "0.4.21", "compatibility": { "minimum": 10, "verified": 10 From f52ff293e89a2eddace2f53a2bf207d737f8f80b Mon Sep 17 00:00:00 2001 From: Jack Holloway Date: Sun, 30 Oct 2022 12:54:31 +0000 Subject: [PATCH 25/31] Remove duplicate macro from pack --- Changelog.md | 4 ++++ packs/macros | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index 000fd69..db0cec0 100644 --- a/Changelog.md +++ b/Changelog.md @@ -89,3 +89,7 @@ Standardize on debounceCollate to help with state oscillation # 0.4.21 New settings to apply/remove aura effects from hidden actors (not invisible). Defaults to true. + +# 0.4.22 + +Duplicate macro removed. diff --git a/packs/macros b/packs/macros index 7a29ab5..07471ec 100644 --- a/packs/macros +++ b/packs/macros @@ -1,4 +1,3 @@ {"name":"AA DoT","type":"script","flags":{"furnace":{"runAsGM":false},"core":{"sourceId":"Macro.nsfe0IvUwY7JP4ya"}},"scope":"global","command":"if(args[0] === \"on\") {\nlet damageRoll = new Roll(`${args[1]}`).roll().toMessage({flavor: `${args[2]}`})\n}","author":"E4BVikjIkVl2lL2j","img":"icons/svg/dice-target.svg","_id":"TWJ7K9Rw384oXFxV","ownership":{"default":0,"E4BVikjIkVl2lL2j":3},"folder":null,"sort":0,"_stats":{"systemId":"dnd5e","systemVersion":"2.0.1","coreVersion":"10.283","createdTime":1662274930162,"modifiedTime":1662274930162,"lastModifiedBy":"GrX6tFOozYFBnDHq"}} {"_id":"UwyXiTHK3Q518uLm","name":"AA ApplyEffectsToTemplate","type":"script","author":"E4BVikjIkVl2lL2j","img":"icons/svg/dice-target.svg","scope":"global","command":"//On use macro to apply an item's active effects to the placed template instead of applying through the normal midi QoL workflow \n// requires midi qol, advanced macros and Active Auras \nAAhelpers.applyTemplate()","folder":null,"sort":0,"flags":{"furnace":{"runAsGM":false},"combat-utility-belt":{"macroTrigger":""},"core":{"sourceId":"Macro.fHwT4N13IcbW4D3I"},"advanced-macros":{"runAsGM":false}},"ownership":{"default":0,"E4BVikjIkVl2lL2j":3},"_stats":{"systemId":"dnd5e","systemVersion":"2.0.1","coreVersion":"10.283","createdTime":1662274930163,"modifiedTime":1662274930163,"lastModifiedBy":"GrX6tFOozYFBnDHq"}} -{"name":"AA Apply effect to drawing","type":"script","author":"jkdToQqzFl0mFXls","img":"icons/svg/dice-target.svg","scope":"global","command":"// Standard Hotbar macro to apply an active effect from a given actor (default Zone Effects) to a selected drawing\n// Change the actor name as required \n\n\nlet effectActor = game.actors.getName(\"\")\nlet effects = effectActor.data.effects.contents\nlet drawing = canvas.drawings.controlled[0]\nlet content\nfor ( let effect of effects) {\ncontent += ``;\n}\nnew Dialog({\n title: 'Example',\n content: `\n
\n
\n \n
\n
\n `,\n buttons: {\n apply: {\n label: \"Apply\",\n callback: async (html) => {\n let effectId = html.find('#effect').val();\n let effect = effects.find(i => i.id === effectId)\n effect.data.origin = `Actor.${effectActor.id}`\n await drawing.document.setFlag(\"ActiveAuras\", \"IsAura\", [effect.data])\n AAhelpers.UserCollateAuras(canvas.scene.id, true, false, \"drawingAdd\")\n }\n }\n }\n}).render(true)","folder":null,"sort":0,"flags":{"furnace":{"runAsGM":false},"combat-utility-belt":{"macroTrigger":""},"core":{"sourceId":"Macro.RukVihRspPDZRSKM"},"advanced-macros":{"runAsGM":false}},"_id":"r8hqRcIOLk3ymtUW","ownership":{"default":0,"E4BVikjIkVl2lL2j":3,"jkdToQqzFl0mFXls":3},"_stats":{"systemId":"dnd5e","systemVersion":"2.0.1","coreVersion":"10.283","createdTime":1662274930165,"modifiedTime":1662274930165,"lastModifiedBy":"GrX6tFOozYFBnDHq"}} {"name":"AA Apply effect to drawing","type":"script","author":"jkdToQqzFl0mFXls","img":"icons/svg/dice-target.svg","scope":"global","command":"// Standard Hotbar macro to apply an active effect from a given actor (default Zone Effects) to a selected drawing\n// Change the actor name as required \n\n\nlet effectActor = game.actors.getName(\"\")\nlet effects = effectActor.effects.contents\nlet drawing = canvas.drawings.controlled[0]\nlet content\nfor ( let effect of effects) {\ncontent += ``;\n}\nnew Dialog({\n title: 'Example',\n content: `\n
\n
\n \n
\n
\n `,\n buttons: {\n apply: {\n label: \"Apply\",\n callback: async (html) => {\n let effectId = html.find('#effect').val();\n let effect = effects.find(i => i.id === effectId)\n effect.origin = `Actor.${effectActor.id}`\n await drawing.document.setFlag(\"ActiveAuras\", \"IsAura\", [effect.data])\n AAhelpers.UserCollateAuras(canvas.scene.id, true, false, \"drawingAdd\")\n }\n }\n }\n}).render(true)","folder":null,"sort":0,"flags":{"furnace":{"runAsGM":false},"combat-utility-belt":{"macroTrigger":""},"core":{"sourceId":"Macro.RukVihRspPDZRSKM"},"advanced-macros":{"runAsGM":false}},"_id":"r8hqRcIOLk3ymtUW","ownership":{"default":0,"E4BVikjIkVl2lL2j":3,"jkdToQqzFl0mFXls":3},"_stats":{"systemId":"dnd5e","systemVersion":"2.0.1","coreVersion":"10.283","createdTime":1662274930165,"modifiedTime":1662275271741,"lastModifiedBy":"GrX6tFOozYFBnDHq"}} From a8a9b669cc05c270191750785bb4d947b7121110 Mon Sep 17 00:00:00 2001 From: Jack Holloway Date: Tue, 1 Nov 2022 10:33:04 +0000 Subject: [PATCH 26/31] Template walls block again --- Changelog.md | 1 + src/measureDistances.js | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Changelog.md b/Changelog.md index db0cec0..eb30f20 100644 --- a/Changelog.md +++ b/Changelog.md @@ -93,3 +93,4 @@ New settings to apply/remove aura effects from hidden actors (not invisible). De # 0.4.22 Duplicate macro removed. +Walls now block on templates again. diff --git a/src/measureDistances.js b/src/measureDistances.js index fd8d7ea..dc832a2 100644 --- a/src/measureDistances.js +++ b/src/measureDistances.js @@ -85,8 +85,7 @@ class AAmeasure { static isTokenInside(templateDetails, token, wallsBlockTargeting = false) { const grid = canvas?.scene?.grid?.size; - if (!grid) - return false; + if (!grid) return false; const templatePos = { x: templateDetails.x, y: templateDetails.y }; // Check for center of each square the token uses. // e.g. for large tokens all 4 squares @@ -107,6 +106,8 @@ class AAmeasure { ty = ty + templateDetails.shape.height / 2; } const r = new Ray({ x: tx, y: ty }, { x: currGrid.x + templatePos.x, y: currGrid.y + templatePos.y }); + + console.warn("templateCheck", { templateDetails, token, contains, tx, ty, r }) /**if (wallsBlockTargeting && game.modules.get("levels")?.active) { let p1 = { x: currGrid.x + templatePos.x, y: currGrid.y + templatePos.y, @@ -126,7 +127,9 @@ class AAmeasure { //@ts-ignore }*/ - contains = !canvas?.walls?.checkCollision(r); + // collision blocked by sight or movement + contains = !canvas.walls.checkCollision(r, {mode: "any", type: "sight" }) + && !canvas.walls.checkCollision(r, {mode: "any", type: "move" }); } // Check the distance from origin. From 5c67b5be91050e9471afa4e5ec9e6d13b9f11b21 Mon Sep 17 00:00:00 2001 From: Jack Holloway Date: Tue, 1 Nov 2022 10:33:28 +0000 Subject: [PATCH 27/31] v0.4.22 --- module.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module.json b/module.json index 719fb9e..a678138 100644 --- a/module.json +++ b/module.json @@ -8,7 +8,7 @@ "url": "https://github.com/kandashi/Active-Auras", "bugs": "https://github.com/kandashi/Active-Auras/issues", "flags": {}, - "version": "0.4.21", + "version": "0.4.22", "compatibility": { "minimum": 10, "verified": 10 From d870d5992e4a496e5e0854b399b61a5dc5b44a88 Mon Sep 17 00:00:00 2001 From: Jack Holloway Date: Thu, 3 Nov 2022 17:52:28 +0000 Subject: [PATCH 28/31] Assignment to const in template auras --- Changelog.md | 4 ++++ src/collateAuras.js | 8 ++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Changelog.md b/Changelog.md index eb30f20..33b2808 100644 --- a/Changelog.md +++ b/Changelog.md @@ -94,3 +94,7 @@ New settings to apply/remove aura effects from hidden actors (not invisible). De Duplicate macro removed. Walls now block on templates again. + +# 0.4.23 + +Some template auras would fail. diff --git a/src/collateAuras.js b/src/collateAuras.js index e0057c7..c7caa87 100644 --- a/src/collateAuras.js +++ b/src/collateAuras.js @@ -40,7 +40,7 @@ async function CollateAuras(sceneID, checkAuras, removeAuras, source) { for (const change of newEffect.data.changes) { if (typeof change.value !== "string") continue; let s = change.value; - for (const match of s.match(re) || []) { + for (let match of s.match(re) || []) { if (s.includes("@@")) { s = s.replace(match, match.slice(1)); } @@ -98,7 +98,7 @@ function RetrieveTemplateAuras(effectArray) { const re = /@[\w\.]+/g; for (const change of newEffect.data.changes) { if (typeof change.value !== "string") continue; - const s = change.value; + let s = change.value; for (const match of s.match(re) || []) s = s.replace(match, getProperty(rollData, match.slice(1))); change.value = s; if (change.key === "macro.execute" || change.key === "macro.itemMacro") newEffect.data.flags.ActiveAuras.isMacro = true; @@ -132,8 +132,8 @@ function RetrieveDrawingAuras(effectArray) { for (let change of newEffect.data.changes) { if (typeof change.value !== "string") continue; const re = /@[\w\.]+/g; - const s = change.value; - for (let match of s.match(re) || []) s = s.replace(match, getProperty(rollData, match.slice(1))); + let s = change.value; + for (const match of s.match(re) || []) s = s.replace(match, getProperty(rollData, match.slice(1))); change.value = s; if (change.key === "macro.execute" || change.key === "macro.itemMacro") newEffect.data.flags.ActiveAuras.isMacro = true; } From 57ef7cfeba2a3bbaf1e64ed876319bf1ad551780 Mon Sep 17 00:00:00 2001 From: Jack Holloway Date: Thu, 3 Nov 2022 17:53:00 +0000 Subject: [PATCH 29/31] v0.4.23 --- module.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module.json b/module.json index a678138..932ecfe 100644 --- a/module.json +++ b/module.json @@ -8,7 +8,7 @@ "url": "https://github.com/kandashi/Active-Auras", "bugs": "https://github.com/kandashi/Active-Auras/issues", "flags": {}, - "version": "0.4.22", + "version": "0.4.23", "compatibility": { "minimum": 10, "verified": 10 From 6fa8427d2da61ae200da994896fd501b34f4f577 Mon Sep 17 00:00:00 2001 From: Jack Holloway Date: Fri, 11 Nov 2022 21:04:56 +0000 Subject: [PATCH 30/31] Handle Multiple GMs --- Changelog.md | 4 ++++ src/AAhooks.js | 34 +++++++++++++++++++++------------- src/settings.js | 6 ++++++ 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/Changelog.md b/Changelog.md index 33b2808..5778bfd 100644 --- a/Changelog.md +++ b/Changelog.md @@ -98,3 +98,7 @@ Walls now block on templates again. # 0.4.23 Some template auras would fail. + +# 0.4.24 + +Handle cases of multiple GM's logged in. diff --git a/src/AAhooks.js b/src/AAhooks.js index 13c5551..26ed469 100644 --- a/src/AAhooks.js +++ b/src/AAhooks.js @@ -34,14 +34,24 @@ Hooks.on("init", () => { libWrapper.register("ActiveAuras", "ActiveEffect.prototype._displayScrollingStatus", AAhelpers.scrollingText, "WRAPPER"); }); -Hooks.on("ready", () => { +Hooks.on("ready", async () => { + if (game.settings.get("ActiveAuras", "debug")) AAdebug = true; if (canvas.scene === null) { if (AAdebug) { console.log("Active Auras disabled due to no canvas") } return } - AAgm = game.user === game.users.find((u) => u.isGM && u.active); - CollateAuras(canvas.id, true, false); - - if (game.settings.get("ActiveAuras", "debug")) AAdebug = true + // determine if this user is the active gm/first active in current session + if (game.user.isGM) { + const currentGMUser = game.users.get(game.settings.get("ActiveAuras", "ActiveGM")); + if ((currentGMUser && !currentGMUser.active) || !currentGMUser || currentGMUser.id === game.user.id) { + await game.settings.set("ActiveAuras", "ActiveGM", game.user.id); + AAgm = true; + } else { + AAgm = false; + } + } else { + AAgm = false; + } + CollateAuras(canvas.id, true, false); }); Hooks.on("createToken", (token) => { @@ -106,7 +116,7 @@ Hooks.on("updateToken", async (token, update, _flags, _id) => { }; if(_flags.animate === false) { - movementUpdate(); + await movementUpdate(); } else { const moveHookId = Hooks.on("refreshToken", async (rToken) => { if (rToken.id !== token.id @@ -115,17 +125,15 @@ Hooks.on("updateToken", async (token, update, _flags, _id) => { || ("elevation" in update && rToken.document.elevation !== update.elevation) ) return; Hooks.off("refreshToken", moveHookId); - movementUpdate(); + await movementUpdate(); }) - movementUpdate(); + // await movementUpdate(); } - } - // in v10 invisible is now a thing, so hidden is considered "not on scene" - else if (hasProperty(update, "hidden") && (!update.hidden || AAhelpers.IsAuraToken(token.id, token.parent.id))) { + } else if (hasProperty(update, "hidden") && (!update.hidden || AAhelpers.IsAuraToken(token.id, token.parent.id))) { + // in v10 invisible is now a thing, so hidden is considered "not on scene" if (AAdebug) console.log(`hidden, collate auras ${!update.hidden} ${update.hidden}`); debouncedCollate(canvas.scene.id, !update.hidden, update.hidden, "updateToken, hidden"); - } - else if (AAhelpers.IsAuraToken(token.id, token.parent.id) && AAhelpers.HPCheck(token)) { + } else if (AAhelpers.IsAuraToken(token.id, token.parent.id) && AAhelpers.HPCheck(token)) { if (AAdebug) console.log("0hp, collate auras true true"); debouncedCollate(canvas.scene.id, true, true, "updateToken, dead"); } diff --git a/src/settings.js b/src/settings.js index d3d0b82..43b7294 100644 --- a/src/settings.js +++ b/src/settings.js @@ -67,4 +67,10 @@ Hooks.on('init', () => { default: false, type: Boolean, }); + game.settings.register("ActiveAuras", "ActiveGM", { + scope: "world", + config: false, + default: null, + type: String, + }); }); From 808fd6e846c9d55723e5b215cf24b585d4053bb0 Mon Sep 17 00:00:00 2001 From: Jack Holloway Date: Fri, 11 Nov 2022 21:05:20 +0000 Subject: [PATCH 31/31] v0.4.24 --- module.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module.json b/module.json index 932ecfe..8e992c2 100644 --- a/module.json +++ b/module.json @@ -8,7 +8,7 @@ "url": "https://github.com/kandashi/Active-Auras", "bugs": "https://github.com/kandashi/Active-Auras/issues", "flags": {}, - "version": "0.4.23", + "version": "0.4.24", "compatibility": { "minimum": 10, "verified": 10