Skip to content

Commit

Permalink
Improved multiattack detection
Browse files Browse the repository at this point in the history
Various types of multiattacks are now more accurately detected.
Also added option to show multiattack description.
  • Loading branch information
Stendarpaval committed Apr 25, 2021
1 parent b639575 commit a11e9bd
Show file tree
Hide file tree
Showing 4 changed files with 33 additions and 17 deletions.
4 changes: 2 additions & 2 deletions module.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
"description": "A module for dnd5e that adds support for Mob Attacks.",
"author": "Stendarpaval (Lupusmalus)",
"url": "https://github.com/Stendarpaval/mob-attack-tool",
"version": "0.1.23",
"version": "0.1.24",
"minimumCoreVersion": "0.7.7",
"compatibleCoreVersion": "0.7.9",
"system": "dnd5e",
"esmodules": [
"scripts/mobAttack.js"
],
"manifest": "https://raw.githubusercontent.com/Stendarpaval/mob-attack-tool/main/module.json",
"download": "https://github.com/Stendarpaval/mob-attack-tool/releases/download/v0.1.23/module.zip"
"download": "https://github.com/Stendarpaval/mob-attack-tool/releases/download/v0.1.24/module.zip"
}
9 changes: 9 additions & 0 deletions scripts/mobAttack.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ Hooks.once("init", () => {
type: Boolean
})

game.settings.register(MODULE, "showMultiattackDescription", {
name: "Show multiattack description",
hint: "If enabled, then a multiattack description will be shown for any mob attacker that has a multiattack feature on their actor sheet. (Client specific setting)",
config: true,
scope: "client",
default: true,
type: Boolean
})

