From 0d41527a5ec2458889e9c196c011225503bade0c Mon Sep 17 00:00:00 2001 From: Mike Bloy Date: Sat, 18 May 2024 22:34:27 -0500 Subject: [PATCH] buttons and descriptions for VAR module, added elemental manipulation --- CHANGELOG.md | 4 + src/module/globals.js | 4 + src/module/powers/banish.js | 1 - src/module/powers/basePowers.js | 39 ++++++++- src/module/powers/elementalManipulation.js | 97 ++++++++++++++++++++++ src/module/powers/powers.js | 67 ++++++++++++++- src/module/swade-mb-helpers.js | 3 +- 7 files changed, 209 insertions(+), 6 deletions(-) create mode 100644 src/module/powers/elementalManipulation.js diff --git a/CHANGELOG.md b/CHANGELOG.md index ebc089c..d1d3ab5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [3.0.0] UNRELEASED +### Added + +- Optional Visual Active Effect integration for power descriptions + ### Changed - Refactor and redo of powers handling diff --git a/src/module/globals.js b/src/module/globals.js index 434734c..283eb00 100644 --- a/src/module/globals.js +++ b/src/module/globals.js @@ -23,6 +23,10 @@ export class moduleHelpers { return 'system'; } + static get useVAE() { + return !!game.modules.get('visual-active-effects')?.active; + } + static getActorFolderByPath(path) { const names = path.split('/'); if (names[0] === '') { diff --git a/src/module/powers/banish.js b/src/module/powers/banish.js index 6962c51..35e9ed2 100644 --- a/src/module/powers/banish.js +++ b/src/module/powers/banish.js @@ -1,6 +1,5 @@ import { PowerEffect } from './basePowers.js'; import { requestRollFromTokens } from '../helpers.js'; -import { getPowerModifiers } from '../rollHelpers.js'; export class BanishEffect extends PowerEffect { get name() { diff --git a/src/module/powers/basePowers.js b/src/module/powers/basePowers.js index 0b53184..d892bfb 100644 --- a/src/module/powers/basePowers.js +++ b/src/module/powers/basePowers.js @@ -426,11 +426,40 @@ export class PowerEffect { return ''; } + get primaryEffectButtons() { + // button objects should have a label and a type. + // type should have one of the following, with the associated additional + // fields: + // roll: + // formula: dice formula eg '3d6 + 3' + // flavor: flavor text (optional) + // trait: + // rollType: 'attribute' or 'skill + // rollDesc: name or swid of the attribute or skill + // flavor: flavor text (optional) + // mods: list of mods { label, value, ignore } + // damage: + // formula: dice formula for example '1d4x[Blades]' + // ap: optional, a positive integer or 0, armor piercing + // flavor: flavor text (optional) + // callback: + // callback: the function callback to run, takes a token as an argument + return []; + } + async createPrimaryEffect(maintId) { const doc = this.createEffectDocument(this.icon, this.effectName, this.getPrimaryEffectChanges()); - doc.description += this.description; + if (moduleHelpers.useVAE) { + doc.flags['visual-active-effects'] = { data: { content: this.description } }; + } else { + doc.description += this.description; + } doc.flags[moduleName].maintId = maintId; doc.duration.seconds = 594; + const effectButtons = this.primaryEffectButtons; + if (effectButtons.length > 0) { + doc.flags[moduleName].buttons = effectButtons; + } return doc; } @@ -441,7 +470,11 @@ export class PowerEffect { } const doc = this.createEffectDocument(icon, `Maintaining ${this.effectName}`, []); doc.duration.rounds = this.duration; - doc.description += this.description; + if (moduleHelpers.useVAE) { + doc.flags['visual-active-effects'] = { data: { content: this.description } }; + } else { + doc.description += this.description; + } doc.flags.swade.expiration = CONFIG.SWADE.CONST.STATUS_EFFECT_EXPIRATION.EndOfTurnPrompt; doc.flags.swade.loseTurnOnHold = true; doc.flags[moduleName].maintainingId = maintId; @@ -541,7 +574,7 @@ export class PowerEffect { if (this.isDamaging && this.data.ap > 0) { list.push(`AP ${this.data.ap}`); } - if (this.data.range != 'none') { + if (this.data.range ?? 'none' != 'none') { list.push(`Range ${this.data.range}`); } return list; diff --git a/src/module/powers/elementalManipulation.js b/src/module/powers/elementalManipulation.js new file mode 100644 index 0000000..86d6fac --- /dev/null +++ b/src/module/powers/elementalManipulation.js @@ -0,0 +1,97 @@ +import { PowerEffect } from './basePowers.js'; + +export class ElementalManipulationEffect extends PowerEffect { + get name() { + return 'Elemental Manipulation'; + } + + get icon() { + return 'icons/magic/earth/projectiles-stone-salvo-gray.webp'; + } + + get duration() { + return this.data?.weather ? 0 : 5; + } + + get isTargeted() { + return this.data?.weather ? false : true; + } + + get isRaisable() { + return true; + } + + get usePrimaryEffect() { + return this.data?.weather ? false : true; + } + + get oneTarget() { + return true; + } + + get basePowerPoints() { + return 5; + } + + get modifiers() { + return [ + { + name: 'Power', + type: 'checkbox', + value: 3, + id: 'power', + epic: true, + effect: false, + }, + { + name: 'Weather', + type: 'checkbox', + value: 5, + id: 'weather', + epic: true, + effect: false, + }, + ]; + } + + get primaryEffectButtons() { + const dmg = `${this.data.raise ? 3 : 2}d${this.data.power ? 6 : 4}`; + return [ + ...super.primaryEffectButtons, + { + type: 'damage', + label: `Damage (${dmg})`, + formula: `${dmg}x`, + }, + ]; + } + + get description() { + if (this.data.weather) { + return ( + super.description + + `Bring or disperse rain, snow, sun, and wind in about a five mile radius. + This takes 10 minutes and lasts an hour. + ` + ); + } + let damage = `${this.data.raise ? 3 : 2}d${this.data.power ? 6 : 4}x`; + return ( + super.description + + ` +

