Compare commits

...

7 Commits

Author SHA1 Message Date
uberwald 4724cdf2bb VArious fixes for rolls and ranged attacks
Release Creation / build (release) Successful in 44s
2026-05-23 09:08:16 +02:00
uberwald 6d06c8ddad Various fixes for spell and ranged attacks 2026-05-23 00:21:05 +02:00
uberwald 2770774aa3 Various fixes for spell and ranged attacks 2026-05-23 00:11:58 +02:00
uberwald e417b61625 Spells fixe
Release Creation / build (release) Successful in 46s
2026-05-20 23:17:07 +02:00
uberwald 9a8d580ef6 Other fixes for damage buttons from chat
Release Creation / build (release) Successful in 53s
2026-05-20 10:53:46 +02:00
uberwald 9ccb0f90f0 Other fixes for damage buttons from chat 2026-05-20 10:53:22 +02:00
uberwald 6cf0880ad3 Enhance spell damage and messages content
Release Creation / build (release) Successful in 44s
2026-05-19 10:52:03 +02:00
38 changed files with 478 additions and 164 deletions
+1 -1
View File
@@ -59,5 +59,5 @@ jobs:
version: ${{github.event.release.tag_name}} version: ${{github.event.release.tag_name}}
manifest: "https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/system.json" manifest: "https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/system.json"
notes: "https://www.uberwald.me/gitea/public/fvtt-lethal-fantasy/raw/branch/main/changelog.md" notes: "https://www.uberwald.me/gitea/public/fvtt-lethal-fantasy/raw/branch/main/changelog.md"
compatibility-minimum: "13" compatibility-minimum: "14"
compatibility-verified: "14" compatibility-verified: "14"
+112 -21
View File
@@ -2027,9 +2027,12 @@ i.lethalfantasy {
background: #4a4940 !important; background: #4a4940 !important;
color: #ffffff !important; color: #ffffff !important;
} }
.lethalfantasy .grit-luck-dialog {
color: var(--color-text-dark-primary, #191813);
}
.lethalfantasy .grit-luck-dialog .combat-status { .lethalfantasy .grit-luck-dialog .combat-status {
padding: 12px; padding: 12px;
background: linear-gradient(to bottom, rgba(42, 41, 32, 0.8) 0%, rgba(26, 25, 16, 0.9) 100%); background: linear-gradient(to bottom, rgba(42, 41, 32, 0.88) 0%, rgba(26, 25, 16, 0.95) 100%);
border: 1px solid rgba(212, 175, 55, 0.5); border: 1px solid rgba(212, 175, 55, 0.5);
border-radius: 6px; border-radius: 6px;
margin-bottom: 16px; margin-bottom: 16px;
@@ -2049,11 +2052,25 @@ i.lethalfantasy {
margin-top: 8px; margin-top: 8px;
} }
.lethalfantasy .grit-luck-dialog .offer-text { .lethalfantasy .grit-luck-dialog .offer-text {
color: #f0e6d2; color: var(--color-text-dark-primary, #191813);
font-size: calc(var(--font-size-standard) * 1); font-size: calc(var(--font-size-standard) * 1);
text-align: center; text-align: center;
font-weight: 600; font-weight: 600;
margin: 0 0 8px 0;
}
.lethalfantasy .grit-luck-dialog .shield-warning {
color: #7a4000;
background: rgba(255, 160, 0, 0.12);
border: 1px solid rgba(255, 160, 0, 0.4);
border-radius: 5px;
font-size: calc(var(--font-size-standard) * 0.88);
padding: 6px 10px;
margin: 0; margin: 0;
text-align: center;
}
.lethalfantasy .grit-luck-dialog .shield-warning i {
color: #c07000;
margin-right: 5px;
} }
.lethalfantasy .attack-result { .lethalfantasy .attack-result {
padding: 16px; padding: 16px;
@@ -2144,31 +2161,66 @@ i.lethalfantasy {
color: #d4af37; color: #d4af37;
} }
.lethalfantasy .attack-result .attack-result-damage { .lethalfantasy .attack-result .attack-result-damage {
display: flex; display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px; gap: 8px;
justify-content: center; }
.lethalfantasy .attack-result .attack-result-damage.single-btn {
grid-template-columns: 1fr;
max-width: 280px;
margin: 0 auto;
}
.lethalfantasy .attack-result .attack-result-damage.spell-damage {
grid-template-columns: 1fr;
width: 100%;
} }
.lethalfantasy .attack-result .attack-result-damage .roll-damage-btn { .lethalfantasy .attack-result .attack-result-damage .roll-damage-btn {
padding: 10px 16px; padding: 10px 14px;
background: linear-gradient(to bottom, #8b0000 0%, #660000 100%); background: linear-gradient(to bottom, #8b0000 0%, #660000 100%);
border: 1px solid #ff0000; border: 1px solid #4b0000;
border-radius: 6px; border-radius: 6px;
color: #f0e6d2; color: #f0e6d2;
font-weight: 600; font-weight: 600;
font-size: calc(var(--font-size-standard) * 0.9);
text-align: center;
white-space: nowrap;
cursor: pointer; cursor: pointer;
transition: all 0.2s ease; transition: all 0.2s ease;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); box-shadow: 0 3px 6px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.1);
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
position: relative;
overflow: hidden;
}
.lethalfantasy .attack-result .attack-result-damage .roll-damage-btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.15), transparent);
transition: left 0.5s;
}
.lethalfantasy .attack-result .attack-result-damage .roll-damage-btn:hover::before {
left: 100%;
}
.lethalfantasy .attack-result .attack-result-damage .roll-damage-btn i {
font-size: calc(var(--font-size-standard) * 1.1);
filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5));
flex-shrink: 0;
} }
.lethalfantasy .attack-result .attack-result-damage .roll-damage-btn:hover { .lethalfantasy .attack-result .attack-result-damage .roll-damage-btn:hover {
background: linear-gradient(to bottom, #a00000 0%, #7b0000 100%); background: linear-gradient(to bottom, #a00000 0%, #7b0000 100%);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.4); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.15);
transform: translateY(-2px); transform: translateY(-2px);
border-color: #5b0000;
} }
.lethalfantasy .attack-result .attack-result-damage .roll-damage-btn:active { .lethalfantasy .attack-result .attack-result-damage .roll-damage-btn:active {
transform: translateY(0); transform: translateY(0);
} box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4), inset 0 1px 3px rgba(0, 0, 0, 0.3);
.lethalfantasy .attack-result .attack-result-damage .roll-damage-btn i {
margin-right: 6px;
} }
.lethalfantasy .equipment-content { .lethalfantasy .equipment-content {
font-family: var(--font-primary); font-family: var(--font-primary);
@@ -3943,35 +3995,74 @@ i.lethalfantasy {
} }
.message .attack-result .attack-result-damage, .message .attack-result .attack-result-damage,
.attack-result .attack-result-damage { .attack-result .attack-result-damage {
display: flex; display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px; gap: 8px;
justify-content: center; }
.message .attack-result .attack-result-damage.single-btn,
.attack-result .attack-result-damage.single-btn {
grid-template-columns: 1fr;
max-width: 280px;
margin: 0 auto;
}
.message .attack-result .attack-result-damage.spell-damage,
.attack-result .attack-result-damage.spell-damage {
grid-template-columns: 1fr;
width: 100%;
} }
.message .attack-result .attack-result-damage .roll-damage-btn, .message .attack-result .attack-result-damage .roll-damage-btn,
.attack-result .attack-result-damage .roll-damage-btn { .attack-result .attack-result-damage .roll-damage-btn {
padding: 10px 16px; padding: 10px 14px;
background: linear-gradient(to bottom, #8b0000 0%, #660000 100%); background: linear-gradient(to bottom, #8b0000 0%, #660000 100%);
border: 1px solid #ff0000; border: 1px solid #4b0000;
border-radius: 6px; border-radius: 6px;
color: #f0e6d2; color: #f0e6d2;
font-weight: 600; font-weight: 600;
font-size: calc(var(--font-size-standard) * 0.9);
text-align: center;
white-space: nowrap;
cursor: pointer; cursor: pointer;
transition: all 0.2s ease; transition: all 0.2s ease;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); box-shadow: 0 3px 6px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.1);
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
position: relative;
overflow: hidden;
}
.message .attack-result .attack-result-damage .roll-damage-btn::before,
.attack-result .attack-result-damage .roll-damage-btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.15), transparent);
transition: left 0.5s;
}
.message .attack-result .attack-result-damage .roll-damage-btn:hover::before,
.attack-result .attack-result-damage .roll-damage-btn:hover::before {
left: 100%;
}
.message .attack-result .attack-result-damage .roll-damage-btn i,
.attack-result .attack-result-damage .roll-damage-btn i {
font-size: calc(var(--font-size-standard) * 1.1);
filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5));
flex-shrink: 0;
} }
.message .attack-result .attack-result-damage .roll-damage-btn:hover, .message .attack-result .attack-result-damage .roll-damage-btn:hover,
.attack-result .attack-result-damage .roll-damage-btn:hover { .attack-result .attack-result-damage .roll-damage-btn:hover {
background: linear-gradient(to bottom, #a00000 0%, #7b0000 100%); background: linear-gradient(to bottom, #a00000 0%, #7b0000 100%);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.4); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.15);
transform: translateY(-2px); transform: translateY(-2px);
border-color: #5b0000;
} }
.message .attack-result .attack-result-damage .roll-damage-btn:active, .message .attack-result .attack-result-damage .roll-damage-btn:active,
.attack-result .attack-result-damage .roll-damage-btn:active { .attack-result .attack-result-damage .roll-damage-btn:active {
transform: translateY(0); transform: translateY(0);
} box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4), inset 0 1px 3px rgba(0, 0, 0, 0.3);
.message .attack-result .attack-result-damage .roll-damage-btn i,
.attack-result .attack-result-damage .roll-damage-btn i {
margin-right: 6px;
} }
#token-hud .hp-loss-wrap { #token-hud .hp-loss-wrap {
position: absolute; position: absolute;
+19 -5
View File
@@ -230,9 +230,15 @@ Hooks.on("renderChatMessageHTML", (message, html, data) => {
const weaponName = message.rolls[0]?.rollName || "weapon" const weaponName = message.rolls[0]?.rollName || "weapon"
const attackRoll = message.rolls[0]?.rollTotal || 0 const attackRoll = message.rolls[0]?.rollTotal || 0
const defenderName = combatant.name const defenderName = combatant.name
const attackWeaponId = message.rolls[0]?.rollTarget?.weapon?.id || message.rolls[0]?.rollTarget?.weapon?._id
const attackRollType = message.rolls[0]?.type const attackRollType = message.rolls[0]?.type
const attackRollKey = message.rolls[0]?.rollTarget?.rollKey const rollTargetData = message.rolls[0]?.rollTarget
// For spell/miracle attacks, rollTarget IS the spell item (serialised as its data object).
// For weapon attacks, rollTarget is a plain skill+weapon object and weapon.id is the weapon.
const isSpellMiracleAttack = attackRollType === "spell-attack" || attackRollType === "miracle-attack"
const attackWeaponId = isSpellMiracleAttack
? (rollTargetData?._id || rollTargetData?.id)
: (rollTargetData?.weapon?.id || rollTargetData?.weapon?._id)
const attackRollKey = rollTargetData?.rollKey
console.log(`[LF] request-defense-btn | attackRollType=${attackRollType} defender=${defenderName} defenderType=${combatant.actor?.type}`) console.log(`[LF] request-defense-btn | attackRollType=${attackRollType} defender=${defenderName} defenderType=${combatant.actor?.type}`)
const attackD30result = message.rolls[0]?.options?.D30result || null const attackD30result = message.rolls[0]?.options?.D30result || null
const attackD30message = message.rolls[0]?.options?.D30message || null const attackD30message = message.rolls[0]?.options?.D30message || null
@@ -256,6 +262,7 @@ Hooks.on("renderChatMessageHTML", (message, html, data) => {
const isRangedAttack = (rollTargetOptions?.attackMode === "ranged") const isRangedAttack = (rollTargetOptions?.attackMode === "ranged")
|| (attacker?.type === "monster" && attacker.system.attackMode === "ranged") || (attacker?.type === "monster" && attacker.system.attackMode === "ranged")
|| (attackerWeapon?.system?.weaponType === "ranged") || (attackerWeapon?.system?.weaponType === "ranged")
|| (rollTargetOptions?.isRangedAttack === true)
const defenseMsg = { const defenseMsg = {
type: "requestDefense", type: "requestDefense",
@@ -363,7 +370,7 @@ Hooks.on("renderChatMessageHTML", (message, html, data) => {
action: "applyDR", action: "applyDR",
label: game.i18n.localize("LETHALFANTASY.Combat.spellApplyDR"), label: game.i18n.localize("LETHALFANTASY.Combat.spellApplyDR"),
icon: "fa-solid fa-shield", icon: "fa-solid fa-shield",
callback: (event, button, dialog) => Number(dialog.querySelector("[name='manualDr']")?.value) || 0 callback: (event, button) => Number(button.form?.elements?.manualDr?.value) || 0
}, },
{ {
action: "cancel", action: "cancel",
@@ -387,8 +394,15 @@ Hooks.on("renderChatMessageHTML", (message, html, data) => {
actorName: actor.name, actorName: actor.name,
actorImage: actor.img actorImage: actor.img
} }
const roll = new LethalFantasyRoll(damageFormula, {}, rollOpts) const roll = new documents.LethalFantasyRoll(damageFormula, {}, rollOpts)
await roll.evaluate() await roll.evaluate()
const diceResults = []
for (const term of roll.dice) {
for (const r of term.results) {
diceResults.push({ dice: `1D${term.faces}`, value: r.result })
}
}
roll.options.diceResults = diceResults
roll.options.rollTotal = roll.total roll.options.rollTotal = roll.total
if (game?.dice3d) await game.dice3d.showForRoll(roll, game.user, true) if (game?.dice3d) await game.dice3d.showForRoll(roll, game.user, true)
await roll.toMessage() await roll.toMessage()
@@ -402,7 +416,7 @@ Hooks.on("renderChatMessageHTML", (message, html, data) => {
} }
// Pour les monstres, utiliser prepareMonsterRoll // Pour les monstres, utiliser prepareMonsterRoll
if (isMonster || actor.type === "monster") { if (isMonster === "true" || actor.type === "monster") {
await actor.system.prepareMonsterRoll("monster-damage", weaponId, undefined, undefined, damageModifier) await actor.system.prepareMonsterRoll("monster-damage", weaponId, undefined, undefined, damageModifier)
return return
} }
+83 -2
View File
@@ -22,6 +22,7 @@ export default class LethalFantasyCharacterSheet extends LethalFantasyActorSheet
divinityPointsMinus: LethalFantasyCharacterSheet.#onDivinityPointsMinus, divinityPointsMinus: LethalFantasyCharacterSheet.#onDivinityPointsMinus,
aetherPointsPlus: LethalFantasyCharacterSheet.#onAetherPointsPlus, aetherPointsPlus: LethalFantasyCharacterSheet.#onAetherPointsPlus,
aetherPointsMinus: LethalFantasyCharacterSheet.#onAetherPointsMinus, aetherPointsMinus: LethalFantasyCharacterSheet.#onAetherPointsMinus,
rollSpellDamage: LethalFantasyCharacterSheet.#onRollSpellDamage,
}, },
} }
@@ -70,10 +71,10 @@ export default class LethalFantasyCharacterSheet extends LethalFantasyActorSheet
biography: { id: "biography", group: "sheet", icon: "fa-solid fa-book", label: "LETHALFANTASY.Label.biography" }, biography: { id: "biography", group: "sheet", icon: "fa-solid fa-book", label: "LETHALFANTASY.Label.biography" },
} }
if (this.actor.system.biodata.magicUser) { if (this.actor.system.biodata.magicUser) {
tabs.spells = { id: "spells", group: "sheet", icon: "fa-sharp-duotone fa-solid fa-wand-magic-sparkles", label: "LETHALFANTASY.Label.spells" } tabs.spells = { id: "spells", group: "sheet", icon: "fa-solid fa-wand-magic-sparkles", label: "LETHALFANTASY.Label.spells" }
} }
if (this.actor.system.biodata.clericUser) { if (this.actor.system.biodata.clericUser) {
tabs.miracles = { id: "miracles", group: "sheet", icon: "fa-sharp-duotone fa-solid fa-hands-praying", label: "LETHALFANTASY.Label.miracles" } tabs.miracles = { id: "miracles", group: "sheet", icon: "fa-solid fa-hands-praying", label: "LETHALFANTASY.Label.miracles" }
} }
for (const v of Object.values(tabs)) { for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] === v.id v.active = this.tabGroups[v.group] === v.id
@@ -219,6 +220,86 @@ export default class LethalFantasyCharacterSheet extends LethalFantasyActorSheet
this.actor.update({ "system.aetherPoints.value": points }) this.actor.update({ "system.aetherPoints.value": points })
} }
/**
* Handles spell damage roll from the spell sheet tab.
* Shows a DR dialog then rolls the appropriate damage formula.
* @param {PointerEvent} event
* @param {HTMLElement} target
*/
static async #onRollSpellDamage(event, target) {
if (this.isEditMode) return
const itemId = target.dataset.itemId
const tier = target.dataset.damageTier
const spell = this.actor.items.get(itemId)
if (!spell) return
const formulaMap = {
standard: spell.system.damageDice,
overpowered: spell.system.damageDiceOverpowered,
overpowered2: spell.system.damageDiceOverpowered2,
}
const formula = formulaMap[tier]
if (!formula) return
const manualDR = await foundry.applications.api.DialogV2.wait({
window: { title: game.i18n.localize("LETHALFANTASY.Combat.spellDRDialogTitle") },
classes: ["lethalfantasy"],
position: { width: 320 },
content: `<div style="padding:0.5rem 0">
<p style="margin-bottom:0.6rem">${game.i18n.localize("LETHALFANTASY.Combat.spellDRDialogMsg")}</p>
<div style="display:flex;align-items:center;gap:0.5rem">
<label style="font-weight:bold">${game.i18n.localize("LETHALFANTASY.Combat.spellDRLabel")}</label>
<input type="number" name="manualDr" value="0" min="0" style="width:5rem"/>
</div>
</div>`,
buttons: [
{
action: "noDR",
label: game.i18n.localize("LETHALFANTASY.Combat.spellNoDR"),
icon: "fa-solid fa-wand-magic-sparkles",
callback: () => 0
},
{
action: "applyDR",
label: game.i18n.localize("LETHALFANTASY.Combat.spellApplyDR"),
icon: "fa-solid fa-shield",
callback: (event, button) => Number(button.form?.elements?.manualDr?.value) || 0
},
{
action: "cancel",
label: game.i18n.localize("LETHALFANTASY.Combat.proceedNo"),
callback: () => null
}
],
rejectClose: false
})
if (manualDR === null) return
const rollOpts = {
type: "spell-damage",
rollType: "spell-damage",
rollName: `${spell.name}${formula}`,
isDamage: true,
rollData: { isDamage: true },
manualDR,
actorId: this.actor.id,
actorName: this.actor.name,
actorImage: this.actor.img
}
const roll = new LethalFantasyRoll(formula, {}, rollOpts)
await roll.evaluate()
const diceResults = []
for (const term of roll.dice) {
for (const r of term.results) {
diceResults.push({ dice: `1D${term.faces}`, value: r.result })
}
}
roll.options.diceResults = diceResults
roll.options.rollTotal = roll.total
if (game?.dice3d) await game.dice3d.showForRoll(roll, game.user, true)
await roll.toMessage()
}
static #onCreateEquipment(event, target) { static #onCreateEquipment(event, target) {
} }
+6 -1
View File
@@ -608,7 +608,12 @@
}, },
"arcane_spell_attack": { "arcane_spell_attack": {
"type": "spell_calamity", "type": "spell_calamity",
"description": "Possible Spell Calamity or Catastrophe" "description": "A possible spell calamity has occurred"
},
"melee_attack": {
"type": "fumble",
"detail": "melee_fumble",
"description": "Possible Fumble"
} }
} }
}, },
+1 -1
View File
@@ -85,7 +85,7 @@ export const RANGE_CHOICES = {
"pointblank": { label: "Point Blank (Special)", value: "pointblank" }, "pointblank": { label: "Point Blank (Special)", value: "pointblank" },
"short": { label: "Short (+0)", value: "0" }, "short": { label: "Short (+0)", value: "0" },
"medium": { label: "Medium (Red +5)", value: "+5" }, "medium": { label: "Medium (Red +5)", value: "+5" },
"long": { label: "Long (Purle +7)", value: "+7" }, "long": { label: "Long (Purple +7)", value: "+7" },
"extreme": { label: "Extreme (Grey +9)", value: "+9" }, "extreme": { label: "Extreme (Grey +9)", value: "+9" },
"beyondskill": { label: "Beyond Skill (Blue +11)", value: "beyondskill" } "beyondskill": { label: "Beyond Skill (Blue +11)", value: "beyondskill" }
} }
+2 -1
View File
@@ -261,7 +261,8 @@ export default class LethalFantasyActor extends Actor {
weapon: weapon, weapon: weapon,
weaponSkillModifier: skill.weaponSkillModifier, weaponSkillModifier: skill.weaponSkillModifier,
rollKey: rollKey, rollKey: rollKey,
combat: foundry.utils.duplicate(this.system.combat) combat: foundry.utils.duplicate(this.system.combat),
isRangedAttack: weapon.system.weaponType === "ranged"
} }
if (rollType === "weapon-damage-small" || rollType === "weapon-damage-medium") { if (rollType === "weapon-damage-small" || rollType === "weapon-damage-medium") {
rollTarget.grantedDice = this.system.granted.damageDice rollTarget.grantedDice = this.system.granted.damageDice
+4 -5
View File
@@ -111,17 +111,16 @@ export default class D30Roll {
if (externalType === "weapon-attack") { if (externalType === "weapon-attack") {
if (!weapon) { if (!weapon) {
console.warn("D30Roll | Weapon object required for weapon-attack type") console.warn("D30Roll | Weapon object required for weapon-attack type")
return this.ROLL_TYPES.MELEE_ATTACK // Default to melee // Fall through to use options.isRanged if available, otherwise default melee
} }
return weapon.system?.weaponType === "ranged" return (options.isRanged || weapon?.system?.weaponType === "ranged")
? this.ROLL_TYPES.RANGED_ATTACK ? this.ROLL_TYPES.RANGED_ATTACK
: this.ROLL_TYPES.MELEE_ATTACK : this.ROLL_TYPES.MELEE_ATTACK
} }
// Monster attacks - default to melee // Monster attacks - check options.isRanged (set from rollTarget.attackMode) or weapon type
if (externalType === "monster-attack") { if (externalType === "monster-attack") {
// Check if weapon object has range information if (options.isRanged || weapon?.system?.weaponType === "ranged") {
if (weapon?.system?.weaponType === "ranged") {
return this.ROLL_TYPES.RANGED_ATTACK return this.ROLL_TYPES.RANGED_ATTACK
} }
return this.ROLL_TYPES.MELEE_ATTACK return this.ROLL_TYPES.MELEE_ATTACK
+23 -16
View File
@@ -237,6 +237,7 @@ export default class LethalFantasyRoll extends Roll {
baseFormula = "D20" baseFormula = "D20"
hasModifier = true hasModifier = true
hasChangeDice = false hasChangeDice = false
hasFavor = true
options.rollTarget.value = options.rollTarget.actorModifiers.levelSpellModifier + options.rollTarget.actorModifiers.intSpellModifier options.rollTarget.value = options.rollTarget.actorModifiers.levelSpellModifier + options.rollTarget.actorModifiers.intSpellModifier
options.rollTarget.charModifier = options.rollTarget.actorModifiers.intSpellModifier options.rollTarget.charModifier = options.rollTarget.actorModifiers.intSpellModifier
hasStaticModifier = options.rollType === "spell-power" hasStaticModifier = options.rollType === "spell-power"
@@ -253,6 +254,7 @@ export default class LethalFantasyRoll extends Roll {
dice = "1D20" dice = "1D20"
baseFormula = "D20" baseFormula = "D20"
hasChangeDice = false hasChangeDice = false
hasFavor = true
options.rollTarget.value = options.rollTarget.actorModifiers.levelMiracleModifier + options.rollTarget.actorModifiers.chaMiracleModifier options.rollTarget.value = options.rollTarget.actorModifiers.levelMiracleModifier + options.rollTarget.actorModifiers.chaMiracleModifier
options.rollTarget.charModifier = options.rollTarget.actorModifiers.chaMiracleModifier options.rollTarget.charModifier = options.rollTarget.actorModifiers.chaMiracleModifier
hasStaticModifier = options.rollType === "miracle-power" hasStaticModifier = options.rollType === "miracle-power"
@@ -392,7 +394,7 @@ export default class LethalFantasyRoll extends Roll {
label: label, label: label,
callback: (event, button, dialog) => { callback: (event, button, dialog) => {
console.log("Roll context", event, button, dialog) console.log("Roll context", event, button, dialog)
let position = dialog.position let position = dialog?.position
game.user.setFlag(SYSTEM.id, "roll-dialog-pos", foundry.utils.duplicate(position)) game.user.setFlag(SYSTEM.id, "roll-dialog-pos", foundry.utils.duplicate(position))
const output = Array.from(button.form.elements).reduce((obj, input) => { const output = Array.from(button.form.elements).reduce((obj, input) => {
if (input.name) obj[input.name] = input.value if (input.name) obj[input.name] = input.value
@@ -403,22 +405,22 @@ export default class LethalFantasyRoll extends Roll {
}, },
], ],
actions: { actions: {
"selectGranted": (event, button, dialog) => { "selectGranted": (event, button) => {
hasGrantedDice = event.target.checked hasGrantedDice = event.target.checked
}, },
"selectBeyondSkill": (event, button, dialog) => { "selectBeyondSkill": (event, button) => {
beyondSkill = button.checked beyondSkill = button.checked
}, },
"selectPointBlank": (event, button, dialog) => { "selectPointBlank": (event, button) => {
pointBlank = button.checked pointBlank = button.checked
}, },
"selectLetItFly": (event, button, dialog) => { "selectLetItFly": (event, button) => {
letItFly = button.checked letItFly = button.checked
}, },
"saveSpellCheck": (event, button, dialog) => { "saveSpellCheck": (event, button) => {
saveSpell = button.checked saveSpell = button.checked
}, },
"gotoToken": (event, button, dialog) => { "gotoToken": (event, button) => {
let tokenId = $(button).data("tokenId") let tokenId = $(button).data("tokenId")
let token = canvas.tokens?.get(tokenId) let token = canvas.tokens?.get(tokenId)
if (token) { if (token) {
@@ -592,12 +594,17 @@ export default class LethalFantasyRoll extends Roll {
} }
options.D30result = rollD30.total options.D30result = rollD30.total
// Récupérer le message D30 correspondant // Compute isRanged for D30: covers defense (isRangedDefense), monster ranged attacks (attackMode),
// and PC weapon attacks (isRangedAttack or weaponType)
const isRangedForD30 = options.isRangedDefense
|| options.rollTarget?.attackMode === "ranged"
|| options.rollTarget?.isRangedAttack === true
|| options.rollTarget?.weapon?.system?.weaponType === "ranged"
const d30Message = D30Roll.getResult( const d30Message = D30Roll.getResult(
rollD30.total, rollD30.total,
options.rollType, options.rollType,
options.rollTarget?.weapon, options.rollTarget?.weapon,
{ isRanged: options.isRangedDefense } { isRanged: isRangedForD30 }
) )
options.D30message = d30Message options.D30message = d30Message
} }
@@ -710,7 +717,7 @@ export default class LethalFantasyRoll extends Roll {
buttons: [ buttons: [
{ {
label: label, label: label,
callback: (event, button, dialog) => { callback: (event, button) => {
const output = Array.from(button.form.elements).reduce((obj, input) => { const output = Array.from(button.form.elements).reduce((obj, input) => {
if (input.name) obj[input.name] = input.value if (input.name) obj[input.name] = input.value
return obj return obj
@@ -778,7 +785,7 @@ export default class LethalFantasyRoll extends Roll {
buttons.push({ buttons.push({
action: "roll", action: "roll",
label: "Roll progression dice", label: "Roll progression dice",
callback: (event, button, dialog) => { callback: (event, button) => {
let pos = $('#combat-action-dialog').position() let pos = $('#combat-action-dialog').position()
game.user.setFlag(SYSTEM.id, "combat-action-dialog-pos", pos) game.user.setFlag(SYSTEM.id, "combat-action-dialog-pos", pos)
return "rollProgressionDice" return "rollProgressionDice"
@@ -804,7 +811,7 @@ export default class LethalFantasyRoll extends Roll {
buttons.push({ buttons.push({
action: "roll", action: "roll",
label: label, label: label,
callback: (event, button, dialog) => { callback: (event, button) => {
let pos = $('#combat-action-dialog').position() let pos = $('#combat-action-dialog').position()
game.user.setFlag(SYSTEM.id, "combat-action-dialog-pos", foundry.utils.duplicate(pos)) game.user.setFlag(SYSTEM.id, "combat-action-dialog-pos", foundry.utils.duplicate(pos))
return "rollLethargyDice" return "rollLethargyDice"
@@ -815,7 +822,7 @@ export default class LethalFantasyRoll extends Roll {
buttons.push({ buttons.push({
action: "roll", action: "roll",
label: "Select action", label: "Select action",
callback: (event, button, dialog) => { callback: (event, button) => {
let pos = $('#combat-action-dialog').position() let pos = $('#combat-action-dialog').position()
game.user.setFlag(SYSTEM.id, "combat-action-dialog-pos", foundry.utils.duplicate(pos)) game.user.setFlag(SYSTEM.id, "combat-action-dialog-pos", foundry.utils.duplicate(pos))
const output = Array.from(button.form.elements).reduce((obj, input) => { const output = Array.from(button.form.elements).reduce((obj, input) => {
@@ -830,7 +837,7 @@ export default class LethalFantasyRoll extends Roll {
buttons.push({ buttons.push({
action: "cancel", action: "cancel",
label: "Other action, not listed here", label: "Other action, not listed here",
callback: (event, button, dialog) => { callback: (event, button) => {
let pos = $('#combat-action-dialog').position() let pos = $('#combat-action-dialog').position()
game.user.setFlag(SYSTEM.id, "combat-action-dialog-pos", foundry.utils.duplicate(pos)) game.user.setFlag(SYSTEM.id, "combat-action-dialog-pos", foundry.utils.duplicate(pos))
return null; return null;
@@ -980,7 +987,7 @@ export default class LethalFantasyRoll extends Roll {
} }
// Range weapon loading // Range weapon loading
if (!currentAction.weaponLoaded && currentAction.rangedLoad) { if (!currentAction.weaponLoaded && currentAction.rangedLoad) {
if (currentAction.progressionCount <= currentAction.rangedLoad) { if (currentAction.progressionCount < currentAction.rangedLoad) {
let message = `Ranged weapon ${currentAction.name} is loading, loading count : ${currentAction.progressionCount}/${currentAction.rangedLoad}` let message = `Ranged weapon ${currentAction.name} is loading, loading count : ${currentAction.progressionCount}/${currentAction.rangedLoad}`
ChatMessage.create({ content: message, speaker: ChatMessage.getSpeaker({ actor: combatant.actor }) }) ChatMessage.create({ content: message, speaker: ChatMessage.getSpeaker({ actor: combatant.actor }) })
currentAction.progressionCount += 1 currentAction.progressionCount += 1
@@ -1077,7 +1084,7 @@ export default class LethalFantasyRoll extends Roll {
buttons: [ buttons: [
{ {
label: label, label: label,
callback: (event, button, dialog) => { callback: (event, button) => {
const output = Array.from(button.form.elements).reduce((obj, input) => { const output = Array.from(button.form.elements).reduce((obj, input) => {
if (input.name) obj[input.name] = input.value if (input.name) obj[input.name] = input.value
return obj return obj
+5 -4
View File
@@ -324,7 +324,7 @@ export default class LethalFantasyUtils {
<p>Attack roll: <strong>${attackRoll}</strong></p> <p>Attack roll: <strong>${attackRoll}</strong></p>
</div> </div>
<div class="weapon-selection"> <div class="weapon-selection">
<label for="defense-attack">Choose your defense attack:</label> <label for="defense-attack">Choose your defense weapon:</label>
<select id="defense-attack" name="attackKey" style="width: 100%; margin-top: 8px;"> <select id="defense-attack" name="attackKey" style="width: 100%; margin-top: 8px;">
${attacksHTML} ${attacksHTML}
</select> </select>
@@ -711,6 +711,7 @@ export default class LethalFantasyUtils {
${totalBonus > 0 ? `<p class="bonus-info">Bonus already added: +${totalBonus}</p>` : ''} ${totalBonus > 0 ? `<p class="bonus-info">Bonus already added: +${totalBonus}</p>` : ''}
</div> </div>
<p class="offer-text">You are losing! Spend Grit or Luck to add 1D6 to your defense?</p> <p class="offer-text">You are losing! Spend Grit or Luck to add 1D6 to your defense?</p>
<p class="shield-warning"><i class="fa-solid fa-triangle-exclamation"></i> If you intend to use a shield, you must spend Grit or Luck <strong>first</strong> — the shield roll comes after.</p>
</div> </div>
` `
@@ -853,7 +854,7 @@ export default class LethalFantasyUtils {
` `
} else if (data.attackRollType === "monster-attack") { } else if (data.attackRollType === "monster-attack") {
damageButton = ` damageButton = `
<div class="attack-result-damage"> <div class="attack-result-damage single-btn">
<button class="roll-damage-btn" data-attacker-id="${data.attackerId}" data-defender-id="${data.defenderId}" data-defender-token-id="${data.defenderTokenId || ""}" data-extra-shield-dr="${data.shieldDamageReduction || 0}" data-attack-key="${data.attackRollKey}" data-damage-type="monster"> <button class="roll-damage-btn" data-attacker-id="${data.attackerId}" data-defender-id="${data.defenderId}" data-defender-token-id="${data.defenderTokenId || ""}" data-extra-shield-dr="${data.shieldDamageReduction || 0}" data-attack-key="${data.attackRollKey}" data-damage-type="monster">
<i class="fa-solid fa-burst"></i> Damage <i class="fa-solid fa-burst"></i> Damage
</button> </button>
@@ -861,7 +862,7 @@ export default class LethalFantasyUtils {
` `
} else if (data.attackRollType === "spell-attack" || data.attackRollType === "miracle-attack") { } else if (data.attackRollType === "spell-attack" || data.attackRollType === "miracle-attack") {
const attacker = game.actors.get(data.attackerId) const attacker = game.actors.get(data.attackerId)
const spell = attacker?.items.get(data.attackWeaponId) const spell = attacker?.items.get(data.attackWeaponId || data.attackRollKey)
const tiers = [ const tiers = [
{ formula: spell?.system?.damageDice, label: "Standard" }, { formula: spell?.system?.damageDice, label: "Standard" },
{ formula: spell?.system?.damageDiceOverpowered, label: "Overpowered" }, { formula: spell?.system?.damageDiceOverpowered, label: "Overpowered" },
@@ -880,7 +881,7 @@ export default class LethalFantasyUtils {
<i class="fa-solid fa-wand-magic-sparkles"></i> ${t.label} (${escapedFormula}) <i class="fa-solid fa-wand-magic-sparkles"></i> ${t.label} (${escapedFormula})
</button>` </button>`
}).join("") }).join("")
damageButton = `<div class="attack-result-damage">${buttons}</div>` damageButton = `<div class="attack-result-damage spell-damage">${buttons}</div>`
} }
} }
} }
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000599 MANIFEST-000611
+8 -8
View File
@@ -1,8 +1,8 @@
2026/05/18-17:40:11.769288 7f5a477fe6c0 Recovering log #597 2026/05/20-23:09:33.153266 7f179e7ed6c0 Recovering log #609
2026/05/18-17:40:11.780605 7f5a477fe6c0 Delete type=3 #595 2026/05/20-23:09:33.205459 7f179e7ed6c0 Delete type=3 #607
2026/05/18-17:40:11.780699 7f5a477fe6c0 Delete type=0 #597 2026/05/20-23:09:33.205507 7f179e7ed6c0 Delete type=0 #609
2026/05/18-20:25:54.228818 7f5a467fc6c0 Level-0 table #602: started 2026/05/20-23:16:53.517842 7f179d7eb6c0 Level-0 table #614: started
2026/05/18-20:25:54.230037 7f5a467fc6c0 Level-0 table #602: 0 bytes OK 2026/05/20-23:16:53.517893 7f179d7eb6c0 Level-0 table #614: 0 bytes OK
2026/05/18-20:25:54.236369 7f5a467fc6c0 Delete type=0 #600 2026/05/20-23:16:53.523991 7f179d7eb6c0 Delete type=0 #612
2026/05/18-20:25:54.251050 7f5a467fc6c0 Manual compaction at level-0 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end) 2026/05/20-23:16:53.536989 7f179d7eb6c0 Manual compaction at level-0 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end)
2026/05/18-20:25:54.251106 7f5a467fc6c0 Manual compaction at level-1 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end) 2026/05/20-23:16:53.537026 7f179d7eb6c0 Manual compaction at level-1 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end)
+8 -8
View File
@@ -1,8 +1,8 @@
2026/05/18-07:32:52.671725 7f5a94bff6c0 Recovering log #593 2026/05/20-00:10:37.079181 7f0b1e7ee6c0 Recovering log #605
2026/05/18-07:32:52.684619 7f5a94bff6c0 Delete type=3 #591 2026/05/20-00:10:37.089613 7f0b1e7ee6c0 Delete type=3 #603
2026/05/18-07:32:52.684760 7f5a94bff6c0 Delete type=0 #593 2026/05/20-00:10:37.089671 7f0b1e7ee6c0 Delete type=0 #605
2026/05/18-07:58:12.225439 7f5a467fc6c0 Level-0 table #598: started 2026/05/20-10:53:40.566713 7f0b1cfeb6c0 Level-0 table #610: started
2026/05/18-07:58:12.225593 7f5a467fc6c0 Level-0 table #598: 0 bytes OK 2026/05/20-10:53:40.566740 7f0b1cfeb6c0 Level-0 table #610: 0 bytes OK
2026/05/18-07:58:12.232817 7f5a467fc6c0 Delete type=0 #596 2026/05/20-10:53:40.572741 7f0b1cfeb6c0 Delete type=0 #608
2026/05/18-07:58:12.252837 7f5a467fc6c0 Manual compaction at level-0 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end) 2026/05/20-10:53:40.587356 7f0b1cfeb6c0 Manual compaction at level-0 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end)
2026/05/18-07:58:12.252955 7f5a467fc6c0 Manual compaction at level-1 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end) 2026/05/20-10:53:40.587409 7f0b1cfeb6c0 Manual compaction at level-1 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end)
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000596 MANIFEST-000608
+8 -8
View File
@@ -1,8 +1,8 @@
2026/05/18-17:40:11.793894 7f5a94bff6c0 Recovering log #594 2026/05/20-23:09:33.234730 7f179e7ed6c0 Recovering log #606
2026/05/18-17:40:11.804447 7f5a94bff6c0 Delete type=3 #592 2026/05/20-23:09:33.288868 7f179e7ed6c0 Delete type=3 #604
2026/05/18-17:40:11.804549 7f5a94bff6c0 Delete type=0 #594 2026/05/20-23:09:33.288920 7f179e7ed6c0 Delete type=0 #606
2026/05/18-20:25:54.236611 7f5a467fc6c0 Level-0 table #599: started 2026/05/20-23:16:53.524172 7f179d7eb6c0 Level-0 table #611: started
2026/05/18-20:25:54.236652 7f5a467fc6c0 Level-0 table #599: 0 bytes OK 2026/05/20-23:16:53.524219 7f179d7eb6c0 Level-0 table #611: 0 bytes OK
2026/05/18-20:25:54.243997 7f5a467fc6c0 Delete type=0 #597 2026/05/20-23:16:53.530312 7f179d7eb6c0 Delete type=0 #609
2026/05/18-20:25:54.251069 7f5a467fc6c0 Manual compaction at level-0 from '!folders!yPWGvxHJbDNHVSnY' @ 72057594037927935 : 1 .. '!items!x5gLtqlW4sdDmHTd' @ 0 : 0; will stop at (end) 2026/05/20-23:16:53.537003 7f179d7eb6c0 Manual compaction at level-0 from '!folders!yPWGvxHJbDNHVSnY' @ 72057594037927935 : 1 .. '!items!x5gLtqlW4sdDmHTd' @ 0 : 0; will stop at (end)
2026/05/18-20:25:54.264439 7f5a467fc6c0 Manual compaction at level-1 from '!folders!yPWGvxHJbDNHVSnY' @ 72057594037927935 : 1 .. '!items!x5gLtqlW4sdDmHTd' @ 0 : 0; will stop at (end) 2026/05/20-23:16:53.537059 7f179d7eb6c0 Manual compaction at level-1 from '!folders!yPWGvxHJbDNHVSnY' @ 72057594037927935 : 1 .. '!items!x5gLtqlW4sdDmHTd' @ 0 : 0; will stop at (end)
+8 -8
View File
@@ -1,8 +1,8 @@
2026/05/18-07:32:52.700651 7f5a477fe6c0 Recovering log #590 2026/05/20-00:10:37.107483 7f0b1efef6c0 Recovering log #602
2026/05/18-07:32:52.712680 7f5a477fe6c0 Delete type=3 #588 2026/05/20-00:10:37.117907 7f0b1efef6c0 Delete type=3 #600
2026/05/18-07:32:52.712814 7f5a477fe6c0 Delete type=0 #590 2026/05/20-00:10:37.117968 7f0b1efef6c0 Delete type=0 #602
2026/05/18-07:58:12.232998 7f5a467fc6c0 Level-0 table #595: started 2026/05/20-10:53:40.572909 7f0b1cfeb6c0 Level-0 table #607: started
2026/05/18-07:58:12.233130 7f5a467fc6c0 Level-0 table #595: 0 bytes OK 2026/05/20-10:53:40.572948 7f0b1cfeb6c0 Level-0 table #607: 0 bytes OK
2026/05/18-07:58:12.244133 7f5a467fc6c0 Delete type=0 #593 2026/05/20-10:53:40.579001 7f0b1cfeb6c0 Delete type=0 #605
2026/05/18-07:58:12.252873 7f5a467fc6c0 Manual compaction at level-0 from '!folders!yPWGvxHJbDNHVSnY' @ 72057594037927935 : 1 .. '!items!x5gLtqlW4sdDmHTd' @ 0 : 0; will stop at (end) 2026/05/20-10:53:40.587370 7f0b1cfeb6c0 Manual compaction at level-0 from '!folders!yPWGvxHJbDNHVSnY' @ 72057594037927935 : 1 .. '!items!x5gLtqlW4sdDmHTd' @ 0 : 0; will stop at (end)
2026/05/18-07:58:12.252977 7f5a467fc6c0 Manual compaction at level-1 from '!folders!yPWGvxHJbDNHVSnY' @ 72057594037927935 : 1 .. '!items!x5gLtqlW4sdDmHTd' @ 0 : 0; will stop at (end) 2026/05/20-10:53:40.587430 7f0b1cfeb6c0 Manual compaction at level-1 from '!folders!yPWGvxHJbDNHVSnY' @ 72057594037927935 : 1 .. '!items!x5gLtqlW4sdDmHTd' @ 0 : 0; will stop at (end)
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000601 MANIFEST-000613
+8 -8
View File
@@ -1,8 +1,8 @@
2026/05/18-17:40:11.749971 7f5a46ffd6c0 Recovering log #599 2026/05/20-23:09:33.082007 7f179dfec6c0 Recovering log #611
2026/05/18-17:40:11.761423 7f5a46ffd6c0 Delete type=3 #597 2026/05/20-23:09:33.140910 7f179dfec6c0 Delete type=3 #609
2026/05/18-17:40:11.761520 7f5a46ffd6c0 Delete type=0 #599 2026/05/20-23:09:33.140962 7f179dfec6c0 Delete type=0 #611
2026/05/18-20:25:54.219239 7f5a467fc6c0 Level-0 table #604: started 2026/05/20-23:16:53.510592 7f179d7eb6c0 Level-0 table #616: started
2026/05/18-20:25:54.219522 7f5a467fc6c0 Level-0 table #604: 0 bytes OK 2026/05/20-23:16:53.510664 7f179d7eb6c0 Level-0 table #616: 0 bytes OK
2026/05/18-20:25:54.228610 7f5a467fc6c0 Delete type=0 #602 2026/05/20-23:16:53.517661 7f179d7eb6c0 Delete type=0 #614
2026/05/18-20:25:54.251028 7f5a467fc6c0 Manual compaction at level-0 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end) 2026/05/20-23:16:53.536973 7f179d7eb6c0 Manual compaction at level-0 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end)
2026/05/18-20:25:54.251085 7f5a467fc6c0 Manual compaction at level-1 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end) 2026/05/20-23:16:53.537037 7f179d7eb6c0 Manual compaction at level-1 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end)
+8 -8
View File
@@ -1,8 +1,8 @@
2026/05/18-07:32:52.650924 7f5a47fff6c0 Recovering log #595 2026/05/20-00:10:37.061485 7f0b1efef6c0 Recovering log #607
2026/05/18-07:32:52.662191 7f5a47fff6c0 Delete type=3 #593 2026/05/20-00:10:37.071572 7f0b1efef6c0 Delete type=3 #605
2026/05/18-07:32:52.662331 7f5a47fff6c0 Delete type=0 #595 2026/05/20-00:10:37.071684 7f0b1efef6c0 Delete type=0 #607
2026/05/18-07:58:12.218060 7f5a467fc6c0 Level-0 table #600: started 2026/05/20-10:53:40.579189 7f0b1cfeb6c0 Level-0 table #612: started
2026/05/18-07:58:12.218402 7f5a467fc6c0 Level-0 table #600: 0 bytes OK 2026/05/20-10:53:40.579229 7f0b1cfeb6c0 Level-0 table #612: 0 bytes OK
2026/05/18-07:58:12.225237 7f5a467fc6c0 Delete type=0 #598 2026/05/20-10:53:40.587176 7f0b1cfeb6c0 Delete type=0 #610
2026/05/18-07:58:12.244392 7f5a467fc6c0 Manual compaction at level-0 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end) 2026/05/20-10:53:40.587386 7f0b1cfeb6c0 Manual compaction at level-0 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end)
2026/05/18-07:58:12.252899 7f5a467fc6c0 Manual compaction at level-1 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end) 2026/05/20-10:53:40.587419 7f0b1cfeb6c0 Manual compaction at level-1 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end)
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000296 MANIFEST-000308
+8 -8
View File
@@ -1,8 +1,8 @@
2026/05/18-17:40:11.831491 7f5a94bff6c0 Recovering log #294 2026/05/20-23:09:33.368596 7f179e7ed6c0 Recovering log #306
2026/05/18-17:40:11.842913 7f5a94bff6c0 Delete type=3 #292 2026/05/20-23:09:33.422152 7f179e7ed6c0 Delete type=3 #304
2026/05/18-17:40:11.843005 7f5a94bff6c0 Delete type=0 #294 2026/05/20-23:09:33.422210 7f179e7ed6c0 Delete type=0 #306
2026/05/18-20:25:54.257813 7f5a467fc6c0 Level-0 table #299: started 2026/05/20-23:16:53.560903 7f179d7eb6c0 Level-0 table #311: started
2026/05/18-20:25:54.257869 7f5a467fc6c0 Level-0 table #299: 0 bytes OK 2026/05/20-23:16:53.560969 7f179d7eb6c0 Level-0 table #311: 0 bytes OK
2026/05/18-20:25:54.264186 7f5a467fc6c0 Delete type=0 #297 2026/05/20-23:16:53.568455 7f179d7eb6c0 Delete type=0 #309
2026/05/18-20:25:54.267118 7f5a467fc6c0 Manual compaction at level-0 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end) 2026/05/20-23:16:53.568616 7f179d7eb6c0 Manual compaction at level-0 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end)
2026/05/18-20:25:54.296812 7f5a467fc6c0 Manual compaction at level-1 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end) 2026/05/20-23:16:53.578679 7f179d7eb6c0 Manual compaction at level-1 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end)
+8 -8
View File
@@ -1,8 +1,8 @@
2026/05/18-07:32:52.738234 7f5a477fe6c0 Recovering log #290 2026/05/20-00:10:37.136939 7f0b1efef6c0 Recovering log #302
2026/05/18-07:32:52.749839 7f5a477fe6c0 Delete type=3 #288 2026/05/20-00:10:37.146957 7f0b1efef6c0 Delete type=3 #300
2026/05/18-07:32:52.749970 7f5a477fe6c0 Delete type=0 #290 2026/05/20-00:10:37.147030 7f0b1efef6c0 Delete type=0 #302
2026/05/18-07:58:12.253340 7f5a467fc6c0 Level-0 table #295: started 2026/05/20-10:53:40.615430 7f0b1cfeb6c0 Level-0 table #307: started
2026/05/18-07:58:12.253449 7f5a467fc6c0 Level-0 table #295: 0 bytes OK 2026/05/20-10:53:40.615467 7f0b1cfeb6c0 Level-0 table #307: 0 bytes OK
2026/05/18-07:58:12.261029 7f5a467fc6c0 Delete type=0 #293 2026/05/20-10:53:40.621586 7f0b1cfeb6c0 Delete type=0 #305
2026/05/18-07:58:12.286385 7f5a467fc6c0 Manual compaction at level-0 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end) 2026/05/20-10:53:40.621713 7f0b1cfeb6c0 Manual compaction at level-0 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end)
2026/05/18-07:58:12.286487 7f5a467fc6c0 Manual compaction at level-1 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end) 2026/05/20-10:53:40.633723 7f0b1cfeb6c0 Manual compaction at level-1 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end)
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000595 MANIFEST-000607
+8 -8
View File
@@ -1,8 +1,8 @@
2026/05/18-17:40:11.812004 7f5a47fff6c0 Recovering log #593 2026/05/20-23:09:33.302872 7f179efee6c0 Recovering log #605
2026/05/18-17:40:11.823277 7f5a47fff6c0 Delete type=3 #591 2026/05/20-23:09:33.362229 7f179efee6c0 Delete type=3 #603
2026/05/18-17:40:11.823388 7f5a47fff6c0 Delete type=0 #593 2026/05/20-23:09:33.362319 7f179efee6c0 Delete type=0 #605
2026/05/18-20:25:54.244177 7f5a467fc6c0 Level-0 table #598: started 2026/05/20-23:16:53.530498 7f179d7eb6c0 Level-0 table #610: started
2026/05/18-20:25:54.244222 7f5a467fc6c0 Level-0 table #598: 0 bytes OK 2026/05/20-23:16:53.530547 7f179d7eb6c0 Level-0 table #610: 0 bytes OK
2026/05/18-20:25:54.250810 7f5a467fc6c0 Delete type=0 #596 2026/05/20-23:16:53.536854 7f179d7eb6c0 Delete type=0 #608
2026/05/18-20:25:54.251096 7f5a467fc6c0 Manual compaction at level-0 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end) 2026/05/20-23:16:53.537015 7f179d7eb6c0 Manual compaction at level-0 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end)
2026/05/18-20:25:54.264461 7f5a467fc6c0 Manual compaction at level-1 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end) 2026/05/20-23:16:53.537048 7f179d7eb6c0 Manual compaction at level-1 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end)
+8 -8
View File
@@ -1,8 +1,8 @@
2026/05/18-07:32:52.720162 7f5a94bff6c0 Recovering log #589 2026/05/20-00:10:37.121340 7f0b1e7ee6c0 Recovering log #601
2026/05/18-07:32:52.731519 7f5a94bff6c0 Delete type=3 #587 2026/05/20-00:10:37.132435 7f0b1e7ee6c0 Delete type=3 #599
2026/05/18-07:32:52.731690 7f5a94bff6c0 Delete type=0 #589 2026/05/20-00:10:37.132491 7f0b1e7ee6c0 Delete type=0 #601
2026/05/18-07:58:12.244424 7f5a467fc6c0 Level-0 table #594: started 2026/05/20-10:53:40.559937 7f0b1cfeb6c0 Level-0 table #606: started
2026/05/18-07:58:12.244618 7f5a467fc6c0 Level-0 table #594: 0 bytes OK 2026/05/20-10:53:40.560028 7f0b1cfeb6c0 Level-0 table #606: 0 bytes OK
2026/05/18-07:58:12.252579 7f5a467fc6c0 Delete type=0 #592 2026/05/20-10:53:40.566605 7f0b1cfeb6c0 Delete type=0 #604
2026/05/18-07:58:12.252922 7f5a467fc6c0 Manual compaction at level-0 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end) 2026/05/20-10:53:40.587338 7f0b1cfeb6c0 Manual compaction at level-0 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end)
2026/05/18-07:58:12.252996 7f5a467fc6c0 Manual compaction at level-1 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end) 2026/05/20-10:53:40.587398 7f0b1cfeb6c0 Manual compaction at level-1 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end)
+69 -12
View File
@@ -145,9 +145,11 @@
} }
.grit-luck-dialog { .grit-luck-dialog {
color: var(--color-text-dark-primary, #191813);
.combat-status { .combat-status {
padding: 12px; padding: 12px;
background: linear-gradient(to bottom, rgba(42, 41, 32, 0.8) 0%, rgba(26, 25, 16, 0.9) 100%); background: linear-gradient(to bottom, rgba(42, 41, 32, 0.88) 0%, rgba(26, 25, 16, 0.95) 100%);
border: 1px solid rgba(212, 175, 55, 0.5); border: 1px solid rgba(212, 175, 55, 0.5);
border-radius: 6px; border-radius: 6px;
margin-bottom: 16px; margin-bottom: 16px;
@@ -171,11 +173,27 @@
} }
.offer-text { .offer-text {
color: #f0e6d2; color: var(--color-text-dark-primary, #191813);
font-size: calc(var(--font-size-standard) * 1); font-size: calc(var(--font-size-standard) * 1);
text-align: center; text-align: center;
font-weight: 600; font-weight: 600;
margin: 0 0 8px 0;
}
.shield-warning {
color: #7a4000;
background: rgba(255, 160, 0, 0.12);
border: 1px solid rgba(255, 160, 0, 0.4);
border-radius: 5px;
font-size: calc(var(--font-size-standard) * 0.88);
padding: 6px 10px;
margin: 0; margin: 0;
text-align: center;
i {
color: #c07000;
margin-right: 5px;
}
} }
} }
@@ -283,33 +301,72 @@
} }
.attack-result-damage { .attack-result-damage {
display: flex; display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px; gap: 8px;
justify-content: center;
&.single-btn {
grid-template-columns: 1fr;
max-width: 280px;
margin: 0 auto;
}
&.spell-damage {
grid-template-columns: 1fr;
width: 100%;
}
.roll-damage-btn { .roll-damage-btn {
padding: 10px 16px; padding: 10px 14px;
background: linear-gradient(to bottom, #8b0000 0%, #660000 100%); background: linear-gradient(to bottom, #8b0000 0%, #660000 100%);
border: 1px solid #ff0000; border: 1px solid #4b0000;
border-radius: 6px; border-radius: 6px;
color: #f0e6d2; color: #f0e6d2;
font-weight: 600; font-weight: 600;
font-size: calc(var(--font-size-standard) * 0.9);
text-align: center;
white-space: nowrap;
cursor: pointer; cursor: pointer;
transition: all 0.2s ease; transition: all 0.2s ease;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); box-shadow: 0 3px 6px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.1);
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.15), transparent);
transition: left 0.5s;
}
&:hover::before {
left: 100%;
}
i {
font-size: calc(var(--font-size-standard) * 1.1);
filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5));
flex-shrink: 0;
}
&:hover { &:hover {
background: linear-gradient(to bottom, #a00000 0%, #7b0000 100%); background: linear-gradient(to bottom, #a00000 0%, #7b0000 100%);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.4); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.15);
transform: translateY(-2px); transform: translateY(-2px);
border-color: #5b0000;
} }
&:active { &:active {
transform: translateY(0); transform: translateY(0);
} box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4), inset 0 1px 3px rgba(0, 0, 0, 0.3);
i {
margin-right: 6px;
} }
} }
} }
+49 -10
View File
@@ -1285,33 +1285,72 @@
} }
.attack-result-damage { .attack-result-damage {
display: flex; display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px; gap: 8px;
justify-content: center;
&.single-btn {
grid-template-columns: 1fr;
max-width: 280px;
margin: 0 auto;
}
&.spell-damage {
grid-template-columns: 1fr;
width: 100%;
}
.roll-damage-btn { .roll-damage-btn {
padding: 10px 16px; padding: 10px 14px;
background: linear-gradient(to bottom, #8b0000 0%, #660000 100%); background: linear-gradient(to bottom, #8b0000 0%, #660000 100%);
border: 1px solid #ff0000; border: 1px solid #4b0000;
border-radius: 6px; border-radius: 6px;
color: #f0e6d2; color: #f0e6d2;
font-weight: 600; font-weight: 600;
font-size: calc(var(--font-size-standard) * 0.9);
text-align: center;
white-space: nowrap;
cursor: pointer; cursor: pointer;
transition: all 0.2s ease; transition: all 0.2s ease;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); box-shadow: 0 3px 6px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.1);
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.15), transparent);
transition: left 0.5s;
}
&:hover::before {
left: 100%;
}
i {
font-size: calc(var(--font-size-standard) * 1.1);
filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5));
flex-shrink: 0;
}
&:hover { &:hover {
background: linear-gradient(to bottom, #a00000 0%, #7b0000 100%); background: linear-gradient(to bottom, #a00000 0%, #7b0000 100%);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.4); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.15);
transform: translateY(-2px); transform: translateY(-2px);
border-color: #5b0000;
} }
&:active { &:active {
transform: translateY(0); transform: translateY(0);
} box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4), inset 0 1px 3px rgba(0, 0, 0, 0.3);
i {
margin-right: 6px;
} }
} }
} }
+19
View File
@@ -37,6 +37,25 @@
<i class="fa-duotone fa-solid fa-stars" data-roll-type="spell-power" data-roll-key="{{item.id}}"></i> <i class="fa-duotone fa-solid fa-stars" data-roll-type="spell-power" data-roll-key="{{item.id}}"></i>
</a> </a>
{{#if item.system.damageDice}}
<a data-action="rollSpellDamage" data-item-id="{{item.id}}" data-damage-tier="standard"
data-tooltip="Spell Damage (Standard)">
<i class="fa-solid fa-wand-magic-sparkles"></i>S
</a>
{{/if}}
{{#if item.system.damageDiceOverpowered}}
<a data-action="rollSpellDamage" data-item-id="{{item.id}}" data-damage-tier="overpowered"
data-tooltip="Spell Damage (Overpowered)">
<i class="fa-solid fa-wand-magic-sparkles"></i>O
</a>
{{/if}}
{{#if item.system.damageDiceOverpowered2}}
<a data-action="rollSpellDamage" data-item-id="{{item.id}}" data-damage-tier="overpowered2"
data-tooltip="Spell Damage (Overpowered 2)">
<i class="fa-solid fa-wand-magic-sparkles"></i>O2
</a>
{{/if}}
<div class="controls"> <div class="controls">
<a data-tooltip="{{localize 'LETHALFANTASY.Edit'}}" data-action="edit" data-item-id="{{item.id}}" <a data-tooltip="{{localize 'LETHALFANTASY.Edit'}}" data-action="edit" data-item-id="{{item.id}}"
data-item-uuid="{{item.uuid}}"><i class="fas fa-edit"></i></a> data-item-uuid="{{item.uuid}}"><i class="fas fa-edit"></i></a>