import { log, moduleHelpers, moduleName } from '../globals.js'; import { firstOwner, updateOwnedToken } from '../helpers.js'; import { ActorFolderEffect } from './basePowers.js'; export class ShapeChangeEffect extends ActorFolderEffect { get name() { return 'Shape Change'; } 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 0; } get hasRange() { return false; } get actorFolderBase() { return 'Morphables'; } actorValue(actor) { const size = actor.system.stats.size; let value = 3; if (size >= 5) { value = 15; } else if (size >= 3) { value = 11; } else if (size >= 1) { value = 8; } else if (size >= 0) { value = 5; } return value; } get modifiers() { return [ ...super.modifiers, { name: 'Duration', type: 'checkbox', value: 1, id: 'duration', epic: false, effect: false, }, { name: 'Transform Other?', type: 'select', default: 'none', id: 'transform', epic: true, choices: { none: 'None', touch: '⭐ Transform (touch)', smarts: '⭐ Transform (smarts)', }, effects: { none: null, touch: null, smarts: null }, values: { none: 0, touch: 2, smarts: 3 }, }, ]; } async parseValues() { await super.parseValues(); this.target = this?.targets?.[0] ?? this.source; this.data.actorUpdates = { name: `${this.target.actor.name} (${this.targetActor.name} form)`, system: { wildcard: this.target.actor.system.wildcard, attributes: {}, }, }; for (const stat of ['smarts', 'spirit']) { 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; } for (const item of this.target.actor.items.filter( (i) => (i.type === 'skill' && ['smarts', 'spirit'].includes(i.system.attribute)) || ['power', 'edge', 'hindrance', 'action'].includes(i.type), )) { const doc = await this.target.actor.getEmbeddedDocument('Item', item.id); this.data.embeddedUpdates.Item[item.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 `Shape Change into ${this.targetActor.prototypeToken.name}`; } getPrimaryEffectChanges() { const changes = super.getPrimaryEffectChanges(); if (this.data.raise) { for (const stat of ['vigor', 'strength']) { changes.push({ key: `system.attributes.${stat}.die.sides`, mode: CONST.ACTIVE_EFFECT_MODES.ADD, value: 2, priority: 0, }); } } return changes; } 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 += `

The caster ${this.data.transform === 'none' ? 'transforms' : 'causes the target to transform'} into a ${this.targetActor.name}.

`; return desc; } } export async function shapeChangeTokenDeleteHandler(token, options, userId) { log('TOKEN DELETED |', token, options, userId); const sourceInfo = token.getFlag(moduleName, 'shapeChange'); if (!sourceInfo?.srcTokenId) { return; } if (sourceInfo.srcTokenSceneId !== token.parent.id) { return; } const srcToken = await fromUuid(sourceInfo.srcTokenUuid); const size = token.parent.dimensions.size; const owner = firstOwner(srcToken); const updates = { x: token.x - ((srcToken.width - token.width) * size) / 2, y: token.y - ((srcToken.height - token.height) * size) / 2, elevation: token.elevation, hidden: token.hidden, }; moduleHelpers.socket.executeAsUser(updateOwnedToken, owner.id, srcToken.parent.id, srcToken.id, updates, { animate: false, }); }