Compare commits
7 Commits
141d6048e0
...
53f9c33419
| Author | SHA1 | Date | |
|---|---|---|---|
| 53f9c33419 | |||
| 06eba5f835 | |||
| 46fa2d15a3 | |||
| 8aae7bada0 | |||
| ceb62bca3f | |||
| 110ac65ba5 | |||
| 9b75fd4d96 |
@@ -0,0 +1 @@
|
|||||||
|
packs/** filter=lfs diff=lfs merge=lfs -text
|
||||||
@@ -9,3 +9,11 @@ node_modules/
|
|||||||
.history
|
.history
|
||||||
.github/
|
.github/
|
||||||
|
|
||||||
|
# LevelDB internals (auto-generated, churn on every open)
|
||||||
|
packs-system/**/*.log
|
||||||
|
packs-system/**/LOG
|
||||||
|
packs-system/**/LOG.old
|
||||||
|
packs-system/**/CURRENT
|
||||||
|
packs-system/**/LOCK
|
||||||
|
packs-system/**/MANIFEST-*
|
||||||
|
|
||||||
|
|||||||
Vendored
+6
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"cSpell.words": [
|
||||||
|
"biodata",
|
||||||
|
"LETHALFANTASY"
|
||||||
|
]
|
||||||
|
}
|
||||||
+3
-1
@@ -624,7 +624,9 @@
|
|||||||
"messageLethargyKO": "{spellName} : Lethargy still ongoing ... ( dice result : {roll} )",
|
"messageLethargyKO": "{spellName} : Lethargy still ongoing ... ( dice result : {roll} )",
|
||||||
"messageProgressionKO": "{name} can't attack this second.",
|
"messageProgressionKO": "{name} can't attack this second.",
|
||||||
"messageProgressionOKMonster": "{name} can attack this second with {weapon}.",
|
"messageProgressionOKMonster": "{name} can attack this second with {weapon}.",
|
||||||
"messageProgressionKOMonster": "{name} can't attack this second (dice result {roll})."
|
"messageProgressionKOMonster": "{name} can't attack this second (dice result {roll}).",
|
||||||
|
"bleedingCombatEnd": "Bleeding active out of combat: {names}",
|
||||||
|
"bleedingCombatStart": "Bleeding still active on: {names}"
|
||||||
},
|
},
|
||||||
"Opponent": {
|
"Opponent": {
|
||||||
"FIELDS": {}
|
"FIELDS": {}
|
||||||
|
|||||||
+123
-1
@@ -117,6 +117,9 @@ Hooks.once("ready", function () {
|
|||||||
registerWorldCount("lethalFantasy")
|
registerWorldCount("lethalFantasy")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Saignement piloté par le combat tracker
|
||||||
|
_registerBleedingHooks()
|
||||||
|
|
||||||
_showUserGuide()
|
_showUserGuide()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -129,6 +132,97 @@ Hooks.once("ready", function () {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saignement piloté par le combat tracker.
|
||||||
|
* Chaque round = 1 seconde → les acteurs qui saignent perdent 1 HP/blessure.
|
||||||
|
* Hors combat, une notification prévient le MJ que des blessures saignent encore.
|
||||||
|
*/
|
||||||
|
function _registerBleedingHooks() {
|
||||||
|
if (!game.user.isGM) return
|
||||||
|
|
||||||
|
Hooks.on("combatRound", async (combat, previous, current) => {
|
||||||
|
if (previous === current) return
|
||||||
|
const processed = new Set()
|
||||||
|
for (const combatant of combat.combatants) {
|
||||||
|
const actor = combatant.actor
|
||||||
|
if (!actor || processed.has(actor.id)) continue
|
||||||
|
processed.add(actor.id)
|
||||||
|
await _applyBleedingTick(actor)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
Hooks.on("combatEnd", async (combat) => {
|
||||||
|
const bleeding = _findBleedingActors()
|
||||||
|
if (bleeding.length) {
|
||||||
|
ui.notifications.warn(
|
||||||
|
game.i18n.format("LETHALFANTASY.Notifications.bleedingCombatEnd", {
|
||||||
|
names: bleeding.map(a => a.name).join(", "),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
Hooks.on("combatStart", async (combat) => {
|
||||||
|
const bleeding = _findBleedingActors()
|
||||||
|
if (bleeding.length) {
|
||||||
|
ui.notifications.warn(
|
||||||
|
game.i18n.format("LETHALFANTASY.Notifications.bleedingCombatStart", {
|
||||||
|
names: bleeding.map(a => a.name).join(", "),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appliquer 1 HP de dégât par blessure active, décrémenter la durée.
|
||||||
|
* @param {import("foundry/common/documents.mjs").Actor} actor
|
||||||
|
*/
|
||||||
|
async function _applyBleedingTick(actor) {
|
||||||
|
if (!actor?.system?.hp?.wounds) return
|
||||||
|
const wounds = foundry.utils.duplicate(actor.system.hp.wounds)
|
||||||
|
let hpLoss = 0
|
||||||
|
let changed = false
|
||||||
|
for (const wound of wounds) {
|
||||||
|
if (wound.duration > 0 && wound.value > 0) {
|
||||||
|
hpLoss += 1
|
||||||
|
wound.duration -= 1
|
||||||
|
if (wound.duration <= 0) {
|
||||||
|
wound.value = 0
|
||||||
|
wound.description = ""
|
||||||
|
}
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!changed) return
|
||||||
|
const currentHp = actor.system.hp.value ?? 0
|
||||||
|
await actor.update({
|
||||||
|
"system.hp.value": currentHp - hpLoss,
|
||||||
|
"system.hp.wounds": wounds,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retourne les acteurs (monde + tokens) qui ont des blessures actives.
|
||||||
|
* @returns {import("foundry/common/documents.mjs").Actor[]}
|
||||||
|
*/
|
||||||
|
function _findBleedingActors() {
|
||||||
|
const actors = []
|
||||||
|
for (const actor of game.actors.values()) {
|
||||||
|
if (actor?.system?.hp?.wounds?.some(w => w.duration > 0 && w.value > 0)) {
|
||||||
|
actors.push(actor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const token of canvas.tokens?.placeables ?? []) {
|
||||||
|
if (token.actor && !actors.includes(token.actor)) {
|
||||||
|
if (token.actor?.system?.hp?.wounds?.some(w => w.duration > 0 && w.value > 0)) {
|
||||||
|
actors.push(token.actor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return actors
|
||||||
|
}
|
||||||
|
|
||||||
Hooks.on("renderChatMessageHTML", (message, html, data) => {
|
Hooks.on("renderChatMessageHTML", (message, html, data) => {
|
||||||
const typeMessage = data.message.flags.lethalFantasy?.typeMessage
|
const typeMessage = data.message.flags.lethalFantasy?.typeMessage
|
||||||
// Message de demande de jet de dés
|
// Message de demande de jet de dés
|
||||||
@@ -908,9 +1002,37 @@ Hooks.on("createChatMessage", async (message) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If attacker boosted past defense, let the defender react
|
||||||
|
if (attackerHandledBonus && defenseRoll < attackRollFinal && defender) {
|
||||||
|
const defenderOwner = game.users.find(u => u.active && !u.isGM && defender.testUserPermission(u, "OWNER"))
|
||||||
|
if (defenderOwner && defenderOwner.id !== game.user.id) {
|
||||||
|
// Cross-client: send socket to defender's client
|
||||||
|
const sData = LethalFantasyUtils.getShieldReactionData(defender)
|
||||||
|
game.socket.emit(`system.${SYSTEM.id}`, {
|
||||||
|
type: "attackBoosted",
|
||||||
|
userId: defenderOwner.id,
|
||||||
|
attackerName, attackerId, defenderName, defenderId, defenderTokenId,
|
||||||
|
attackRollFinal, defenseRoll, attackWeaponId, attackRollType, attackRollKey,
|
||||||
|
shieldDamageReduction: shieldBlocked ? shieldReaction?.damageReduction ?? 0 : 0,
|
||||||
|
d30Bleed: d30Bleed ? "true" : "",
|
||||||
|
d30DamageMultiplier, d30DrMultiplier,
|
||||||
|
damageTier: damageTier || "standard",
|
||||||
|
attackD30message,
|
||||||
|
hasShield: !!sData,
|
||||||
|
shieldLabel: sData?.label || "",
|
||||||
|
shieldFormula: sData?.formula || "",
|
||||||
|
shieldDr: sData?.damageReduction || 0,
|
||||||
|
canAdHocShield: !sData,
|
||||||
|
})
|
||||||
|
return // Comparison message created by defender's client
|
||||||
|
}
|
||||||
|
// Single-client (GM controls both): restart so defender loop can run
|
||||||
|
mulliganRestart = true
|
||||||
|
}
|
||||||
} while (mulliganRestart)
|
} while (mulliganRestart)
|
||||||
|
|
||||||
const shieldDamageReduction = shieldBlocked ? shieldReaction.damageReduction : 0
|
const shieldDamageReduction = shieldBlocked ? shieldReaction?.damageReduction ?? 0 : 0
|
||||||
const outcome = shieldBlocked ? "shielded-hit" : (attackRollFinal > defenseRoll ? "hit" : "miss")
|
const outcome = shieldBlocked ? "shielded-hit" : (attackRollFinal > defenseRoll ? "hit" : "miss")
|
||||||
|
|
||||||
// Créer le message de comparaison - uniquement par le client qui a géré le dernier bonus
|
// Créer le message de comparaison - uniquement par le client qui a géré le dernier bonus
|
||||||
|
|||||||
@@ -205,12 +205,33 @@ export default class LethalFantasyActor extends Actor {
|
|||||||
case "spell-power":
|
case "spell-power":
|
||||||
case "miracle-attack":
|
case "miracle-attack":
|
||||||
case "miracle-power":
|
case "miracle-power":
|
||||||
rollTarget = this.items.find((i) => (i.type === "miracle" || i.type == "spell") && i.id === rollKey)
|
rollTarget = foundry.utils.duplicate(this.items.find((i) => (i.type === "miracle" || i.type == "spell") && i.id === rollKey))
|
||||||
rollTarget.rollKey = rollKey
|
rollTarget.rollKey = rollKey
|
||||||
// Read damage tier from combatant currentAction if available
|
// Read damage tier from combatant currentAction if available
|
||||||
const activeCombatant = game.combat?.combatants?.find(c => c.actorId === this.id)
|
const activeCombatant = game.combat?.combatants?.find(c => c.actorId === this.id)
|
||||||
const currentAction = activeCombatant?.getFlag(SYSTEM.id, "currentAction")
|
const currentAction = activeCombatant?.getFlag(SYSTEM.id, "currentAction")
|
||||||
const damageTier = currentAction?.damageTier || "standard"
|
let damageTier = currentAction?.damageTier
|
||||||
|
// No tier from combat action — prompt the user if multiple tiers exist
|
||||||
|
if (!damageTier) {
|
||||||
|
const tierMap = { standard: "damageDice", overpowered: "damageDiceOverpowered", overpowered2: "damageDiceOverpowered2" }
|
||||||
|
const available = Object.entries(tierMap).filter(([k, v]) => rollTarget.system?.[v])
|
||||||
|
if (available.length > 1) {
|
||||||
|
const buttons = available.map(([id]) => ({
|
||||||
|
action: id,
|
||||||
|
label: id.charAt(0).toUpperCase() + id.slice(1),
|
||||||
|
callback: () => id,
|
||||||
|
}))
|
||||||
|
damageTier = await foundry.applications.api.DialogV2.wait({
|
||||||
|
window: { title: "Choose spell tier" },
|
||||||
|
classes: ["lethalfantasy"],
|
||||||
|
content: `<p>Select the power level for <strong>${rollTarget.name}</strong>:</p>`,
|
||||||
|
buttons,
|
||||||
|
rejectClose: false,
|
||||||
|
}) || "standard"
|
||||||
|
} else {
|
||||||
|
damageTier = "standard"
|
||||||
|
}
|
||||||
|
}
|
||||||
rollTarget.damageTier = damageTier
|
rollTarget.damageTier = damageTier
|
||||||
if (rollType === "spell-attack" || rollType === "spell-power") {
|
if (rollType === "spell-attack" || rollType === "spell-power") {
|
||||||
const tierCostMap = { standard: "cost", overpowered: "costOverpowered", overpowered2: "costOverpowered2" }
|
const tierCostMap = { standard: "cost", overpowered: "costOverpowered", overpowered2: "costOverpowered2" }
|
||||||
|
|||||||
@@ -358,7 +358,7 @@ export default class LethalFantasyRoll extends Roll {
|
|||||||
dice,
|
dice,
|
||||||
hasTarget: options.hasTarget,
|
hasTarget: options.hasTarget,
|
||||||
modifier,
|
modifier,
|
||||||
saveSpell: false,
|
saveSpell: game.lethalFantasy?.spellDefense ?? false,
|
||||||
favor: "none",
|
favor: "none",
|
||||||
targetName,
|
targetName,
|
||||||
isRangedAttack
|
isRangedAttack
|
||||||
|
|||||||
@@ -189,6 +189,11 @@ export default class LethalFantasyUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
case "attackBoosted":
|
||||||
|
if (msg.userId === game.user.id) {
|
||||||
|
LethalFantasyUtils.handleAttackBoosted(msg)
|
||||||
|
}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,6 +231,180 @@ export default class LethalFantasyUtils {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
static async handleAttackBoosted(msg) {
|
||||||
|
const {
|
||||||
|
attackerName, attackerId, defenderName, defenderId, defenderTokenId,
|
||||||
|
attackRollFinal, defenseRoll, attackWeaponId, attackRollType, attackRollKey,
|
||||||
|
shieldDamageReduction: initialShieldDR,
|
||||||
|
d30Bleed, d30DamageMultiplier, d30DrMultiplier,
|
||||||
|
damageTier, attackD30message,
|
||||||
|
hasShield, shieldLabel, shieldFormula, shieldDr, canAdHocShield
|
||||||
|
} = msg
|
||||||
|
|
||||||
|
const defender = game.actors.get(defenderId)
|
||||||
|
if (!defender) return
|
||||||
|
|
||||||
|
let updatedDefenseRoll = defenseRoll
|
||||||
|
let shieldBlocked = false
|
||||||
|
let shieldReaction = null
|
||||||
|
let canShieldReact = hasShield
|
||||||
|
let canAdHoc = canAdHocShield
|
||||||
|
|
||||||
|
// Show the defense reaction dialog
|
||||||
|
if (defender && updatedDefenseRoll < 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"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
buttons.push({
|
||||||
|
action: "bonusDie",
|
||||||
|
label: "Add bonus die",
|
||||||
|
icon: "fa-solid fa-dice",
|
||||||
|
callback: () => "bonusDie"
|
||||||
|
})
|
||||||
|
|
||||||
|
if (canShieldReact) {
|
||||||
|
buttons.push({
|
||||||
|
action: "shieldReact",
|
||||||
|
label: `Roll shield (${shieldLabel})`,
|
||||||
|
icon: "fa-solid fa-shield",
|
||||||
|
callback: () => "shieldReact"
|
||||||
|
})
|
||||||
|
} else if (canAdHoc) {
|
||||||
|
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 — attack boosted" },
|
||||||
|
classes: ["lethalfantasy"],
|
||||||
|
content: `
|
||||||
|
<div class="grit-luck-dialog">
|
||||||
|
<div class="combat-status">
|
||||||
|
<p><strong>${attackerName}</strong> boosted attack to <strong>${attackRollFinal}</strong></p>
|
||||||
|
<p><strong>${defenderName}</strong> currently has <strong>${updatedDefenseRoll}</strong></p>
|
||||||
|
</div>
|
||||||
|
<p class="offer-text">The attack was boosted! Choose how to improve the defense.</p>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
buttons,
|
||||||
|
rejectClose: false
|
||||||
|
})
|
||||||
|
|
||||||
|
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>`)
|
||||||
|
updatedDefenseRoll += bonusRoll
|
||||||
|
await defender.update({ "system.grit.current": currentGrit - 1 })
|
||||||
|
} else 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>`)
|
||||||
|
updatedDefenseRoll += bonusRoll
|
||||||
|
await defender.update({ "system.luck.current": currentLuck - 1 })
|
||||||
|
} else if (choice === "bonusDie") {
|
||||||
|
const bonusDie = await LethalFantasyUtils.promptCombatBonusDie(defenderName, "attack", updatedDefenseRoll, attackRollFinal)
|
||||||
|
if (bonusDie) {
|
||||||
|
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>`)
|
||||||
|
updatedDefenseRoll += bonusRoll
|
||||||
|
}
|
||||||
|
} else if (choice === "shieldReact" && canShieldReact) {
|
||||||
|
const shieldBonus = await LethalFantasyUtils.rollBonusDie(shieldFormula, defender)
|
||||||
|
const newDefenseTotal = updatedDefenseRoll + shieldBonus
|
||||||
|
updatedDefenseRoll = newDefenseTotal
|
||||||
|
canShieldReact = false
|
||||||
|
if (newDefenseTotal >= attackRollFinal) {
|
||||||
|
shieldBlocked = true
|
||||||
|
shieldReaction = { damageReduction: shieldDr, label: shieldLabel, bonus: shieldBonus }
|
||||||
|
await ChatMessage.create({
|
||||||
|
content: `<p><strong>${defenderName}</strong> rolls <strong>${shieldLabel}</strong> and adds <strong>${shieldBonus}</strong> to defense (${newDefenseTotal} ≥ ${attackRollFinal}). <strong>Shield blocked the attack!</strong> Both armor DR and shield DR <strong>${shieldDr}</strong> will apply to damage.</p>`,
|
||||||
|
speaker: ChatMessage.getSpeaker({ actor: defender })
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
await ChatMessage.create({
|
||||||
|
content: `<p><strong>${defenderName}</strong> rolls <strong>${shieldLabel}</strong> and adds <strong>${shieldBonus}</strong> to defense (${newDefenseTotal} < ${attackRollFinal}). Shield did not block — normal hit, armor DR only.</p>`,
|
||||||
|
speaker: ChatMessage.getSpeaker({ actor: defender })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else if (choice === "adHocShield" && canAdHoc) {
|
||||||
|
const adHoc = await LethalFantasyUtils.promptAdHocShield(defenderName, attackRollFinal, updatedDefenseRoll)
|
||||||
|
if (adHoc) {
|
||||||
|
const shieldBonus = await LethalFantasyUtils.rollBonusDie(adHoc.formula, defender)
|
||||||
|
const newDefenseTotal = updatedDefenseRoll + shieldBonus
|
||||||
|
updatedDefenseRoll = newDefenseTotal
|
||||||
|
canShieldReact = false
|
||||||
|
canAdHoc = false
|
||||||
|
if (newDefenseTotal >= attackRollFinal) {
|
||||||
|
shieldBlocked = true
|
||||||
|
shieldReaction = { damageReduction: adHoc.damageReduction, label: `${adHoc.formula.toUpperCase()} shield`, bonus: shieldBonus }
|
||||||
|
await ChatMessage.create({
|
||||||
|
content: `<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>`,
|
||||||
|
speaker: ChatMessage.getSpeaker({ actor: defender })
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
await ChatMessage.create({
|
||||||
|
content: `<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>`,
|
||||||
|
speaker: ChatMessage.getSpeaker({ actor: defender })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const finalShieldDR = shieldBlocked ? (shieldReaction?.damageReduction || 0) : 0
|
||||||
|
const outcome = shieldBlocked ? "shielded-hit" : (updatedDefenseRoll > attackRollFinal ? "miss" : "hit")
|
||||||
|
|
||||||
|
await LethalFantasyUtils.compareAttackDefense({
|
||||||
|
attackerName,
|
||||||
|
attackerId,
|
||||||
|
attackRoll: attackRollFinal,
|
||||||
|
attackWeaponId,
|
||||||
|
attackRollType,
|
||||||
|
attackRollKey,
|
||||||
|
defenderName,
|
||||||
|
defenderId,
|
||||||
|
defenderTokenId,
|
||||||
|
defenseRoll: updatedDefenseRoll,
|
||||||
|
outcome,
|
||||||
|
shieldDamageReduction: finalShieldDR,
|
||||||
|
d30Bleed: d30Bleed || "",
|
||||||
|
d30DamageMultiplier: d30DamageMultiplier || 1,
|
||||||
|
d30DrMultiplier: d30DrMultiplier || 1,
|
||||||
|
damageTier: damageTier || "standard",
|
||||||
|
attackD30message
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
static async showDefenseRequest(msg) {
|
static async showDefenseRequest(msg) {
|
||||||
const attackerName = msg.attackerName
|
const attackerName = msg.attackerName
|
||||||
@@ -323,6 +502,7 @@ export default class LethalFantasyUtils {
|
|||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
game.lethalFantasy = game.lethalFantasy || {}
|
game.lethalFantasy = game.lethalFantasy || {}
|
||||||
|
game.lethalFantasy.spellDefense = true // pré-cocher "Save against spell" dans le dialog
|
||||||
game.lethalFantasy.nextDefenseData = {
|
game.lethalFantasy.nextDefenseData = {
|
||||||
attackerId,
|
attackerId,
|
||||||
attackRoll,
|
attackRoll,
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
MANIFEST-000631
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
2026/06/11-20:25:36.058631 7f37427ed6c0 Recovering log #629
|
|
||||||
2026/06/11-20:25:36.068520 7f37427ed6c0 Delete type=3 #627
|
|
||||||
2026/06/11-20:25:36.068574 7f37427ed6c0 Delete type=0 #629
|
|
||||||
2026/06/11-20:41:45.938413 7f3740fff6c0 Level-0 table #634: started
|
|
||||||
2026/06/11-20:41:45.938512 7f3740fff6c0 Level-0 table #634: 0 bytes OK
|
|
||||||
2026/06/11-20:41:45.945312 7f3740fff6c0 Delete type=0 #632
|
|
||||||
2026/06/11-20:41:45.965586 7f3740fff6c0 Manual compaction at level-0 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end)
|
|
||||||
2026/06/11-20:41:45.965944 7f3740fff6c0 Manual compaction at level-1 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end)
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
2026/06/10-19:56:01.455200 7f301cbff6c0 Recovering log #625
|
|
||||||
2026/06/10-19:56:01.464323 7f301cbff6c0 Delete type=3 #623
|
|
||||||
2026/06/10-19:56:01.464355 7f301cbff6c0 Delete type=0 #625
|
|
||||||
2026/06/10-20:16:07.843239 7f2fce7fc6c0 Level-0 table #630: started
|
|
||||||
2026/06/10-20:16:07.843267 7f2fce7fc6c0 Level-0 table #630: 0 bytes OK
|
|
||||||
2026/06/10-20:16:07.849298 7f2fce7fc6c0 Delete type=0 #628
|
|
||||||
2026/06/10-20:16:07.849522 7f2fce7fc6c0 Manual compaction at level-0 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end)
|
|
||||||
2026/06/10-20:16:07.849556 7f2fce7fc6c0 Manual compaction at level-1 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end)
|
|
||||||
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
MANIFEST-000628
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
2026/06/11-20:25:36.074598 7f37437ef6c0 Recovering log #626
|
|
||||||
2026/06/11-20:25:36.084983 7f37437ef6c0 Delete type=3 #624
|
|
||||||
2026/06/11-20:25:36.085075 7f37437ef6c0 Delete type=0 #626
|
|
||||||
2026/06/11-20:41:45.965954 7f3740fff6c0 Level-0 table #631: started
|
|
||||||
2026/06/11-20:41:45.965991 7f3740fff6c0 Level-0 table #631: 0 bytes OK
|
|
||||||
2026/06/11-20:41:45.972550 7f3740fff6c0 Delete type=0 #629
|
|
||||||
2026/06/11-20:41:45.992980 7f3740fff6c0 Manual compaction at level-0 from '!folders!yPWGvxHJbDNHVSnY' @ 72057594037927935 : 1 .. '!items!x5gLtqlW4sdDmHTd' @ 0 : 0; will stop at (end)
|
|
||||||
2026/06/11-20:41:45.993323 7f3740fff6c0 Manual compaction at level-1 from '!folders!yPWGvxHJbDNHVSnY' @ 72057594037927935 : 1 .. '!items!x5gLtqlW4sdDmHTd' @ 0 : 0; will stop at (end)
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
2026/06/10-19:56:01.470668 7f2fcffff6c0 Recovering log #622
|
|
||||||
2026/06/10-19:56:01.480292 7f2fcffff6c0 Delete type=3 #620
|
|
||||||
2026/06/10-19:56:01.480316 7f2fcffff6c0 Delete type=0 #622
|
|
||||||
2026/06/10-20:16:07.810643 7f2fce7fc6c0 Level-0 table #627: started
|
|
||||||
2026/06/10-20:16:07.810662 7f2fce7fc6c0 Level-0 table #627: 0 bytes OK
|
|
||||||
2026/06/10-20:16:07.816355 7f2fce7fc6c0 Delete type=0 #625
|
|
||||||
2026/06/10-20:16:07.823439 7f2fce7fc6c0 Manual compaction at level-0 from '!folders!yPWGvxHJbDNHVSnY' @ 72057594037927935 : 1 .. '!items!x5gLtqlW4sdDmHTd' @ 0 : 0; will stop at (end)
|
|
||||||
2026/06/10-20:16:07.823453 7f2fce7fc6c0 Manual compaction at level-1 from '!folders!yPWGvxHJbDNHVSnY' @ 72057594037927935 : 1 .. '!items!x5gLtqlW4sdDmHTd' @ 0 : 0; will stop at (end)
|
|
||||||
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
MANIFEST-000633
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
2026/06/11-20:25:36.041244 7f3742fee6c0 Recovering log #631
|
|
||||||
2026/06/11-20:25:36.052153 7f3742fee6c0 Delete type=3 #629
|
|
||||||
2026/06/11-20:25:36.052218 7f3742fee6c0 Delete type=0 #631
|
|
||||||
2026/06/11-20:41:45.958653 7f3740fff6c0 Level-0 table #636: started
|
|
||||||
2026/06/11-20:41:45.958703 7f3740fff6c0 Level-0 table #636: 0 bytes OK
|
|
||||||
2026/06/11-20:41:45.965435 7f3740fff6c0 Delete type=0 #634
|
|
||||||
2026/06/11-20:41:45.965798 7f3740fff6c0 Manual compaction at level-0 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end)
|
|
||||||
2026/06/11-20:41:45.965918 7f3740fff6c0 Manual compaction at level-1 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end)
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
2026/06/10-19:56:01.439055 7f2fcffff6c0 Recovering log #627
|
|
||||||
2026/06/10-19:56:01.451210 7f2fcffff6c0 Delete type=3 #625
|
|
||||||
2026/06/10-19:56:01.451236 7f2fcffff6c0 Delete type=0 #627
|
|
||||||
2026/06/10-20:16:07.804529 7f2fce7fc6c0 Level-0 table #632: started
|
|
||||||
2026/06/10-20:16:07.804550 7f2fce7fc6c0 Level-0 table #632: 0 bytes OK
|
|
||||||
2026/06/10-20:16:07.810504 7f2fce7fc6c0 Delete type=0 #630
|
|
||||||
2026/06/10-20:16:07.823431 7f2fce7fc6c0 Manual compaction at level-0 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end)
|
|
||||||
2026/06/10-20:16:07.823457 7f2fce7fc6c0 Manual compaction at level-1 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end)
|
|
||||||
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
MANIFEST-000328
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
2026/06/11-20:25:36.103531 7f37437ef6c0 Recovering log #326
|
|
||||||
2026/06/11-20:25:36.114200 7f37437ef6c0 Delete type=3 #324
|
|
||||||
2026/06/11-20:25:36.114249 7f37437ef6c0 Delete type=0 #326
|
|
||||||
2026/06/11-20:41:45.972686 7f3740fff6c0 Level-0 table #331: started
|
|
||||||
2026/06/11-20:41:45.972724 7f3740fff6c0 Level-0 table #331: 0 bytes OK
|
|
||||||
2026/06/11-20:41:45.979068 7f3740fff6c0 Delete type=0 #329
|
|
||||||
2026/06/11-20:41:45.993007 7f3740fff6c0 Manual compaction at level-0 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end)
|
|
||||||
2026/06/11-20:41:45.993301 7f3740fff6c0 Manual compaction at level-1 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end)
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
2026/06/10-19:56:01.497944 7f301cbff6c0 Recovering log #322
|
|
||||||
2026/06/10-19:56:01.506991 7f301cbff6c0 Delete type=3 #320
|
|
||||||
2026/06/10-19:56:01.507034 7f301cbff6c0 Delete type=0 #322
|
|
||||||
2026/06/10-20:16:07.829554 7f2fce7fc6c0 Level-0 table #327: started
|
|
||||||
2026/06/10-20:16:07.829571 7f2fce7fc6c0 Level-0 table #327: 0 bytes OK
|
|
||||||
2026/06/10-20:16:07.836328 7f2fce7fc6c0 Delete type=0 #325
|
|
||||||
2026/06/10-20:16:07.849487 7f2fce7fc6c0 Manual compaction at level-0 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end)
|
|
||||||
2026/06/10-20:16:07.849533 7f2fce7fc6c0 Manual compaction at level-1 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end)
|
|
||||||
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
MANIFEST-000627
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
2026/06/11-20:25:36.088952 7f37427ed6c0 Recovering log #625
|
|
||||||
2026/06/11-20:25:36.100017 7f37427ed6c0 Delete type=3 #623
|
|
||||||
2026/06/11-20:25:36.100073 7f37427ed6c0 Delete type=0 #625
|
|
||||||
2026/06/11-20:41:45.945442 7f3740fff6c0 Level-0 table #630: started
|
|
||||||
2026/06/11-20:41:45.945486 7f3740fff6c0 Level-0 table #630: 0 bytes OK
|
|
||||||
2026/06/11-20:41:45.951778 7f3740fff6c0 Delete type=0 #628
|
|
||||||
2026/06/11-20:41:45.965603 7f3740fff6c0 Manual compaction at level-0 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end)
|
|
||||||
2026/06/11-20:41:45.965769 7f3740fff6c0 Manual compaction at level-1 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end)
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
2026/06/10-19:56:01.483620 7f2fceffd6c0 Recovering log #621
|
|
||||||
2026/06/10-19:56:01.493981 7f2fceffd6c0 Delete type=3 #619
|
|
||||||
2026/06/10-19:56:01.494004 7f2fceffd6c0 Delete type=0 #621
|
|
||||||
2026/06/10-20:16:07.797521 7f2fce7fc6c0 Level-0 table #626: started
|
|
||||||
2026/06/10-20:16:07.797566 7f2fce7fc6c0 Level-0 table #626: 0 bytes OK
|
|
||||||
2026/06/10-20:16:07.804440 7f2fce7fc6c0 Delete type=0 #624
|
|
||||||
2026/06/10-20:16:07.823416 7f2fce7fc6c0 Manual compaction at level-0 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end)
|
|
||||||
2026/06/10-20:16:07.823449 7f2fce7fc6c0 Manual compaction at level-1 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end)
|
|
||||||
Binary file not shown.
@@ -1,4 +1,4 @@
|
|||||||
<div class="control-icon" data-action="lethal-luck-grit-hud">
|
<div class="control-icon lethal-luck-grit-hud" data-action="lethal-luck-grit-hud">
|
||||||
<i class="fa-solid fa-dice" title="Adjust Luck/Grit" style="width:36px;height:36px;display:flex;align-items:center;justify-content:center;font-size:18px;cursor:pointer"></i>
|
<i class="fa-solid fa-dice" title="Adjust Luck/Grit" style="width:36px;height:36px;display:flex;align-items:center;justify-content:center;font-size:18px;cursor:pointer"></i>
|
||||||
|
|
||||||
<div class="luck-grit-wrap luck-grit-hud-disabled">
|
<div class="luck-grit-wrap luck-grit-hud-disabled">
|
||||||
|
|||||||
Reference in New Issue
Block a user