mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-03-07 22:46:12 +01:00
Merge 0f334232d8 into 876e496d24
This commit is contained in:
commit
4176b1ca99
17 changed files with 319 additions and 228 deletions
|
|
@ -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,31 @@ 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 incrementedValue = effect.system.stacking.value + 1;
|
||||
const newValue = effect.system.stacking.max
|
||||
? Math.min(incrementedValue, effect.system.stacking.max)
|
||||
: incrementedValue;
|
||||
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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,97 +1,4 @@
|
|||
/**
|
||||
* @typedef ContextMenuEntry
|
||||
* @property {string} name The context menu label. Can be localized.
|
||||
* @property {string} [icon] A string containing an HTML icon element for the menu item.
|
||||
* @property {string} [classes] Additional CSS classes to apply to this menu item.
|
||||
* @property {string} [group] An identifier for a group this entry belongs to.
|
||||
* @property {ContextMenuJQueryCallback} callback The function to call when the menu item is clicked.
|
||||
* @property {ContextMenuCondition|boolean} [condition] A function to call or boolean value to determine if this entry
|
||||
* appears in the menu.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @callback ContextMenuCondition
|
||||
* @param {jQuery|HTMLElement} html The element of the context menu entry.
|
||||
* @returns {boolean} Whether the entry should be rendered in the context menu.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @callback ContextMenuCallback
|
||||
* @param {HTMLElement} target The element that the context menu has been triggered for.
|
||||
* @returns {unknown}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @callback ContextMenuJQueryCallback
|
||||
* @param {HTMLElement|jQuery} target The element that the context menu has been triggered for. Will
|
||||
* either be a jQuery object or an HTMLElement instance, depending
|
||||
* on how the ContextMenu was configured.
|
||||
* @returns {unknown}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef ContextMenuOptions
|
||||
* @property {string} [eventName="contextmenu"] Optionally override the triggering event which can spawn the menu. If
|
||||
* the menu is using fixed positioning, this event must be a MouseEvent.
|
||||
* @property {ContextMenuCallback} [onOpen] A function to call when the context menu is opened.
|
||||
* @property {ContextMenuCallback} [onClose] A function to call when the context menu is closed.
|
||||
* @property {boolean} [fixed=false] If true, the context menu is given a fixed position rather than being
|
||||
* injected into the target.
|
||||
* @property {boolean} [jQuery=true] If true, callbacks will be passed jQuery objects instead of HTMLElement
|
||||
* instances.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef ContextMenuRenderOptions
|
||||
* @property {Event} [event] The event that triggered the context menu opening.
|
||||
* @property {boolean} [animate=true] Animate the context menu opening.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A subclass of ContextMenu.
|
||||
* @extends {foundry.applications.ux.ContextMenu}
|
||||
*/
|
||||
export default class DHContextMenu extends foundry.applications.ux.ContextMenu {
|
||||
/**
|
||||
* @param {HTMLElement|jQuery} container - The HTML element that contains the context menu targets.
|
||||
* @param {string} selector - A CSS selector which activates the context menu.
|
||||
* @param {ContextMenuEntry[]} menuItems - An Array of entries to display in the menu
|
||||
* @param {ContextMenuOptions} [options] - Additional options to configure the context menu.
|
||||
*/
|
||||
constructor(container, selector, menuItems, options) {
|
||||
super(container, selector, menuItems, options);
|
||||
|
||||
/** @deprecated since v13 until v15 */
|
||||
this.#jQuery = options.jQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to pass jQuery objects or HTMLElement instances to callback.
|
||||
* @type {boolean}
|
||||
*/
|
||||
#jQuery;
|
||||
|
||||
/**@inheritdoc */
|
||||
activateListeners(menu) {
|
||||
menu.addEventListener('click', this.#onClickItem.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle click events on context menu items.
|
||||
* @param {PointerEvent} event The click event
|
||||
*/
|
||||
#onClickItem(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const element = event.target.closest('.context-item');
|
||||
if (!element) return;
|
||||
const item = this.menuItems.find(i => i.element === element);
|
||||
item?.callback(this.#jQuery ? $(this.target) : this.target, event);
|
||||
this.close();
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Trigger a context menu event in response to a normal click on a additional options button.
|
||||
* @param {PointerEvent} event
|
||||
|
|
|
|||
|
|
@ -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,39 @@ 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 foundry.canvas.loadTexture(src, { fallback: 'icons/svg/hazard.svg' });
|
||||
const icon = new PIXI.Sprite(tex);
|
||||
icon.tint = tint ?? 0xffffff;
|
||||
|
||||
if (effect?.system?.stacking?.enabled && effect.system.stacking.value > 1) {
|
||||
const stackOverlay = new PIXI.Text(effect.system.stacking.value, {
|
||||
fill: '#f3c267',
|
||||
stroke: '#000000',
|
||||
fontSize: 96,
|
||||
strokeThickness: 4
|
||||
});
|
||||
const nrDigits = Math.floor(Math.log10(effect.system.stacking.value)) + 1;
|
||||
stackOverlay.y = -8;
|
||||
/* This does not account for 1:s being much less wide than other digits. I don't think it's desired however as it makes it look jumpy */
|
||||
stackOverlay.x = icon.width - 8 - nrDigits * 56;
|
||||
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
|
||||
|
|
|
|||
|
|
@ -65,6 +65,17 @@ 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({ integer: true, label: 'DAGGERHEART.GENERAL.max' })
|
||||
})
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -106,22 +106,11 @@ export default class EffectsField extends fields.ArrayField {
|
|||
}
|
||||
|
||||
/**
|
||||
* Apply an Effect to a target or enable it if already on it
|
||||
* Apply an Effect to a target
|
||||
* @param {object} effect Effect object containing ActiveEffect UUID
|
||||
* @param {object} actor Actor Document
|
||||
*/
|
||||
static async applyEffect(effect, actor) {
|
||||
const existingEffect = actor.effects.find(e => e.origin === effect.uuid);
|
||||
if (existingEffect) {
|
||||
return effect.update(
|
||||
foundry.utils.mergeObject({
|
||||
...effect.constructor.getInitialDuration(),
|
||||
disabled: false
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Otherwise, create a new effect on the target
|
||||
const effectData = foundry.utils.mergeObject({
|
||||
...(effect.toObject?.() ?? effect),
|
||||
disabled: false,
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
/* -------------------------------------------- */
|
||||
|
|
@ -106,6 +106,20 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
|
|||
update.img = 'icons/magic/life/heart-cross-blue.webp';
|
||||
}
|
||||
|
||||
const existingEffect = this.actor.effects.find(x => x.origin === data.origin);
|
||||
const stacks = data.system?.stacking?.enabled;
|
||||
if (existingEffect && !stacks) return false;
|
||||
|
||||
if (existingEffect && stacks) {
|
||||
const incrementedValue = existingEffect.system.stacking.value + 1;
|
||||
await existingEffect.update({
|
||||
'system.stacking.value': existingEffect.system.stacking.max
|
||||
? Math.min(incrementedValue, existingEffect.system.stacking.max)
|
||||
: incrementedValue
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
const statuses = Object.keys(data.statuses ?? {});
|
||||
const immuneStatuses =
|
||||
statuses.filter(
|
||||
|
|
@ -182,10 +196,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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue