also assign effects to unlinked Tokens and fix calculation
All checks were successful
Sonarqube Scanner / Build and analyze (push) Successful in 48s
All checks were successful
Sonarqube Scanner / Build and analyze (push) Successful in 48s
This commit is contained in:
parent
dd1b08ee21
commit
eb43af49b2
@ -1,18 +1,18 @@
|
|||||||
import { MODULE_ID } from "../constants.js";
|
import { MODULE_ID } from "../constants.js";
|
||||||
|
|
||||||
export async function executeApplyStatusAction(action, context) {
|
export async function executeApplyStatusAction(action, context) {
|
||||||
const actor = context.targetActor ?? context.actor;
|
const actor = context.targetTokenDocument?.actor ?? context.tokenDocument?.actor ?? context.targetActor ?? context.actor;
|
||||||
if (!actor) return { success: false, consumed: false, reason: "NO_ACTOR" };
|
if (!actor) return { success: false, consumed: false, reason: "NO_ACTOR" };
|
||||||
|
|
||||||
const statuses = action.statuses ?? [];
|
const statuses = action.statuses ?? [];
|
||||||
if (!statuses.length) return { success: false, consumed: false, reason: "NO_STATUSES" };
|
if (!statuses.length) return { success: false, consumed: false, reason: "NO_STATUSES" };
|
||||||
|
|
||||||
const effects = statuses.map(statusId => {
|
const effects = statuses.map(statusId => {
|
||||||
const statusConfig = CONFIG.statusEffects.find(status => status.id === statusId);
|
const statusConfig = getStatusEffectConfig(statusId);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: statusConfig?.name ? game.i18n.localize(statusConfig.name) : statusId,
|
name: statusConfig?.name ? game.i18n.localize(statusConfig.name) : statusId,
|
||||||
icon: statusConfig?.img ?? "icons/svg/aura.svg",
|
icon: statusConfig?.img ?? statusConfig?.icon ?? "icons/svg/aura.svg",
|
||||||
statuses: [statusId],
|
statuses: [statusId],
|
||||||
origin: context.reactionOrigin,
|
origin: context.reactionOrigin,
|
||||||
duration: buildEffectDuration(action.duration),
|
duration: buildEffectDuration(action.duration),
|
||||||
@ -31,6 +31,13 @@ export async function executeApplyStatusAction(action, context) {
|
|||||||
return { success: true, consumed: true };
|
return { success: true, consumed: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getStatusEffectConfig(statusId) {
|
||||||
|
const statusEffects = CONFIG.statusEffects ?? [];
|
||||||
|
if (Array.isArray(statusEffects)) return statusEffects.find(status => status.id === statusId);
|
||||||
|
if (statusEffects instanceof Map) return statusEffects.get(statusId) ?? null;
|
||||||
|
return Object.values(statusEffects).find(status => status.id === statusId) ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
function buildEffectDuration(duration) {
|
function buildEffectDuration(duration) {
|
||||||
if (!duration || duration.type === "unlimited") return {};
|
if (!duration || duration.type === "unlimited") return {};
|
||||||
|
|
||||||
|
|||||||
@ -57,6 +57,7 @@ export async function assignReactionToSelectedTokens(reactionId) {
|
|||||||
await ensureManagedReactionEffectOnActor(actor, reaction);
|
await ensureManagedReactionEffectOnActor(actor, reaction);
|
||||||
} else {
|
} else {
|
||||||
await addReactionFlag(tokenDocument, reactionId);
|
await addReactionFlag(tokenDocument, reactionId);
|
||||||
|
await ensureManagedReactionEffectOnActor(actor, reaction, tokenDocument.uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
assignedCount++;
|
assignedCount++;
|
||||||
@ -82,7 +83,10 @@ export async function removeAssignment(assignmentId) {
|
|||||||
|
|
||||||
if (assignment.mode === "token" && assignment.tokenUuid) {
|
if (assignment.mode === "token" && assignment.tokenUuid) {
|
||||||
const tokenDocument = await fromUuid(assignment.tokenUuid);
|
const tokenDocument = await fromUuid(assignment.tokenUuid);
|
||||||
if (tokenDocument) await removeReactionFromDocument(tokenDocument, assignment.reactionId, false);
|
if (tokenDocument) {
|
||||||
|
await removeReactionFromDocument(tokenDocument, assignment.reactionId, false);
|
||||||
|
if (tokenDocument.actor) await removeReactionFromDocument(tokenDocument.actor, assignment.reactionId, true, tokenDocument.uuid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,12 +122,13 @@ async function addReactionFlag(document, reactionId) {
|
|||||||
await document.setFlag(MODULE_ID, "assignedReactions", assigned);
|
await document.setFlag(MODULE_ID, "assignedReactions", assigned);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ensureManagedReactionEffectOnActor(actor, reaction) {
|
async function ensureManagedReactionEffectOnActor(actor, reaction, tokenUuid = null) {
|
||||||
if (!game.settings.get(MODULE_ID, SETTINGS.CREATE_MANAGED_EFFECTS_ON_ASSIGNMENT)) return;
|
if (!game.settings.get(MODULE_ID, SETTINGS.CREATE_MANAGED_EFFECTS_ON_ASSIGNMENT)) return;
|
||||||
|
|
||||||
const existing = actor.effects.find(effect =>
|
const existing = actor.effects.find(effect =>
|
||||||
effect.getFlag(MODULE_ID, "reactionId") === reaction.id &&
|
effect.getFlag(MODULE_ID, "reactionId") === reaction.id &&
|
||||||
effect.getFlag(MODULE_ID, "managed") === true
|
effect.getFlag(MODULE_ID, "managed") === true &&
|
||||||
|
(tokenUuid === null || effect.getFlag(MODULE_ID, "tokenUuid") === tokenUuid)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (existing) return;
|
if (existing) return;
|
||||||
@ -138,13 +143,14 @@ async function ensureManagedReactionEffectOnActor(actor, reaction) {
|
|||||||
flags: {
|
flags: {
|
||||||
[MODULE_ID]: {
|
[MODULE_ID]: {
|
||||||
reactionId: reaction.id,
|
reactionId: reaction.id,
|
||||||
managed: true
|
managed: true,
|
||||||
|
tokenUuid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}]);
|
}]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function removeReactionFromDocument(document, reactionId, removeManagedEffects) {
|
async function removeReactionFromDocument(document, reactionId, removeManagedEffects, tokenUuid = null) {
|
||||||
const assigned = foundry.utils.deepClone(document.getFlag(MODULE_ID, "assignedReactions") ?? [])
|
const assigned = foundry.utils.deepClone(document.getFlag(MODULE_ID, "assignedReactions") ?? [])
|
||||||
.filter(entry => entry.reactionId !== reactionId);
|
.filter(entry => entry.reactionId !== reactionId);
|
||||||
|
|
||||||
@ -154,7 +160,11 @@ async function removeReactionFromDocument(document, reactionId, removeManagedEff
|
|||||||
if (!removeManagedEffects || !document.effects) return;
|
if (!removeManagedEffects || !document.effects) return;
|
||||||
|
|
||||||
const effectIds = document.effects
|
const effectIds = document.effects
|
||||||
.filter(effect => effect.getFlag(MODULE_ID, "reactionId") === reactionId && effect.getFlag(MODULE_ID, "managed") === true)
|
.filter(effect =>
|
||||||
|
effect.getFlag(MODULE_ID, "reactionId") === reactionId &&
|
||||||
|
effect.getFlag(MODULE_ID, "managed") === true &&
|
||||||
|
(tokenUuid === null || effect.getFlag(MODULE_ID, "tokenUuid") === tokenUuid)
|
||||||
|
)
|
||||||
.map(effect => effect.id);
|
.map(effect => effect.id);
|
||||||
|
|
||||||
if (effectIds.length) await document.deleteEmbeddedDocuments("ActiveEffect", effectIds);
|
if (effectIds.length) await document.deleteEmbeddedDocuments("ActiveEffect", effectIds);
|
||||||
|
|||||||
@ -13,9 +13,21 @@ export async function checkDamageCondition(config, context) {
|
|||||||
if (!configuredTypes.length) return true;
|
if (!configuredTypes.length) return true;
|
||||||
|
|
||||||
const eventTypes = new Set(context.damageTypes ?? []);
|
const eventTypes = new Set(context.damageTypes ?? []);
|
||||||
if (!eventTypes.size) return false;
|
if (!eventTypes.size) return configuredTypesCoverAllKnownTypes(configuredTypes);
|
||||||
|
|
||||||
if (config.typeMode === "all") return configuredTypes.every(type => eventTypes.has(type));
|
if (config.typeMode === "all") return configuredTypes.every(type => eventTypes.has(type));
|
||||||
if (config.typeMode === "none") return configuredTypes.every(type => !eventTypes.has(type));
|
if (config.typeMode === "none") return configuredTypes.every(type => !eventTypes.has(type));
|
||||||
return configuredTypes.some(type => eventTypes.has(type));
|
return configuredTypes.some(type => eventTypes.has(type));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function configuredTypesCoverAllKnownTypes(configuredTypes) {
|
||||||
|
const knownTypes = getKnownDamageTypes();
|
||||||
|
if (!knownTypes.size) return false;
|
||||||
|
return Array.from(knownTypes).every(type => configuredTypes.includes(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getKnownDamageTypes() {
|
||||||
|
const damageTypes = CONFIG.DND5E?.damageTypes ?? {};
|
||||||
|
if (damageTypes instanceof Map) return new Set(damageTypes.keys());
|
||||||
|
return new Set(Object.keys(damageTypes));
|
||||||
|
}
|
||||||
|
|||||||
@ -7,31 +7,75 @@ export function registerDnd5eDamageTrigger() {
|
|||||||
if (!game.user.isGM) return;
|
if (!game.user.isGM) return;
|
||||||
if (!actor) return;
|
if (!actor) return;
|
||||||
|
|
||||||
const tokenDocument = options.token ?? options.tokenDocument ?? getBestTokenDocumentForActor(actor);
|
const hookOptions = normalizeDamageHookOptions(options);
|
||||||
const damageTypes = extractDamageTypes(options);
|
const tokenDocument = resolveDamageTokenDocument(actor, hookOptions);
|
||||||
|
const damageTypes = extractDamageTypes(hookOptions);
|
||||||
|
const numericAmount = Number(amount) || 0;
|
||||||
|
|
||||||
await handleTrigger(TRIGGER_TYPES.DAMAGE_RECEIVED, {
|
await handleTrigger(TRIGGER_TYPES.DAMAGE_RECEIVED, {
|
||||||
actor,
|
actor: tokenDocument?.actor ?? actor,
|
||||||
targetActor: actor,
|
targetActor: tokenDocument?.actor ?? actor,
|
||||||
tokenDocument,
|
tokenDocument,
|
||||||
targetTokenDocument: tokenDocument,
|
targetTokenDocument: tokenDocument,
|
||||||
targetToken: tokenDocument?.object ?? null,
|
targetToken: tokenDocument?.object ?? null,
|
||||||
amount: Number(amount) || 0,
|
amount: numericAmount,
|
||||||
damageAmount: Number(amount) || 0,
|
damageAmount: numericAmount,
|
||||||
damageTypes,
|
damageTypes,
|
||||||
options
|
options: hookOptions
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractDamageTypes(options) {
|
function normalizeDamageHookOptions(options) {
|
||||||
const candidates = [
|
return options && typeof options === "object" ? options : {};
|
||||||
options.damageTypes,
|
}
|
||||||
options.types,
|
|
||||||
options.damage?.types,
|
function resolveDamageTokenDocument(actor, options) {
|
||||||
options.workflow?.damageItem?.damageDetail?.map?.(part => part.type)
|
return options.tokenDocument ??
|
||||||
];
|
options.token?.document ??
|
||||||
|
options.token ??
|
||||||
const values = candidates.flat().filter(Boolean);
|
options.targetTokenDocument ??
|
||||||
return [...new Set(values)];
|
options.targetToken?.document ??
|
||||||
|
actor.token ??
|
||||||
|
getBestTokenDocumentForActor(actor);
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractDamageTypes(options) {
|
||||||
|
const knownTypes = getKnownDamageTypes();
|
||||||
|
const collected = new Set();
|
||||||
|
collectDamageTypes(options, knownTypes, collected);
|
||||||
|
return Array.from(collected);
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectDamageTypes(value, knownTypes, collected, seen = new WeakSet(), depth = 0) {
|
||||||
|
if (value == null || depth > 5) return;
|
||||||
|
|
||||||
|
if (typeof value === "string") {
|
||||||
|
if (knownTypes.has(value)) collected.add(value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value !== "object") return;
|
||||||
|
if (seen.has(value)) return;
|
||||||
|
seen.add(value);
|
||||||
|
|
||||||
|
if (Array.isArray(value) || value instanceof Set) {
|
||||||
|
for (const entry of value) collectDamageTypes(entry, knownTypes, collected, seen, depth + 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value instanceof Map) {
|
||||||
|
for (const entry of value.values()) collectDamageTypes(entry, knownTypes, collected, seen, depth + 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key of ["type", "damageType", "damageTypeId", "types", "damageTypes", "damage", "damages", "damageDetail", "parts"]) {
|
||||||
|
collectDamageTypes(value[key], knownTypes, collected, seen, depth + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getKnownDamageTypes() {
|
||||||
|
const damageTypes = CONFIG.DND5E?.damageTypes ?? {};
|
||||||
|
if (damageTypes instanceof Map) return new Set(damageTypes.keys());
|
||||||
|
return new Set(Object.keys(damageTypes));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,18 +1,27 @@
|
|||||||
export function getBestTokenDocumentForActor(actor) {
|
export function getBestTokenDocumentForActor(actor) {
|
||||||
if (!actor) return null;
|
if (!actor) return null;
|
||||||
|
|
||||||
|
const syntheticToken = getSyntheticTokenDocument(actor);
|
||||||
|
if (syntheticToken) return syntheticToken;
|
||||||
|
|
||||||
const controlled = canvas?.tokens?.controlled?.find(token => token.actor?.uuid === actor.uuid || token.actor?.id === actor.id);
|
const controlled = canvas?.tokens?.controlled?.find(token => token.actor?.uuid === actor.uuid || token.actor?.id === actor.id);
|
||||||
if (controlled) return controlled.document;
|
if (controlled) return controlled.document;
|
||||||
|
|
||||||
const activeTokens = actor.getActiveTokens?.(true, true) ?? [];
|
|
||||||
if (activeTokens.length === 1) return activeTokens[0].document;
|
|
||||||
|
|
||||||
const combatant = game.combat?.combatants?.find(c => c.actor?.uuid === actor.uuid || c.actor?.id === actor.id);
|
const combatant = game.combat?.combatants?.find(c => c.actor?.uuid === actor.uuid || c.actor?.id === actor.id);
|
||||||
if (combatant?.token) return combatant.token;
|
if (combatant?.token) return combatant.token;
|
||||||
|
|
||||||
|
const activeTokens = actor.getActiveTokens?.(true, true) ?? [];
|
||||||
|
if (activeTokens.length === 1) return activeTokens[0].document;
|
||||||
|
|
||||||
return activeTokens[0]?.document ?? null;
|
return activeTokens[0]?.document ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getSyntheticTokenDocument(actor) {
|
||||||
|
const tokenDocument = actor.token ?? actor.prototypeToken ?? null;
|
||||||
|
if (tokenDocument?.documentName === "Token") return tokenDocument;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
export function getPrimaryOwner(actor) {
|
export function getPrimaryOwner(actor) {
|
||||||
if (!actor) return null;
|
if (!actor) return null;
|
||||||
return game.users.find(user => user.active && !user.isGM && actor.testUserPermission(user, "OWNER")) ?? null;
|
return game.users.find(user => user.active && !user.isGM && actor.testUserPermission(user, "OWNER")) ?? null;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user