diff --git a/src/module/powers/basePowers.js b/src/module/powers/basePowers.js index 537b63f..d5d902f 100644 --- a/src/module/powers/basePowers.js +++ b/src/module/powers/basePowers.js @@ -733,4 +733,171 @@ export class ActorFolderEffect extends PowerEffect { } return actors; } + + // eslint-disable-next-line no-unused-vars + actorValue(actor) { + return 0; + } + + getActors() { + this.data.actors = this.prepActors(); + const choices = {}; + const effects = {}; + const values = {}; + Object.keys(this.data.actors) + .filter((k) => !k.includes('_template')) + .sort() + .forEach((key) => { + const id = this.data.actors[key].id; + choices[id] = key; + effects[id] = null; + values[id] = this.actorValue(this.data.actors[key]); + }); + return { choices, effects, values }; + } + + get modifiers() { + const { choices, effects, values } = this.getActors(); + return [ + ...super.modifiers, + { + name: 'Select Creature', + id: 'actorId', + type: 'select', + choices, + effects, + values, + epic: false, + effect: false, + }, + ]; + } + + get spawnUpdates() { + const updates = { + actor: {}, + token: { + actorLink: false, + }, + embedded: { + ActiveEffect: {}, + Item: {}, + }, + }; + return updates; + } + + #documentFinder(documentType, oldDoc, newDoc) { + if (documentType === 'Item') { + return oldDoc.name.toLowerCase() === newDoc.name.toLowerCase() && oldDoc.type === newDoc.type; + } + return oldDoc.name.toLowerCase() === newDoc.name.toLowerCase(); + } + + async updateEmbedded(actor, newDocs) { + const adds = {}; + const updates = {}; + for (const documentType of Object.keys(newDocs ?? {})) { + const collection = actor.getEmbeddedCollection(documentType); + adds[documentType] = []; + updates[documentType] = []; + for (const newDocKey in newDocs[documentType]) { + const newDoc = newDocs[documentType][newDocKey]; + const oldDoc = collection.find((doc) => this.#documentFinder(documentType, doc, newDoc)); + if (oldDoc) { + const _id = oldDoc.id; + updates[documentType].push({ ...newDoc, _id }); + } else { + adds[documentType].push(newDoc); + } + } + const updateOpts = {}; + if (documentType === 'Item') { + updateOpts.renderSheet = null; + } + try { + if (adds[documentType].length > 0) { + actor.createEmbeddedDocuments(documentType, adds[documentType], updateOpts); + } + } catch (e) { + log('ERROR', e); + } + try { + if (updates[documentType].length > 0) { + actor.updateEmbeddedDocuments(documentType, updates[documentType], updateOpts); + } + } catch (e) { + log('ERROR', e); + } + } + } + + async parseValues() { + await super.parseValues(); + this.data.maintid = randomID(); + this.targetActor = await game.actors.get(this.data.actorId); + this.targetTokenDoc = await this.targetActor.getTokenDocument(); + const sourceUpdates = { + delta: { + ownership: { + [game.user.id]: CONST.DOCUMENT_PERMISSION_LEVELS.OWNER, + }, + }, + }; + this.targetTokenDoc.updateSource(sourceUpdates); + } + + async spawn() { + this.targetTokenDoc.updateSource({ + x: this.source.x, + y: this.source.y, + elevation: this.source.elevation, + }); + return this.source.scene.createEmbeddedDocuments('Token', [this.targetTokenDoc]); + } + + async apply() { + this.data.spawned = await this.spawn(); + const updates = this.spawnUpdates; + const secondaryDocs = await this.createSecondaryEffects(this.data.maintId); + const primaryDoc = await this.createPrimaryEffect(this.data.maintId); + const promises = []; + for (const token of this.data.spawned) { + if (updates?.token) { + promises.push(token.update(updates.token)); + } + if (updates?.actor) { + promises.push(token.actor.update(updates.actor)); + } + if (updates?.embedded) { + promises.push(this.updateEmbedded(token.actor, updates.embedded)); + } + const activeEffects = await this.secondaryDocsForTarget(secondaryDocs, token); + activeEffects.push(await this.primaryDocForTarget(primaryDoc, token)); + promises.push(token.actor.createEmbeddedDocuments('ActiveEffect', activeEffects)); + } + const maintainDoc = await this.createMaintainEffect(this.data.maintId); + if (this.duration > 0) { + promises.push(this.applyActiveEffects(this.source, [maintainDoc])); + } + await Promise.all(promises); + } + + async sideEffects() { + if (this.data.fatigue) { + for (const target of this.data.spawned) { + const actor = target.actor; + const update = { + system: { + fatigue: { + value: actor.system.fatigue.value + 1, + }, + }, + }; + if (actor.system.fatigue.value < actor.system.fatigue.max) { + await actor.update(update); + } + } + } + } } diff --git a/src/module/powers/shapeChange.js b/src/module/powers/shapeChange.js index 67d40d8..7d706c0 100644 --- a/src/module/powers/shapeChange.js +++ b/src/module/powers/shapeChange.js @@ -1,3 +1,4 @@ +import { moduleName } from '../globals.js'; import { ActorFolderEffect } from './basePowers.js'; export class ShapeChangeEffect extends ActorFolderEffect { @@ -17,6 +18,10 @@ export class ShapeChangeEffect extends ActorFolderEffect { return true; } + get oneTarget() { + return true; + } + get isRaisable() { return true; } @@ -33,48 +38,24 @@ export class ShapeChangeEffect extends ActorFolderEffect { return 'Morphables'; } - getShapeChangeActors() { - this.data.actors = this.prepActors(); - const choices = {}; - const effects = {}; - const values = {}; - Object.keys(this.data.actors) - .filter((k) => !k.includes('_template')) - .sort() - .forEach((key) => { - const id = this.data.actors[key].id; - const size = this.data.actors[key].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; - } - choices[id] = key; - effects[id] = null; - values[id] = value; - }); - return { choices, effects, values }; + 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() { - const { choices, effects, values } = this.getShapeChangeActors(); return [ ...super.modifiers, - { - name: 'Shape Change Into', - id: 'actorId', - type: 'select', - choices, - effects, - values, - epic: false, - effect: false, - }, { name: 'Duration', type: 'checkbox', @@ -84,7 +65,7 @@ export class ShapeChangeEffect extends ActorFolderEffect { effect: false, }, { - name: 'Transform', + name: 'Transform Other?', type: 'select', default: 'none', id: 'transform', @@ -100,6 +81,87 @@ export class ShapeChangeEffect extends ActorFolderEffect { ]; } + 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.srcTokenId': this.target.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 = 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() { + this.targetTokenDoc.updateSource({ + x: this.target.x, + y: this.target.y, + elevation: this.target.elevation, + }); + return this.source.scene.createEmbeddedDocuments('Token', [this.targetTokenDoc]); + } + + 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; + } + get description() { let desc = super.description; return desc; diff --git a/src/templates/powerDialog.html b/src/templates/powerDialog.html index 7f62474..46d5c99 100644 --- a/src/templates/powerDialog.html +++ b/src/templates/powerDialog.html @@ -17,7 +17,7 @@ {{#if targets.length}}

Targets: - {{#each targets}}{{#if @index}}, {{/if}}{{this}}{{/each}} + {{#each targets}}{{#if @index}}, {{/if}}{{{this}}}{{/each}} {{#if recipients.cost}}
({{#if recipients.epic}}⭐ {{/if}}Additional Recipients {{recipients.cost}}pp each × {{recipients.count}} = {{recipients.total}})