refactor: remove D30 choice dialog, extract defense reaction buttons, fix bugs
Release Creation / build (release) Successful in 45s
Release Creation / build (release) Successful in 45s
- Remove D30 choice dialog — auto-roll bonus dice, flag special effects - Fix d30ChangedAttack infinite loop in defense do-while (missing reset) - Fix chat button dataset attributes (rollType/rollTarget/rollAvantage) - Extract buildDefenseReactionButtons from both defense loops - Merge Aether/Grace deduction via _deductResourceOnCast helper - Extract HP HUD toggling (_toggleHudWraps/_disableHudWraps) - Fix SYSTEM.EQUIPMENT_CATEGORIES typo in equipment model - Add missing imports to combat.mjs - Remove dead d30Auto branches, _buildSpecialLabel, d30-special-choice.hbs
This commit is contained in:
+39
-149
@@ -14,12 +14,12 @@ Hooks.on("renderChatMessageHTML", (message, html, data) => {
|
||||
} else {
|
||||
for (const btn of html.querySelectorAll(".ask-roll-dice")) {
|
||||
btn.addEventListener("click", () => {
|
||||
const type = btn.dataset.type
|
||||
const value = btn.dataset.value
|
||||
const avantage = btn.dataset.avantage ?? "="
|
||||
const type = btn.dataset.rollType
|
||||
const value = btn.dataset.rollTarget
|
||||
const avantage = btn.dataset.rollAvantage ?? "normal"
|
||||
const character = game.user.character
|
||||
if (type === SYSTEM.ROLL_TYPE.RESOURCE) character.rollResource(value)
|
||||
else if (type === SYSTEM.ROLL_TYPE.SAVE) character.rollSave(value, avantage)
|
||||
if (type === "resource") character.rollResource(value)
|
||||
else if (type === "save") character.rollSave(value, avantage)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -453,6 +453,7 @@ Hooks.on("createChatMessage", async (message) => {
|
||||
mulliganRestart = false
|
||||
defenderHandledBonus = false
|
||||
attackerHandledBonus = false
|
||||
d30ChangedAttack = false
|
||||
|
||||
// ── D30 bonus dice (defense) — resolved before grit/luck/shield ───────
|
||||
if (defenseD30message && !defenseD30Processed && isPrimaryController(defender) && !attackerIsCrossClient) {
|
||||
@@ -463,10 +464,6 @@ Hooks.on("createChatMessage", async (message) => {
|
||||
await createReactionMessage(defender, {type:"d30Bonus", actorName:defenderName, value:d30Result.modifier, side:"defense"})
|
||||
}
|
||||
}
|
||||
if (d30Result.specialEffect === "auto") {
|
||||
defenseRoll = attackRollFinal + 1 // auto-block
|
||||
await createReactionMessage(defender, {type:"d30Auto", actorName:defenderName, specialName:d30Result.specialName||"Special Defense", side:"defense"})
|
||||
}
|
||||
if (d30Result.specialEffect === "flag") {
|
||||
await createReactionMessage(defender, {type:"d30Flag", actorName:defenderName, specialName:d30Result.specialName||"Special Effect"})
|
||||
}
|
||||
@@ -483,66 +480,7 @@ Hooks.on("createChatMessage", async (message) => {
|
||||
// create the comparison message with the updated attack roll.
|
||||
if (defender && defenseRoll < attackRollFinal && isPrimaryController(defender) && !isSpellOrMiracle && !attackerIsCrossClient && !d30PendingFromGM) {
|
||||
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"
|
||||
})
|
||||
}
|
||||
|
||||
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 (Mulligan)",
|
||||
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"
|
||||
})
|
||||
}
|
||||
|
||||
buttons.push({
|
||||
action: "continue",
|
||||
label: "Continue (no defense bonus)",
|
||||
icon: "fa-solid fa-forward",
|
||||
callback: () => "continue"
|
||||
})
|
||||
const buttons = LethalFantasyUtils.buildDefenseReactionButtons(defender, { canRerollDefense, shieldData, canShieldReact, canAdHocShield })
|
||||
|
||||
const dialogContent = await foundry.applications.handlebars.renderTemplate("systems/fvtt-lethal-fantasy/templates/dialogs/defense-reaction.hbs", {
|
||||
attackerName,
|
||||
@@ -569,7 +507,7 @@ Hooks.on("createChatMessage", async (message) => {
|
||||
if (choice === "grit") {
|
||||
const bonusRoll = await LethalFantasyUtils.rollBonusDie("1d6", defender)
|
||||
defenseRoll += bonusRoll
|
||||
await defender.update({ "system.grit.current": currentGrit - 1 })
|
||||
await defender.update({ "system.grit.current": Math.max(0, (Number(defender.system?.grit?.current) || 0) - 1) })
|
||||
await createReactionMessage(defender, {type:"grit", actorName:defenderName, resource:"Grit", value:bonusRoll, side:"defense"})
|
||||
continue
|
||||
}
|
||||
@@ -577,7 +515,7 @@ Hooks.on("createChatMessage", async (message) => {
|
||||
if (choice === "luck") {
|
||||
const bonusRoll = await LethalFantasyUtils.rollBonusDie("1d6", defender)
|
||||
defenseRoll += bonusRoll
|
||||
await defender.update({ "system.luck.current": currentLuck - 1 })
|
||||
await defender.update({ "system.luck.current": Math.max(0, (Number(defender.system?.luck?.current) || 0) - 1) })
|
||||
await createReactionMessage(defender, {type:"luck", actorName:defenderName, resource:"Luck", value:bonusRoll, side:"defense"})
|
||||
continue
|
||||
}
|
||||
@@ -675,12 +613,6 @@ Hooks.on("createChatMessage", async (message) => {
|
||||
await createReactionMessage(attacker, {type:"d30Bonus", actorName:attackerName, value:d30Result.modifier, side:"attack"})
|
||||
}
|
||||
}
|
||||
if (d30Result.specialEffect === "auto") {
|
||||
attackRollFinal = defenseRoll + 1 // auto-hit
|
||||
if (canDialog) {
|
||||
await createReactionMessage(attacker, {type:"d30Auto", actorName:attackerName, specialName:d30Result.specialName||"Special Strike", side:"attack"})
|
||||
}
|
||||
}
|
||||
if (d30Result.specialEffect === "flag" && canDialog) {
|
||||
await createReactionMessage(attacker, {type:"d30Flag", actorName:attackerName, specialName:d30Result.specialName||"Special Effect"})
|
||||
}
|
||||
@@ -901,93 +833,51 @@ Hooks.on("createChatMessage", async (message) => {
|
||||
}
|
||||
})
|
||||
|
||||
// Hook: deduct aether when a spell-attack or spell-power roll is posted to chat
|
||||
Hooks.on("createChatMessage", async (message) => {
|
||||
if (!["spell-attack", "spell-power"].includes(message.rolls[0]?.options?.rollType)) return
|
||||
|
||||
async function _deductResourceOnCast(message, rollTypes, itemType, costFn, resourceField, templateType) {
|
||||
if (!rollTypes.includes(message.rolls[0]?.options?.rollType)) return
|
||||
const actorId = message.rolls[0]?.options?.actorId
|
||||
if (!actorId) return
|
||||
const actor = game.actors.get(actorId)
|
||||
if (!actor) return
|
||||
|
||||
// Only the primary controller (player owner or GM) handles this
|
||||
const activePlayerOwners = game.users.filter(u => u.active && !u.isGM && actor.testUserPermission(u, "OWNER"))
|
||||
const isPrimary = activePlayerOwners.length > 0
|
||||
? activePlayerOwners[0].id === game.user.id
|
||||
: game.user.isGM
|
||||
if (!isPrimary) return
|
||||
if (!isPrimaryController(actor)) return
|
||||
|
||||
const rollTarget = message.rolls[0]?.options?.rollTarget
|
||||
const spellId = rollTarget?.id || rollTarget?._id
|
||||
const spell = spellId ? actor.items.get(spellId) : null
|
||||
if (!spell || spell.type !== "spell") return
|
||||
const itemId = rollTarget?.id || rollTarget?._id
|
||||
const item = itemId ? actor.items.get(itemId) : null
|
||||
if (!item || item.type !== itemType) return
|
||||
|
||||
const damageTier = message.rolls[0]?.options?.damageTier || "standard"
|
||||
const tierCostMap = { standard: "cost", overpowered: "costOverpowered", overpowered2: "costOverpowered2" }
|
||||
const costField = tierCostMap[damageTier] || "cost"
|
||||
const cost = Number(spell.system?.[costField]) || 0
|
||||
const cost = costFn(item, damageTier)
|
||||
if (cost <= 0) return
|
||||
|
||||
const currentAether = Number(actor.system.aetherPoints?.value) || 0
|
||||
const newAether = Math.max(0, currentAether - cost)
|
||||
await actor.update({ "system.aetherPoints.value": newAether })
|
||||
const current = Number(foundry.utils.getProperty(actor.system, resourceField)) || 0
|
||||
const newValue = Math.max(0, current - cost)
|
||||
await actor.update({ [`system.${resourceField}`]: newValue })
|
||||
|
||||
const tierLabel = damageTier === "standard" ? "" : ` (${damageTier})`
|
||||
const aetherContent = await foundry.applications.handlebars.renderTemplate("systems/fvtt-lethal-fantasy/templates/chat/reaction-message.hbs", {
|
||||
type: "aetherSpend",
|
||||
actorName: actor.name,
|
||||
spellName: spell.name,
|
||||
tierLabel,
|
||||
value: cost,
|
||||
oldValue: currentAether,
|
||||
newValue: newAether
|
||||
const content = await foundry.applications.handlebars.renderTemplate("systems/fvtt-lethal-fantasy/templates/chat/reaction-message.hbs", {
|
||||
type: templateType, actorName: actor.name, spellName: item.name, tierLabel,
|
||||
value: cost, oldValue: current, newValue
|
||||
})
|
||||
await ChatMessage.create({
|
||||
content: aetherContent,
|
||||
speaker: ChatMessage.getSpeaker({ actor })
|
||||
})
|
||||
})
|
||||
await ChatMessage.create({ content, speaker: ChatMessage.getSpeaker({ actor }) })
|
||||
}
|
||||
|
||||
// Hook: deduct aether when a spell-attack or spell-power roll is posted to chat
|
||||
Hooks.on("createChatMessage", (message) => _deductResourceOnCast(message,
|
||||
["spell-attack", "spell-power"], "spell",
|
||||
(item, tier) => {
|
||||
const m = { standard: "cost", overpowered: "costOverpowered", overpowered2: "costOverpowered2" }
|
||||
return Number(item.system?.[m[tier] || "cost"]) || 0
|
||||
},
|
||||
"aetherPoints.value", "aetherSpend"
|
||||
))
|
||||
|
||||
// Hook: deduct grace when a miracle-attack or miracle-power roll is posted to chat
|
||||
Hooks.on("createChatMessage", async (message) => {
|
||||
if (!["miracle-attack", "miracle-power"].includes(message.rolls[0]?.options?.rollType)) return
|
||||
|
||||
const actorId = message.rolls[0]?.options?.actorId
|
||||
if (!actorId) return
|
||||
const actor = game.actors.get(actorId)
|
||||
if (!actor) return
|
||||
|
||||
const activePlayerOwners = game.users.filter(u => u.active && !u.isGM && actor.testUserPermission(u, "OWNER"))
|
||||
const isPrimary = activePlayerOwners.length > 0
|
||||
? activePlayerOwners[0].id === game.user.id
|
||||
: game.user.isGM
|
||||
if (!isPrimary) return
|
||||
|
||||
const rollTarget = message.rolls[0]?.options?.rollTarget
|
||||
const miracleId = rollTarget?.id || rollTarget?._id
|
||||
const miracle = miracleId ? actor.items.get(miracleId) : null
|
||||
if (!miracle || miracle.type !== "miracle") return
|
||||
|
||||
const cost = Number(miracle.system?.level) || 0
|
||||
if (cost <= 0) return
|
||||
|
||||
const currentGrace = Number(actor.system.divinityPoints?.value) || 0
|
||||
const newGrace = Math.max(0, currentGrace - cost)
|
||||
await actor.update({ "system.divinityPoints.value": newGrace })
|
||||
|
||||
const graceContent = await foundry.applications.handlebars.renderTemplate("systems/fvtt-lethal-fantasy/templates/chat/reaction-message.hbs", {
|
||||
type: "graceSpend",
|
||||
actorName: actor.name,
|
||||
spellName: miracle.name,
|
||||
value: cost,
|
||||
oldValue: currentGrace,
|
||||
newValue: newGrace
|
||||
})
|
||||
await ChatMessage.create({
|
||||
content: graceContent,
|
||||
speaker: ChatMessage.getSpeaker({ actor })
|
||||
})
|
||||
})
|
||||
Hooks.on("createChatMessage", (message) => _deductResourceOnCast(message,
|
||||
["miracle-attack", "miracle-power"], "miracle",
|
||||
(item) => Number(item.system?.level) || 0,
|
||||
"divinityPoints.value", "graceSpend"
|
||||
))
|
||||
|
||||
// Hook pour appliquer automatiquement les dégâts si une cible est définie
|
||||
Hooks.on("createChatMessage", async (message) => {
|
||||
|
||||
Reference in New Issue
Block a user