323 lines
9.3 KiB
JavaScript
323 lines
9.3 KiB
JavaScript
import { moduleName } from './globals.js'
|
|
|
|
const MAINTAIN_ICON = 'icons/magic/symbols/runes-star-blue.webp'
|
|
|
|
class PowerEffect {
|
|
constructor (token, targets) {
|
|
this.source = token
|
|
this.targets = targets
|
|
this.data = {}
|
|
}
|
|
|
|
static async getStatus (label, name, favorite = true) {
|
|
const effect = deepClone(
|
|
CONFIG.statusEffects.find(se => se.label === label))
|
|
effect.name = ('name' in effect ? effect.name : effect.label)
|
|
if (!('flags' in effect)) {
|
|
effect.flags = {}
|
|
}
|
|
if (favorite) {
|
|
if (!('swade' in effect.flags)) {
|
|
effect.flags.swade = {}
|
|
}
|
|
effect.flags.swade.favorite = true
|
|
}
|
|
effect.flags.core = { statusId: effect.id }
|
|
return effect
|
|
}
|
|
|
|
createEffectDocument (icon, name, changes = null) {
|
|
if (changes === null) {
|
|
changes = []
|
|
}
|
|
return {
|
|
icon,
|
|
name,
|
|
changes,
|
|
description: `<p>From <strong>${this.source.name}</strong> casting <em>${this.name}</em></p>`,
|
|
duration: { rounds: 99 },
|
|
flags: {
|
|
[moduleName]: {
|
|
powerEffect: true
|
|
},
|
|
swade: {
|
|
loseTurnOnHold: false,
|
|
expiration: CONFIG.SWADE.CONST.STATUS_EFFECT_EXPIRATION.StartOfTurnAuto
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
async applyActiveEffects (token, effectDocuments) {
|
|
const mutation = {
|
|
embedded: { ActiveEffect: {} }
|
|
}
|
|
const mutateOptions = {
|
|
permanent: true,
|
|
description: `${this.source.name} applying ${effectDocuments[effectDocuments.length - 1]?.name} to ${token.name}`
|
|
|
|
}
|
|
for (const effectDocument of effectDocuments) {
|
|
mutation.embedded.ActiveEffect[effectDocument.name] = effectDocument
|
|
}
|
|
await warpgate.mutate(token.document, mutation, {}, mutateOptions)
|
|
}
|
|
|
|
get name () { return 'Unknown Power' }
|
|
get effectName () { return this.name }
|
|
get icon () { return 'icons/magic/symbols/question-stone-yellow.webp' }
|
|
get duration () { return 5 }
|
|
get basePowerPoints () { return 0 }
|
|
get usePrimaryEffect () { return true }
|
|
get hasAdditionalRecipients () { return false }
|
|
get additionalRecipientCost () { return 0 }
|
|
get isTargeted () { return false }
|
|
get modifiers () {
|
|
return [
|
|
{ name: 'Glow',
|
|
id: 'glow',
|
|
value: 1,
|
|
epic: false,
|
|
effect: true,
|
|
icon: 'icons/magic/light/orb-shadow-blue.webp',
|
|
changes: [ { key: '@Skill{Stealth}[system.die.modifier]', value: -2,
|
|
priority: 0, mode: foundry.CONST.ACTIVE_EFFECT_MODES.ADD } ]
|
|
},
|
|
{ name: 'Shroud',
|
|
id: 'shroud',
|
|
value: 1,
|
|
epic: false,
|
|
effect: true,
|
|
icon: 'icons/magic/perception/shadow-stealth-eyes-purple.webp',
|
|
changes: [ { key: '@Skill{Stealth}[system.die.modifier]', value: 1,
|
|
priority: 0, mode: foundry.CONST.ACTIVE_EFFECT_MODES.ADD } ]
|
|
},
|
|
{ name: 'Hinder',
|
|
id: 'hinder',
|
|
value: 1,
|
|
epic: false,
|
|
effect: true,
|
|
icon: 'icons/magic/control/debuff-chains-shackle-movement-red.webp',
|
|
changes: [ { key: 'system.stats.speed.value', value: -2,
|
|
priority: 0, mode: foundry.CONST.ACTIVE_EFFECT_MODES.ADD } ]
|
|
},
|
|
{ name: 'Hurry',
|
|
id: 'hurry',
|
|
value: 1,
|
|
epic: false,
|
|
effect: true,
|
|
icon: 'icons/skills/movement/feet-winged-sandals-tan.webp',
|
|
changes: [ { key: 'system.stats.speed.value', value: 2,
|
|
priority: 0, mode: foundry.CONST.ACTIVE_EFFECT_MODES.ADD } ]
|
|
},
|
|
]
|
|
}
|
|
|
|
get menuData () {
|
|
return {
|
|
inputs: this.menuInputs,
|
|
buttons: this.menuButtons,
|
|
}
|
|
}
|
|
|
|
get menuInputs () {
|
|
const data = [
|
|
{ type: 'header', label: `${this.name} Effect` },
|
|
{ type: 'info', label: `Apply ${this.name} Effect` },
|
|
]
|
|
if (this.isTargeted) {
|
|
let label = `<strong>Targets:</strong> ${this.targets.map(t => t.name).join(',')}`
|
|
if (this.targets.length > 1 && this.hasAdditionalRecipients) {
|
|
label += ` (${this.targets.length - 1} additional recipients ` +
|
|
`+${this.additionalRecipientCost} ea.)`
|
|
}
|
|
data.push({
|
|
type: 'info',
|
|
label: label
|
|
})
|
|
}
|
|
for (const mod of this.modifiers) {
|
|
data.push({
|
|
type: 'checkbox',
|
|
label: (
|
|
`${mod.epic ? '⭐ ' : ''}${mod.name} ` +
|
|
`(${mod.value >= 0 ? '+' : ''}${mod.value})`
|
|
),
|
|
})
|
|
}
|
|
return data
|
|
}
|
|
|
|
get menuButtons () {
|
|
const data = [
|
|
{ label: 'Apply', value: 'apply' },
|
|
{ label: 'Apply with Raise', value: 'raise' },
|
|
{ label: 'Cancel', value: 'cancel' }
|
|
]
|
|
return data
|
|
}
|
|
|
|
get menuOptions () {
|
|
return {
|
|
title: `${this.name} Effect`,
|
|
defaultButton: 'Cancel',
|
|
options: {}
|
|
}
|
|
}
|
|
|
|
async powerEffect () {
|
|
const { buttons, inputs } = await warpgate.menu(
|
|
this.menuData, this.menuOptions
|
|
)
|
|
if (buttons && buttons !== 'cancel') {
|
|
this.data.button = buttons
|
|
this.data.values = inputs
|
|
await this.parseValues()
|
|
await this.apply()
|
|
await this.sideEffects()
|
|
await this.chatMessage()
|
|
}
|
|
}
|
|
|
|
async parseValues () {
|
|
this.data.rawValues = deepClone(this.data.values)
|
|
this.data.raise = this.data.button === 'raise'
|
|
for (let i = 0; i < 2; i++) {
|
|
this.data.values.shift()
|
|
}
|
|
if (this.isTargeted) {
|
|
this.data.values.shift()
|
|
}
|
|
this.data.mods = new Set()
|
|
for (const mod of this.modifiers) {
|
|
const modEnabled = this.data.values.shift()
|
|
if (modEnabled) {
|
|
this.data.mods.add(mod.id)
|
|
}
|
|
}
|
|
}
|
|
|
|
async createSecondaryEffects (maintId) {
|
|
const docs = []
|
|
for (const mod of this.modifiers) {
|
|
if (this.data.mods.has(mod.id) && mod.effect) {
|
|
const doc = this.createEffectDocument(mod.icon, mod.name, mod.changes)
|
|
if (this.duration === 0 && !this.usePrimaryEffect) {
|
|
// set secondary effects of instant spells to expire on victim's next
|
|
// turn
|
|
doc.duration.rounds = 1
|
|
doc.flags.swade.expiration = CONFIG.SWADE.CONST.STATUS_EFFECT_EXPIRATION.StartOfTurnAuto
|
|
} else {
|
|
doc.duration.seconds = 594
|
|
doc.flags[moduleName].maintId = maintId
|
|
}
|
|
docs.push(doc)
|
|
}
|
|
}
|
|
return docs
|
|
}
|
|
|
|
getPrimaryEffectChanges () {
|
|
const changes = [
|
|
{ key: 'flags.swade-mb-helpers.powerAffected', value: 1,
|
|
priority: 0, mode: foundry.CONST.ACTIVE_EFFECT_MODES.OVERRIDE } ]
|
|
return changes
|
|
}
|
|
|
|
async createPrimaryEffect (maintId) {
|
|
const doc = this.createEffectDocument(this.icon, this.effectName,
|
|
this.getPrimaryEffectChanges())
|
|
doc.flags[moduleName].maintId = maintId
|
|
doc.duration.seconds = 594
|
|
return doc
|
|
}
|
|
|
|
async createMaintainEffect (maintId) {
|
|
const doc = this.createEffectDocument(
|
|
MAINTAIN_ICON, `Maintaining ${this.name}`, [])
|
|
doc.duration.rounds = this.duration
|
|
doc.flags.swade.expiration = CONFIG.SWADE.CONST.STATUS_EFFECT_EXPIRATION.StartOfTurnPrompt
|
|
doc.flags.swade.loseTurnOnHold = true
|
|
doc.flags[moduleName].maintainingId = maintId
|
|
doc.flags[moduleName].targetIds = this.targets.map(t => t.id)
|
|
return doc
|
|
}
|
|
|
|
// eslint-disable-next-line no-unused-vars
|
|
async secondaryDocsForTarget(docs, target) {
|
|
return deepClone(docs)
|
|
}
|
|
|
|
// eslint-disable-next-line no-unused-vars
|
|
async primaryDocForTarget(doc, target) {
|
|
const newDoc = deepClone(doc)
|
|
return newDoc
|
|
}
|
|
|
|
async apply () {
|
|
const maintId = randomID()
|
|
const secondaryDocs = await this.createSecondaryEffects(maintId)
|
|
const primaryDoc = await this.createPrimaryEffect(maintId)
|
|
const maintainDoc = await this.createMaintainEffect(maintId)
|
|
for (const target of this.targets) {
|
|
const targetDocs = await this.secondaryDocsForTarget(secondaryDocs, target)
|
|
if (this.duration > 0 || this.usePrimaryEffect) {
|
|
targetDocs.push(await this.primaryDocForTarget(primaryDoc, target))
|
|
}
|
|
if (targetDocs.length > 0) {
|
|
await this.applyActiveEffects(target, targetDocs)
|
|
}
|
|
}
|
|
if (this.duration > 0) {
|
|
await this.applyActiveEffects(this.source, [maintainDoc])
|
|
}
|
|
}
|
|
|
|
async sideEffects () {
|
|
}
|
|
|
|
get powerPoints () {
|
|
let total = this.basePowerPoints
|
|
for (const mod of this.modifiers) {
|
|
if (this.data.mods.has(mod.id)) {
|
|
total += mod.value
|
|
}
|
|
}
|
|
if (this.targets.length > 1 && this.hasAdditionalRecipients) {
|
|
total += (this.targets.length - 1) * this.additionalRecipientCost
|
|
}
|
|
return total
|
|
}
|
|
|
|
async chatMessage () {
|
|
let text = `Cast ${this.name}`
|
|
if (this.targets.length > 0) {
|
|
text += ` on ${this.targets.map(t => t.name).join(', ')}`
|
|
}
|
|
return ChatMessage.create({
|
|
flavor: `Calculated cost: ${this.powerPoints} pp`,
|
|
speaker: ChatMessage.getSpeaker(this.source.actor),
|
|
content: text,
|
|
whisper: ChatMessage.getWhisperRecipients('GM', game.user.name),
|
|
}, { chatBubble: false });
|
|
}
|
|
}
|
|
|
|
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 effectName () {
|
|
return `${this.name} (${this.data.raise ? 'full' : 'half'} pace)`
|
|
}
|
|
}
|
|
|
|
export const PowerClasses = {
|
|
burrow: BurrowEffect
|
|
}
|
|
|