446 lines
14 KiB
JavaScript

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 += `<p>Hostile powers are at ${this._penaltyAmount} when
targeting or damaging the affected character.</p>`
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: 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 description () {
return super.description + `
<p>An opposed roll of the caster's skill vs the target's Spirit.
<strong>Success:</strong> Shaken, <strong>each Raise:</strong> 1 Wound
Incapacitation results in banishment to home plane.</p>
`
}
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 += `<p>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 += '</p>'
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 += '<p>Creatures will overcome instincts to follow orders.'
} else {
text += '<p>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 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 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 + `
<p>The blast covers a ${size} and does ${dmgDie}d6 damage</p>`
}
}
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
}
get description () {
return super.description +
`<p>${this.data.raise ? -4 : -2} penalty to all actions involving sight.</p>
<p>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.</p>`
}
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 +
"<p>This is the raise effect which can be shaken off separately.</p>"
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 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 Blast', value: 1, 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 + '<p>'
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 <em>disintegrating</em>. 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 += '</p>'
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 +
`<p>Meld into the ground. Move at ${this.data.raise ? 'full' : 'half'} pace. May not run.</p>`
if (this.data.mods.has('power')) {
text += `<p>Can <em>burrow</em> through solid stone, concrete, etc</p>`
}
return text
}
}
const PowerClasses = {
"arcane-protection": ArcaneProtectionEffect,
banish: BanishEffect,
barrier: BarrierEffect,
"beast-friend": BeastFriendEffect,
blast: BlastEffect,
blind: BlindEffect,
bolt: BoltEffect,
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}`)
}