swade-mb-helpers/scripts/powerEffects.js

333 lines
11 KiB
JavaScript

import { CONST, shim } from './shim.js'
function baseMenu (title, targets) {
const targetList = targets.map(t => t.name).join(', ')
return {
menuOptions: {
title,
defaultButton: 'Cancel',
options: {}
},
menuData: {
inputs: [
{ type: 'header', label: title },
{ type: 'info', label: `Apply ${title} to ${targetList}` },
{ type: 'header', label: 'Global Modifiers' },
{ type: 'checkbox', label: 'Glow (+1)' },
{ type: 'checkbox', label: 'Shroud (+1)' },
{ type: 'checkbox', label: 'Hinder (+1)' },
{ type: 'checkbox', label: 'Hurry (+1)' },
{ type: 'header', label: 'Power Modifiers' }
],
buttons: [
{ label: 'Apply', value: 'apply' },
{ label: 'Apply with Raise', value: 'raise' },
{ label: 'Cancel', value: 'cancel' }
]
}
}
}
class ModifierEffects {
static glow (basename, durationRounds) {
const effectDoc = shim.createEffectDocument(
'icons/magic/light/orb-shadow-blue.webp',
'Glow',
durationRounds
)
effectDoc.changes = [
{
key: '@Skill{Stealth}[system.die.modifier]',
mode: CONST.FOUNDRY.ACTIVE_EFFECT_MODES.ADD,
value: -2,
priority: 0
}
]
return effectDoc
}
static shroud (basename, durationRounds) {
const effectDoc = shim.createEffectDocument(
'icons/magic/perception/shadow-stealth-eyes-purple.webp',
'Shroud',
durationRounds
)
effectDoc.changes = [
{
key: '@Skill{Stealth}[system.die.modifier]',
mode: CONST.FOUNDRY.ACTIVE_EFFECT_MODES.ADD,
value: 1,
priority: 0
}
]
return effectDoc
}
static hinder (basename, durationRounds) {
const effectDoc = shim.createEffectDocument(
'icons/magic/control/debuff-chains-shackle-movement-red.webp',
'Hinder',
durationRounds
)
effectDoc.changes = [
{
key: 'system.stats.speed.value',
mode: CONST.FOUNDRY.ACTIVE_EFFECT_MODES.ADD,
value: -2,
priority: 0
}
]
return effectDoc
}
static hurry (basename, durationRounds) {
const effectDoc = shim.createEffectDocument(
'icons/skills/movement/feet-winged-sandals-tan.webp',
'Hurry',
durationRounds
)
effectDoc.changes = [
{
key: 'system.stats.speed.value',
mode: CONST.FOUNDRY.ACTIVE_EFFECT_MODES.ADD,
value: 2,
priority: 0
}
]
return effectDoc
}
}
function globalModifierEffects (inputs, basename, durationRounds) {
const effectDocs = []
const inputIndex = 8
if (inputs[3]) { // glow
effectDocs.push(ModifierEffects.glow(basename, durationRounds))
}
if (inputs[4]) { // shroud
effectDocs.push(ModifierEffects.shroud(basename, durationRounds))
}
if (inputs[5]) { // hinder
effectDocs.push(ModifierEffects.hinder(basename, durationRounds))
}
if (inputs[6]) { // hurry
effectDocs.push(ModifierEffects.hurry(basename, durationRounds))
}
return { effectDocs, inputIndex }
}
const PowerMenus = {
blind: function (token, targets) {
if (targets.length < 1) {
shim.notifications.error('No target selected for Blind')
return null
}
const { menuOptions, menuData } = baseMenu('Blind', targets)
menuData.inputs.push({
type: 'checkbox',
label: 'Strong (+1 point)',
options: false
})
return { menuOptions, menuData, extra: {} }
},
'boost/lower trait': function (token, targets) {
if (targets.length < 1) {
shim.notifications.error('No target selected for Boost/Lower Trait')
return null
}
const { menuOptions, menuData } = baseMenu('Boost/Lower Trait', targets)
let traitOptions = ['Agility', 'Smarts', 'Spirit', 'Strength', 'Vigor']
const allSkills = []
const traits = {}
for (const traitName of traitOptions) {
const lower = traitName.toLowerCase()
traits[traitName] = {
name: traitName,
type: 'attribute',
modkey: `system.attributes.${lower}.die.modifier`,
diekey: `system.attributes.${lower}.die.sides`
}
}
for (const token of targets) {
const skills = token.actor.items.filter(item => item.type === 'skill')
for (const skill of skills) {
const name = skill.name
traits[name] = {
name,
type: 'skill',
modkey: `@Skill{${name}}[system.die.modifier]`,
diekey: `@Skill{${name}}[system.die.sides]`
}
if (name !== 'Unskilled' && !allSkills.find(v => v === name)) {
allSkills.push(name)
}
}
traitOptions = traitOptions.concat(allSkills.sort())
}
menuData.inputs.push({
type: 'select', label: 'Trait', options: traitOptions
})
menuData.inputs.push({ type: 'info', label: 'Boost or Lower?' })
menuData.inputs.push({ type: 'radio', label: 'Boost', options: ['isBoost', true] })
menuData.inputs.push({ type: 'radio', label: 'Lower', options: ['isBoost', false] })
menuData.inputs.push({ type: 'checkbox', label: 'Greater', options: false })
menuData.inputs.push({ type: 'checkbox', label: 'Strong (lower only)', options: false })
return { menuOptions, menuData, extra: { traits } }
},
confusion: function (token, targets) {
if (targets.length < 1) {
shim.notifications.error('No target selected for Confusion')
return null
}
const { menuOptions, menuData } = baseMenu('Confusion', targets)
menuData.inputs.push({ type: 'checkbox', label: 'Greater (adds Shaken)', options: false })
menuData.buttons = [
{ label: 'Distracted', value: 'distracted' },
{ label: 'Vulnerable', value: 'vulnerable' },
{ label: 'Raise (both)', value: 'raise' },
{ label: 'Cancel', value: 'cancel' }
]
return { menuOptions, menuData, extra: {} }
},
deflection: function (token, targets) {
if (targets.length < 1) {
shim.notifications.error('No target selected for Deflection')
return null
}
const { menuOptions, menuData } = baseMenu('Deflection', targets)
menuData.buttons = [
{ label: 'Melee', value: 'melee' },
{ label: 'Ranged', value: 'ranged' },
{ label: 'Raise (both)', value: 'raise' },
{ label: 'Cancel', value: 'cancel' }
]
return { menuOptions, menuData, extra: {} }
}
}
const PowerHandlers = {
blind: async function (token, targets, buttons, inputs, extra) {
const raise = (buttons === 'raise')
const { effectDocs, inputIndex } = globalModifierEffects(inputs, 'Blind', 1)
const strong = !!inputs[inputIndex]
const icon = 'icons/skills/wounds/injury-eyes-blood-red.webp'
const changes = [
{
key: 'system.stats.globalMods.trait',
mode: CONST.FOUNDRY.ACTIVE_EFFECT_MODES.ADD,
value: '-2',
priority: 0
}
]
effectDocs.push(
shim.createEffectDocument(
icon, `minor Blindness (Vigor ${strong ? '-2 ' : ''}ends)`, 1, changes)
)
if (raise) {
effectDocs.push(
shim.createEffectDocument(
icon, `major Blindness (Vigor ${strong ? '-2 ' : ''}ends)`, 1, changes)
)
}
for (const target of targets) {
shim.applyActiveEffects(target, effectDocs)
}
},
'boost/lower trait': async function (token, targets, buttons, inputs, extra) {
const raise = (buttons === 'raise')
const direction = inputs[inputs.length - 4] ? 'Boost' : 'Lower'
const durationRounds = (direction === 'Boost' ? 5 : 1)
const { effectDocs, inputIndex } = globalModifierEffects(
inputs, 'Boost/Lower Trait', durationRounds)
const icon = (direction === 'Boost'
? 'icons/magic/life/cross-embers-glow-yellow-purple.webp'
: 'icons/magic/movement/chevrons-down-yellow.webp')
const trait = extra.traits[inputs[inputIndex]]
const greater = !!inputs[inputIndex + 4]
const strong = !!inputs[inputIndex + 5]
let namePart = `${direction} ${trait.name}`
const mods = []
if (direction === 'Lower') {
mods.push(`Spirit${strong ? '-2' : ''} ends`)
}
if (greater) {
mods.push('greater')
}
if (mods.length > 0) {
namePart = `${namePart} (${mods.join(', ')})`
}
const mode = CONST.FOUNDRY.ACTIVE_EFFECT_MODES.ADD
const modValue = (direction === 'Boost' ? '+2' : '-2')
const minorEffect = shim.createEffectDocument(
icon, `minor ${namePart}`, durationRounds, [
{ key: trait.diekey, mode, value: modValue, priority: 0 }
])
if (direction === 'Lower' && greater) {
minorEffect.changes.push({ key: trait.modkey, mode, value: modValue, priority: 0 })
}
const majorEffect = shim.createEffectDocument(
icon, `major ${namePart}`, durationRounds, [
{ key: trait.diekey, mode, value: modValue, priority: 0 }
])
effectDocs.push(minorEffect)
if (raise) { effectDocs.push(majorEffect) }
for (const target of targets) {
shim.applyActiveEffects(target, effectDocs)
}
},
confusion: async function (token, targets, buttons, inputs, extra) {
const { effectDocs, inputIndex } = globalModifierEffects(inputs, 'Confusion', 1)
const greater = !!inputs[inputIndex]
if (buttons === 'distracted' || buttons === 'raise') {
effectDocs.push(shim.getStatus('SWADE.Distr', 'Distracted'))
}
if (buttons === 'vulnerable' || buttons === 'raise') {
effectDocs.push(shim.getStatus('SWADE.Vuln', 'Vulnerable'))
}
if (greater) {
effectDocs.push(shim.getStatus('SWADE.Shaken', 'Shaken'))
}
for (const target of targets) {
shim.applyActiveEffects(target, effectDocs)
}
},
deflection: async function (token, targets, buttons, inputs, extra) {
const { effectDocs } = globalModifierEffects(inputs, 'Confusion', 1)
const effectName = `Deflection (${buttons === 'raise' ? 'all' : buttons})`
const icon = 'icons/magic/defensive/shield-barrier-deflect-teal.webp'
effectDocs.push(shim.createEffectDocument(icon, effectName, 5))
for (const target of targets) {
shim.applyActiveEffects(target, effectDocs)
}
}
}
export async function powerEffects (options = {}) {
// options available
const token = 'token' in options ? options.token : []
const targets = 'targets' in options ? Array.from(options.targets) : []
const item = 'item' in options ? options.item : null
const name = 'name' in options
? options.name
: (
item !== null ? item.name : null)
const lcName = name.toLowerCase()
if (lcName in PowerMenus && lcName in PowerHandlers) {
const data = PowerMenus[lcName](token, targets)
if (data === null) { return }
const { menuOptions, menuData, extra } = data
const { buttons, inputs } = await shim.warpgateMenu(menuData, menuOptions)
if (buttons && buttons !== 'cancel') {
await PowerHandlers[lcName](token, targets, buttons, inputs, extra)
}
}
}