206 lines
7.3 KiB
JavaScript
206 lines
7.3 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 = 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>`,
|
|
rolls: [],
|
|
};
|
|
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>';
|
|
if (roll) {
|
|
messageData.rolls.unshift(roll);
|
|
}
|
|
}
|
|
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 }],
|
|
});
|
|
},
|
|
});
|
|
}
|
|
}
|