configurable-reactions/scripts/apps/assignment-manager.js
ChatGPT 95e8b7b994
All checks were successful
Sonarqube Scanner / Build and analyze (push) Successful in 50s
handle marker as feature
2026-06-05 00:37:37 +02:00

195 lines
6.8 KiB
JavaScript

import { MODULE_ID, SETTINGS } from "../constants.js";
const ASSIGNED_REACTIONS_FLAG = "assignedReactions";
const MANAGED_FEATURE_FLAG = "managedAssignmentFeature";
const FEATURE_TYPE = "feat";
export async function assignReactionToSelectedTokens(reactionId) {
if (!game.user.isGM) {
ui.notifications.warn(game.i18n.localize("CONFIGURABLE_REACTIONS.Errors.GmOnly"));
return;
}
if (!reactionId) {
ui.notifications.warn(game.i18n.localize("CONFIGURABLE_REACTIONS.Errors.NoReactionSelected"));
return;
}
const selectedTokens = canvas?.tokens?.controlled ?? [];
if (!selectedTokens.length) {
ui.notifications.warn(game.i18n.localize("CONFIGURABLE_REACTIONS.Errors.NoTokensSelected"));
return;
}
const reactions = game.settings.get(MODULE_ID, SETTINGS.REACTIONS) ?? [];
const reaction = reactions.find(candidate => candidate.id === reactionId);
if (!reaction) {
ui.notifications.error(game.i18n.localize("CONFIGURABLE_REACTIONS.Errors.ReactionNotFound"));
return;
}
const assignments = foundry.utils.deepClone(game.settings.get(MODULE_ID, SETTINGS.ASSIGNMENTS) ?? []);
let assignedCount = 0;
let skippedCount = 0;
for (const token of selectedTokens) {
const result = await assignReactionToToken({ token, reaction, reactionId, assignments });
if (result === "assigned") assignedCount++;
else skippedCount++;
}
await game.settings.set(MODULE_ID, SETTINGS.ASSIGNMENTS, assignments);
ui.notifications.info(game.i18n.format("CONFIGURABLE_REACTIONS.Assignments.AssignedResult", { assigned: assignedCount, skipped: skippedCount }));
}
async function assignReactionToToken({ token, reaction, reactionId, assignments }) {
const tokenDocument = token?.document;
const actor = token?.actor;
if (!tokenDocument || !actor) return "skipped";
const assignment = buildAssignmentForToken(tokenDocument, actor, reactionId);
if (hasAssignment(assignments, assignment)) return "skipped";
assignments.push(assignment);
if (assignment.mode === "actor") {
await addReactionFlag(actor, reactionId);
await ensureManagedReactionFeature(actor, reaction);
return "assigned";
}
await addReactionFlag(tokenDocument, reactionId);
await ensureManagedReactionFeature(actor, reaction, tokenDocument.uuid);
return "assigned";
}
function hasAssignment(assignments, assignment) {
return assignments.some(existing =>
existing.reactionId === assignment.reactionId &&
existing.mode === assignment.mode &&
existing.actorUuid === assignment.actorUuid &&
existing.tokenUuid === assignment.tokenUuid
);
}
export async function removeAssignment(assignmentId) {
if (!game.user.isGM) return;
const assignments = foundry.utils.deepClone(game.settings.get(MODULE_ID, SETTINGS.ASSIGNMENTS) ?? []);
const assignment = assignments.find(candidate => candidate.id === assignmentId);
if (!assignment) return;
await game.settings.set(MODULE_ID, SETTINGS.ASSIGNMENTS, assignments.filter(candidate => candidate.id !== assignmentId));
await removeAssignmentMarkers(assignment);
}
async function removeAssignmentMarkers(assignment) {
if (assignment.mode === "actor" && assignment.actorUuid) {
const actor = await fromUuid(assignment.actorUuid);
if (actor) await removeReactionFromDocument(actor, assignment.reactionId, true);
return;
}
if (assignment.mode !== "token" || !assignment.tokenUuid) return;
const tokenDocument = await fromUuid(assignment.tokenUuid);
if (!tokenDocument) return;
await removeReactionFromDocument(tokenDocument, assignment.reactionId, false);
if (tokenDocument.actor) await removeReactionFromDocument(tokenDocument.actor, assignment.reactionId, true, tokenDocument.uuid);
}
function buildAssignmentForToken(tokenDocument, actor, reactionId) {
if (tokenDocument.actorLink === true) {
return {
id: foundry.utils.randomID(),
reactionId,
mode: "actor",
actorUuid: actor.uuid,
tokenUuid: null,
sceneUuid: null,
name: actor.name
};
}
return {
id: foundry.utils.randomID(),
reactionId,
mode: "token",
actorUuid: null,
tokenUuid: tokenDocument.uuid,
sceneUuid: tokenDocument.parent?.uuid ?? canvas.scene?.uuid ?? null,
name: tokenDocument.name
};
}
async function addReactionFlag(document, reactionId) {
const assigned = foundry.utils.deepClone(document.getFlag(MODULE_ID, ASSIGNED_REACTIONS_FLAG) ?? []);
if (!assigned.some(entry => entry.reactionId === reactionId)) {
assigned.push({ reactionId, assignedAt: Date.now(), assignedBy: game.user.id });
}
await document.setFlag(MODULE_ID, ASSIGNED_REACTIONS_FLAG, assigned);
}
async function ensureManagedReactionFeature(actor, reaction, tokenUuid = null) {
if (!game.settings.get(MODULE_ID, SETTINGS.CREATE_MANAGED_EFFECTS_ON_ASSIGNMENT)) return;
if (!actor?.createEmbeddedDocuments) return;
if (findManagedReactionFeature(actor, reaction.id, tokenUuid)) return;
await actor.createEmbeddedDocuments("Item", [buildManagedReactionFeatureData(reaction, tokenUuid)]);
}
function findManagedReactionFeature(actor, reactionId, tokenUuid = null) {
return actor.items?.find(item =>
item.type === FEATURE_TYPE &&
item.getFlag(MODULE_ID, "reactionId") === reactionId &&
item.getFlag(MODULE_ID, MANAGED_FEATURE_FLAG) === true &&
(tokenUuid === null || item.getFlag(MODULE_ID, "tokenUuid") === tokenUuid)
);
}
function buildManagedReactionFeatureData(reaction, tokenUuid = null) {
const name = game.i18n.format("CONFIGURABLE_REACTIONS.Effects.ManagedReaction", { name: reaction.name });
return {
name,
type: FEATURE_TYPE,
img: reaction.icon ?? "icons/svg/aura.svg",
system: {
description: {
value: `<p>${name}</p>`,
chat: ""
}
},
flags: {
[MODULE_ID]: {
reactionId: reaction.id,
managed: true,
[MANAGED_FEATURE_FLAG]: true,
tokenUuid
}
}
};
}
async function removeReactionFromDocument(document, reactionId, removeManagedFeature, tokenUuid = null) {
const assigned = foundry.utils.deepClone(document.getFlag(MODULE_ID, ASSIGNED_REACTIONS_FLAG) ?? [])
.filter(entry => entry.reactionId !== reactionId);
if (assigned.length) await document.setFlag(MODULE_ID, ASSIGNED_REACTIONS_FLAG, assigned);
else await document.unsetFlag(MODULE_ID, ASSIGNED_REACTIONS_FLAG);
if (removeManagedFeature) await removeManagedReactionFeatures(document, reactionId, tokenUuid);
}
async function removeManagedReactionFeatures(actor, reactionId, tokenUuid = null) {
const itemIds = actor.items
?.filter(item =>
item.getFlag(MODULE_ID, "reactionId") === reactionId &&
item.getFlag(MODULE_ID, MANAGED_FEATURE_FLAG) === true &&
(tokenUuid === null || item.getFlag(MODULE_ID, "tokenUuid") === tokenUuid)
)
.map(item => item.id) ?? [];
if (itemIds.length) await actor.deleteEmbeddedDocuments("Item", itemIds);
}