Fixes and enhancements, from issue list

This commit is contained in:
2026-03-27 21:21:17 +01:00
parent f1dda301d7
commit c22c3d713b
25 changed files with 531 additions and 111 deletions

View File

@@ -915,7 +915,15 @@ export async function rollInitiativeCheck(actor, options = {}) {
})
}
// NPC: Fate rank + initiativeBonus
// NPC: find Leadership skillnpc item, fall back to Fate rank + initiativeBonus
const leadershipSkill = actor.items.find(
i => i.type === "skillnpc" && i.name.toLowerCase() === "leadership"
)
if (leadershipSkill) {
return rollNPCSkill(actor, leadershipSkill, { bonus, visibility })
}
// Fallback: Fate rank + initiativeBonus
const sys = actor.system
const fateRank = sys.attributes?.fate?.rank ?? 1
const initBonus = sys.initiativeBonus ?? 0
@@ -967,7 +975,7 @@ export async function rollInitiativeCheck(actor, options = {}) {
* @param {object} options
*/
export async function rollNPCSkill(actor, skillItem, options = {}) {
const { bonus = 0, colorOverride, visibility } = options
const { bonus = 0, colorOverride, visibility, explodeOn5 = false } = options
const sys = skillItem.system
const colorType = colorOverride || sys.colorDiceType
@@ -976,12 +984,14 @@ export async function rollNPCSkill(actor, skillItem, options = {}) {
const totalDice = Math.max(sys.dicePool + bonus, 1)
const { roll, rolls, successes, diceResults } = await _rollPool(totalDice, threshold, false)
const { roll, rolls, successes, diceResults } = await _rollPool(totalDice, threshold, explodeOn5)
const diceHtml = _diceHtml(diceResults, threshold)
const modLine = bonus !== 0
? `<div class="oh-roll-mods">${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}</div>`
: ""
const explodedCount = diceResults.filter(d => d.exploded).length
const modParts = []
if (bonus !== 0) modParts.push(`${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}`)
if (explodedCount > 0) modParts.push(`💥 ${explodedCount} ${game.i18n.localize("OATHHAMMER.Roll.Exploded")}`)
const modLine = modParts.length ? `<div class="oh-roll-mods">${modParts.join(" · ")}</div>` : ""
const content = `
<div class="oh-roll-card">
@@ -1025,6 +1035,7 @@ export async function rollNPCWeaponAttack(actor, weapon, options = {}) {
const modParts = []
if (bonus !== 0) modParts.push(`${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}`)
if (diceResults.filter(d => d.exploded).length > 0) modParts.push(`💥 ${diceResults.filter(d => d.exploded).length} ${game.i18n.localize("OATHHAMMER.Roll.Exploded")}`)
const modLine = modParts.length ? `<div class="oh-roll-mods">${modParts.join(" · ")}</div>` : ""
const content = `
@@ -1067,6 +1078,7 @@ export async function rollNPCWeaponDamage(actor, weapon, options = {}) {
const modParts = []
if (bonus !== 0) modParts.push(`${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}`)
if (diceResults.filter(d => d.exploded).length > 0) modParts.push(`💥 ${diceResults.filter(d => d.exploded).length} ${game.i18n.localize("OATHHAMMER.Roll.Exploded")}`)
const modLine = modParts.length ? `<div class="oh-roll-mods">${modParts.join(" · ")}</div>` : ""
const content = `
@@ -1098,7 +1110,7 @@ export async function rollNPCWeaponDamage(actor, weapon, options = {}) {
* NPC armor dice roll — rolls actor's armorDice.value dice with armorDice.colorDiceType color.
*/
export async function rollNPCArmor(actor, options = {}) {
const { bonus = 0, colorOverride, visibility } = options
const { bonus = 0, colorOverride, visibility, explodeOn5 = false } = options
const sys = actor.system
const basePool = sys.armorDice?.value ?? 0
@@ -1107,12 +1119,15 @@ export async function rollNPCArmor(actor, options = {}) {
const colorEmoji = colorType === "black" ? "⬛" : colorType === "red" ? "🔴" : "⬜"
const totalDice = Math.max(basePool + bonus, 1)
const { roll, rolls, successes, diceResults } = await _rollPool(totalDice, threshold, false)
const { roll, rolls, successes, diceResults } = await _rollPool(totalDice, threshold, explodeOn5)
const diceHtml = _diceHtml(diceResults, threshold)
const modLine = bonus !== 0
? `<div class="oh-roll-mods">${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}</div>`
: ""
const explodedCount = diceResults.filter(d => d.exploded).length
const modParts = []
if (bonus !== 0) modParts.push(`${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}`)
if (explodeOn5) modParts.push(`💥 ${game.i18n.localize("OATHHAMMER.Dialog.ExplodeOn5")}`)
if (explodedCount > 0) modParts.push(`💥 ${explodedCount} ${game.i18n.localize("OATHHAMMER.Roll.Exploded")}`)
const modLine = modParts.length ? `<div class="oh-roll-mods">${modParts.join(" · ")}</div>` : ""
const label = game.i18n.localize("OATHHAMMER.Label.ArmorDice")
const content = `
@@ -1144,20 +1159,23 @@ export async function rollNPCArmor(actor, options = {}) {
* NPC spell cast — flat dice pool, no arcane stress, posts DV success/failure to chat.
*/
export async function rollNPCSpell(actor, spell, options = {}) {
const { dicePool = 3, bonus = 0, colorOverride, visibility } = options
const { dicePool = 3, bonus = 0, colorOverride, visibility, explodeOn5 = false } = options
const dv = spell.system.difficultyValue ?? 1
const colorType = colorOverride || "white"
const threshold = colorType === "black" ? 2 : colorType === "red" ? 3 : 4
const colorEmoji = colorType === "black" ? "⬛" : colorType === "red" ? "🔴" : "⬜"
const totalDice = Math.max(dicePool + bonus, 1)
const { roll, rolls, successes, diceResults } = await _rollPool(totalDice, threshold, false)
const { roll, rolls, successes, diceResults } = await _rollPool(totalDice, threshold, explodeOn5)
const diceHtml = _diceHtml(diceResults, threshold)
const isSuccess = successes >= dv
const modLine = bonus !== 0
? `<div class="oh-roll-mods">${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}</div>`
: ""
const explodedCount = diceResults.filter(d => d.exploded).length
const modParts = []
if (bonus !== 0) modParts.push(`${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}`)
if (explodeOn5) modParts.push(`💥 ${game.i18n.localize("OATHHAMMER.Dialog.ExplodeOn5")}`)
if (explodedCount > 0) modParts.push(`💥 ${explodedCount} ${game.i18n.localize("OATHHAMMER.Roll.Exploded")}`)
const modLine = modParts.length ? `<div class="oh-roll-mods">${modParts.join(" · ")}</div>` : ""
const resultClass = isSuccess ? "roll-success" : "roll-failure"
const resultLabel = isSuccess
@@ -1193,19 +1211,22 @@ export async function rollNPCSpell(actor, spell, options = {}) {
* NPC miracle invocation — flat dice pool, no blocked tracking, posts DV success/failure to chat.
*/
export async function rollNPCMiracle(actor, miracle, options = {}) {
const { dicePool = 3, bonus = 0, visibility } = options
const { dicePool = 3, bonus = 0, visibility, explodeOn5 = false } = options
const dv = 1
const threshold = 4
const colorEmoji = "⬜"
const totalDice = Math.max(dicePool + bonus, 1)
const { roll, rolls, successes, diceResults } = await _rollPool(totalDice, threshold, false)
const { roll, rolls, successes, diceResults } = await _rollPool(totalDice, threshold, explodeOn5)
const diceHtml = _diceHtml(diceResults, threshold)
const isSuccess = successes >= dv
const modLine = bonus !== 0
? `<div class="oh-roll-mods">${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}</div>`
: ""
const explodedCount = diceResults.filter(d => d.exploded).length
const modParts = []
if (bonus !== 0) modParts.push(`${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}`)
if (explodeOn5) modParts.push(`💥 ${game.i18n.localize("OATHHAMMER.Dialog.ExplodeOn5")}`)
if (explodedCount > 0) modParts.push(`💥 ${explodedCount} ${game.i18n.localize("OATHHAMMER.Roll.Exploded")}`)
const modLine = modParts.length ? `<div class="oh-roll-mods">${modParts.join(" · ")}</div>` : ""
const resultClass = isSuccess ? "roll-success" : "roll-failure"
const resultLabel = isSuccess
@@ -1241,7 +1262,7 @@ export async function rollNPCMiracle(actor, miracle, options = {}) {
* NPC attack damage roll — flat dice pool from the npcattack item, no Might.
*/
export async function rollNPCAttackDamage(actor, attack, options = {}) {
const { bonus = 0, visibility } = options
const { bonus = 0, visibility, explodeOn5 = false } = options
const sys = attack.system
const colorType = sys.colorDiceType || "white"
const threshold = colorType === "black" ? 2 : colorType === "red" ? 3 : 4
@@ -1249,12 +1270,15 @@ export async function rollNPCAttackDamage(actor, attack, options = {}) {
const totalDice = Math.max((sys.damageDice ?? 1) + bonus, 1)
const ap = sys.ap ?? 0
const { roll, rolls, successes, diceResults } = await _rollPool(totalDice, threshold, false)
const { roll, rolls, successes, diceResults } = await _rollPool(totalDice, threshold, explodeOn5)
const diceHtml = _diceHtml(diceResults, threshold)
const explodedCount = diceResults.filter(d => d.exploded).length
const modParts = []
if (bonus !== 0) modParts.push(`${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}`)
if (ap > 0) modParts.push(`AP ${ap}`)
if (bonus !== 0) modParts.push(`${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}`)
if (ap > 0) modParts.push(`AP ${ap}`)
if (explodeOn5) modParts.push(`💥 ${game.i18n.localize("OATHHAMMER.Dialog.ExplodeOn5")}`)
if (explodedCount > 0) modParts.push(`💥 ${explodedCount} ${game.i18n.localize("OATHHAMMER.Roll.Exploded")}`)
const modLine = modParts.length ? `<div class="oh-roll-mods">${modParts.join(" · ")}</div>` : ""
const content = `