(async () => { if (!token) { ui.notifications.warn("You must select a token!"); return; } const ac = actor; const magusLevel = ac.classes?.magus?.level ?? 0; const enhancementCap = Math.max(1, Math.floor((magusLevel - 1) / 4 + 1)); const arcanePool = ac.system?.resources?.classFeat_arcanePool?.value ?? 0; const FLAG_SCOPE = "world"; const FLAG_ROOT = "arcanePool"; const flagPath = (key) => `${FLAG_ROOT}.${key}`; const getFlag = (doc, key, fallback) => { const value = doc.getFlag(FLAG_SCOPE, flagPath(key)); return value === undefined ? fallback : value; }; const setFlag = (doc, key, value) => doc.setFlag(FLAG_SCOPE, flagPath(key), value); const readBuffFlag = (doc, key, fallback) => { const value = doc.getFlag(FLAG_SCOPE, flagPath(key)); return value === undefined ? fallback : value; }; const setBuffFlag = (doc, key, value) => doc.setFlag(FLAG_SCOPE, flagPath(key), value); function isAttackActive(item) { if (item.type !== "attack") return false; const equipped = item.system?.equipped; if (typeof equipped === "boolean") return equipped; const held = item.system?.held; if (typeof held === "string" && held.length) { const normalized = held.toLowerCase(); if (["none", "not", "unheld", "disabled", "inactive"].includes(normalized)) return false; } return true; } const equippedWeapons = (ac.itemTypes?.attack ?? ac.items.filter((item) => item.type === "attack")).filter( isAttackActive ); if (!equippedWeapons.length) { ui.notifications.error("No equipped weapon found! Equip a weapon before using Arcane Pool."); return; } const existingConfig = getFlag(ac, "config", {}) ?? {}; const currentWeapon = equippedWeapons.find((item) => item.id === existingConfig.weaponId) ?? equippedWeapons[0]; const SYNC_MACRO_NAME = "_arcanePoolApplyEffects"; const OPTION_KEY_MAP = { Keen: "keen", "Ghost Touch": "ghostTouch", Speed: "speed", Dancing: "dancing", "Brilliant Energy": "brilliantEnergy", Vorpal: "vorpal", Flaming: "flaming", "Flaming Burst": "flamingBurst", Frost: "frost", "Icy Burst": "icyBurst", Shock: "shock", "Shocking Burst": "shockingBurst", }; const BUFF_DEFINITIONS = { base: { name: "Arcane Pool (Enhancement)", aliases: ["Arcane Pool", "Arcane Pool - Enhancement"], img: "icons/magic/defensive/shield-barrier-flaming-pentagon.webp", role: "base", }, keen: { name: "Keen", aliases: ["Arcane Pool - Keen"], img: "icons/skills/melee/weapons-crossed-swords-yellow.webp", property: "keen", }, ghostTouch: { name: "Ghost Touch", aliases: ["Arcane Pool - Ghost Touch"], img: "icons/magic/perception/eye-slit-pink.webp", property: "ghost-touch", }, speed: { name: "Speed", aliases: ["Arcane Pool - Speed"], img: "icons/magic/movement/trail-streak-zigzag-yellow.webp", property: "speed", }, dancing: { name: "Dancing", aliases: ["Arcane Pool - Dancing"], img: "icons/magic/control/fear-fright-monster-green.webp", property: "dancing", }, brilliantEnergy: { name: "Brilliant Energy", aliases: ["Arcane Pool - Brilliant Energy"], img: "icons/magic/light/projectile-beam-strike-yellow.webp", property: "brilliant-energy", }, vorpal: { name: "Vorpal", aliases: ["Arcane Pool - Vorpal"], img: "icons/skills/melee/blade-tip-triple-blue.webp", property: "vorpal", }, flaming: { name: "Flaming", aliases: ["Arcane Pool - Flaming"], img: "icons/magic/fire/flame-burning-sword.webp", property: "flaming", damageFormula: "1d6[fire]", }, flamingBurst: { name: "Flaming Burst", aliases: ["Arcane Pool - Flaming Burst"], img: "icons/magic/fire/explosion-fireball-medium-red.webp", property: "flaming-burst", damageFormula: "1d6[fire]+1d10[fire]", }, frost: { name: "Frost", aliases: ["Arcane Pool - Frost"], img: "icons/magic/water/snowflake-ice-blue.webp", property: "frost", damageFormula: "1d6[cold]", }, icyBurst: { name: "Icy Burst", aliases: ["Arcane Pool - Icy Burst"], img: "icons/magic/water/projectile-ice-snowball.webp", property: "icy-burst", damageFormula: "1d6[cold]+1d10[cold]", }, shock: { name: "Shock", aliases: ["Arcane Pool - Shock"], img: "icons/magic/lightning/bolt-forked-blue.webp", property: "shock", damageFormula: "1d6[electricity]", }, shockingBurst: { name: "Shocking Burst", aliases: ["Arcane Pool - Shocking Burst"], img: "icons/magic/lightning/bolt-strike-beam-blue.webp", property: "shocking-burst", damageFormula: "1d6[electricity]+1d10[electricity]", }, }; async function ensureSyncScript(buff) { const script = ` const macro = game.macros.getName("${SYNC_MACRO_NAME}"); if (macro) { macro.execute([{ actorId: this.actor?.id }]); }`; const scriptCalls = foundry.utils.duplicate(buff.system?.scriptCalls ?? []); const existing = scriptCalls.find((entry) => entry.name === "Arcane Pool Sync"); if (existing) { if (existing.value !== script) { existing.value = script; await buff.update({ "system.scriptCalls": scriptCalls }); } return; } scriptCalls.push({ _id: foundry.utils.randomID(8), name: "Arcane Pool Sync", img: "icons/svg/coins.svg", type: "script", category: "toggle", value: script, hidden: false, }); await buff.update({ "system.scriptCalls": scriptCalls }); } async function ensureArcanePoolBuff(actorDocument, key) { const definition = BUFF_DEFINITIONS[key]; if (!definition) return null; const lookupNames = [definition.name, ...(definition.aliases ?? [])]; const buff = actorDocument.items.find( (item) => item.type === "buff" && lookupNames.includes(item.name) ); if (!buff) { const message = `Arcane Pool requires the "${definition.name}" buff on ${actorDocument.name}. Please add it to the actor.`; ui.notifications.error(message); throw new Error(message); } if (readBuffFlag(buff, "tag") !== key) await setBuffFlag(buff, "tag", key); if (readBuffFlag(buff, "role") !== (definition.role ?? "property")) { await setBuffFlag(buff, "role", definition.role ?? "property"); } if (readBuffFlag(buff, "property") !== (definition.property ?? null)) { await setBuffFlag(buff, "property", definition.property ?? null); } if (readBuffFlag(buff, "damageFormula") !== (definition.damageFormula ?? null)) { await setBuffFlag(buff, "damageFormula", definition.damageFormula ?? null); } await ensureSyncScript(buff); return buff; } const DIALOG_WIDTH = 480; // 20% wider than the default ~400px dialog function buildDialog(weaponName) { return `
Enhancements are applied via Arcane Pool buff items and stay in sync with any toggle source.
| Enhancement | Options | Cost |
|---|---|---|
| Keen [1] | 0 | |
| Ghost Touch [1] | 0 | |
| Speed [3] | 0 | |
| Dancing [4] | 0 | |
| Brilliant Energy [4] | 0 | |
| Vorpal [5] | 0 | |
| Fire | None Flaming Flaming Burst | 0 |
| Ice | None Frost Icy Burst | 0 |
| Lightning | None Shock Shocking Burst | 0 |