From 65803287468f67b39dc5c9d3c323732da7b67588 Mon Sep 17 00:00:00 2001 From: Mike Bloy Date: Sun, 26 Apr 2026 21:33:28 -0500 Subject: [PATCH] add request trait and damage roll enrichers --- src/module/enrichers.js | 152 +++++++++++++++++++++++++++++++++ src/module/swade-mb-helpers.js | 2 + 2 files changed, 154 insertions(+) create mode 100644 src/module/enrichers.js diff --git a/src/module/enrichers.js b/src/module/enrichers.js new file mode 100644 index 0000000..2379351 --- /dev/null +++ b/src/module/enrichers.js @@ -0,0 +1,152 @@ +import { requestRollFromTokens } from './helpers.js'; + +const enrichers = [ + { + id: 'mb-swade-damage', + pattern: /@SWADEDamage\[\s*(?[\d+-dx]+)\s*\](?:\(\s*(?\d+)\s*\))?(?:\{(?[^}]+)\})?/g, + enricher: swadeDamageEnricher, + }, + { + id: 'mb-swade-trait', + pattern: + /@SWADERequestRoll\[\s*(?skill|attribute),(?[\w\s-]+)(?:,(?\d+))?\s*\](?:\(\s*(?[-+]?\d+)(?:,(?[^)]+))?\s*\))?(?:\{(?[^}]+)\})?/g, + enricher: swadeTraitRollEnricher, + }, +]; + +function swadeDamageEnricher(match, options) { + if (!match.groups) { + return null; + } + const roll = match.groups.roll; + const ap = parseInt(match.groups.ap ?? '0'); + const flavor = match.groups.flavor ?? ''; + const dataset = { roll, ap, flavor, rollType: 'damage' }; + let text = 'Damage'; + if (flavor) { + text += ` ${flavor}`; + } + text += `: ${roll}`; + if (ap) { + text += ` AP ${ap}`; + } + if (!game.user.isGM) { + const span = document.createElement('span'); + span.innerHTML = text; + return span; + } + const anchor = document.createElement('a'); + const classes = ['content-link', 'mb-swade-roll-damage-link', 'mb-swade-roll-link']; + for (const cls of classes) { + anchor.classList.add(cls); + } + for (let [k, v] of Object.entries(dataset)) { + anchor.dataset[k] = v; + } + anchor.innerHTML = ` ${text}`; + return anchor; +} + +function swadeTraitRollEnricher(match, options) { + if (!match.groups) { + return null; + } + const traitType = match.groups.traitType; + const traitName = match.groups.traitName; + if (!traitType || !traitName) { + return null; + } + const tn = parseInt(match.groups.tn ?? '4'); + const modVal = parseInt(match.groups.modVal ?? '0'); + const modDesc = match.groups.modDesc ?? 'Circumstance'; + const flavor = match.groups.flavor ?? ''; + const dataset = { traitType, traitName, tn, modVal, modDesc, flavor, rollType: 'trait' }; + const displayTraitName = (traitName[0].toUpperCase() + traitName.slice(1).toLowerCase()).replace(/[-_]/, ' '); + let text = `Request Roll: ${displayTraitName}`; + if (modVal != 0) { + text += ` ${modVal}`; + } + if (tn !== 4) { + text += ` TN: ${tn}`; + } + if (flavor) { + text += ` (${flavor})`; + } + const anchor = document.createElement('a'); + const classes = ['content-link', 'mb-swade-roll-request-link', 'mb-swade-roll-link']; + for (const cls of classes) { + anchor.classList.add(cls); + } + for (let [k, v] of Object.entries(dataset)) { + anchor.dataset[k] = v; + } + anchor.innerHTML = ` ${text}`; + return anchor; +} + +function onSwadeRollLink(ev) { + console.log('SWADE ROLL', ev); + const targetElement = ev.target; + if (!targetElement.closest('a.mb-swade-roll-link')) return; + ev.preventDefault(); + const rollType = targetElement.dataset.rollType; + switch (rollType) { + case 'trait': + return onRequestTraitRollLink(targetElement); + case 'damage': + return onDamageRollLink(targetElement); + } +} + +function onRequestTraitRollLink(targetElement) { + const tokens = game.user.targets.size > 0 ? Array.from(game.user.targets) : canvas.tokens.controlled; + console.log(tokens); + if (tokens.length < 1) { + foundry.ui.notifications.error('Please target or select some tokens.'); + return; + } + const { traitType, traitName, modDesc, flavor } = targetElement.dataset; + const tn = parseInt(targetElement.dataset.tn); + const modVal = parseInt(targetElement.dataset.modVal); + const options = { targetNumber: 4, flavor }; + if (tn != 4) { + options.targetNumber = tn; + } + if (modVal != 0) { + options.mods = [{ label: modDesc, value: modVal }]; + } + requestRollFromTokens(tokens, traitType, traitName, options); +} + +function onDamageRollLink(targetElement) { + const roll = targetElement.dataset.roll; + const ap = parseInt(targetElement.dataset.ap); + let flavor = targetElement.dataset.flavor; + const options = {}; + if (ap > 0) { + flavor = `${flavor ? flavor + ' - ' : ''}AP: ${ap}`; + options.ap = ap; + } + new CONFIG.Dice.DamageRoll(roll, null, options).toMessage({ flavor }); +} + +export function setupEnrichers() { + Hooks.once('ready', async () => { + for (const enricher of enrichers) { + CONFIG.TextEditor.enrichers.push(enricher); + } + }); + Hooks.on('renderApplicationV1', (application, html, data) => { + $(html) + .find('a.mb-swade-roll-link') + .each((i, el) => $(el).click(onSwadeRollLink)); + }); + + Hooks.on('renderApplicationV2', (application, element, context, options) => { + element.querySelectorAll('a.mb-swade-roll-link').forEach((el) => el.addEventListener('click', onSwadeRollLink)); + }); + + Hooks.on('renderChatMessageHTML', (message, html, context) => { + html.querySelectorAll('a.mb-swade-roll-link').forEach((el) => el.addEventListener('click', onSwadeRollLink)); + }); +} diff --git a/src/module/swade-mb-helpers.js b/src/module/swade-mb-helpers.js index f734063..be43f68 100644 --- a/src/module/swade-mb-helpers.js +++ b/src/module/swade-mb-helpers.js @@ -1,6 +1,7 @@ /* globals socketlib */ // Import JavaScript modules import { registerSettings } from './settings.js'; +import { setupEnrichers } from './enrichers.js'; import { preloadTemplates } from './preloadTemplates.js'; import { api } from './api.js'; import { initVisionModes } from './visionModes.js'; @@ -32,6 +33,7 @@ Hooks.once('init', async () => { }); // Setup module +setupEnrichers(); Hooks.once('setup', async () => { api.registerFunctions(); // Register custom module settings