diff --git a/module.json b/module.json index 437fb6c..253fa7a 100644 --- a/module.json +++ b/module.json @@ -4,7 +4,7 @@ "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", @@ -12,5 +12,5 @@ "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" } diff --git a/scripts/mobAttack.js b/scripts/mobAttack.js index f7b910c..6d5c2b6 100644 --- a/scripts/mobAttack.js +++ b/scripts/mobAttack.js @@ -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)`, diff --git a/scripts/mobAttackTool.js b/scripts/mobAttackTool.js index 8341eca..19ad5ed 100644 --- a/scripts/mobAttackTool.js +++ b/scripts/mobAttackTool.js @@ -60,6 +60,13 @@ async function mobAttackTool() { if (!monsters[token.actor.id].optionVisible) { content += `
` + 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 += `
${token.actor.items.filter(i => i.name.startsWith("Multiattack"))[0].data.data.description.value}
`; + } else if (token.actor.items.entries.filter(i => i.name.startsWith("Extra Attack")).length > 0) { + content += `
${token.actor.items.filter(i => i.name.startsWith("Extra Attack"))[0].data.data.description.value}
`; + } + } } } @@ -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, @@ -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 = ``; @@ -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(" ","-")}`, @@ -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; @@ -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]; } \ No newline at end of file diff --git a/scripts/multiattack.js b/scripts/multiattack.js index 38d7ca7..9d73353 100644 --- a/scripts/multiattack.js +++ b/scripts/multiattack.js @@ -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; @@ -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 = [",",".",":"]; @@ -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`; @@ -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]; }