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 } } } } 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) } } } 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) } } }