import { moduleHelpers } from './globals.js'; export async function requestFearRollFromTokens(tokens, options = {}) { // tokens: list of tokens to request the roll from // options: // title: tile for the roll dialog. Will have "- {{ token name }}" appended // flavour: flavor text for the roll card. Defaults to title // fear: value of the fear modifier. Defaults to 0. Positive number. const requestingUser = game.user; const title = options?.title || `${requestingUser.name} requests a Fear check`; const flavour = options?.flavour || options?.flavor || title; const fear = options.fear || 0; const rollOpts = { title, flavour, mods: [{ label: 'Fear Penalty', value: Math.abs(fear) * -1, ignore: false }], }; return requestRollFromTokens(tokens, 'attribute', 'spirit', rollOpts); } export function firstOwner(doc) { // lifted from warpgate // https://github.com/trioderegion/warpgate/blob/master/src/scripts/module.js if (!doc) return undefined; const corrected = doc instanceof TokenDocument ? doc.actor : doc instanceof Token ? doc.document.actor : doc; const permissionObject = foundry.utils.getProperty(corrected ?? {}, 'ownership'); const playerOwners = Object.entries(permissionObject) .filter(([id, level]) => !game.users.get(id)?.isGM && game.users.get(id)?.active && level === 3) .map(([id]) => id); if (playerOwners.length > 0) { return game.users.get(playerOwners[0]); } return firstGM(); } export function firstGM() { // lifted from warpgate // https://github.com/trioderegion/warpgate/blob/master/src/scripts/module.js return game.users?.find((u) => u.isGM && u.active); } export async function requestRollFromTokens(tokens, rollType, rollDesc, options = {}) { // tokens: list of tokens to request a roll from // rollType: 'attribute' or 'skill // rollDesc: name of attribute or skill // options: // title: title for the roll dialog. Will have "- {{ token name }}" // appended // flavour: flavor text for the roll card. Defaults to title // targetNumber: defaults to 4 // mods: list of modifiers {label: "", value: 0, ignore: false} // modCallback: callback function that takes a token and returns a list of // modifiers in the same format as modifiers, above const requestingUser = game.user; const title = options?.title || `${requestingUser.name} requests a ${rollDesc} roll`; const flavour = options?.flavour || options?.flavor || title; const targetNumber = options?.targetNumber || 4; const promises = []; for (const token of tokens) { const owner = firstOwner(token.document); const rollOpts = { title: `${title} - ${token.name}`, targetNumber, flavour, }; const additionalMods = []; if ('mods' in options) { for (const mod of options.mods) { additionalMods.push(mod); } } if ('modCallback' in options) { const tokenMods = await options.modCallback(token); for (const tm of tokenMods) { additionalMods.push(tm); } } if (additionalMods.length > 0) { rollOpts.additionalMods = additionalMods; } promises.push( moduleHelpers.socket.executeAsUser( requestTokenRoll, owner.id, token.scene.id, token.id, rollType, rollDesc, rollOpts, ), ); } const results = (await Promise.allSettled(promises)).map((r) => r.value); const contentExtra = targetNumber === 4 ? '' : ` vs TN: ${targetNumber}`; const messageData = { flavor: flavour, speaker: { alias: 'Requested Roll Results' }, whisper: [...ChatMessage.getWhisperRecipients('GM'), requestingUser], content: `

Results of ${rollDesc[0].toUpperCase()}${rollDesc.slice(1)} roll${contentExtra}:

`, }; for (const result of results) { const token = game.scenes.get(result.sceneId).tokens.get(result.tokenId); const roll = result.result instanceof CONFIG.Dice.SwadeRoll ? result.result : CONFIG.Dice[result.result.class].fromData(result.result); roll.targetNumber = targetNumber; let textResult = ''; if (roll.successes === -1) { textResult = 'CRITICAL FAILURE'; } else if (roll.successes === 0) { textResult = 'failed'; } else if (roll.successes === 1) { textResult = 'success'; } else { textResult = `success and ${roll.successes - 1} raise${roll.successes > 2 ? 's' : ''}`; } messageData.content += '' + `` + `` + `` + ''; } messageData.content += '
TokenRollResult
${token.name}${roll ? roll.total : 'Canceled'}${textResult}
'; ChatMessage.create(messageData, {}); return results; } export async function requestTokenRoll(sceneId, tokenId, rollType, rollDesc, options) { const scene = game.scenes.get(sceneId); const token = scene.tokens.get(tokenId); let rollFunc = 'rollAttribute'; let rollId = rollDesc.toLowerCase(); if (rollType === 'skill') { rollFunc = 'rollSkill'; rollId = token.actor.items .filter((i) => i.type === 'skill') .find((i) => i.system.swid === rollDesc.toLowerCase() || i.name.toLowerCase() === rollDesc.toLowerCase())?.id; } const result = await token.actor[rollFunc](rollId, options); return { sceneId, tokenId, result }; } function _getSceneToken(sceneId, tokenId) { const scene = game.scenes.get(sceneId); const token = scene.tokens.get(tokenId); return token; } export async function addActiveEffectsToToken(sceneId, tokenId, effectDocuments) { const token = _getSceneToken(sceneId, tokenId); await token.actor.createEmbeddedDocuments('ActiveEffect', effectDocuments); } export async function deleteActiveEffectsFromToken(sceneId, tokenId, effectIds) { const token = _getSceneToken(sceneId, tokenId); await token.actor.deleteEmbeddedDocuments('ActiveEffect', effectIds); } export async function addItemsToToken(sceneId, tokenId, itemDocuments) { const token = _getSceneToken(sceneId, tokenId); await token.actor.createEmbeddedDocuments('Item', itemDocuments, { renderSheet: false }); } export async function deleteItemsFromActor(actorUuid, itemIds) { const actor = await fromUuid(actorUuid); await actor.deleteEmbeddedDocuments('Item', itemIds); } export async function updateOwnedToken(sceneId, tokenId, updates, options = {}) { const token = _getSceneToken(sceneId, tokenId); return token.update(updates, options); } export async function deleteToken(sceneId, tokenId) { const token = _getSceneToken(sceneId, tokenId); return token.delete(); } export function SwadeVAEbuttons(effect, buttons) { if (['Bound', 'Entangled'].includes(effect?.name)) { buttons.push({ label: 'Break Free (Athletics)', callback: function () { const skillId = effect.parent.items.find((i) => i.type === 'skill' && i.system.swid === 'athletics')?.id; effect.parent.rollSkill(skillId, { flavor: 'Breaking Free' }); }, }); buttons.push({ label: 'Break Free (Strength -2)', callback: function () { effect.parent.rollAttribute('strength', { flavor: 'Breaking Free', additionalMods: [{ label: 'Breaking Free with Strength', value: -2 }], }); }, }); } }