Use the activation roll for:

+ + ` + ); + } +} diff --git a/src/module/powers/powers.js b/src/module/powers/powers.js index f589f20..6947195 100644 --- a/src/module/powers/powers.js +++ b/src/module/powers/powers.js @@ -1,4 +1,4 @@ -import { moduleName, moduleHelpers } from '../globals.js'; +import { moduleName, moduleHelpers, log } from '../globals.js'; import { firstOwner, deleteActiveEffectsFromToken } from '../helpers.js'; import { ArcaneProtectionEffect } from './arcaneProtection.js'; import { BanishEffect } from './banish.js'; @@ -20,6 +20,7 @@ import { DisguiseEffect } from './disguise.js'; import { DispelEffect } from './dispel.js'; import { DivinationEffect } from './divination.js'; import { DrainPowerPointsEffect } from './drainPowerPoints.js'; +import { ElementalManipulationEffect } from './elementalManipulation.js'; const PowerClasses = { 'arcane-protection': ArcaneProtectionEffect, @@ -47,11 +48,75 @@ const PowerClasses = { dispel: DispelEffect, divination: DivinationEffect, 'drain-power-points': DrainPowerPointsEffect, + 'elemental-manipulation': ElementalManipulationEffect, 'lower-trait': BoostLowerTraitEffect, }; /* ---------------------------------------------------------------- */ +export function visualActiveEffectPowerButtons(effect, buttons) { + if (!effect.flags?.[moduleName]?.buttons) { + return; + } + for (const button of effect.flags[moduleName].buttons) { + let callback = null; + switch (button.type) { + case 'roll': + callback = function () { + new CONFIG.Dice.SwadeRoll(button.formula ?? '3d6', null, {}).toMessage({ + flavor: button.flavor ?? `${effect.name} roll`, + }); + }; + break; + case 'trait': + callback = function () { + let rollFunc = 'rollAttribute'; + const rollType = button?.rollType ?? 'attribute'; + const rollDesc = button?.rollDesc ?? 'agility'; + let rollId = rollDesc.toLowerCase(); + if (rollType === 'skill') { + rollFunc = 'rollSkill'; + rollId = effect.parent.items + .filter((i) => i.type === 'skill') + .find( + (i) => i.system.swid === rollDesc.toLowerCase() || i.name.toLowerCase() === rollDesc.toLowerCase(), + )?.id; + } + const options = { + flavor: button?.flavor ?? `${effect.name} ${rollDesc} roll`, + }; + if (button?.mods) { + options.mods = button.mods; + } + effect.parent[rollFunc](rollId, options); + }; + break; + case 'damage': + callback = function () { + const formula = button?.formula ?? '2d6x'; + const ap = button?.ap ?? 0; + const flavor = button?.flavor ?? `${effect.name} damage roll`; + const options = {}; + if (ap > 0) { + options.ap = ap; + } + new CONFIG.Dice.DamageRoll(formula, null, options).toMessage({ flavor }); + }; + break; + case 'callback': + callback = + button?.callback || + function () { + console.log('not implemented'); + }; + break; + } + if (button?.label && callback) { + buttons.push({ label: button.label, callback }); + } + } +} + export async function powerEffectManagementHook(effect, data, userId) { if (game.user.id !== userId) { return; diff --git a/src/module/swade-mb-helpers.js b/src/module/swade-mb-helpers.js index f3132cc..568be5d 100644 --- a/src/module/swade-mb-helpers.js +++ b/src/module/swade-mb-helpers.js @@ -7,7 +7,7 @@ import { initVisionModes } from './visionModes.js'; import { requestTokenRoll, addActiveEffectsToToken, deleteActiveEffectsFromToken } from './helpers.js'; import { preDamageRollModifiers, preTraitRollModifiers } from './rollHelpers.js'; import { log, moduleHelpers } from './globals.js'; -import { powerEffectManagementHook } from './powers/powers.js'; +import { powerEffectManagementHook, visualActiveEffectPowerButtons } from './powers/powers.js'; // Initialize module Hooks.once('init', async () => { @@ -44,6 +44,7 @@ Hooks.on('swadePreRollAttribute', preTraitRollModifiers); Hooks.on('swadePreRollSkill', preTraitRollModifiers); Hooks.on('swadeRollDamage', preDamageRollModifiers); Hooks.on('deleteActiveEffect', powerEffectManagementHook); +Hooks.on('visual-active-effects.createEffectButtons', visualActiveEffectPowerButtons); Hooks.once('socketlib.ready', () => { const _socket = socketlib.registerModule('swade-mb-helpers');