Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f6fb0b68b8 | |||
| e45edd60c4 | |||
| d389a85a9f | |||
| c217490a5b |
@@ -529,16 +529,16 @@ i.lethalfantasy {
|
|||||||
}
|
}
|
||||||
.lethalfantasy .tab.character-combat .main-div .combat-details .combat-detail .armor-hp {
|
.lethalfantasy .tab.character-combat .main-div .combat-details .combat-detail .armor-hp {
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
min-width: 10rem;
|
min-width: 11rem;
|
||||||
max-width: 10rem;
|
max-width: 11rem;
|
||||||
}
|
}
|
||||||
.lethalfantasy .tab.character-combat .main-div .combat-details .combat-detail .armor-hp .name {
|
.lethalfantasy .tab.character-combat .main-div .combat-details .combat-detail .armor-hp .name {
|
||||||
min-width: 6rem;
|
min-width: 6rem;
|
||||||
max-width: 6rem;
|
max-width: 6rem;
|
||||||
}
|
}
|
||||||
.lethalfantasy .tab.character-combat .main-div .combat-details .combat-detail .armor-hp .input {
|
.lethalfantasy .tab.character-combat .main-div .combat-details .combat-detail .armor-hp .input {
|
||||||
min-width: 2.5rem;
|
min-width: 3.5rem;
|
||||||
max-width: 2.5rem;
|
max-width: 3.5rem;
|
||||||
}
|
}
|
||||||
.lethalfantasy .tab.character-combat .main-div .combat-details .combat-detail .granted {
|
.lethalfantasy .tab.character-combat .main-div .combat-details .combat-detail .granted {
|
||||||
min-width: 8rem;
|
min-width: 8rem;
|
||||||
|
|||||||
+267
-265
@@ -375,12 +375,12 @@ Hooks.on("renderChatMessageHTML", (message, html, data) => {
|
|||||||
{
|
{
|
||||||
action: "cancel",
|
action: "cancel",
|
||||||
label: game.i18n.localize("LETHALFANTASY.Combat.proceedNo"),
|
label: game.i18n.localize("LETHALFANTASY.Combat.proceedNo"),
|
||||||
callback: () => null
|
callback: () => "cancel"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
rejectClose: false
|
rejectClose: false
|
||||||
})
|
})
|
||||||
if (manualDR === null) return
|
if (manualDR === null || manualDR === "cancel") return
|
||||||
const rollOpts = {
|
const rollOpts = {
|
||||||
type: "spell-damage",
|
type: "spell-damage",
|
||||||
rollType: "spell-damage",
|
rollType: "spell-damage",
|
||||||
@@ -394,18 +394,7 @@ Hooks.on("renderChatMessageHTML", (message, html, data) => {
|
|||||||
actorName: actor.name,
|
actorName: actor.name,
|
||||||
actorImage: actor.img
|
actorImage: actor.img
|
||||||
}
|
}
|
||||||
const roll = new documents.LethalFantasyRoll(damageFormula, {}, rollOpts)
|
await documents.LethalFantasyRoll.rollSpellDamageToMessage(damageFormula, rollOpts)
|
||||||
await roll.evaluate()
|
|
||||||
const diceResults = []
|
|
||||||
for (const term of roll.dice) {
|
|
||||||
for (const r of term.results) {
|
|
||||||
diceResults.push({ dice: `1D${term.faces}`, value: r.result })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
roll.options.diceResults = diceResults
|
|
||||||
roll.options.rollTotal = roll.total
|
|
||||||
if (game?.dice3d) await game.dice3d.showForRoll(roll, game.user, true)
|
|
||||||
await roll.toMessage()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -537,281 +526,294 @@ Hooks.on("createChatMessage", async (message) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Si le défenseur est un personnage qui perd, proposer Grit/Luck (seulement s'il a des points)
|
// Reaction phase — both sides may use grit/luck/shield/mulligan before the outcome is resolved.
|
||||||
// Seulement si l'utilisateur actuel est le propriétaire du défenseur
|
// After a mulligan reroll (either side), the comparison restarts so both sides can react to the new numbers.
|
||||||
let defenderHandledBonus = false
|
let defenderHandledBonus = false
|
||||||
|
let attackerHandledBonus = false
|
||||||
let shieldReaction = null
|
let shieldReaction = null
|
||||||
let shieldBlocked = false
|
let shieldBlocked = false
|
||||||
const isSpellOrMiracle = attackRollType === "spell-attack" || attackRollType === "miracle-attack"
|
const isSpellOrMiracle = attackRollType === "spell-attack" || attackRollType === "miracle-attack"
|
||||||
if (defender && defenseRoll < attackRoll && isPrimaryController(defender) && !isSpellOrMiracle) {
|
|
||||||
const shieldData = LethalFantasyUtils.getShieldReactionData(defender)
|
|
||||||
let canRerollDefense = LethalFantasyUtils.hasD30Reroll(defenseD30message)
|
|
||||||
let canShieldReact = !!shieldData
|
|
||||||
let canAdHocShield = !shieldData
|
|
||||||
|
|
||||||
while (defenseRoll < attackRoll) {
|
|
||||||
const currentGrit = Number(defender.system?.grit?.current) || 0
|
|
||||||
const currentLuck = Number(defender.system?.luck?.current) || 0
|
|
||||||
const buttons = []
|
|
||||||
|
|
||||||
if (currentGrit > 0) {
|
|
||||||
buttons.push({
|
|
||||||
action: "grit",
|
|
||||||
label: `Spend 1 Grit (+1D6) [${currentGrit} left]`,
|
|
||||||
icon: "fa-solid fa-fist-raised",
|
|
||||||
callback: () => "grit"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentLuck > 0) {
|
|
||||||
buttons.push({
|
|
||||||
action: "luck",
|
|
||||||
label: `Spend 1 Luck (+1D6) [${currentLuck} left]`,
|
|
||||||
icon: "fa-solid fa-clover",
|
|
||||||
callback: () => "luck"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
buttons.push({
|
|
||||||
action: "bonusDie",
|
|
||||||
label: "Add bonus die",
|
|
||||||
icon: "fa-solid fa-dice",
|
|
||||||
callback: () => "bonusDie"
|
|
||||||
})
|
|
||||||
|
|
||||||
if (canRerollDefense) {
|
|
||||||
buttons.push({
|
|
||||||
action: "rerollDefense",
|
|
||||||
label: "Re-roll defense",
|
|
||||||
icon: "fa-solid fa-rotate-right",
|
|
||||||
callback: () => "rerollDefense"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (canShieldReact) {
|
|
||||||
buttons.push({
|
|
||||||
action: "shieldReact",
|
|
||||||
label: `Roll shield (${shieldData.label})`,
|
|
||||||
icon: "fa-solid fa-shield",
|
|
||||||
callback: () => "shieldReact"
|
|
||||||
})
|
|
||||||
} else if (canAdHocShield) {
|
|
||||||
// No pre-configured shield — offer ad-hoc shield option (useful for monsters)
|
|
||||||
buttons.push({
|
|
||||||
action: "adHocShield",
|
|
||||||
label: "Roll ad-hoc shield (choose dice + DR)",
|
|
||||||
icon: "fa-solid fa-shield-halved",
|
|
||||||
callback: () => "adHocShield"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
buttons.push({
|
|
||||||
action: "continue",
|
|
||||||
label: "Continue (no defense bonus)",
|
|
||||||
icon: "fa-solid fa-forward",
|
|
||||||
callback: () => "continue"
|
|
||||||
})
|
|
||||||
|
|
||||||
const choice = await foundry.applications.api.DialogV2.wait({
|
|
||||||
window: { title: "Defense reactions" },
|
|
||||||
classes: ["lethalfantasy"],
|
|
||||||
content: `
|
|
||||||
<div class="grit-luck-dialog">
|
|
||||||
<div class="combat-status">
|
|
||||||
<p><strong>${attackerName}</strong> rolled <strong>${attackRoll}</strong></p>
|
|
||||||
<p><strong>${defenderName}</strong> currently has <strong>${defenseRoll}</strong></p>
|
|
||||||
${defenseD30message ? `<p class="bonus-info">D30 special: ${defenseD30message.description}</p>` : ""}
|
|
||||||
</div>
|
|
||||||
<p class="offer-text">Choose how to improve the defense before resolving the hit.</p>
|
|
||||||
</div>
|
|
||||||
`,
|
|
||||||
buttons,
|
|
||||||
rejectClose: false
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!choice || choice === "continue") break
|
|
||||||
|
|
||||||
defenderHandledBonus = true
|
|
||||||
|
|
||||||
if (choice === "grit") {
|
|
||||||
const bonusRoll = await LethalFantasyUtils.rollBonusDie("1d6", defender, (total) => `<p><strong>${defenderName}</strong> spends 1 Grit and rolls <strong>${total}</strong> for defense.</p>`)
|
|
||||||
defenseRoll += bonusRoll
|
|
||||||
await defender.update({ "system.grit.current": currentGrit - 1 })
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (choice === "luck") {
|
|
||||||
const bonusRoll = await LethalFantasyUtils.rollBonusDie("1d6", defender, (total) => `<p><strong>${defenderName}</strong> spends 1 Luck and rolls <strong>${total}</strong> for defense.</p>`)
|
|
||||||
defenseRoll += bonusRoll
|
|
||||||
await defender.update({ "system.luck.current": currentLuck - 1 })
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (choice === "bonusDie") {
|
|
||||||
const bonusDie = await LethalFantasyUtils.promptCombatBonusDie(defenderName, "attack", defenseRoll, attackRoll)
|
|
||||||
if (!bonusDie) continue
|
|
||||||
const bonusRoll = await LethalFantasyUtils.rollBonusDie(bonusDie, defender, (total, formula) => `<p><strong>${defenderName}</strong> adds <strong>${formula.toUpperCase()}</strong> and rolls <strong>${total}</strong> for defense.</p>`)
|
|
||||||
defenseRoll += bonusRoll
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (choice === "rerollDefense" && canRerollDefense) {
|
|
||||||
const oldDefenseRoll = defenseRoll
|
|
||||||
const reroll = await LethalFantasyUtils.rerollConfiguredRoll(defenseRerollContext)
|
|
||||||
canRerollDefense = false
|
|
||||||
if (!reroll) continue
|
|
||||||
defenseRoll = reroll.options?.rollTotal || reroll.total || oldDefenseRoll
|
|
||||||
await createReactionMessage(defender, `<p><strong>${defenderName}</strong> uses Mulligan and re-rolls defense: <strong>${oldDefenseRoll}</strong> → <strong>${defenseRoll}</strong>.</p>`)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (choice === "shieldReact" && canShieldReact) {
|
|
||||||
const shieldBonus = await LethalFantasyUtils.rollBonusDie(shieldData.formula, defender)
|
|
||||||
const newDefenseTotal = defenseRoll + shieldBonus
|
|
||||||
defenseRoll = newDefenseTotal
|
|
||||||
canShieldReact = false
|
|
||||||
|
|
||||||
if (newDefenseTotal >= attackRoll) {
|
|
||||||
shieldBlocked = true
|
|
||||||
shieldReaction = {
|
|
||||||
damageReduction: shieldData.damageReduction,
|
|
||||||
label: shieldData.label,
|
|
||||||
bonus: shieldBonus
|
|
||||||
}
|
|
||||||
await createReactionMessage(
|
|
||||||
defender,
|
|
||||||
`<p><strong>${defenderName}</strong> rolls <strong>${shieldData.label}</strong> and adds <strong>${shieldBonus}</strong> to defense (${newDefenseTotal} ≥ ${attackRoll}). <strong>Shield blocked the attack!</strong> Both armor DR and shield DR <strong>${shieldData.damageReduction}</strong> will apply to damage.</p>`
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
shieldReaction = null
|
|
||||||
await createReactionMessage(
|
|
||||||
defender,
|
|
||||||
`<p><strong>${defenderName}</strong> rolls <strong>${shieldData.label}</strong> and adds <strong>${shieldBonus}</strong> to defense (${newDefenseTotal} < ${attackRoll}). Shield did not block — normal hit, armor DR only.</p>`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (choice === "adHocShield") {
|
|
||||||
const adHoc = await LethalFantasyUtils.promptAdHocShield(defenderName, attackRoll, defenseRoll)
|
|
||||||
if (!adHoc) continue
|
|
||||||
const shieldBonus = await LethalFantasyUtils.rollBonusDie(adHoc.formula, defender)
|
|
||||||
const newDefenseTotal = defenseRoll + shieldBonus
|
|
||||||
defenseRoll = newDefenseTotal
|
|
||||||
canShieldReact = false
|
|
||||||
canAdHocShield = false
|
|
||||||
|
|
||||||
if (newDefenseTotal >= attackRoll) {
|
|
||||||
shieldBlocked = true
|
|
||||||
shieldReaction = {
|
|
||||||
damageReduction: adHoc.damageReduction,
|
|
||||||
label: `${adHoc.formula.toUpperCase()} shield`,
|
|
||||||
bonus: shieldBonus
|
|
||||||
}
|
|
||||||
await createReactionMessage(
|
|
||||||
defender,
|
|
||||||
`<p><strong>${defenderName}</strong> rolls <strong>${adHoc.formula.toUpperCase()}</strong> shield and adds <strong>${shieldBonus}</strong> to defense (${newDefenseTotal} ≥ ${attackRoll}). <strong>Shield blocked the attack!</strong> Both armor DR and shield DR <strong>${adHoc.damageReduction}</strong> will apply to damage.</p>`
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
shieldReaction = null
|
|
||||||
await createReactionMessage(
|
|
||||||
defender,
|
|
||||||
`<p><strong>${defenderName}</strong> rolls <strong>${adHoc.formula.toUpperCase()}</strong> shield and adds <strong>${shieldBonus}</strong> to defense (${newDefenseTotal} < ${attackRoll}). Shield did not block — normal hit, armor DR only.</p>`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// These persist across mulligan restarts (once used they stay consumed)
|
||||||
|
const shieldData = LethalFantasyUtils.getShieldReactionData(defender)
|
||||||
|
let canRerollDefense = LethalFantasyUtils.hasD30Reroll(defenseD30message)
|
||||||
|
let canShieldReact = !!shieldData
|
||||||
|
let canAdHocShield = !shieldData
|
||||||
let attackRollFinal = attackRoll
|
let attackRollFinal = attackRoll
|
||||||
let attackerHandledBonus = false
|
let canRerollAttack = LethalFantasyUtils.hasD30Reroll(attackD30message)
|
||||||
|
let mulliganRestart = false
|
||||||
|
|
||||||
// Si l'attaquant est un personnage qui perd et a du Grit
|
do {
|
||||||
// Seulement si l'utilisateur actuel est le propriétaire de l'attaquant (pas le MJ)
|
mulliganRestart = false
|
||||||
if (!defenderHandledBonus && attacker && attackRollFinal <= defenseRoll && isPrimaryController(attacker)) {
|
defenderHandledBonus = false
|
||||||
let canRerollAttack = LethalFantasyUtils.hasD30Reroll(attackD30message)
|
attackerHandledBonus = false
|
||||||
|
|
||||||
while (attackRollFinal <= defenseRoll) {
|
// ── Defense reaction loop ──────────────────────────────────────────────
|
||||||
const currentGrit = Number(attacker.system?.grit?.current) || 0
|
if (defender && defenseRoll < attackRollFinal && isPrimaryController(defender) && !isSpellOrMiracle) {
|
||||||
const buttons = []
|
while (defenseRoll < attackRollFinal) {
|
||||||
|
const currentGrit = Number(defender.system?.grit?.current) || 0
|
||||||
|
const currentLuck = Number(defender.system?.luck?.current) || 0
|
||||||
|
const buttons = []
|
||||||
|
|
||||||
|
if (currentGrit > 0) {
|
||||||
|
buttons.push({
|
||||||
|
action: "grit",
|
||||||
|
label: `Spend 1 Grit (+1D6) [${currentGrit} left]`,
|
||||||
|
icon: "fa-solid fa-fist-raised",
|
||||||
|
callback: () => "grit"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentLuck > 0) {
|
||||||
|
buttons.push({
|
||||||
|
action: "luck",
|
||||||
|
label: `Spend 1 Luck (+1D6) [${currentLuck} left]`,
|
||||||
|
icon: "fa-solid fa-clover",
|
||||||
|
callback: () => "luck"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if (currentGrit > 0) {
|
|
||||||
buttons.push({
|
buttons.push({
|
||||||
action: "grit",
|
action: "bonusDie",
|
||||||
label: `Spend 1 Grit (+1D6) [${currentGrit} left]`,
|
label: "Add bonus die",
|
||||||
icon: "fa-solid fa-fist-raised",
|
icon: "fa-solid fa-dice",
|
||||||
callback: () => "grit"
|
callback: () => "bonusDie"
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
buttons.push({
|
if (canRerollDefense) {
|
||||||
action: "bonusDie",
|
buttons.push({
|
||||||
label: "Add bonus die",
|
action: "rerollDefense",
|
||||||
icon: "fa-solid fa-dice",
|
label: "Re-roll defense (Mulligan)",
|
||||||
callback: () => "bonusDie"
|
icon: "fa-solid fa-rotate-right",
|
||||||
})
|
callback: () => "rerollDefense"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canShieldReact) {
|
||||||
|
buttons.push({
|
||||||
|
action: "shieldReact",
|
||||||
|
label: `Roll shield (${shieldData.label})`,
|
||||||
|
icon: "fa-solid fa-shield",
|
||||||
|
callback: () => "shieldReact"
|
||||||
|
})
|
||||||
|
} else if (canAdHocShield) {
|
||||||
|
buttons.push({
|
||||||
|
action: "adHocShield",
|
||||||
|
label: "Roll ad-hoc shield (choose dice + DR)",
|
||||||
|
icon: "fa-solid fa-shield-halved",
|
||||||
|
callback: () => "adHocShield"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if (canRerollAttack && attackRerollContext) {
|
|
||||||
buttons.push({
|
buttons.push({
|
||||||
action: "rerollAttack",
|
action: "continue",
|
||||||
label: "Re-roll attack",
|
label: "Continue (no defense bonus)",
|
||||||
icon: "fa-solid fa-rotate-right",
|
icon: "fa-solid fa-forward",
|
||||||
callback: () => "rerollAttack"
|
callback: () => "continue"
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
buttons.push({
|
const choice = await foundry.applications.api.DialogV2.wait({
|
||||||
action: "continue",
|
window: { title: "Defense reactions" },
|
||||||
label: "Continue (no attack bonus)",
|
classes: ["lethalfantasy"],
|
||||||
icon: "fa-solid fa-forward",
|
content: `
|
||||||
callback: () => "continue"
|
<div class="grit-luck-dialog">
|
||||||
})
|
<div class="combat-status">
|
||||||
|
<p><strong>${attackerName}</strong> rolled <strong>${attackRollFinal}</strong></p>
|
||||||
const choice = await foundry.applications.api.DialogV2.wait({
|
<p><strong>${defenderName}</strong> currently has <strong>${defenseRoll}</strong></p>
|
||||||
window: { title: "Attack reactions" },
|
${defenseD30message ? `<p class="bonus-info">D30 special: ${defenseD30message.description}</p>` : ""}
|
||||||
classes: ["lethalfantasy"],
|
</div>
|
||||||
content: `
|
<p class="offer-text">Choose how to improve the defense before resolving the hit.</p>
|
||||||
<div class="grit-luck-dialog">
|
|
||||||
<div class="combat-status">
|
|
||||||
<p><strong>${attackerName}</strong> currently has <strong>${attackRollFinal}</strong></p>
|
|
||||||
<p><strong>${defenderName}</strong> rolled <strong>${defenseRoll}</strong></p>
|
|
||||||
${attackD30message ? `<p class="bonus-info">D30 special: ${attackD30message.description}</p>` : ""}
|
|
||||||
</div>
|
</div>
|
||||||
<p class="offer-text">Choose how to improve the attack before resolving the combat result.</p>
|
`,
|
||||||
</div>
|
buttons,
|
||||||
`,
|
rejectClose: false
|
||||||
buttons,
|
})
|
||||||
rejectClose: false
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!choice || choice === "continue") break
|
if (!choice || choice === "continue") break
|
||||||
|
|
||||||
attackerHandledBonus = true
|
defenderHandledBonus = true
|
||||||
|
|
||||||
if (choice === "grit") {
|
if (choice === "grit") {
|
||||||
const attackBonus = await LethalFantasyUtils.rollBonusDie("1d6", attacker, (total) => `<p><strong>${attackerName}</strong> spends 1 Grit and rolls <strong>${total}</strong> for attack.</p>`)
|
const bonusRoll = await LethalFantasyUtils.rollBonusDie("1d6", defender, total => `<p><strong>${defenderName}</strong> spends 1 Grit and rolls <strong>${total}</strong> for defense.</p>`)
|
||||||
attackRollFinal += attackBonus
|
defenseRoll += bonusRoll
|
||||||
await attacker.update({ "system.grit.current": currentGrit - 1 })
|
await defender.update({ "system.grit.current": currentGrit - 1 })
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if (choice === "bonusDie") {
|
if (choice === "luck") {
|
||||||
const bonusDie = await LethalFantasyUtils.promptCombatBonusDie(attackerName, "defense", attackRollFinal, defenseRoll)
|
const bonusRoll = await LethalFantasyUtils.rollBonusDie("1d6", defender, total => `<p><strong>${defenderName}</strong> spends 1 Luck and rolls <strong>${total}</strong> for defense.</p>`)
|
||||||
if (!bonusDie) continue
|
defenseRoll += bonusRoll
|
||||||
const attackBonus = await LethalFantasyUtils.rollBonusDie(bonusDie, attacker, (total, formula) => `<p><strong>${attackerName}</strong> adds <strong>${formula.toUpperCase()}</strong> and rolls <strong>${total}</strong> for attack.</p>`)
|
await defender.update({ "system.luck.current": currentLuck - 1 })
|
||||||
attackRollFinal += attackBonus
|
continue
|
||||||
continue
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (choice === "rerollAttack" && canRerollAttack && attackRerollContext) {
|
if (choice === "bonusDie") {
|
||||||
const oldAttackRoll = attackRollFinal
|
const bonusDie = await LethalFantasyUtils.promptCombatBonusDie(defenderName, "attack", defenseRoll, attackRollFinal)
|
||||||
const reroll = await LethalFantasyUtils.rerollConfiguredRoll(attackRerollContext)
|
if (!bonusDie) continue
|
||||||
canRerollAttack = false
|
const bonusRoll = await LethalFantasyUtils.rollBonusDie(bonusDie, defender, (total, formula) => `<p><strong>${defenderName}</strong> adds <strong>${formula.toUpperCase()}</strong> and rolls <strong>${total}</strong> for defense.</p>`)
|
||||||
if (!reroll) continue
|
defenseRoll += bonusRoll
|
||||||
attackRollFinal = reroll.options?.rollTotal || reroll.total || oldAttackRoll
|
continue
|
||||||
await createReactionMessage(attacker, `<p><strong>${attackerName}</strong> uses Mulligan and re-rolls attack: <strong>${oldAttackRoll}</strong> → <strong>${attackRollFinal}</strong>.</p>`)
|
}
|
||||||
|
|
||||||
|
if (choice === "rerollDefense" && canRerollDefense) {
|
||||||
|
const oldDefenseRoll = defenseRoll
|
||||||
|
const reroll = await LethalFantasyUtils.rerollConfiguredRoll(defenseRerollContext)
|
||||||
|
canRerollDefense = false
|
||||||
|
if (!reroll) continue
|
||||||
|
defenseRoll = reroll.options?.rollTotal || reroll.total || oldDefenseRoll
|
||||||
|
await createReactionMessage(defender, `<p><strong>${defenderName}</strong> uses Mulligan and re-rolls defense: <strong>${oldDefenseRoll}</strong> → <strong>${defenseRoll}</strong>. Both sides may now react to the new numbers.</p>`)
|
||||||
|
// Restart the full comparison so both sides can react to the new roll
|
||||||
|
mulliganRestart = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (choice === "shieldReact" && canShieldReact) {
|
||||||
|
const shieldBonus = await LethalFantasyUtils.rollBonusDie(shieldData.formula, defender)
|
||||||
|
const newDefenseTotal = defenseRoll + shieldBonus
|
||||||
|
defenseRoll = newDefenseTotal
|
||||||
|
canShieldReact = false
|
||||||
|
|
||||||
|
if (newDefenseTotal >= attackRollFinal) {
|
||||||
|
shieldBlocked = true
|
||||||
|
shieldReaction = {
|
||||||
|
damageReduction: shieldData.damageReduction,
|
||||||
|
label: shieldData.label,
|
||||||
|
bonus: shieldBonus
|
||||||
|
}
|
||||||
|
await createReactionMessage(
|
||||||
|
defender,
|
||||||
|
`<p><strong>${defenderName}</strong> rolls <strong>${shieldData.label}</strong> and adds <strong>${shieldBonus}</strong> to defense (${newDefenseTotal} ≥ ${attackRollFinal}). <strong>Shield blocked the attack!</strong> Both armor DR and shield DR <strong>${shieldData.damageReduction}</strong> will apply to damage.</p>`
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
shieldReaction = null
|
||||||
|
await createReactionMessage(
|
||||||
|
defender,
|
||||||
|
`<p><strong>${defenderName}</strong> rolls <strong>${shieldData.label}</strong> and adds <strong>${shieldBonus}</strong> to defense (${newDefenseTotal} < ${attackRollFinal}). Shield did not block — normal hit, armor DR only.</p>`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (choice === "adHocShield") {
|
||||||
|
const adHoc = await LethalFantasyUtils.promptAdHocShield(defenderName, attackRollFinal, defenseRoll)
|
||||||
|
if (!adHoc) continue
|
||||||
|
const shieldBonus = await LethalFantasyUtils.rollBonusDie(adHoc.formula, defender)
|
||||||
|
const newDefenseTotal = defenseRoll + shieldBonus
|
||||||
|
defenseRoll = newDefenseTotal
|
||||||
|
canShieldReact = false
|
||||||
|
canAdHocShield = false
|
||||||
|
|
||||||
|
if (newDefenseTotal >= attackRollFinal) {
|
||||||
|
shieldBlocked = true
|
||||||
|
shieldReaction = {
|
||||||
|
damageReduction: adHoc.damageReduction,
|
||||||
|
label: `${adHoc.formula.toUpperCase()} shield`,
|
||||||
|
bonus: shieldBonus
|
||||||
|
}
|
||||||
|
await createReactionMessage(
|
||||||
|
defender,
|
||||||
|
`<p><strong>${defenderName}</strong> rolls <strong>${adHoc.formula.toUpperCase()}</strong> shield and adds <strong>${shieldBonus}</strong> to defense (${newDefenseTotal} ≥ ${attackRollFinal}). <strong>Shield blocked the attack!</strong> Both armor DR and shield DR <strong>${adHoc.damageReduction}</strong> will apply to damage.</p>`
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
shieldReaction = null
|
||||||
|
await createReactionMessage(
|
||||||
|
defender,
|
||||||
|
`<p><strong>${defenderName}</strong> rolls <strong>${adHoc.formula.toUpperCase()}</strong> shield and adds <strong>${shieldBonus}</strong> to defense (${newDefenseTotal} < ${attackRollFinal}). Shield did not block — normal hit, armor DR only.</p>`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
if (mulliganRestart) continue
|
||||||
|
|
||||||
|
// ── Attack reaction loop ───────────────────────────────────────────────
|
||||||
|
if (!defenderHandledBonus && attacker && attackRollFinal <= defenseRoll && isPrimaryController(attacker)) {
|
||||||
|
while (attackRollFinal <= defenseRoll) {
|
||||||
|
const currentGrit = Number(attacker.system?.grit?.current) || 0
|
||||||
|
const buttons = []
|
||||||
|
|
||||||
|
if (currentGrit > 0) {
|
||||||
|
buttons.push({
|
||||||
|
action: "grit",
|
||||||
|
label: `Spend 1 Grit (+1D6) [${currentGrit} left]`,
|
||||||
|
icon: "fa-solid fa-fist-raised",
|
||||||
|
callback: () => "grit"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
buttons.push({
|
||||||
|
action: "bonusDie",
|
||||||
|
label: "Add bonus die",
|
||||||
|
icon: "fa-solid fa-dice",
|
||||||
|
callback: () => "bonusDie"
|
||||||
|
})
|
||||||
|
|
||||||
|
if (canRerollAttack && attackRerollContext) {
|
||||||
|
buttons.push({
|
||||||
|
action: "rerollAttack",
|
||||||
|
label: "Re-roll attack (Mulligan)",
|
||||||
|
icon: "fa-solid fa-rotate-right",
|
||||||
|
callback: () => "rerollAttack"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
buttons.push({
|
||||||
|
action: "continue",
|
||||||
|
label: "Continue (no attack bonus)",
|
||||||
|
icon: "fa-solid fa-forward",
|
||||||
|
callback: () => "continue"
|
||||||
|
})
|
||||||
|
|
||||||
|
const choice = await foundry.applications.api.DialogV2.wait({
|
||||||
|
window: { title: "Attack reactions" },
|
||||||
|
classes: ["lethalfantasy"],
|
||||||
|
content: `
|
||||||
|
<div class="grit-luck-dialog">
|
||||||
|
<div class="combat-status">
|
||||||
|
<p><strong>${attackerName}</strong> currently has <strong>${attackRollFinal}</strong></p>
|
||||||
|
<p><strong>${defenderName}</strong> rolled <strong>${defenseRoll}</strong></p>
|
||||||
|
${attackD30message ? `<p class="bonus-info">D30 special: ${attackD30message.description}</p>` : ""}
|
||||||
|
</div>
|
||||||
|
<p class="offer-text">Choose how to improve the attack before resolving the combat result.</p>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
buttons,
|
||||||
|
rejectClose: false
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!choice || choice === "continue") break
|
||||||
|
|
||||||
|
attackerHandledBonus = true
|
||||||
|
|
||||||
|
if (choice === "grit") {
|
||||||
|
const attackBonus = await LethalFantasyUtils.rollBonusDie("1d6", attacker, total => `<p><strong>${attackerName}</strong> spends 1 Grit and rolls <strong>${total}</strong> for attack.</p>`)
|
||||||
|
attackRollFinal += attackBonus
|
||||||
|
await attacker.update({ "system.grit.current": currentGrit - 1 })
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (choice === "bonusDie") {
|
||||||
|
const bonusDie = await LethalFantasyUtils.promptCombatBonusDie(attackerName, "defense", attackRollFinal, defenseRoll)
|
||||||
|
if (!bonusDie) continue
|
||||||
|
const attackBonus = await LethalFantasyUtils.rollBonusDie(bonusDie, attacker, (total, formula) => `<p><strong>${attackerName}</strong> adds <strong>${formula.toUpperCase()}</strong> and rolls <strong>${total}</strong> for attack.</p>`)
|
||||||
|
attackRollFinal += attackBonus
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (choice === "rerollAttack" && canRerollAttack && attackRerollContext) {
|
||||||
|
const oldAttackRoll = attackRollFinal
|
||||||
|
const reroll = await LethalFantasyUtils.rerollConfiguredRoll(attackRerollContext)
|
||||||
|
canRerollAttack = false
|
||||||
|
if (!reroll) continue
|
||||||
|
attackRollFinal = reroll.options?.rollTotal || reroll.total || oldAttackRoll
|
||||||
|
await createReactionMessage(attacker, `<p><strong>${attackerName}</strong> uses Mulligan and re-rolls attack: <strong>${oldAttackRoll}</strong> → <strong>${attackRollFinal}</strong>. Both sides may now react to the new numbers.</p>`)
|
||||||
|
// Restart the full comparison so both sides can react to the new roll
|
||||||
|
mulliganRestart = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (mulliganRestart)
|
||||||
|
|
||||||
const shieldDamageReduction = shieldBlocked ? shieldReaction.damageReduction : 0
|
const shieldDamageReduction = shieldBlocked ? shieldReaction.damageReduction : 0
|
||||||
const outcome = shieldBlocked ? "shielded-hit" : (attackRollFinal > defenseRoll ? "hit" : "miss")
|
const outcome = shieldBlocked ? "shielded-hit" : (attackRollFinal > defenseRoll ? "hit" : "miss")
|
||||||
|
|||||||
@@ -268,12 +268,12 @@ export default class LethalFantasyCharacterSheet extends LethalFantasyActorSheet
|
|||||||
{
|
{
|
||||||
action: "cancel",
|
action: "cancel",
|
||||||
label: game.i18n.localize("LETHALFANTASY.Combat.proceedNo"),
|
label: game.i18n.localize("LETHALFANTASY.Combat.proceedNo"),
|
||||||
callback: () => null
|
callback: () => "cancel"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
rejectClose: false
|
rejectClose: false
|
||||||
})
|
})
|
||||||
if (manualDR === null) return
|
if (manualDR === null || manualDR === "cancel") return
|
||||||
|
|
||||||
const rollOpts = {
|
const rollOpts = {
|
||||||
type: "spell-damage",
|
type: "spell-damage",
|
||||||
@@ -286,18 +286,7 @@ export default class LethalFantasyCharacterSheet extends LethalFantasyActorSheet
|
|||||||
actorName: this.actor.name,
|
actorName: this.actor.name,
|
||||||
actorImage: this.actor.img
|
actorImage: this.actor.img
|
||||||
}
|
}
|
||||||
const roll = new LethalFantasyRoll(formula, {}, rollOpts)
|
await LethalFantasyRoll.rollSpellDamageToMessage(formula, rollOpts)
|
||||||
await roll.evaluate()
|
|
||||||
const diceResults = []
|
|
||||||
for (const term of roll.dice) {
|
|
||||||
for (const r of term.results) {
|
|
||||||
diceResults.push({ dice: `1D${term.faces}`, value: r.result })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
roll.options.diceResults = diceResults
|
|
||||||
roll.options.rollTotal = roll.total
|
|
||||||
if (game?.dice3d) await game.dice3d.showForRoll(roll, game.user, true)
|
|
||||||
await roll.toMessage()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static #onCreateEquipment(event, target) {
|
static #onCreateEquipment(event, target) {
|
||||||
|
|||||||
@@ -108,6 +108,10 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "Possible Flawless or Legendary Defense or Add D20E to Defense"
|
"description": "Possible Flawless or Legendary Defense or Add D20E to Defense"
|
||||||
|
},
|
||||||
|
"saving_throws": {
|
||||||
|
"type": "save_auto_success",
|
||||||
|
"description": "Saving Throw Succeeds Regardless of Opposing Roll"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"29": {
|
"29": {
|
||||||
@@ -145,6 +149,11 @@
|
|||||||
"type": "gain_grit",
|
"type": "gain_grit",
|
||||||
"amount": 1,
|
"amount": 1,
|
||||||
"description": "Gain 1 Grit"
|
"description": "Gain 1 Grit"
|
||||||
|
},
|
||||||
|
"saving_throws": {
|
||||||
|
"type": "gain_grit",
|
||||||
|
"amount": 1,
|
||||||
|
"description": "Gain 1 Grit"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"28": {
|
"28": {
|
||||||
@@ -199,6 +208,12 @@
|
|||||||
"amount": 1,
|
"amount": 1,
|
||||||
"target": "skill",
|
"target": "skill",
|
||||||
"description": "Add 1 to Skill Roll"
|
"description": "Add 1 to Skill Roll"
|
||||||
|
},
|
||||||
|
"saving_throws": {
|
||||||
|
"type": "bonus_flat",
|
||||||
|
"amount": 1,
|
||||||
|
"target": "save",
|
||||||
|
"description": "Add 1 to Saving Throw"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"21": {
|
"21": {
|
||||||
@@ -239,6 +254,12 @@
|
|||||||
"ranged_defense": {
|
"ranged_defense": {
|
||||||
"type": "recover_pain",
|
"type": "recover_pain",
|
||||||
"description": "Defender Recovers or ignores any flash of pain"
|
"description": "Defender Recovers or ignores any flash of pain"
|
||||||
|
},
|
||||||
|
"saving_throws": {
|
||||||
|
"type": "bonus_dice",
|
||||||
|
"dice": "D6",
|
||||||
|
"target": "save",
|
||||||
|
"description": "Granted D6 (1-6) Saving Throw Modifier for this Saving Throw Attempt"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"20": {
|
"20": {
|
||||||
@@ -349,6 +370,12 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "Possible 20/20 defense that avoids Any Attack Except a Lethal Strike or adds D12 to defense"
|
"description": "Possible 20/20 defense that avoids Any Attack Except a Lethal Strike or adds D12 to defense"
|
||||||
|
},
|
||||||
|
"saving_throws": {
|
||||||
|
"type": "bonus_flat",
|
||||||
|
"amount": 20,
|
||||||
|
"target": "save",
|
||||||
|
"description": "20 Added to Saving Throw"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"15": {
|
"15": {
|
||||||
@@ -391,6 +418,12 @@
|
|||||||
"shield_bash"
|
"shield_bash"
|
||||||
],
|
],
|
||||||
"description": "Kick, Punch or Shield Bash"
|
"description": "Kick, Punch or Shield Bash"
|
||||||
|
},
|
||||||
|
"saving_throws": {
|
||||||
|
"type": "bonus_flat",
|
||||||
|
"amount": 1,
|
||||||
|
"target": "save",
|
||||||
|
"description": "Add 1 to Saving Throw"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"13": {},
|
"13": {},
|
||||||
@@ -445,6 +478,12 @@
|
|||||||
"shield_bash"
|
"shield_bash"
|
||||||
],
|
],
|
||||||
"description": "Kick, Punch or Shield Bash"
|
"description": "Kick, Punch or Shield Bash"
|
||||||
|
},
|
||||||
|
"saving_throws": {
|
||||||
|
"type": "bonus_flat",
|
||||||
|
"amount": 1,
|
||||||
|
"target": "save",
|
||||||
|
"description": "Add 1 to Saving Throw"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"8": {
|
"8": {
|
||||||
@@ -475,6 +514,10 @@
|
|||||||
"ranged_defense": {
|
"ranged_defense": {
|
||||||
"type": "mulligan",
|
"type": "mulligan",
|
||||||
"description": "Mulligan, Can Choose to Re-Roll This Defense"
|
"description": "Mulligan, Can Choose to Re-Roll This Defense"
|
||||||
|
},
|
||||||
|
"saving_throws": {
|
||||||
|
"type": "mulligan",
|
||||||
|
"description": "Mulligan, Can Re-Roll This Saving Throw"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"7": {
|
"7": {
|
||||||
@@ -528,6 +571,12 @@
|
|||||||
"shield_bash"
|
"shield_bash"
|
||||||
],
|
],
|
||||||
"description": "Kick, Punch, or Shield Bash"
|
"description": "Kick, Punch, or Shield Bash"
|
||||||
|
},
|
||||||
|
"saving_throws": {
|
||||||
|
"type": "bonus_flat",
|
||||||
|
"amount": 1,
|
||||||
|
"target": "save",
|
||||||
|
"description": "Add 1 to Saving Throw"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"3": {
|
"3": {
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ export default class D30Roll {
|
|||||||
RANGED_DEFENSE: "ranged_defense",
|
RANGED_DEFENSE: "ranged_defense",
|
||||||
ARCANE_SPELL_ATTACK: "arcane_spell_attack",
|
ARCANE_SPELL_ATTACK: "arcane_spell_attack",
|
||||||
ARCANE_SPELL_DEFENSE: "arcane_spell_defense",
|
ARCANE_SPELL_DEFENSE: "arcane_spell_defense",
|
||||||
SKILL_ROLLS: "skill_rolls"
|
SKILL_ROLLS: "skill_rolls",
|
||||||
|
SAVING_THROWS: "saving_throws"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -137,11 +138,15 @@ export default class D30Roll {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Skill types
|
// Skill types
|
||||||
if (externalType === "skill" || externalType === "monster-skill" ||
|
if (externalType === "skill" || externalType === "monster-skill" || externalType === "challenge") {
|
||||||
externalType === "save" || externalType === "challenge") {
|
|
||||||
return this.ROLL_TYPES.SKILL_ROLLS
|
return this.ROLL_TYPES.SKILL_ROLLS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Saving throw types
|
||||||
|
if (externalType === "save") {
|
||||||
|
return options.isSpellSave ? this.ROLL_TYPES.ARCANE_SPELL_DEFENSE : this.ROLL_TYPES.SAVING_THROWS
|
||||||
|
}
|
||||||
|
|
||||||
// If no match, return null
|
// If no match, return null
|
||||||
console.warn(`D30Roll | Unknown external roll type: ${externalType}`)
|
console.warn(`D30Roll | Unknown external roll type: ${externalType}`)
|
||||||
return null
|
return null
|
||||||
|
|||||||
@@ -338,6 +338,10 @@ export default class LethalFantasyRoll extends Roll {
|
|||||||
let modifier = "+0"
|
let modifier = "+0"
|
||||||
let targetName
|
let targetName
|
||||||
|
|
||||||
|
// True for any ranged attack: PC weapon (ranged type) or monster attack (ranged mode)
|
||||||
|
const isRangedAttack = (options.rollType === "weapon-attack" && options.rollTarget?.weapon?.system?.weaponType === "ranged")
|
||||||
|
|| (options.rollType === "monster-attack" && options.rollTarget?.attackMode === "ranged")
|
||||||
|
|
||||||
let dialogContext = {
|
let dialogContext = {
|
||||||
rollType: options.rollType,
|
rollType: options.rollType,
|
||||||
rollTarget: options.rollTarget,
|
rollTarget: options.rollTarget,
|
||||||
@@ -362,7 +366,8 @@ export default class LethalFantasyRoll extends Roll {
|
|||||||
modifier,
|
modifier,
|
||||||
saveSpell: false,
|
saveSpell: false,
|
||||||
favor: "none",
|
favor: "none",
|
||||||
targetName
|
targetName,
|
||||||
|
isRangedAttack
|
||||||
}
|
}
|
||||||
let rollContext
|
let rollContext
|
||||||
if (options.rollContext) {
|
if (options.rollContext) {
|
||||||
@@ -604,7 +609,7 @@ export default class LethalFantasyRoll extends Roll {
|
|||||||
rollD30.total,
|
rollD30.total,
|
||||||
options.rollType,
|
options.rollType,
|
||||||
options.rollTarget?.weapon,
|
options.rollTarget?.weapon,
|
||||||
{ isRanged: isRangedForD30 }
|
{ isRanged: isRangedForD30, isSpellSave: saveSpell }
|
||||||
)
|
)
|
||||||
options.D30message = d30Message
|
options.D30message = d30Message
|
||||||
}
|
}
|
||||||
@@ -782,9 +787,18 @@ export default class LethalFantasyRoll extends Roll {
|
|||||||
let buttons = []
|
let buttons = []
|
||||||
if (currentAction) {
|
if (currentAction) {
|
||||||
if (currentAction.type === "weapon") {
|
if (currentAction.type === "weapon") {
|
||||||
|
let weaponLabel = "Roll progression dice"
|
||||||
|
if (currentAction.rangedMode) {
|
||||||
|
// Compute loading count from the speed formula (e.g. "3+1d6" → load=3)
|
||||||
|
const speedStr = currentAction.system?.speed?.[currentAction.rangedMode] ?? ""
|
||||||
|
const rangedLoad = currentAction.rangedLoad ?? (Number(speedStr.split("+")[0]) || 0)
|
||||||
|
if (rangedLoad > 0 && !currentAction.weaponLoaded) {
|
||||||
|
weaponLabel = "Load weapon"
|
||||||
|
}
|
||||||
|
}
|
||||||
buttons.push({
|
buttons.push({
|
||||||
action: "roll",
|
action: "roll",
|
||||||
label: "Roll progression dice",
|
label: weaponLabel,
|
||||||
callback: (event, button) => {
|
callback: (event, button) => {
|
||||||
let pos = $('#combat-action-dialog').position()
|
let pos = $('#combat-action-dialog').position()
|
||||||
game.user.setFlag(SYSTEM.id, "combat-action-dialog-pos", pos)
|
game.user.setFlag(SYSTEM.id, "combat-action-dialog-pos", pos)
|
||||||
@@ -910,7 +924,8 @@ export default class LethalFantasyRoll extends Roll {
|
|||||||
await combatant.setFlag(SYSTEM.id, "currentAction", foundry.utils.duplicate(currentAction))
|
await combatant.setFlag(SYSTEM.id, "currentAction", foundry.utils.duplicate(currentAction))
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
let message = `Spell/Miracle ${currentAction.name} ready to be cast on next second !`
|
// Last counting second — announce ready and transition immediately (no extra second consumed)
|
||||||
|
let message = `Casting time : ${currentAction.name}, count : ${time}/${time} — ready to cast next second !`
|
||||||
ChatMessage.create({ content: message, speaker: ChatMessage.getSpeaker({ actor: combatant.actor }) })
|
ChatMessage.create({ content: message, speaker: ChatMessage.getSpeaker({ actor: combatant.actor }) })
|
||||||
currentAction.castingTime = 1
|
currentAction.castingTime = 1
|
||||||
currentAction.spellStatus = "toBeCasted"
|
currentAction.spellStatus = "toBeCasted"
|
||||||
@@ -1524,4 +1539,43 @@ export default class LethalFantasyRoll extends Roll {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluate a spell/miracle damage formula with per-die explosion, then post to chat.
|
||||||
|
* Explosion dice are shown manually via showForRoll; the main roll is shown automatically
|
||||||
|
* by toMessage() (which triggers Dice So Nice via its createChatMessage hook).
|
||||||
|
* Append "NE" to the formula to disable explosion.
|
||||||
|
*
|
||||||
|
* @param {string} formula Dice formula, e.g. "1d8", "2d6", "1d8NE"
|
||||||
|
* @param {Object} rollOpts Options for LethalFantasyRoll (rollType, actorId, defenderId, etc.)
|
||||||
|
* @returns {Promise<ChatMessage>}
|
||||||
|
*/
|
||||||
|
static async rollSpellDamageToMessage(formula, rollOpts) {
|
||||||
|
const roll = new LethalFantasyRoll(formula, {}, rollOpts)
|
||||||
|
await roll.evaluate()
|
||||||
|
const shouldExplode = !/NE$/i.test(formula)
|
||||||
|
const diceResults = []
|
||||||
|
let diceSum = 0
|
||||||
|
for (const term of roll.dice) {
|
||||||
|
const singleDice = `1D${term.faces}`
|
||||||
|
for (const r of term.results) {
|
||||||
|
let diceResult = r.result
|
||||||
|
diceResults.push({ dice: singleDice.toUpperCase(), value: diceResult })
|
||||||
|
diceSum += diceResult
|
||||||
|
if (shouldExplode && term.faces > 0) {
|
||||||
|
while (diceResult === term.faces) {
|
||||||
|
const xr = await new Roll(singleDice).evaluate()
|
||||||
|
if (game?.dice3d) await game.dice3d.showForRoll(xr, game.user, true)
|
||||||
|
// Optional chaining guards against unexpected roll structure
|
||||||
|
diceResult = xr.dice?.[0]?.results?.[0]?.result ?? (term.faces - 1)
|
||||||
|
diceResults.push({ dice: `${singleDice.toUpperCase()}-1`, value: diceResult - 1 })
|
||||||
|
diceSum += (diceResult - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
roll.options.diceResults = diceResults
|
||||||
|
roll.options.rollTotal = diceSum
|
||||||
|
return roll.toMessage()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -283,23 +283,6 @@ export default class LethalFantasyCharacter extends foundry.abstract.TypeDataMod
|
|||||||
*/
|
*/
|
||||||
async roll(rollType, rollTarget, defenderId, defenderTokenId, extraShieldDr = 0) {
|
async roll(rollType, rollTarget, defenderId, defenderTokenId, extraShieldDr = 0) {
|
||||||
const hasTarget = false
|
const hasTarget = false
|
||||||
|
|
||||||
// Ranged weapon attacks from PCs use the ranged attack dialog (range, movement, aim modifiers)
|
|
||||||
if (rollType === "weapon-attack" && rollTarget?.isRangedAttack === true) {
|
|
||||||
let roll = await LethalFantasyRoll.promptRangedAttack({
|
|
||||||
rollType: "weapon-attack",
|
|
||||||
actorId: this.parent.id,
|
|
||||||
actorName: this.parent.name,
|
|
||||||
actorImage: this.parent.img,
|
|
||||||
rollTarget,
|
|
||||||
defenderId,
|
|
||||||
defenderTokenId,
|
|
||||||
})
|
|
||||||
if (!roll) return null
|
|
||||||
await roll.toMessage({}, { messageMode: roll.options.rollMode })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let roll = await LethalFantasyRoll.prompt({
|
let roll = await LethalFantasyRoll.prompt({
|
||||||
rollType,
|
rollType,
|
||||||
rollTarget,
|
rollTarget,
|
||||||
|
|||||||
@@ -167,22 +167,7 @@ export default class LethalFantasyMonster extends foundry.abstract.TypeDataModel
|
|||||||
async roll(rollType, rollTarget, defenderId = undefined, defenderTokenId = undefined, extraShieldDr = 0) {
|
async roll(rollType, rollTarget, defenderId = undefined, defenderTokenId = undefined, extraShieldDr = 0) {
|
||||||
const hasTarget = false
|
const hasTarget = false
|
||||||
|
|
||||||
// Ranged monster attacks use a specialized dialog with range/movement/size/aim modifiers
|
// Ranged monster defense uses the ranged defense dialog (movement, range, size modifiers)
|
||||||
if (rollType === "monster-attack" && rollTarget?.attackMode === "ranged") {
|
|
||||||
let roll = await LethalFantasyRoll.promptRangedAttack({
|
|
||||||
actorId: this.parent.id,
|
|
||||||
actorName: this.parent.name,
|
|
||||||
actorImage: this.parent.img,
|
|
||||||
rollTarget,
|
|
||||||
defenderId,
|
|
||||||
defenderTokenId,
|
|
||||||
})
|
|
||||||
if (!roll) return null
|
|
||||||
await roll.toMessage({}, { messageMode: roll.options.rollMode })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ranged monster defense uses the same ranged defense dialog as PC characters
|
|
||||||
if (rollType === "monster-defense" && rollTarget?.isRangedDefense === true) {
|
if (rollType === "monster-defense" && rollTarget?.isRangedDefense === true) {
|
||||||
let roll = await LethalFantasyRoll.promptRangedDefense({
|
let roll = await LethalFantasyRoll.promptRangedDefense({
|
||||||
actorId: this.parent.id,
|
actorId: this.parent.id,
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
MANIFEST-000615
|
MANIFEST-000623
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
2026/05/23-18:45:10.214922 7fa233fff6c0 Recovering log #613
|
2026/05/25-11:43:40.059603 7f88315ff6c0 Recovering log #621
|
||||||
2026/05/23-18:45:10.223944 7fa233fff6c0 Delete type=3 #611
|
2026/05/25-11:43:40.071136 7f88315ff6c0 Delete type=3 #619
|
||||||
2026/05/23-18:45:10.223997 7fa233fff6c0 Delete type=0 #613
|
2026/05/25-11:43:40.071202 7f88315ff6c0 Delete type=0 #621
|
||||||
2026/05/23-19:09:56.190877 7fa2327fc6c0 Level-0 table #618: started
|
2026/05/25-12:29:17.727995 7f87e2ffd6c0 Level-0 table #626: started
|
||||||
2026/05/23-19:09:56.190922 7fa2327fc6c0 Level-0 table #618: 0 bytes OK
|
2026/05/25-12:29:17.728028 7f87e2ffd6c0 Level-0 table #626: 0 bytes OK
|
||||||
2026/05/23-19:09:56.224359 7fa2327fc6c0 Delete type=0 #616
|
2026/05/25-12:29:17.733783 7f87e2ffd6c0 Delete type=0 #624
|
||||||
2026/05/23-19:09:56.339160 7fa2327fc6c0 Manual compaction at level-0 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end)
|
2026/05/25-12:29:17.740143 7f87e2ffd6c0 Manual compaction at level-0 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end)
|
||||||
2026/05/23-19:09:56.339194 7fa2327fc6c0 Manual compaction at level-1 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end)
|
2026/05/25-12:29:17.740167 7f87e2ffd6c0 Manual compaction at level-1 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
2026/05/20-23:09:33.153266 7f179e7ed6c0 Recovering log #609
|
2026/05/24-09:22:43.041326 7fdfa95ff6c0 Recovering log #617
|
||||||
2026/05/20-23:09:33.205459 7f179e7ed6c0 Delete type=3 #607
|
2026/05/24-09:22:43.083988 7fdfa95ff6c0 Delete type=3 #615
|
||||||
2026/05/20-23:09:33.205507 7f179e7ed6c0 Delete type=0 #609
|
2026/05/24-09:22:43.084045 7fdfa95ff6c0 Delete type=0 #617
|
||||||
2026/05/20-23:16:53.517842 7f179d7eb6c0 Level-0 table #614: started
|
2026/05/24-09:42:00.304351 7fdf5affd6c0 Level-0 table #622: started
|
||||||
2026/05/20-23:16:53.517893 7f179d7eb6c0 Level-0 table #614: 0 bytes OK
|
2026/05/24-09:42:00.304423 7fdf5affd6c0 Level-0 table #622: 0 bytes OK
|
||||||
2026/05/20-23:16:53.523991 7f179d7eb6c0 Delete type=0 #612
|
2026/05/24-09:42:00.311333 7fdf5affd6c0 Delete type=0 #620
|
||||||
2026/05/20-23:16:53.536989 7f179d7eb6c0 Manual compaction at level-0 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end)
|
2026/05/24-09:42:00.334170 7fdf5affd6c0 Manual compaction at level-0 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end)
|
||||||
2026/05/20-23:16:53.537026 7f179d7eb6c0 Manual compaction at level-1 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end)
|
2026/05/24-09:42:00.334370 7fdf5affd6c0 Manual compaction at level-1 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
Binary file not shown.
@@ -1 +1 @@
|
|||||||
MANIFEST-000612
|
MANIFEST-000620
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
2026/05/23-18:45:10.228083 7fa232ffd6c0 Recovering log #610
|
2026/05/25-11:43:40.077580 7f87e37fe6c0 Recovering log #618
|
||||||
2026/05/23-18:45:10.236935 7fa232ffd6c0 Delete type=3 #608
|
2026/05/25-11:43:40.087750 7f87e37fe6c0 Delete type=3 #616
|
||||||
2026/05/23-18:45:10.236958 7fa232ffd6c0 Delete type=0 #610
|
2026/05/25-11:43:40.087800 7f87e37fe6c0 Delete type=0 #618
|
||||||
2026/05/23-19:09:56.261648 7fa2327fc6c0 Level-0 table #615: started
|
2026/05/25-12:29:17.758832 7f87e2ffd6c0 Level-0 table #623: started
|
||||||
2026/05/23-19:09:56.261672 7fa2327fc6c0 Level-0 table #615: 0 bytes OK
|
2026/05/25-12:29:17.758858 7f87e2ffd6c0 Level-0 table #623: 0 bytes OK
|
||||||
2026/05/23-19:09:56.304021 7fa2327fc6c0 Delete type=0 #613
|
2026/05/25-12:29:17.764622 7f87e2ffd6c0 Delete type=0 #621
|
||||||
2026/05/23-19:09:56.339181 7fa2327fc6c0 Manual compaction at level-0 from '!folders!yPWGvxHJbDNHVSnY' @ 72057594037927935 : 1 .. '!items!x5gLtqlW4sdDmHTd' @ 0 : 0; will stop at (end)
|
2026/05/25-12:29:17.777473 7f87e2ffd6c0 Manual compaction at level-0 from '!folders!yPWGvxHJbDNHVSnY' @ 72057594037927935 : 1 .. '!items!x5gLtqlW4sdDmHTd' @ 0 : 0; will stop at (end)
|
||||||
2026/05/23-19:09:56.339208 7fa2327fc6c0 Manual compaction at level-1 from '!folders!yPWGvxHJbDNHVSnY' @ 72057594037927935 : 1 .. '!items!x5gLtqlW4sdDmHTd' @ 0 : 0; will stop at (end)
|
2026/05/25-12:29:17.784124 7f87e2ffd6c0 Manual compaction at level-1 from '!folders!yPWGvxHJbDNHVSnY' @ 72057594037927935 : 1 .. '!items!x5gLtqlW4sdDmHTd' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
2026/05/20-23:09:33.234730 7f179e7ed6c0 Recovering log #606
|
2026/05/24-09:22:43.091828 7fdfa8dfe6c0 Recovering log #614
|
||||||
2026/05/20-23:09:33.288868 7f179e7ed6c0 Delete type=3 #604
|
2026/05/24-09:22:43.131041 7fdfa8dfe6c0 Delete type=3 #612
|
||||||
2026/05/20-23:09:33.288920 7f179e7ed6c0 Delete type=0 #606
|
2026/05/24-09:22:43.131104 7fdfa8dfe6c0 Delete type=0 #614
|
||||||
2026/05/20-23:16:53.524172 7f179d7eb6c0 Level-0 table #611: started
|
2026/05/24-09:42:00.326388 7fdf5affd6c0 Level-0 table #619: started
|
||||||
2026/05/20-23:16:53.524219 7f179d7eb6c0 Level-0 table #611: 0 bytes OK
|
2026/05/24-09:42:00.326417 7fdf5affd6c0 Level-0 table #619: 0 bytes OK
|
||||||
2026/05/20-23:16:53.530312 7f179d7eb6c0 Delete type=0 #609
|
2026/05/24-09:42:00.334051 7fdf5affd6c0 Delete type=0 #617
|
||||||
2026/05/20-23:16:53.537003 7f179d7eb6c0 Manual compaction at level-0 from '!folders!yPWGvxHJbDNHVSnY' @ 72057594037927935 : 1 .. '!items!x5gLtqlW4sdDmHTd' @ 0 : 0; will stop at (end)
|
2026/05/24-09:42:00.334205 7fdf5affd6c0 Manual compaction at level-0 from '!folders!yPWGvxHJbDNHVSnY' @ 72057594037927935 : 1 .. '!items!x5gLtqlW4sdDmHTd' @ 0 : 0; will stop at (end)
|
||||||
2026/05/20-23:16:53.537059 7f179d7eb6c0 Manual compaction at level-1 from '!folders!yPWGvxHJbDNHVSnY' @ 72057594037927935 : 1 .. '!items!x5gLtqlW4sdDmHTd' @ 0 : 0; will stop at (end)
|
2026/05/24-09:42:00.334364 7fdf5affd6c0 Manual compaction at level-1 from '!folders!yPWGvxHJbDNHVSnY' @ 72057594037927935 : 1 .. '!items!x5gLtqlW4sdDmHTd' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
Binary file not shown.
@@ -1 +1 @@
|
|||||||
MANIFEST-000617
|
MANIFEST-000625
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
2026/05/23-18:45:10.200781 7fa2337fe6c0 Recovering log #615
|
2026/05/25-11:43:40.044921 7f87e3fff6c0 Recovering log #623
|
||||||
2026/05/23-18:45:10.211345 7fa2337fe6c0 Delete type=3 #613
|
2026/05/25-11:43:40.054992 7f87e3fff6c0 Delete type=3 #621
|
||||||
2026/05/23-18:45:10.211416 7fa2337fe6c0 Delete type=0 #615
|
2026/05/25-11:43:40.055048 7f87e3fff6c0 Delete type=0 #623
|
||||||
2026/05/23-19:09:56.304132 7fa2327fc6c0 Level-0 table #620: started
|
2026/05/25-12:29:17.710484 7f87e2ffd6c0 Level-0 table #628: started
|
||||||
2026/05/23-19:09:56.304162 7fa2327fc6c0 Level-0 table #620: 0 bytes OK
|
2026/05/25-12:29:17.710568 7f87e2ffd6c0 Level-0 table #628: 0 bytes OK
|
||||||
2026/05/23-19:09:56.339034 7fa2327fc6c0 Delete type=0 #618
|
2026/05/25-12:29:17.716928 7f87e2ffd6c0 Delete type=0 #626
|
||||||
2026/05/23-19:09:56.339189 7fa2327fc6c0 Manual compaction at level-0 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end)
|
2026/05/25-12:29:17.740121 7f87e2ffd6c0 Manual compaction at level-0 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end)
|
||||||
2026/05/23-19:09:56.339203 7fa2327fc6c0 Manual compaction at level-1 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end)
|
2026/05/25-12:29:17.758707 7f87e2ffd6c0 Manual compaction at level-1 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
2026/05/20-23:09:33.082007 7f179dfec6c0 Recovering log #611
|
2026/05/24-09:22:42.993966 7fdfa8dfe6c0 Recovering log #619
|
||||||
2026/05/20-23:09:33.140910 7f179dfec6c0 Delete type=3 #609
|
2026/05/24-09:22:43.035467 7fdfa8dfe6c0 Delete type=3 #617
|
||||||
2026/05/20-23:09:33.140962 7f179dfec6c0 Delete type=0 #611
|
2026/05/24-09:22:43.035518 7fdfa8dfe6c0 Delete type=0 #619
|
||||||
2026/05/20-23:16:53.510592 7f179d7eb6c0 Level-0 table #616: started
|
2026/05/24-09:42:00.319082 7fdf5affd6c0 Level-0 table #624: started
|
||||||
2026/05/20-23:16:53.510664 7f179d7eb6c0 Level-0 table #616: 0 bytes OK
|
2026/05/24-09:42:00.319109 7fdf5affd6c0 Level-0 table #624: 0 bytes OK
|
||||||
2026/05/20-23:16:53.517661 7f179d7eb6c0 Delete type=0 #614
|
2026/05/24-09:42:00.326261 7fdf5affd6c0 Delete type=0 #622
|
||||||
2026/05/20-23:16:53.536973 7f179d7eb6c0 Manual compaction at level-0 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end)
|
2026/05/24-09:42:00.334196 7fdf5affd6c0 Manual compaction at level-0 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end)
|
||||||
2026/05/20-23:16:53.537037 7f179d7eb6c0 Manual compaction at level-1 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end)
|
2026/05/24-09:42:00.334355 7fdf5affd6c0 Manual compaction at level-1 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
Binary file not shown.
@@ -1 +1 @@
|
|||||||
MANIFEST-000312
|
MANIFEST-000320
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
2026/05/23-18:45:10.252202 7fa2337fe6c0 Recovering log #310
|
2026/05/25-11:43:40.107633 7f8830dfe6c0 Recovering log #318
|
||||||
2026/05/23-18:45:10.261300 7fa2337fe6c0 Delete type=3 #308
|
2026/05/25-11:43:40.118039 7f8830dfe6c0 Delete type=3 #316
|
||||||
2026/05/23-18:45:10.261343 7fa2337fe6c0 Delete type=0 #310
|
2026/05/25-11:43:40.118104 7f8830dfe6c0 Delete type=0 #318
|
||||||
2026/05/23-19:09:56.467263 7fa2327fc6c0 Level-0 table #315: started
|
2026/05/25-12:29:17.777484 7f87e2ffd6c0 Level-0 table #323: started
|
||||||
2026/05/23-19:09:56.467308 7fa2327fc6c0 Level-0 table #315: 0 bytes OK
|
2026/05/25-12:29:17.777511 7f87e2ffd6c0 Level-0 table #323: 0 bytes OK
|
||||||
2026/05/23-19:09:56.509084 7fa2327fc6c0 Delete type=0 #313
|
2026/05/25-12:29:17.784021 7f87e2ffd6c0 Delete type=0 #321
|
||||||
2026/05/23-19:09:56.608544 7fa2327fc6c0 Manual compaction at level-0 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end)
|
2026/05/25-12:29:17.784188 7f87e2ffd6c0 Manual compaction at level-0 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end)
|
||||||
2026/05/23-19:09:56.665018 7fa2327fc6c0 Manual compaction at level-1 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end)
|
2026/05/25-12:29:17.803354 7f87e2ffd6c0 Manual compaction at level-1 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
2026/05/20-23:09:33.368596 7f179e7ed6c0 Recovering log #306
|
2026/05/24-09:22:43.194643 7fdfa8dfe6c0 Recovering log #314
|
||||||
2026/05/20-23:09:33.422152 7f179e7ed6c0 Delete type=3 #304
|
2026/05/24-09:22:43.231409 7fdfa8dfe6c0 Delete type=3 #312
|
||||||
2026/05/20-23:09:33.422210 7f179e7ed6c0 Delete type=0 #306
|
2026/05/24-09:22:43.231470 7fdfa8dfe6c0 Delete type=0 #314
|
||||||
2026/05/20-23:16:53.560903 7f179d7eb6c0 Level-0 table #311: started
|
2026/05/24-09:42:00.341818 7fdf5affd6c0 Level-0 table #319: started
|
||||||
2026/05/20-23:16:53.560969 7f179d7eb6c0 Level-0 table #311: 0 bytes OK
|
2026/05/24-09:42:00.341853 7fdf5affd6c0 Level-0 table #319: 0 bytes OK
|
||||||
2026/05/20-23:16:53.568455 7f179d7eb6c0 Delete type=0 #309
|
2026/05/24-09:42:00.348538 7fdf5affd6c0 Delete type=0 #317
|
||||||
2026/05/20-23:16:53.568616 7f179d7eb6c0 Manual compaction at level-0 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end)
|
2026/05/24-09:42:00.367125 7fdf5affd6c0 Manual compaction at level-0 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end)
|
||||||
2026/05/20-23:16:53.578679 7f179d7eb6c0 Manual compaction at level-1 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end)
|
2026/05/24-09:42:00.367348 7fdf5affd6c0 Manual compaction at level-1 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
BIN
Binary file not shown.
@@ -1 +1 @@
|
|||||||
MANIFEST-000611
|
MANIFEST-000619
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
2026/05/23-18:45:10.238924 7fa233fff6c0 Recovering log #609
|
2026/05/25-11:43:40.093877 7f87e3fff6c0 Recovering log #617
|
||||||
2026/05/23-18:45:10.250309 7fa233fff6c0 Delete type=3 #607
|
2026/05/25-11:43:40.103670 7f87e3fff6c0 Delete type=3 #615
|
||||||
2026/05/23-18:45:10.250371 7fa233fff6c0 Delete type=0 #609
|
2026/05/25-11:43:40.103720 7f87e3fff6c0 Delete type=0 #617
|
||||||
2026/05/23-19:09:56.224507 7fa2327fc6c0 Level-0 table #614: started
|
2026/05/25-12:29:17.764689 7f87e2ffd6c0 Level-0 table #622: started
|
||||||
2026/05/23-19:09:56.224539 7fa2327fc6c0 Level-0 table #614: 0 bytes OK
|
2026/05/25-12:29:17.764705 7f87e2ffd6c0 Level-0 table #622: 0 bytes OK
|
||||||
2026/05/23-19:09:56.261516 7fa2327fc6c0 Delete type=0 #612
|
2026/05/25-12:29:17.770486 7f87e2ffd6c0 Delete type=0 #620
|
||||||
2026/05/23-19:09:56.339172 7fa2327fc6c0 Manual compaction at level-0 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end)
|
2026/05/25-12:29:17.784104 7f87e2ffd6c0 Manual compaction at level-0 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end)
|
||||||
2026/05/23-19:09:56.339199 7fa2327fc6c0 Manual compaction at level-1 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end)
|
2026/05/25-12:29:17.784130 7f87e2ffd6c0 Manual compaction at level-1 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
2026/05/20-23:09:33.302872 7f179efee6c0 Recovering log #605
|
2026/05/24-09:22:43.141936 7fdf5bfff6c0 Recovering log #613
|
||||||
2026/05/20-23:09:33.362229 7f179efee6c0 Delete type=3 #603
|
2026/05/24-09:22:43.189387 7fdf5bfff6c0 Delete type=3 #611
|
||||||
2026/05/20-23:09:33.362319 7f179efee6c0 Delete type=0 #605
|
2026/05/24-09:22:43.189438 7fdf5bfff6c0 Delete type=0 #613
|
||||||
2026/05/20-23:16:53.530498 7f179d7eb6c0 Level-0 table #610: started
|
2026/05/24-09:42:00.311436 7fdf5affd6c0 Level-0 table #618: started
|
||||||
2026/05/20-23:16:53.530547 7f179d7eb6c0 Level-0 table #610: 0 bytes OK
|
2026/05/24-09:42:00.311459 7fdf5affd6c0 Level-0 table #618: 0 bytes OK
|
||||||
2026/05/20-23:16:53.536854 7f179d7eb6c0 Delete type=0 #608
|
2026/05/24-09:42:00.318964 7fdf5affd6c0 Delete type=0 #616
|
||||||
2026/05/20-23:16:53.537015 7f179d7eb6c0 Manual compaction at level-0 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end)
|
2026/05/24-09:42:00.334187 7fdf5affd6c0 Manual compaction at level-0 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end)
|
||||||
2026/05/20-23:16:53.537048 7f179d7eb6c0 Manual compaction at level-1 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end)
|
2026/05/24-09:42:00.334334 7fdf5affd6c0 Manual compaction at level-1 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
BIN
Binary file not shown.
@@ -387,11 +387,11 @@
|
|||||||
min-width: 6rem;
|
min-width: 6rem;
|
||||||
max-width: 6rem;
|
max-width: 6rem;
|
||||||
}
|
}
|
||||||
min-width: 10rem;
|
min-width: 11rem;
|
||||||
max-width: 10rem;
|
max-width: 11rem;
|
||||||
.input {
|
.input {
|
||||||
min-width: 2.5rem;
|
min-width: 3.5rem;
|
||||||
max-width: 2.5rem;
|
max-width: 3.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.granted {
|
.granted {
|
||||||
|
|||||||
+27
-29
@@ -55,35 +55,33 @@
|
|||||||
<div class="dialog-save">Add Granted Attack Dice
|
<div class="dialog-save">Add Granted Attack Dice
|
||||||
<input type="checkbox" data-action="selectGranted" name="granted" />
|
<input type="checkbox" data-action="selectGranted" name="granted" />
|
||||||
</div>
|
</div>
|
||||||
{{#if rollTarget.weapon}}
|
{{#if isRangedAttack}}
|
||||||
{{#if (eq rollTarget.weapon.system.weaponType "melee")}}{{else}}
|
<div class="dialog-save">Point Blank Range Attack
|
||||||
<div class="dialog-save">Point Blank Range Attack
|
<input
|
||||||
<input
|
type="checkbox"
|
||||||
type="checkbox"
|
data-action="selectPointBlank"
|
||||||
data-action="selectPointBlank"
|
name="pointBlankV"
|
||||||
name="pointBlankV"
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
<div class="dialog-save">Beyond Skill Range Attack
|
||||||
<div class="dialog-save">Beyond Skill Range Attack
|
<input
|
||||||
<input
|
type="checkbox"
|
||||||
type="checkbox"
|
data-action="selectBeyondSkill"
|
||||||
data-action="selectBeyondSkill"
|
name="beyondSkillV"
|
||||||
name="beyondSkillV"
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
<div class="dialog-save">Let it Fly (Pure D20E)
|
||||||
<div class="dialog-save">Let it Fly (Pure D20E)
|
<input
|
||||||
<input
|
type="checkbox"
|
||||||
type="checkbox"
|
data-action="selectLetItFly"
|
||||||
data-action="selectLetItFly"
|
name="letItFlyV"
|
||||||
name="letItFlyV"
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
<div class="dialog-save">Aiming
|
||||||
<div class="dialog-save">Aiming
|
<select name="attackerAim" data-tooltip-direction="UP">
|
||||||
<select name="attackerAim" data-tooltip-direction="UP">
|
{{selectOptions attackerAimChoices selected=attackerAim}}
|
||||||
{{selectOptions attackerAimChoices selected=attackerAim}}
|
</select>
|
||||||
</select>
|
</div>
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|||||||
Reference in New Issue
Block a user