diff --git a/lang/en.json b/lang/en.json index 1aa43b5b..9553ab9f 100755 --- a/lang/en.json +++ b/lang/en.json @@ -138,6 +138,9 @@ "Config": { "rangeDependence": { "title": "Range Dependence" + }, + "conditional": { + "title": "Conditional Application" } }, "RangeDependance": { @@ -684,6 +687,9 @@ } }, "CONFIG": { + "ActiveEffectConditionalTarget": { + "self": "Self" + }, "ActiveEffectDuration": { "temporary": "Temporary", "act": "Next Spotlight", @@ -1015,6 +1021,13 @@ "withinRange": "Within Range", "outsideRange": "Outside Range" }, + "Comparator": { + "eq": "Equals", + "gt": "Greater Than", + "gte": "Greater Or Equals", + "lt": "Lesser Than", + "lte": "Lesser Or Equals" + }, "Condition": { "deathMove": { "name": "Death Move", @@ -2198,7 +2211,8 @@ "triggers": "Triggers", "deathMoves": "Deathmoves", "sources": "Sources", - "packs": "Packs" + "packs": "Packs", + "conditionals": "Conditionals" }, "Tiers": { "singular": "Tier", @@ -2226,6 +2240,7 @@ "basics": "Basics", "bonus": "Bonus", "burden": "Burden", + "comparator": "Comparator", "condition": "Condition", "continue": "Continue", "criticalSuccess": "Critical Success", diff --git a/module/applications/sheets-configs/activeEffectConfig.mjs b/module/applications/sheets-configs/activeEffectConfig.mjs index 2bd7d5b9..062883b6 100644 --- a/module/applications/sheets-configs/activeEffectConfig.mjs +++ b/module/applications/sheets-configs/activeEffectConfig.mjs @@ -8,7 +8,11 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac } static DEFAULT_OPTIONS = { - classes: ['daggerheart', 'sheet', 'dh-style'] + classes: ['daggerheart', 'sheet', 'dh-style'], + actions: { + addConditional: DhActiveEffectConfig.#addConditional, + removeConditional: DhActiveEffectConfig.#removeConditional + } }; static PARTS = { @@ -16,6 +20,7 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac tabs: { template: 'templates/generic/tab-navigation.hbs' }, details: { template: 'systems/daggerheart/templates/sheets/activeEffect/details.hbs', scrollable: [''] }, settings: { template: 'systems/daggerheart/templates/sheets/activeEffect/settings.hbs' }, + conditionals: { template: 'systems/daggerheart/templates/sheets/activeEffect/conditionals.hbs' }, changes: { template: 'systems/daggerheart/templates/sheets/activeEffect/changes.hbs', templates: ['systems/daggerheart/templates/sheets/activeEffect/change.hbs'], @@ -29,6 +34,11 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac tabs: [ { id: 'details', icon: 'fa-solid fa-book' }, { id: 'settings', icon: 'fa-solid fa-bars', label: 'DAGGERHEART.GENERAL.Tabs.settings' }, + { + id: 'conditionals', + icon: 'fa-solid fa-person-circle-question', + label: 'DAGGERHEART.GENERAL.Tabs.conditionals' + }, { id: 'changes', icon: 'fa-solid fa-gears' } ], initial: 'details', @@ -185,6 +195,16 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac group: CONST.ACTIVE_EFFECT_TIME_DURATION_UNITS.includes(value) ? groups.time : groups.combat })); break; + case 'conditionals': + partContext.conditionals = this.document.system.conditionals.map(conditional => ({ + ...conditional, + ...game.system.api.data.activeEffects.EffectConditional.getConditionalFieldUseage(conditional.type) + })); + partContext.statusChoices = Object.values(CONFIG.statusEffects).map(x => ({ + id: x.id, + label: x.name + })); + break; case 'changes': const fields = this.document.system.schema.fields.changes.element.fields; partContext.changes = await Promise.all( @@ -226,6 +246,21 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac ); } + static #addConditional() { + const submitData = this._processFormData(null, this.form, new FormDataExtended(this.form)); + const conditionals = Object.values(submitData.system?.conditionals ?? {}); + conditionals.push(this.document.system.schema.fields.conditionals.element.getInitialValue()); + return this.submit({ updateData: { system: { conditionals } } }); + } + + static async #removeConditional(_event, button) { + const submitData = this._processFormData(null, this.form, new FormDataExtended(this.form)); + const conditionals = Object.values(submitData.system.conditionals); + const index = Number(button.dataset.index) || 0; + conditionals.splice(index, 1); + return this.submit({ updateData: { system: { conditionals: conditionals } } }); + } + /** @inheritDoc */ _onChangeForm(_formConfig, event) { if (foundry.utils.isElementInstanceOf(event.target, 'select') && event.target.name === 'system.duration.type') { @@ -237,6 +272,35 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac if (event.target.value === 'temporary') durationDescription.classList.add('visible'); else durationDescription.classList.remove('visible'); } + + if ( + foundry.utils.isElementInstanceOf(event.target, 'select') && + event.target.name.startsWith('system.conditionals') && + event.target.name.endsWith('type') + ) { + const container = event.target.closest('.conditional-container'); + + const statusSelect = container.querySelector('.form-group.status-select'); + const attributeAuto = container.querySelector('.form-group.attribute-auto'); + if (event.target.value === CONFIG.DH.GENERAL.activeEffectConditionalType.status.id) { + statusSelect.classList.remove('not-visible'); + attributeAuto.classList.add('not-visible'); + } else { + statusSelect.classList.add('not-visible'); + attributeAuto.classList.remove('not-visible'); + } + statusSelect.querySelector('select').selectedIndex = '-1'; + attributeAuto.querySelector('input').value = ''; + + const { usesValue, usesComparator } = + game.system.api.data.activeEffects.EffectConditional.getConditionalFieldUseage(event.target.value); + + if (usesValue) container.querySelector('.form-group.value').classList.remove('not-visible'); + else container.querySelector('.form-group.value').classList.add('not-visible'); + + if (usesComparator) container.querySelector('.form-group.comparator').classList.remove('not-visible'); + else container.querySelector('.form-group.comparator').classList.add('not-visible'); + } } /** @inheritDoc */ diff --git a/module/config/generalConfig.mjs b/module/config/generalConfig.mjs index 8ec7034a..da7b96ac 100644 --- a/module/config/generalConfig.mjs +++ b/module/config/generalConfig.mjs @@ -917,3 +917,48 @@ export const activeEffectDurations = { label: 'DAGGERHEART.CONFIG.ActiveEffectDuration.custom' } }; + +export const activeEffectConditionalTarget = { + self: { + id: 'self', + label: 'DAGGERHEART.CONFIG.ActiveEffectConditionalTarget.self' + } + // target: { + // id: 'target', + // label: 'DAGGERHEART.CONFIG.ActiveEffectConditionalTarget.target' + // } +}; + +export const activeEffectConditionalType = { + attribute: { + id: 'attribute', + label: 'Attribute' + }, + status: { + id: 'status', + label: 'Status' + } +}; + +export const comparator = { + eq: { + id: 'eq', + label: 'DAGGERHEART.CONFIG.Comparator.eq' + }, + gt: { + id: 'gt', + label: 'DAGGERHEART.CONFIG.Comparator.gt' + }, + gte: { + id: 'gte', + label: 'DAGGERHEART.CONFIG.Comparator.gte' + }, + lt: { + id: 'lt', + label: 'DAGGERHEART.CONFIG.Comparator.lt' + }, + lte: { + id: 'lte', + label: 'DAGGERHEART.CONFIG.Comparator.lte' + } +}; diff --git a/module/data/activeEffect/_module.mjs b/module/data/activeEffect/_module.mjs index 1a50088a..9b4e3263 100644 --- a/module/data/activeEffect/_module.mjs +++ b/module/data/activeEffect/_module.mjs @@ -1,8 +1,9 @@ import BaseEffect from './baseEffect.mjs'; import BeastformEffect from './beastformEffect.mjs'; import HordeEffect from './hordeEffect.mjs'; +import { EffectConditionals, EffectConditional } from './effectConditional.mjs'; -export { BaseEffect, BeastformEffect, HordeEffect }; +export { BaseEffect, BeastformEffect, HordeEffect, EffectConditionals, EffectConditional }; export const config = { base: BaseEffect, diff --git a/module/data/activeEffect/baseEffect.mjs b/module/data/activeEffect/baseEffect.mjs index 98a961d7..d45ba15a 100644 --- a/module/data/activeEffect/baseEffect.mjs +++ b/module/data/activeEffect/baseEffect.mjs @@ -1,3 +1,5 @@ +import { EffectConditionals } from './effectConditional.mjs'; + /** -- Changes Type Priorities -- * - Base Number - * Custom: 0 @@ -33,6 +35,7 @@ export default class BaseEffect extends foundry.data.ActiveEffectTypeDataModel { priority: new fields.NumberField() }) ), + conditionals: new EffectConditionals(), duration: new fields.SchemaField({ type: new fields.StringField({ choices: CONFIG.DH.GENERAL.activeEffectDurations, diff --git a/module/data/activeEffect/effectConditional.mjs b/module/data/activeEffect/effectConditional.mjs new file mode 100644 index 00000000..7ed91e33 --- /dev/null +++ b/module/data/activeEffect/effectConditional.mjs @@ -0,0 +1,82 @@ +import { compareValues, itemAbleRollParse } from '../../helpers/utils.mjs'; + +export class EffectConditionals extends foundry.data.fields.ArrayField { + constructor(context) { + super(new EffectConditional(), context); + } + + static isConditionalSuspended(effect) { + const actor = + effect.parent.type === 'character' + ? effect.parent + : effect.parent.parent?.type === 'character' + ? effect.parent.parent + : null; + if (!actor) return false; + + for (const conditional of effect.system.conditionals) { + switch (conditional.type) { + case CONFIG.DH.GENERAL.activeEffectConditionalType.status.id: + const hasStatus = Array.from(actor.allApplicableEffects()).some( + x => !x.disabled && x.statuses.has(conditional.key) + ); + if (!hasStatus) return true; + break; + case CONFIG.DH.GENERAL.activeEffectConditionalType.attribute.id: + const actorValue = foundry.utils.getProperty(actor, conditional.key); + const conditionalValue = game.system.api.documents.DhActiveEffect.effectSafeEval( + itemAbleRollParse(conditional.value, actor) + ); + if (!compareValues(actorValue, conditionalValue, conditional.comparator)) return true; + break; + } + } + + return false; + } +} + +export class EffectConditional extends foundry.data.fields.SchemaField { + constructor(context) { + const fields = foundry.data.fields; + + const schema = { + target: new fields.StringField({ + required: true, + choices: CONFIG.DH.GENERAL.activeEffectConditionalTarget, + initial: CONFIG.DH.GENERAL.activeEffectConditionalTarget.self.id, + label: 'DAGGERHEART.GENERAL.Target.single' + }), + type: new fields.StringField({ + required: true, + choices: CONFIG.DH.GENERAL.activeEffectConditionalType, + initial: CONFIG.DH.GENERAL.activeEffectConditionalType.status.id, + label: 'DAGGERHEART.GENERAL.type' + }), + key: new fields.StringField({ required: true, label: 'EFFECT.FIELDS.changes.element.key.label' }), + value: new fields.StringField({ nullable: true, label: 'EFFECT.FIELDS.changes.element.value.label' }), + comparator: new fields.StringField({ + choices: CONFIG.DH.GENERAL.comparator, + initial: CONFIG.DH.GENERAL.comparator.eq.id, + label: 'DAGGERHEART.GENERAL.comparator' + }) + }; + + super(schema, context); + } + + static getConditionalFieldUseage(conditionalType) { + let usesValue = false; + let usesComparator = false; + + if ([CONFIG.DH.GENERAL.activeEffectConditionalType.attribute.id].includes(conditionalType)) { + usesValue = true; + usesComparator = true; + } + + return { + usesValue, + usesComparator + }; + } +} diff --git a/module/documents/activeEffect.mjs b/module/documents/activeEffect.mjs index f8b19a3a..ad664f15 100644 --- a/module/documents/activeEffect.mjs +++ b/module/documents/activeEffect.mjs @@ -8,6 +8,9 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect { /**@override */ get isSuppressed() { + const conditionalSuspended = game.system.api.data.activeEffects.EffectConditionals.isConditionalSuspended(this); + if (conditionalSuspended) return true; + // If this is a copied effect from an attachment, never suppress it // (These effects have attachmentSource metadata) if (this.flags?.daggerheart?.attachmentSource) { diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index 7e4d794b..0043f183 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -538,7 +538,7 @@ export function getIconVisibleActiveEffects(effects) { const alwaysShown = effect.showIcon === CONST.ACTIVE_EFFECT_SHOW_ICON.ALWAYS; const conditionalShown = effect.showIcon === CONST.ACTIVE_EFFECT_SHOW_ICON.CONDITIONAL && !effect.transfer; // TODO: system specific logic - return !effect.disabled && (alwaysShown || conditionalShown); + return !effect.active && (alwaysShown || conditionalShown); }); } export async function getFeaturesHTMLData(features) { @@ -603,6 +603,28 @@ export function calculateExpectedValue(formulaOrTerms) { return terms.reduce((r, t) => r + (t.bonus ?? 0) + (t.diceQuantity ? (t.diceQuantity * (t.faces + 1)) / 2 : 0), 0); } +/** + * + * @param {Number} valA The number being compared to a second one + * @param {Number} valB The number the first is being compared to + * @param {Comparator} comparator The type of comparison + * @returns { Boolean } Whether valA passes the comparison + */ +export function compareValues(valA, valB, comparator) { + switch (comparator) { + case CONFIG.DH.GENERAL.comparator.eq.id: + return valA === valB; + case CONFIG.DH.GENERAL.comparator.gt.id: + return valA > valB; + case CONFIG.DH.GENERAL.comparator.gte.id: + return valA >= valB; + case CONFIG.DH.GENERAL.comparator.lt.id: + return valA < valB; + case CONFIG.DH.GENERAL.comparator.lte.id: + return valA <= valB; + } +} + export function parseRallyDice(value, effect) { const legacyStartsWithPrefix = value.toLowerCase().startsWith('d'); const workingValue = legacyStartsWithPrefix ? value.slice(1) : value; diff --git a/src/packs/domains/domainCard_On_the_Brink_zbxPl81kbWEegKQN.json b/src/packs/domains/domainCard_On_the_Brink_zbxPl81kbWEegKQN.json index e5f90fe4..bdfda213 100644 --- a/src/packs/domains/domainCard_On_the_Brink_zbxPl81kbWEegKQN.json +++ b/src/packs/domains/domainCard_On_the_Brink_zbxPl81kbWEegKQN.json @@ -28,27 +28,36 @@ "type": "withinRange", "target": "hostile", "range": "melee" - } + }, + "changes": [ + { + "key": "system.rules.damageReduction.thresholdImmunities.minor", + "value": 1, + "priority": null, + "type": "override" + } + ], + "duration": { + "type": "" + }, + "conditionals": [ + { + "type": "attribute", + "key": "system.resources.hitPoints.value", + "value": "@system.resources.hitPoints.max - 2", + "comparator": "gte", + "target": "self" + } + ] }, "_id": "UJTsJlnhi5Zi0XQ2", "img": "systems/daggerheart/assets/icons/domains/domain-card/bone.png", - "changes": [ - { - "key": "system.rules.damageReduction.thresholdImmunities.minor", - "mode": 5, - "value": "1", - "priority": null - } - ], - "disabled": true, + "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

When you have 2 or fewer Hit Points unmarked, you don’t take Minor damage.

", "origin": null, @@ -60,6 +69,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!zbxPl81kbWEegKQN.UJTsJlnhi5Zi0XQ2" } ], diff --git a/src/packs/subclasses/feature_Adrenaline_uByM34yQlw38yf1V.json b/src/packs/subclasses/feature_Adrenaline_uByM34yQlw38yf1V.json index dc25498b..8b9524f5 100644 --- a/src/packs/subclasses/feature_Adrenaline_uByM34yQlw38yf1V.json +++ b/src/packs/subclasses/feature_Adrenaline_uByM34yQlw38yf1V.json @@ -30,31 +30,40 @@ "type": "withinRange", "target": "hostile", "range": "melee" - } - }, - "changes": [ - { - "key": "system.bonuses.damage.physical.bonus", - "mode": 2, - "value": "@system.levelData.level.current", - "priority": null }, - { - "key": "system.bonuses.damage.magical.bonus", - "mode": 2, - "value": "@system.levelData.level.current", - "priority": null - } - ], - "disabled": true, + "changes": [ + { + "key": "system.bonuses.damage.physical.bonus", + "value": "@system.levelData.level.current", + "priority": null, + "type": "add" + }, + { + "key": "system.bonuses.damage.magical.bonus", + "value": "@system.levelData.level.current", + "priority": null, + "type": "add" + } + ], + "duration": { + "type": "" + }, + "conditionals": [ + { + "type": "status", + "key": "vulnerable", + "value": "", + "comparator": "eq", + "target": "self" + } + ] + }, + "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

