Nombreuses corrections sur les fiches settlement/NPC

This commit is contained in:
2026-03-22 21:35:47 +01:00
parent ec291e9c60
commit b46c6d804c
51 changed files with 2892 additions and 227 deletions

View File

@@ -242,7 +242,7 @@ export async function rollWeaponAttack(actor, weapon, options = {}) {
const actorSys = actor.system
const isRanged = sys.proficiencyGroup === "bows" || sys.proficiencyGroup === "throwing"
const skillKey = isRanged ? "shooting" : "fighting"
const skillKey = (sys.skillOverride && SYSTEM.SKILLS[sys.skillOverride]) ? sys.skillOverride : (isRanged ? "shooting" : "fighting")
const skillDef = SYSTEM.SKILLS[skillKey]
const defaultAttr = skillDef.attribute
@@ -339,8 +339,11 @@ export async function rollWeaponDamage(actor, weapon, options = {}) {
const colorEmoji = hasDeadly ? "⬛" : hasBrutal ? "🔴" : "⬜"
const colorLabel = hasDeadly ? "Black" : hasBrutal ? "Red" : "White"
const mightRank = actorSys.attributes.might.rank
const baseDamageDice = sys.usesMight ? Math.max(mightRank + sys.damageMod, 1) : Math.max(sys.damageMod, 1)
const isRangedDmg = sys.proficiencyGroup === "bows" || sys.proficiencyGroup === "throwing"
const dmgSkillKey = (sys.skillOverride && SYSTEM.SKILLS[sys.skillOverride]) ? sys.skillOverride : (isRangedDmg ? "shooting" : "fighting")
const dmgAttrKey = SYSTEM.SKILLS[dmgSkillKey].attribute
const dmgAttrRank = actorSys.attributes[dmgAttrKey].rank
const baseDamageDice = sys.usesMight ? Math.max(dmgAttrRank + sys.damageMod, 1) : Math.max(sys.damageMod, 1)
const totalDice = Math.max(baseDamageDice + sv + damageBonus + autoDamageBonus, 1)
const { roll, rolls, successes, diceResults } = await _rollPool(totalDice, threshold)
@@ -951,3 +954,330 @@ export async function rollInitiativeCheck(actor, options = {}) {
return { successes, dv: 0, isSuccess: null }
}
// ============================================================
// NPC SKILL ROLL
// ============================================================
/**
* Roll an NPC skill check (skillnpc item) and post to chat.
*
* @param {Actor} actor The NPC/creature actor
* @param {Item} skillItem The skillnpc item
* @param {object} options
*/
export async function rollNPCSkill(actor, skillItem, options = {}) {
const { bonus = 0, colorOverride, visibility } = options
const sys = skillItem.system
const colorType = colorOverride || sys.colorDiceType
const threshold = colorType === "black" ? 2 : colorType === "red" ? 3 : 4
const colorEmoji = colorType === "black" ? "⬛" : colorType === "red" ? "🔴" : "⬜"
const totalDice = Math.max(sys.dicePool + bonus, 1)
const { roll, rolls, successes, diceResults } = await _rollPool(totalDice, threshold, false)
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 content = `
<div class="oh-roll-card">
<div class="oh-roll-header">
<img src="${skillItem.img}" class="oh-card-weapon-img" alt="${skillItem.name}" />
<span>${skillItem.name}${actor.name}</span>
</div>
<div class="oh-roll-info">
<span>${colorEmoji} ${totalDice}d6 (${threshold}+)</span>
</div>
${modLine}
<div class="oh-roll-dice">${diceHtml}</div>
<div class="oh-roll-result">
<span class="oh-roll-successes">${successes} ${game.i18n.localize("OATHHAMMER.Roll.Successes")}</span>
</div>
</div>
`
const rollMode = visibility ?? game.settings.get("core", "rollMode")
const msgData = { speaker: ChatMessage.getSpeaker({ actor }), content, rolls: rolls, sound: CONFIG.sounds.dice }
ChatMessage.applyRollMode(msgData, rollMode)
await ChatMessage.create(msgData)
return { successes }
}
/**
* NPC weapon attack roll — uses NPC's flat attackBonus as dice pool.
* Rolls white dice (4+) with optional bonus modifier.
*/
export async function rollNPCWeaponAttack(actor, weapon, options = {}) {
const { bonus = 0, visibility } = options
const sys = actor.system
const basePool = sys.attackBonus ?? 0
const totalDice = Math.max(basePool + bonus, 1)
const threshold = 4
const colorEmoji = "⬜"
const { roll, rolls, successes, diceResults } = await _rollPool(totalDice, threshold, false)
const diceHtml = _diceHtml(diceResults, threshold)
const modParts = []
if (bonus !== 0) modParts.push(`${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}`)
const modLine = modParts.length ? `<div class="oh-roll-mods">${modParts.join(" · ")}</div>` : ""
const content = `
<div class="oh-roll-card oh-weapon-card">
<div class="oh-roll-header">
<img src="${weapon.img}" class="oh-card-weapon-img" alt="${weapon.name}" />
<span>${weapon.name}${game.i18n.localize("OATHHAMMER.Dialog.Attack")} (${actor.name})</span>
</div>
<div class="oh-roll-info">
<span>${colorEmoji} ${totalDice}d6 (${threshold}+)</span>
</div>
${modLine}
<div class="oh-roll-dice">${diceHtml}</div>
<div class="oh-roll-result">
<span class="oh-roll-successes">${successes} ${game.i18n.localize("OATHHAMMER.Roll.Successes")}</span>
</div>
</div>
`
const rollMode = visibility ?? game.settings.get("core", "rollMode")
const msgData = { speaker: ChatMessage.getSpeaker({ actor }), content, rolls: rolls, sound: CONFIG.sounds.dice }
ChatMessage.applyRollMode(msgData, rollMode)
await ChatMessage.create(msgData)
return { successes }
}
/**
* NPC weapon damage roll — uses NPC damageBonus + weapon damageMod as dice pool.
*/
export async function rollNPCWeaponDamage(actor, weapon, options = {}) {
const { bonus = 0, visibility } = options
const sys = actor.system
const basePool = (sys.damageBonus ?? 0) + (weapon.system.damageMod ?? 0)
const totalDice = Math.max(basePool + bonus, 1)
const threshold = 4
const colorEmoji = "⬜"
const { roll, rolls, successes, diceResults } = await _rollPool(totalDice, threshold, false)
const diceHtml = _diceHtml(diceResults, threshold)
const modParts = []
if (bonus !== 0) modParts.push(`${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}`)
const modLine = modParts.length ? `<div class="oh-roll-mods">${modParts.join(" · ")}</div>` : ""
const content = `
<div class="oh-roll-card oh-weapon-card">
<div class="oh-roll-header">
<img src="${weapon.img}" class="oh-card-weapon-img" alt="${weapon.name}" />
<span>${weapon.name}${game.i18n.localize("OATHHAMMER.Dialog.Damage")} (${actor.name})</span>
</div>
<div class="oh-roll-info">
<span>${colorEmoji} ${totalDice}d6 (${threshold}+)</span>
<span>${game.i18n.localize("OATHHAMMER.Label.Damage")}: ${weapon.system.damageLabel}</span>
</div>
${modLine}
<div class="oh-roll-dice">${diceHtml}</div>
<div class="oh-roll-result">
<span class="oh-roll-successes">${successes} ${game.i18n.localize("OATHHAMMER.Roll.Successes")}</span>
</div>
</div>
`
const rollMode = visibility ?? game.settings.get("core", "rollMode")
const msgData = { speaker: ChatMessage.getSpeaker({ actor }), content, rolls: rolls, sound: CONFIG.sounds.dice }
ChatMessage.applyRollMode(msgData, rollMode)
await ChatMessage.create(msgData)
return { successes }
}
/**
* 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 sys = actor.system
const basePool = sys.armorDice?.value ?? 0
const colorType = colorOverride || sys.armorDice?.colorDiceType || "white"
const threshold = colorType === "black" ? 2 : colorType === "red" ? 3 : 4
const colorEmoji = colorType === "black" ? "⬛" : colorType === "red" ? "🔴" : "⬜"
const totalDice = Math.max(basePool + bonus, 1)
const { roll, rolls, successes, diceResults } = await _rollPool(totalDice, threshold, false)
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 label = game.i18n.localize("OATHHAMMER.Label.ArmorDice")
const content = `
<div class="oh-roll-card oh-armor-card">
<div class="oh-roll-header">
<img src="${actor.img}" class="oh-card-weapon-img" alt="${actor.name}" />
<span>${label}${actor.name}</span>
</div>
<div class="oh-roll-info">
<span>${colorEmoji} ${totalDice}d6 (${threshold}+)</span>
</div>
${modLine}
<div class="oh-roll-dice">${diceHtml}</div>
<div class="oh-roll-result ${successes > 0 ? "roll-success" : ""}">
<span class="oh-roll-successes">${successes} ${game.i18n.localize("OATHHAMMER.Roll.Successes")}</span>
<span class="oh-roll-verdict">${successes} ${game.i18n.localize("OATHHAMMER.Roll.Damage")}</span>
</div>
</div>
`
const rollMode = visibility ?? game.settings.get("core", "rollMode")
const msgData = { speaker: ChatMessage.getSpeaker({ actor }), content, rolls: rolls, sound: CONFIG.sounds.dice }
ChatMessage.applyRollMode(msgData, rollMode)
await ChatMessage.create(msgData)
return { successes }
}
/**
* 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 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 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 resultClass = isSuccess ? "roll-success" : "roll-failure"
const resultLabel = isSuccess
? game.i18n.localize("OATHHAMMER.Roll.Success")
: game.i18n.localize("OATHHAMMER.Roll.Failure")
const content = `
<div class="oh-roll-card oh-spell-card">
<div class="oh-roll-header">
<img src="${spell.img}" class="oh-card-weapon-img" alt="${spell.name}" />
<span>${spell.name} (DV ${dv}) — ${actor.name}</span>
</div>
<div class="oh-roll-info">
<span>${colorEmoji} ${totalDice}d6 (${threshold}+)</span>
</div>
${modLine}
<div class="oh-roll-dice">${diceHtml}</div>
<div class="oh-roll-result ${resultClass}">
<span class="oh-roll-successes">${successes} / ${dv}</span>
<span class="oh-roll-verdict">${resultLabel}</span>
</div>
</div>
`
const rollMode = visibility ?? game.settings.get("core", "rollMode")
const msgData = { speaker: ChatMessage.getSpeaker({ actor }), content, rolls, sound: CONFIG.sounds.dice }
ChatMessage.applyRollMode(msgData, rollMode)
await ChatMessage.create(msgData)
return { successes, dv, isSuccess }
}
/**
* 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 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 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 resultClass = isSuccess ? "roll-success" : "roll-failure"
const resultLabel = isSuccess
? game.i18n.localize("OATHHAMMER.Roll.Success")
: game.i18n.localize("OATHHAMMER.Roll.Failure")
const content = `
<div class="oh-roll-card oh-miracle-card">
<div class="oh-roll-header">
<img src="${miracle.img}" class="oh-card-weapon-img" alt="${miracle.name}" />
<span>${miracle.name}${actor.name}</span>
</div>
<div class="oh-roll-info">
<span>${colorEmoji} ${totalDice}d6 (${threshold}+)</span>
</div>
${modLine}
<div class="oh-roll-dice">${diceHtml}</div>
<div class="oh-roll-result ${resultClass}">
<span class="oh-roll-successes">${successes} / ${dv}</span>
<span class="oh-roll-verdict">${resultLabel}</span>
</div>
</div>
`
const rollMode = visibility ?? game.settings.get("core", "rollMode")
const msgData = { speaker: ChatMessage.getSpeaker({ actor }), content, rolls, sound: CONFIG.sounds.dice }
ChatMessage.applyRollMode(msgData, rollMode)
await ChatMessage.create(msgData)
return { successes, dv, isSuccess }
}
/**
* 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 sys = attack.system
const colorType = sys.colorDiceType || "white"
const threshold = colorType === "black" ? 2 : colorType === "red" ? 3 : 4
const colorEmoji = colorType === "black" ? "⬛" : colorType === "red" ? "🔴" : "⬜"
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 diceHtml = _diceHtml(diceResults, threshold)
const modParts = []
if (bonus !== 0) modParts.push(`${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}`)
if (ap > 0) modParts.push(`AP ${ap}`)
const modLine = modParts.length ? `<div class="oh-roll-mods">${modParts.join(" · ")}</div>` : ""
const content = `
<div class="oh-roll-card oh-weapon-card">
<div class="oh-roll-header">
<img src="${attack.img}" class="oh-card-weapon-img" alt="${attack.name}" />
<span>${attack.name}${game.i18n.localize("OATHHAMMER.Dialog.Damage")} (${actor.name})</span>
</div>
<div class="oh-roll-info">
<span>${colorEmoji} ${totalDice}d6 (${threshold}+)</span>
${ap > 0 ? `<span>AP ${ap}</span>` : ""}
</div>
${modLine}
<div class="oh-roll-dice">${diceHtml}</div>
<div class="oh-roll-result">
<span class="oh-roll-successes">${successes} ${game.i18n.localize("OATHHAMMER.Roll.Successes")}</span>
</div>
</div>
`
const rollMode = visibility ?? game.settings.get("core", "rollMode")
const msgData = { speaker: ChatMessage.getSpeaker({ actor }), content, rolls, sound: CONFIG.sounds.dice }
ChatMessage.applyRollMode(msgData, rollMode)
await ChatMessage.create(msgData)
return { successes }
}