daggerheart/module/documents/activeEffect.mjs

192 lines
6.2 KiB
JavaScript

import { itemAbleRollParse } from '../helpers/utils.mjs';
export default class DhActiveEffect extends ActiveEffect {
get isSuppressed() {
// If this is a copied effect from an attachment, never suppress it
// (These effects have attachmentSource metadata)
if (this.flags?.daggerheart?.attachmentSource) {
return false;
}
// Then apply the standard suppression rules
if (['weapon', 'armor'].includes(this.parent?.type)) {
return !this.parent.system.equipped;
}
if (this.parent?.type === 'domainCard') {
return this.parent.system.inVault;
}
return super.isSuppressed;
}
/**
* Check if the parent item is currently attached to another item
* @returns {boolean}
*/
get isAttached() {
if (!this.parent || !this.parent.parent) return false;
// Check if this item's UUID is in any actor's armor or weapon attachment lists
const actor = this.parent.parent;
if (!actor || !actor.items) return false;
return actor.items.some(item => {
return (
(item.type === 'armor' || item.type === 'weapon') &&
item.system?.attached &&
Array.isArray(item.system.attached) &&
item.system.attached.includes(this.parent.uuid)
);
});
}
get localizedStatuses() {
const { statusMap, isStatusActiveEffect } = this.isStatusActiveEffect;
if (!isStatusActiveEffect) return [];
return this.statuses.map(x => ({
key: x,
name: game.i18n.localize(statusMap.get(x).name)
}));
}
get isStatusActiveEffect() {
const statusMap = new Map(foundry.CONFIG.statusEffects.map(status => [status.id, status]));
const isStatusActiveEffect =
this.statuses.size === 1 && this.name === game.i18n.localize(statusMap.get(this.statuses.first()).name);
return { statusMap, isStatusActiveEffect };
}
async _preCreate(data, options, user) {
const update = {};
if (!data.img) {
update.img = 'icons/magic/life/heart-cross-blue.webp';
}
if (Object.keys(update).length > 0) {
await this.updateSource(update);
}
await super._preCreate(data, options, user);
}
_onCreate(data, options, userId) {
super._onCreate(data, options, userId);
if (game.user.id === userId) {
this.addStatusActiveEffects(data.statuses);
}
}
_onUpdate(changed, options, userId) {
super._onUpdate(changed, options, userId);
if ('disabled' in changed) {
if (changed.disabled) {
this.removeStatusActiveEffects(this.statuses);
} else {
this.addStatusActiveEffects(this.statuses);
}
}
}
_onDelete(data, userId) {
super._onDelete(data, userId);
if (game.user.id === userId) {
this.removeStatusActiveEffects(this.statuses);
}
}
static applyField(model, change, field) {
const evalValue = this.effectSafeEval(itemAbleRollParse(change.value, model, change.effect.parent));
change.value = evalValue ?? change.value;
super.applyField(model, change, field);
}
/* Altered Foundry safeEval to allow non-numeric returns */
static effectSafeEval(expression) {
let result;
try {
// eslint-disable-next-line no-new-func
const evl = new Function('sandbox', `with (sandbox) { return ${expression}}`);
result = evl(Roll.MATH_PROXY);
} catch (err) {
return expression;
}
return result;
}
async addStatusActiveEffects(statuses) {
if (this.parent.type !== 'character') return;
const { statusMap, isStatusActiveEffect } = this.isStatusActiveEffect;
if (isStatusActiveEffect) return;
const statusesToAdd = statuses.reduce((acc, status) => {
const statusData = statusMap.get(status);
const statusName = game.i18n.localize(statusData.name);
const alreadyExists = this.parent.effects.find(
effect => effect.statuses.size === 1 && effect.statuses.first() === status && effect.name === statusName
);
if (!alreadyExists) {
acc.push({
name: statusName,
description: game.i18n.localize(statusData.description),
img: statusData.icon,
statuses: [status]
});
}
return acc;
}, []);
await this.parent.createEmbeddedDocuments('ActiveEffect', statusesToAdd);
}
async removeStatusActiveEffects(statuses) {
if (this.parent.type !== 'character') return;
const { statusMap, isStatusActiveEffect } = this.isStatusActiveEffect;
if (isStatusActiveEffect) return;
const statusesToRemove = statuses.reduce((acc, status) => {
const statusName = game.i18n.localize(statusMap.get(status).name);
const existingEffect = this.parent.effects.find(
effect => effect.statuses.size === 1 && effect.statuses.first() === status && effect.name === statusName
);
if (existingEffect) {
acc.push(existingEffect.id);
}
return acc;
}, []);
await this.parent.deleteEmbeddedDocuments('ActiveEffect', statusesToRemove);
}
async toChat(origin) {
const cls = getDocumentClass('ChatMessage');
const systemData = {
title: game.i18n.localize('DAGGERHEART.CONFIG.ActionType.action'),
origin: origin,
img: this.img,
name: this.name,
description: this.description,
actions: []
};
const msg = new cls({
type: 'abilityUse',
user: game.user.id,
system: systemData,
content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/ui/chat/ability-use.hbs',
systemData
)
});
cls.create(msg.toObject());
}
}