diff --git a/daggerheart.mjs b/daggerheart.mjs index 56ad3e3d..c13719f6 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -6,7 +6,7 @@ import * as dice from './module/dice/_module.mjs'; import * as fields from './module/data/fields/_module.mjs'; import RegisterHandlebarsHelpers from './module/helpers/handlebarsHelper.mjs'; import { enricherConfig, enricherRenderSetup } from './module/enrichers/_module.mjs'; -import { getCommandTarget, rollCommandToJSON } from './module/helpers/utils.mjs'; +import { compareValues, getCommandTarget, rollCommandToJSON } from './module/helpers/utils.mjs'; import { NarrativeCountdowns } from './module/applications/ui/countdowns.mjs'; import { DualityRollColor } from './module/data/settings/Appearance.mjs'; import { DHRoll, DualityRoll, D20Roll, DamageRoll, DualityDie } from './module/dice/_module.mjs'; @@ -235,3 +235,73 @@ Hooks.on('renderJournalDirectory', async (tab, html, _, options) => { }; } }); + +Hooks.on('moveToken', async (movedToken, data) => { + const effectsAutomation = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).effects; + if (!effectsAutomation.rangeDependent) return; + + const rangeDependantEffects = movedToken.actor.effects.filter(effect => effect.system.rangeDependence.enabled); + + const { x, y, height, width } = data.destination; + const getDimensions = (x, y, height, width) => { + const heightModifier = Math.max(Math.round(height) - 1, 0); + const widthModifier = Math.max(Math.round(width) - 1, 0); + + return { + minX: x - widthModifier, + maxX: x + widthModifier, + minY: y - heightModifier, + maxY: y + widthModifier + }; + }; + + const { minX: dMinX, maxX: dMaxX, minY: dMinY, maxY: dMaxY } = getDimensions(x, y, height, width); + + const updateEffects = async (token, dimensions, effects, effectUpdates) => { + const { minX, maxX, minY, maxY } = dimensions; + + const rangeMeasurement = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.RangeMeasurement); + + for (let effect of effects.filter(x => x.system.rangeDependence.enabled)) { + const { target, range, type } = effect.system.rangeDependence; + if ( + (target === 'friendly' && token.disposition !== 1) || + (target === 'hostile' && token.disposition !== -1) + ) + return false; + + const distance = rangeMeasurement[range] * 20; // x/y scale is 100. Grid in feet is 5. + const xOkay = + compareValues(Math.abs(minX - dMinX), distance, type) || + compareValues(Math.abs(maxX - dMaxX), distance, type); + const yOkay = + compareValues(Math.abs(minY - dMinY), distance, type) || + compareValues(Math.abs(maxY - dMaxY), distance, type); + + const reverse = ['moreThan', 'moreThanEqual'].includes(type); + const newDisabled = reverse ? !xOkay && !yOkay : !xOkay || !yOkay; + const oldDisabled = effectUpdates[effect.uuid] ? effectUpdates[effect.uuid].disabled : newDisabled; + effectUpdates[effect.uuid] = { + disabled: reverse ? oldDisabled || newDisabled : oldDisabled && newDisabled, + value: effect + }; + } + }; + + const effectUpdates = {}; + for (let token of game.scenes.find(x => x.active).tokens) { + if (token.id === movedToken.id) continue; + + const { x, y, height, width } = token; + const dimensions = getDimensions(x, y, height, width); + + await updateEffects(token, dimensions, rangeDependantEffects, effectUpdates); + + if (token.actor) await updateEffects(token, dimensions, token.actor.effects, effectUpdates); + } + + for (let key in effectUpdates) { + const effect = effectUpdates[key]; + await effect.value.update({ disabled: effect.disabled }); + } +}); diff --git a/lang/en.json b/lang/en.json index 40317f8c..d8ce2e2b 100755 --- a/lang/en.json +++ b/lang/en.json @@ -88,6 +88,17 @@ } } }, + "ACTIVEEFFECT": { + "Config": { + "rangeDependence": { + "title": "Range Dependence" + } + }, + "RangeDependance": { + "hint": "Settings for an optional distance at which this effect should activate", + "title": "Range Dependant" + } + }, "ACTORS": { "Adversary": { "FIELDS": { @@ -622,6 +633,13 @@ "oneHanded": "One-Handed", "twoHanded": "Two-Handed" }, + "CompareOperator": { + "lessThan": "Less Than", + "lessThanEqual": "Less Than Equal", + "equal": "Equal", + "moreThanEqual": "More Than Equal", + "moreThan": "More Than" + }, "Condition": { "dead": { "name": "Dead", @@ -1546,6 +1564,12 @@ "hordeDamage": { "label": "Automatic Horde Damage", "hint": "Automatically active horde effect to lower damage when reaching half or lower HP." + }, + "effects": { + "rangeDependent": { + "label": "Effect Range Dependent", + "hint": "Effects with defined range dependency will automatically turn on/off depending on range" + } } } }, diff --git a/module/applications/sheets-configs/activeEffectConfig.mjs b/module/applications/sheets-configs/activeEffectConfig.mjs index 88edf9d6..25f7d2b5 100644 --- a/module/applications/sheets-configs/activeEffectConfig.mjs +++ b/module/applications/sheets-configs/activeEffectConfig.mjs @@ -27,7 +27,7 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac header: { template: 'systems/daggerheart/templates/sheets/activeEffect/header.hbs' }, tabs: { template: 'templates/generic/tab-navigation.hbs' }, details: { template: 'systems/daggerheart/templates/sheets/activeEffect/details.hbs', scrollable: [''] }, - duration: { template: 'systems/daggerheart/templates/sheets/activeEffect/duration.hbs' }, + settings: { template: 'systems/daggerheart/templates/sheets/activeEffect/settings.hbs' }, changes: { template: 'systems/daggerheart/templates/sheets/activeEffect/changes.hbs', scrollable: ['ol[data-changes]'] @@ -39,7 +39,7 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac sheet: { tabs: [ { id: 'details', icon: 'fa-solid fa-book' }, - { id: 'duration', icon: 'fa-solid fa-clock' }, + { id: 'settings', icon: 'fa-solid fa-bars', label: 'DAGGERHEART.GENERAL.Tabs.settings' }, { id: 'changes', icon: 'fa-solid fa-gears' } ], initial: 'details', diff --git a/module/config/actionConfig.mjs b/module/config/actionConfig.mjs index 1f1ebf8b..3c669a7b 100644 --- a/module/config/actionConfig.mjs +++ b/module/config/actionConfig.mjs @@ -43,25 +43,6 @@ export const actionTypes = { } }; -export const targetTypes = { - self: { - id: 'self', - label: 'Self' - }, - friendly: { - id: 'friendly', - label: 'Friendly' - }, - hostile: { - id: 'hostile', - label: 'Hostile' - }, - any: { - id: 'any', - label: 'Any' - } -}; - export const damageOnSave = { none: { id: 'none', diff --git a/module/config/generalConfig.mjs b/module/config/generalConfig.mjs index 7e44cad7..005ac4a3 100644 --- a/module/config/generalConfig.mjs +++ b/module/config/generalConfig.mjs @@ -43,6 +43,52 @@ export const range = { } }; +export const compareOperator = { + lessThan: { + id: 'lessThan', + label: 'DAGGERHEART.CONFIG.CompareOperator.lessThan' + }, + lessThanEqual: { + id: 'lessThanEqual', + label: 'DAGGERHEART.CONFIG.CompareOperator.lessThanEqual' + }, + equal: { + id: 'equal', + label: 'DAGGERHEART.CONFIG.CompareOperator.equal' + }, + moreThanEqual: { + id: 'moreThanEqual', + label: 'DAGGERHEART.CONFIG.CompareOperator.moreThanEqual' + }, + moreThan: { + id: 'moreThan', + label: 'DAGGERHEART.CONFIG.CompareOperator.moreThan' + } +}; + +export const otherTargetTypes = { + friendly: { + id: 'friendly', + label: 'Friendly' + }, + hostile: { + id: 'hostile', + label: 'Hostile' + }, + any: { + id: 'any', + label: 'Any' + } +}; + +export const targetTypes = { + self: { + id: 'self', + label: 'Self' + }, + ...otherTargetTypes +}; + export const burden = { oneHanded: { value: 'oneHanded', diff --git a/module/data/activeEffect/_module.mjs b/module/data/activeEffect/_module.mjs index 126aec5e..79ad7813 100644 --- a/module/data/activeEffect/_module.mjs +++ b/module/data/activeEffect/_module.mjs @@ -1,7 +1,9 @@ +import BaseEffect from './baseEffect.mjs'; import BeastformEffect from './beastformEffect.mjs'; -export { BeastformEffect }; +export { BaseEffect, BeastformEffect }; export const config = { + base: BaseEffect, beastform: BeastformEffect }; diff --git a/module/data/activeEffect/baseEffect.mjs b/module/data/activeEffect/baseEffect.mjs new file mode 100644 index 00000000..178b7fc4 --- /dev/null +++ b/module/data/activeEffect/baseEffect.mjs @@ -0,0 +1,33 @@ +export default class BaseEffect extends foundry.abstract.TypeDataModel { + static defineSchema() { + const fields = foundry.data.fields; + + return { + rangeDependence: new fields.SchemaField({ + enabled: new fields.BooleanField({ + required: true, + initial: false, + label: 'DAGGERHEART.GENERAL.enabled' + }), + type: new fields.StringField({ + required: true, + choices: CONFIG.DH.GENERAL.compareOperator, + initial: CONFIG.DH.GENERAL.compareOperator.lessThanEqual.id, + label: 'DAGGERHEART.GENERAL.type' + }), + target: new fields.StringField({ + required: true, + choices: CONFIG.DH.GENERAL.otherTargetTypes, + initial: CONFIG.DH.GENERAL.otherTargetTypes.hostile.id, + label: 'DAGGERHEART.GENERAL.Target.single' + }), + range: new fields.StringField({ + required: true, + choices: CONFIG.DH.GENERAL.range, + initial: CONFIG.DH.GENERAL.range.melee.id, + label: 'DAGGERHEART.GENERAL.range' + }) + }) + }; + } +} diff --git a/module/data/activeEffect/beastformEffect.mjs b/module/data/activeEffect/beastformEffect.mjs index 91b84614..e040d8d8 100644 --- a/module/data/activeEffect/beastformEffect.mjs +++ b/module/data/activeEffect/beastformEffect.mjs @@ -1,6 +1,7 @@ import { updateActorTokens } from '../../helpers/utils.mjs'; +import BaseEffect from './baseEffect.mjs'; -export default class BeastformEffect extends foundry.abstract.TypeDataModel { +export default class BeastformEffect extends BaseEffect { static defineSchema() { const fields = foundry.data.fields; return { diff --git a/module/data/fields/action/targetField.mjs b/module/data/fields/action/targetField.mjs index 1024d5d7..f54cd6fe 100644 --- a/module/data/fields/action/targetField.mjs +++ b/module/data/fields/action/targetField.mjs @@ -4,8 +4,8 @@ export default class TargetField extends fields.SchemaField { constructor(options = {}, context = {}) { const targetFields = { type: new fields.StringField({ - choices: CONFIG.DH.ACTIONS.targetTypes, - initial: CONFIG.DH.ACTIONS.targetTypes.any.id, + choices: CONFIG.DH.GENERAL.targetTypes, + initial: CONFIG.DH.GENERAL.targetTypes.any.id, nullable: true }), amount: new fields.NumberField({ nullable: true, initial: null, integer: true, min: 0 }) @@ -16,11 +16,11 @@ export default class TargetField extends fields.SchemaField { static prepareConfig(config) { if (!this.target?.type) return []; let targets; - if (this.target?.type === CONFIG.DH.ACTIONS.targetTypes.self.id) + if (this.target?.type === CONFIG.DH.GENERAL.targetTypes.self.id) targets = [this.actor.token ?? this.actor.prototypeToken]; else { targets = Array.from(game.user.targets); - if (this.target.type !== CONFIG.DH.ACTIONS.targetTypes.any.id) { + if (this.target.type !== CONFIG.DH.GENERAL.targetTypes.any.id) { targets = targets.filter(t => TargetField.isTargetFriendly.call(this, t)); if (this.target.amount && targets.length > this.target.amount) targets = []; } @@ -43,9 +43,9 @@ export default class TargetField extends fields.SchemaField { : this.actor.prototypeToken.disposition, targetDisposition = target.document.disposition; return ( - (this.target.type === CONFIG.DH.ACTIONS.targetTypes.friendly.id && + (this.target.type === CONFIG.DH.GENERAL.targetTypes.friendly.id && actorDisposition === targetDisposition) || - (this.target.type === CONFIG.DH.ACTIONS.targetTypes.hostile.id && + (this.target.type === CONFIG.DH.GENERAL.targetTypes.hostile.id && actorDisposition + targetDisposition === 0) ); } diff --git a/module/data/settings/Automation.mjs b/module/data/settings/Automation.mjs index 63377b26..d4d6a2a7 100644 --- a/module/data/settings/Automation.mjs +++ b/module/data/settings/Automation.mjs @@ -23,6 +23,12 @@ export default class DhAutomation extends foundry.abstract.DataModel { required: true, initial: true, label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.hordeDamage.label' + }), + effects: new fields.SchemaField({ + rangeDependent: new fields.BooleanField({ + initial: true, + label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.effects.rangeDependent.label' + }) }) }; } diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index ac9ac9e7..120b51fb 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -321,3 +321,18 @@ export const arraysEqual = (a, b) => [...new Set([...a, ...b])].every(v => a.filter(e => e === v).length === b.filter(e => e === v).length); export const setsEqual = (a, b) => a.size === b.size && [...a].every(value => b.has(value)); + +export function compareValues(a, b, compare) { + switch (compare) { + case CONFIG.DH.GENERAL.compareOperator.lessThan.id: + return a < b; + case CONFIG.DH.GENERAL.compareOperator.lessThanEqual.id: + return a <= b; + case CONFIG.DH.GENERAL.compareOperator.equal.id: + return a === b; + case CONFIG.DH.GENERAL.compareOperator.moreThanEqual.id: + return a >= b; + case CONFIG.DH.GENERAL.compareOperator.moreThan.id: + return a > b; + } +} diff --git a/templates/settings/automation-settings.hbs b/templates/settings/automation-settings.hbs index 9ffe5049..a02d6f97 100644 --- a/templates/settings/automation-settings.hbs +++ b/templates/settings/automation-settings.hbs @@ -10,6 +10,7 @@ {{formGroup settingFields.schema.fields.actionPoints value=settingFields._source.actionPoints localize=true}} {{formGroup settingFields.schema.fields.hordeDamage value=settingFields._source.hordeDamage localize=true}} + {{formGroup settingFields.schema.fields.effects.fields.rangeDependent value=settingFields._source.effects.rangeDependent localize=true}}