swade-mb-helpers/scripts/powerEffects.js

959 lines
28 KiB
JavaScript

import { CONST, log, shim } from './shim.js'
class PowerEffect {
constructor (token, targets) {
this.token = token
this.targets = targets
this.effectDocs = []
this.menuData = {
inputs: [
{ type: 'header', label: `${this.name} Effect` },
{ type: 'info', label: `Apply ${this.name} Effect` },
{ 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: '---------------' }
],
buttons: [
{ label: 'Apply', value: 'apply' },
{ label: 'Apply with Raise', value: 'raise' },
{ label: 'Cancel', value: 'cancel' }
]
}
this.menuOptions = {
title: `${this.name} Effect`,
defaultButton: 'Cancel',
options: {}
}
this.inputs = []
this.buttons = null
}
get name () {
return 'Unknown Power'
}
get durationRounds () {
return this.baseDurationRounds
}
get baseDurationRounds () {
return 5
}
async powerEffect () {
try {
await this.prepMenu()
} catch (e) {
log('Error preparing menu for power effect: ' + e.toString())
return
}
const { buttons, inputs } = await shim.warpgateMenu(
this.menuData, this.menuOptions)
this.buttons = buttons
this.inputs = inputs
if (this.buttons && this.buttons !== 'cancel') {
this.globalModifierEffects()
await this.prepResult()
await this.applyResult()
}
}
async prepResult () {
}
async applyResult () {
for (const target of this.targets) {
shim.applyActiveEffects(target, this.effectDocs)
}
}
static modEffectDoc (icon, name, key, value, durationRounds) {
return shim.createEffectDocument(icon, name, durationRounds, [
{
key,
mode: CONST.FOUNDRY.ACTIVE_EFFECT_MODES.ADD,
value,
priority: 0
}
])
}
static glow (durationRounds) {
return PowerEffect.modEffectDoc(
'icons/magic/light/orb-shadow-blue.webp',
'Glow', '@Skill{Stealth}[system.die.modifier]', -2, durationRounds)
}
static shroud (durationRounds) {
return PowerEffect.modEffectDoc(
'icons/magic/perception/shadow-stealth-eyes-purple.webp',
'Shroud', '@Skill{Stealth}[system.die.modifier]', 1, durationRounds)
}
static hinder (durationRounds) {
return PowerEffect.modEffectDoc(
'icons/magic/control/debuff-chains-shackle-movement-red.webp',
'Hinder', 'system.stats.speed.value', -2, durationRounds)
}
static hurry (durationRounds) {
return PowerEffect.modEffectDoc(
'icons/skills/movement/feet-winged-sandals-tan.webp',
'Hurry', 'system.stats.speed.value', 2, durationRounds)
}
globalModifierEffects () {
this.inputIndex = 8
if (this.inputs[3]) { // glow
this.effectDocs.push(PowerEffect.glow(this.durationRounds))
}
if (this.inputs[4]) { // shroud
this.effectDocs.push(PowerEffect.shroud(this.durationRounds))
}
if (this.inputs[5]) { // hinder
this.effectDocs.push(PowerEffect.hinder(this.durationRounds))
}
if (this.inputs[6]) { // hurry
this.effectDocs.push(PowerEffect.hurry(this.durationRounds))
}
}
}
class TargetedPowerEffect extends PowerEffect {
constructor (token, targets) {
super(token, targets)
const targetList = this.targets.map(t => t.name).join(', ')
this.menuData.inputs[1] = {
type: 'info',
label: `Apply ${this.name} Effect to ${targetList}`
}
}
async powerEffect () {
if (this.targets.length < 1) {
shim.notifications.error(`No target selected for ${this.name}`)
return
}
super.powerEffect()
}
}
class BlindEffect extends TargetedPowerEffect {
async prepMenu (token, targets) {
this.menuData.inputs.push({
type: 'checkbox',
label: 'Strong (+1 point)',
options: false
})
}
get name () {
return 'Blind'
}
get baseDurationRounds () {
return 1
}
async prepResult () {
const raise = (this.buttons === 'raise')
const strong = !!this.inputs[this.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
}
]
this.effectDocs.push(
shim.createEffectDocument(
icon, `minor Blindness (Vigor ${strong ? '-2 ' : ''}ends)`,
this.durationRounds, changes))
if (raise) {
this.effectDocs.push(
shim.createEffectDocument(
icon, `major Blindness (Vigor ${strong ? '-2 ' : ''}ends)`,
this.durationRounds, changes)
)
}
}
}
class BoostLowerTraitEffect extends TargetedPowerEffect {
get name () {
return 'Boost/Lower Trait'
}
get baseDurationRounds () {
if (!this.inputs) {
return 1
}
if (this.inputs[this.inputs.length - 4]) { // Boost
return 5
}
return 1 // Lower
}
async prepMenu () {
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 this.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())
}
this.menuData.inputs = this.menuData.inputs.concat(
{ type: 'select', label: 'Trait', options: traitOptions },
{ type: 'info', label: 'Boost or Lower?' },
{ type: 'radio', label: 'Boost', options: ['isBoost', true] },
{ type: 'radio', label: 'Lower', options: ['isBoost', false] },
{ type: 'checkbox', label: 'Greater', options: false },
{ type: 'checkbox', label: 'Strong (lower only)', options: false }
)
this.traits = traits
}
async prepResult () {
const raise = (this.buttons === 'raise')
const direction = this.inputs[this.inputs.length - 4] ? 'Boost' : 'Lower'
const durationRounds = (direction === 'Boost' ? 5 : 1)
const icon = (direction === 'Boost'
? 'icons/magic/life/cross-embers-glow-yellow-purple.webp'
: 'icons/magic/movement/chevrons-down-yellow.webp')
const trait = this.traits[this.inputs[this.inputIndex]]
const greater = !!this.inputs[this.inputIndex + 4]
const strong = !!this.inputs[this.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 }
])
this.effectDocs.push(minorEffect)
if (raise) { this.effectDocs.push(majorEffect) }
}
}
class ConfusionEffect extends TargetedPowerEffect {
get name () {
return 'Confusion'
}
get baseDurationRounds () {
return 1
}
async prepMenu () {
this.menuData.inputs.push(
{ type: 'checkbox', label: 'Greater (adds Shaken)', options: false })
this.menuData.buttons = [
{ label: 'Distracted', value: 'distracted' },
{ label: 'Vulnerable', value: 'vulnerable' },
{ label: 'Raise (both)', value: 'raise' },
{ label: 'Cancel', value: 'cancel' }
]
}
async prepResult () {
const greater = !!this.inputs[this.inputIndex]
if (this.buttons === 'distracted' || this.buttons === 'raise') {
this.effectDocs.push(shim.getStatus('SWADE.Distr', 'Distracted'))
}
if (this.buttons === 'vulnerable' || this.buttons === 'raise') {
this.effectDocs.push(shim.getStatus('SWADE.Vuln', 'Vulnerable'))
}
if (greater) {
this.effectDocs.push(shim.getStatus('SWADE.Shaken', 'Shaken'))
}
}
}
class DeflectionEffect extends TargetedPowerEffect {
get name () {
return 'Deflection'
}
get baseDurationRounds () {
return 5
}
async prepMenu () {
this.menuData.buttons = [
{ label: 'Melee', value: 'melee' },
{ label: 'Ranged', value: 'ranged' },
{ label: 'Raise (both)', value: 'raise' },
{ label: 'Cancel', value: 'cancel' }
]
}
async prepResult () {
const effectName = `Deflection (${this.buttons === 'raise' ? 'all' : this.buttons})`
const icon = 'icons/magic/defensive/shield-barrier-deflect-teal.webp'
this.effectDocs.push(shim.createEffectDocument(icon, effectName, this.durationRounds))
}
}
class EntangleEffect extends TargetedPowerEffect {
get name () {
return 'Entangle'
}
get baseDurationRounds () {
return 1
}
async prepMenu () {
this.menuData.inputs = this.menuData.inputs.concat([
{ type: 'radio', label: 'Not Damaging', options: ['dmg', true] },
{ type: 'radio', label: 'Damaging', options: ['dmg', false] },
{ type: 'radio', label: 'Deadly', options: ['dmg', false] },
{ type: 'checkbox', label: 'Tough', options: false }
])
this.menuData.buttons = [
{ label: 'Entangled', value: 'apply' },
{ label: 'Bound (raise)', value: 'raise' },
{ label: 'Cancel', value: 'cancel' }
]
}
async prepResult () {
const damage = (this.inputs[this.inputIndex + 1]
? '2d4'
: (this.inputs[this.inputIndex + 2] ? '2d6' : null))
const tough = !!this.inputs[this.inputIndex + 3]
const effectSearch = (this.buttons === 'raise' ? 'SWADE.Bound' : 'SWADE.Entangled')
const effectName = (this.buttons === 'raise' ? 'Bound' : 'Entangled')
const effect = shim.getStatus(effectSearch, effectName)
const extraIcon = 'icons/magic/nature/root-vine-barrier-wall-brown.webp'
const extraEffect = shim.createEffectDocument(extraIcon,
'Entangle Modifier', this.durationRounds, [])
if (damage) {
extraEffect.name = `${extraEffect.name} - ${damage} dmg`
}
if (tough) {
extraEffect.name = `Tough ${extraEffect.name}`
}
this.effectDocs.push(effect)
if (damage || tough) {
this.effectDocs.push(extraEffect)
}
}
}
class IntangibilityEffect extends TargetedPowerEffect {
get name () {
return 'Intangility'
}
get baseDurationRounds () {
if (!this.inputs) {
return 5
}
if (this.inputs[this.inputs.length - 1]) { // Duration
return 50
}
return 5 // no duration
}
async prepMenu () {
this.menuData.inputs.push({ type: 'checkbox', label: 'Duration', options: false })
this.menuData.buttons = [
{ label: 'Apply', value: 'apply' },
{ label: 'Cancel', value: 'cancel' }
]
}
async prepResult () {
const icon = 'icons/magic/control/debuff-energy-hold-levitate-blue-yellow.webp'
const effect = shim.createEffectDocument(icon, this.name, this.durationRounds, [])
this.effectDocs.push(effect)
}
}
class InvisibilityEffect extends TargetedPowerEffect {
get name () {
return 'Invisiblity'
}
get baseDurationRounds () {
if (!this.inputs) {
return 5
}
if (this.inputs[this.inputs.length - 1]) { // Duration
return 50
}
return 5 // no duration
}
async prepMenu () {
this.menuData.inputs.push({ type: 'checkbox', label: 'Duration', options: false })
}
async prepResult () {
const effect = shim.getStatus('EFFECT.StatusInvisible', 'Invisible')
effect.duration = { rounds: this.durationRounds }
this.effectDocs.push(effect)
}
}
class ProtectionEffect extends TargetedPowerEffect {
get name () {
return 'Protection'
}
get baseDurationRounds () {
return 5
}
async prepMenu () {
this.menuData.buttons = [
{ label: 'Apply (+2 armor)', value: 'apply' },
{ label: 'Apply with raise (+2 toughness)', value: 'raise' },
{ label: 'Cancel', value: 'cancel' }
]
}
async prepResult () {
const effect = shim.getStatus('SWADE.Protection', 'Protection')
effect.duration = { rounds: this.durationRounds }
const mode = CONST.FOUNDRY.ACTIVE_EFFECT_MODES.ADD
effect.changes = [
{ key: 'system.stats.toughness.armor', mode, value: 2, priority: 0 }
]
if (this.buttons === 'raise') {
effect.changes[0].key = 'system.stats.toughness.value'
}
this.effectDocs.push(effect)
}
}
class SmiteEffect extends TargetedPowerEffect {
get name () {
return 'Smite'
}
get baseDurationRounds () {
return 5
}
async prepMenu () {
this.menuData.inputs.push({
type: 'checkbox', label: 'Greater', options: false
})
const tokenWeapons = {}
let index = this.menuData.inputs.length - 1
for (const token of this.targets) {
index += 2
tokenWeapons[token.id] = index
this.menuData.inputs.push({ type: 'info', label: `<h2>${token.name}</h2>` })
const weapons = token.actor.items.filter(i => i.type === 'weapon').map(
i => { return { value: i.name, html: i.name } })
weapons.unshift({ value: '', html: '<i>None</i>' })
this.menuData.inputs.push({ type: 'select', label: token.name, options: weapons })
}
this.tokenWeapons = tokenWeapons
}
async prepResult () {
this.baseEffect = shim.getStatus('SWADE.Smite', 'Smite')
}
async applyResult () {
const mode = CONST.FOUNDRY.ACTIVE_EFFECT_MODES.ADD
const raise = (this.buttons === 'raise')
const greater = !!this.inputs[this.inputIndex]
const changeValue = (greater ? (raise ? '+6' : '+4') : (raise ? '+4' : '+2'))
for (const token of this.targets) {
const weaponName = this.inputs[this.tokenWeapons[token.id]]
const weaponId = token.actor.items.getName(weaponName)?.id
const changeKey = `@Weapon{${weaponName}}[system.actions.dmgMod]`
if (!weaponId) {
continue
}
const effectName = `${this.buttons === 'raise' ? 'major' : 'minor'} Smite${greater ? ' (greater)' : ''} (${weaponName})`
const changes = [
{ key: changeKey, mode, value: changeValue, priority: 0 }
]
this.baseEffect.changes = changes
this.baseEffect.name = effectName
console.log(token, weaponName, weaponId, effectName, changeKey)
await shim.applyActiveEffects(token, [this.baseEffect].concat(this.effectDocs))
}
}
}
class SummonEffect extends PowerEffect {
ICON = 'icons/magic/symbols/runes-triangle-blue.webp'
get actorFolderBase () {
return 'Summonables'
}
get actorFolder () {
return `${this.actorFolderBase}/${this.name}`
}
get name () {
return 'Summon Creature'
}
get baseDurationRounds () {
return 5
}
async prepFolders () {
const folders = []
const folderNames = [
this.actorFolder,
`${this.actorFolder} - Default`,
`${this.actorFolder}/Default`,
`${this.actorFolder} - ${this.token.name}`,
`${this.actorFolder} - ${this.token.actor.name}`,
`${this.actorFolder}/${this.token.name}`,
`${this.actorFolder}/${this.token.actor.name}`
]
for (const folderName of folderNames) {
const folder = shim.getActorFolderByPath(folderName)
if (folder) {
log(`Found actor folder ${folderName}`)
folders.push(folder)
}
}
if (folders.length > 1) {
folders.shift()
}
return folders
}
async prepActors () {
const folders = await this.prepFolders()
const actors = {}
for (const folder of folders) {
const folderActors = shim.getActorsInFolder(folder)
for (const key in folderActors) {
actors[key] = folderActors[key]
}
}
return actors
}
async prepMenu () {
this.menuData.inputs[1].label = `${this.token.name} is summoning...`
const actors = await this.prepActors()
if (Object.keys(actors).length < 1) {
shim.notifications.error('No summonables found')
throw new Error('No summonables found')
}
function actorData (key) {
return {
value: actors[key].id,
html: key
}
}
this.summonableActors = actors
this.menuData.inputs = this.menuData.inputs.concat([
{
type: 'select',
label: 'Creature to summon',
options: Object.keys(actors).filter(
k => !k.includes('_template')).sort().map(actorData)
}, {
type: 'number',
label: 'Number to spawn (+half base cost per)',
options: 1
}, {
type: 'checkbox',
label: 'Add Increased Trait(s)? (+1 per trait)',
options: false
}
])
}
async prepResult () {
this.raise = (this.buttons === 'raise')
this.actorId = (this.inputs[this.inputIndex])
this.number = (this.inputs[this.inputIndex + 1])
this.actor = shim.actors.get(this.actorId)
this.icon = this.actor.prototypeToken.texture.src
this.protoDoc = await this.actor.getTokenDocument()
this.increasedTrait = !!(this.inputs[this.inputIndex + 2])
this.inputIndex += 3
this.spawnOptions = {
controllingActor: this.token.actor,
duplicates: this.number,
crosshairs: {
icon: this.icon,
label: `Summon ${this.actor.name}`,
drawOutline: true,
rememberControlled: true
}
}
this.spawnMutation = {
actor: {
name: `${this.token.name}'s ${this.actor.name}`
},
token: {
actorLink: false,
name: `${this.token.name}'s ${this.protoDoc.name}`
},
embedded: { ActiveEffect: {}, Item: {} }
}
if (this.raise && ('raise_template' in this.summonableActors)) {
const raiseTemplate = this.summonableActors.raise_template
for (const item of raiseTemplate.items) {
const raiseItemDoc = await raiseTemplate.getEmbeddedDocument('Item', item.id)
this.spawnMutation.embedded.Item[item.name] = raiseItemDoc
}
}
for (const effectDocument of this.effectDocs) {
this.spawnMutation.embedded.ActiveEffect[effectDocument.name] = effectDocument
}
}
async prepAdditional () {
if (!this.increasedTrait) {
return
}
const traitMenuOptions = {
title: `${this.name} Summon Trait Increase`,
defaultButton: 'Cancel',
options: {}
}
const skillSet = new Set()
for (const skill of this.actor.items.filter(i => i.type === 'skill')) {
skillSet.add(skill.name)
}
for (const item of Object.values(this.spawnMutation.embedded.Item).filter(i => i.type === 'skill')) {
skillSet.add(item.name)
}
const skillList = Array.from(skillSet)
const attrList = ['Agility', 'Smarts', 'Spirit', 'Strength', 'Vigor']
skillList.sort()
const traitMenuData = {
inputs: [
{ type: 'header', label: 'Increase Attributes (+1 each)' }
],
buttons: [
{ label: 'Apply', value: 'apply' },
{ label: 'Increase no traits', value: 'cancel' }
]
}
traitMenuData.inputs = traitMenuData.inputs.concat(
attrList.map((x) => { return { type: 'checkbox', label: x, options: false } }))
traitMenuData.inputs.push({ type: 'header', label: 'Increase Skills (+1 each)' })
traitMenuData.inputs = traitMenuData.inputs.concat(
skillList.map((x) => { return { type: 'checkbox', label: x, options: false } }))
const { buttons, inputs } = await shim.warpgateMenu(traitMenuData, traitMenuOptions)
if (!buttons || buttons === 'cancel') {
return
}
const modKeys = []
for (let i = 0; i < attrList.length; i++) {
if (inputs[i + 1]) {
modKeys.push(`system.attributes.${attrList[i].toLowerCase()}.die.sides`)
}
}
for (let i = 0; i < skillList.length; i++) {
if (inputs[i + 7]) {
modKeys.push(`@Skill{${skillList[i]}}[system.die.sides]`)
}
}
const effectDoc = shim.createEffectDocument(
this.ICON, 'Increased Trait', this.durationRounds)
effectDoc.changes = modKeys.map(key => {
return {
key, mode: CONST.FOUNDRY.ACTIVE_EFFECT_MODES.ADD, value: '+2', priority: 0
}
})
this.spawnMutation.embedded.ActiveEffect[effectDoc.name] = effectDoc
}
async applyResult () {
await this.prepAdditional()
await shim.warpgateSpawn(this.protoDoc, this.spawnMutation, {}, this.spawnOptions)
}
}
class SummonAllyEffect extends SummonEffect {
get name () {
return 'Summon Ally'
}
get mirrorFolder () {
return `${this.actorFolderBase}/Mirror Selves`
}
async prepMenu () {
await super.prepMenu()
this.menuData.inputs = this.menuData.inputs.concat([
{
type: 'checkbox',
label: 'Bite/Claw (+1)',
options: false
}, {
type: 'checkbox',
label: 'Up to 3 Combat Edges (+1 per)',
options: false
}, {
type: 'checkbox',
label: 'Flight (+3)',
options: false
}
])
}
async prepResult () {
await super.prepResult()
this.biteClaw = !!(this.inputs[this.inputIndex])
this.combatEdge = !!(this.inputs[this.inputIndex + 1])
this.flight = !!(this.inputs[this.inputIndex + 2])
await this.prepMirrorSelf()
}
async prepAdditional () {
await super.prepAdditional()
await this.prepBiteClaw()
await this.prepFlight()
await this.prepCombatEdge()
}
async prepCombatEdge () {
if (!this.combatEdge || !('combat-edge_template' in this.summonableActors)) {
return
}
const template = this.summonableActors['combat-edge_template']
const edges = template.items.filter(i => i.type === 'edge').map(i => i.name)
edges.sort()
edges.unshift('None')
const edgeMenuData = {
inputs: [
{ type: 'header', label: 'Choose Edges (+1 per choice)' },
{ type: 'select', label: 'Edge 1', options: edges },
{ type: 'select', label: 'Edge 2', options: edges },
{ type: 'select', label: 'Edge 3', options: edges }
],
buttons: [
{ label: 'Apply', value: 'apply' },
{ label: 'Add no edges', value: 'cancel' }
]
}
const edgeMenuOptions = {
title: `${this.name} Combat Edge Selection`,
defaultButton: 'Cancel',
options: {}
}
const { buttons, inputs } = await shim.warpgateMenu(edgeMenuData, edgeMenuOptions)
if (!buttons || buttons === 'cancel') {
return
}
for (let i = 1; i <= 3; i++) {
if (inputs[i] === 'None') {
continue
}
const edge = template.items.getName(inputs[i])
if (edge) {
const doc = template.getEmbeddedDocument('Item', edge.id)
this.spawnMutation.embedded.Item[edge.name] = doc
}
}
}
async prepBiteClaw () {
if (!this.biteClaw || !('bite-claw_template' in this.summonableActors)) {
return
}
const template = this.summonableActors['bite-claw_template']
for (const item of template.items) {
const doc = await template.getEmbeddedDocument('Item', item.id)
this.spawnMutation.embedded.Item[item.name] = doc
log(`Added ${item.name} to spawn mutation`)
}
}
async prepFlight () {
if (!this.flight || !('flight_template' in this.summonableActors)) {
return
}
const template = this.summonableActors.flight_template
for (const item of template.items) {
const doc = await template.getEmbeddedDocument('Item', item.id)
this.spawnMutation.embedded.Item[item.name] = doc
log(`Added ${item.name} to spawn mutation`)
}
for (const effect of template.effects.values()) {
const doc = shim.ActiveEffect.fromSource(effect)
this.spawnMutation.embedded.ActiveEffect[effect.name] = doc
log(`Added ${effect.name} to spawn mutation`)
}
}
async prepMirrorSelf () {
if (this.actor.name !== 'Mirror Self') {
return
}
const actorFolder = shim.getActorFolderByPath(this.mirrorFolder)
const oldActor = actorFolder.contents.find(a => a.name === `Mirror ${this.token.name}`)
if (oldActor) {
await oldActor.delete()
}
const actorDoc = this.token.actor.clone({
type: 'npc',
name: `Mirror ${this.token.actor.name}`,
folder: actorFolder.id,
'system.wildcard': false,
'system.fatigue.value': 0,
'system.wounds.value': 0,
'system.wounds.max': 0,
'system.bennies.max': 0,
'system.bennies.value': 0,
'prototypeToken.actorLink': false,
'prototypeToken.name': `Mirror ${this.token.name}`,
'prototypeToken.texture.scaleX': this.token.document.texture.scaleX * -1
})
const mirrorActor = this.actor
this.actor = await shim.Actor.create(actorDoc)
this.actorId = this.actor.id
this.icon = this.actor.prototypeToken.texture.src
this.protoDoc = await this.actor.getTokenDocument()
this.spawnOptions.crosshairs.icon = this.icon
for (const mirrorItem of mirrorActor.items) {
this.spawnMutation.embedded.Item[mirrorItem.name] =
mirrorActor.getEmbeddedDocument('Item', mirrorItem.id)
}
this.spawnMutation.embedded.Item['Summon Ally'] = CONST.WARPGATE.DELETE
const effectChanges = []
for (const item of this.token.actor.items.filter(i => i.type === 'skill')) {
effectChanges.push({
key: `@Skill{${item.name}}[system.die.sides]`,
mode: CONST.FOUNDRY.ACTIVE_EFFECT_MODES.ADD,
value: '-2',
priority: 0
})
}
this.spawnMutation.embedded.ActiveEffect['Mirror Self'] =
shim.createEffectDocument(this.ICON, 'Mirror Self',
this.durationRounds, effectChanges)
}
}
class SummonAnimalEffect extends SummonEffect {
get name () {
return 'Summon Animal'
}
}
class SummonMonsterEffect extends SummonEffect {
get name () {
return 'Summon Monster'
}
}
class SummonNaturesAllyEffect extends SummonEffect {
get name () {
return "Summon Nature's Ally"
}
}
class SummonPlanarAllyEffect extends SummonEffect {
get name () {
return 'Summon Planar Ally'
}
}
class SummonUndeadEffect extends SummonEffect {
get name () {
return 'Summon Undead'
}
}
const PowerClasses = {
blind: BlindEffect,
'boost/lower trait': BoostLowerTraitEffect,
'boost trait': BoostLowerTraitEffect,
confusion: ConfusionEffect,
deflection: DeflectionEffect,
entangle: EntangleEffect,
intangibility: IntangibilityEffect,
invisibility: InvisibilityEffect,
'lower trait': BoostLowerTraitEffect,
protection: ProtectionEffect,
smite: SmiteEffect,
'summon ally': SummonAllyEffect,
'summon animal': SummonAnimalEffect,
'summon monster': SummonMonsterEffect,
"summon nature's ally": SummonNaturesAllyEffect,
'summon planar ally': SummonPlanarAllyEffect,
'summon undead': SummonUndeadEffect
}
export async function powerEffects (options = {}) {
const token = 'token' in options ? options.token : []
if (token === undefined || token === null) {
shim.notifications.error('Please select one token')
return
}
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()
for (const name in PowerClasses) {
if (lcName.includes(name)) {
const runner = new PowerClasses[name](token, targets)
runner.powerEffect()
return
}
}
shim.notifications.error(`No power effect found for ${name}`)
}