game.settings.register(MODULE, "autoDetectMultiattacks", {
name: "Autodetect multiattack",
hint: `Attempt to automatically detect the multiattack weapon options of mob attackers. If set to "Autodetect + autoselect", then the multiattack weapon options should already be selected when you open the Mob Attack dialog. (Client specific setting)`,
Expand Down
28 changes: 18 additions & 10 deletions scripts/mobAttackTool.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@ async function mobAttackTool() {
if (!monsters[token.actor.id].optionVisible) {
content += `<hr>` + await formatMonsterLabel(monsters[token.actor.id]);
monsters[token.actor.id].optionVisible = true;
if (game.settings.get(MODULE, "showMultiattackDescription")) {
if (token.actor.items.entries.filter(i => i.name.startsWith("Multiattack")).length > 0) {
content += `<div class="hint">${token.actor.items.filter(i => i.name.startsWith("Multiattack"))[0].data.data.description.value}</div>`;
} else if (token.actor.items.entries.filter(i => i.name.startsWith("Extra Attack")).length > 0) {
content += `<div class="hint">${token.actor.items.filter(i => i.name.startsWith("Extra Attack"))[0].data.data.description.value}</div>`;
}
}
}
}

Expand Down Expand Up @@ -459,13 +466,11 @@ async function processIndividualDamageRolls(data, weaponData, finalAttackBonus,
let diceFormula = diceFormulas.join(" + ");
let damageType = damageTypes.join(", ");
let damageRoll = new Roll(diceFormula, {mod: weaponData.actor.data.data.abilities[weaponData.abilityMod].mod});

//TODO: use better crit formula
await damageRoll.alter(numHitAttacks, numCrits, {multiplyNumeric: true}).roll();

// Roll Dice so Nice dice
if (game.modules.get("dice-so-nice")?.active) game.dice3d.showForRoll(damageRoll);

//TODO: find out how to properly tell MidiQOL about multiple damage types
new MidiQOL.DamageOnlyWorkflow(
weaponData.options.actor,
data.targetToken,
Expand Down Expand Up @@ -728,7 +733,7 @@ async function formatMonsterLabel(monsterData) {
async function formatWeaponLabel(weapons, itemData) {
let weaponLabel = ``;
let checkVersatile = itemData.data.data.damage.versatile != "";
for (let j = 0; j < ((checkVersatile) ? 2 : 1); j++) {
for (let j = 0; j < 1 + ((checkVersatile) ? 1 : 0); j++) {
let isVersatile = (j < 1) ? false : itemData.data.data.damage.versatile != "";
let damageData = getDamageFormulaAndType(itemData, isVersatile);
let weaponDamageText = ``;
Expand All @@ -738,14 +743,14 @@ async function formatWeaponLabel(weapons, itemData) {
let numAttacksTotal = 1, preChecked = false;
let autoDetect = game.settings.get("mob-attack-tool","autoDetectMultiattacks");
if (autoDetect > 0) [numAttacksTotal, preChecked] = getMultiattackFromActor(itemData.name, itemData.actor, weapons);
if (autoDetect === 1) preChecked = false;
if (autoDetect === 1 || isVersatile) preChecked = false;

let labelData = {
numAttacksName: `numAttacks${(itemData.id + ((isVersatile) ? ` (Versatile)` : ``)).replace(" ","-")}`,
numAttack: numAttacksTotal,
weaponImg: itemData.img,
weaponNameImg: itemData.name.replace(" ","-"),
weaponName: itemData.name + ((isVersatile) ? ` (Versatile)` : ``),
weaponName: `${itemData.name}${((isVersatile) ? ` (Versatile)` : ``)}`,
weaponAttackBonus: getAttackBonus(itemData),
weaponDamageText: weaponDamageText,
useButtonName: `use${(itemData.id + ((isVersatile) ? ` (Versatile)` : ``)).replace(" ","-")}`,
Expand Down Expand Up @@ -821,7 +826,7 @@ function getAttackBonus(weaponData) {
const actorName = weaponData.actor.name;
let weaponAbility = weaponData.abilityMod;
if (weaponAbility === "" || typeof weaponAbility === "undefined" || weaponAbility == null) {
if (!weaponData.type == "spell") {
if (!weaponData.type === "spell") {
weaponAbility = "str";
} else {
weaponAbility = weaponData.actor.data.data.attributes.spellcasting;
Expand Down Expand Up @@ -862,20 +867,23 @@ function getDamageFormulaAndType(weaponData, versatile) {
let diceFormulas = [];
let damageTypes = [];
let damageTypeLabels = []
let partsLength = weaponData.data.data.damage.parts.length;
let lengthIndex = 0;
for (let diceFormulaParts of weaponData.data.data.damage.parts) {
damageTypeLabels.push(diceFormulaParts[1]);
damageTypes.push(diceFormulaParts[1].capitalize());
if (weaponData.type == "spell") {
if (weaponData.data.data.scaling.mode == "cantrip") {
let rollFormula = new Roll(((versatile) ? weaponData.data.data.damage.versatile : diceFormulaParts[0]),{mod: weaponData.actor.data.data.abilities[weaponData.abilityMod].mod});
let rollFormula = new Roll(((versatile && lengthIndex === 0) ? weaponData.data.data.damage.versatile : diceFormulaParts[0]),{mod: weaponData.actor.data.data.abilities[weaponData.abilityMod].mod});
rollFormula.alter(0,cantripScalingFactor,{multiplyNumeric: false})
diceFormulas.push(rollFormula.formula);
} else {
diceFormulas.push(((versatile) ? weaponData.data.data.damage.versatile : diceFormulaParts[0]).replace("@mod",weaponData.actor.data.data.abilities[weaponData.abilityMod].mod));
diceFormulas.push(((versatile && lengthIndex === 0) ? weaponData.data.data.damage.versatile : diceFormulaParts[0]).replace("@mod",weaponData.actor.data.data.abilities[weaponData.abilityMod].mod));
}
} else {
diceFormulas.push(((versatile) ? weaponData.data.data.damage.versatile : diceFormulaParts[0]).replace("@mod",weaponData.actor.data.data.abilities[weaponData.abilityMod].mod));
diceFormulas.push(((versatile && lengthIndex === 0) ? weaponData.data.data.damage.versatile : diceFormulaParts[0]).replace("@mod",weaponData.actor.data.data.abilities[weaponData.abilityMod].mod));
}
lengthIndex++;
}
return [diceFormulas, damageTypes, damageTypeLabels];
}
9 changes: 4 additions & 5 deletions scripts/multiattack.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,14 @@ export function getMultiattackFromActor(weaponName, actorData, weapons) {
if (attackType === `melee`) {
if (![`mwak`, `msak`].includes(weaponData.data.data.actionType)) {
attackType = `choose`;
numAttacksWeapon = 1;
}
} else if (attackType === `ranged`) {
if (![`rwak`, `rsak`].includes(weaponData.data.data.actionType)) {
attackType = `choose`;
numAttacksWeapon = 1;
}
}
console.log(weaponName, attackType);
}

let weaponDetected = false;
Expand All @@ -76,8 +77,6 @@ export function getMultiattackFromActor(weaponName, actorData, weapons) {
// Step backwards through multiattack description
for (let word of remainingWords) {



// homogenize words to simplify detection
word = word.toLowerCase();
let interpunction = [",",".",":"];
Expand All @@ -96,7 +95,7 @@ export function getMultiattackFromActor(weaponName, actorData, weapons) {
}

// detect possibility of choosing what kind of multiattack to use
const optionKeywordsSingle = [`or`,`alternatively`,` instead`];
const optionKeywordsSingle = [`or`, `alternatively`, `instead`, `while`];
if (weaponDetected) {
if (optionKeywordsSingle.includes(word)) {
attackType = `choose`;
Expand Down Expand Up @@ -129,7 +128,7 @@ export function getMultiattackFromActor(weaponName, actorData, weapons) {
// either return the specific or total number of multiattacks
if (numAttacksTotal !== 0) {
if (numAttacksWeapon !== 0) {
multiattack = [(numWeaponsInventory === numAttacksWeapon) ? 1 : numAttacksWeapon, (attackType !== `choose`) ? true : false];
multiattack = [(numWeaponsInventory === numAttacksWeapon && numWeaponsInventory === numAttacksTotal) ? 1 : numAttacksWeapon, (attackType !== `choose`) ? true : false];
} else if (weaponDetected) {
multiattack = [(numWeaponsInventory === numAttacksTotal) ? 1 : numAttacksTotal, (attackType !== `choose`) ? true : false];
}
Expand Down

0 comments on commit a11e9bd

Please sign in to comment.