Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature rsr 5e compatibility #18

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
211 changes: 117 additions & 94 deletions scripts/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ Hooks.once('init', () => {
game.modules.get(moduleID).api = Multiattack5e;
ma5e = game.modules.get(moduleID).api;

// Determine active roller modules
if (game.modules.get("ready-set-roll-5e")?.active) roller = "rsr";

// Register module settings.
game.settings.register(moduleID, 'condenseChatMessagesEnabled', {
name: ma5eLocalize('settings.condenseChatMessagesEnabled.name'),
Expand Down Expand Up @@ -72,6 +75,15 @@ Hooks.once('init', () => {
default: 'enabled'
});

game.settings.register(moduleID, 'readySetRollDSN', {
name: ma5eLocalize('settings.readySetRollDSN.name'),
hint: ma5eLocalize('settings.readySetRollDSN.hint'),
scope: 'world',
config: game.modules.get('dice-so-nice')?.active && game.modules.get('ready-set-roll-5e')?.active,
type: Boolean,
default: true
});

});


Expand Down Expand Up @@ -179,115 +191,126 @@ class Multiattack5e {
const itemArray = isIDs ? itemIDarray : itemNameArray;
if (!itemArray.length) return;

// Assume messageData if none provided.
if (!messageData) {
messageData = {
speaker: ChatMessage.getSpeaker({ actor }),
type: CONST.CHAT_MESSAGE_TYPES.ROLL,
if (roller === 'core') {
// Assume messageData if none provided.
if (!messageData) {
messageData = {
speaker: ChatMessage.getSpeaker({ actor }),
type: CONST.CHAT_MESSAGE_TYPES.ROLL,
};
}

// Build array of rolls.
const rollMethod = isAttackRoll ? CONFIG.Item.documentClass.prototype.rollAttack : CONFIG.Item.documentClass.prototype.rollDamage;
const condenseChatMessages = game.settings.get(moduleID, 'condenseChatMessagesEnabled');
let rollOptions;
const commonRollOptions = {
fastForward: true,
chatMessage: !condenseChatMessages // Prevent extra roll chat messages if condenseChatMessages enabled.
};
}
if (isAttackRoll) {
rollOptions = commonRollOptions;
rollOptions.advantage = vantage === 'advantage';
rollOptions.disadvantage = vantage === 'disadvantage';
} else {
rollOptions = {
critical: isCritical,
options: commonRollOptions
};
}
const preHook = isAttackRoll ? 'preRollAttack' : 'preRollDamage';
const hk = Hooks.on(`dnd5e.${preHook}`, (item, rollConfig) => {
if (sitBonus) rollConfig.parts.push(sitBonus);
});
let rollOrder = 1;
const rolls = [];
if (primeRoll) rolls.push(primeRoll);
for (const id of itemArray) {
const item = isIDs ? actor.items.get(id) : actor.items.getName(id);
const r = await rollMethod.call(item, rollOptions);
if (r) {
r.id = id;
if (rollMode === 'publicroll' && isExtraAttack) {
r.dice[0].options.rollOrder = rollOrder;
rollOrder++;
}
rolls.push(r);
}

// Build array of rolls.
const rollMethod = isAttackRoll ? CONFIG.Item.documentClass.prototype.rollAttack : CONFIG.Item.documentClass.prototype.rollDamage;
const condenseChatMessages = game.settings.get(moduleID, 'condenseChatMessagesEnabled');
let rollOptions;
const commonRollOptions = {
fastForward: true,
chatMessage: !condenseChatMessages // Prevent extra roll chat messages if condenseChatMessages enabled.
};
if (isAttackRoll) {
rollOptions = commonRollOptions;
rollOptions.advantage = vantage === 'advantage';
rollOptions.disadvantage = vantage === 'disadvantage';
} else {
rollOptions = {
critical: isCritical,
options: commonRollOptions
await delay(100); // Short delay to allow roll to complete ammoUpdate.
}
Hooks.off(`dnd5e.${preHook}`, hk);

// Build templateData for rendering custom condensed chat message template.
const templateData = {
items: {}
};
}
const preHook = isAttackRoll ? 'preRollAttack' : 'preRollDamage';
const hk = Hooks.on(`dnd5e.${preHook}`, (item, rollConfig) => {
if (sitBonus) rollConfig.parts.push(sitBonus);
});
let rollOrder = 1;
const rolls = [];
if (primeRoll) rolls.push(primeRoll);
for (const id of itemArray) {
const item = isIDs ? actor.items.get(id) : actor.items.getName(id);
await delay(100); // Short delay to allow previous roll to complete ammoUpdate.
const r = await rollMethod.call(item, rollOptions);
if (r) {
r.id = id;
if (rollMode === 'publicroll' && isExtraAttack) {
r.dice[0].options.rollOrder = rollOrder;
rollOrder++;
for (const roll of rolls) {
roll.tooltip = await roll.getTooltip();
if (isAttackRoll) {
if (roll.isCritical) roll.highlight = 'critical';
else if (roll.isFumble) roll.highlight = 'fumble';
else roll.highlight = '';
}
rolls.push(r);
const { id } = roll;
if (!templateData.items[id]) {
templateData.items[id] = {
flavor: roll.options.flavor,
formula: roll.formula,
rolls: [roll]
};
if (roll.hasAdvantage) templateData.items[id].flavor += ` (${game.i18n.localize("DND5E.Advantage")})`;
if (roll.hasDisadvantage) templateData.items[id].flavor += ` (${game.i18n.localize("DND5E.Disadvantage")})`;
} else templateData.items[id].rolls.push(roll);
}

}
Hooks.off(`dnd5e.${preHook}`, hk);
// Subsequent processing only applies to condensed chat messages.
if (!condenseChatMessages) return rolls;

// Build templateData for rendering custom condensed chat message template.
const templateData = {
items: {}
};
for (const roll of rolls) {
roll.tooltip = await roll.getTooltip();
if (isAttackRoll) {
if (roll.isCritical) roll.highlight = 'critical';
else if (roll.isFumble) roll.highlight = 'fumble';
else roll.highlight = '';
}
const { id } = roll;
if (!templateData.items[id]) {
templateData.items[id] = {
flavor: roll.options.flavor,
formula: roll.formula,
rolls: [roll]
};
if (roll.hasAdvantage) templateData.items[id].flavor += ` (${game.i18n.localize("DND5E.Advantage")})`;
if (roll.hasDisadvantage) templateData.items[id].flavor += ` (${game.i18n.localize("DND5E.Disadvantage")})`;
} else templateData.items[id].rolls.push(roll);
}
// Attach rolls array to messageData for DsN integration and total damage application.
messageData.rolls = rolls;

// Subsequent processing only applies to condensed chat messages.
if (!condenseChatMessages) return rolls;
// Calculate total damage if damage roll.
if (!isAttackRoll) templateData.totalDamage = rolls.reduce((acc, current) => { return acc += current.total }, 0);

// Attach rolls array to messageData for DsN integration and total damage application.
messageData.rolls = rolls;
// Render template.
const content = await renderTemplate(`modules/${moduleID}/templates/condensed-chat-message.hbs`, templateData);
messageData.content = content;

// Calculate total damage if damage roll.
if (!isAttackRoll) templateData.totalDamage = rolls.reduce((acc, current) => { return acc += current.total }, 0);
// Compatibility with Semi-Private Rolls.
messageData.flags = {
'semi-private-rolls': {
flavor: messageData.flavor
}
};
// Flavor is already included in custom template.
delete messageData.flavor;

// Conditionally hide DsN based on extraAttackDSN setting.
const dsn = isExtraAttack
? game.settings.get(moduleID, 'extraAttackDSN')
: game.settings.get(moduleID, 'multiattackDSN');
if (dsn !== 'enabled' && (extraAttackDSN === 'disabled' || dsn !== rollType)) {
Hooks.once('diceSoNiceRollStart', (id, context) => { context.blind = true });
}

// Render template.
const content = await renderTemplate(`modules/${moduleID}/templates/condensed-chat-message.hbs`, templateData);
messageData.content = content;
// Create condensed chat message.
if (chatMessage) await ChatMessage.create(messageData, { rollMode });

messageData.flags = {
[moduleID]: {
isMultiattack: true,
},
'semi-private-rolls': { // Compatibility with Semi-Private Rolls.
return rolls;

flavor: messageData.flavor
} else if (roller === "rsr") {
// Hide DSN based on module setting
let hk;
if (!game.settings.get(moduleID, "readySetRollDSN")) hk = Hooks.on("diceSoNiceRollStart", (messageID, context) => { context.blind = true });

// Use rsr5e to perform rolls
for (const id of itemArray) {
const item = isIDs ? actor.items.get(id) : actor.items.getName(id);
rsr5e.macro.rollItem(item._id, actor._id);
}
};
// Flavor is already included in custom template.
delete messageData.flavor;

// Conditionally hide DsN based on extraAttackDSN setting.
const dsn = isExtraAttack
? game.settings.get(moduleID, 'extraAttackDSN')
: game.settings.get(moduleID, 'multiattackDSN');
if (dsn !== 'enabled' && (extraAttackDSN === 'disabled' || dsn !== rollType)) {
Hooks.once('diceSoNiceRollStart', (id, context) => { context.blind = true });
if (hk) Hooks.off("diceSoNiceRollStart", hk);
}

// Create condensed chat message.
if (chatMessage) await ChatMessage.create(messageData, { rollMode });

return rolls;
}

static async multiattackTool() {
Expand Down