add spell item dropin for spell based triggers and actions

This commit is contained in:
ChatGPT
2026-06-04 21:01:54 +02:00
parent 3c25080666
commit c2ffa47ff2
29 changed files with 191 additions and 13 deletions
View File
+98 -4
View File
@@ -31,7 +31,9 @@ export class ConfigurableReactionsConfigApp extends HandlebarsApplicationMixin(A
assignSelectedTokens: ConfigurableReactionsConfigApp.#onAssignSelectedTokens,
removeAssignment: ConfigurableReactionsConfigApp.#onRemoveAssignment,
removeAction: ConfigurableReactionsConfigApp.#onRemoveAction,
saveReactionBasics: ConfigurableReactionsConfigApp.#onSaveReactionBasics
saveReactionBasics: ConfigurableReactionsConfigApp.#onSaveReactionBasics,
clearTriggerSpell: ConfigurableReactionsConfigApp.#onClearTriggerSpell,
clearActionSpell: ConfigurableReactionsConfigApp.#onClearActionSpell
}
};
@@ -62,6 +64,8 @@ export class ConfigurableReactionsConfigApp extends HandlebarsApplicationMixin(A
}));
const activeTrigger = triggerPalette.find(t => t.type === selectedReaction?.trigger?.type) ?? null;
const triggerSpellName = selectedReaction?.trigger?.spell?.itemName || selectedReaction?.trigger?.spell?.itemUuid || "";
const activeTriggerAcceptsSpell = isSpellTrigger(selectedReaction?.trigger?.type);
const visualActions = (selectedReaction?.actions ?? []).map((action, index) => {
const paletteEntry = actionPalette.find(p => p.type === action.type);
return {
@@ -69,7 +73,9 @@ export class ConfigurableReactionsConfigApp extends HandlebarsApplicationMixin(A
index,
label: paletteEntry?.label ?? action.type,
icon: paletteEntry?.icon ?? "fa-solid fa-puzzle-piece",
summary: summarizeAction(action)
summary: summarizeAction(action),
isSpellAction: action.type === ACTION_TYPES.CAST_SPELL_FROM_TOKEN,
spellName: action.spellSelection?.itemName || action.spellSelection?.itemUuid || ""
};
});
@@ -86,6 +92,8 @@ export class ConfigurableReactionsConfigApp extends HandlebarsApplicationMixin(A
triggerPalette,
actionPalette,
activeTrigger,
activeTriggerAcceptsSpell,
triggerSpellName,
visualActions,
statusEffects: CONFIG.statusEffects.map(s => ({ id: s.id, name: game.i18n.localize(s.name ?? s.id) }))
};
@@ -130,18 +138,37 @@ export class ConfigurableReactionsConfigApp extends HandlebarsApplicationMixin(A
event.preventDefault();
dropZone.classList.remove("cr-drop-active");
event.stopPropagation();
const payload = readDragPayload(event);
if (!payload) return;
const droppedItem = await resolveDroppedItem(payload);
if (dropZone.dataset.crDrop === "trigger" && payload.kind === "trigger") {
await this.#setTrigger(payload.type);
return;
}
if (dropZone.dataset.crDrop === "trigger" && isSpellItem(droppedItem)) {
await this.#setTriggerSpell(droppedItem);
return;
}
if (dropZone.dataset.crDrop === "actions" && payload.kind === "action") {
await this.#addAction(createDefaultAction(payload.type));
return;
}
if (dropZone.dataset.crDrop === "actions" && isSpellItem(droppedItem)) {
await this.#addAction(createDefaultAction(ACTION_TYPES.CAST_SPELL_FROM_TOKEN, droppedItem));
return;
}
if (dropZone.dataset.crDrop === "action-spell" && isSpellItem(droppedItem)) {
await this.#setActionSpell(Number(dropZone.dataset.actionIndex), droppedItem);
return;
}
});
}
}
@@ -230,6 +257,24 @@ export class ConfigurableReactionsConfigApp extends HandlebarsApplicationMixin(A
});
}
static async #onClearTriggerSpell(event, target) {
await this.#mutateSelectedReaction(reaction => {
reaction.trigger ??= {};
delete reaction.trigger.spell;
});
}
static async #onClearActionSpell(event, target) {
const actionIndex = Number(target.dataset.actionIndex);
if (!Number.isInteger(actionIndex)) return;
await this.#mutateSelectedReaction(reaction => {
const action = reaction.actions?.[actionIndex];
if (!action || action.type !== ACTION_TYPES.CAST_SPELL_FROM_TOKEN) return;
action.spellSelection = { mode: "itemName", itemName: "" };
});
}
static async #onSaveReactionBasics(event, target) {
const name = this.element.querySelector("[name='reactionName']")?.value?.trim();
const enabled = this.element.querySelector("[name='reactionEnabled']")?.checked === true;
@@ -244,6 +289,25 @@ export class ConfigurableReactionsConfigApp extends HandlebarsApplicationMixin(A
await this.#mutateSelectedReaction(reaction => {
reaction.trigger ??= {};
reaction.trigger.type = triggerType;
if (!isSpellTrigger(triggerType)) delete reaction.trigger.spell;
});
}
async #setTriggerSpell(item) {
await this.#mutateSelectedReaction(reaction => {
reaction.trigger ??= {};
if (!isSpellTrigger(reaction.trigger.type)) reaction.trigger.type = TRIGGER_TYPES.SPELL_SEEN;
reaction.trigger.spell = buildSpellSelection(item);
});
}
async #setActionSpell(actionIndex, item) {
if (!Number.isInteger(actionIndex)) return;
await this.#mutateSelectedReaction(reaction => {
const action = reaction.actions?.[actionIndex];
if (!action || action.type !== ACTION_TYPES.CAST_SPELL_FROM_TOKEN) return;
action.spellSelection = buildSpellSelection(item);
});
}
@@ -277,7 +341,37 @@ function readDragPayload(event) {
}
}
function createDefaultAction(type) {
async function resolveDroppedItem(data) {
if (!data) return null;
if (data.uuid) {
const document = await fromUuid(data.uuid);
if (document?.documentName === "Item") return document;
}
if (data.type === "Item" && data.id) {
return game.items.get(data.id) ?? null;
}
return null;
}
function isSpellItem(item) {
return item?.documentName === "Item" && item.type === "spell";
}
function isSpellTrigger(type) {
return [TRIGGER_TYPES.SPELL_SEEN, TRIGGER_TYPES.SPELL_CAST_START, TRIGGER_TYPES.SPELL_CAST_COMPLETE].includes(type);
}
function buildSpellSelection(item) {
return {
mode: "itemName",
itemName: item.name
};
}
function createDefaultAction(type, item = null) {
switch (type) {
case ACTION_TYPES.TELEPORT:
return {
@@ -309,7 +403,7 @@ function createDefaultAction(type) {
type: ACTION_TYPES.CAST_SPELL_FROM_TOKEN,
enabled: true,
casterMode: "affectedToken",
spellSelection: {
spellSelection: item ? buildSpellSelection(item) : {
mode: "itemName",
itemName: ""
},