import { moduleName } from './globals.js' import { PowerEffect } from './basePowers.js' class ArcaneProtectionEffect extends PowerEffect { get name () { return 'Arcane Protection' } get duration () { return 5 } get icon () { return 'icons/magic/defensive/shield-barrier-flaming-pentagon-blue.webp' } get hasAdditionalRecipients () { return true } get additionalRecipientCost () { return 1 } get isTargeted () { return true } get modifiers () { const mods = super.modifiers mods.push({ name: 'Greater Arcane Protection', id: 'greater', value: 2, epic: true, effect: false }) return mods } get _penaltyAmount () { return (this.data.raise ? -4 : -2) + (this.data.mods.has('greater') ? -2 : 0) } get description () { let text = super.description text += `

Hostile powers are at ${this._penaltyAmount} when targeting or damaging the affected character.

` return text } get effectName () { const greater = this.data.mods.has('greater') const raise = this.data.raise const amount = this._penaltyAmount return `${greater ? 'Greater ' : ''}Arcane Protection (${raise ? 'major, ' : ''}${amount})` } } class BanishEffect extends PowerEffect { get name () { return 'Banish' } get duration () { return 0 } get basePowerPoints () { return 3 } get usePrimaryEffect () { return false } get isTargeted () { return true } get isRaisable () { return false } get hasAoe () { return true } get menuInputs () { const inputs = super.menuInputs inputs.push inputs.push({ type: 'select', label: '⭐ Area of Effect', options: [ {html: 'None', value: 0, selected: true}, {html: 'Small Blast Template (+1)', value: 1, selected: false}, {html: 'Medium Blast Template (+2)', value: 2, selected: false}, {html: 'Large Blast Template (+3)', value: 3, selected: false}, ]}) return inputs } get powerPoints () { let total = super.powerPoints total += this.data.aoe return total } get description () { return super.description + `

An opposed roll of the caster's skill vs the target's Spirit. Success: Shaken, each Raise: 1 Wound Incapacitation results in banishment to home plane.

` } get chatMessageEffects () { const list = super.chatMessageEffects switch (this.data.aoe) { case 0: break case 1: list.push('SBT') break case 2: list.push('MBT') break case 3: list.push('LBT') break } return list } async parseValues () { await super.parseValues() this.data.aoe = this.data.values.shift() } } class BarrierEffect extends PowerEffect { get name () { return 'Barrier' } get duration () { return 5 } get icon () { return 'icons/environment/settlement/fence-stone-brick.webp' } get isTargeted () { return false } get isDamaging () { return true } get basePowerPoints () { return 2 } get usePrimaryEffect () { return false } get modifiers () { const mods = super.modifiers mods.push({ name: 'Damage', id: 'damage', value: 1, epic: false, effect: false }) mods.push({ name: 'Damage (immaterial trapping)', id: 'damage', value: 0, epic: false, effect: false }) mods.push({ name: 'Deadly', id: 'deadly', value: 2, epic: true, effect: false }) mods.push({ name: 'Hardened', id: 'hardened', value: 1, epic: false, effect: false }) mods.push({ name: 'Shaped', id: 'shaped', value: 1, epic: false, effect: false }) mods.push({ name: 'Size', id: 'size', value: 1, epic: false, effect: false }) return mods } get _length () { let height = 10 if (this.data.raise) { height *= 2 } if (this.data.mods.has('size')) { height *= 2} return `${height}" (${height*2} yards)` } get _height () { return `${this.data.mods.has('size') ? '2" (4' : '1" (2'} yards` } get _hardness () { return (this.data.raise ? 12 : 10) + (this.data.mods.has('hardened') ? 2 : 0) } get description () { let text = super.description text += `

A barrier ${this._height} tall and ${this._length} long, of hardness ${this._hardness}. ` if (this.data.mods.has('deadly')) { text += 'It does 2d6 damage to anyone who contacts it. ' } else if (this.data.mods.has('damage')) { text += 'It does 2d4 damage to anyone who contacts it. ' } if (this.data.mods.has('shaped')) { text += 'It was shaped into a circle, square, or rectangle. ' } text += '

' return text } } class BeastFriendEffect extends PowerEffect { get name () { return 'Beast Friend' } get duration () { return (this.data.mods.has('duration') ? 30 : 10) * 6 * 60 } get icon () { return 'icons/magic/nature/wolf-paw-glow-large-green.webp' } get isTargeted () { return true } get usePrimaryEffect () { return true } get modifiers () { const mods = super.modifiers mods.push( { name: 'Bestiarium', value: 2, id: 'bestiarium', epic: true, effect: false }, { name: 'Duration', value: 1, id: 'duration', epic: false, effect: false }, { name: 'Mind Rider', value: 1, id: 'mindrider', epic: false, effect: false }, ) return mods } get menuInputs () { const inputs = super.menuInputs const pp = Math.max( this.targets. map(t => Math.max(t.actor.system.stats.size, 1)). reduce((a,b) => a+b, 0), 1) inputs.push({ type: 'number', label: 'Base power points', options: pp}) return inputs } async parseValues () { await super.parseValues() this.data.basePP = this.data.values.shift() } get basePowerPoints () { return this?.data?.basePP || 2 } get description () { let text = super.description if (this.data.raise) { text += '

Creatures will overcome instincts to follow orders.' } else { text += '

Creatures obey simple commands, subject to their insticts.' } if (this.data.mods.has('bestiarium')) { text += ' The caster may even effect magical beasts.' } return text } } class BlastEffect extends PowerEffect { get name () { return 'Blast' } get icon () { return 'icons/magic/fire/explosion-fireball-large-red-orange.webp' } get duration () { return 0 } get isTargeted () { return true } get usePrimaryEffect () { return false } get isDamaging () { return true } get basePowerPoints () { return 3 } get hasAoe () { return true } get menuInputs () { const inputs = super.menuInputs inputs.push inputs.push({ type: 'select', label: 'Area of Effect', options: [ {html: 'Small Blast Template (0)', value: 's', selected: false}, {html: 'Medium Blast Template (0)', value: 'm', selected: true}, {html: 'Large Blast Template (+1)', value: 'l', selected: false}, ]}) return inputs } get modifiers () { const mods = super.modifiers mods.push( { name: 'Damage', value: 2, id: 'damage', epic: false, effect: false }, { name: 'Greater Blast', value: 4, id: 'greater', epic: true, effect: false }, ) return mods } get powerPoints () { let total = super.powerPoints total += (this.data.aoe === 'l' ? 1 : 0) return total } async parseValues () { await super.parseValues() this.data.aoe = this.data.values.shift() } get description () { const dmgDie = ( this.data.mods.has('greater') ? 4 : (this.data.mods.has('damage') ? 3 : 2)) + (this.data.raise ? 1 : 0) const size = ( this.data.aoe === 'l' ? 'LBT' : (this.data.aoe === 's' ? 'SBT' : 'MBT')) return super.description + `

The blast covers a ${size} and does ${dmgDie}d6 damage

` } } class BlindEffect extends PowerEffect { get name () { return 'Blind' } get icon () { return 'icons/skills/wounds/injury-eyes-blood-red.webp' } get duration () { return 0 } get isTargeted () { return true } get hasAoe () { return true } get basePowerPoints () { return 2 } getPrimaryEffectChanges () { const changes = [ { key: 'system.stats.globalMods.trait', value: -2, priority: 0, mode: foundry.CONST.ACTIVE_EFFECT_MODES.ADD } ] return changes } get description () { return super.description + `

${this.data.raise ? -4 : -2} penalty to all actions involving sight.

Shake off attempts at end of turns with a Vigor ${this.data.mods.has('strong') ? '-2 ' : ''}roll as a free action. Success removes 2 points of penalties. A raise removes the effect.

` } async createSecondaryEffects (maintId) { const docs = await super.createSecondaryEffects(maintId) if (this.data.raise) { const strong = this.data.mods.has('strong') const doc = this.createEffectDocument( this.icon, `Blinded (${strong ? 'Strong, ' : ''}Raise)`, [ { key: 'system.stats.globalMods.trait', value: -2, priority: 0, mode: foundry.CONST.ACTIVE_EFFECT_MODES.ADD } ]) doc.duration.seconds = 594 doc.description = this.description + "

This is the raise effect which can be shaken off separately.

" doc.flags[moduleName].maintId = maintId docs.push(doc) } return docs } get modifiers () { const mods = super.modifiers mods.push( { name: 'Strong', value: 1, id: 'strong', epic: false, effect: false }, ) return mods } get menuInputs () { const inputs = super.menuInputs inputs.push inputs.push({ type: 'select', label: 'Area of Effect', options: [ {html: 'None', value: 0, selected: true}, {html: 'Medium Blast Template (+2)', value: 2, selected: false}, {html: 'Large Blast Template (+3)', value: 3, selected: false}, ]}) return inputs } async parseValues () { await super.parseValues() this.data.aoe = this.data.values.shift() } get powerPoints () { let total = super.powerPoints total += this.data.aoe return total } get effectName () { const strong = this.data.mods.has('strong') return `Blinded${strong ? ' (Strong)' : ''}` } get chatMessageEffects () { const list = super.chatMessageEffects switch (this.data.aoe) { case 2: list.push('MBT'); break case 3: list.push('LBT'); break } return list } } class BoltEffect extends PowerEffect { get name () { return 'Bolt' } get icon () { return 'icons/magic/fire/explosion-fireball-large-red-orange.webp' } get duration () { return 0 } get isTargeted () { return true } get isDamaging () { return true } get basePowerPoints () { return 3 } get usePrimaryEffect () { return false } get modifiers () { const mods = super.modifiers mods.push( { name: 'Damage', value: 2, id: 'damage', epic: false, effect: false }, { name: 'Disintegrate', value: 1, id: 'disintigrate', epic: true, effect: false }, { name: 'Greater Bolt', value: 4, id: 'greater', epic: true, effect: false }, { name: 'Rate of Fire', value: 2, id: 'rof', epic: true, effect: false }, ) return mods } get powerPoints () { let total = super.powerPoints total += (this.data.aoe === 'l' ? 1 : 0) return total } get description () { const dmgDie = ( this.data.mods.has('greater') ? 4 : (this.data.mods.has('damage') ? 3 : 2)) + (this.data.raise ? 1 : 0) let desc = super.description + '

' if (this.data.mods.has('rof')) { desc += `Up to two bolts (RoF 2) do ${dmgDie}d6 damage each.` } else { desc += `The bolt does ${dmgDie}d6 damage.` } if (this.data.mods.has('disintegrate')) { desc += 'The bolt is disintegrating. If being used to break' + ' something, the damage dice can Ace. A creature Incapacitated by a ' + 'disintegrating bolt must make a Vigor roll or its body turns to dust' } desc += '

' return desc } } class BurrowEffect extends PowerEffect { get name () { return 'Burrow' } get duration () { return 5 } get icon () { return 'icons/magic/earth/projectile-stone-landslide.webp' } get hasAdditionalRecipients () { return true } get additionalRecipientCost () { return 1 } get basePowerPoints () { return 1 } get isTargeted () { return true } get modifiers () { const mods = super.modifiers mods.push( { name: 'Power', id: 'power', value: 1, epic: false, effect: false, }) return mods } get effectName () { return `${this.name} ${this.data.mods.has('power') ? '[Power] ' : ''}` + `(${this.data.raise ? 'full' : 'half'} pace)` } get description() { let text = super.description + `

Meld into the ground. Move at ${this.data.raise ? 'full' : 'half'} pace. May not run.

` if (this.data.mods.has('power')) { text += `

Can burrow through solid stone, concrete, etc

` } return text } } class BoostLowerTraitEffect extends PowerEffect { get name () { return 'Boost/Lower Trait' } get hasAdditionalRecipients () { return true } get additionalRecipientCost () { return 2 } get icon () { return (this?.data?.direction === 'Boost' ? 'icons/magic/life/cross-embers-glow-yellow-purple.webp' : 'icons/magic/movement/chevrons-down-yellow.webp' ) } get duration () { return (this?.data?.direction === 'Boost' ? 5 : 0) } get isTargeted () { return true } get basePowerPoints () { return 3 } getPrimaryEffectChanges () { let modValue = '2' if (this.data.raise && this.data.direction === 'Boost') { modValue = '4' } modValue = (this.data.direction === 'Boost' ? '+' : '-') + modValue const changes = [ { key: this.data.trait.diekey, value: modValue, priority: 0, mode: foundry.CONST.ACTIVE_EFFECT_MODES.ADD } ] if (this.data.direction === 'Lower' && this.data.mods.has('greater')) { changes.push({ key: this.data.trait.modkey, mode: foundry.CONST.ACTIVE_EFFECT_MODES.ADD, value: -2, priority: 0}) } return changes } async createSecondaryEffects (maintId) { const docs = await super.createSecondaryEffects(maintId) if (this.data.raise && this.data.direction === 'Lower') { const name = 'major ' + this.effectName const modValue = (this.data.direction === 'Boost' ? '+2': '-2') const changes = [ { key: this.data.trait.diekey, value: modValue, priority: 0, mode: foundry.CONST.ACTIVE_EFFECT_MODES.ADD } ] const doc = this.createEffectDocument(this.icon, name, changes) doc.duration.seconds = 594 doc.description = this.description + "

This is the raise effect which can be shaken off separately.

" doc.flags[moduleName].maintId = maintId docs.push(doc) } return docs } get effectName () { let name = `${this.data.direction} ${this.data.trait.name}` const nameMods = [] if (this.data.mods.has('greater')) { nameMods.push('Greater') } if (this.data.direction === 'Lower' && this.data.mods.has('strong')) { nameMods.push('Strong') } if (nameMods.length > 0) { name += ` (${nameMods.join(', ')})` } return name } get description () { let desc = super.description const amount = `${this.data.raise ? 2 : 1} die type${this.data.raise ? 's' :''}` desc += `

${this.data.direction === 'Boost' ? 'Raise' : 'Lower'} the target's ${this.data.trait.name} die type ${amount}.` if (this.data.mods.has('greater')) { if (this.data.direction === 'Boost') { desc += ` Additionally, the target gains a free ${this.data.trait.name} reroll once per ${this.data.raise ? 'action' : 'round'}.` } else { desc += ` Additionally, the target suffers a -2 penalty to their ${this.data.trait.name} rolls.` } } desc += '

' if (this.data.direction === 'Lower') { desc += `

At the end of the target's following turns, they attempt to shake off the affect with a Spirit${this.data.mods.has('strong') ? ' -2' : ''} roll as a free action. Success reduces the effect one die type. A raise completely shakes off the effect.

` } return desc } get modifiers () { const mods = super.modifiers mods.push( {name: 'Greater Boost/Lower Trailt', value: 2, id: 'greater', epic: true, effect: false}) mods.push( { name: 'Strong (lower only)', value: 1, id: 'strong', epic: false, effect: false } ) return mods } get menuInputs () { const inputs = super.menuInputs let traitOptions = ['Agility', 'Smarts', 'Spirit', 'Strength', 'Vigor'] const allSkills = new Set() 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.add(name) } } } traitOptions = traitOptions.concat(Array.from(allSkills).sort()) this.data.traits = traits inputs.push({type: 'select', label: 'Trait', options: traitOptions}) inputs.push({type: 'info', label: 'Boost or Lower?'}) inputs.push({type: 'radio', label: 'Boost', options: ['isBoost', true]}) inputs.push({type: 'radio', label: 'Lower', options: ['isBoost', false]}) return inputs } async parseValues () { await super.parseValues() this.data.trait = this.data.traits[this.data.values.shift()] this.data.values.shift() this.data.direction = this.data.values.shift() ? 'Boost' : 'Lower' } get powerPoints () { let total = super.powerPoints return total } } class BurstEffect extends PowerEffect { get name () { return 'Blast' } get icon () { return 'icons/magic/sonic/projectile-shock-wave-blue.webp' } get duration () { return 0 } get isTargeted () { return true } get usePrimaryEffect () { return false } get isDamaging () { return true } get basePowerPoints () { return 3 } get hasAoe () { return true } get modifiers () { const mods = super.modifiers mods.push( { name: 'Damage', value: 2, id: 'damage', epic: false, effect: false }, { name: 'Greater Burst', value: 4, id: 'greater', epic: true, effect: false }, ) return mods } get description () { const dmgDie = ( this.data.mods.has('greater') ? 4 : (this.data.mods.has('damage') ? 3 : 2)) + (this.data.raise ? 1 : 0) return super.description + `

The blast covers a Cone or Stream template and does ${dmgDie}d6 damage

` } } class ConfusionEffect extends PowerEffect { get name () { return 'Confusion' } get icon () { return 'icons/magic/control/hypnosis-mesmerism-swirl.webp' } get duration () { return 0 } get isTargeted () { return true } get usePrimaryEffect () { return false } get basePowerPoints () { return 2 } get hasAoe () { return true } get menuInputs () { const inputs = super.menuInputs inputs.push inputs.push({ type: 'select', label: 'Area of Effect', options: [ {html: 'Small Blast Template (0)', value: 's', selected: false}, {html: 'Medium Blast Template (0)', value: 'm', selected: true}, {html: 'Large Blast Template (+1)', value: 'l', selected: false}, ]}) return inputs } get modifiers () { const mods = super.modifiers mods.push( { name: 'Greater Confusion', value: 2, id: 'greater', epic: true, effect: false }, ) return mods } get powerPoints () { let total = super.powerPoints total += (this.data.aoe === 'l' ? 1 : 0) return total } get menuButtons () { const data = [ { label: 'Apply with Distracted', value: 'distracted' }, { label: 'Apply with Vulnerable', value: 'vulnerable' }, { label: 'Apply with both (raise)', value: 'raise' }, { label: 'Cancel', value: 'cancel' }, ] return data } async parseValues () { await super.parseValues() this.data.distracted = this.data.button === 'distracted' || this.data.button === 'raise' this.data.vulnerable = this.data.button === 'vulnerable' || this.data.button === 'raise' this.data.aoe = this.data.values.shift() } get description () { const size = ( this.data.aoe === 'l' ? 'LBT' : (this.data.aoe === 's' ? 'SBT' : 'MBT')) let effect = 'Vulnerable' if (this.data.raise) { effect = 'both Distracted and Vulnerable' } else if (this.data.distracted) { effect = 'Distracted' } if (this.data.mods.has('Greater')) { effect += ' as well as Shaken' } return super.description + `

The targets in the ${size} are ${effect}.

` } async createSecondaryEffects (maintId) { const docs = await super.createSecondaryEffects(maintId) if (this.data.distracted) { PowerEffect.getStatus('SWADE.Distr', 'Distracted', false).then(v => docs.push(v)) } if (this.data.distracted) { PowerEffect.getStatus('SWADE.Vuln', 'Vulnerable', false).then(v => docs.push(v)) } if (this.data.mods.has('greater')) { PowerEffect.getStatus('SWADE.Shaken', 'Shaken', false).then(v => docs.push(v)) } return docs } } class CurseEffect extends PowerEffect { get name () { return 'Curse' } get icon () { return 'icons/magic/control/voodoo-doll-pain-damage-purple.webp' } get duration () { return 500*24*60*6 } get isTargeted () { return true } get oneTarget () { return true } get isRaisable () { return false } get basePowerPoints () { return 5 } get modifiers () { const mods = super.modifiers mods.push({ name: 'Turn to Stone', value: 5, id: 'turntostone', epic: true, effect: false }) return mods } get description () { let desc = super.description desc += `

The victim must defend with a Spirit roll opposed by the caster's arcane skill roll. Failure means the victim suffers a level of Fatigue immediately.

` if (this.data.mods.has('turntostone')) { desc += `

On every following run the victim must make a Spirit roll or take a level of Fatigue. When Incapacitated, the victim turns to stone, with a Hardness equal to his Tougness.

` } else { desc += `

At sunset every day, the victim suffers a level of Fatigue. When Incapacitated by this, he makes a Vigor roll each day to avoid death.

` } desc += `

Breaking the curse: The curse can be lifted by the original caster at will, and ends if the caster is slain. Dispel at -2 also removes the curse, but each individual may only try once.

` return desc } } class DamageFieldEffect extends PowerEffect { get name () { return 'Damage Field' } get icon () { return 'icons/magic/defensive/shield-barrier-blades-teal.webp' } get duration () { return 5 } get basePowerPoints () { return 4 } get isTargeted () { return true } get oneTarget () { return true } get isRaisable () { return false } get modifiers () { const mods = super.modifiers mods.push({ name: 'Area of Effect', value: 2, id: 'aoe', epic: false, effect: false }) mods.push({ name: 'Damage', value: 2, id: 'damage', epic: false, effect: false }) mods.push({ name: 'Greater Damage Field', value: 4, id: 'greater', epic: true, effect: false }) mods.push({ name: 'Mobile', value: 2, id: 'mobile', epic: false, effect: false }) return mods } get description () { let desc = super.description let area = 'all adjacent creatures' let damage = '2d4' if (this.data.mods.has('greater')) { damage = '3d6 (heavy weapon)' } else if (this.data.mods.has('damage')) { damage = '2d6' } if (this.data.mods.has('aoe')) { area = 'all creatures within a MBT' } desc += `

At the end of the recipient's turn, ${area} automatically take ${damage} damage.` if (this.data.mods.has('mobile')) { desc += `The caster may detach the damage field from the recipient and move it up to his Smarts die type each round, as a limited free action.` } desc += '

' return desc } getPrimaryEffectChanges () { const base = 'flags.swade.auras.damagefield' const priority = 0 const mode = foundry.CONST.ACTIVE_EFFECT_MODES.OVERRIDE const changes = [ { key: `${base}.enabled`, value: true, priority, mode }, { key: `${base}.walls`, value: true, priority, mode }, { key: `${base}.color`, value: '#ffcc00', priority, mode }, { key: `${base}.alpha`, value: 0.1, priority, mode }, { key: `${base}.radius`, value: (this.data.mods.has('aoe') ? 1.5 : 0.5), priority, mode }, { key: `${base}.visibleTo`, value: [-1, 0, 1], priority, mode }, ] return changes } } class DarksightEffect extends PowerEffect { get name () { return 'Darksight' } get icon () { return 'icons/magic/perception/eye-ringed-glow-angry-small-teal.webp' } get duration () { return 600 } get basePowerPoints () { return 1 } get isTargeted () { return true } get hasAdditionalRecipients () { return true } get additionalRecipientCost () { return 1 } get modifiers () { const mods = super.modifiers mods.push({name: 'Greater Darksight', value: 2, id: 'greater', epic: true, effect: false}) return mods } get description () { let desc = super.description desc += '

' if (this.data.mods.has('greater')) { desc += `Can see in all darkness, ignoring all illumination penalties and 4 points of penalties from invisible creatures` } else if (this.data.raise) { desc += 'Can see in Pitch Darkness and ignore up to 6 points of illumination penalties' } else { desc += 'Can see in darkness and ignore 4 points of illumination penalties' } desc += '

' return desc } get effectName () { if (this.data.mods.has('greater')) { return 'Greater Darksight' } else if (this.data.raise) { return 'Major Darksight' } else { return 'Darksignt' } } } class DeflectionEffect extends PowerEffect { get name () { return 'Deflection' } get icon () { return 'icons/magic/defensive/shield-barrier-deflect-teal.webp' } get duration () { return 5 } get basePowerPoints () { return 2 } get isTargeted () { return true } get hasAdditionalRecipients () { return true } get additionalRecipientCost () { return 1 } get menuButtons () { const data = [ { label: 'Melee', value: 'melee' }, { label: 'Ranged', value: 'vulnerable' }, { label: 'Raise (both)', value: 'raise' }, { label: 'Cancel', value: 'cancel' }, ] return data } async parseValues () { await super.parseValues() this.data.affects = this.data.button === 'raise' ? 'all' : this.data.button } get effectName () { return `Deflection (${this.data.affects})` } get description () { return super.description + `

Attackers subtract -2 from ${this.data.affects} attacks when targeting this creature.

` } } class DetectConcealArcanaEffect extends PowerEffect { get name () { return 'Detect/Conceal Arcana' } get icon () { return this.data.detect ? 'icons/magic/perception/third-eye-blue-red.webp' : 'icons/magic/perception/silhouette-stealth-shadow.webp' } get duration () { return this.data.detect ? 5 : 600 } get basePowerPoints () { return 2 } get isTargeted () { return true } get hasAdditionalRecipients () { return true } get additionalRecipientCost () { return (this.data?.aoe || 0) > 0 ? 0 : 1 } get modifiers () { const mods = super.modifiers mods.push({name: 'Alignment Sense (detect)', value: 1, id: 'alignment', epic: false, effect: false}) mods.push({name: 'Identify (detect)', value: 1, id: 'identify', epic: false, effect: false}) mods.push({name: 'Strong (conceal)', value: 1, id: 'strong', epic: false, effect: false}) return mods } get hasAoe () { return true } get menuInputs () { const inputs = super.menuInputs inputs.push({ type: 'select', label: 'Area of Effect (conceal)', options: [ {html: 'None', value: 0, selected: true}, {html: 'Medium Blast Template (+1)', value: 1, selected: false}, {html: 'Large Blast Template (+2)', value: 2, selected: false}, ]}) inputs.push({type: 'info', label: 'Detect or Conceal?'}) inputs.push({type: 'radio', label: 'Detect', options: ['isDetect', true]}) inputs.push({type: 'radio', label: 'Conceal', options: ['isDetect', false]}) return inputs } async parseValues () { await super.parseValues() this.data.aoe = this.data.values.shift() this.data.values.shift() this.data.detect = this.data.values.shift() } get powerPoints () { return super.powerPoints + this.data.aoe } get effectName () { return `${this.data.detect ? 'Detect' : 'Conceal'} Arcana` } get description () { let desc = super.description if (this.data.detect) { desc += `

The recipient can see and detect all supernatural persons, objects, or effects in sight. This includes invisible foes, enchanted objects, and so on.` if (this.data.raise) { desc += `Since this was major Detect Arcana, the type of enchantments is also known.` } desc+= `

If cast to learn more about a creature, the caster learns active powers and arcane abilities.` if (this.data.raise) { desc += `As major Detect in this mode, the caster also learns any Weaknesses common to that creature type.` } if (this.data.mods.has('identify')) { desc += `

Items detected also give the recipient an idea of their powers and how to activate them.

` } if (this.data.mods.has('alignment')) { desc += `

The recipient can also detect the presence and location of supernatural good or evil within range, regardless of line of sight.

` } desc += `

Invisible Creatures: The recipient may also ignore ${this.data.raise ? 'all' : 'up to 4 points of'} penalties when attacking invisible or magically concealed foes.

` } else { let area = 'one item or being' if (this.data.aoe !== 0) { area = `everything in a sphere the size of a ${this.data.aoe === 1 ? 'Medium' : 'Large'} Blast Template` } desc += `

Conceal ${area} from the Detect Magic ability for one hour. Attempts to detect arcana suffer a ${(this.data.mods.has('strong') ? -2 : 0) + this.data.raise ? -2 : -4} penalty.` } return desc } } const PowerClasses = { "arcane-protection": ArcaneProtectionEffect, banish: BanishEffect, barrier: BarrierEffect, "beast-friend": BeastFriendEffect, blast: BlastEffect, blind: BlindEffect, bolt: BoltEffect, "boost-lower-trait": BoostLowerTraitEffect, "boost-trait": BoostLowerTraitEffect, burrow: BurrowEffect, burst: BurstEffect, confusion: ConfusionEffect, curse: CurseEffect, "damage-field": DamageFieldEffect, darksight: DarksightEffect, deflection: DeflectionEffect, "detect-conceal-arcana": DetectConcealArcanaEffect, "lower-trait": BoostLowerTraitEffect, } /* ---------------------------------------------------------------- */ export async function powerEffectManagementHook(effect, data, userId) { if (game.user.id !== userId) { return } const maintId = effect.getFlag(moduleName, 'maintainingId') if (!maintId) { return } const mutateOptions = { permanent: true, comparisonKeys: { ActiveEffect: 'id' } } const targetIds = effect.getFlag(moduleName, 'targetIds') || [] for (const targetId of targetIds) { const mutation = { embedded: { ActiveEffect: {} } } const target = canvas.tokens.get(targetId) if (!target) { continue } const effects = target.actor.effects.filter( e => e.getFlag(moduleName, 'maintId') === maintId) for (const effect of effects) { mutation.embedded.ActiveEffect[effect.id] = warpgate.CONST.DELETE } mutateOptions.description = `${effect.parent.name} is no longer ${effect.name} on ${target.name}` await warpgate.mutate(target.document, mutation, {}, mutateOptions) } } export async function powers (options = {}) { const token = 'token' in options ? options.token : null if (token === undefined || token === null) { ui.notifications.error('Please select one token to be the caster') return } const targets = 'targets' in options ? Array.from(options.targets) : [] const item = 'item' in options ? options.item : null const swid = options?.name || item?.system.swid || null if (swid in PowerClasses) { const runner = new PowerClasses[swid](token, targets) runner.powerEffect() return } ui.notifications.error(`No power effect found for ${name}`) }