diff --git a/lang/en.json b/lang/en.json index 24d6168a..86e4e633 100755 --- a/lang/en.json +++ b/lang/en.json @@ -138,7 +138,8 @@ "Config": { "rangeDependence": { "title": "Range Dependence" - } + }, + "stacking": { "title": "Stacking" } }, "RangeDependance": { "hint": "Settings for an optional distance at which this effect should activate", @@ -2864,6 +2865,8 @@ }, "EffectsDisplay": { "removeThing": "[Right Click] Remove {thing}", + "increaseStacks": "[Left Click] Increment Stacks", + "decreaseStacks": "[Right Click] Decrement Stacks", "appliedBy": "Applied By: {by}" }, "ItemBrowser": { diff --git a/module/applications/ui/effectsDisplay.mjs b/module/applications/ui/effectsDisplay.mjs index 3bc5e716..4df42479 100644 --- a/module/applications/ui/effectsDisplay.mjs +++ b/module/applications/ui/effectsDisplay.mjs @@ -52,7 +52,8 @@ export default class DhEffectsDisplay extends HandlebarsApplicationMixin(Applica if (this.element) { this.element.querySelectorAll('.effect-container a').forEach(element => { - element.addEventListener('contextmenu', this.removeEffect.bind(this)); + element.addEventListener('click', this.effectLeftclick.bind(this)); + element.addEventListener('contextmenu', this.effectRightclick.bind(this)); }); } } @@ -87,11 +88,28 @@ export default class DhEffectsDisplay extends HandlebarsApplicationMixin(Applica this.render(); } - async removeEffect(event) { + async effectLeftclick(event) { const element = event.target.closest('.effect-container'); const effects = DhEffectsDisplay.getTokenEffects(); const effect = effects.find(x => x.id === element.dataset.effectId); - await effect.delete(); + + if (!effect.system.stacking?.enabled) return; + + const newValue = Math.min(effect.system.stacking.value + 1, effect.system.stacking.max); + await effect.update({ 'system.stacking.value': newValue }); + this.render(); + } + + async effectRightclick(event) { + const element = event.target.closest('.effect-container'); + const effects = DhEffectsDisplay.getTokenEffects(); + const effect = effects.find(x => x.id === element.dataset.effectId); + if (effect.system.stacking?.enabled && effect.system.stacking.value > 1) { + await effect.update({ 'system.stacking.value': effect.system.stacking.value - 1 }); + } else { + await effect.delete(); + } + this.render(); } diff --git a/module/canvas/placeables/token.mjs b/module/canvas/placeables/token.mjs index b3a19a92..ad60b931 100644 --- a/module/canvas/placeables/token.mjs +++ b/module/canvas/placeables/token.mjs @@ -30,8 +30,8 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token { if (!effect.img) continue; const promise = effect === overlayEffect - ? this._drawOverlay(effect.img, effect.tint) - : this._drawEffect(effect.img, effect.tint); + ? this._drawOverlay(effect.img, effect.tint, effect) + : this._drawEffect(effect.img, effect.tint, effect); promises.push( promise.then(e => { if (e) e.zIndex = i; @@ -45,6 +45,38 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token { this.renderFlags.set({ refreshEffects: true }); } + /**@inheritdoc */ + async _drawEffect(src, tint, effect) { + if (!src) return; + const tex = await loadTexture(src, { fallback: 'icons/svg/hazard.svg' }); + const icon = new PIXI.Sprite(tex); + icon.tint = tint ?? 0xffffff; + + if (effect?.system?.stacking?.enabled) { + const stackOverlay = new PIXI.Text(effect.system.stacking.value, { + fill: '#f3c267', + stroke: '#000000', + fontSize: 96, + strokeThickness: 4 + }); + const nrDigits = Math.floor(effect.system.stacking.value / 10) + 1; + stackOverlay.x = icon.width - 56 * nrDigits; + stackOverlay.y = 4; + stackOverlay.anchor.set(0, 0); + + icon.addChild(stackOverlay); + } + + return this.effects.addChild(icon); + } + + async _drawOverlay(src, tint, effect) { + const icon = await this._drawEffect(src, tint, effect); + if (icon) icon.alpha = 0.8; + this.effects.overlay = icon ?? null; + return icon; + } + /** * Returns the distance from this token to another token object. * This value is corrected to handle alternate token sizes and other grid types diff --git a/module/data/activeEffect/baseEffect.mjs b/module/data/activeEffect/baseEffect.mjs index 98a961d7..3867fa23 100644 --- a/module/data/activeEffect/baseEffect.mjs +++ b/module/data/activeEffect/baseEffect.mjs @@ -65,6 +65,18 @@ export default class BaseEffect extends foundry.data.ActiveEffectTypeDataModel { initial: CONFIG.DH.GENERAL.range.melee.id, label: 'DAGGERHEART.GENERAL.range' }) + }), + stacking: new fields.SchemaField({ + enabled: new fields.BooleanField({ initial: false, label: 'DAGGERHEART.GENERAL.enabled' }), + value: new fields.NumberField({ + initial: 1, + min: 1, + integer: true, + nullable: false, + label: 'DAGGERHEART.GENERAL.value' + }), + max: new fields.NumberField({ label: 'DAGGERHEART.GENERAL.max' }) + // max: new fields.StringField({ required: true, nullable: false }), }) }; } diff --git a/module/documents/activeEffect.mjs b/module/documents/activeEffect.mjs index f8b19a3a..8c7f0668 100644 --- a/module/documents/activeEffect.mjs +++ b/module/documents/activeEffect.mjs @@ -1,5 +1,5 @@ import { itemAbleRollParse } from '../helpers/utils.mjs'; -import { RefreshType, socketEvent } from '../systemRegistration/socket.mjs'; +import { RefreshType } from '../systemRegistration/socket.mjs'; export default class DhActiveEffect extends foundry.documents.ActiveEffect { /* -------------------------------------------- */ @@ -182,10 +182,17 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect { } catch (_) {} } - const evalValue = this.effectSafeEval(itemAbleRollParse(key, parseModel, effect.parent)); + const evalValue = this.effectSafeEval(this.effectRollParse(key, parseModel, effect.parent, effect)); return evalValue ?? key; } + static effectRollParse(value, actor, item, effect) { + const stackingParsedValue = effect.system.stacking?.enabled + ? Roll.replaceFormulaData(value, { stacks: effect.system.stacking.value }) + : value; + return itemAbleRollParse(stackingParsedValue, actor, item); + } + /** * Altered Foundry safeEval to allow non-numeric return * @param {string} expression diff --git a/styles/less/global/elements.less b/styles/less/global/elements.less index 98c05348..6a229d58 100755 --- a/styles/less/global/elements.less +++ b/styles/less/global/elements.less @@ -460,6 +460,10 @@ &.even { grid-template-columns: 1fr 1fr; } + + &.full-width { + width: 100%; + } } line-div { diff --git a/styles/less/ui/effects-display/sheet.less b/styles/less/ui/effects-display/sheet.less index 1331b094..3b040725 100644 --- a/styles/less/ui/effects-display/sheet.less +++ b/styles/less/ui/effects-display/sheet.less @@ -35,6 +35,19 @@ color: @golden; filter: drop-shadow(0 0 3px black); } + + .stacking-value { + position: absolute; + top: 4px; + right: 4px; + font-size: 16px; + font-weight: bold; + color: @golden; + background: black; + padding: 1px; + border-radius: 6px; + border: 1px solid @golden; + } } } } diff --git a/styles/less/ux/tooltip/bordered-tooltip.less b/styles/less/ux/tooltip/bordered-tooltip.less index b3a5ed29..abec93b7 100644 --- a/styles/less/ux/tooltip/bordered-tooltip.less +++ b/styles/less/ux/tooltip/bordered-tooltip.less @@ -6,6 +6,7 @@ .daggerheart.dh-style.tooltip { display: flex; flex-direction: column; + align-items: start; text-align: start; width: 100%; gap: 5px; @@ -13,6 +14,7 @@ border-radius: 3px; .tooltip-header { + width: 100%; display: flex; flex-direction: column; align-items: center; @@ -35,12 +37,48 @@ } } - .close-hint { - border-radius: 3px; - padding: 3px; - background: @rustic-brown-80; - color: @golden; - font-size: 12px; + .effect-stacks-outer-container { + display: flex; + flex-direction: column; + gap: 4px; + width: 100%; + + .effect-stacks-title { + font-size: var(--font-size-20); + font-weight: bold; + text-align: center; + } + + .effect-stacks-container { + display: flex; + justify-content: space-between; + + .effect-stacks-inner-container { + display: flex; + flex-direction: column; + gap: 2px; + + .effect-stack-title { + font-weight: bold; + } + } + } + } + + .close-hints { + margin-top: 0.5rem; + display: flex; + flex-direction: column; + gap: 4px; + + .close-hint { + border-radius: 3px; + padding: 3px; + background: @rustic-brown-80; + color: @golden; + font-size: 12px; + margin: 0; + } } .duration-container { diff --git a/templates/sheets/activeEffect/settings.hbs b/templates/sheets/activeEffect/settings.hbs index 9443edfb..67d7b5f2 100644 --- a/templates/sheets/activeEffect/settings.hbs +++ b/templates/sheets/activeEffect/settings.hbs @@ -1,4 +1,15 @@
+
+ {{localize "DAGGERHEART.ACTIVEEFFECT.Config.stacking.title"}} + + {{formGroup systemFields.stacking.fields.enabled value=source.system.stacking.enabled localize=true }} + +
+ {{formGroup systemFields.stacking.fields.value value=source.system.stacking.value localize=true }} + {{formGroup systemFields.stacking.fields.max value=source.system.stacking.max localize=true }} +
+
+
{{localize "DAGGERHEART.ACTIVEEFFECT.Config.rangeDependence.title"}} diff --git a/templates/ui/effects-display.hbs b/templates/ui/effects-display.hbs index 95c6023c..13daa4de 100644 --- a/templates/ui/effects-display.hbs +++ b/templates/ui/effects-display.hbs @@ -8,6 +8,9 @@ + {{#if effect.system.stacking.enabled}} + {{effect.system.stacking.value}} + {{/if}} {{#if effect.condition}}{{/if}} {{/each}} diff --git a/templates/ui/tooltip/effect-display.hbs b/templates/ui/tooltip/effect-display.hbs index d37b5147..52419193 100644 --- a/templates/ui/tooltip/effect-display.hbs +++ b/templates/ui/tooltip/effect-display.hbs @@ -17,7 +17,6 @@ {{/if}} {{#if effect.system.duration.type}} -
{{localize "EFFECT.DURATION.Label"}}: @@ -25,10 +24,39 @@
{{/if}} + + {{#if effect.system.stacking.enabled}} +
+
{{localize "Stacks"}}
+
+
+ {{localize "Current"}} + {{effect.system.stacking.value}} +
+ {{#if effect.system.stacking.max}} +
+ {{localize "Max"}} + {{effect.system.stacking.max}} +
+ {{/if}} +
+
+ {{/if}} {{#unless effect.isLockedCondition}} -

- {{localize "DAGGERHEART.UI.EffectsDisplay.removeThing" thing=(localize "DAGGERHEART.GENERAL.Effect.single")}} -

+
+ {{#if effect.system.stacking.enabled}} +

+ {{localize "DAGGERHEART.UI.EffectsDisplay.increaseStacks"}} +

+

+ {{localize "DAGGERHEART.UI.EffectsDisplay.decreaseStacks"}} +

+ {{else}} +

+ {{localize "DAGGERHEART.UI.EffectsDisplay.removeThing" thing=(localize "DAGGERHEART.GENERAL.Effect.single")}} +

+ {{/if}} +
{{/unless}} \ No newline at end of file