While you're Vulnerable, add your level to your damage rolls.

", "tint": "#ffffff", @@ -64,6 +73,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!uByM34yQlw38yf1V.HMx9uZ54mvMiH95x" } ], diff --git a/src/packs/subclasses/feature_Conjure_Shield_oirsCnN66GOlK3Fa.json b/src/packs/subclasses/feature_Conjure_Shield_oirsCnN66GOlK3Fa.json index 99b04487..27a4f0d0 100644 --- a/src/packs/subclasses/feature_Conjure_Shield_oirsCnN66GOlK3Fa.json +++ b/src/packs/subclasses/feature_Conjure_Shield_oirsCnN66GOlK3Fa.json @@ -26,27 +26,36 @@ "type": "withinRange", "target": "hostile", "range": "melee" - } + }, + "changes": [ + { + "key": "system.evasion", + "value": "@system.proficiency", + "priority": 21, + "type": "add" + } + ], + "duration": { + "type": "" + }, + "conditionals": [ + { + "type": "attribute", + "key": "system.resources.hope.value", + "value": "2", + "comparator": "gte", + "target": "self" + } + ] }, "_id": "0i7GVOvjH6bK5AUM", "img": "icons/magic/defensive/barrier-shield-dome-blue-purple.webp", - "changes": [ - { - "key": "system.evasion", - "mode": 2, - "value": "@system.proficiency", - "priority": 21 - } - ], "disabled": false, "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null + "value": null, + "units": "seconds", + "expiry": null, + "expired": false }, "description": "

