Last active
March 27, 2021 23:00
-
-
Save schultzcole/b90c2a900cc717afeb622dc55075ecd2 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Allows a user to quickly roll a high number of attack and damage rolls for a weapon. | |
* Paste as a script macro, select token (or have an actor assigned as your character), run macro | |
* D&D5e only | |
* @author cole#9640 | |
* @version 1 | |
*/ | |
async function main() { | |
if (!actor) { | |
// User either does not have an "assigned" character or doesn't have a token selected. | |
ui.notifications.warn("You must select a token."); | |
return; | |
} | |
const weaponOptions = actor.items.filter(i => i.data.type === "weapon" && i.hasAttack).map(i => `<option value="${i.id}">${i.data.name}</options>`); | |
const dlgContent = ` | |
<p class="hint">${actor.data.name} is rolling a multiattack.</p> | |
<div class="form-group"> | |
<label for="weaponSelect">Weapon:</label><select name="weaponSelect">${weaponOptions.join("\n")}</select> | |
</div> | |
<div class="form-group"> | |
<label for="numRolls">Number of Attacks:</label><input name="numRolls" type="number" value="1" /> | |
</div> | |
`; | |
let result; | |
try { | |
result = await form({ title: "Multiattack", content: dlgContent, buttons: { adv:"Advantage", nor:"Normal", dis:"Disadvantage" }, defaultButton: "nor" }); | |
} catch { | |
// User canceled the dialog: abort. | |
return; | |
} | |
try { | |
validateResult(result); | |
} catch (e) { | |
// User entered invalid data: warn them, then abort. | |
ui.notifications.warn(e.message); | |
return; | |
} | |
const item = actor.items.get(result.form.weaponSelect); | |
if (!item) { | |
// The item ID could not be found on the actor. | |
// This should not happen, but it is possible: for instance if someone opens the dialog, then deletes a weapon, then submits the form. | |
ui.notifications.error(`Unable to find item with ID ${result.form.weaponSelect}`); | |
return; | |
} | |
const attackOptions = { fastForward: true, advantage: result.button === "adv", disadvantage: result.button === "dis", chatMessage: false }; | |
const damageOptions = { fastForward: true, chatMessage: false }; | |
const rolls = [] | |
for (let i = 0; i < result.form.numRolls; i++) { | |
rolls.push({ | |
attack: await item.rollAttack(attackOptions), | |
damage: await item.rollDamage({ options: damageOptions }), | |
}); | |
} | |
const rows = rolls.map(attack => `<tr>${makeCell(attack.attack, "attack")}${makeCell(attack.damage, "damage")}</tr>`); | |
const content = ` | |
<div class="dnd5e chat-card"> | |
<header class="card-header flexrow"> | |
<img src="${item.data.img}" title="${item.data.name} width="36" height="36" /> | |
<h3>${item.data.name} - Multiattack</h3> | |
</header> | |
<table class="multiattack-table"> | |
<tr> | |
<th>Attack<br /><span class="header-roll">[[/r ${rolls[0].attack.formula}]]</span></th> | |
<th>Damage<br /><span class="header-roll">[[/r ${rolls[0].damage.formula}]]</span></th> | |
</tr> | |
${rows.join("\n")} | |
</table> | |
</div>`; | |
ChatMessage.create({ content, speaker: ChatMessage.getSpeaker({ actor }) }); | |
} | |
main(); | |
// Utility function for creating a dialog with a function and getting the contents of the form within | |
function form({ title, content, buttons={ submit: "Submit" }, defaultButton="submit", options } = {}) { | |
return new Promise((resolve, reject) => { | |
buttons = Object.entries(buttons).reduce((acc, [key, label]) => { | |
acc[key] = { | |
label, | |
callback: ($html) => { | |
const fd = new FormDataExtended($html.find("form")[0]); | |
resolve({ button: key, form: fd.toObject() }); | |
} | |
} | |
return acc; | |
}, {}); | |
const dialog = new Dialog({ | |
title, | |
content: `<form onsubmit="event.preventDefault()">${content}</form>`, | |
buttons, | |
default: defaultButton, | |
close: reject, | |
}, options); | |
dialog.render(true); | |
}); | |
} | |
function validateResult(result) { | |
if (!Number.isInteger(result.form.numRolls) || result.form.numRolls < 1) throw Error(`Invalid number of rolls: "${result.form.numRolls}". Must be an integer greater than 0.`); | |
} | |
function makeCell(roll, cssClass) { | |
return ` | |
<td> | |
<a class="inline-roll inline-result multiattack-roll multiattack-${cssClass}" title="${roll.formula}" data-roll="${escape(JSON.stringify(roll))}">${roll.total}</a> | |
</td>`; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* technically optional css styling to improve the appearance of the multiattack chat cards */ | |
/* use with the Custom CSS module or as a world stylesheet */ | |
.multiattack-table { | |
border-spacing: 2px; | |
table-layout: fixed; | |
text-align: center; | |
border: 1px #999 solid; | |
} | |
.multiattack-table td:first-child, | |
.multiattack-table th:first-child{ | |
border-right: 1px #999 solid; | |
} | |
.multiattack-table th { | |
font-size: 18px; | |
} | |
.multiattack-table th .inline-roll { | |
font-size: 12px; | |
font-weight: normal; | |
} | |
.multiattack-table .multiattack-roll { | |
display: inline-block; | |
width: calc(100% - 4px); | |
height: 22px; | |
line-height: 20px; | |
margin: 2px; | |
padding: 0; | |
vertical-align: middle; | |
font-size: 18px; | |
font-weight: bold; | |
background: #00000020; | |
border: 1px solid #999; | |
box-shadow: 0 0 2px #FFF inset; | |
} | |
.multiattack-table .multiattack-roll > i { | |
float: left; | |
line-height: 1.5rem; | |
vertical-align: middle; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Here's a screenshot of what the end result looks like with the optional css included:
