Compare commits

...

6 Commits
v4.2.3 ... main

6 changed files with 290 additions and 16 deletions

View File

@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [4.3.0]
### Added
- `@SWADEDamage` and `@SWADERequestRoll` text enrichers.
## [4.2.4]
### Changed
- Changed how summon powers spawn multiple tokens.
## [4.2.3] ## [4.2.3]
### Changed ### Changed

View File

@ -9,7 +9,7 @@
} }
], ],
"url": "https://git.bloy.org/foundryvtt/swade-mb-helpers", "url": "https://git.bloy.org/foundryvtt/swade-mb-helpers",
"version": "4.2.3", "version": "4.3.0",
"compatibility": { "compatibility": {
"minimum": "13", "minimum": "13",
"verified": "13" "verified": "13"

153
src/module/enrichers.js Normal file
View File

@ -0,0 +1,153 @@
import { requestRollFromTokens } from './helpers.js';
const enrichers = [
{
id: 'mb-swade-damage',
pattern: /@SWADEDamage\[\s*(?<roll>[\d+-dx]+)\s*\](?:\(\s*(?<ap>\d+)\s*\))?(?:\{(?<flavor>[^}]+)\})?/g,
enricher: swadeDamageEnricher,
},
{
id: 'mb-swade-trait',
pattern:
/@SWADERequestRoll\[\s*(?<traitType>skill|attribute),(?<traitName>[\w\s-]+)(?:,(?<tn>\d+))?\s*\](?:\(\s*(?<modVal>[-+]?\d+)(?:,(?<modDesc>[^)]+))?\s*\))?(?:\{(?<flavor>[^}]+)\})?/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 = '<strong>Damage</strong>';
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 = `<i class="fas fa-heart-crack"></i> ${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 = `<strong>Request Roll:</strong> ${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 = `<i class="fas fa-question"></i> <i class="fas fa-dice"></i> ${text}`;
return anchor;
}
function onSwadeRollLink(ev) {
console.log('SWADE ROLL', ev);
const targetElement = ev.target.closest('a.mb-swade-roll-link');
if (!targetElement) 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 ?? '4');
const modVal = parseInt(targetElement.dataset.modVal ?? '0');
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 ?? '0');
let flavor = targetElement.dataset.flavor ?? '';
const options = {};
if (ap > 0) {
flavor = `${flavor ? flavor + ' - ' : ''}AP: ${ap}`;
options.ap = ap;
}
console.log('DamageRollLink', roll, ap, flavor, options);
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));
});
}

View File

@ -45,25 +45,28 @@ export class BaseSummonEffect extends ActorFolderEffect {
} }
async spawn() { async spawn() {
const spawned = [];
for (let i = 0; i < this.summonCount; i++) {
let label = this.targetTokenDoc.name;
if (this.summonCount > 1) {
label = `${this.targetTokenDoc.name} #${i + 1}`;
}
const location = await Sequencer.Crosshair.show({ const location = await Sequencer.Crosshair.show({
distance: this.targetTokenDoc.height / 2, distance: this.targetTokenDoc.height / 2,
texture: this.targetTokenDoc.texture.src, texture: this.targetTokenDoc.texture.src,
snap: { snap: {
position: CONST.GRID_SNAPPING_MODES.VERTEX | CONST.GRID_SNAPPING_MODES.CENTER, position: CONST.GRID_SNAPPING_MODES.VERTEX | CONST.GRID_SNAPPING_MODES.CENTER,
}, },
label: { text: this.targetTokenDoc.name }, label: { text: label },
}); });
const tokenDocs = []; const tokenDoc = this.targetTokenDoc.clone({
for (let i = 0; i < this.summonCount; i++) { x: location.token.x,
tokenDocs[i] = this.targetTokenDoc.clone({ y: location.token.y,
x: location.token.x + i * 5,
y: location.token.y + i * 5,
elevation: this.source.elevation, elevation: this.source.elevation,
}); });
const spawn = await this.source.scene.createEmbeddedDocuments('Token', [tokenDoc]);
spawned.push(...spawn);
} }
log('token docs', tokenDocs);
const spawned = await this.source.scene.createEmbeddedDocuments('Token', tokenDocs);
log('Spawned', spawned);
return spawned; return spawned;
} }

View File

@ -1,6 +1,7 @@
/* globals socketlib */ /* globals socketlib */
// Import JavaScript modules // Import JavaScript modules
import { registerSettings } from './settings.js'; import { registerSettings } from './settings.js';
import { setupEnrichers } from './enrichers.js';
import { preloadTemplates } from './preloadTemplates.js'; import { preloadTemplates } from './preloadTemplates.js';
import { api } from './api.js'; import { api } from './api.js';
import { initVisionModes } from './visionModes.js'; import { initVisionModes } from './visionModes.js';
@ -32,6 +33,7 @@ Hooks.once('init', async () => {
}); });
// Setup module // Setup module
setupEnrichers();
Hooks.once('setup', async () => { Hooks.once('setup', async () => {
api.registerFunctions(); api.registerFunctions();
// Register custom module settings // Register custom module settings

File diff suppressed because one or more lines are too long