diff --git a/daggerheart.mjs b/daggerheart.mjs index 11111802..4cf4bc3f 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -381,6 +381,74 @@ Hooks.on(CONFIG.DH.HOOKS.hooksConfig.groupRollStart, async data => { } }); +const updateActorsRangeDependentEffects = async token => { + if (!token) return; + + const rangeMeasurement = game.settings.get( + CONFIG.DH.id, + CONFIG.DH.SETTINGS.gameSettings.variantRules + ).rangeMeasurement; + + for (let effect of token.actor?.allApplicableEffects() ?? []) { + if (!effect.system.rangeDependence || effect.system.rangeDependence.enabled === false) continue; + const { target, range, type } = effect.system.rangeDependence; + + // If there are no targets, assume false. Otherwise, start with the effect enabled. + let enabledEffect = game.user.targets.size !== 0; + // Expect all targets to meet the rangeDependence requirements + for (let userTarget of game.user.targets) { + const disposition = userTarget.document.disposition; + if ((target === 'friendly' && disposition !== 1) || (target === 'hostile' && disposition !== -1)) { + enabledEffect = false; + break; + } + + // Get required distance and special case 5 feet to test adjacency + const required = rangeMeasurement[range]; + const reverse = type === CONFIG.DH.GENERAL.rangeInclusion.outsideRange.id; + const inRange = userTarget.distanceTo(token.object) <= required; + if (reverse ? inRange : !inRange) { + enabledEffect = false; + break; + } + } + + await effect.update({ disabled: !enabledEffect }); + } +}; + +const updateAllRangeDependentEffects = async () => { + const effectsAutomation = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).effects; + if (!effectsAutomation.rangeDependent) return; + + const tokens = canvas.scene?.tokens; + if (!tokens) return; + + if (game.user.character) { + // The character updates their character's token. There can be only one token. + const characterToken = tokens.find(x => x.actor === game.user.character); + updateActorsRangeDependentEffects(characterToken); + } else if (game.user.isActiveGM) { + // The GM is responsible for all other tokens. + const playerCharacters = game.users.players.filter(x => x.active).map(x => x.character); + for (const token of tokens.filter(x => !playerCharacters.includes(x.actor))) { + updateActorsRangeDependentEffects(token); + } + } +}; + +const debouncedRangeEffectCall = foundry.utils.debounce(updateAllRangeDependentEffects, 50); + +Hooks.on('targetToken', () => { + debouncedRangeEffectCall(); +}); + +Hooks.on('refreshToken', (token, options) => { + if (options.refreshPosition && !token._original) { + debouncedRangeEffectCall(); + } +}); + Hooks.on('renderCompendiumDirectory', (app, html) => applications.ui.ItemBrowser.injectSidebarButton(html)); Hooks.on('renderDocumentDirectory', (app, html) => applications.ui.ItemBrowser.injectSidebarButton(html)); diff --git a/module/applications/sheets-configs/activeEffectConfig.mjs b/module/applications/sheets-configs/activeEffectConfig.mjs index 01fc447a..1f18a61e 100644 --- a/module/applications/sheets-configs/activeEffectConfig.mjs +++ b/module/applications/sheets-configs/activeEffectConfig.mjs @@ -233,6 +233,7 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac const systemData = { rangeDependence: event.target.checked ? { + enabled: true, // Temporary while the onMove handling is still in type: rangeFields.type.initial, target: rangeFields.target.initial, range: rangeFields.range.initial diff --git a/module/data/activeEffect/baseEffect.mjs b/module/data/activeEffect/baseEffect.mjs index 1c69d926..34ebe9d4 100644 --- a/module/data/activeEffect/baseEffect.mjs +++ b/module/data/activeEffect/baseEffect.mjs @@ -57,6 +57,10 @@ export default class BaseEffect extends foundry.data.ActiveEffectTypeDataModel { description: new fields.HTMLField({ label: 'DAGGERHEART.GENERAL.description' }) }), rangeDependence: new fields.SchemaField({ + enabled: new fields.BooleanField({ // Temporary for the remaining onMove logic + initial: false, + label: 'DAGGERHEART.GENERAL.enabled' + }), type: new fields.StringField({ required: true, choices: CONFIG.DH.GENERAL.rangeInclusion,