diff --git a/scripts/api.js b/scripts/api.js index 70a8a10..63353d9 100644 --- a/scripts/api.js +++ b/scripts/api.js @@ -1,9 +1,10 @@ import { helpers } from './helpers.js' +import { shim, log } from './shim.js' import { powerEffects } from './powerEffects.js' export class api { static registerFunctions () { - console.log('SWADE MB Helpers initialized') + log('SWADE MB Helpers initialized') api.globals() } @@ -11,11 +12,11 @@ export class api { globalThis.swadeMBHelpers = { DEBUG: true, powerEffects, - createEffectDocument: helpers.createEffectDocument, + createEffectDocument: shim.createEffectDocument, createMutationWithEffect: helpers.createMutationWithEffect, defaultMutationOptions: helpers.defaultMutationOptions, - getActorFolderByPath: helpers.getActorFolderByPath, - getActorsInFolder: helpers.getActorsInFolder, + getActorFolderByPath: shim.getActorFolderByPath, + getActorsInFolder: shim.getActorsInFolder, runOnTargetOrSelectedTokens: helpers.runOnTargetOrSelectedTokens } } diff --git a/scripts/powerEffects.js b/scripts/powerEffects.js index 6d1a64e..80e888f 100644 --- a/scripts/powerEffects.js +++ b/scripts/powerEffects.js @@ -1,4 +1,4 @@ -import { CONST, shim } from './shim.js' +import { CONST, log, shim } from './shim.js' class PowerEffect { constructor (token, targets) { @@ -40,7 +40,12 @@ class PowerEffect { } async powerEffect () { - this.prepMenu() + try { + await this.prepMenu() + } catch (e) { + log('Error preparing menu for power effect: ' + e.toString()) + return + } const { buttons, inputs } = await shim.warpgateMenu( this.menuData, this.menuOptions) this.buttons = buttons @@ -518,6 +523,132 @@ class SmiteEffect extends TargetedPowerEffect { } } +class SummonEffect extends PowerEffect { + ICON = 'icons/magic/symbols/runes-triangle-blue.webp' + + get actorFolder () { + return 'Summonables' + } + + get name () { + return 'Summon Creature' + } + + get durationRounds () { + return 5 + } + + async prepFolders () { + const folders = [] + const folderNames = [ + this.actorFolder, + `${this.actorFolder} - Default`, + `${this.actorFolder}/Default`, + `${this.actorFolder} - ${this.token.name}`, + `${this.actorFolder} - ${this.token.actor.name}`, + `${this.actorFolder}/${this.token.name}`, + `${this.actorFolder}/${this.token.actor.name}` + ] + for (const folderName of folderNames) { + const folder = shim.getActorFolderByPath(folderName) + if (folder) { + log(`Found actor folder ${folderName}`) + folders.push(folder) + } + } + if (folders.length > 1) { + folders.shift() + } + return folders + } + + async prepActors () { + const folders = await this.prepFolders() + const actors = {} + for (const folder of folders) { + const folderActors = shim.getActorsInFolder(folder) + for (const key in folderActors) { + actors[key] = folderActors[key] + } + } + return actors + } + + async prepMenu () { + this.menuData.inputs[1].label = `${this.token.name} is summoning...` + const actors = await this.prepActors() + if (Object.keys(actors).length < 1) { + shim.notifications.error('No summonables found') + throw new Error('No summonables found') + } + + function actorData (key) { + return { + value: actors[key].id, + html: key + } + } + + this.menuData.inputs.push({ + type: 'select', + label: 'Creature to summon', + options: Object.keys(actors).filter( + k => !k.includes('_template')).sort().map(actorData) + }) + this.menuData.inputs.push({ + type: 'number', + label: 'Number to spawn (+half base cost per)', + options: 1 + }) + } + + async prepResult () { + this.raise = (this.buttons === 'raise') + this.actorId = (this.inputs[this.inputIndex]) + this.number = (this.inputs[this.inputIndex + 1]) + this.actor = shim.actors.get(this.actorId) + this.icon = this.actor.prototypeToken.texture.src + this.protoDoc = await this.actor.getTokenDocument() + this.spawnOptions = { + controllingActor: this.token.actor, + duplicates: this.number, + crosshairs: { + icon: this.icon, + label: `Summon ${this.actor.name}`, + drawOutline: true, + rememberControlled: true + } + } + this.spawnMutation = { + actor: { + name: `${this.token.name}'s ${this.actor.name}` + }, + token: { + actorLink: false, + name: `${this.token.name}'s ${this.protoDoc.name}` + }, + embedded: { ActiveEffect: {} } + } + for (const effectDocument of this.effectDocs) { + this.spawnMutation.embedded.ActiveEffect[effectDocument.name] = effectDocument + } + } + + async applyResult () { + await shim.warpgateSpawn(this.protoDoc, this.spawnMutation, {}, this.spawnOptions) + } +} + +class SummonAllyEffect extends SummonEffect { + get name () { + return 'Summon Ally' + } + + get actorFolder () { + return `${super.actorFolder}/Summon Ally` + } +} + const PowerClasses = { blind: BlindEffect, 'boost/lower trait': BoostLowerTraitEffect, @@ -529,7 +660,8 @@ const PowerClasses = { invisibility: InvisibilityEffect, 'lower trait': BoostLowerTraitEffect, protection: ProtectionEffect, - smite: SmiteEffect + smite: SmiteEffect, + 'summon ally': SummonAllyEffect } export async function powerEffects (options = {}) { diff --git a/scripts/shim.js b/scripts/shim.js index 6fba75f..22d1571 100644 --- a/scripts/shim.js +++ b/scripts/shim.js @@ -24,6 +24,10 @@ export class shim { return game.user } + static get actors () { + return game.actors + } + static getStatus (label, name, favorite = true) { const effect = JSON.parse(JSON.stringify( CONFIG.statusEffects.find(se => se.label === label))) @@ -76,6 +80,53 @@ export class shim { static warpgateMenu (menuData, menuOptions) { return warpgate.menu(menuData, menuOptions) } + + static warpgateSpawn (...args) { + return warpgate.spawn(...args) + } + + static getActorFolderByPath (path) { + const names = path.split('/') + if (names[0] === '') { + names.shift() + } + let name = names.shift() + let folder = shim.folders.filter( + f => f.type === 'Actor' && !f.folder + ).find(f => f.name === name) + if (!folder) { return undefined } + while (names.length > 0) { + name = names.shift() + folder = folder.children.find(c => c.folder.name === name) + if (!folder) { return undefined } + folder = folder.folder + } + return folder + } + + static getActorsInFolder (inFolder) { + const prefixStack = [''] + const actors = {} + const folderStack = [inFolder] + while (folderStack.length > 0) { + const prefix = prefixStack.shift() + const folder = folderStack.shift() + for (const actor of folder.contents) { + if (shim.user.isGM || + actor.testUserPermission( + shim.user, CONST.FOUNDRY.DOCUMENT_OWNERSHIP_LEVELS.OBSERVER) + ) { + actors[`${prefix}${actor.name}`] = actor + } + } + for (const child of folder.children) { + const newPrefix = `${prefix}${child.folder.name} | ` + prefixStack.push(newPrefix) + folderStack.push(child.folder) + } + } + return actors + } } export function log (...args) {