441 lines
12 KiB
JavaScript
441 lines
12 KiB
JavaScript
/* 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 = `<form><h2>Increase Attributes</h2><p>(+1 pp each)</p>`;
|
|
html += attrList
|
|
.map(
|
|
(name) => `<div class="form-group">
|
|
<label><input type="checkbox" name="${name}">${name}</label>
|
|
</div>`,
|
|
)
|
|
.join('');
|
|
html += `<h2>Increase Skills</h2><p>(+1 pp each)</p>`;
|
|
html += skillList
|
|
.map(
|
|
(name) => `<div class="form-group">
|
|
<label><input type="checkbox" name="${name}">${name}</label>
|
|
</div>`,
|
|
)
|
|
.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 += `<p>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.</p>`;
|
|
if (this.data.mindRider) {
|
|
desc += `<p>The caster can sense and communicate through the ally.</p>`;
|
|
}
|
|
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 = `<p>Summon a clone of ${this.source.name} with the same number of
|
|
Power Points (after casting), but lowered skills, an Extra, and mundane
|
|
equipment.</p>`;
|
|
if (this.data.mindRider) {
|
|
desc += `<p>The caster can sense and communicate through the ally.</p>`;
|
|
}
|
|
}
|
|
return desc;
|
|
}
|
|
}
|