From a8354b49835cd0327f6d7676926dc31bcac6c206 Mon Sep 17 00:00:00 2001 From: Mike Bloy Date: Sun, 2 Jun 2024 15:30:47 -0500 Subject: [PATCH] add baleful polymorph --- src/module/powers/balefulPolymorph.js | 196 ++++++++++++++++++++++++++ src/module/powers/powers.js | 2 + 2 files changed, 198 insertions(+) create mode 100644 src/module/powers/balefulPolymorph.js diff --git a/src/module/powers/balefulPolymorph.js b/src/module/powers/balefulPolymorph.js new file mode 100644 index 0000000..9558bb0 --- /dev/null +++ b/src/module/powers/balefulPolymorph.js @@ -0,0 +1,196 @@ +import { moduleHelpers, moduleName } from '../globals.js'; +import { firstOwner, updateOwnedToken } from '../helpers.js'; +import { ActorFolderEffect } from './basePowers.js'; + +export class BalefulPolymorphEffect extends ActorFolderEffect { + get name() { + return 'Baleful Polymorph'; + } + + get icon() { + return 'icons/magic/control/silhouette-hold-change-blue.webp'; + } + + get duration() { + return this.data.duration ? 50 : 5; + } + + get isTargeted() { + return true; + } + + get oneTarget() { + return true; + } + + get isRaisable() { + return true; + } + + get basePowerPoints() { + return 3; + } + + get hasRange() { + return false; + } + + get actorFolderBase() { + return 'Morphables'; + } + + actorValue(actor) { + const size = actor.system.stats.size; + const targetSize = this.targets[0].actor.system.stats.size; + return Math.abs(targetSize - size); + } + + get modifiers() { + return [ + ...super.modifiers, + { + name: 'Duration', + type: 'checkbox', + value: 2, + id: 'duration', + epic: true, + effect: false, + }, + { + name: 'Victim critical failure', + type: 'checkbox', + default: false, + id: 'critfail', + epic: false, + value: 0, + effect: false, + }, + ]; + } + + async parseValues() { + await super.parseValues(); + this.target = this?.targets?.[0]; + this.data.actorUpdates = { + name: `${this.target.actor.name} (${this.targetActor.name} form)`, + system: { + wildcard: this.target.actor.system.wildcard, + attributes: {}, + }, + }; + const attrList = ['spirit']; + if (!this.data.critfail) { + attrList.push('smarts'); + } + for (const stat of attrList) { + this.data.actorUpdates.system.attributes[stat] = { + die: this.target.actor.system.attributes[stat].die, + 'wild-die': this.target.actor.system.attributes[stat]['wild-die'], + }; + } + this.data.tokenUpdates = { + flags: { + [moduleName]: { + 'shapeChange.srcTokenUuid': this.target.document.uuid, + 'shapeChange.srcTokenId': this.target.document.id, + 'shapeChange.srcTokenSceneId': this.target.scene.id, + }, + }, + actorLink: false, + name: `${this.target.name} (${this.targetActor.prototypeToken.name} form)`, + disposition: this.target.document.disposition, + sight: { + enabled: true, + }, + }; + this.data.embeddedUpdates = { + ActiveEffect: {}, + Item: {}, + }; + for (const effect of this.target.actor.effects) { + const doc = deepClone(await this.target.actor.getEmbeddedDocument('ActiveEffect', effect.id)); + this.data.embeddedUpdates.ActiveEffect[effect.name] = doc; + } + } + + async spawn() { + const target = this.target.document; + const size = target.parent.dimensions.size; + const protoWidth = this.targetActor.prototypeToken.width; + const protoHeight = this.targetActor.prototypeToken.height; + this.targetTokenDoc.updateSource({ + x: target.x - ((protoWidth - target.width) * size) / 2, + y: target.y - ((protoHeight - target.height) * size) / 2, + elevation: target.elevation, + hidden: target.hidden, + }); + return this.source.scene.createEmbeddedDocuments('Token', [this.targetTokenDoc]); + } + + async apply() { + await super.apply(); + const maintainDoc = await this.createMaintainEffect(this.data.maintId); + maintainDoc.flags[moduleName].targetIds = this.data.spawned.map((t) => t.id); + maintainDoc.flags[moduleName].shapeChangeSourceId = this.target.id; + maintainDoc.flags[moduleName].shapeChangeTempTokenId = this.data.spawned[0].id; + let maintainer = this.source; + if (this.source.id === this.target.id) { + maintainer = this.data.spawned[0]; + } + await this.applyActiveEffects(maintainer, [maintainDoc]); + } + + get effectName() { + return `Baleful Polymorph into ${this.targetActor.prototypeToken.name}`; + } + + 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 sideEffects() { + const owner = firstOwner(this.target); + moduleHelpers.socket.executeAsUser( + updateOwnedToken, + owner.id, + this.target.document.parent.id, + this.target.document.id, + { hidden: true, x: 0, y: 0 }, + { animate: false }, + ); + } + + get description() { + let desc = super.description; + desc += `

On losing an opposed roll vs the victim's Spirit, the victim + is transformed, taking the form and abilities of a ${this.targetActor.name} + but retaining their ${this.data.critfail ? '' : 'Smarts and '}Spirit. `; + if (this.data.critfail) { + desc += `The victim believes they are the animal for + the duration of the power.`; + } + desc += `

The victim may attempt to shake off the effect with a Spirit + roll at ${this.data.raise ? -4 : -2} at the end of subsequent turns.`; + return desc; + } + + get primaryEffectButtons() { + const buttons = super.primaryEffectButtons; + const modvalue = this.data.raise ? -4 : -2; + const mods = []; + mods.push({ label: 'Strong', value: modvalue }); + buttons.push({ + label: `Shake off (Spirit ${modvalue})`, + type: 'trait', + rollType: 'attribute', + rollDesc: 'Spirit', + flavor: 'Success shakes off the effects of sloth', + mods, + }); + return buttons; + } +} diff --git a/src/module/powers/powers.js b/src/module/powers/powers.js index 881bf54..c0ede4d 100644 --- a/src/module/powers/powers.js +++ b/src/module/powers/powers.js @@ -1,6 +1,7 @@ import { moduleName, moduleHelpers } from '../globals.js'; import { firstOwner, deleteActiveEffectsFromToken, deleteToken } from '../helpers.js'; import { ArcaneProtectionEffect } from './arcaneProtection.js'; +import { BalefulPolymorphEffect } from './balefulPolymorph.js'; import { BanishEffect } from './banish.js'; import { BarrierEffect } from './barrier.js'; import { BeastFriendEffect } from './beastFriend.js'; @@ -54,6 +55,7 @@ import { SlumberEffect } from './slumber.js'; const PowerClasses = { 'arcane-protection': ArcaneProtectionEffect, + 'baleful-polymorph': BalefulPolymorphEffect, banish: BanishEffect, barrier: BarrierEffect, 'beast-friend': BeastFriendEffect,