diff --git a/scripts/apps/reaction-config-app.js b/scripts/apps/reaction-config-app.js index 732e8dd..3e5a67d 100644 --- a/scripts/apps/reaction-config-app.js +++ b/scripts/apps/reaction-config-app.js @@ -236,7 +236,6 @@ export class ConfigurableReactionsConfigApp extends HandlebarsApplicationMixin(A if (dropZone.dataset.crDrop === "action-spell" && isSpellItem(droppedItem)) { await this.#setActionSpell(Number(dropZone.dataset.actionIndex), droppedItem); - return; } }); } @@ -409,6 +408,7 @@ export class ConfigurableReactionsConfigApp extends HandlebarsApplicationMixin(A try { reaction = JSON.parse(textarea.value); } catch (error) { + console.warn(`${MODULE_ID} | Invalid reaction JSON`, error); ui.notifications.error(game.i18n.localize("CONFIGURABLE_REACTIONS.Errors.InvalidJson")); return false; } diff --git a/scripts/engine/reaction-engine.js b/scripts/engine/reaction-engine.js index 690b475..0e3e137 100644 --- a/scripts/engine/reaction-engine.js +++ b/scripts/engine/reaction-engine.js @@ -10,21 +10,30 @@ export async function handleTrigger(triggerType, context) { const assignedReactions = await getAssignedReactionsForContext(context); for (const reaction of assignedReactions) { - try { - if (!reaction?.enabled) continue; - if (reaction.trigger?.type !== triggerType) continue; - if (!matchesTriggerSpellFilter(reaction.trigger, context)) continue; - if (!await checkConsumptionAvailable(reaction, context)) continue; - if (!await checkConditions(reaction, context)) continue; - - const result = await executeReactionActions(reaction, context); - if (result.shouldConsume) await consumeReactionUse(reaction, context); - } catch (error) { - console.error(`${MODULE_ID} | Failed to handle reaction`, reaction, error); - } + await handleAssignedReaction(triggerType, context, reaction); } } +async function handleAssignedReaction(triggerType, context, reaction) { + try { + if (!await shouldExecuteReaction(triggerType, context, reaction)) return; + + const result = await executeReactionActions(reaction, context); + if (result.shouldConsume) await consumeReactionUse(reaction, context); + } catch (error) { + console.error(`${MODULE_ID} | Failed to handle reaction`, reaction, error); + } +} + +async function shouldExecuteReaction(triggerType, context, reaction) { + if (!reaction?.enabled) return false; + if (reaction.trigger?.type !== triggerType) return false; + if (!matchesTriggerSpellFilter(reaction.trigger, context)) return false; + if (!await checkConsumptionAvailable(reaction, context)) return false; + + return checkConditions(reaction, context); +} + function matchesTriggerSpellFilter(trigger, context) { const expectedName = trigger?.spell?.itemName; if (!expectedName) return true; diff --git a/scripts/utils/distance-utils.js b/scripts/utils/distance-utils.js index 7dda205..fcb0d8d 100644 --- a/scripts/utils/distance-utils.js +++ b/scripts/utils/distance-utils.js @@ -1,6 +1,6 @@ export function randomPointInCircle(origin, radiusPx) { - const angle = Math.random() * Math.PI * 2; - const distance = Math.sqrt(Math.random()) * radiusPx; + const angle = randomUnitFloat() * Math.PI * 2; + const distance = Math.sqrt(randomUnitFloat()) * radiusPx; return { x: origin.x + Math.cos(angle) * distance, @@ -19,3 +19,14 @@ export function sceneDistanceToPixels(distance) { const distancePerGrid = canvas.scene.grid.distance || 5; return distance / distancePerGrid * gridSize; } + +function randomUnitFloat() { + const cryptoApi = globalThis.crypto; + if (!cryptoApi?.getRandomValues) { + throw new Error("A cryptographically secure random generator is not available."); + } + + const values = new Uint32Array(1); + cryptoApi.getRandomValues(values); + return values[0] / 0x100000000; +} diff --git a/styles/configurable-reactions.css b/styles/configurable-reactions.css index a5f3fc4..f1d3e18 100644 --- a/styles/configurable-reactions.css +++ b/styles/configurable-reactions.css @@ -167,11 +167,24 @@ .configurable-reactions .cr-json-editor { flex: 1 1 auto; + display: flex; + flex-direction: column; + gap: 0.35rem; min-height: 0; } +.configurable-reactions .cr-json-editor-heading, +.configurable-reactions .cr-json-editor-heading label { + display: block; +} + +.configurable-reactions .cr-json-editor-heading { + flex: 0 0 auto; +} + .configurable-reactions .cr-main textarea[name="reactionJson"] { width: 100%; + flex: 1 1 auto; height: 100%; min-height: 180px; font-family: var(--font-monospace, monospace); @@ -239,7 +252,7 @@ gap: 0.3rem; max-width: 100%; padding: 0.12rem 0.2rem 0.12rem 0.35rem; - border: 1px solid var(--color-border-light-secondary, #777); + border: none; border-radius: 999px; background: rgb(0 0 0 / 0.16); font-size: 70%; diff --git a/templates/reaction-config.hbs b/templates/reaction-config.hbs index f78d9ec..4cc7089 100644 --- a/templates/reaction-config.hbs +++ b/templates/reaction-config.hbs @@ -199,9 +199,11 @@ -
{{localize "CONFIGURABLE_REACTIONS.Reactions.None"}}