223 lines
5.4 KiB
JavaScript

import {
MODULE_ID,
TRIGGER_FLAG,
DAMAGE_SETS,
ADAPTATION_TYPES,
ADAPTATION_CONFIG
} from "./constants.js";
export function isActiveGM() {
return game.user?.isGM === true;
}
export function localize(key) {
return game.i18n.localize(key);
}
export function getDamageTypeLabel(type) {
const configured = CONFIG.DND5E?.damageTypes?.[type];
if (!configured) return type;
if (typeof configured === "string") {
return game.i18n.localize(configured);
}
return game.i18n.localize(configured.label ?? type);
}
export function getDamageValue(damage) {
if (!damage) return 0;
const candidates = [
damage.value,
damage.amount,
damage.total,
damage.damage,
damage.active?.value,
damage.active?.amount,
damage.active?.total
];
for (const value of candidates) {
if (typeof value === "number" && Number.isFinite(value)) {
return value;
}
}
return 0;
}
export function getDamageType(damage) {
return damage?.type
?? damage?.damageType
?? damage?.application?.type
?? null;
}
export function toArrayValue(value) {
if (!value) return [];
if (Array.isArray(value)) {
return value;
}
if (value instanceof Set) {
return Array.from(value);
}
if (typeof value === "object") {
return Object.keys(value).filter(key => value[key]);
}
if (typeof value === "string") {
return [value];
}
return [];
}
export function actorResistanceValues(actor) {
return toArrayValue(actor?.system?.traits?.dr?.value);
}
export function actorImmunityValues(actor) {
return toArrayValue(actor?.system?.traits?.di?.value);
}
export function actorVulnerabilityValues(actor) {
return toArrayValue(actor?.system?.traits?.dv?.value);
}
export function actorVulnerableTo(actor, damageType) {
return actorVulnerabilityValues(actor).includes(damageType);
}
export function actorAlreadyResists(actor, damageType) {
return actorResistanceValues(actor).includes(damageType);
}
export function actorAlreadyImmune(actor, damageType) {
return actorImmunityValues(actor).includes(damageType);
}
export function actorAlreadyProtected(actor, damageType) {
return actorAlreadyResists(actor, damageType)
|| actorAlreadyImmune(actor, damageType);
}
/**
* Liefert alle aktuell auf den Actor angewendeten Marker-Effects.
*
* Hierdurch funktionieren sowohl:
* - Feature-Items auf dem Actor,
* - transferierende Effekte von Ausrüstung,
* - transferierende Effekte von attuned Items,
* - direkt auf den Actor gesetzte Marker-Effects.
*/
export function getAdaptiveTriggerEffects(actor) {
if (!actor) return [];
return Array.from(actor.appliedEffects ?? []).filter(effect => {
const trigger = effect.getFlag(MODULE_ID, TRIGGER_FLAG);
const adaptationType =
trigger?.adaptationType ?? ADAPTATION_TYPES.RESISTANCE;
return trigger?.enabled === true
&& DAMAGE_SETS[trigger?.setId]
&& ADAPTATION_CONFIG[adaptationType];
});
}
/**
* Prüft, ob eine konkrete adaptive Quelle weiterhin auf dem Actor angewendet wird.
*/
export function isAdaptiveTriggerApplied(actor, effectUuid) {
if (!effectUuid) return false;
return getAdaptiveTriggerEffects(actor).some(
effect => effect.uuid === effectUuid
);
}
/**
* Bereitet aus den aktuell angewendeten Marker-Effects alle erlaubten
* Schadenstyp-/Adaptationstyp-Kombinationen auf.
*/
export function getAllowedAdaptationsForActor(actor) {
const result = [];
for (const effect of getAdaptiveTriggerEffects(actor)) {
const trigger = effect.getFlag(MODULE_ID, TRIGGER_FLAG);
const set = DAMAGE_SETS[trigger.setId];
const adaptationType =
trigger.adaptationType ?? ADAPTATION_TYPES.RESISTANCE;
for (const damageType of set.damageTypes) {
result.push({
damageType,
adaptationType,
priority: ADAPTATION_CONFIG[adaptationType].priority,
sourceEffectUuid: effect.uuid
});
}
}
return result;
}
/**
* Ermittelt die tatsächlich für den Treffer in Frage kommenden
* adaptiven Schadensreaktionen.
*/
export function getCandidatesForActor(actor, damages) {
const allowed = getAllowedAdaptationsForActor(actor);
if (!allowed.length || !Array.isArray(damages)) {
return [];
}
return damages
.map(damage => ({
type: getDamageType(damage),
value: getDamageValue(damage)
}))
.filter(damage => damage.type && damage.value > 0)
.flatMap(damage => allowed
.filter(entry => entry.damageType === damage.type)
.map(entry => ({
type: damage.type,
value: damage.value,
adaptationType: entry.adaptationType,
priority: entry.priority,
sourceEffectUuid: entry.sourceEffectUuid
}))
);
}
/**
* Bei mehreren passenden Schadenstypen gewinnt zunächst der höchste Schaden.
* Bei Gleichstand gewinnt Immunität vor Resistenz über deren höhere Priorität.
*/
export function getDominantDamageCandidate(candidates) {
if (!candidates?.length) return null;
return [...candidates].sort((a, b) => {
const valueDifference = b.value - a.value;
if (valueDifference !== 0) {
return valueDifference;
}
return b.priority - a.priority;
})[0];
}
export function getDominantDamageType(candidates) {
return getDominantDamageCandidate(candidates)?.type ?? null;
}
// Backwards-compatible alias used by older local patches of this module.
export function getElementalCandidatesForActor(actor, damages) {
return getCandidatesForActor(actor, damages);
}