202 lines
7.2 KiB
JavaScript

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: `<p>Results of ${rollDesc[0].toUpperCase()}${rollDesc.slice(1)} roll${contentExtra}:</p>
<table><thead><tr><th>Token</th><th>Roll</th><th>Result</th></tr></thead><tbody>`,
};
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 +=
'<tr>' +
`<th>${token.name}</th>` +
`<td>${roll ? roll.total : '<i>Canceled</i>'}</td>` +
`<td>${textResult}</td>` +
'</tr>';
}
messageData.content += '</tbody></table>';
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 }],
});
},
});
}
}