While you have at least 2 Hope, you add your Proficiency to your Evasion.

", "origin": null, @@ -58,6 +67,16 @@ "_stats": { "compendiumSource": null }, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "showIcon": 1, + "folder": null, "_key": "!items.effects!oirsCnN66GOlK3Fa.0i7GVOvjH6bK5AUM" } ], diff --git a/src/packs/subclasses/feature_Rise_to_the_Challenge_dcutk8RVOJ2sEkO1.json b/src/packs/subclasses/feature_Rise_to_the_Challenge_dcutk8RVOJ2sEkO1.json index 5421f1fd..dfd3223c 100644 --- a/src/packs/subclasses/feature_Rise_to_the_Challenge_dcutk8RVOJ2sEkO1.json +++ b/src/packs/subclasses/feature_Rise_to_the_Challenge_dcutk8RVOJ2sEkO1.json @@ -16,7 +16,72 @@ "artist": "" } }, - "effects": [], + "effects": [ + { + "name": "Rise To The Challenge", + "type": "base", + "system": { + "changes": [ + { + "key": "system.rules.dualityRoll.defaultHopeDice", + "type": "upgrade", + "value": 20, + "priority": null, + "phase": "initial" + } + ], + "conditionals": [ + { + "type": "attribute", + "key": "system.resources.hitPoints.value", + "value": "@system.resources.hitPoints.max - 2", + "comparator": "gte", + "target": "self" + } + ], + "duration": { + "description": "", + "type": "" + }, + "rangeDependence": { + "enabled": false, + "type": "withinRange", + "target": "hostile", + "range": "melee" + } + }, + "_id": "MA744uJrGMQCDywg", + "img": "icons/magic/control/debuff-energy-hold-levitate-yellow.webp", + "disabled": false, + "start": { + "time": 0, + "combat": null, + "combatant": null, + "initiative": null, + "round": null, + "turn": null + }, + "duration": { + "value": null, + "units": "seconds", + "expiry": null, + "expired": false + }, + "description": "

