2021-07-13 02:03:10 +02:00
|
|
|
// SPDX-FileCopyrightText: 2021 Johannes Loher
|
|
|
|
//
|
|
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
|
|
|
|
import { DS4Actor } from "./actor/actor";
|
2022-10-31 22:58:04 +01:00
|
|
|
import { mathEvaluator } from "./expression-evaluation/evaluator";
|
2021-07-23 12:29:01 +02:00
|
|
|
import { getGame } from "./helpers";
|
2022-02-17 00:55:22 +01:00
|
|
|
|
|
|
|
import type { DS4Item } from "./item/item";
|
2021-07-13 02:03:10 +02:00
|
|
|
|
|
|
|
declare global {
|
|
|
|
interface DocumentClassConfig {
|
|
|
|
ActiveEffect: typeof DS4ActiveEffect;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-23 12:29:01 +02:00
|
|
|
type PromisedType<T> = T extends Promise<infer U> ? U : T;
|
2021-08-19 03:04:40 +02:00
|
|
|
|
2021-07-13 02:03:10 +02:00
|
|
|
export class DS4ActiveEffect extends ActiveEffect {
|
2021-07-23 17:32:26 +02:00
|
|
|
/**
|
|
|
|
* A fallback icon that can be used if no icon is defined for the effect.
|
|
|
|
*/
|
|
|
|
static FALLBACK_ICON = "icons/svg/aura.svg";
|
|
|
|
|
2021-07-23 12:29:01 +02:00
|
|
|
/**
|
|
|
|
* A cached reference to the source document to avoid recurring database lookups
|
|
|
|
*/
|
|
|
|
protected source: PromisedType<ReturnType<typeof fromUuid>> | undefined = undefined;
|
|
|
|
|
2021-08-19 03:04:40 +02:00
|
|
|
/**
|
|
|
|
* Whether or not this effect is currently surpressed.
|
|
|
|
*/
|
|
|
|
get isSurpressed(): boolean {
|
|
|
|
const originatingItem = this.originatingItem;
|
|
|
|
if (!originatingItem) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return originatingItem.isNonEquippedEuipable();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The item which this effect originates from if it has been transferred from an item to an actor.
|
|
|
|
*/
|
|
|
|
get originatingItem(): DS4Item | undefined {
|
|
|
|
if (!(this.parent instanceof DS4Actor)) {
|
|
|
|
return;
|
|
|
|
}
|
2021-09-23 12:45:53 +02:00
|
|
|
const itemIdRegex = /Item\.([a-zA-Z0-9]+)/;
|
|
|
|
const itemId = this.data.origin?.match(itemIdRegex)?.[1];
|
2021-08-19 03:04:40 +02:00
|
|
|
if (!itemId) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
return this.parent.items.get(itemId);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The number of times this effect should be applied.
|
|
|
|
*/
|
|
|
|
get factor(): number {
|
|
|
|
return this.originatingItem?.activeEffectFactor ?? 1;
|
|
|
|
}
|
|
|
|
|
2022-05-29 17:47:27 +02:00
|
|
|
override apply(actor: DS4Actor, change: foundry.data.ActiveEffectData["changes"][number]): unknown {
|
2021-07-13 02:03:10 +02:00
|
|
|
change.value = Roll.replaceFormulaData(change.value, actor.data);
|
|
|
|
try {
|
2022-10-31 22:58:04 +01:00
|
|
|
change.value = DS4ActiveEffect.safeEval(change.value).toString();
|
2021-07-13 02:03:10 +02:00
|
|
|
} catch (e) {
|
2022-10-31 22:58:04 +01:00
|
|
|
logger.warn(e);
|
2021-07-13 02:03:10 +02:00
|
|
|
// this is a valid case, e.g., if the effect change simply is a string
|
|
|
|
}
|
|
|
|
return super.apply(actor, change);
|
|
|
|
}
|
2021-07-20 02:35:55 +02:00
|
|
|
|
|
|
|
/**
|
2021-07-23 12:29:01 +02:00
|
|
|
* Gets the current source name based on the cached source object.
|
|
|
|
*/
|
|
|
|
async getCurrentSourceName(): Promise<string> {
|
|
|
|
const game = getGame();
|
|
|
|
const origin = await this.getSource();
|
|
|
|
if (origin === null) return game.i18n.localize("None");
|
|
|
|
return origin.name ?? game.i18n.localize("Unknown");
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-01-22 14:15:39 +01:00
|
|
|
* Gets the source document for this effect. Uses the cached {@link DS4ActiveEffect#source} if it has already been
|
2021-07-23 12:29:01 +02:00
|
|
|
* set.
|
2021-07-20 02:35:55 +02:00
|
|
|
*/
|
2021-07-23 12:29:01 +02:00
|
|
|
protected async getSource(): ReturnType<typeof fromUuid> {
|
|
|
|
if (this.source === undefined) {
|
|
|
|
this.source = this.data.origin !== undefined ? await fromUuid(this.data.origin) : null;
|
|
|
|
}
|
|
|
|
return this.source;
|
2021-07-20 02:35:55 +02:00
|
|
|
}
|
2021-08-19 03:04:40 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a new {@link DS4ActiveEffect} using default data.
|
|
|
|
*
|
2021-09-22 12:32:18 +02:00
|
|
|
* @param parent The parent {@link DS4Actor} or {@link DS4Item} of the effect.
|
2021-08-19 03:04:40 +02:00
|
|
|
* @returns A promise that resolved to the created effect or udifined of the creation was prevented.
|
|
|
|
*/
|
2021-09-22 12:32:18 +02:00
|
|
|
static async createDefault(parent: DS4Actor | DS4Item): Promise<DS4ActiveEffect | undefined> {
|
2021-08-19 03:04:40 +02:00
|
|
|
const createData = {
|
|
|
|
label: getGame().i18n.localize(`DS4.NewEffectLabel`),
|
|
|
|
icon: this.FALLBACK_ICON,
|
|
|
|
};
|
2021-09-22 12:32:18 +02:00
|
|
|
|
|
|
|
return this.create(createData, { parent, pack: parent.pack ?? undefined });
|
2021-08-19 03:04:40 +02:00
|
|
|
}
|
2022-10-31 22:58:04 +01:00
|
|
|
|
|
|
|
static safeEval(expression: string): number | `${number | boolean}` {
|
|
|
|
const result = mathEvaluator.evaluate(expression);
|
|
|
|
if (!Number.isNumeric(result)) {
|
|
|
|
throw new Error(`mathEvaluator.evaluate produced a non-numeric result from expression "${expression}"`);
|
|
|
|
}
|
|
|
|
return result as number | `${number | boolean}`;
|
|
|
|
}
|
2021-07-13 02:03:10 +02:00
|
|
|
}
|