(async () => { try { const FLAG_SCOPE = "world"; const FLAG_ROOT = "arcanePool"; const flagPath = (key) => `${FLAG_ROOT}.${key}`; const getFlag = (doc, key) => doc.getFlag(FLAG_SCOPE, flagPath(key)); const setFlag = (doc, key, value) => doc.setFlag(FLAG_SCOPE, flagPath(key), value); const readBuffFlag = (doc, key) => doc.getFlag(FLAG_SCOPE, flagPath(key)); // Normalize arguments const rawArgs = Array.isArray(args) ? args : []; const options = rawArgs[0] ?? {}; const actorId = options.actorId ?? actor?.id; if (!actorId) { console.warn("Arcane Pool AE: Missing actor id"); return false; } const targetActor = game.actors.get(actorId); if (!targetActor) { console.warn(`Arcane Pool AE: Actor ${actorId} not found`); return false; } // Merge config updates when provided const currentConfig = getFlag(targetActor, "config") ?? {}; const configUpdates = { weaponId: options.weaponId, enhancementBonus: options.enhancementBonus, poolCost: options.poolCost, enduringBlade: options.enduringBlade, ghostBlade: options.ghostBlade, }; const sanitizedUpdates = Object.fromEntries( Object.entries(configUpdates).filter(([, value]) => value !== undefined) ); const updatedConfig = { ...currentConfig, ...sanitizedUpdates, }; if (!foundry.utils.isEmpty(updatedConfig) && !foundry.utils.isEqual(updatedConfig, currentConfig)) { await setFlag(targetActor, "config", updatedConfig); } const config = getFlag(targetActor, "config"); const weaponId = config?.weaponId; if (!weaponId) { console.warn("Arcane Pool AE: No weaponId in config"); return false; } const weapon = targetActor.items.get(weaponId); if (!weapon) { console.warn(`Arcane Pool AE: Weapon ${weaponId} not found on actor ${targetActor.name}`); return false; } // Locate primary attack action (default "Attack") const attacks = weapon.system?.actions ?? []; const attackIndex = attacks.findIndex((action) => (action?.name ?? "").toLowerCase() === "attack"); const attackPath = attackIndex >= 0 ? `system.actions.${attackIndex}` : null; // Cache baseline weapon data to allow clean restoration let baseline = getFlag(weapon, "baseline"); const baselineWeaponId = baseline?.weaponId; if (!baseline || baselineWeaponId !== weaponId) { baseline = { weaponId, enh: weapon.system?.enh ?? 0, damageParts: foundry.utils.duplicate(weapon.system?.damage?.parts ?? []), attackAbility: attackPath ? { critRange: foundry.utils.getProperty(weapon.system, `${attackPath}.ability.critRange`) ?? null, critMult: foundry.utils.getProperty(weapon.system, `${attackPath}.ability.critMult`) ?? null, } : null, }; await setFlag(weapon, "baseline", baseline); } const activeBuffs = targetActor.items.filter( (item) => item.type === "buff" && item.system?.active && readBuffFlag(item, "tag") ); const baseBuff = activeBuffs.find((buff) => readBuffFlag(buff, "role") === "base"); // Restore baseline if base buff is inactive if (!baseBuff) { const restoreData = { "system.enh": baseline.enh ?? 0, "system.damage.parts": baseline.damageParts ?? [], "flags.arcanePool.active": false, "flags.arcanePool.activeProperties": [], }; if (baseline.attackAbility && attackPath) { if (baseline.attackAbility.critRange !== null) { restoreData[`${attackPath}.ability.critRange`] = baseline.attackAbility.critRange; } if (baseline.attackAbility.critMult !== null) { restoreData[`${attackPath}.ability.critMult`] = baseline.attackAbility.critMult; } } await weapon.update(restoreData); return true; } const enhancementBonus = Number(config?.enhancementBonus ?? readBuffFlag(baseBuff, "enhancementBonus") ?? 0) || 0; // Build new damage array starting from baseline const newDamage = foundry.utils.duplicate(baseline.damageParts ?? []); const activeProperties = []; for (const buff of activeBuffs) { if (buff.id === baseBuff.id) continue; const propertyKey = readBuffFlag(buff, "property"); if (!propertyKey) continue; activeProperties.push(propertyKey); const damageFormula = readBuffFlag(buff, "damageFormula"); if (!damageFormula) continue; const parts = damageFormula.split("+").map((segment) => segment.trim()).filter(Boolean); for (const part of parts) { const match = part.match(/([^[]+)\[([^]+)]/); if (!match) continue; const [, dice, damageType] = match; newDamage.push([dice.trim(), damageType.trim()]); } } const updateData = { "system.enh": (baseline.enh ?? 0) + enhancementBonus, "system.damage.parts": newDamage, "flags.arcanePool.active": true, "flags.arcanePool.activeProperties": activeProperties, "flags.arcanePool.lastApplied": Date.now(), }; // Keen, Speed, and other non-damage properties currently rely on player toggles. // Additional automation can be added by extending property handlers here. if (baseline.attackAbility && attackPath) { if (baseline.attackAbility.critRange !== null) { updateData[`${attackPath}.ability.critRange`] = baseline.attackAbility.critRange; } if (baseline.attackAbility.critMult !== null) { updateData[`${attackPath}.ability.critMult`] = baseline.attackAbility.critMult; } } await weapon.update(updateData); return true; } catch (error) { console.error("Arcane Pool AE: Failed to apply effects", error); ui.notifications.error("Failed to synchronize Arcane Pool effects."); return false; } })();