Add Roll Damage button to spell chat cards
Spell damage is now rolled separately via button click instead of automatically, matching the attack roll behavior. Includes spell chat card styling fixes for proper header layout and damage display. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
a30cc62957
commit
7f06ec229a
@ -472,15 +472,8 @@ export default class SpellCastDialog extends VagabondRollDialog {
|
|||||||
modifier: this.rollConfig.modifier,
|
modifier: this.rollConfig.modifier,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Roll damage if the cast succeeded and spell deals damage
|
// Damage is rolled separately via button click (like attacks)
|
||||||
let damageResult = null;
|
// Just pass null for damageResult - the button will handle it
|
||||||
if (result.success && spell.system.isDamaging() && this.castConfig.damageDice > 0) {
|
|
||||||
const damageFormula = this._getDamageFormula();
|
|
||||||
damageResult = await damageRoll(damageFormula, {
|
|
||||||
isCrit: result.isCrit,
|
|
||||||
rollData: this.actor.getRollData(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Spend mana (regardless of success - mana is spent on attempt)
|
// Spend mana (regardless of success - mana is spent on attempt)
|
||||||
await this.actor.update({
|
await this.actor.update({
|
||||||
@ -513,19 +506,19 @@ export default class SpellCastDialog extends VagabondRollDialog {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send to chat
|
// Send to chat (damage is rolled separately via button click)
|
||||||
await this._sendToChat(result, damageResult);
|
await this._sendToChat(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send the spell cast result to chat.
|
* Send the spell cast result to chat.
|
||||||
*
|
*
|
||||||
* @param {VagabondRollResult} result - The casting skill check result
|
* @param {VagabondRollResult} result - The casting skill check result
|
||||||
* @param {Roll|null} damageResult - The damage roll (if applicable)
|
* @param {Roll|null} damageResult - The damage roll (if already rolled, e.g., for updates)
|
||||||
* @returns {Promise<ChatMessage>}
|
* @returns {Promise<ChatMessage>}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
async _sendToChat(result, damageResult) {
|
async _sendToChat(result, damageResult = null) {
|
||||||
const spell = this.spell;
|
const spell = this.spell;
|
||||||
const castingSkill = this._getCastingSkill();
|
const castingSkill = this._getCastingSkill();
|
||||||
const skillConfig = CONFIG.VAGABOND?.skills?.[castingSkill];
|
const skillConfig = CONFIG.VAGABOND?.skills?.[castingSkill];
|
||||||
@ -571,11 +564,14 @@ export default class SpellCastDialog extends VagabondRollDialog {
|
|||||||
netFavorHinder: this.netFavorHinder,
|
netFavorHinder: this.netFavorHinder,
|
||||||
favorSources: this.rollConfig.autoFavorHinder.favorSources,
|
favorSources: this.rollConfig.autoFavorHinder.favorSources,
|
||||||
hinderSources: this.rollConfig.autoFavorHinder.hinderSources,
|
hinderSources: this.rollConfig.autoFavorHinder.hinderSources,
|
||||||
// Damage info
|
// Damage info (only present if damage was rolled)
|
||||||
hasDamage: !!damageResult,
|
hasDamage: !!damageResult,
|
||||||
damageTotal: damageResult?.total,
|
damageTotal: damageResult?.total,
|
||||||
damageFormula: damageResult?.formula,
|
damageFormula: damageResult?.formula,
|
||||||
damageDice: this.castConfig.damageDice,
|
damageDice: this.castConfig.damageDice,
|
||||||
|
// Show damage button if cast succeeded and there's a damage formula to roll
|
||||||
|
pendingDamageFormula: this._getDamageFormula(),
|
||||||
|
showDamageButton: result.success && !!this._getDamageFormula() && !damageResult,
|
||||||
// Effect info
|
// Effect info
|
||||||
includeEffect: this.castConfig.includeEffect,
|
includeEffect: this.castConfig.includeEffect,
|
||||||
hasEffect: Boolean(spell.system.effect && spell.system.effect.trim()),
|
hasEffect: Boolean(spell.system.effect && spell.system.effect.trim()),
|
||||||
@ -591,13 +587,30 @@ export default class SpellCastDialog extends VagabondRollDialog {
|
|||||||
const rolls = [result.roll];
|
const rolls = [result.roll];
|
||||||
if (damageResult) rolls.push(damageResult);
|
if (damageResult) rolls.push(damageResult);
|
||||||
|
|
||||||
// Create the chat message
|
// Create the chat message with flags for later damage rolling
|
||||||
|
const damageFormula = this._getDamageFormula();
|
||||||
const chatData = {
|
const chatData = {
|
||||||
user: game.user.id,
|
user: game.user.id,
|
||||||
speaker: ChatMessage.getSpeaker({ actor: this.actor }),
|
speaker: ChatMessage.getSpeaker({ actor: this.actor }),
|
||||||
content,
|
content,
|
||||||
rolls,
|
rolls,
|
||||||
sound: CONFIG.sounds.dice,
|
sound: CONFIG.sounds.dice,
|
||||||
|
flags: {
|
||||||
|
vagabond: {
|
||||||
|
type: "spell-cast",
|
||||||
|
actorId: this.actor.id,
|
||||||
|
spellId: spell.id,
|
||||||
|
spellName: spell.name,
|
||||||
|
damageFormula,
|
||||||
|
damageType: spell.system.damageType,
|
||||||
|
damageTypeLabel: game.i18n.localize(
|
||||||
|
CONFIG.VAGABOND?.damageTypes?.[spell.system.damageType] || spell.system.damageType
|
||||||
|
),
|
||||||
|
isCrit: result.isCrit,
|
||||||
|
success: result.success,
|
||||||
|
damageRolled: !!damageResult,
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return ChatMessage.create(chatData);
|
return ChatMessage.create(chatData);
|
||||||
|
|||||||
@ -653,6 +653,189 @@ async function _extractAttackDataFromMessage(message) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Spell Damage Roll Handling */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle clicks on "Roll Damage" buttons in spell chat cards.
|
||||||
|
* Rolls damage using the stored spell data and updates the message.
|
||||||
|
*/
|
||||||
|
Hooks.on("renderChatMessage", (message, html) => {
|
||||||
|
// Only handle spell-cast messages
|
||||||
|
const flags = message.flags?.vagabond;
|
||||||
|
if (!flags || flags.type !== "spell-cast") return;
|
||||||
|
|
||||||
|
// Find damage roll buttons in this message
|
||||||
|
html.find(".roll-damage-btn").on("click", async (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const button = event.currentTarget;
|
||||||
|
|
||||||
|
// Verify damage hasn't been rolled yet
|
||||||
|
if (flags.damageRolled) {
|
||||||
|
ui.notifications.warn("Damage has already been rolled for this spell");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable button immediately to prevent double-clicks
|
||||||
|
button.disabled = true;
|
||||||
|
button.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> Rolling...';
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get the actor for roll data
|
||||||
|
const actor = game.actors.get(flags.actorId);
|
||||||
|
if (!actor) {
|
||||||
|
ui.notifications.error("Could not find actor for damage roll");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Import damageRoll function
|
||||||
|
const { damageRoll } = await import("./dice/rolls.mjs");
|
||||||
|
|
||||||
|
// Roll damage
|
||||||
|
const roll = await damageRoll(flags.damageFormula, {
|
||||||
|
isCrit: flags.isCrit,
|
||||||
|
rollData: actor.getRollData(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Render updated content with damage
|
||||||
|
const content = await renderTemplate("systems/vagabond/templates/chat/spell-cast.hbs", {
|
||||||
|
// Original spell data from message content
|
||||||
|
...(await _extractSpellDataFromMessage(message)),
|
||||||
|
// Damage data
|
||||||
|
hasDamage: true,
|
||||||
|
damageTotal: roll.total,
|
||||||
|
damageFormula: roll.formula,
|
||||||
|
isCrit: flags.isCrit,
|
||||||
|
showDamageButton: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update the message with damage rolled
|
||||||
|
await message.update({
|
||||||
|
content,
|
||||||
|
rolls: [...message.rolls, roll],
|
||||||
|
"flags.vagabond.damageRolled": true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Play dice sound
|
||||||
|
AudioHelper.play({ src: CONFIG.sounds.dice }, true);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Vagabond RPG | Error rolling spell damage:", error);
|
||||||
|
ui.notifications.error("Failed to roll damage");
|
||||||
|
button.disabled = false;
|
||||||
|
button.innerHTML = '<i class="fa-solid fa-burst"></i> Roll Damage';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract spell data from an existing chat message for re-rendering.
|
||||||
|
* @param {ChatMessage} message - The chat message
|
||||||
|
* @returns {Promise<Object>} Template data extracted from the message
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
async function _extractSpellDataFromMessage(message) {
|
||||||
|
const flags = message.flags?.vagabond || {};
|
||||||
|
const content = message.content;
|
||||||
|
|
||||||
|
// Parse values from the message HTML
|
||||||
|
const parser = new DOMParser();
|
||||||
|
const doc = parser.parseFromString(content, "text/html");
|
||||||
|
|
||||||
|
// Extract spell info
|
||||||
|
const spellImg = doc.querySelector(".spell-icon")?.getAttribute("src") || "";
|
||||||
|
const spellName = doc.querySelector(".spell-name")?.textContent || flags.spellName || "Unknown";
|
||||||
|
const castingSkillLabel = doc.querySelector(".casting-skill-badge")?.textContent || "";
|
||||||
|
|
||||||
|
// Extract cast config
|
||||||
|
const deliveryLabel =
|
||||||
|
doc.querySelector(".config-item.delivery .value")?.textContent?.trim() || "";
|
||||||
|
const durationLabel =
|
||||||
|
doc.querySelector(".config-item.duration .value")?.textContent?.trim() || "";
|
||||||
|
const manaCost = doc.querySelector(".config-item.mana-cost .value")?.textContent?.trim() || "0";
|
||||||
|
|
||||||
|
// Extract roll result
|
||||||
|
const total = doc.querySelector(".roll-total")?.textContent || "0";
|
||||||
|
const status = doc.querySelector(".roll-status .status")?.classList;
|
||||||
|
const isCrit = status?.contains("critical") || false;
|
||||||
|
const isFumble = status?.contains("fumble") || false;
|
||||||
|
const success = status?.contains("success") || status?.contains("critical") || false;
|
||||||
|
|
||||||
|
// Extract formula and breakdown
|
||||||
|
const formula = doc.querySelector(".roll-formula .value")?.textContent || "";
|
||||||
|
const d20Result = doc.querySelector(".d20-result")?.textContent?.replace(/[^\d]/g, "") || "0";
|
||||||
|
const favorDieEl = doc.querySelector(".favor-die");
|
||||||
|
const favorDie = favorDieEl?.textContent?.replace(/[^\d-]/g, "") || null;
|
||||||
|
const netFavorHinder = favorDieEl?.classList.contains("favor")
|
||||||
|
? 1
|
||||||
|
: favorDieEl?.classList.contains("hinder")
|
||||||
|
? -1
|
||||||
|
: 0;
|
||||||
|
const modifier = doc.querySelector(".modifier")?.textContent?.replace(/[^\d-]/g, "") || null;
|
||||||
|
|
||||||
|
// Extract difficulty info
|
||||||
|
const difficulty = doc.querySelector(".difficulty .value")?.textContent || "10";
|
||||||
|
const critThreshold =
|
||||||
|
doc.querySelector(".crit-threshold .value")?.textContent?.replace(/[^\d]/g, "") || "20";
|
||||||
|
|
||||||
|
// Extract spell effect
|
||||||
|
const effectText = doc.querySelector(".spell-effect-section .effect-text")?.innerHTML || "";
|
||||||
|
const critEffectText = doc.querySelector(".spell-effect-section .crit-text")?.innerHTML || "";
|
||||||
|
const hasEffect = !!effectText;
|
||||||
|
const includeEffect = hasEffect;
|
||||||
|
|
||||||
|
// Extract focus indicator
|
||||||
|
const isFocus = !!doc.querySelector(".focus-indicator");
|
||||||
|
|
||||||
|
// Extract favor/hinder sources
|
||||||
|
const favorSourcesEl = doc.querySelector(".favor-sources span");
|
||||||
|
const hinderSourcesEl = doc.querySelector(".hinder-sources span");
|
||||||
|
const favorSources =
|
||||||
|
favorSourcesEl?.textContent
|
||||||
|
?.replace(/^[^:]+:\s*/, "")
|
||||||
|
.split(", ")
|
||||||
|
.filter(Boolean) || [];
|
||||||
|
const hinderSources =
|
||||||
|
hinderSourcesEl?.textContent
|
||||||
|
?.replace(/^[^:]+:\s*/, "")
|
||||||
|
.split(", ")
|
||||||
|
.filter(Boolean) || [];
|
||||||
|
|
||||||
|
return {
|
||||||
|
spell: {
|
||||||
|
id: flags.spellId,
|
||||||
|
name: spellName,
|
||||||
|
img: spellImg,
|
||||||
|
damageType: flags.damageType,
|
||||||
|
damageTypeLabel: flags.damageTypeLabel,
|
||||||
|
effect: effectText,
|
||||||
|
critEffect: critEffectText,
|
||||||
|
isDamaging: !!flags.damageFormula,
|
||||||
|
},
|
||||||
|
castingSkillLabel,
|
||||||
|
deliveryLabel,
|
||||||
|
durationLabel,
|
||||||
|
isFocus,
|
||||||
|
manaCost,
|
||||||
|
total,
|
||||||
|
d20Result,
|
||||||
|
favorDie: favorDie ? parseInt(favorDie) : null,
|
||||||
|
modifier: modifier ? parseInt(modifier) : null,
|
||||||
|
formula,
|
||||||
|
difficulty,
|
||||||
|
critThreshold,
|
||||||
|
success,
|
||||||
|
isCrit,
|
||||||
|
isFumble,
|
||||||
|
netFavorHinder,
|
||||||
|
favorSources,
|
||||||
|
hinderSources,
|
||||||
|
hasEffect,
|
||||||
|
includeEffect,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
/* Quench Test Registration */
|
/* Quench Test Registration */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|||||||
@ -545,8 +545,36 @@
|
|||||||
.vagabond.chat-card.spell-card,
|
.vagabond.chat-card.spell-card,
|
||||||
.vagabond.chat-card.spell-cast {
|
.vagabond.chat-card.spell-cast {
|
||||||
.card-header {
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: $spacing-3;
|
||||||
|
|
||||||
|
.spell-icon {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: $radius-sm;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-text {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0; // Prevent flex overflow
|
||||||
|
|
||||||
.spell-name {
|
.spell-name {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
font-size: $font-size-base;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.casting-skill-badge {
|
||||||
|
font-size: $font-size-xs;
|
||||||
|
padding: $spacing-1 $spacing-2;
|
||||||
|
background-color: rgba(112, 80, 176, 0.2);
|
||||||
|
color: var(--color-reason);
|
||||||
|
border-radius: $radius-full;
|
||||||
|
font-weight: $font-weight-medium;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mana-cost-badge {
|
.mana-cost-badge {
|
||||||
@ -563,6 +591,37 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cast configuration display (delivery, duration, mana)
|
||||||
|
.cast-config {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: $spacing-2;
|
||||||
|
margin: $spacing-2 $spacing-3;
|
||||||
|
padding: $spacing-2;
|
||||||
|
background-color: var(--color-bg-tertiary);
|
||||||
|
border-radius: $radius-md;
|
||||||
|
font-size: $font-size-sm;
|
||||||
|
|
||||||
|
.config-item {
|
||||||
|
display: flex;
|
||||||
|
gap: $spacing-1;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
font-weight: $font-weight-medium;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
|
||||||
|
i {
|
||||||
|
color: var(--color-warning);
|
||||||
|
margin-left: $spacing-1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.spell-effect {
|
.spell-effect {
|
||||||
padding: $spacing-3;
|
padding: $spacing-3;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
@ -572,6 +631,109 @@
|
|||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Spell effect section in chat (different from .spell-effect above)
|
||||||
|
.spell-effect-section {
|
||||||
|
margin: $spacing-3;
|
||||||
|
padding: $spacing-3;
|
||||||
|
background-color: var(--color-bg-secondary);
|
||||||
|
border-radius: $radius-md;
|
||||||
|
border-left: 3px solid var(--color-accent-primary);
|
||||||
|
|
||||||
|
.effect-header {
|
||||||
|
@include flex-center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: $spacing-2;
|
||||||
|
margin-bottom: $spacing-2;
|
||||||
|
font-weight: $font-weight-semibold;
|
||||||
|
color: var(--color-accent-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.effect-text {
|
||||||
|
font-size: $font-size-sm;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crit-effect {
|
||||||
|
margin-top: $spacing-2;
|
||||||
|
padding-top: $spacing-2;
|
||||||
|
border-top: 1px solid var(--color-border);
|
||||||
|
|
||||||
|
.crit-label {
|
||||||
|
font-weight: $font-weight-semibold;
|
||||||
|
color: var(--color-warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
.crit-text {
|
||||||
|
font-size: $font-size-sm;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Damage section (similar to attack-roll)
|
||||||
|
.damage-section {
|
||||||
|
margin: $spacing-3;
|
||||||
|
padding: $spacing-3;
|
||||||
|
background-color: rgba(201, 68, 68, 0.1);
|
||||||
|
border: 1px solid rgba(201, 68, 68, 0.3);
|
||||||
|
border-radius: $radius-md;
|
||||||
|
|
||||||
|
&.critical {
|
||||||
|
background-color: rgba(212, 163, 44, 0.15);
|
||||||
|
border-color: var(--color-warning);
|
||||||
|
|
||||||
|
.damage-total {
|
||||||
|
color: var(--color-warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.damage-header {
|
||||||
|
@include flex-center;
|
||||||
|
gap: $spacing-2;
|
||||||
|
font-weight: $font-weight-semibold;
|
||||||
|
margin-bottom: $spacing-2;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
|
||||||
|
i {
|
||||||
|
color: var(--color-danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
.crit-label {
|
||||||
|
color: var(--color-warning);
|
||||||
|
font-size: $font-size-sm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.damage-result {
|
||||||
|
@include flex-center;
|
||||||
|
gap: $spacing-2;
|
||||||
|
|
||||||
|
.damage-total {
|
||||||
|
font-family: $font-family-header;
|
||||||
|
font-size: $font-size-3xl;
|
||||||
|
font-weight: $font-weight-bold;
|
||||||
|
color: var(--color-danger);
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.damage-type {
|
||||||
|
font-size: $font-size-sm;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.damage-formula {
|
||||||
|
@include flex-center;
|
||||||
|
margin-top: $spacing-2;
|
||||||
|
font-family: $font-family-mono;
|
||||||
|
font-size: $font-size-sm;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.spell-meta {
|
.spell-meta {
|
||||||
@include grid(2, $spacing-2);
|
@include grid(2, $spacing-2);
|
||||||
margin: 0 $spacing-3 $spacing-3;
|
margin: 0 $spacing-3 $spacing-3;
|
||||||
@ -621,6 +783,54 @@
|
|||||||
font-size: $font-size-sm;
|
font-size: $font-size-sm;
|
||||||
color: var(--color-warning);
|
color: var(--color-warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Roll Damage button (same style as attack-roll)
|
||||||
|
.card-buttons {
|
||||||
|
.roll-damage-btn {
|
||||||
|
@include flex-center;
|
||||||
|
gap: $spacing-2;
|
||||||
|
width: 100%;
|
||||||
|
padding: $spacing-2 $spacing-3;
|
||||||
|
background-color: rgba(201, 68, 68, 0.2);
|
||||||
|
border: 1px solid var(--color-danger);
|
||||||
|
border-radius: $radius-md;
|
||||||
|
color: var(--color-danger);
|
||||||
|
font-weight: $font-weight-semibold;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:hover:not(:disabled) {
|
||||||
|
background-color: rgba(201, 68, 68, 0.3);
|
||||||
|
border-color: var(--color-danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.7;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.critical {
|
||||||
|
background-color: rgba(212, 163, 44, 0.2);
|
||||||
|
border-color: var(--color-warning);
|
||||||
|
color: var(--color-warning);
|
||||||
|
|
||||||
|
&:hover:not(:disabled) {
|
||||||
|
background-color: rgba(212, 163, 44, 0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.crit-label {
|
||||||
|
font-size: $font-size-sm;
|
||||||
|
color: var(--color-warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
.damage-preview {
|
||||||
|
font-family: $font-family-mono;
|
||||||
|
font-size: $font-size-sm;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Animations
|
// Animations
|
||||||
|
|||||||
@ -106,7 +106,7 @@
|
|||||||
{{/if}}
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{!-- Damage Section (if hit and dealing damage) --}}
|
{{!-- Damage Section (if damage was rolled) --}}
|
||||||
{{#if hasDamage}}
|
{{#if hasDamage}}
|
||||||
<div class="damage-section {{#if isCrit}}critical{{/if}}">
|
<div class="damage-section {{#if isCrit}}critical{{/if}}">
|
||||||
<div class="damage-header">
|
<div class="damage-header">
|
||||||
@ -126,6 +126,20 @@
|
|||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
{{!-- Roll Damage Button (if cast succeeded but damage not yet rolled) --}}
|
||||||
|
{{#if showDamageButton}}
|
||||||
|
<div class="card-buttons">
|
||||||
|
<button type="button" class="roll-damage-btn {{#if isCrit}}critical{{/if}}">
|
||||||
|
<i class="fa-solid fa-burst"></i>
|
||||||
|
{{localize "VAGABOND.RollDamage"}}
|
||||||
|
{{#if isCrit}}
|
||||||
|
<span class="crit-label">({{localize "VAGABOND.Critical"}}!)</span>
|
||||||
|
{{/if}}
|
||||||
|
<span class="damage-preview">({{pendingDamageFormula}})</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
{{!-- Focus Indicator --}}
|
{{!-- Focus Indicator --}}
|
||||||
{{#if isFocus}}
|
{{#if isFocus}}
|
||||||
{{#if success}}
|
{{#if success}}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user