From c37d92af25021bbaea3c55db5a39591166f43a13 Mon Sep 17 00:00:00 2001 From: LeRatierBretonnier Date: Sun, 12 Apr 2026 01:07:58 +0200 Subject: [PATCH] Various initiative fixes + shield management messages --- lethal-fantasy.mjs | 309 ++++++++++++++++-- module/applications/combat.mjs | 26 +- module/documents/actor.mjs | 4 +- module/documents/roll.mjs | 122 ++++--- module/models/character.mjs | 5 +- module/models/monster.mjs | 9 +- module/utils.mjs | 136 +++++++- .../lf-equipment/{000545.log => 000553.log} | 0 packs-system/lf-equipment/CURRENT | 2 +- packs-system/lf-equipment/LOG | 16 +- packs-system/lf-equipment/LOG.old | 16 +- .../{MANIFEST-000543 => MANIFEST-000551} | Bin 178 -> 178 bytes .../lf-gifts/{000542.log => 000550.log} | 0 packs-system/lf-gifts/CURRENT | 2 +- packs-system/lf-gifts/LOG | 16 +- packs-system/lf-gifts/LOG.old | 16 +- .../{MANIFEST-000540 => MANIFEST-000548} | Bin 247 -> 247 bytes .../lf-skills/{000547.log => 000555.log} | 0 packs-system/lf-skills/CURRENT | 2 +- packs-system/lf-skills/LOG | 16 +- packs-system/lf-skills/LOG.old | 16 +- .../{MANIFEST-000545 => MANIFEST-000553} | Bin 178 -> 178 bytes .../{000242.log => 000250.log} | 0 packs-system/lf-spells-miracles/CURRENT | 2 +- packs-system/lf-spells-miracles/LOG | 16 +- packs-system/lf-spells-miracles/LOG.old | 16 +- .../{MANIFEST-000240 => MANIFEST-000248} | Bin 177 -> 177 bytes .../{000541.log => 000549.log} | 0 packs-system/lf-vulnerabilities/CURRENT | 2 +- packs-system/lf-vulnerabilities/LOG | 16 +- packs-system/lf-vulnerabilities/LOG.old | 16 +- .../{MANIFEST-000539 => MANIFEST-000547} | Bin 176 -> 176 bytes templates/chat-message.hbs | 4 +- templates/combat-tracker-footer-v2.hbs | 2 +- templates/combat-tracker-header-v2.hbs | 2 +- 35 files changed, 589 insertions(+), 200 deletions(-) rename packs-system/lf-equipment/{000545.log => 000553.log} (100%) rename packs-system/lf-equipment/{MANIFEST-000543 => MANIFEST-000551} (71%) rename packs-system/lf-gifts/{000542.log => 000550.log} (100%) rename packs-system/lf-gifts/{MANIFEST-000540 => MANIFEST-000548} (77%) rename packs-system/lf-skills/{000547.log => 000555.log} (100%) rename packs-system/lf-skills/{MANIFEST-000545 => MANIFEST-000553} (71%) rename packs-system/lf-spells-miracles/{000242.log => 000250.log} (100%) rename packs-system/lf-spells-miracles/{MANIFEST-000240 => MANIFEST-000248} (72%) rename packs-system/lf-vulnerabilities/{000541.log => 000549.log} (100%) rename packs-system/lf-vulnerabilities/{MANIFEST-000539 => MANIFEST-000547} (72%) diff --git a/lethal-fantasy.mjs b/lethal-fantasy.mjs index b265a1e..84a3cd8 100644 --- a/lethal-fantasy.mjs +++ b/lethal-fantasy.mjs @@ -250,6 +250,18 @@ Hooks.on(hookName, (message, html, data) => { const attackWeaponId = message.rolls[0]?.rollTarget?.weapon?.id || message.rolls[0]?.rollTarget?.weapon?._id const attackRollType = message.rolls[0]?.type const attackRollKey = message.rolls[0]?.rollTarget?.rollKey + const attackD30result = message.rolls[0]?.options?.D30result || null + const attackD30message = message.rolls[0]?.options?.D30message || null + const attackRerollContext = { + rollType: message.rolls[0]?.options?.rollType, + rollTarget: foundry.utils.duplicate(message.rolls[0]?.options?.rollTarget ?? {}), + actorId: message.rolls[0]?.options?.actorId, + actorName: message.rolls[0]?.options?.actorName, + actorImage: message.rolls[0]?.options?.actorImage, + defenderId: combatant.actor?.id || null, + defenderTokenId: tokenId || combatant.token?.id || null, + rollContext: foundry.utils.duplicate(message.rolls[0]?.options?.rollData ?? {}) + } // Préparer le message de demande de défense const defenseMsg = { @@ -262,6 +274,9 @@ Hooks.on(hookName, (message, html, data) => { attackWeaponId: attackWeaponId, attackRollType: attackRollType, attackRollKey: attackRollKey, + attackD30result: attackD30result, + attackD30message: attackD30message, + attackRerollContext: attackRerollContext, combatantId: combatantId, tokenId: tokenId } @@ -318,6 +333,7 @@ Hooks.on(hookName, (message, html, data) => { let attackerId = button.data("attacker-id") const defenderId = button.data("defender-id") const defenderTokenId = button.data("defender-token-id") || null + const extraShieldDr = Number(button.data("extra-shield-dr") || 0) const damageType = button.data("damage-type") const damageFormula = button.data("damage-formula") const damageModifier = button.data("damage-modifier") @@ -332,7 +348,7 @@ Hooks.on(hookName, (message, html, data) => { // Pour les boutons de résultat de combat (monster damage) if (damageType === "monster" && attackKey) { - await actor.system.prepareMonsterRoll("monster-damage", attackKey, undefined, undefined, undefined, defenderId, defenderTokenId) + await actor.system.prepareMonsterRoll("monster-damage", attackKey, undefined, undefined, undefined, defenderId, defenderTokenId, extraShieldDr) return } @@ -351,7 +367,7 @@ Hooks.on(hookName, (message, html, data) => { // Lancer les dégâts avec la bonne méthode const rollType = damageType === "small" ? "weapon-damage-small" : "weapon-damage-medium" - await actor.prepareRoll(rollType, weaponId, undefined, defenderId, defenderTokenId) + await actor.prepareRoll(rollType, weaponId, undefined, defenderId, defenderTokenId, extraShieldDr) }) // Masquer les boutons de dommages dans les messages de résultat de combat si l'utilisateur n'est pas l'attaquant @@ -410,8 +426,21 @@ Hooks.on("createChatMessage", async (message) => { return } - const { attackerId, attackRoll, attackerName, defenderName, attackWeaponId, attackRollType, attackRollKey, defenderId, defenderTokenId } = attackData + const { + attackerId, + attackRoll, + attackerName, + defenderName, + attackWeaponId, + attackRollType, + attackRollKey, + attackD30message, + attackRerollContext, + defenderId, + defenderTokenId + } = attackData let defenseRoll = message.rolls[0]?.options?.rollTotal || message.rolls[0]?.total || 0 + const defenseD30message = message.rolls[0]?.options?.D30message || null console.log("Processing defense:", { attackRoll, defenseRoll, attackerId, defenderId }) @@ -423,26 +452,165 @@ Hooks.on("createChatMessage", async (message) => { // Récupérer le défenseur et l'attaquant const defender = game.actors.get(defenderId) const attacker = game.actors.get(attackerId) + const defenseRerollContext = { + rollType: message.rolls[0]?.options?.rollType, + rollTarget: foundry.utils.duplicate(message.rolls[0]?.options?.rollTarget ?? {}), + actorId: message.rolls[0]?.options?.actorId, + actorName: message.rolls[0]?.options?.actorName, + actorImage: message.rolls[0]?.options?.actorImage, + defenderId, + defenderTokenId, + rollContext: foundry.utils.duplicate(message.rolls[0]?.options?.rollData ?? {}) + } + + const isPrimaryController = actor => { + if (!actor) return false + const activePlayerOwners = game.users.filter(u => u.active && !u.isGM && actor.testUserPermission(u, "OWNER")) + if (activePlayerOwners.length > 0) { + return activePlayerOwners[0].id === game.user.id + } + return game.user.isGM + } + + const createReactionMessage = async (actorDocument, content) => { + await ChatMessage.create({ + content, + speaker: ChatMessage.getSpeaker({ actor: actorDocument }) + }) + } // Si le défenseur est un personnage qui perd, proposer Grit/Luck (seulement s'il a des points) // Seulement si l'utilisateur actuel est le propriétaire du défenseur let defenderHandledBonus = false - if (defender?.type === "character" && defenseRoll < attackRoll && !game.user.isGM && defender.isOwner) { - const hasGritOrLuck = (defender.system.grit.current > 0) || (defender.system.luck.current > 0) + let shieldReaction = null + if (defender && defenseRoll < attackRoll && isPrimaryController(defender)) { + const shieldData = LethalFantasyUtils.getShieldReactionData(defender) + let canRerollDefense = LethalFantasyUtils.hasD30Reroll(defenseD30message) + let canShieldReact = !!shieldData - if (hasGritOrLuck) { - const bonusRoll = await LethalFantasyUtils.offerGritLuckBonus( - defender, - attackRoll, - defenseRoll, - attackerName, - defenderName - ) - if (bonusRoll > 0) { + 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" + }) + } + + 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: ` +
+
+

${attackerName} rolled ${attackRoll}

+

${defenderName} currently has ${defenseRoll}

+ ${defenseD30message ? `

D30 special: ${defenseD30message}

` : ""} +
+

Choose how to improve the defense before resolving the hit.

+
+ `, + buttons, + rejectClose: false + }) + + if (!choice || choice === "continue") break + + defenderHandledBonus = true + + if (choice === "grit") { + const bonusRoll = await LethalFantasyUtils.rollBonusDie("1d6", defender, (total) => `

${defenderName} spends 1 Grit and rolls ${total} for defense.

`) defenseRoll += bonusRoll + await defender.update({ "system.grit.current": currentGrit - 1 }) + continue + } + + if (choice === "luck") { + const bonusRoll = await LethalFantasyUtils.rollBonusDie("1d6", defender, (total) => `

${defenderName} spends 1 Luck and rolls ${total} for defense.

`) + 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) => `

${defenderName} adds ${formula.toUpperCase()} and rolls ${total} for defense.

`) + 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, `

${defenderName} uses Mulligan and re-rolls defense: ${oldDefenseRoll}${defenseRoll}.

`) + continue + } + + if (choice === "shieldReact" && canShieldReact) { + const shieldBonus = await LethalFantasyUtils.rollBonusDie(shieldData.formula, defender) + defenseRoll += shieldBonus + shieldReaction = { + damageReduction: shieldData.damageReduction, + label: shieldData.label, + bonus: shieldBonus + } + canShieldReact = false + await createReactionMessage( + defender, + `

${defenderName} rolls ${shieldData.label} and adds ${shieldBonus} to defense.${defenseRoll < attackRoll ? ` The hit still lands, but shield DR ${shieldData.damageReduction} will reduce the damage.` : ""}

` + ) } } - defenderHandledBonus = true } let attackRollFinal = attackRoll @@ -450,31 +618,98 @@ Hooks.on("createChatMessage", async (message) => { // Si l'attaquant est un personnage qui perd et a du Grit // Seulement si l'utilisateur actuel est le propriétaire de l'attaquant (pas le MJ) - if (attacker?.type === "character" && attackRollFinal <= defenseRoll && attacker.system.grit.current > 0) { - // Vérifier si l'utilisateur est un propriétaire non-GM de l'attaquant - const isAttackerOwner = !game.user.isGM && attacker.testUserPermission(game.user, "OWNER") + if (!defenderHandledBonus && attacker && attackRollFinal <= defenseRoll && isPrimaryController(attacker)) { + let canRerollAttack = LethalFantasyUtils.hasD30Reroll(attackD30message) - if (isAttackerOwner) { - console.log("Offering Grit to attacker") + while (attackRollFinal <= defenseRoll) { + const currentGrit = Number(attacker.system?.grit?.current) || 0 + const buttons = [] - const attackBonus = await LethalFantasyUtils.offerAttackerGritBonus( - attacker, - attackRollFinal, - defenseRoll, - attackerName, - defenderName - ) + 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", + 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: ` +
+
+

${attackerName} currently has ${attackRollFinal}

+

${defenderName} rolled ${defenseRoll}

+ ${attackD30message ? `

D30 special: ${attackD30message}

` : ""} +
+

Choose how to improve the attack before resolving the combat result.

+
+ `, + buttons, + rejectClose: false + }) + + if (!choice || choice === "continue") break - attackRollFinal += attackBonus attackerHandledBonus = true - } else { - console.log("Not attacker owner or is GM, skipping Grit offer") + + if (choice === "grit") { + const attackBonus = await LethalFantasyUtils.rollBonusDie("1d6", attacker, (total) => `

${attackerName} spends 1 Grit and rolls ${total} for attack.

`) + 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) => `

${attackerName} adds ${formula.toUpperCase()} and rolls ${total} for attack.

`) + 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, `

${attackerName} uses Mulligan and re-rolls attack: ${oldAttackRoll}${attackRollFinal}.

`) + } } } + const shieldDamageReduction = shieldReaction && attackRollFinal > defenseRoll ? shieldReaction.damageReduction : 0 + const outcome = shieldDamageReduction > 0 ? "shielded-hit" : (attackRollFinal > defenseRoll ? "hit" : "miss") + // Créer le message de comparaison - uniquement par le client qui a géré le dernier bonus // Priorité: attaquant si il a géré le bonus, sinon défenseur si il a géré le bonus, sinon défenseur - const shouldCreateMessage = attackerHandledBonus || (!attackerHandledBonus && defenderHandledBonus) || (!attackerHandledBonus && !defenderHandledBonus && defender.isOwner) + const shouldCreateMessage = attackerHandledBonus || (!attackerHandledBonus && defenderHandledBonus) || (!attackerHandledBonus && !defenderHandledBonus && isPrimaryController(defender)) if (shouldCreateMessage) { console.log("Creating comparison message", { attackerHandledBonus, defenderHandledBonus, isDefenderOwner: defender.isOwner }) @@ -489,7 +724,9 @@ Hooks.on("createChatMessage", async (message) => { defenderName, defenderId, defenderTokenId, - defenseRoll + defenseRoll, + outcome, + shieldDamageReduction }) } else { console.log("Skipping message creation", { attackerHandledBonus, defenderHandledBonus }) @@ -552,7 +789,9 @@ Hooks.on("createChatMessage", async (message) => { // Calculer les DR const armorDR = defender.computeDamageReduction() || 0 - const finalDamage = Math.max(0, damageTotal - armorDR) + const extraShieldDr = Number(message.rolls[0]?.options?.extraShieldDr) || 0 + const totalDR = armorDR + extraShieldDr + const finalDamage = Math.max(0, damageTotal - totalDR) // Prefer the token ID stored in roll options (set at attack time when the exact token is known). // For unlinked tokens (default for monsters), this ensures we target the right instance even @@ -584,7 +823,7 @@ Hooks.on("createChatMessage", async (message) => { { targetName: defender.name, damage: finalDamage, - drText: armorDR > 0 ? `Armor DR: ${armorDR}` : "", + drText: totalDR > 0 ? `Armor DR: ${armorDR}${extraShieldDr > 0 ? ` + Shield DR: ${extraShieldDr}` : ""}` : "", weaponName: weaponName, attackerName: attackerName, rawDamage: damageTotal @@ -624,4 +863,4 @@ async function registerWorldCount(registerKey) { console.log("No usage log ") } } -} \ No newline at end of file +} diff --git a/module/applications/combat.mjs b/module/applications/combat.mjs index 06285a6..cb7ec5e 100644 --- a/module/applications/combat.mjs +++ b/module/applications/combat.mjs @@ -100,6 +100,14 @@ export class LethalFantasyCombat extends Combat { return this.turns = turns; } + async startCombat() { + this._playCombatSound("startEncounter") + const updateData = { round: 0, turn: 0 } + Hooks.callAll("combatStart", this, updateData) + await this.update(updateData) + return this + } + async rollInitiative(ids, options) { console.log("%%%%%%%%% Roll Initiative", ids, options); @@ -110,13 +118,12 @@ export class LethalFantasyCombat extends Combat { let updates = []; for (let cId of ids) { const c = this.combatants.get(cId); - let user = game.users.find(u => u.active && u.character && u.character.id === c.actor.id); - if (user?.hasPlayerOwner) { + const playerOwner = game.users.find(u => u.active && !u.isGM && u.character?.id === c.actor.id); + if (game.user.isGM && playerOwner) { console.log("Rolling initiative for", c.actor.name); - game.socket.emit(`system.${SYSTEM.id}`, { type: "rollInitiative", actorId: c.actor.id, combatId: this.id, combatantId: c.id }); + game.socket.emit(`system.${SYSTEM.id}`, { type: "rollInitiative", userId: playerOwner.id, actorId: c.actor.id, combatId: this.id, combatantId: c.id }); } else { - user = game.users.find(u => u.active && u.isGM); - c.actor.system.rollInitiative(this.id, c.id); + await c.actor.system.rollInitiative(this.id, c.id); } } @@ -196,12 +203,11 @@ export class LethalFantasyCombat extends Combat { for (let c of this.combatants) { if (nextRound >= c.initiative) { - let user = game.users.find(u => u.active && u.character && u.character.id === c.actor.id); - if (user?.hasPlayerOwner) { - game.socket.emit(`system.${SYSTEM.id}`, { type: "rollProgressionDice", progressionCount: c.system.progressionCount + 1, actorId: c.actor.id, combatId: this.id, combatantId: c.id }); + const playerOwner = game.users.find(u => u.active && !u.isGM && u.character?.id === c.actor.id); + if (game.user.isGM && playerOwner) { + game.socket.emit(`system.${SYSTEM.id}`, { type: "rollProgressionDice", userId: playerOwner.id, progressionCount: c.system.progressionCount + 1, actorId: c.actor.id, combatId: this.id, combatantId: c.id }); } else { - user = game.users.find(u => u.active && u.isGM); - c.actor.system.rollProgressionDice(this.id, c.id); + await c.actor.system.rollProgressionDice(this.id, c.id); } } } diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index 977aec8..a62fd8f 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -153,7 +153,7 @@ export default class LethalFantasyActor extends Actor { } /* *************************************************/ - async prepareRoll(rollType, rollKey, rollDice, defenderId, defenderTokenId) { + async prepareRoll(rollType, rollKey, rollDice, defenderId, defenderTokenId, extraShieldDr = 0) { console.log("Preparing roll", rollType, rollKey, rollDice, defenderId) let rollTarget switch (rollType) { @@ -268,7 +268,7 @@ export default class LethalFantasyActor extends Actor { rollTarget.magicUser = this.system.biodata.magicUser rollTarget.actorModifiers = foundry.utils.duplicate(this.system.modifiers) rollTarget.actorLevel = this.system.biodata.level - await this.system.roll(rollType, rollTarget, defenderId, defenderTokenId) + await this.system.roll(rollType, rollTarget, defenderId, defenderTokenId, extraShieldDr) } } diff --git a/module/documents/roll.mjs b/module/documents/roll.mjs index f708ac7..d2b9e59 100644 --- a/module/documents/roll.mjs +++ b/module/documents/roll.mjs @@ -346,58 +346,73 @@ export default class LethalFantasyRoll extends Roll { favor: "none", targetName } - const content = await foundry.applications.handlebars.renderTemplate("systems/fvtt-lethal-fantasy/templates/roll-dialog.hbs", dialogContext) + let rollContext + if (options.rollContext) { + rollContext = foundry.utils.duplicate(options.rollContext) + hasGrantedDice = !!rollContext.hasGrantedDice + pointBlank = !!rollContext.pointBlank + beyondSkill = !!rollContext.beyondSkill + letItFly = !!rollContext.letItFly + saveSpell = !!rollContext.saveSpell + rollContext.visibility ||= rollContext.rollMode || game.settings.get("core", "rollMode") + rollContext.modifier ||= modifier + rollContext.favor ||= "none" + rollContext.changeDice ||= `${dice}` + rollContext.attackerAim ||= "0" + } else { + const content = await foundry.applications.handlebars.renderTemplate("systems/fvtt-lethal-fantasy/templates/roll-dialog.hbs", dialogContext) - let position = game.user.getFlag(SYSTEM.id, "roll-dialog-pos") || { top: -1, left: -1 } - const label = game.i18n.localize("LETHALFANTASY.Roll.roll") - const rollContext = await foundry.applications.api.DialogV2.wait({ - window: { title: "Roll dialog" }, - classes: ["lethalfantasy"], - content, - position, - buttons: [ - { - label: label, - callback: (event, button, dialog) => { - console.log("Roll context", event, button, dialog) - let position = dialog.position - game.user.setFlag(SYSTEM.id, "roll-dialog-pos", foundry.utils.duplicate(position)) - const output = Array.from(button.form.elements).reduce((obj, input) => { - if (input.name) obj[input.name] = input.value - return obj - }, {}) - return output + let position = game.user.getFlag(SYSTEM.id, "roll-dialog-pos") || { top: -1, left: -1 } + const label = game.i18n.localize("LETHALFANTASY.Roll.roll") + rollContext = await foundry.applications.api.DialogV2.wait({ + window: { title: "Roll dialog" }, + classes: ["lethalfantasy"], + content, + position, + buttons: [ + { + label: label, + callback: (event, button, dialog) => { + console.log("Roll context", event, button, dialog) + let position = dialog.position + game.user.setFlag(SYSTEM.id, "roll-dialog-pos", foundry.utils.duplicate(position)) + const output = Array.from(button.form.elements).reduce((obj, input) => { + if (input.name) obj[input.name] = input.value + return obj + }, {}) + return output + }, }, - }, - ], - actions: { - "selectGranted": (event, button, dialog) => { - hasGrantedDice = event.target.checked - }, - "selectBeyondSkill": (event, button, dialog) => { - beyondSkill = button.checked - }, - "selectPointBlank": (event, button, dialog) => { - pointBlank = button.checked - }, - "selectLetItFly": (event, button, dialog) => { - letItFly = button.checked - }, - "saveSpellCheck": (event, button, dialog) => { - saveSpell = button.checked - }, - "gotoToken": (event, button, dialog) => { - let tokenId = $(button).data("tokenId") - let token = canvas.tokens?.get(tokenId) - if (token) { - canvas.animatePan({ x: token.x, y: token.y, duration: 200 }) - canvas.tokens.releaseAll(); - token.control({ releaseOthers: true }); + ], + actions: { + "selectGranted": (event, button, dialog) => { + hasGrantedDice = event.target.checked + }, + "selectBeyondSkill": (event, button, dialog) => { + beyondSkill = button.checked + }, + "selectPointBlank": (event, button, dialog) => { + pointBlank = button.checked + }, + "selectLetItFly": (event, button, dialog) => { + letItFly = button.checked + }, + "saveSpellCheck": (event, button, dialog) => { + saveSpell = button.checked + }, + "gotoToken": (event, button, dialog) => { + let tokenId = $(button).data("tokenId") + let token = canvas.tokens?.get(tokenId) + if (token) { + canvas.animatePan({ x: token.x, y: token.y, duration: 200 }) + canvas.tokens.releaseAll() + token.control({ releaseOthers: true }) + } } - } - }, - rejectClose: false // Click on Close button will not launch an error - }) + }, + rejectClose: false // Click on Close button will not launch an error + }) + } // If the user cancels the dialog, exit if (rollContext === null) return @@ -547,6 +562,10 @@ export default class LethalFantasyRoll extends Roll { rollFavor = null } + if (options.forceNoD30) { + hasD30 = false + } + if (hasD30) { let rollD30 = await new Roll("1D30").evaluate() if (game?.dice3d) { @@ -621,6 +640,7 @@ export default class LethalFantasyRoll extends Roll { rollBase.options.rollData = foundry.utils.duplicate(rollData) rollBase.options.defenderId = options.defenderId rollBase.options.defenderTokenId = options.defenderTokenId + rollBase.options.extraShieldDr = options.extraShieldDr || 0 /** * A hook event that fires after the roll has been made. @@ -1296,7 +1316,7 @@ export default class LethalFantasyRoll extends Roll { * @returns {Promise} - A promise that resolves when the message is created. */ async toMessage(messageData = {}, { rollMode, create = true } = {}) { - super.toMessage( + return await super.toMessage( { isSave: this.isSave, isChallenge: this.isChallenge, @@ -1313,7 +1333,7 @@ export default class LethalFantasyRoll extends Roll { rollData: this.rollData, ...messageData, }, - { rollMode: rollMode }, + { rollMode, create }, ) } diff --git a/module/models/character.mjs b/module/models/character.mjs index f8dde99..86ac573 100644 --- a/module/models/character.mjs +++ b/module/models/character.mjs @@ -274,7 +274,7 @@ export default class LethalFantasyCharacter extends foundry.abstract.TypeDataMod * @param {"="|"+"|"++"|"-"|"--"} rollAdvantage If there is an avantage (+), a disadvantage (-), a double advantage (++), a double disadvantage (--) or a normal roll (=). * @returns {Promise} - A promise that resolves to null if the roll is cancelled. */ - async roll(rollType, rollTarget, defenderId, defenderTokenId) { + async roll(rollType, rollTarget, defenderId, defenderTokenId, extraShieldDr = 0) { const hasTarget = false let roll = await LethalFantasyRoll.prompt({ rollType, @@ -285,7 +285,8 @@ export default class LethalFantasyCharacter extends foundry.abstract.TypeDataMod hasTarget, target: false, defenderId, - defenderTokenId + defenderTokenId, + extraShieldDr }) if (!roll) return null diff --git a/module/models/monster.mjs b/module/models/monster.mjs index df80535..a478874 100644 --- a/module/models/monster.mjs +++ b/module/models/monster.mjs @@ -141,7 +141,7 @@ export default class LethalFantasyMonster extends foundry.abstract.TypeDataModel * @param {"="|"+"|"++"|"-"|"--"} rollAdvantage If there is an avantage (+), a disadvantage (-), a double advantage (++), a double disadvantage (--) or a normal roll (=). * @returns {Promise} - A promise that resolves to null if the roll is cancelled. */ - async roll(rollType, rollTarget, defenderId = undefined, defenderTokenId = undefined) { + async roll(rollType, rollTarget, defenderId = undefined, defenderTokenId = undefined, extraShieldDr = 0) { const hasTarget = false let roll = await LethalFantasyRoll.prompt({ rollType, @@ -152,14 +152,15 @@ export default class LethalFantasyMonster extends foundry.abstract.TypeDataModel hasTarget, target: false, defenderId, - defenderTokenId + defenderTokenId, + extraShieldDr }) if (!roll) return null await roll.toMessage({}, { rollMode: roll.options.rollMode }) } - async prepareMonsterRoll(rollType, rollKey, rollDice = undefined, tokenId = undefined, damageModifier = undefined, defenderId = undefined, defenderTokenId = undefined) { + async prepareMonsterRoll(rollType, rollKey, rollDice = undefined, tokenId = undefined, damageModifier = undefined, defenderId = undefined, defenderTokenId = undefined, extraShieldDr = 0) { let rollTarget switch (rollType) { case "monster-attack": @@ -255,7 +256,7 @@ export default class LethalFantasyMonster extends foundry.abstract.TypeDataModel if (rollTarget) { rollTarget.tokenId = tokenId console.log(rollTarget) - await this.roll(rollType, rollTarget, defenderId, defenderTokenId) + await this.roll(rollType, rollTarget, defenderId, defenderTokenId, extraShieldDr) } } diff --git a/module/utils.mjs b/module/utils.mjs index 0b2f9ce..6c4a381 100644 --- a/module/utils.mjs +++ b/module/utils.mjs @@ -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 => ``).join("") + const content = ` +
+
+

${actorName} currently has ${currentRoll}

+

Opposing ${sideLabel} roll: ${opposingRoll}

+
+
+ + +
+
+ ` + + 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 = `
- -
@@ -559,7 +679,7 @@ export default class LethalFantasyUtils { } else if (data.attackRollType === "monster-attack") { damageButton = `
-
@@ -588,9 +708,11 @@ export default class LethalFantasyUtils {
- ${isAttackWin ? - ` ${data.attackerName} hits ${data.defenderName}!` : - ` ${data.defenderName} parries the attack!` + ${outcome === "shielded-hit" + ? ` ${data.attackerName} still hits ${data.defenderName}, but shield DR ${data.shieldDamageReduction || 0} reduces the damage.` + : isAttackWin + ? ` ${data.attackerName} hits ${data.defenderName}!` + : ` ${data.defenderName} parries the attack!` }
${damageButton} diff --git a/packs-system/lf-equipment/000545.log b/packs-system/lf-equipment/000553.log similarity index 100% rename from packs-system/lf-equipment/000545.log rename to packs-system/lf-equipment/000553.log diff --git a/packs-system/lf-equipment/CURRENT b/packs-system/lf-equipment/CURRENT index 19d4421..536f6fc 100644 --- a/packs-system/lf-equipment/CURRENT +++ b/packs-system/lf-equipment/CURRENT @@ -1 +1 @@ -MANIFEST-000543 +MANIFEST-000551 diff --git a/packs-system/lf-equipment/LOG b/packs-system/lf-equipment/LOG index 62767a3..41dda52 100644 --- a/packs-system/lf-equipment/LOG +++ b/packs-system/lf-equipment/LOG @@ -1,8 +1,8 @@ -2026/04/07-20:41:02.007080 7f6820dfe6c0 Recovering log #541 -2026/04/07-20:41:02.061052 7f6820dfe6c0 Delete type=3 #539 -2026/04/07-20:41:02.061103 7f6820dfe6c0 Delete type=0 #541 -2026/04/07-20:44:04.752043 7f656afef6c0 Level-0 table #546: started -2026/04/07-20:44:04.752063 7f656afef6c0 Level-0 table #546: 0 bytes OK -2026/04/07-20:44:04.788614 7f656afef6c0 Delete type=0 #544 -2026/04/07-20:44:04.864197 7f656afef6c0 Manual compaction at level-0 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end) -2026/04/07-20:44:04.864237 7f656afef6c0 Manual compaction at level-1 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end) +2026/04/12-00:56:41.816652 7f20ecbfc6c0 Recovering log #549 +2026/04/12-00:56:41.832396 7f20ecbfc6c0 Delete type=3 #547 +2026/04/12-00:56:41.832446 7f20ecbfc6c0 Delete type=0 #549 +2026/04/12-01:07:05.182307 7f1e4ffff6c0 Level-0 table #554: started +2026/04/12-01:07:05.182364 7f1e4ffff6c0 Level-0 table #554: 0 bytes OK +2026/04/12-01:07:05.188459 7f1e4ffff6c0 Delete type=0 #552 +2026/04/12-01:07:05.207103 7f1e4ffff6c0 Manual compaction at level-0 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end) +2026/04/12-01:07:05.207149 7f1e4ffff6c0 Manual compaction at level-1 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end) diff --git a/packs-system/lf-equipment/LOG.old b/packs-system/lf-equipment/LOG.old index 0a70b47..e7ebbf3 100644 --- a/packs-system/lf-equipment/LOG.old +++ b/packs-system/lf-equipment/LOG.old @@ -1,8 +1,8 @@ -2026/02/06-21:03:10.117032 7f71b6ffd6c0 Recovering log #537 -2026/02/06-21:03:10.127068 7f71b6ffd6c0 Delete type=3 #535 -2026/02/06-21:03:10.127128 7f71b6ffd6c0 Delete type=0 #537 -2026/02/06-21:51:23.216143 7f71b67fc6c0 Level-0 table #542: started -2026/02/06-21:51:23.216190 7f71b67fc6c0 Level-0 table #542: 0 bytes OK -2026/02/06-21:51:23.251149 7f71b67fc6c0 Delete type=0 #540 -2026/02/06-21:51:23.327713 7f71b67fc6c0 Manual compaction at level-0 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end) -2026/02/06-21:51:23.327750 7f71b67fc6c0 Manual compaction at level-1 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end) +2026/04/11-21:32:22.595743 7f20ecbfc6c0 Recovering log #545 +2026/04/11-21:32:22.605266 7f20ecbfc6c0 Delete type=3 #543 +2026/04/11-21:32:22.605313 7f20ecbfc6c0 Delete type=0 #545 +2026/04/12-00:19:41.485614 7f1e4ffff6c0 Level-0 table #550: started +2026/04/12-00:19:41.485662 7f1e4ffff6c0 Level-0 table #550: 0 bytes OK +2026/04/12-00:19:41.492344 7f1e4ffff6c0 Delete type=0 #548 +2026/04/12-00:19:41.513106 7f1e4ffff6c0 Manual compaction at level-0 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end) +2026/04/12-00:19:41.513142 7f1e4ffff6c0 Manual compaction at level-1 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end) diff --git a/packs-system/lf-equipment/MANIFEST-000543 b/packs-system/lf-equipment/MANIFEST-000551 similarity index 71% rename from packs-system/lf-equipment/MANIFEST-000543 rename to packs-system/lf-equipment/MANIFEST-000551 index a109daa538fc2983723bab41fd900ba62ffa0fa2..e7bb98846463965f4e22648426fc0701c2866ba2 100644 GIT binary patch delta 43 tcmdnQxQTH>pGw-SDNDE+7@1bEa56BjWMP@dX@4a59Y|y)NMtog1OObe3w{6q delta 43 tcmdnQxQTH>pUTaWWtHOOO)t3_7@3x`a56A2V`16N*yo$V1rk{X5?KKf0RT0Z3y%N* delta 43 tcmey)_?>aWWtGfm)0^B3j7)P`I2oAdv9RoBY}EUa2ojkG5?KHe0RSnz3y%N* diff --git a/packs-system/lf-skills/000547.log b/packs-system/lf-skills/000555.log similarity index 100% rename from packs-system/lf-skills/000547.log rename to packs-system/lf-skills/000555.log diff --git a/packs-system/lf-skills/CURRENT b/packs-system/lf-skills/CURRENT index 7f02150..06420a4 100644 --- a/packs-system/lf-skills/CURRENT +++ b/packs-system/lf-skills/CURRENT @@ -1 +1 @@ -MANIFEST-000545 +MANIFEST-000553 diff --git a/packs-system/lf-skills/LOG b/packs-system/lf-skills/LOG index eef68f6..598b9fd 100644 --- a/packs-system/lf-skills/LOG +++ b/packs-system/lf-skills/LOG @@ -1,8 +1,8 @@ -2026/04/07-20:41:01.946718 7f67eb7fe6c0 Recovering log #543 -2026/04/07-20:41:01.999435 7f67eb7fe6c0 Delete type=3 #541 -2026/04/07-20:41:01.999504 7f67eb7fe6c0 Delete type=0 #543 -2026/04/07-20:44:04.712609 7f656afef6c0 Level-0 table #548: started -2026/04/07-20:44:04.712651 7f656afef6c0 Level-0 table #548: 0 bytes OK -2026/04/07-20:44:04.751939 7f656afef6c0 Delete type=0 #546 -2026/04/07-20:44:04.864184 7f656afef6c0 Manual compaction at level-0 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end) -2026/04/07-20:44:04.864223 7f656afef6c0 Manual compaction at level-1 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end) +2026/04/12-00:56:41.797453 7f20ed3fd6c0 Recovering log #551 +2026/04/12-00:56:41.813315 7f20ed3fd6c0 Delete type=3 #549 +2026/04/12-00:56:41.813371 7f20ed3fd6c0 Delete type=0 #551 +2026/04/12-01:07:05.200810 7f1e4ffff6c0 Level-0 table #556: started +2026/04/12-01:07:05.200837 7f1e4ffff6c0 Level-0 table #556: 0 bytes OK +2026/04/12-01:07:05.207008 7f1e4ffff6c0 Delete type=0 #554 +2026/04/12-01:07:05.207134 7f1e4ffff6c0 Manual compaction at level-0 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end) +2026/04/12-01:07:05.207157 7f1e4ffff6c0 Manual compaction at level-1 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end) diff --git a/packs-system/lf-skills/LOG.old b/packs-system/lf-skills/LOG.old index 08838ff..0db9311 100644 --- a/packs-system/lf-skills/LOG.old +++ b/packs-system/lf-skills/LOG.old @@ -1,8 +1,8 @@ -2026/02/06-21:03:10.101791 7f71b77fe6c0 Recovering log #539 -2026/02/06-21:03:10.111914 7f71b77fe6c0 Delete type=3 #537 -2026/02/06-21:03:10.111991 7f71b77fe6c0 Delete type=0 #539 -2026/02/06-21:51:23.251285 7f71b67fc6c0 Level-0 table #544: started -2026/02/06-21:51:23.251325 7f71b67fc6c0 Level-0 table #544: 0 bytes OK -2026/02/06-21:51:23.291180 7f71b67fc6c0 Delete type=0 #542 -2026/02/06-21:51:23.327724 7f71b67fc6c0 Manual compaction at level-0 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end) -2026/02/06-21:51:23.327764 7f71b67fc6c0 Manual compaction at level-1 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end) +2026/04/11-21:32:22.580345 7f20ee3ff6c0 Recovering log #547 +2026/04/11-21:32:22.590425 7f20ee3ff6c0 Delete type=3 #545 +2026/04/11-21:32:22.590495 7f20ee3ff6c0 Delete type=0 #547 +2026/04/12-00:19:41.520423 7f1e4ffff6c0 Level-0 table #552: started +2026/04/12-00:19:41.520464 7f1e4ffff6c0 Level-0 table #552: 0 bytes OK +2026/04/12-00:19:41.527028 7f1e4ffff6c0 Delete type=0 #550 +2026/04/12-00:19:41.543900 7f1e4ffff6c0 Manual compaction at level-0 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end) +2026/04/12-00:19:41.554664 7f1e4ffff6c0 Manual compaction at level-1 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end) diff --git a/packs-system/lf-skills/MANIFEST-000545 b/packs-system/lf-skills/MANIFEST-000553 similarity index 71% rename from packs-system/lf-skills/MANIFEST-000545 rename to packs-system/lf-skills/MANIFEST-000553 index 265df6489925858e7136c09ff5fcb44c1ea06ee1..2d11c7bbfec80d9e76046515ca1163ad82085083 100644 GIT binary patch delta 43 tcmdnQxQTH>pGt}zt35XZBhxAtP6p=HEG!RMk3E>}1rk{e5?Kop0RZ?Y3eNxl delta 43 tcmdnQxQTH>pUQcC2L^5iMy5q9oD9s1Sy&#j=H6Y!4H8)l5?Klo0Ra2R3X1>$ diff --git a/packs-system/lf-spells-miracles/000242.log b/packs-system/lf-spells-miracles/000250.log similarity index 100% rename from packs-system/lf-spells-miracles/000242.log rename to packs-system/lf-spells-miracles/000250.log diff --git a/packs-system/lf-spells-miracles/CURRENT b/packs-system/lf-spells-miracles/CURRENT index 803ffe2..f95348a 100644 --- a/packs-system/lf-spells-miracles/CURRENT +++ b/packs-system/lf-spells-miracles/CURRENT @@ -1 +1 @@ -MANIFEST-000240 +MANIFEST-000248 diff --git a/packs-system/lf-spells-miracles/LOG b/packs-system/lf-spells-miracles/LOG index 13a890e..059ecd7 100644 --- a/packs-system/lf-spells-miracles/LOG +++ b/packs-system/lf-spells-miracles/LOG @@ -1,8 +1,8 @@ -2026/04/07-20:41:02.201674 7f6820dfe6c0 Recovering log #238 -2026/04/07-20:41:02.254572 7f6820dfe6c0 Delete type=3 #236 -2026/04/07-20:41:02.254638 7f6820dfe6c0 Delete type=0 #238 -2026/04/07-20:44:04.827060 7f656afef6c0 Level-0 table #243: started -2026/04/07-20:44:04.827088 7f656afef6c0 Level-0 table #243: 0 bytes OK -2026/04/07-20:44:04.864062 7f656afef6c0 Delete type=0 #241 -2026/04/07-20:44:04.864215 7f656afef6c0 Manual compaction at level-0 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end) -2026/04/07-20:44:04.864230 7f656afef6c0 Manual compaction at level-1 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end) +2026/04/12-00:56:41.870438 7f20ecbfc6c0 Recovering log #246 +2026/04/12-00:56:41.885745 7f20ecbfc6c0 Delete type=3 #244 +2026/04/12-00:56:41.885813 7f20ecbfc6c0 Delete type=0 #246 +2026/04/12-01:07:05.229253 7f1e4ffff6c0 Level-0 table #251: started +2026/04/12-01:07:05.229286 7f1e4ffff6c0 Level-0 table #251: 0 bytes OK +2026/04/12-01:07:05.235261 7f1e4ffff6c0 Delete type=0 #249 +2026/04/12-01:07:05.235386 7f1e4ffff6c0 Manual compaction at level-0 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end) +2026/04/12-01:07:05.245080 7f1e4ffff6c0 Manual compaction at level-1 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end) diff --git a/packs-system/lf-spells-miracles/LOG.old b/packs-system/lf-spells-miracles/LOG.old index fa08aab..e80364e 100644 --- a/packs-system/lf-spells-miracles/LOG.old +++ b/packs-system/lf-spells-miracles/LOG.old @@ -1,8 +1,8 @@ -2026/02/06-21:03:10.160494 7f71b6ffd6c0 Recovering log #234 -2026/02/06-21:03:10.170771 7f71b6ffd6c0 Delete type=3 #232 -2026/02/06-21:03:10.170846 7f71b6ffd6c0 Delete type=0 #234 -2026/02/06-21:51:23.435610 7f71b67fc6c0 Level-0 table #239: started -2026/02/06-21:51:23.435649 7f71b67fc6c0 Level-0 table #239: 0 bytes OK -2026/02/06-21:51:23.473038 7f71b67fc6c0 Delete type=0 #237 -2026/02/06-21:51:23.473211 7f71b67fc6c0 Manual compaction at level-0 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end) -2026/02/06-21:51:23.473250 7f71b67fc6c0 Manual compaction at level-1 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end) +2026/04/11-21:32:22.642207 7f20edbfe6c0 Recovering log #242 +2026/04/11-21:32:22.652005 7f20edbfe6c0 Delete type=3 #240 +2026/04/11-21:32:22.652058 7f20edbfe6c0 Delete type=0 #242 +2026/04/12-00:19:41.506566 7f1e4ffff6c0 Level-0 table #247: started +2026/04/12-00:19:41.506602 7f1e4ffff6c0 Level-0 table #247: 0 bytes OK +2026/04/12-00:19:41.512971 7f1e4ffff6c0 Delete type=0 #245 +2026/04/12-00:19:41.513136 7f1e4ffff6c0 Manual compaction at level-0 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end) +2026/04/12-00:19:41.513162 7f1e4ffff6c0 Manual compaction at level-1 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end) diff --git a/packs-system/lf-spells-miracles/MANIFEST-000240 b/packs-system/lf-spells-miracles/MANIFEST-000248 similarity index 72% rename from packs-system/lf-spells-miracles/MANIFEST-000240 rename to packs-system/lf-spells-miracles/MANIFEST-000248 index da96f669e3b4a33f14f84a7a20932c80344de521..219127b3dfb729e380acda1016e1c721e6ecb455 100644 GIT binary patch delta 43 tcmdnUxRG%}ugZ%vb$hrO7@2-DaxyUgVq}@baXNP91(3)ukjNj92mnlt4T1mw delta 43 tcmdnUxRG%}uZr^p_iAniMy8L9oD9sL7+EH9BqXev2om`O68QoW0RS3O3$*|M diff --git a/packs-system/lf-vulnerabilities/000541.log b/packs-system/lf-vulnerabilities/000549.log similarity index 100% rename from packs-system/lf-vulnerabilities/000541.log rename to packs-system/lf-vulnerabilities/000549.log diff --git a/packs-system/lf-vulnerabilities/CURRENT b/packs-system/lf-vulnerabilities/CURRENT index c02f3d8..8ef3a46 100644 --- a/packs-system/lf-vulnerabilities/CURRENT +++ b/packs-system/lf-vulnerabilities/CURRENT @@ -1 +1 @@ -MANIFEST-000539 +MANIFEST-000547 diff --git a/packs-system/lf-vulnerabilities/LOG b/packs-system/lf-vulnerabilities/LOG index 0412df1..ace657c 100644 --- a/packs-system/lf-vulnerabilities/LOG +++ b/packs-system/lf-vulnerabilities/LOG @@ -1,8 +1,8 @@ -2026/04/07-20:41:02.136268 7f67eb7fe6c0 Recovering log #537 -2026/04/07-20:41:02.189277 7f67eb7fe6c0 Delete type=3 #535 -2026/04/07-20:41:02.189349 7f67eb7fe6c0 Delete type=0 #537 -2026/04/07-20:44:04.864322 7f656afef6c0 Level-0 table #542: started -2026/04/07-20:44:04.864350 7f656afef6c0 Level-0 table #542: 0 bytes OK -2026/04/07-20:44:04.894847 7f656afef6c0 Delete type=0 #540 -2026/04/07-20:44:05.055029 7f656afef6c0 Manual compaction at level-0 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end) -2026/04/07-20:44:05.055068 7f656afef6c0 Manual compaction at level-1 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end) +2026/04/12-00:56:41.853085 7f20ed3fd6c0 Recovering log #545 +2026/04/12-00:56:41.867862 7f20ed3fd6c0 Delete type=3 #543 +2026/04/12-00:56:41.867925 7f20ed3fd6c0 Delete type=0 #545 +2026/04/12-01:07:05.188580 7f1e4ffff6c0 Level-0 table #550: started +2026/04/12-01:07:05.188606 7f1e4ffff6c0 Level-0 table #550: 0 bytes OK +2026/04/12-01:07:05.194367 7f1e4ffff6c0 Delete type=0 #548 +2026/04/12-01:07:05.207115 7f1e4ffff6c0 Manual compaction at level-0 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end) +2026/04/12-01:07:05.207142 7f1e4ffff6c0 Manual compaction at level-1 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end) diff --git a/packs-system/lf-vulnerabilities/LOG.old b/packs-system/lf-vulnerabilities/LOG.old index dde7648..b11ba39 100644 --- a/packs-system/lf-vulnerabilities/LOG.old +++ b/packs-system/lf-vulnerabilities/LOG.old @@ -1,8 +1,8 @@ -2026/02/06-21:03:10.146976 7f71b77fe6c0 Recovering log #533 -2026/02/06-21:03:10.156549 7f71b77fe6c0 Delete type=3 #531 -2026/02/06-21:03:10.156610 7f71b77fe6c0 Delete type=0 #533 -2026/02/06-21:51:23.327858 7f71b67fc6c0 Level-0 table #538: started -2026/02/06-21:51:23.327892 7f71b67fc6c0 Level-0 table #538: 0 bytes OK -2026/02/06-21:51:23.359727 7f71b67fc6c0 Delete type=0 #536 -2026/02/06-21:51:23.473179 7f71b67fc6c0 Manual compaction at level-0 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end) -2026/02/06-21:51:23.473221 7f71b67fc6c0 Manual compaction at level-1 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end) +2026/04/11-21:32:22.628476 7f20ecbfc6c0 Recovering log #541 +2026/04/11-21:32:22.638519 7f20ecbfc6c0 Delete type=3 #539 +2026/04/11-21:32:22.638582 7f20ecbfc6c0 Delete type=0 #541 +2026/04/12-00:19:41.499517 7f1e4ffff6c0 Level-0 table #546: started +2026/04/12-00:19:41.499556 7f1e4ffff6c0 Level-0 table #546: 0 bytes OK +2026/04/12-00:19:41.506406 7f1e4ffff6c0 Delete type=0 #544 +2026/04/12-00:19:41.513127 7f1e4ffff6c0 Manual compaction at level-0 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end) +2026/04/12-00:19:41.513155 7f1e4ffff6c0 Manual compaction at level-1 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end) diff --git a/packs-system/lf-vulnerabilities/MANIFEST-000539 b/packs-system/lf-vulnerabilities/MANIFEST-000547 similarity index 72% rename from packs-system/lf-vulnerabilities/MANIFEST-000539 rename to packs-system/lf-vulnerabilities/MANIFEST-000547 index 34a0c5faaae1ad729d52ee7fe73264388ffa1809..ca00070db72624b559f3d084a2dc1634e990ef36 100644 GIT binary patch delta 41 rcmdnMxPfs(pHeJC-%KtBMy4e!oD9rMSy;08@?`dd1eSsXmIDO<+?@$A delta 41 scmdnMxPfs(pOVJ5lQXy&7@6j
- Damage automatically applied to defender (with Armor DR) + Damage automatically applied to defender (with damage reduction)
{{else}} @@ -288,4 +288,4 @@ {{/if}} {{/if}} - \ No newline at end of file + diff --git a/templates/combat-tracker-footer-v2.hbs b/templates/combat-tracker-footer-v2.hbs index 82406f3..6eb221e 100644 --- a/templates/combat-tracker-footer-v2.hbs +++ b/templates/combat-tracker-footer-v2.hbs @@ -3,7 +3,7 @@ {{!-- GM Controls --}} {{#if user.isGM}} - {{#if combat.round}} + {{#if (or combat.round (eq combat.turn 0))}}