track HP in chat
This commit is contained in:
168
src/macro_arcanePool_applyEffects.js
Normal file
168
src/macro_arcanePool_applyEffects.js
Normal file
@@ -0,0 +1,168 @@
|
||||
(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;
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user