From 8f015bc05c76e635ea449273dcd4c486aa745d55 Mon Sep 17 00:00:00 2001 From: Mike Bloy Date: Mon, 13 May 2024 19:56:38 -0500 Subject: [PATCH] add resist roll to banish --- src/module/helpers.js | 125 ++++++++-------- src/module/powers/banish.js | 22 +++ src/module/rollHelpers.js | 260 +++++++++++++++++---------------- src/templates/powerDialog.html | 10 +- 4 files changed, 229 insertions(+), 188 deletions(-) diff --git a/src/module/helpers.js b/src/module/helpers.js index d84b497..6b861f2 100644 --- a/src/module/helpers.js +++ b/src/module/helpers.js @@ -1,27 +1,25 @@ /* global warpgate */ -import { moduleHelpers } from './globals.js' +import { moduleHelpers } from './globals.js'; -export async function requestFearRollFromTokens (tokens, options = {}) { +export async function requestFearRollFromTokens(tokens, options = {}) { // tokens: list of tokens to request the roll from // options: // title: tile for the roll dialog. Will have "- {{ token name }}" appended // flavour: flavor text for the roll card. Defaults to title // fear: value of the fear modifier. Defaults to 0. Positive number. - const requestingUser = game.user - const title = options?.title || `${requestingUser.name} requests a Fear check` - const flavour = options?.flavour || options?.flavor || title - const fear = options.fear || 0 + const requestingUser = game.user; + const title = options?.title || `${requestingUser.name} requests a Fear check`; + const flavour = options?.flavour || options?.flavor || title; + const fear = options.fear || 0; const rollOpts = { title, flavour, - mods: [ - { label: 'Fear Penalty', value: Math.abs(fear) * -1, ignore: false } - ] - } - return requestRollFromTokens(tokens, 'attribute', 'spirit', rollOpts) + mods: [{ label: 'Fear Penalty', value: Math.abs(fear) * -1, ignore: false }], + }; + return requestRollFromTokens(tokens, 'attribute', 'spirit', rollOpts); } -export async function requestRollFromTokens (tokens, rollType, rollDesc, options = {}) { +export async function requestRollFromTokens(tokens, rollType, rollDesc, options = {}) { // tokens: list of tokens to request a roll from // rollType: 'attribute' or 'skill // rollDesc: name of attribute or skill @@ -33,91 +31,98 @@ export async function requestRollFromTokens (tokens, rollType, rollDesc, options // mods: list of modifiers {label: "", value: 0, ignore: false} // modCallback: callback function that takes a token and returns a list of // modifiers in the same format as modifiers, above - const requestingUser = game.user - const title = options?.title || `${requestingUser.name} requests a ${rollDesc} roll` - const flavour = options?.flavour || options?.flavor || title - const targetNumber = options?.targetNumber || 4 - const promises = [] + const requestingUser = game.user; + const title = options?.title || `${requestingUser.name} requests a ${rollDesc} roll`; + const flavour = options?.flavour || options?.flavor || title; + const targetNumber = options?.targetNumber || 4; + const promises = []; for (const token of tokens) { - const owner = warpgate.util.firstOwner(token.document) + const owner = warpgate.util.firstOwner(token.document); const rollOpts = { title: `${title} - ${token.name}`, targetNumber, - flavour - } - const additionalMods = [] + flavour, + }; + const additionalMods = []; if ('mods' in options) { for (const mod of options.mods) { - additionalMods.push(mod) + additionalMods.push(mod); } } if ('modCallback' in options) { - const tokenMods = await options.modCallback(token) + const tokenMods = await options.modCallback(token); for (const tm of tokenMods) { - additionalMods.push(tm) + additionalMods.push(tm); } } if (additionalMods.length > 0) { - rollOpts.additionalMods = additionalMods + rollOpts.additionalMods = additionalMods; } - promises.push(moduleHelpers.socket.executeAsUser(requestTokenRoll, owner.id, - token.scene.id, token.id, rollType, rollDesc, rollOpts)) + promises.push( + moduleHelpers.socket.executeAsUser( + requestTokenRoll, + owner.id, + token.scene.id, + token.id, + rollType, + rollDesc, + rollOpts, + ), + ); } - const results = (await Promise.allSettled(promises)).map(r => r.value) - const contentExtra = targetNumber === 4 ? '' : ` vs TN: ${targetNumber}` + const results = (await Promise.allSettled(promises)).map((r) => r.value); + const contentExtra = targetNumber === 4 ? '' : ` vs TN: ${targetNumber}`; const messageData = { flavor: flavour, speaker: { alias: 'Requested Roll Results' }, whisper: [...ChatMessage.getWhisperRecipients('GM'), requestingUser], content: `

Results of ${rollDesc[0].toUpperCase()}${rollDesc.slice(1)} roll${contentExtra}:

`, - rolls: [] - } + rolls: [], + }; for (const result of results) { - const token = game.scenes.get(result.sceneId).tokens.get(result.tokenId) - const roll = ( + const token = game.scenes.get(result.sceneId).tokens.get(result.tokenId); + const roll = result.result instanceof CONFIG.Dice.SwadeRoll ? result.result - : CONFIG.Dice[result.result.class].fromData(result.result) - ) - roll.targetNumber = targetNumber - let textResult = '' + : CONFIG.Dice[result.result.class].fromData(result.result); + roll.targetNumber = targetNumber; + let textResult = ''; if (roll.successes === -1) { - textResult = 'CRITICAL FAILURE' + textResult = 'CRITICAL FAILURE'; } else if (roll.successes === 0) { - textResult = 'failed' + textResult = 'failed'; } else if (roll.successes === 1) { - textResult = 'success' + textResult = 'success'; } else { - textResult = `success and ${roll.successes - 1} raise${roll.successes > 2 ? 's' : ''}` + textResult = `success and ${roll.successes - 1} raise${roll.successes > 2 ? 's' : ''}`; } - messageData.content += ('' + + messageData.content += + '' + `` + `` + `` + - '') + ''; if (roll) { - messageData.rolls.unshift(roll) + messageData.rolls.unshift(roll); } } - messageData.content += '
TokenRollResult
${token.name}${roll ? roll.total : 'Canceled'}${textResult}
' - ChatMessage.create(messageData, {}) - return results + messageData.content += ''; + ChatMessage.create(messageData, {}); + return results; } -export async function requestTokenRoll ( - sceneId, tokenId, rollType, rollDesc, options -) { - const scene = game.scenes.get(sceneId) - const token = scene.tokens.get(tokenId) - let rollFunc = 'rollAttribute' - let rollId = rollDesc.toLowerCase() +export async function requestTokenRoll(sceneId, tokenId, rollType, rollDesc, options) { + const scene = game.scenes.get(sceneId); + const token = scene.tokens.get(tokenId); + let rollFunc = 'rollAttribute'; + let rollId = rollDesc.toLowerCase(); if (rollType === 'skill') { - rollFunc = 'rollSkill' - rollId = token.actor.items.filter(i => i.type === 'skill').find(i => ( - i.system.swid === rollDesc.toLowerCase() || - i.name.toLowerCase() === rollDesc.toLowerCase()))?.id + rollFunc = 'rollSkill'; + rollId = token.actor.items + .filter((i) => i.type === 'skill') + .find((i) => i.system.swid === rollDesc.toLowerCase() || i.name.toLowerCase() === rollDesc.toLowerCase())?.id; } - const result = await token.actor[rollFunc](rollId, options) - return { sceneId, tokenId, result } + const result = await token.actor[rollFunc](rollId, options); + return { sceneId, tokenId, result }; } diff --git a/src/module/powers/banish.js b/src/module/powers/banish.js index 6477899..6b7688f 100644 --- a/src/module/powers/banish.js +++ b/src/module/powers/banish.js @@ -1,4 +1,6 @@ import { PowerEffect } from './basePowers.js'; +import { requestRollFromTokens } from '../helpers.js'; +import { getPowerModifiers } from '../rollHelpers.js'; export class BanishEffect extends PowerEffect { get name() { @@ -31,6 +33,15 @@ export class BanishEffect extends PowerEffect { get modifiers() { const mods = super.modifiers; + mods.push({ + type: 'number', + default: 4, + name: 'Opposed Target Number', + id: 'tn', + epic: false, + effect: false, + value: 0, + }); mods.push({ type: 'select', default: 'none', @@ -49,6 +60,17 @@ export class BanishEffect extends PowerEffect { return mods; } + async sideEffects() { + await super.sideEffects(); + const rollOpts = { + title: 'Spirit roll to resist banishment', + flavor: 'Roll Spirit to resist banishment!', + modCallback: getPowerModifiers, + targetNumber: this.data.tn, + }; + await requestRollFromTokens(this.targets, 'ability', 'spirit', rollOpts); + } + get description() { return ( super.description + diff --git a/src/module/rollHelpers.js b/src/module/rollHelpers.js index 6afcfc4..4b82c90 100644 --- a/src/module/rollHelpers.js +++ b/src/module/rollHelpers.js @@ -1,60 +1,64 @@ -import { log } from './globals.js' +import { log } from './globals.js'; -export async function preTraitRollModifiers (actor, trait, roll, modifiers, options) { - const targets = Array.from(game.user.targets) - const token = game.canvas.tokens.controlled.length > 0 ? game.canvas.tokens.controlled[0] : null - if (targets.some(target => target.actor.system.status.isVulnerable)) { - modifiers.push({ label: 'Target is Vulnerable', value: '+2', ignore: false }) +export async function preTraitRollModifiers(actor, trait, roll, modifiers, options) { + const targets = Array.from(game.user.targets); + const token = game.canvas.tokens.controlled.length > 0 ? game.canvas.tokens.controlled[0] : null; + if (targets.some((target) => target.actor.system.status.isVulnerable)) { + modifiers.push({ label: 'Target is Vulnerable', value: '+2', ignore: false }); } - if (targets.some( - target => target.actor.effects.filter( - e => !e.disabled && e.name.toLowerCase().includes('deflection')).length > 0) + if ( + targets.some( + (target) => + target.actor.effects.filter((e) => !e.disabled && e.name.toLowerCase().includes('deflection')).length > 0, + ) ) { - modifiers.push({ label: 'Target has Deflection', value: '-2', ignore: false }) + modifiers.push({ label: 'Target has Deflection', value: '-2', ignore: false }); } - if (targets.some( - target => target.actor.effects.filter( - e => !e.disabled && e.name.toLowerCase().includes('glow')).length > 0) + if ( + targets.some( + (target) => target.actor.effects.filter((e) => !e.disabled && e.name.toLowerCase().includes('glow')).length > 0, + ) ) { modifiers.push({ label: 'Glowing target (negate 1 point of illumination penalty)', value: '+1', - ignore: true - }) + ignore: true, + }); } - if (targets.some( - target => target.actor.effects.filter( - e => !e.disabled && e.name.toLowerCase().includes('shroud')).length > 0) + if ( + targets.some( + (target) => target.actor.effects.filter((e) => !e.disabled && e.name.toLowerCase().includes('shroud')).length > 0, + ) ) { modifiers.push({ label: 'Shrouded target', value: '-1', - ignore: false - }) + ignore: false, + }); } if (targets.length === 1 && token) { - const target = targets[0] - _addArcaneModifiers(target, modifiers) - _addRangeModifiers(token, target, options, modifiers) - const scaleMod = calcScaleMod(token, target) + const target = targets[0]; + _addArcaneModifiers(target, modifiers); + _addRangeModifiers(token, target, options, modifiers); + const scaleMod = calcScaleMod(token, target); if (scaleMod !== 0) { - modifiers.push({ label: 'Scale', value: scaleMod, ignore: false }) + modifiers.push({ label: 'Scale', value: scaleMod, ignore: false }); } - if (target.actor.items.find(e => e.type === 'edge' && e.system.swid === 'dodge')) { - modifiers.push({ label: 'Dodge', value: -2, ignore: true }) + if (target.actor.items.find((e) => e.type === 'edge' && e.system.swid === 'dodge')) { + modifiers.push({ label: 'Dodge', value: -2, ignore: true }); } if (trait?.type === 'skill' && trait?.system?.swid === 'fighting') { - const gangUpBonus = calcGangup(token, target) + const gangUpBonus = calcGangup(token, target); if (gangUpBonus > 0) { - modifiers.push({ label: 'Gang Up', value: gangUpBonus, ignore: false }) + modifiers.push({ label: 'Gang Up', value: gangUpBonus, ignore: false }); } } } } -export async function preDamageRollModifiers (actor, item, roll, modifiers, options) { - const targets = Array.from(game.user.targets) - const token = game.canvas.tokens.controlled.length > 0 ? game.canvas.tokens.controlled[0] : null +export async function preDamageRollModifiers(actor, item, roll, modifiers, options) { + const targets = Array.from(game.user.targets); + const token = game.canvas.tokens.controlled.length > 0 ? game.canvas.tokens.controlled[0] : null; // log('ACTOR', actor) // log('TOKEN', token) // log('ITEM', item) @@ -63,148 +67,150 @@ export async function preDamageRollModifiers (actor, item, roll, modifiers, opti // log('OPTIONS', options) // log('TARGET', targets) if (targets.length === 1 && token) { - const target = targets[0] - _addArcaneModifiers(target, modifiers) + const target = targets[0]; + _addArcaneModifiers(target, modifiers); const weaknesses = target.actor.items.filter( - i => i.type === 'ability' && i.system.swid.toLowerCase().includes('environmental-weakness')) + (i) => i.type === 'ability' && i.system.swid.toLowerCase().includes('environmental-weakness'), + ); if (weaknesses.length > 0) { - modifiers.push(...weaknesses.map(i => { return { label: i.name, value: '+4', ignore: true } })) + modifiers.push( + ...weaknesses.map((i) => { + return { label: i.name, value: '+4', ignore: true }; + }), + ); } const resistances = target.actor.items.filter( - i => i.type === 'ability' && i.system.swid.toLowerCase().includes('environmental-resistance')) + (i) => i.type === 'ability' && i.system.swid.toLowerCase().includes('environmental-resistance'), + ); if (resistances.length > 0) { - modifiers.push(...resistances.map(i => { return { label: i.name, value: '-4', ignore: true } })) + modifiers.push( + ...resistances.map((i) => { + return { label: i.name, value: '-4', ignore: true }; + }), + ); } if (_findItem(token.actor, 'ability', 'pack-tactics')) { - const gangupBonus = calcGangup(token, target) + const gangupBonus = calcGangup(token, target); if (gangupBonus > 0) { - modifiers.push({ label: 'Gang Up (Pack Tactics)', value: gangupBonus, ignore: false }) + modifiers.push({ label: 'Gang Up (Pack Tactics)', value: gangupBonus, ignore: false }); } } } } -function _addRangeModifiers (token, target, options, modifiers) { +export async function getPowerModifiers(token) { + const modifiers = []; + _addArcaneModifiers(token, modifiers); + return modifiers; +} + +function _addRangeModifiers(token, target, options, modifiers) { if (options?.item?.type !== 'weapon' || !options?.item?.system?.range.includes('/')) { - return + return; } - const ranges = options.item.system.range.split('/').map(x => parseInt(x)) - const distance = getDistance(token, target) - const rollmods = CONFIG.SWADE.prototypeRollGroups.find(g => g.name === 'Range').modifiers - log('ITEM RANGES:', ranges) + const ranges = options.item.system.range.split('/').map((x) => parseInt(x)); + const distance = getDistance(token, target); + const rollmods = CONFIG.SWADE.prototypeRollGroups.find((g) => g.name === 'Range').modifiers; + log('ITEM RANGES:', ranges); if (distance <= ranges[0]) { // nothing here } else if (ranges.length >= 2 && distance <= ranges[1]) { - modifiers.push(rollmods[0]) + modifiers.push(rollmods[0]); } else if (ranges.length >= 3 && distance <= ranges[2]) { - modifiers.push(rollmods[1]) + modifiers.push(rollmods[1]); } else { - modifiers.push(rollmods[2]) // extreme range + modifiers.push(rollmods[2]); // extreme range } } -function _addArcaneModifiers (target, modifiers) { +function _addArcaneModifiers(target, modifiers) { if (_findItem(target.actor, 'edge', 'improved-arcane-resistance')) { - modifiers.push({ label: 'Arcane Resistance', value: '-4', ignore: true }) + modifiers.push({ label: 'Arcane Resistance', value: '-4', ignore: true }); } else if (_findItem(target.actor, 'edge', 'arcane-resistance')) { - modifiers.push({ label: 'Arcane Resistance', value: '-2', ignore: true }) + modifiers.push({ label: 'Arcane Resistance', value: '-2', ignore: true }); } - const effect = target.actor.effects.find( - e => !e.disabled && e.name.toLowerCase().includes('arcane protection')) + const effect = target.actor.effects.find((e) => !e.disabled && e.name.toLowerCase().includes('arcane protection')); if (effect) { - const effectName = effect.name.toLowerCase() - const effectMod = ( - -2 + - (effectName.includes('major') ? -2 : 0) + - (effectName.includes('greater') ? -2 : 0) - ) - modifiers.push({ label: 'Target Arcane Protection', value: effectMod, ignore: true }) + const effectName = effect.name.toLowerCase(); + const effectMod = -2 + (effectName.includes('major') ? -2 : 0) + (effectName.includes('greater') ? -2 : 0); + modifiers.push({ label: 'Target Arcane Protection', value: effectMod, ignore: true }); } } -function getScaleDistanceMod (token) { - const scale = token.actor.system.stats.scale - return (scale > 0 ? (scale / 2) : 0) +function getScaleDistanceMod(token) { + const scale = token.actor.system.stats.scale; + return scale > 0 ? scale / 2 : 0; } -function getDistance (origin, target) { - const ray = new Ray(origin, target) - const originScale = getScaleDistanceMod(origin) - const targetScale = getScaleDistanceMod(target) - const flatDistance = game.canvas.grid.measureDistances( - [{ ray }], { gridSpaces: true })[0] - const elevation = Math.abs(origin.document.elevation - target.document.elevation) - const distance = Math.sqrt(elevation * elevation + flatDistance * flatDistance) - return distance - (originScale + targetScale) +function getDistance(origin, target) { + const ray = new Ray(origin, target); + const originScale = getScaleDistanceMod(origin); + const targetScale = getScaleDistanceMod(target); + const flatDistance = game.canvas.grid.measureDistances([{ ray }], { gridSpaces: true })[0]; + const elevation = Math.abs(origin.document.elevation - target.document.elevation); + const distance = Math.sqrt(elevation * elevation + flatDistance * flatDistance); + return distance - (originScale + targetScale); } -function withinRange (origin, target, range) { - const distance = getDistance(origin, target) - return range >= distance +function withinRange(origin, target, range) { + const distance = getDistance(origin, target); + return range >= distance; } -function _findItem (actor, type, swid) { - return actor.items.find(i => i.type === type && i.system.swid === swid) +function _findItem(actor, type, swid) { + return actor.items.find((i) => i.type === type && i.system.swid === swid); } -function calcScaleMod (attacker, target) { - const attackerScale = attacker.actor.system.stats.scale - const targetScale = target.actor.system.stats.scale - const attackerHasSwat = !!_findItem(attacker.actor, 'ability', 'swat') - let modifier = targetScale - attackerScale +function calcScaleMod(attacker, target) { + const attackerScale = attacker.actor.system.stats.scale; + const targetScale = target.actor.system.stats.scale; + const attackerHasSwat = !!_findItem(attacker.actor, 'ability', 'swat'); + let modifier = targetScale - attackerScale; if (attackerHasSwat && modifier < 0) { - modifier = Math.min(modifier + 4, 0) + modifier = Math.min(modifier + 4, 0); } - return modifier + return modifier; } -function calcGangup (attacker, target, debug) { - debug = (typeof debug === 'undefined') ? false : debug - const range = 1.2 - let modifier = 0 +function calcGangup(attacker, target, debug) { + debug = typeof debug === 'undefined' ? false : debug; + const range = 1.2; + let modifier = 0; if (_findItem(target.actor, 'edge', 'improved-block')) { - modifier = -2 + modifier = -2; } else if (_findItem(target.actor, 'edge', 'block')) { - modifier = -1 + modifier = -1; } - const attackerHasFormationFighter = !!(_findItem(attacker.actor, 'edge', 'formation-fighter')) + const attackerHasFormationFighter = !!_findItem(attacker.actor, 'edge', 'formation-fighter'); - const withinRangeOfToken = game.canvas.tokens.placeables.filter(t => - t.id !== attacker.id && - t.id !== target.id && - t.actor.system.status.isStunned === false && - t.visible && - withinRange(target, t, range) && - !(t.actor.effects.find(c => c.name === 'Incapacitated' || c.name === 'Defeated')) - ) - const attackerAllies = withinRangeOfToken.filter( - t => t.document.disposition === attacker.document.disposition) + const withinRangeOfToken = game.canvas.tokens.placeables.filter( + (t) => + t.id !== attacker.id && + t.id !== target.id && + t.actor.system.status.isStunned === false && + t.visible && + withinRange(target, t, range) && + !t.actor.effects.find((c) => c.name === 'Incapacitated' || c.name === 'Defeated'), + ); + const attackerAllies = withinRangeOfToken.filter((t) => t.document.disposition === attacker.document.disposition); const targetAllies = withinRangeOfToken.filter( - t => t.document.disposition === target.document.disposition && - withinRange(attacker, t, range) - ) - const attackersWithFormationFighter = attackerAllies.filter( - t => !!_findItem(t.actor, 'edge', 'formation-fighter')) - const attackerCount = attackerAllies.length - const attackerFormationBonus = ( - (attackerCount > 0 && attackerHasFormationFighter ? 1 : 0) + - attackersWithFormationFighter.length - ) - const defenderCount = targetAllies.length - const gangUp = Math.max( - 0, - Math.min( - 4, - attackerCount + attackerFormationBonus - defenderCount + modifier)) + (t) => t.document.disposition === target.document.disposition && withinRange(attacker, t, range), + ); + const attackersWithFormationFighter = attackerAllies.filter((t) => !!_findItem(t.actor, 'edge', 'formation-fighter')); + const attackerCount = attackerAllies.length; + const attackerFormationBonus = + (attackerCount > 0 && attackerHasFormationFighter ? 1 : 0) + attackersWithFormationFighter.length; + const defenderCount = targetAllies.length; + const gangUp = Math.max(0, Math.min(4, attackerCount + attackerFormationBonus - defenderCount + modifier)); if (debug) { - log('GANG UP | Attacker:', attacker) - log('GANG UP | Target:', target) - log('GANG UP | Others within range:', withinRangeOfToken) - log('GANG UP | Attacker Allies:', attackerCount) - log('GANG UP | Attacker Formation Bonus:', attackerFormationBonus) - log('GANG UP | Effective Defender Allies:', defenderCount) - log('GANG UP | Target Block Modifier:', modifier) - log('GANG UP | Total Bonus:', gangUp) + log('GANG UP | Attacker:', attacker); + log('GANG UP | Target:', target); + log('GANG UP | Others within range:', withinRangeOfToken); + log('GANG UP | Attacker Allies:', attackerCount); + log('GANG UP | Attacker Formation Bonus:', attackerFormationBonus); + log('GANG UP | Effective Defender Allies:', defenderCount); + log('GANG UP | Target Block Modifier:', modifier); + log('GANG UP | Total Bonus:', gangUp); } - return gangUp + return gangUp; } diff --git a/src/templates/powerDialog.html b/src/templates/powerDialog.html index 85bf557..6a1a6eb 100644 --- a/src/templates/powerDialog.html +++ b/src/templates/powerDialog.html @@ -26,12 +26,20 @@ {{#if epic}}⭐ {{/if}}{{name}} ({{numberFormat value decimals=0 sign=true}}) {{/if}} + {{#if isText}} + + + {{/if}} + {{#if isNumber}} + + + {{/if}} {{#if isSelect}} {{/if}} {{#if isRadio}} - {{radioBoxes id choices checked=default}} + {{#if epic}}⭐ {{/if}}{{name}}: {{radioBoxes id choices checked=default}} {{/if}} {{/each}}