/* globals Portal */ import { moduleName } from '../globals.js'; 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?.additionalAllies ?? 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)) { this.data.increasedTraitCount = 0; return; } const skillSet = new Set(); for (const skill of this.targetActor.items.filter((i) => i.type === 'skill')) { skillSet.add(skill.name); } for (const item of Object.values(this.data.embeddedUpdates.Item).filter((i) => i.type === 'skill')) { skillSet.add(item.name); } const skillList = Array.from(skillSet); skillList.sort(); const attrList = ['Agility', 'Smarts', 'Spirit', 'Strength', 'Vigor']; let html = `

Increase Attributes

(+1 pp each)

`; html += attrList .map( (name) => `
`, ) .join(''); html += `

Increase Skills

(+1 pp each)

`; html += skillList .map( (name) => `
`, ) .join(''); const formData = await Dialog.wait({ title: 'Select Trait increases', content: html, buttons: { submit: { label: 'Submit', callback: (html) => { const formElement = html[0].querySelector('form'); const formData = new FormDataExtended(formElement); return formData.object; }, }, cancel: { label: 'Cancel' }, }, }); const mode = CONST.ACTIVE_EFFECT_MODES.ADD; const value = 2; const priority = 0; let count = 0; this.data.increasedTraitChanges = []; for (const attr of attrList) { if (formData[attr]) { this.data.increasedTraitChanges.push({ key: `system.attributes.${attr.toLowerCase()}.die.sides`, value, mode, priority, }); count++; } } for (const skill of skillList) { if (formData[skill]) { this.data.increasedTraitChanges.push({ key: `@Skill{${skill}}[system.die.sides]`, value, mode, priority, }); count++; } } this.data.increasedTraitCount = count; } get powerPoints() { let value = super.powerPoints; value += this.data.increasedTraitCount; let halfValue = Math.ceil(value / 2); return value + (this.data.additionalAllies ?? 0) * halfValue; } async prePrep() {} async parseValues() { await super.parseValues(); this.data.actorUpdates = { name: `${this.source.actor.name}'s summoned ${this.targetActor.name}`, }; 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: {}, }; this.data.primaryEffectChanges = []; await this.prePrep(); await this.prepIncreasedTrait(); } getPrimaryEffectChanges() { return [...super.getPrimaryEffectChanges(), ...this.data.primaryEffectChanges]; } async createSecondaryEffects(maintId) { const effects = await super.createSecondaryEffects(maintId); if (this.data.mindRider) { const doc = this.enhanceSecondaryEffect( maintId, this.createEffectDocument('icons/magic/control/hypnosis-mesmerism-eye.webp', 'Mind Rider', []), ); doc.description = `The caster can communicate and sense through the ally`; effects.push(doc); } if ((this.data?.increasedTraitChanges?.length ?? 0) > 0) { const doc = this.enhanceSecondaryEffect( maintId, this.createEffectDocument(this.icon, 'Increased Trait', this.data.increasedTraitChanges), ); effects.push(doc); } return effects; } 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, { count: this.summonCount }) .texture(this.targetTokenDoc.texture.src) .spawn(); return spawned; } async apply() { await super.apply(); const maintainDoc = await this.createMaintainEffect(this.data.maintId); maintainDoc.flags[moduleName].targetIds = this.data.spawned.map((t) => t.id); if (this.duration > 0) { await this.applyActiveEffects(this.source, [maintainDoc]); } } get description() { let desc = super.description; desc += `

Summon a ${this.targetActor.prototypeToken.name} ally to serve the caster. The Ally acts on the caster's action card, and follows commands to the best of its ability.

`; if (this.data.mindRider) { desc += `

The caster can sense and communicate through the ally.

`; } return desc; } } export class SummonAllyEffect extends BaseSummonEffect { get name() { return 'Summon Ally'; } get basePowerPoints() { return 0; } get duration() { return 5; } get icon() { return 'icons/magic/control/silhouette-hold-beam-blue.webp'; } get isTargeted() { return false; } 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 []; } const edges = template.items.filter((i) => i.type === 'edge').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; if ('flight_template' in this.data.actors) { mods.push({ type: 'checkbox', default: false, name: 'Flight', id: 'flight', value: 2, epic: false, effect: false, }); } if ('combat-edge_template' in this.data.actors) { const { choices, effects, values } = this._edges; for (let i = 1; i <= 3; i++) { mods.push({ type: 'select', name: `Combat Edge #${i}`, id: `combatEdge${i}`, default: 'None', epic: false, choices, effects, values, }); } } return mods; } async prePrep() { await super.prePrep(); if (this.data.raise && this.data.actors['raise_template']) { const raiseTemplate = this.data.actors.raise_template; for (const item of raiseTemplate.items) { const raiseItemDoc = await raiseTemplate.getEmbeddedDocument('Item', item.id); this.data.embeddedUpdates.Item[item.name] = raiseItemDoc; } } if (this.targetActor.name !== 'Mirror Self') { return; } const mirrorActor = this.source.actor; mergeObject(this.data.actorUpdates, { system: mirrorActor.system .clone({ 'fatigue.value': 0, 'wounds.value': 0, 'wounds.max': 0, 'bennies.max': 0, 'bennies.value': 0, }) .toObject(), name: `Mirror ${mirrorActor.name}`, img: mirrorActor.img, }); this.data.actorUpdates.system.wildcard = false; mergeObject(this.data.tokenUpdates, { name: `Mirror ${this.source.name}`, texture: { src: this.source.document.texture.src, scaleX: this.source.document.texture.scaleX * -1, scaleY: this.source.document.texture.scaleY, }, }); this.data.mirrorChanges = []; for (const mirrorItem of mirrorActor.items) { if ( mirrorItem.type === 'power' && (mirrorItem.system?.swid === 'summon-ally' || mirrorItem.name === 'Summon Ally') ) { continue; } if (['weapon', 'armor', 'consumable', 'gear'].includes(mirrorItem.type)) { continue; } this.data.embeddedUpdates.Item[mirrorItem.name] = await mirrorActor.getEmbeddedDocument('Item', mirrorItem.id); if (mirrorItem.type === 'skill') { this.data.mirrorChanges.push({ key: `@Skill{${mirrorItem.name}}[system.die.sides]`, mode: CONST.ACTIVE_EFFECT_MODES.ADD, value: -2, priority: 0, }); } } } async parseValues() { await super.parseValues(); const template = this.data.actors['combat-edge_template']; for (let i = 1; i <= 3; i++) { const edgeName = this.data[`combatEdge${i}`]; if (edgeName === 'None') { continue; } const edgeId = template.items.find((i) => i.type === 'edge' && i.name === edgeName).id; const edge = await template.getEmbeddedDocument('Item', edgeId); this.data.embeddedUpdates.Item[edgeName] = edge; } if (this.data.flight && 'flight_template' in this.data.actors) { const flightTemplate = this.data.actors.flight_template; for (const item of flightTemplate.items) { const doc = await flightTemplate.getEmbeddedDocument('Item', item.id); this.data.embeddedUpdates.Item[item.name] = doc; } for (const effect of flightTemplate.effects.values()) { const doc = ActiveEffect.fromSource(effect); this.data.embeddedUpdates.ActiveEffect[effect.name] = doc; } } } async createSecondaryEffects(maintId) { const effects = await super.createSecondaryEffects(maintId); if ((this.data?.mirrorChanges?.length ?? 0) > 0) { const doc = this.enhanceSecondaryEffect( maintId, this.createEffectDocument( 'icons/magic/control/mouth-smile-deception-purple.webp', 'Mirror Self', this.data.mirrorChanges, ), ); doc.description = 'A mirror self ally has skills 1 die type worse than the caster'; effects.push(doc); } return effects; } get description() { let desc = super.description; if (this.targetActor.prototypeToken.name === 'Mirror Self') { desc = `

Summon a clone of ${this.source.name} with the same number of Power Points (after casting), but lowered skills, an Extra, and mundane equipment.

`; if (this.data.mindRider) { desc += `

The caster can sense and communicate through the ally.

`; } } return desc; } }