diff --git a/src/module/powers/powers.js b/src/module/powers/powers.js index 60b8ae9..236c568 100644 --- a/src/module/powers/powers.js +++ b/src/module/powers/powers.js @@ -55,6 +55,7 @@ import { SlumberEffect } from './slumber.js'; import { SmiteEffect } from './smite.js'; import { SoundSilenceEffect } from './soundSilence.js'; import { SpeakLanguageEffect } from './speakLanguage.js'; +import { StunEffect } from './stun.js'; const PowerClasses = { 'arcane-protection': ArcaneProtectionEffect, @@ -130,6 +131,7 @@ const PowerClasses = { 'sound-silence': SoundSilenceEffect, sound: SoundSilenceEffect, 'speak-language': SpeakLanguageEffect, + stun: StunEffect, }; /* ---------------------------------------------------------------- */ diff --git a/src/module/powers/stun.js b/src/module/powers/stun.js new file mode 100644 index 0000000..77c7372 --- /dev/null +++ b/src/module/powers/stun.js @@ -0,0 +1,65 @@ +import { PowerEffect } from './basePowers.js'; + +export class StunEffect 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 modifiers() { + const mods = super.modifiers; + mods.push({ + type: 'select', + default: 'none', + name: 'Area of Effect', + id: 'aoe', + epic: false, + choices: { + none: 'None', + mbt: 'Medium Blast Template', + lbt: 'Large Blast Template', + }, + effects: { none: null, mbt: null, lbt: null }, + values: { none: 0, mbt: 2, lbt: 3 }, + }); + return mods; + } + + get description() { + return ( + super.description + + ` +

The target(s) must make a Vigor roll${this.data.raise ? ' at -2' : ''} + or be Stunned.

` + ); + } + + async createSecondaryEffects(maintId) { + const docs = await super.createSecondaryEffects(maintId); + docs.push(await PowerEffect.getStatus('SWADE.Stunned', 'Stunned', false)); + return docs; + } +} diff --git a/src/module/powers/summon.js b/src/module/powers/summon.js new file mode 100644 index 0000000..c0657e5 --- /dev/null +++ b/src/module/powers/summon.js @@ -0,0 +1,186 @@ +/* globals Portal */ +import { ActorFolderEffect } from './basePowers.js'; + +class BaseSummonEffect extends ActorFolderEffect { + get name() { + return 'Base Summon'; + } + + get actorFolderBase() { + return 'Summonables'; + } + + get values() { + return {}; + } + + get hasIncreasedTrait() { + return true; + } + + get summonCount() { + return 1 + (this?.data?.additional ?? 0); + } + + get additionalName() { + return 'Additional Allies'; + } + + get additionalDesc() { + return '(half base cost (round up))'; + } + + get additionalEpic() { + return true; + } + + get modifiers() { + const mods = super.modifiers; + if (this.hasIncreasedTrait) { + mods.push({ + type: 'checkbox', + name: 'Increased Trait (+1 per trait)', + id: 'increasedTrait', + value: 0, + effect: false, + epic: false, + default: false, + }); + } + mods.push({ + type: 'number', + name: `${this.additionalName} ${this.additionalDesc}`, + value: 0, + id: 'additionalAllies', + default: 0, + epic: this.additionalEpic, + effect: false, + }); + mods.push({ + type: 'checkbox', + name: 'Mind Rider', + id: 'mindRider', + value: 1, + effect: false, + epic: false, + default: false, + }); + return mods; + } + + actorValue(actor) { + const values = this.values; + if (actor.name.toLowerCase() in values) { + return values[actor.name.toLowerCase()]; + } + return 0; + } + + async prepIncreasedTrait() { + if (!(this.hasIncreasedTrait && this?.data?.increasedTrait)) { + return; + } + } + + async parseValues() { + await super.parseValues(); + this.data.actorUpdates = { + name: `${this.source.actor.name}'s summoned ${this.targetActor.name}`, + system: { + wildcard: this.source.actor.system.wildcard, + attributes: {}, + }, + }; + this.data.tokenUpdates = { + actorLink: false, + name: `${this.source.name}'s ${this.targetActor.prototypeToken.name}`, + disposition: this.source.document.disposition, + sight: { + enabled: true, + }, + }; + this.data.embeddedUpdates = { + ActiveEffect: {}, + Item: {}, + }; + await this.prepIncreasedTrait(); + } + + get spawnUpdates() { + const updates = super.spawnUpdates; + mergeObject(updates.actor, this.data.actorUpdates); + mergeObject(updates.token, this.data.tokenUpdates); + mergeObject(updates.embedded, this.data.embeddedUpdates); + return updates; + } + + async spawn() { + const spawned = await new Portal() + .addCreature(this.targetTokenDoc) + .texture(this.targetTokenDoc.texture.src) + .spawn(); + return spawned; + } +} + +export class SummonAllyEffect extends BaseSummonEffect() { + get name() { + return 'Summon Ally'; + } + + get basePowerPoints() { + return 0; + } + + get values() { + return { + attendant: 1, + bodyguard: 3, + sentinel: 5, + 'mirror self': 7, + }; + } + + get _edges() { + const template = this.data.actors['combat-edge_template']; + if (!template) { + return []; + } + this.data.combatEdges = template.items.filter((i) => i.type === 'edge'); + const edges = this.data.CombatEdges.map((i) => i.name); + edges.sort(); + edges.unshift('None'); + const choices = {}; + const effects = {}; + const values = {}; + edges.forEach((edge) => { + choices[edge] = edge; + effects[edge] = null; + values[edge] = edge === 'None' ? 0 : 1; + }); + return { choices, effects, values }; + } + + get modifiers() { + const mods = super.modifiers; + mods.push({ + type: 'checkbox', + default: false, + name: 'Combat Edge (+1 per edge, up to 3)', + id: 'combatEdge', + value: 0, + epic: false, + effect: false, + }); + mods.push({ + type: 'checkbox', + default: false, + name: 'Flight', + id: 'flight', + value: 2, + epic: false, + effect: false, + }); + return mods; + } +}