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) } getPrimaryEffectDescription () { let text = super.getPrimaryEffectDescription() text += `

Hostile powers are at ${this._penaltyAmount} targeting or damaging this 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 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: 1, selected: false}, {html: 'Large Blast Template (+3)', value: 1, selected: false}, ]}) return inputs } get powerPoints () { let total = super.powerPoints total += this.data.aoe return total } get chatMessageEffects () { const list = [] 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 } list.push("Opposed Roll: caster's arcane skill vs target's Spirit.") list.push("Success: Shaken, Each Raise: 1 Wound") list.push("If target incapacitated by this, banishment to home plane") 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) } getPrimaryEffectDescription () { let text = super.getPrimaryEffectDescription() 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 } get chatMessageEffects () { const list = [] list.push( `The Barrier is ${this._height} tall and ${this._length} long, of hardness ${this._hardness}.` ) if (this.data.mods.has('deadly')) { list.push('Deadly: 2d6 to anyone who contacts') } else if (this.data.mods.has('damage')) { list.push('Damage: 2d4 to anyone who contacts') } if (this.data.mods.has('shaped')) { list.push('Shaped (circle, square, or rectangle)') } if (this.data.mods.has('size')) { list.push('Size - length and height doubled') } return list } } 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 false } get isDamaging () { return true } get basePowerPoints () { return 2 } get usePrimaryEffect () { return false } 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 } getPrimaryEffectDescription () { let text = super.getPrimaryEffectDescription() 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('bestiarium')) { text += ' The caster may even effect magical beasts.' } return text } get chatMessageEffects () { const list = [] if (this.data.mods.has('bestiarium')) { list.push('Bestiarium, affect magical beasts with animal intelligence') } if (this.data.mods.has('mindrider')) { list.push('Mind rider. Can communicate and sense through any befrended beasts') } return list } } 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 false } get isDamaging () { return true } get basePowerPoints () { return 3 } 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: 1, 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 chatMessageEffects () { const list = [] switch (this.data.aoe) { case 's': list.push('SBT'); break; case 'm': list.push('MBT'); break; case 'l': list.push('LBT'); break; } if (this.data.mods.has('greater')) { list.push('Greater Blast: 4d6 damage') } else if (this.data.mods.has('damage')) { list.push('Damaging: 3d6 damage') } else { list.push('2d6 damage') } return list } } 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 basePowerPoints () { return 2 } getPrimaryEffectChanges () { const changes = [ { key: 'system.stats.globalMods.trait', value: -2, priority: 0, mode: foundry.CONST.ACTIVE_EFFECT_MODES.ADD } ] return changes } getPrimaryEffectDescription() { return super.getPrimaryEffectDescription() + `

${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.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 } if (this.data.mods.has('strong')) { list.push('Strong') } return list } } 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)` } getPrimaryEffectDescription() { let text = super.getPrimaryEffectDescription() + `

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 } get chatMessageEffects () { const list = super.chatMessageEffects if (this.data.mods.has('Power')) { list.push('Power: Burrow through solid stone, concrete') } return list } } const PowerClasses = { "arcane-protection": ArcaneProtectionEffect, banish: BanishEffect, barrier: BarrierEffect, "beast-friend": BeastFriendEffect, blast: BlastEffect, blind: BlindEffect, burrow: BurrowEffect, } /* ---------------------------------------------------------------- */ 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}`) }