While you have 2 or fewer Hit Points unmarked, you can roll a d20 as your Hope Die.

", + "origin": null, + "tint": "#ffffff", + "transfer": true, + "statuses": [], + "showIcon": 1, + "folder": null, + "sort": 0, + "flags": {}, + "_stats": { + "compendiumSource": null + }, + "_key": "!items.effects!dcutk8RVOJ2sEkO1.MA744uJrGMQCDywg" + } + ], "sort": 0, "ownership": { "default": 0, diff --git a/styles/less/sheets/activeEffects/activeEffects.less b/styles/less/sheets/activeEffects/activeEffects.less index ba3ff43f..42f79456 100644 --- a/styles/less/sheets/activeEffects/activeEffects.less +++ b/styles/less/sheets/activeEffects/activeEffects.less @@ -23,6 +23,18 @@ } } + .conditionals-container { + display: flex; + flex-direction: column; + gap: 8px; + + .conditional-container { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 4px; + } + } + .tab.changes { gap: 0; @@ -32,4 +44,8 @@ } } } + + .form-group.not-visible { + display: none; + } } diff --git a/templates/sheets/activeEffect/conditionals.hbs b/templates/sheets/activeEffect/conditionals.hbs new file mode 100644 index 00000000..0265eaae --- /dev/null +++ b/templates/sheets/activeEffect/conditionals.hbs @@ -0,0 +1,50 @@ +
+ + + {{#each conditionals as |conditional index|}} +
+ + +
+
+ {{!-- {{formGroup ../systemFields.conditionals.element.fields.target value=conditional.target name=(concat "system.conditionals." index ".target") localize=true }} --}} + {{formGroup ../systemFields.conditionals.element.fields.type value=conditional.type name=(concat "system.conditionals." index ".type") localize=true }} + +
+ + +
+ +
+
+ +
+ + +
+ +
+
+ +
+ + +
+ {{formInput ../systemFields.conditionals.element.fields.value value=conditional.value name=(concat "system.conditionals." index ".value") localize=true }} +
+
+ +
+ + +
+ {{formInput ../systemFields.conditionals.element.fields.comparator value=conditional.comparator name=(concat "system.conditionals." index ".comparator") localize=true }} +
+
+
+
+
+ {{/each}} +
\ No newline at end of file diff --git a/templates/sheets/activeEffect/settings.hbs b/templates/sheets/activeEffect/settings.hbs index 9443edfb..0bf55a00 100644 --- a/templates/sheets/activeEffect/settings.hbs +++ b/templates/sheets/activeEffect/settings.hbs @@ -7,6 +7,7 @@ {{formGroup systemFields.rangeDependence.fields.target value=source.system.rangeDependence.target localize=true }} {{formGroup systemFields.rangeDependence.fields.range value=source.system.rangeDependence.range localize=true }} +
{{localize "EFFECT.DURATION.Label"}}