Various initiative fixes + shield management messages
All checks were successful
Release Creation / build (release) Successful in 46s

This commit is contained in:
2026-04-12 01:07:58 +02:00
parent 42945d33db
commit c37d92af25
35 changed files with 589 additions and 200 deletions

View File

@@ -124,10 +124,12 @@ export default class LethalFantasyUtils {
}
break
case "rollInitiative":
if (msg.userId && msg.userId !== game.user.id) break
actor = game.actors.get(msg.actorId)
actor.system.rollInitiative(msg.combatId, msg.combatantId)
break
case "rollProgressionDice":
if (msg.userId && msg.userId !== game.user.id) break
actor = game.actors.get(msg.actorId)
actor.system.rollProgressionDice(msg.combatId, msg.combatantId, msg.rollProgressionCount)
break
@@ -190,6 +192,9 @@ export default class LethalFantasyUtils {
const attackWeaponId = msg.attackWeaponId
const attackRollType = msg.attackRollType
const attackRollKey = msg.attackRollKey
const attackD30result = msg.attackD30result
const attackD30message = msg.attackD30message
const attackRerollContext = msg.attackRerollContext
const combatantId = msg.combatantId
const tokenId = msg.tokenId
@@ -289,6 +294,9 @@ export default class LethalFantasyUtils {
attackWeaponId,
attackRollType,
attackRollKey,
attackD30result,
attackD30message,
attackRerollContext,
defenderId: defender.id,
defenderTokenId
}
@@ -358,6 +366,9 @@ export default class LethalFantasyUtils {
attackWeaponId,
attackRollType,
attackRollKey,
attackD30result,
attackD30message,
attackRerollContext,
defenderId: defender.id,
defenderTokenId
}
@@ -368,6 +379,114 @@ export default class LethalFantasyUtils {
}
}
/* -------------------------------------------- */
static hasD30Reroll(d30Message) {
return /mulligan|re-?roll/i.test(d30Message || "")
}
/* -------------------------------------------- */
static getCombatBonusDiceChoices() {
return ["1d4", "1d6", "1d8", "1d10", "1d12", "1d20", "1d20e"]
}
/* -------------------------------------------- */
static getShieldReactionData(actor) {
if (!actor) return null
if (actor.type === "monster") {
const formula = actor.system.combat?.shieldDefenseDice
const damageReduction = actor.getShieldDR()
if (!formula || damageReduction <= 0) return null
return {
label: game.i18n.localize("LETHALFANTASY.Label.shieldDefenseDice"),
formula,
damageReduction
}
}
const equippedShields = actor.items.filter(item => item.type === "shield" && item.system.equipped)
if (equippedShields.length === 0) return null
const shield = equippedShields[0]
return {
label: shield.name,
formula: shield.system.defense,
damageReduction: actor.getShieldDR(),
shieldId: shield.id
}
}
/* -------------------------------------------- */
static async promptCombatBonusDie(actorName, sideLabel, currentRoll, opposingRoll) {
const choices = this.getCombatBonusDiceChoices()
const optionsHtml = choices.map(choice => `<option value="${choice}">${choice.toUpperCase()}</option>`).join("")
const content = `
<div class="grit-luck-dialog">
<div class="combat-status">
<p><strong>${actorName}</strong> currently has <strong>${currentRoll}</strong></p>
<p>Opposing ${sideLabel} roll: <strong>${opposingRoll}</strong></p>
</div>
<div class="weapon-selection">
<label for="bonus-die">Choose a bonus die:</label>
<select id="bonus-die" name="bonusDie" style="width: 100%; margin-top: 8px;">
${optionsHtml}
</select>
</div>
</div>
`
return await foundry.applications.api.DialogV2.wait({
window: { title: "Add Bonus Die" },
classes: ["lethalfantasy"],
content,
buttons: [
{
label: "Roll Bonus Die",
icon: "fa-solid fa-dice",
callback: (event, button, dialog) => button.form.elements.bonusDie.value
},
{
label: "Cancel",
icon: "fa-solid fa-xmark",
callback: () => null
}
],
rejectClose: false
})
}
/* -------------------------------------------- */
static async rollBonusDie(formula, actor, messageContent) {
const roll = new Roll(formula)
await roll.evaluate()
if (game?.dice3d) {
await game.dice3d.showForRoll(roll, game.user, true)
}
if (messageContent) {
await ChatMessage.create({
content: messageContent(roll.total, formula),
speaker: ChatMessage.getSpeaker({ actor })
})
}
return roll.total
}
/* -------------------------------------------- */
static async rerollConfiguredRoll(rerollContext = {}) {
const RollClass = CONFIG.Dice.rolls.find(r => r.name === "LethalFantasyRoll")
if (typeof RollClass?.prompt !== "function") {
ui.notifications.error("Lethal Fantasy roll class not available for reroll")
return null
}
return await RollClass.prompt({
...foundry.utils.duplicate(rerollContext),
rollContext: foundry.utils.duplicate(rerollContext.rollContext || {}),
forceNoD30: true,
hasTarget: false,
target: false
})
}
/* -------------------------------------------- */
static async offerGritLuckBonus(defender, attackRoll, currentDefenseRoll, attackerName, defenderName) {
let totalBonus = 0
@@ -538,7 +657,8 @@ export default class LethalFantasyUtils {
/* -------------------------------------------- */
static async compareAttackDefense(data) {
console.log("compareAttackDefense called with:", data)
const isAttackWin = data.attackRoll > data.defenseRoll
const outcome = data.outcome || (data.attackRoll > data.defenseRoll ? "hit" : "miss")
const isAttackWin = outcome !== "miss"
console.log("isAttackWin:", isAttackWin, "attackRoll:", data.attackRoll, "defenseRoll:", data.defenseRoll)
let damageButton = ""
@@ -548,10 +668,10 @@ export default class LethalFantasyUtils {
if (data.attackRollType === "weapon-attack") {
damageButton = `
<div class="attack-result-damage">
<button class="roll-damage-btn" data-attacker-id="${data.attackerId}" data-defender-id="${data.defenderId}" data-defender-token-id="${data.defenderTokenId || ""}" data-weapon-id="${data.attackWeaponId}" data-damage-type="small">
<button class="roll-damage-btn" data-attacker-id="${data.attackerId}" data-defender-id="${data.defenderId}" data-defender-token-id="${data.defenderTokenId || ""}" data-extra-shield-dr="${data.shieldDamageReduction || 0}" data-weapon-id="${data.attackWeaponId}" data-damage-type="small">
<i class="fa-solid fa-dice-d6"></i> Damage (Small)
</button>
<button class="roll-damage-btn" data-attacker-id="${data.attackerId}" data-defender-id="${data.defenderId}" data-defender-token-id="${data.defenderTokenId || ""}" data-weapon-id="${data.attackWeaponId}" data-damage-type="medium">
<button class="roll-damage-btn" data-attacker-id="${data.attackerId}" data-defender-id="${data.defenderId}" data-defender-token-id="${data.defenderTokenId || ""}" data-extra-shield-dr="${data.shieldDamageReduction || 0}" data-weapon-id="${data.attackWeaponId}" data-damage-type="medium">
<i class="fa-solid fa-dice-d20"></i> Damage (Medium)
</button>
</div>
@@ -559,7 +679,7 @@ export default class LethalFantasyUtils {
} else if (data.attackRollType === "monster-attack") {
damageButton = `
<div class="attack-result-damage">
<button class="roll-damage-btn" data-attacker-id="${data.attackerId}" data-defender-id="${data.defenderId}" data-defender-token-id="${data.defenderTokenId || ""}" data-attack-key="${data.attackRollKey}" data-damage-type="monster">
<button class="roll-damage-btn" data-attacker-id="${data.attackerId}" data-defender-id="${data.defenderId}" data-defender-token-id="${data.defenderTokenId || ""}" data-extra-shield-dr="${data.shieldDamageReduction || 0}" data-attack-key="${data.attackRollKey}" data-damage-type="monster">
<i class="fa-solid fa-burst"></i> Damage
</button>
</div>
@@ -588,9 +708,11 @@ export default class LethalFantasyUtils {
</div>
</div>
<div class="combat-result-text">
${isAttackWin ?
`<i class="fa-solid fa-circle-check"></i> <strong>${data.attackerName}</strong> hits <strong>${data.defenderName}</strong>!` :
`<i class="fa-solid fa-shield-halved"></i> <strong>${data.defenderName}</strong> parries the attack!`
${outcome === "shielded-hit"
? `<i class="fa-solid fa-shield"></i> <strong>${data.attackerName}</strong> still hits <strong>${data.defenderName}</strong>, but shield DR <strong>${data.shieldDamageReduction || 0}</strong> reduces the damage.`
: isAttackWin
? `<i class="fa-solid fa-circle-check"></i> <strong>${data.attackerName}</strong> hits <strong>${data.defenderName}</strong>!`
: `<i class="fa-solid fa-shield-halved"></i> <strong>${data.defenderName}</strong> parries the attack!`
}
</div>
${damageButton}