From 52877e3a68a63d3de757fadca5b51a7ba215f2bb Mon Sep 17 00:00:00 2001 From: LeRatierBretonnien Date: Mon, 19 Jan 2026 23:22:32 +0100 Subject: [PATCH] New combat management and various improvments --- css/fvtt-lethal-fantasy.css | 1579 ++++++++++++++++- lang/en.json | 12 + lethal-fantasy.mjs | 356 +++- module/config/d30_results_tables.json | 152 ++ module/documents/_module.mjs | 1 + module/documents/actor.mjs | 6 +- module/documents/d30-roll.mjs | 215 +++ module/documents/roll.mjs | 104 +- module/models/character.mjs | 12 +- module/models/monster.mjs | 81 +- module/utils.mjs | 481 ++++- .../lf-equipment/{000500.log => 000529.log} | 0 packs-system/lf-equipment/CURRENT | 2 +- packs-system/lf-equipment/LOG | 23 +- packs-system/lf-equipment/LOG.old | 16 +- packs-system/lf-equipment/MANIFEST-000498 | Bin 382 -> 0 bytes packs-system/lf-equipment/MANIFEST-000527 | Bin 0 -> 178 bytes .../lf-gifts/{000498.log => 000526.log} | 0 packs-system/lf-gifts/CURRENT | 2 +- packs-system/lf-gifts/LOG | 16 +- packs-system/lf-gifts/LOG.old | 16 +- .../{MANIFEST-000496 => MANIFEST-000524} | Bin 247 -> 247 bytes .../lf-skills/{000503.ldb => 000508.ldb} | Bin 118712 -> 118731 bytes .../lf-skills/{000501.log => 000531.log} | 0 packs-system/lf-skills/CURRENT | 2 +- packs-system/lf-skills/LOG | 23 +- packs-system/lf-skills/LOG.old | 16 +- packs-system/lf-skills/MANIFEST-000499 | Bin 381 -> 0 bytes packs-system/lf-skills/MANIFEST-000529 | Bin 0 -> 178 bytes .../{000198.log => 000226.log} | 0 packs-system/lf-spells-miracles/CURRENT | 2 +- packs-system/lf-spells-miracles/LOG | 16 +- packs-system/lf-spells-miracles/LOG.old | 16 +- .../{MANIFEST-000196 => MANIFEST-000224} | Bin 177 -> 177 bytes .../{000497.log => 000525.log} | 0 packs-system/lf-vulnerabilities/CURRENT | 2 +- packs-system/lf-vulnerabilities/LOG | 16 +- packs-system/lf-vulnerabilities/LOG.old | 16 +- .../{MANIFEST-000495 => MANIFEST-000523} | Bin 176 -> 176 bytes styles/chat.less | 275 +++ styles/roll.less | 1084 ++++++++++- templates/chat-message.hbs | 442 +++-- templates/damage-applied-message.hbs | 36 + templates/monster-combat.hbs | 52 +- templates/monster-main.hbs | 14 - templates/progression-message.hbs | 44 + 46 files changed, 4655 insertions(+), 475 deletions(-) create mode 100644 module/config/d30_results_tables.json create mode 100644 module/documents/d30-roll.mjs rename packs-system/lf-equipment/{000500.log => 000529.log} (100%) delete mode 100644 packs-system/lf-equipment/MANIFEST-000498 create mode 100644 packs-system/lf-equipment/MANIFEST-000527 rename packs-system/lf-gifts/{000498.log => 000526.log} (100%) rename packs-system/lf-gifts/{MANIFEST-000496 => MANIFEST-000524} (77%) rename packs-system/lf-skills/{000503.ldb => 000508.ldb} (97%) rename packs-system/lf-skills/{000501.log => 000531.log} (100%) delete mode 100644 packs-system/lf-skills/MANIFEST-000499 create mode 100644 packs-system/lf-skills/MANIFEST-000529 rename packs-system/lf-spells-miracles/{000198.log => 000226.log} (100%) rename packs-system/lf-spells-miracles/{MANIFEST-000196 => MANIFEST-000224} (72%) rename packs-system/lf-vulnerabilities/{000497.log => 000525.log} (100%) rename packs-system/lf-vulnerabilities/{MANIFEST-000495 => MANIFEST-000523} (72%) create mode 100644 templates/damage-applied-message.hbs create mode 100644 templates/progression-message.hbs diff --git a/css/fvtt-lethal-fantasy.css b/css/fvtt-lethal-fantasy.css index 46a4fa0..f3ba267 100644 --- a/css/fvtt-lethal-fantasy.css +++ b/css/fvtt-lethal-fantasy.css @@ -1901,6 +1901,236 @@ i.lethalfantasy { font-family: var(--font-secondary); font-size: calc(var(--font-size-standard) * 1.2); } +.lethalfantasy .defense-request { + padding: 12px; + background: linear-gradient(to bottom, #3a3930 0%, #2a2920 100%); + border: 2px solid #d4af37; + border-radius: 8px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.4); +} +.lethalfantasy .defense-request h3 { + margin: 0 0 10px 0; + color: #d4af37; + font-size: calc(var(--font-size-standard) * 1.1); + font-weight: 700; + text-align: center; + text-transform: uppercase; + letter-spacing: 1px; +} +.lethalfantasy .defense-request h3 i { + margin-right: 6px; +} +.lethalfantasy .defense-request p { + margin: 8px 0; + color: #f0e6d2; + font-size: calc(var(--font-size-standard) * 0.95); + line-height: 1.4; +} +.lethalfantasy .defense-request p strong { + color: #d4af37; + font-weight: 600; +} +.lethalfantasy .defense-request .defense-prompt { + margin-top: 12px; + padding: 8px; + background: rgba(212, 175, 55, 0.1); + border-left: 3px solid #d4af37; + border-radius: 4px; + font-weight: 600; + color: #d4af37; + text-align: center; +} +.lethalfantasy .defense-request-dialog .attack-info { + padding: 12px; + background: linear-gradient(to bottom, rgba(42, 41, 32, 0.8) 0%, rgba(26, 25, 16, 0.9) 100%); + border: 1px solid rgba(212, 175, 55, 0.5); + border-radius: 6px; + margin-bottom: 16px; +} +.lethalfantasy .defense-request-dialog .attack-info p { + margin: 6px 0; + color: #f0e6d2; + font-size: calc(var(--font-size-standard) * 0.95); +} +.lethalfantasy .defense-request-dialog .attack-info p strong { + color: #d4af37; + font-weight: 600; +} +.lethalfantasy .defense-request-dialog .weapon-selection label { + display: block; + margin-bottom: 8px; + color: #d4af37; + font-weight: 600; + font-size: calc(var(--font-size-standard) * 0.95); +} +.lethalfantasy .defense-request-dialog .weapon-selection select { + width: 100%; + padding: 8px 12px; + background: #3a3930 !important; + border: 1px solid #d4af37; + border-radius: 4px; + color: #ffffff !important; + font-size: calc(var(--font-size-standard) * 0.95); + cursor: pointer; +} +.lethalfantasy .defense-request-dialog .weapon-selection select:focus { + outline: none; + border-color: #f0e6d2; + box-shadow: 0 0 0 2px rgba(212, 175, 55, 0.3); +} +.lethalfantasy .defense-request-dialog .weapon-selection select option { + background: #3a3930 !important; + color: #ffffff !important; + padding: 6px; +} +.lethalfantasy .defense-request-dialog .weapon-selection select option:checked, +.lethalfantasy .defense-request-dialog .weapon-selection select option:hover { + background: #4a4940 !important; + color: #ffffff !important; +} +.lethalfantasy .grit-luck-dialog .combat-status { + padding: 12px; + background: linear-gradient(to bottom, rgba(42, 41, 32, 0.8) 0%, rgba(26, 25, 16, 0.9) 100%); + border: 1px solid rgba(212, 175, 55, 0.5); + border-radius: 6px; + margin-bottom: 16px; +} +.lethalfantasy .grit-luck-dialog .combat-status p { + margin: 6px 0; + color: #f0e6d2; + font-size: calc(var(--font-size-standard) * 0.95); +} +.lethalfantasy .grit-luck-dialog .combat-status p strong { + color: #d4af37; + font-weight: 600; +} +.lethalfantasy .grit-luck-dialog .combat-status .bonus-info { + color: #90EE90; + font-style: italic; + margin-top: 8px; +} +.lethalfantasy .grit-luck-dialog .offer-text { + color: #f0e6d2; + font-size: calc(var(--font-size-standard) * 1); + text-align: center; + font-weight: 600; + margin: 0; +} +.lethalfantasy .attack-result { + padding: 16px; + border-radius: 8px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.4); + margin: 8px 0; +} +.lethalfantasy .attack-result.attack-success { + background: linear-gradient(to bottom, rgba(139, 0, 0, 0.2) 0%, rgba(100, 0, 0, 0.3) 100%); + border: 2px solid rgba(220, 20, 60, 0.6); +} +.lethalfantasy .attack-result.attack-failure { + background: linear-gradient(to bottom, rgba(0, 100, 139, 0.2) 0%, rgba(0, 70, 100, 0.3) 100%); + border: 2px solid rgba(70, 130, 180, 0.6); +} +.lethalfantasy .attack-result h3 { + margin: 0 0 16px 0; + color: #d4af37; + font-size: calc(var(--font-size-standard) * 1.2); + font-weight: 700; + text-align: center; + text-transform: uppercase; + letter-spacing: 1px; +} +.lethalfantasy .attack-result h3 i { + margin-right: 8px; +} +.lethalfantasy .attack-result .combat-comparison { + display: flex; + align-items: center; + justify-content: space-around; + margin-bottom: 16px; + padding: 12px; + background: rgba(0, 0, 0, 0.3); + border-radius: 6px; +} +.lethalfantasy .attack-result .combat-comparison .combat-side { + flex: 1; + text-align: center; + padding: 12px; + border-radius: 6px; +} +.lethalfantasy .attack-result .combat-comparison .combat-side.winner { + background: rgba(0, 255, 0, 0.1); + border: 2px solid rgba(0, 255, 0, 0.4); +} +.lethalfantasy .attack-result .combat-comparison .combat-side.loser { + background: rgba(255, 0, 0, 0.1); + border: 2px solid rgba(255, 0, 0, 0.3); +} +.lethalfantasy .attack-result .combat-comparison .combat-side .side-label { + font-size: calc(var(--font-size-standard) * 0.8); + color: #aaa; + text-transform: uppercase; + letter-spacing: 1px; + margin-bottom: 4px; +} +.lethalfantasy .attack-result .combat-comparison .combat-side .side-name { + font-size: calc(var(--font-size-standard) * 1); + color: #f0e6d2; + font-weight: 600; + margin-bottom: 8px; +} +.lethalfantasy .attack-result .combat-comparison .combat-side .side-roll { + font-size: calc(var(--font-size-standard) * 1.5); + color: #d4af37; + font-weight: 700; +} +.lethalfantasy .attack-result .combat-comparison .combat-vs { + font-size: calc(var(--font-size-standard) * 1.2); + color: #d4af37; + font-weight: 700; + padding: 0 16px; +} +.lethalfantasy .attack-result .combat-result-text { + text-align: center; + font-size: calc(var(--font-size-standard) * 1.1); + color: #f0e6d2; + margin-bottom: 16px; + padding: 12px; + background: rgba(0, 0, 0, 0.2); + border-radius: 6px; +} +.lethalfantasy .attack-result .combat-result-text i { + margin-right: 8px; +} +.lethalfantasy .attack-result .combat-result-text strong { + color: #d4af37; +} +.lethalfantasy .attack-result .attack-result-damage { + display: flex; + gap: 8px; + justify-content: center; +} +.lethalfantasy .attack-result .attack-result-damage .roll-damage-btn { + padding: 10px 16px; + background: linear-gradient(to bottom, #8b0000 0%, #660000 100%); + border: 1px solid #ff0000; + border-radius: 6px; + color: #f0e6d2; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); +} +.lethalfantasy .attack-result .attack-result-damage .roll-damage-btn:hover { + background: linear-gradient(to bottom, #a00000 0%, #7b0000 100%); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.4); + transform: translateY(-2px); +} +.lethalfantasy .attack-result .attack-result-damage .roll-damage-btn:active { + transform: translateY(0); +} +.lethalfantasy .attack-result .attack-result-damage .roll-damage-btn i { + margin-right: 6px; +} .lethalfantasy .equipment-content { font-family: var(--font-primary); font-size: calc(var(--font-size-standard) * 1); @@ -2365,158 +2595,1329 @@ i.lethalfantasy { max-width: 8rem; margin-left: 1rem; } -.dice-roll { +.lethalfantasy.dice-roll, +.fvtt-lethal-fantasy.dice-roll, +.message.lethalfantasy.dice-roll, +.message.fvtt-lethal-fantasy.dice-roll { flex-direction: column; + overflow: hidden; } -.dice-roll .dice-total, -.dice-roll .dice-formula { +.lethalfantasy.dice-roll .dice-total, +.fvtt-lethal-fantasy.dice-roll .dice-total, +.message.lethalfantasy.dice-roll .dice-total, +.message.fvtt-lethal-fantasy.dice-roll .dice-total, +.lethalfantasy.dice-roll .dice-formula, +.fvtt-lethal-fantasy.dice-roll .dice-formula, +.message.lethalfantasy.dice-roll .dice-formula, +.message.fvtt-lethal-fantasy.dice-roll .dice-formula { padding-top: 4px; } -.dice-roll .dice-total { +.lethalfantasy.dice-roll .dice-total, +.fvtt-lethal-fantasy.dice-roll .dice-total, +.message.lethalfantasy.dice-roll .dice-total, +.message.fvtt-lethal-fantasy.dice-roll .dice-total { margin-bottom: 4px; } -.dice-roll .message-header { +.lethalfantasy.dice-roll .message-header, +.fvtt-lethal-fantasy.dice-roll .message-header, +.message.lethalfantasy.dice-roll .message-header, +.message.fvtt-lethal-fantasy.dice-roll .message-header { font-family: var(--font-primary); } -.dice-roll img { - border: 0px; +.lethalfantasy.dice-roll .chat-header, +.fvtt-lethal-fantasy.dice-roll .chat-header, +.message.lethalfantasy.dice-roll .chat-header, +.message.fvtt-lethal-fantasy.dice-roll .chat-header { + background: linear-gradient(135deg, rgba(40, 30, 20, 0.7) 0%, rgba(30, 22, 15, 0.9) 100%); + border-bottom: 2px solid rgba(139, 69, 19, 0.4); + padding: 6px 8px; + margin-bottom: 6px; } -.dice-roll .intro-chat { - border-radius: 20px; +.lethalfantasy.dice-roll .chat-header .character-info, +.fvtt-lethal-fantasy.dice-roll .chat-header .character-info, +.message.lethalfantasy.dice-roll .chat-header .character-info, +.message.fvtt-lethal-fantasy.dice-roll .chat-header .character-info { display: flex; - flex-direction: row; -} -.dice-roll .intro-chat .intro-img { - padding: 4px; - width: 80px; - align-self: center; -} -.dice-roll .intro-chat .intro-right { - display: flex; - flex-direction: column; -} -.dice-roll .intro-chat .intro-right .introText { - font-family: var(--font-secondary); - font-size: calc(var(--font-size-standard) * 1); - width: 210px; - margin-left: 20px; -} -.dice-roll .result { - display: flex; - flex-direction: column; - justify-content: center; align-items: center; - font-size: calc(var(--font-size-standard) * 1); - text-shadow: 0 0 10px var(--color-shadow-primary); + gap: 8px; } -.dice-roll .damage-buttons { +.lethalfantasy.dice-roll .chat-header .character-info .character-avatar, +.fvtt-lethal-fantasy.dice-roll .chat-header .character-info .character-avatar, +.message.lethalfantasy.dice-roll .chat-header .character-info .character-avatar, +.message.fvtt-lethal-fantasy.dice-roll .chat-header .character-info .character-avatar { + flex-shrink: 0; + width: 48px; + height: 48px; + border-radius: 50%; + overflow: hidden; + border: 3px solid rgba(139, 69, 19, 0.6); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4); +} +.lethalfantasy.dice-roll .chat-header .character-info .character-avatar img, +.fvtt-lethal-fantasy.dice-roll .chat-header .character-info .character-avatar img, +.message.lethalfantasy.dice-roll .chat-header .character-info .character-avatar img, +.message.fvtt-lethal-fantasy.dice-roll .chat-header .character-info .character-avatar img { width: 100%; - padding: 8px; - margin-top: 8px; + height: 100%; + object-fit: cover; + border: 0; } -.dice-roll .damage-buttons .damage-buttons-title { +.lethalfantasy.dice-roll .chat-header .character-info .character-details, +.fvtt-lethal-fantasy.dice-roll .chat-header .character-info .character-details, +.message.lethalfantasy.dice-roll .chat-header .character-info .character-details, +.message.fvtt-lethal-fantasy.dice-roll .chat-header .character-info .character-details { + flex-grow: 1; +} +.lethalfantasy.dice-roll .chat-header .character-info .character-details .character-name, +.fvtt-lethal-fantasy.dice-roll .chat-header .character-info .character-details .character-name, +.message.lethalfantasy.dice-roll .chat-header .character-info .character-details .character-name, +.message.fvtt-lethal-fantasy.dice-roll .chat-header .character-info .character-details .character-name { + font-family: var(--font-primary); + font-size: calc(var(--font-size-standard) * 1.2); font-weight: bold; - margin-bottom: 8px; - font-size: calc(var(--font-size-standard) * 0.95); - text-align: center; + color: #f0e6d2; + text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.8); + margin-bottom: 4px; } -.dice-roll .damage-buttons .damage-buttons-grid { - display: grid; - grid-template-columns: repeat(2, 1fr); +.lethalfantasy.dice-roll .chat-header .character-info .character-details .roll-type-badge, +.fvtt-lethal-fantasy.dice-roll .chat-header .character-info .character-details .roll-type-badge, +.message.lethalfantasy.dice-roll .chat-header .character-info .character-details .roll-type-badge, +.message.fvtt-lethal-fantasy.dice-roll .chat-header .character-info .character-details .roll-type-badge { + display: inline-flex; + align-items: center; gap: 6px; - padding: 8px; - background-color: rgba(0, 0, 0, 0.1); - border-radius: 5px; + padding: 4px 10px; + background: rgba(139, 69, 19, 0.3); + border: 1px solid rgba(139, 69, 19, 0.5); + border-radius: 12px; + font-size: calc(var(--font-size-standard) * 0.9); + color: #d4c5a9; } -.dice-roll .damage-buttons .damage-buttons-grid.monster-damage { - grid-template-columns: 1fr; - justify-items: center; +.lethalfantasy.dice-roll .chat-header .character-info .character-details .roll-type-badge i, +.fvtt-lethal-fantasy.dice-roll .chat-header .character-info .character-details .roll-type-badge i, +.message.lethalfantasy.dice-roll .chat-header .character-info .character-details .roll-type-badge i, +.message.fvtt-lethal-fantasy.dice-roll .chat-header .character-info .character-details .roll-type-badge i { + font-size: calc(var(--font-size-standard) * 1); } -.dice-roll .damage-buttons .damage-buttons-grid .damage-roll-btn { - padding: 6px 10px; - background: linear-gradient(to bottom, #8b4513 0%, #6b3410 100%); - border: 1px solid #4b2408; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); - color: #f0f0e0; - cursor: pointer; +.lethalfantasy.dice-roll .roll-details, +.fvtt-lethal-fantasy.dice-roll .roll-details, +.message.lethalfantasy.dice-roll .roll-details, +.message.fvtt-lethal-fantasy.dice-roll .roll-details { + display: flex; + flex-wrap: wrap; + gap: 4px; + padding: 4px 6px; + background: rgba(0, 0, 0, 0.1); + border-radius: 4px; + margin: 0 6px 6px 6px; +} +.lethalfantasy.dice-roll .roll-details .detail-item, +.fvtt-lethal-fantasy.dice-roll .roll-details .detail-item, +.message.lethalfantasy.dice-roll .roll-details .detail-item, +.message.fvtt-lethal-fantasy.dice-roll .roll-details .detail-item { + display: flex; + align-items: center; + gap: 5px; + padding: 4px 8px; + background: rgba(255, 255, 255, 0.05); border-radius: 4px; font-size: calc(var(--font-size-standard) * 0.85); +} +.lethalfantasy.dice-roll .roll-details .detail-item i, +.fvtt-lethal-fantasy.dice-roll .roll-details .detail-item i, +.message.lethalfantasy.dice-roll .roll-details .detail-item i, +.message.fvtt-lethal-fantasy.dice-roll .roll-details .detail-item i { + opacity: 0.7; +} +.lethalfantasy.dice-roll .roll-details .detail-item.weapon-name, +.fvtt-lethal-fantasy.dice-roll .roll-details .detail-item.weapon-name, +.message.lethalfantasy.dice-roll .roll-details .detail-item.weapon-name, +.message.fvtt-lethal-fantasy.dice-roll .roll-details .detail-item.weapon-name { font-weight: 500; + color: #3a3a2a; +} +.lethalfantasy.dice-roll .roll-details .detail-item.other-result, +.fvtt-lethal-fantasy.dice-roll .roll-details .detail-item.other-result, +.message.lethalfantasy.dice-roll .roll-details .detail-item.other-result, +.message.fvtt-lethal-fantasy.dice-roll .roll-details .detail-item.other-result { + flex-basis: 100%; + font-style: italic; + color: #e0d5c0; +} +.lethalfantasy.dice-roll .roll-details .detail-badge, +.fvtt-lethal-fantasy.dice-roll .roll-details .detail-badge, +.message.lethalfantasy.dice-roll .roll-details .detail-badge, +.message.fvtt-lethal-fantasy.dice-roll .roll-details .detail-badge { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 4px 10px; + border-radius: 12px; + font-size: calc(var(--font-size-standard) * 0.8); + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; +} +.lethalfantasy.dice-roll .roll-details .detail-badge.favor-badge, +.fvtt-lethal-fantasy.dice-roll .roll-details .detail-badge.favor-badge, +.message.lethalfantasy.dice-roll .roll-details .detail-badge.favor-badge, +.message.fvtt-lethal-fantasy.dice-roll .roll-details .detail-badge.favor-badge { + background: linear-gradient(135deg, rgba(34, 139, 34, 0.3) 0%, rgba(0, 100, 0, 0.4) 100%); + border: 1px solid rgba(34, 139, 34, 0.6); + color: #90ee90; + text-shadow: 0 0 4px rgba(34, 139, 34, 0.8); +} +.lethalfantasy.dice-roll .roll-details .detail-badge.favor-badge i, +.fvtt-lethal-fantasy.dice-roll .roll-details .detail-badge.favor-badge i, +.message.lethalfantasy.dice-roll .roll-details .detail-badge.favor-badge i, +.message.fvtt-lethal-fantasy.dice-roll .roll-details .detail-badge.favor-badge i { + color: #98fb98; + animation: sparkle 2s ease-in-out infinite; +} +.lethalfantasy.dice-roll .roll-details .detail-badge.disfavor-badge, +.fvtt-lethal-fantasy.dice-roll .roll-details .detail-badge.disfavor-badge, +.message.lethalfantasy.dice-roll .roll-details .detail-badge.disfavor-badge, +.message.fvtt-lethal-fantasy.dice-roll .roll-details .detail-badge.disfavor-badge { + background: linear-gradient(135deg, rgba(139, 0, 0, 0.3) 0%, rgba(100, 0, 0, 0.4) 100%); + border: 1px solid rgba(139, 0, 0, 0.6); + color: #ff6b6b; + text-shadow: 0 0 4px rgba(139, 0, 0, 0.8); +} +.lethalfantasy.dice-roll .roll-details .detail-badge.disfavor-badge i, +.fvtt-lethal-fantasy.dice-roll .roll-details .detail-badge.disfavor-badge i, +.message.lethalfantasy.dice-roll .roll-details .detail-badge.disfavor-badge i, +.message.fvtt-lethal-fantasy.dice-roll .roll-details .detail-badge.disfavor-badge i { + color: #ff5252; +} +.lethalfantasy.dice-roll .roll-details .detail-badge.special-badge, +.fvtt-lethal-fantasy.dice-roll .roll-details .detail-badge.special-badge, +.message.lethalfantasy.dice-roll .roll-details .detail-badge.special-badge, +.message.fvtt-lethal-fantasy.dice-roll .roll-details .detail-badge.special-badge { + background: linear-gradient(135deg, rgba(218, 165, 32, 0.3) 0%, rgba(184, 134, 11, 0.4) 100%); + border: 1px solid rgba(218, 165, 32, 0.6); + color: #ffd700; + text-shadow: 0 0 4px rgba(218, 165, 32, 0.8); +} +.lethalfantasy.dice-roll .roll-details .detail-badge.damage-badge, +.fvtt-lethal-fantasy.dice-roll .roll-details .detail-badge.damage-badge, +.message.lethalfantasy.dice-roll .roll-details .detail-badge.damage-badge, +.message.fvtt-lethal-fantasy.dice-roll .roll-details .detail-badge.damage-badge { + background: linear-gradient(135deg, rgba(178, 34, 34, 0.3) 0%, rgba(139, 0, 0, 0.4) 100%); + border: 1px solid rgba(178, 34, 34, 0.6); + color: #ff8080; +} +.lethalfantasy.dice-roll .formula-section, +.fvtt-lethal-fantasy.dice-roll .formula-section, +.message.lethalfantasy.dice-roll .formula-section, +.message.fvtt-lethal-fantasy.dice-roll .formula-section { + padding: 4px 6px; + margin: 0 6px 6px 6px; + background: rgba(0, 0, 0, 0.15); + border-radius: 4px; + border: 1px solid rgba(139, 69, 19, 0.3); +} +.lethalfantasy.dice-roll .formula-section .formula-display, +.fvtt-lethal-fantasy.dice-roll .formula-section .formula-display, +.message.lethalfantasy.dice-roll .formula-section .formula-display, +.message.fvtt-lethal-fantasy.dice-roll .formula-section .formula-display { + display: flex; + align-items: center; + gap: 6px; + margin-bottom: 4px; + padding-bottom: 4px; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); +} +.lethalfantasy.dice-roll .formula-section .formula-display i, +.fvtt-lethal-fantasy.dice-roll .formula-section .formula-display i, +.message.lethalfantasy.dice-roll .formula-section .formula-display i, +.message.fvtt-lethal-fantasy.dice-roll .formula-section .formula-display i { + color: #d4af37; + font-size: calc(var(--font-size-standard) * 1); +} +.lethalfantasy.dice-roll .formula-section .formula-display .formula-text, +.fvtt-lethal-fantasy.dice-roll .formula-section .formula-display .formula-text, +.message.lethalfantasy.dice-roll .formula-section .formula-display .formula-text, +.message.fvtt-lethal-fantasy.dice-roll .formula-section .formula-display .formula-text { + font-family: var(--font-primary); + font-size: calc(var(--font-size-standard) * 0.95); + font-weight: 500; + color: #3a3a2a; +} +.lethalfantasy.dice-roll .formula-section .dice-breakdown, +.fvtt-lethal-fantasy.dice-roll .formula-section .dice-breakdown, +.message.lethalfantasy.dice-roll .formula-section .dice-breakdown, +.message.fvtt-lethal-fantasy.dice-roll .formula-section .dice-breakdown { + display: flex; + flex-wrap: wrap; + gap: 8px; +} +.lethalfantasy.dice-roll .formula-section .dice-breakdown .dice-item, +.fvtt-lethal-fantasy.dice-roll .formula-section .dice-breakdown .dice-item, +.message.lethalfantasy.dice-roll .formula-section .dice-breakdown .dice-item, +.message.fvtt-lethal-fantasy.dice-roll .formula-section .dice-breakdown .dice-item { + display: flex; + align-items: center; + gap: 6px; + padding: 4px 10px; + background: rgba(139, 69, 19, 0.2); + border-radius: 4px; + border: 1px solid rgba(139, 69, 19, 0.3); +} +.lethalfantasy.dice-roll .formula-section .dice-breakdown .dice-item .dice-type, +.fvtt-lethal-fantasy.dice-roll .formula-section .dice-breakdown .dice-item .dice-type, +.message.lethalfantasy.dice-roll .formula-section .dice-breakdown .dice-item .dice-type, +.message.fvtt-lethal-fantasy.dice-roll .formula-section .dice-breakdown .dice-item .dice-type { + font-weight: 600; + color: #2a2a1a; + font-size: calc(var(--font-size-standard) * 0.85); +} +.lethalfantasy.dice-roll .formula-section .dice-breakdown .dice-item .dice-separator, +.fvtt-lethal-fantasy.dice-roll .formula-section .dice-breakdown .dice-item .dice-separator, +.message.lethalfantasy.dice-roll .formula-section .dice-breakdown .dice-item .dice-separator, +.message.fvtt-lethal-fantasy.dice-roll .formula-section .dice-breakdown .dice-item .dice-separator { + color: rgba(255, 255, 255, 0.4); + font-weight: 300; +} +.lethalfantasy.dice-roll .formula-section .dice-breakdown .dice-item .dice-value, +.fvtt-lethal-fantasy.dice-roll .formula-section .dice-breakdown .dice-item .dice-value, +.message.lethalfantasy.dice-roll .formula-section .dice-breakdown .dice-item .dice-value, +.message.fvtt-lethal-fantasy.dice-roll .formula-section .dice-breakdown .dice-item .dice-value { + font-weight: bold; + color: #ffd700; + font-size: calc(var(--font-size-standard) * 0.95); +} +.lethalfantasy.dice-roll .result-section, +.fvtt-lethal-fantasy.dice-roll .result-section, +.message.lethalfantasy.dice-roll .result-section, +.message.fvtt-lethal-fantasy.dice-roll .result-section { + margin: 0 6px 6px 6px; +} +.lethalfantasy.dice-roll .result-section .main-result, +.fvtt-lethal-fantasy.dice-roll .result-section .main-result, +.message.lethalfantasy.dice-roll .result-section .main-result, +.message.fvtt-lethal-fantasy.dice-roll .result-section .main-result { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + padding: 6px 10px; + background: linear-gradient(135deg, rgba(40, 30, 20, 0.6) 0%, rgba(20, 15, 10, 0.8) 100%); + border-radius: 6px; + border: 2px solid rgba(139, 69, 19, 0.5); + margin-bottom: 6px; +} +.lethalfantasy.dice-roll .result-section .main-result .result-label, +.fvtt-lethal-fantasy.dice-roll .result-section .main-result .result-label, +.message.lethalfantasy.dice-roll .result-section .main-result .result-label, +.message.fvtt-lethal-fantasy.dice-roll .result-section .main-result .result-label { + font-size: calc(var(--font-size-standard) * 0.9); + text-transform: uppercase; + letter-spacing: 0.5px; + color: #c9b896; + margin: 0; +} +.lethalfantasy.dice-roll .result-section .main-result .result-value, +.fvtt-lethal-fantasy.dice-roll .result-section .main-result .result-value, +.message.lethalfantasy.dice-roll .result-section .main-result .result-value, +.message.fvtt-lethal-fantasy.dice-roll .result-section .main-result .result-value { + font-family: var(--font-primary); + font-size: calc(var(--font-size-standard) * 1.6); + font-weight: bold; + text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8); +} +.lethalfantasy.dice-roll .result-section .main-result .result-value.success, +.fvtt-lethal-fantasy.dice-roll .result-section .main-result .result-value.success, +.message.lethalfantasy.dice-roll .result-section .main-result .result-value.success, +.message.fvtt-lethal-fantasy.dice-roll .result-section .main-result .result-value.success { + color: #90ee90; + text-shadow: 0 0 10px rgba(34, 139, 34, 0.8), 2px 2px 4px rgba(0, 0, 0, 0.8); +} +.lethalfantasy.dice-roll .result-section .main-result .result-value.failure, +.fvtt-lethal-fantasy.dice-roll .result-section .main-result .result-value.failure, +.message.lethalfantasy.dice-roll .result-section .main-result .result-value.failure, +.message.fvtt-lethal-fantasy.dice-roll .result-section .main-result .result-value.failure { + color: #ff6b6b; + text-shadow: 0 0 10px rgba(139, 0, 0, 0.8), 2px 2px 4px rgba(0, 0, 0, 0.8); +} +.lethalfantasy.dice-roll .result-section .d30-result, +.fvtt-lethal-fantasy.dice-roll .result-section .d30-result, +.message.lethalfantasy.dice-roll .result-section .d30-result, +.message.fvtt-lethal-fantasy.dice-roll .result-section .d30-result { + background: linear-gradient(135deg, rgba(75, 0, 130, 0.2) 0%, rgba(138, 43, 226, 0.3) 100%); + border: 2px solid rgba(138, 43, 226, 0.5); + border-radius: 6px; + padding: 6px; + margin-bottom: 6px; +} +.lethalfantasy.dice-roll .result-section .d30-result .d30-header, +.fvtt-lethal-fantasy.dice-roll .result-section .d30-result .d30-header, +.message.lethalfantasy.dice-roll .result-section .d30-result .d30-header, +.message.fvtt-lethal-fantasy.dice-roll .result-section .d30-result .d30-header { + display: flex; + align-items: center; + gap: 6px; + padding: 4px 6px; + background: rgba(0, 0, 0, 0.2); + border-radius: 4px; + margin-bottom: 4px; +} +.lethalfantasy.dice-roll .result-section .d30-result .d30-header i, +.fvtt-lethal-fantasy.dice-roll .result-section .d30-result .d30-header i, +.message.lethalfantasy.dice-roll .result-section .d30-result .d30-header i, +.message.fvtt-lethal-fantasy.dice-roll .result-section .d30-result .d30-header i { + color: #da70d6; + font-size: calc(var(--font-size-standard) * 1.2); +} +.lethalfantasy.dice-roll .result-section .d30-result .d30-header .d30-label, +.fvtt-lethal-fantasy.dice-roll .result-section .d30-result .d30-header .d30-label, +.message.lethalfantasy.dice-roll .result-section .d30-result .d30-header .d30-label, +.message.fvtt-lethal-fantasy.dice-roll .result-section .d30-result .d30-header .d30-label { + flex-grow: 1; + font-weight: bold; + color: #e0d5f0; + text-transform: uppercase; + letter-spacing: 0.5px; + font-size: calc(var(--font-size-standard) * 0.9); +} +.lethalfantasy.dice-roll .result-section .d30-result .d30-header .d30-value, +.fvtt-lethal-fantasy.dice-roll .result-section .d30-result .d30-header .d30-value, +.message.lethalfantasy.dice-roll .result-section .d30-result .d30-header .d30-value, +.message.fvtt-lethal-fantasy.dice-roll .result-section .d30-result .d30-header .d30-value { + font-family: var(--font-primary); + font-size: calc(var(--font-size-standard) * 1.5); + font-weight: bold; + color: #ff69b4; + background: rgba(255, 105, 180, 0.1); + padding: 4px 12px; + border-radius: 12px; + border: 1px solid rgba(255, 105, 180, 0.3); +} +.lethalfantasy.dice-roll .result-section .d30-result .d30-message, +.fvtt-lethal-fantasy.dice-roll .result-section .d30-result .d30-message, +.message.lethalfantasy.dice-roll .result-section .d30-result .d30-message, +.message.fvtt-lethal-fantasy.dice-roll .result-section .d30-result .d30-message { + display: flex; + align-items: flex-start; + gap: 6px; + padding: 6px 8px; + background: rgba(230, 220, 255, 0.2); + border-left: 3px solid #da70d6; + border-radius: 4px; + font-style: italic; + color: #2a2a3a; + line-height: 1.4; +} +.lethalfantasy.dice-roll .result-section .d30-result .d30-message i, +.fvtt-lethal-fantasy.dice-roll .result-section .d30-result .d30-message i, +.message.lethalfantasy.dice-roll .result-section .d30-result .d30-message i, +.message.fvtt-lethal-fantasy.dice-roll .result-section .d30-result .d30-message i { + color: #ff69b4; + margin-top: 2px; + flex-shrink: 0; +} +.lethalfantasy.dice-roll .result-section .d30-result .d30-message span, +.fvtt-lethal-fantasy.dice-roll .result-section .d30-result .d30-message span, +.message.lethalfantasy.dice-roll .result-section .d30-result .d30-message span, +.message.fvtt-lethal-fantasy.dice-roll .result-section .d30-result .d30-message span { + flex-grow: 1; +} +.lethalfantasy.dice-roll .outcome-badge, +.fvtt-lethal-fantasy.dice-roll .outcome-badge, +.message.lethalfantasy.dice-roll .outcome-badge, +.message.fvtt-lethal-fantasy.dice-roll .outcome-badge { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + padding: 10px 15px; + margin: 0 8px 10px 8px; + border-radius: 6px; + font-weight: bold; + font-size: calc(var(--font-size-standard) * 1.1); + text-transform: uppercase; + letter-spacing: 1px; +} +.lethalfantasy.dice-roll .outcome-badge.success-outcome, +.fvtt-lethal-fantasy.dice-roll .outcome-badge.success-outcome, +.message.lethalfantasy.dice-roll .outcome-badge.success-outcome, +.message.fvtt-lethal-fantasy.dice-roll .outcome-badge.success-outcome { + background: linear-gradient(135deg, rgba(34, 139, 34, 0.3) 0%, rgba(0, 100, 0, 0.4) 100%); + border: 2px solid rgba(34, 139, 34, 0.6); + color: #90ee90; + text-shadow: 0 0 8px #228b22; +} +.lethalfantasy.dice-roll .outcome-badge.success-outcome i, +.fvtt-lethal-fantasy.dice-roll .outcome-badge.success-outcome i, +.message.lethalfantasy.dice-roll .outcome-badge.success-outcome i, +.message.fvtt-lethal-fantasy.dice-roll .outcome-badge.success-outcome i { + font-size: calc(var(--font-size-standard) * 1.3); +} +.lethalfantasy.dice-roll .outcome-badge.failure-outcome, +.fvtt-lethal-fantasy.dice-roll .outcome-badge.failure-outcome, +.message.lethalfantasy.dice-roll .outcome-badge.failure-outcome, +.message.fvtt-lethal-fantasy.dice-roll .outcome-badge.failure-outcome { + background: linear-gradient(135deg, rgba(139, 0, 0, 0.3) 0%, rgba(100, 0, 0, 0.4) 100%); + border: 2px solid rgba(139, 0, 0, 0.6); + color: #ff6b6b; + text-shadow: 0 0 8px #8b0000; +} +.lethalfantasy.dice-roll .outcome-badge.failure-outcome i, +.fvtt-lethal-fantasy.dice-roll .outcome-badge.failure-outcome i, +.message.lethalfantasy.dice-roll .outcome-badge.failure-outcome i, +.message.fvtt-lethal-fantasy.dice-roll .outcome-badge.failure-outcome i { + font-size: calc(var(--font-size-standard) * 1.3); +} +.lethalfantasy.dice-roll .outcome-badge.failure-outcome .resource-lost, +.fvtt-lethal-fantasy.dice-roll .outcome-badge.failure-outcome .resource-lost, +.message.lethalfantasy.dice-roll .outcome-badge.failure-outcome .resource-lost, +.message.fvtt-lethal-fantasy.dice-roll .outcome-badge.failure-outcome .resource-lost { + font-size: calc(var(--font-size-standard) * 0.85); + opacity: 0.9; +} +.lethalfantasy.dice-roll .target-info, +.fvtt-lethal-fantasy.dice-roll .target-info, +.message.lethalfantasy.dice-roll .target-info, +.message.fvtt-lethal-fantasy.dice-roll .target-info { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 10px; + margin: 0 6px 6px 6px; + background: linear-gradient(135deg, rgba(178, 34, 34, 0.2) 0%, rgba(139, 0, 0, 0.3) 100%); + border-left: 3px solid #dc143c; + border-radius: 6px; + font-size: calc(var(--font-size-standard) * 0.9); + font-weight: 500; + color: #2a1a1a; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); +} +.lethalfantasy.dice-roll .target-info i, +.fvtt-lethal-fantasy.dice-roll .target-info i, +.message.lethalfantasy.dice-roll .target-info i, +.message.fvtt-lethal-fantasy.dice-roll .target-info i { + color: #dc143c; + font-size: calc(var(--font-size-standard) * 1.1); + filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.3)); +} +.lethalfantasy.dice-roll .target-info strong, +.fvtt-lethal-fantasy.dice-roll .target-info strong, +.message.lethalfantasy.dice-roll .target-info strong, +.message.fvtt-lethal-fantasy.dice-roll .target-info strong { + color: #8b0000; + font-weight: 700; +} +.lethalfantasy.dice-roll .private-result, +.fvtt-lethal-fantasy.dice-roll .private-result, +.message.lethalfantasy.dice-roll .private-result, +.message.fvtt-lethal-fantasy.dice-roll .private-result { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + padding: 20px; + margin: 0 8px 10px 8px; + background: rgba(80, 80, 80, 0.3); + border: 2px dashed rgba(120, 120, 120, 0.5); + border-radius: 8px; + color: #b0b0b0; + font-style: italic; +} +.lethalfantasy.dice-roll .private-result i, +.fvtt-lethal-fantasy.dice-roll .private-result i, +.message.lethalfantasy.dice-roll .private-result i, +.message.fvtt-lethal-fantasy.dice-roll .private-result i { + font-size: calc(var(--font-size-standard) * 1.2); +} +@keyframes sparkle { + 0%, + 100% { + opacity: 1; + transform: scale(1); + } + 50% { + opacity: 0.7; + transform: scale(1.2); + } +} +.lethalfantasy.dice-roll img, +.fvtt-lethal-fantasy.dice-roll img, +.message.lethalfantasy.dice-roll img, +.message.fvtt-lethal-fantasy.dice-roll img { + border: 0px; +} +.lethalfantasy.dice-roll .damage-buttons, +.fvtt-lethal-fantasy.dice-roll .damage-buttons, +.message.lethalfantasy.dice-roll .damage-buttons, +.message.fvtt-lethal-fantasy.dice-roll .damage-buttons { + width: calc(100% - 16px); + padding: 10px; + margin: 0 8px 10px 8px; + background: linear-gradient(135deg, rgba(139, 69, 19, 0.15) 0%, rgba(101, 50, 14, 0.2) 100%); + border: 1px solid rgba(139, 69, 19, 0.3); + border-radius: 8px; +} +.lethalfantasy.dice-roll .damage-buttons .damage-buttons-title, +.fvtt-lethal-fantasy.dice-roll .damage-buttons .damage-buttons-title, +.message.lethalfantasy.dice-roll .damage-buttons .damage-buttons-title, +.message.fvtt-lethal-fantasy.dice-roll .damage-buttons .damage-buttons-title { + font-weight: bold; + margin-bottom: 10px; + font-size: calc(var(--font-size-standard) * 1); + text-align: center; + color: #d4af37; + text-transform: uppercase; + letter-spacing: 1px; + text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5); +} +.lethalfantasy.dice-roll .damage-buttons .damage-buttons-grid, +.fvtt-lethal-fantasy.dice-roll .damage-buttons .damage-buttons-grid, +.message.lethalfantasy.dice-roll .damage-buttons .damage-buttons-grid, +.message.fvtt-lethal-fantasy.dice-roll .damage-buttons .damage-buttons-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 8px; +} +.lethalfantasy.dice-roll .damage-buttons .damage-buttons-grid.monster-damage, +.fvtt-lethal-fantasy.dice-roll .damage-buttons .damage-buttons-grid.monster-damage, +.message.lethalfantasy.dice-roll .damage-buttons .damage-buttons-grid.monster-damage, +.message.fvtt-lethal-fantasy.dice-roll .damage-buttons .damage-buttons-grid.monster-damage { + grid-template-columns: 1fr; + max-width: 280px; + margin: 0 auto; +} +.lethalfantasy.dice-roll .damage-buttons .damage-buttons-grid .damage-roll-btn, +.fvtt-lethal-fantasy.dice-roll .damage-buttons .damage-buttons-grid .damage-roll-btn, +.message.lethalfantasy.dice-roll .damage-buttons .damage-buttons-grid .damage-roll-btn, +.message.fvtt-lethal-fantasy.dice-roll .damage-buttons .damage-buttons-grid .damage-roll-btn { + padding: 10px 14px; + background: linear-gradient(to bottom, #8b4513 0%, #6b3410 100%); + border: 1px solid #4b2408; + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.1); + color: #f0e6d2; + cursor: pointer; + border-radius: 6px; + font-size: calc(var(--font-size-standard) * 0.9); + font-weight: 600; text-align: center; transition: all 0.2s ease; display: flex; align-items: center; justify-content: center; - gap: 4px; + gap: 6px; + position: relative; + overflow: hidden; } -.dice-roll .damage-buttons .damage-buttons-grid .damage-roll-btn i { - font-size: calc(var(--font-size-standard) * 1); +.lethalfantasy.dice-roll .damage-buttons .damage-buttons-grid .damage-roll-btn::before, +.fvtt-lethal-fantasy.dice-roll .damage-buttons .damage-buttons-grid .damage-roll-btn::before, +.message.lethalfantasy.dice-roll .damage-buttons .damage-buttons-grid .damage-roll-btn::before, +.message.fvtt-lethal-fantasy.dice-roll .damage-buttons .damage-buttons-grid .damage-roll-btn::before { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); + transition: left 0.5s; } -.dice-roll .damage-buttons .damage-buttons-grid .damage-roll-btn:hover { +.lethalfantasy.dice-roll .damage-buttons .damage-buttons-grid .damage-roll-btn:hover::before, +.fvtt-lethal-fantasy.dice-roll .damage-buttons .damage-buttons-grid .damage-roll-btn:hover::before, +.message.lethalfantasy.dice-roll .damage-buttons .damage-buttons-grid .damage-roll-btn:hover::before, +.message.fvtt-lethal-fantasy.dice-roll .damage-buttons .damage-buttons-grid .damage-roll-btn:hover::before { + left: 100%; +} +.lethalfantasy.dice-roll .damage-buttons .damage-buttons-grid .damage-roll-btn i, +.fvtt-lethal-fantasy.dice-roll .damage-buttons .damage-buttons-grid .damage-roll-btn i, +.message.lethalfantasy.dice-roll .damage-buttons .damage-buttons-grid .damage-roll-btn i, +.message.fvtt-lethal-fantasy.dice-roll .damage-buttons .damage-buttons-grid .damage-roll-btn i { + font-size: calc(var(--font-size-standard) * 1.1); + filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5)); +} +.lethalfantasy.dice-roll .damage-buttons .damage-buttons-grid .damage-roll-btn:hover, +.fvtt-lethal-fantasy.dice-roll .damage-buttons .damage-buttons-grid .damage-roll-btn:hover, +.message.lethalfantasy.dice-roll .damage-buttons .damage-buttons-grid .damage-roll-btn:hover, +.message.fvtt-lethal-fantasy.dice-roll .damage-buttons .damage-buttons-grid .damage-roll-btn:hover { background: linear-gradient(to bottom, #9b5523 0%, #7b4420 100%); - box-shadow: 0 3px 6px rgba(0, 0, 0, 0.4); - transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.15); + transform: translateY(-2px); border-color: #5b3418; } -.dice-roll .damage-buttons .damage-buttons-grid .damage-roll-btn:active { +.lethalfantasy.dice-roll .damage-buttons .damage-buttons-grid .damage-roll-btn:active, +.fvtt-lethal-fantasy.dice-roll .damage-buttons .damage-buttons-grid .damage-roll-btn:active, +.message.lethalfantasy.dice-roll .damage-buttons .damage-buttons-grid .damage-roll-btn:active, +.message.fvtt-lethal-fantasy.dice-roll .damage-buttons .damage-buttons-grid .damage-roll-btn:active { transform: translateY(0); - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4), inset 0 1px 3px rgba(0, 0, 0, 0.3); } -.dice-roll .damage-result { - width: 100%; - padding: 8px; +.lethalfantasy.dice-roll .damage-result, +.fvtt-lethal-fantasy.dice-roll .damage-result, +.message.lethalfantasy.dice-roll .damage-result, +.message.fvtt-lethal-fantasy.dice-roll .damage-result { + width: calc(100% - 16px); + padding: 10px; + margin: 0 8px 10px 8px; + background: linear-gradient(135deg, rgba(80, 80, 80, 0.15) 0%, rgba(60, 60, 60, 0.2) 100%); + border: 1px solid rgba(100, 100, 100, 0.3); + border-radius: 8px; } -.dice-roll .damage-result ul { +.lethalfantasy.dice-roll .damage-result.auto-applied, +.fvtt-lethal-fantasy.dice-roll .damage-result.auto-applied, +.message.lethalfantasy.dice-roll .damage-result.auto-applied, +.message.fvtt-lethal-fantasy.dice-roll .damage-result.auto-applied { + background: linear-gradient(135deg, rgba(0, 100, 0, 0.15) 0%, rgba(0, 70, 0, 0.2) 100%); + border: 1px solid rgba(0, 150, 0, 0.4); +} +.lethalfantasy.dice-roll .damage-result .auto-damage-notice, +.fvtt-lethal-fantasy.dice-roll .damage-result .auto-damage-notice, +.message.lethalfantasy.dice-roll .damage-result .auto-damage-notice, +.message.fvtt-lethal-fantasy.dice-roll .damage-result .auto-damage-notice { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + color: #1a1a1a; + font-weight: 600; + font-size: calc(var(--font-size-standard) * 0.9); +} +.lethalfantasy.dice-roll .damage-result .auto-damage-notice i, +.fvtt-lethal-fantasy.dice-roll .damage-result .auto-damage-notice i, +.message.lethalfantasy.dice-roll .damage-result .auto-damage-notice i, +.message.fvtt-lethal-fantasy.dice-roll .damage-result .auto-damage-notice i { + font-size: calc(var(--font-size-standard) * 1.2); +} +.lethalfantasy.dice-roll .damage-result ul, +.fvtt-lethal-fantasy.dice-roll .damage-result ul, +.message.lethalfantasy.dice-roll .damage-result ul, +.message.fvtt-lethal-fantasy.dice-roll .damage-result ul { padding: 0; margin: 0; } -.dice-roll .damage-result .li-apply-wounds { +.lethalfantasy.dice-roll .damage-result .li-apply-wounds, +.fvtt-lethal-fantasy.dice-roll .damage-result .li-apply-wounds, +.message.lethalfantasy.dice-roll .damage-result .li-apply-wounds, +.message.fvtt-lethal-fantasy.dice-roll .damage-result .li-apply-wounds { list-style: none; - margin-top: 10px; + margin: 0; padding: 0; display: none; } -.dice-roll .damage-result .li-apply-wounds > div:first-child { +.lethalfantasy.dice-roll .damage-result .li-apply-wounds > div:first-child, +.fvtt-lethal-fantasy.dice-roll .damage-result .li-apply-wounds > div:first-child, +.message.lethalfantasy.dice-roll .damage-result .li-apply-wounds > div:first-child, +.message.fvtt-lethal-fantasy.dice-roll .damage-result .li-apply-wounds > div:first-child { font-weight: bold; - margin-bottom: 8px; - font-size: calc(var(--font-size-standard) * 0.95); + margin-bottom: 10px; + font-size: calc(var(--font-size-standard) * 1); text-align: center; + color: #d4af37; + text-transform: uppercase; + letter-spacing: 1px; } -.dice-roll .damage-result .li-apply-wounds .combatants-grid { +.lethalfantasy.dice-roll .damage-result .li-apply-wounds .combatants-grid, +.fvtt-lethal-fantasy.dice-roll .damage-result .li-apply-wounds .combatants-grid, +.message.lethalfantasy.dice-roll .damage-result .li-apply-wounds .combatants-grid, +.message.fvtt-lethal-fantasy.dice-roll .damage-result .li-apply-wounds .combatants-grid { display: grid; grid-template-columns: repeat(2, 1fr); - gap: 6px; - margin-top: 5px; - padding: 8px; - background-color: rgba(0, 0, 0, 0.1); - border-radius: 5px; + gap: 8px; } -.dice-roll .damage-result .li-apply-wounds .combatants-grid .apply-wounds-btn { - padding: 6px 10px; +.lethalfantasy.dice-roll .damage-result .li-apply-wounds .combatants-grid .apply-wounds-btn, +.fvtt-lethal-fantasy.dice-roll .damage-result .li-apply-wounds .combatants-grid .apply-wounds-btn, +.message.lethalfantasy.dice-roll .damage-result .li-apply-wounds .combatants-grid .apply-wounds-btn, +.message.fvtt-lethal-fantasy.dice-roll .damage-result .li-apply-wounds .combatants-grid .apply-wounds-btn, +.lethalfantasy.dice-roll .damage-result .li-apply-wounds .combatants-grid .request-defense-btn, +.fvtt-lethal-fantasy.dice-roll .damage-result .li-apply-wounds .combatants-grid .request-defense-btn, +.message.lethalfantasy.dice-roll .damage-result .li-apply-wounds .combatants-grid .request-defense-btn, +.message.fvtt-lethal-fantasy.dice-roll .damage-result .li-apply-wounds .combatants-grid .request-defense-btn { + padding: 8px 12px; background: linear-gradient(to bottom, #5a5850 0%, #4b4a44 100%); border: 1px solid #2b2a24; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); - color: #f0f0e0; + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.08); + color: #f0e6d2; cursor: pointer; - border-radius: 4px; + border-radius: 6px; font-size: calc(var(--font-size-standard) * 0.85); - font-weight: 500; + font-weight: 600; text-align: center; transition: all 0.2s ease; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; + position: relative; } -.dice-roll .damage-result .li-apply-wounds .combatants-grid .apply-wounds-btn:hover { +.lethalfantasy.dice-roll .damage-result .li-apply-wounds .combatants-grid .apply-wounds-btn::after, +.fvtt-lethal-fantasy.dice-roll .damage-result .li-apply-wounds .combatants-grid .apply-wounds-btn::after, +.message.lethalfantasy.dice-roll .damage-result .li-apply-wounds .combatants-grid .apply-wounds-btn::after, +.message.fvtt-lethal-fantasy.dice-roll .damage-result .li-apply-wounds .combatants-grid .apply-wounds-btn::after, +.lethalfantasy.dice-roll .damage-result .li-apply-wounds .combatants-grid .request-defense-btn::after, +.fvtt-lethal-fantasy.dice-roll .damage-result .li-apply-wounds .combatants-grid .request-defense-btn::after, +.message.lethalfantasy.dice-roll .damage-result .li-apply-wounds .combatants-grid .request-defense-btn::after, +.message.fvtt-lethal-fantasy.dice-roll .damage-result .li-apply-wounds .combatants-grid .request-defense-btn::after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 0; + height: 0; + border-radius: 50%; + background: rgba(255, 255, 255, 0.2); + transform: translate(-50%, -50%); + transition: width 0.4s, height 0.4s; +} +.lethalfantasy.dice-roll .damage-result .li-apply-wounds .combatants-grid .apply-wounds-btn:hover::after, +.fvtt-lethal-fantasy.dice-roll .damage-result .li-apply-wounds .combatants-grid .apply-wounds-btn:hover::after, +.message.lethalfantasy.dice-roll .damage-result .li-apply-wounds .combatants-grid .apply-wounds-btn:hover::after, +.message.fvtt-lethal-fantasy.dice-roll .damage-result .li-apply-wounds .combatants-grid .apply-wounds-btn:hover::after, +.lethalfantasy.dice-roll .damage-result .li-apply-wounds .combatants-grid .request-defense-btn:hover::after, +.fvtt-lethal-fantasy.dice-roll .damage-result .li-apply-wounds .combatants-grid .request-defense-btn:hover::after, +.message.lethalfantasy.dice-roll .damage-result .li-apply-wounds .combatants-grid .request-defense-btn:hover::after, +.message.fvtt-lethal-fantasy.dice-roll .damage-result .li-apply-wounds .combatants-grid .request-defense-btn:hover::after { + width: 200%; + height: 200%; +} +.lethalfantasy.dice-roll .damage-result .li-apply-wounds .combatants-grid .apply-wounds-btn:hover, +.fvtt-lethal-fantasy.dice-roll .damage-result .li-apply-wounds .combatants-grid .apply-wounds-btn:hover, +.message.lethalfantasy.dice-roll .damage-result .li-apply-wounds .combatants-grid .apply-wounds-btn:hover, +.message.fvtt-lethal-fantasy.dice-roll .damage-result .li-apply-wounds .combatants-grid .apply-wounds-btn:hover, +.lethalfantasy.dice-roll .damage-result .li-apply-wounds .combatants-grid .request-defense-btn:hover, +.fvtt-lethal-fantasy.dice-roll .damage-result .li-apply-wounds .combatants-grid .request-defense-btn:hover, +.message.lethalfantasy.dice-roll .damage-result .li-apply-wounds .combatants-grid .request-defense-btn:hover, +.message.fvtt-lethal-fantasy.dice-roll .damage-result .li-apply-wounds .combatants-grid .request-defense-btn:hover { background: linear-gradient(to bottom, #6a6860 0%, #5a5850 100%); - box-shadow: 0 3px 6px rgba(0, 0, 0, 0.4); - transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.12); + transform: translateY(-2px); border-color: #3b3a34; } -.dice-roll .damage-result .li-apply-wounds .combatants-grid .apply-wounds-btn:active { +.lethalfantasy.dice-roll .damage-result .li-apply-wounds .combatants-grid .apply-wounds-btn:active, +.fvtt-lethal-fantasy.dice-roll .damage-result .li-apply-wounds .combatants-grid .apply-wounds-btn:active, +.message.lethalfantasy.dice-roll .damage-result .li-apply-wounds .combatants-grid .apply-wounds-btn:active, +.message.fvtt-lethal-fantasy.dice-roll .damage-result .li-apply-wounds .combatants-grid .apply-wounds-btn:active, +.lethalfantasy.dice-roll .damage-result .li-apply-wounds .combatants-grid .request-defense-btn:active, +.fvtt-lethal-fantasy.dice-roll .damage-result .li-apply-wounds .combatants-grid .request-defense-btn:active, +.message.lethalfantasy.dice-roll .damage-result .li-apply-wounds .combatants-grid .request-defense-btn:active, +.message.fvtt-lethal-fantasy.dice-roll .damage-result .li-apply-wounds .combatants-grid .request-defense-btn:active { transform: translateY(0); - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4), inset 0 1px 3px rgba(0, 0, 0, 0.3); +} +.lethalfantasy.dice-roll .attack-targets, +.fvtt-lethal-fantasy.dice-roll .attack-targets, +.message.lethalfantasy.dice-roll .attack-targets, +.message.fvtt-lethal-fantasy.dice-roll .attack-targets { + margin: 0 8px 10px 8px; + background: linear-gradient(135deg, rgba(80, 80, 80, 0.15) 0%, rgba(60, 60, 60, 0.2) 100%); + border: 1px solid rgba(100, 100, 100, 0.3); + border-radius: 8px; +} +.lethalfantasy.dice-roll .attack-targets ul, +.fvtt-lethal-fantasy.dice-roll .attack-targets ul, +.message.lethalfantasy.dice-roll .attack-targets ul, +.message.fvtt-lethal-fantasy.dice-roll .attack-targets ul { + padding: 0; + margin: 0; +} +.lethalfantasy.dice-roll .attack-targets .li-select-target, +.fvtt-lethal-fantasy.dice-roll .attack-targets .li-select-target, +.message.lethalfantasy.dice-roll .attack-targets .li-select-target, +.message.fvtt-lethal-fantasy.dice-roll .attack-targets .li-select-target { + list-style: none; + margin: 0; + padding: 0; +} +.lethalfantasy.dice-roll .attack-targets .li-select-target > div:first-child, +.fvtt-lethal-fantasy.dice-roll .attack-targets .li-select-target > div:first-child, +.message.lethalfantasy.dice-roll .attack-targets .li-select-target > div:first-child, +.message.fvtt-lethal-fantasy.dice-roll .attack-targets .li-select-target > div:first-child { + font-weight: bold; + margin-bottom: 10px; + font-size: calc(var(--font-size-standard) * 1); + text-align: center; + color: #d4af37; + text-transform: uppercase; + letter-spacing: 1px; +} +.lethalfantasy.dice-roll .attack-targets .li-select-target .combatants-grid, +.fvtt-lethal-fantasy.dice-roll .attack-targets .li-select-target .combatants-grid, +.message.lethalfantasy.dice-roll .attack-targets .li-select-target .combatants-grid, +.message.fvtt-lethal-fantasy.dice-roll .attack-targets .li-select-target .combatants-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 8px; +} +.lethalfantasy.dice-roll .d30-message, +.fvtt-lethal-fantasy.dice-roll .d30-message, +.message.lethalfantasy.dice-roll .d30-message, +.message.fvtt-lethal-fantasy.dice-roll .d30-message { + margin-top: 0.5rem; + padding: 0.5rem; + background-color: rgba(139, 0, 0, 0.1); + border-left: 3px solid var(--color-level-error); + border-radius: 3px; + font-size: calc(var(--font-size-standard) * 0.95); + font-style: italic; + color: var(--color-dark-1); +} +/* -------------------------------------------- */ +/* Damage Applied Message */ +/* -------------------------------------------- */ +.message .fvtt-lethal-fantasy.damage-applied-message, +.fvtt-lethal-fantasy.damage-applied-message { + background: linear-gradient(135deg, rgba(220, 20, 60, 0.1) 0%, rgba(139, 0, 0, 0.15) 100%); + border: 1px solid rgba(220, 20, 60, 0.3); + border-radius: 6px; + padding: 0.75rem; + margin: 0.25rem 0; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15); +} +.message .fvtt-lethal-fantasy.damage-applied-message .damage-header, +.fvtt-lethal-fantasy.damage-applied-message .damage-header { + display: flex; + align-items: center; + gap: 0.75rem; + margin-bottom: 0.75rem; + padding-bottom: 0.5rem; + border-bottom: 1px solid rgba(220, 20, 60, 0.2); +} +.message .fvtt-lethal-fantasy.damage-applied-message .damage-header .damage-icon, +.fvtt-lethal-fantasy.damage-applied-message .damage-header .damage-icon { + font-size: 2rem; + color: #dc143c; + text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); + animation: heartbeat 2s ease-in-out infinite; +} +.message .fvtt-lethal-fantasy.damage-applied-message .damage-header .damage-info, +.fvtt-lethal-fantasy.damage-applied-message .damage-header .damage-info { + flex: 1; +} +.message .fvtt-lethal-fantasy.damage-applied-message .damage-header .damage-info .target-name, +.fvtt-lethal-fantasy.damage-applied-message .damage-header .damage-info .target-name { + font-size: calc(var(--font-size-standard) * 1.1); + font-weight: bold; + color: #2a1a1a; + text-shadow: 0 1px 2px rgba(255, 255, 255, 0.5); +} +.message .fvtt-lethal-fantasy.damage-applied-message .damage-header .damage-info .damage-subtitle, +.fvtt-lethal-fantasy.damage-applied-message .damage-header .damage-info .damage-subtitle { + font-size: calc(var(--font-size-standard) * 0.85); + color: #5a3a3a; + font-style: italic; +} +.message .fvtt-lethal-fantasy.damage-applied-message .damage-details, +.fvtt-lethal-fantasy.damage-applied-message .damage-details { + display: flex; + flex-direction: column; + gap: 0.5rem; +} +.message .fvtt-lethal-fantasy.damage-applied-message .damage-details .damage-amount, +.fvtt-lethal-fantasy.damage-applied-message .damage-details .damage-amount { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.5rem; + background: linear-gradient(90deg, rgba(255, 255, 255, 0.4), rgba(255, 255, 255, 0.2)); + border-radius: 4px; + border-left: 3px solid #dc143c; +} +.message .fvtt-lethal-fantasy.damage-applied-message .damage-details .damage-amount .damage-label, +.fvtt-lethal-fantasy.damage-applied-message .damage-details .damage-amount .damage-label { + font-weight: 600; + color: #3a2a2a; +} +.message .fvtt-lethal-fantasy.damage-applied-message .damage-details .damage-amount .damage-value, +.fvtt-lethal-fantasy.damage-applied-message .damage-details .damage-amount .damage-value { + font-size: calc(var(--font-size-standard) * 1.4); + font-weight: bold; + color: #dc143c; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); +} +.message .fvtt-lethal-fantasy.damage-applied-message .damage-details .damage-amount .damage-raw, +.fvtt-lethal-fantasy.damage-applied-message .damage-details .damage-amount .damage-raw { + font-size: calc(var(--font-size-standard) * 0.85); + color: #6a4a4a; + margin-left: 6px; + font-style: italic; +} +.message .fvtt-lethal-fantasy.damage-applied-message .damage-details .damage-reduction, +.fvtt-lethal-fantasy.damage-applied-message .damage-details .damage-reduction, +.message .fvtt-lethal-fantasy.damage-applied-message .damage-details .damage-source, +.fvtt-lethal-fantasy.damage-applied-message .damage-details .damage-source { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.35rem 0.5rem; + background-color: rgba(255, 255, 255, 0.3); + border-radius: 3px; + font-size: calc(var(--font-size-standard) * 0.95); + color: #3a2a2a; +} +.message .fvtt-lethal-fantasy.damage-applied-message .damage-details .damage-reduction i, +.fvtt-lethal-fantasy.damage-applied-message .damage-details .damage-reduction i, +.message .fvtt-lethal-fantasy.damage-applied-message .damage-details .damage-source i, +.fvtt-lethal-fantasy.damage-applied-message .damage-details .damage-source i { + color: #8b4513; + font-size: calc(var(--font-size-standard) * 0.9); +} +.message .fvtt-lethal-fantasy.damage-applied-message .damage-details .damage-reduction strong, +.fvtt-lethal-fantasy.damage-applied-message .damage-details .damage-reduction strong, +.message .fvtt-lethal-fantasy.damage-applied-message .damage-details .damage-source strong, +.fvtt-lethal-fantasy.damage-applied-message .damage-details .damage-source strong { + font-weight: 700; + color: #2a1a1a; +} +.message .fvtt-lethal-fantasy.damage-applied-message .damage-details .damage-reduction i, +.fvtt-lethal-fantasy.damage-applied-message .damage-details .damage-reduction i { + color: #4a6fa5; +} +@keyframes heartbeat { + 0%, + 100% { + transform: scale(1); + } + 10%, + 30% { + transform: scale(1.1); + } + 20%, + 40% { + transform: scale(1); + } +} +/* -------------------------------------------- */ +/* Progression Message */ +/* -------------------------------------------- */ +.message .fvtt-lethal-fantasy.progression-message, +.fvtt-lethal-fantasy.progression-message { + border-radius: 4px; + padding: 0.4rem 0.5rem; + margin: 0.2rem 0; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12); +} +.message .fvtt-lethal-fantasy.progression-message.progression-success, +.fvtt-lethal-fantasy.progression-message.progression-success { + background: linear-gradient(135deg, rgba(34, 139, 34, 0.1) 0%, rgba(0, 100, 0, 0.15) 100%); + border: 1px solid rgba(34, 139, 34, 0.3); +} +.message .fvtt-lethal-fantasy.progression-message.progression-failure, +.fvtt-lethal-fantasy.progression-message.progression-failure { + background: linear-gradient(135deg, rgba(255, 140, 0, 0.1) 0%, rgba(184, 134, 11, 0.15) 100%); + border: 1px solid rgba(255, 140, 0, 0.3); +} +.message .fvtt-lethal-fantasy.progression-message .progression-header, +.fvtt-lethal-fantasy.progression-message .progression-header { + display: flex; + align-items: center; + gap: 0.5rem; + margin-bottom: 0.4rem; + padding-bottom: 0.3rem; +} +.progression-success .message .fvtt-lethal-fantasy.progression-message .progression-header, +.progression-success .fvtt-lethal-fantasy.progression-message .progression-header { + border-bottom: 1px solid rgba(34, 139, 34, 0.2); +} +.progression-failure .message .fvtt-lethal-fantasy.progression-message .progression-header, +.progression-failure .fvtt-lethal-fantasy.progression-message .progression-header { + border-bottom: 1px solid rgba(255, 140, 0, 0.2); +} +.message .fvtt-lethal-fantasy.progression-message .progression-header .progression-icon, +.fvtt-lethal-fantasy.progression-message .progression-header .progression-icon { + font-size: 1.5rem; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); +} +.progression-success .message .fvtt-lethal-fantasy.progression-message .progression-header .progression-icon, +.progression-success .fvtt-lethal-fantasy.progression-message .progression-header .progression-icon { + color: #228b22; + animation: pulse-success 2s ease-in-out infinite; +} +.progression-failure .message .fvtt-lethal-fantasy.progression-message .progression-header .progression-icon, +.progression-failure .fvtt-lethal-fantasy.progression-message .progression-header .progression-icon { + color: #ff8c00; + animation: rotate-slow 3s linear infinite; +} +.message .fvtt-lethal-fantasy.progression-message .progression-header .progression-info, +.fvtt-lethal-fantasy.progression-message .progression-header .progression-info { + flex: 1; +} +.message .fvtt-lethal-fantasy.progression-message .progression-header .progression-info .actor-name, +.fvtt-lethal-fantasy.progression-message .progression-header .progression-info .actor-name { + font-size: calc(var(--font-size-standard) * 1); + font-weight: bold; + color: #2a1a1a; + text-shadow: 0 1px 2px rgba(255, 255, 255, 0.5); + line-height: 1.2; +} +.message .fvtt-lethal-fantasy.progression-message .progression-header .progression-info .progression-subtitle, +.fvtt-lethal-fantasy.progression-message .progression-header .progression-info .progression-subtitle { + font-size: calc(var(--font-size-standard) * 0.8); + font-style: italic; + line-height: 1.2; + margin-top: 0.1rem; +} +.progression-success .message .fvtt-lethal-fantasy.progression-message .progression-header .progression-info .progression-subtitle, +.progression-success .fvtt-lethal-fantasy.progression-message .progression-header .progression-info .progression-subtitle { + color: #2d5a2d; +} +.progression-failure .message .fvtt-lethal-fantasy.progression-message .progression-header .progression-info .progression-subtitle, +.progression-failure .fvtt-lethal-fantasy.progression-message .progression-header .progression-info .progression-subtitle { + color: #8b6914; +} +.message .fvtt-lethal-fantasy.progression-message .progression-details, +.fvtt-lethal-fantasy.progression-message .progression-details { + display: flex; + flex-direction: column; + gap: 0.3rem; +} +.message .fvtt-lethal-fantasy.progression-message .progression-details .progression-weapon, +.fvtt-lethal-fantasy.progression-message .progression-details .progression-weapon, +.message .fvtt-lethal-fantasy.progression-message .progression-details .progression-roll, +.fvtt-lethal-fantasy.progression-message .progression-details .progression-roll, +.message .fvtt-lethal-fantasy.progression-message .progression-details .progression-count, +.fvtt-lethal-fantasy.progression-message .progression-details .progression-count { + display: flex; + align-items: center; + gap: 0.4rem; + padding: 0.25rem 0.4rem; + background-color: rgba(255, 255, 255, 0.3); + border-radius: 3px; + font-size: calc(var(--font-size-standard) * 0.9); + color: #3a2a2a; + line-height: 1.2; +} +.message .fvtt-lethal-fantasy.progression-message .progression-details .progression-weapon i, +.fvtt-lethal-fantasy.progression-message .progression-details .progression-weapon i, +.message .fvtt-lethal-fantasy.progression-message .progression-details .progression-roll i, +.fvtt-lethal-fantasy.progression-message .progression-details .progression-roll i, +.message .fvtt-lethal-fantasy.progression-message .progression-details .progression-count i, +.fvtt-lethal-fantasy.progression-message .progression-details .progression-count i { + font-size: calc(var(--font-size-standard) * 0.85); +} +.message .fvtt-lethal-fantasy.progression-message .progression-details .progression-weapon strong, +.fvtt-lethal-fantasy.progression-message .progression-details .progression-weapon strong, +.message .fvtt-lethal-fantasy.progression-message .progression-details .progression-roll strong, +.fvtt-lethal-fantasy.progression-message .progression-details .progression-roll strong, +.message .fvtt-lethal-fantasy.progression-message .progression-details .progression-count strong, +.fvtt-lethal-fantasy.progression-message .progression-details .progression-count strong { + font-weight: 700; + color: #2a1a1a; +} +.message .fvtt-lethal-fantasy.progression-message .progression-details .progression-weapon, +.fvtt-lethal-fantasy.progression-message .progression-details .progression-weapon { + border-left: 3px solid #8b4513; +} +.message .fvtt-lethal-fantasy.progression-message .progression-details .progression-weapon i, +.fvtt-lethal-fantasy.progression-message .progression-details .progression-weapon i { + color: #8b4513; +} +.progression-success .message .fvtt-lethal-fantasy.progression-message .progression-details .progression-roll, +.progression-success .fvtt-lethal-fantasy.progression-message .progression-details .progression-roll { + border-left: 3px solid #228b22; +} +.progression-failure .message .fvtt-lethal-fantasy.progression-message .progression-details .progression-roll, +.progression-failure .fvtt-lethal-fantasy.progression-message .progression-details .progression-roll { + border-left: 3px solid #ff8c00; +} +.message .fvtt-lethal-fantasy.progression-message .progression-details .progression-roll .roll-label, +.fvtt-lethal-fantasy.progression-message .progression-details .progression-roll .roll-label { + font-weight: 600; +} +.message .fvtt-lethal-fantasy.progression-message .progression-details .progression-roll .roll-value, +.fvtt-lethal-fantasy.progression-message .progression-details .progression-roll .roll-value { + font-size: calc(var(--font-size-standard) * 1.1); + font-weight: bold; +} +.progression-success .message .fvtt-lethal-fantasy.progression-message .progression-details .progression-roll .roll-value, +.progression-success .fvtt-lethal-fantasy.progression-message .progression-details .progression-roll .roll-value { + color: #228b22; +} +.progression-failure .message .fvtt-lethal-fantasy.progression-message .progression-details .progression-roll .roll-value, +.progression-failure .fvtt-lethal-fantasy.progression-message .progression-details .progression-roll .roll-value { + color: #ff8c00; +} +.progression-success .message .fvtt-lethal-fantasy.progression-message .progression-details .progression-roll i, +.progression-success .fvtt-lethal-fantasy.progression-message .progression-details .progression-roll i { + color: #228b22; +} +.progression-failure .message .fvtt-lethal-fantasy.progression-message .progression-details .progression-roll i, +.progression-failure .fvtt-lethal-fantasy.progression-message .progression-details .progression-roll i { + color: #ff8c00; +} +.message .fvtt-lethal-fantasy.progression-message .progression-details .progression-count, +.fvtt-lethal-fantasy.progression-message .progression-details .progression-count { + border-left: 3px solid #4a6fa5; +} +.message .fvtt-lethal-fantasy.progression-message .progression-details .progression-count i, +.fvtt-lethal-fantasy.progression-message .progression-details .progression-count i { + color: #4a6fa5; +} +@keyframes pulse-success { + 0%, + 100% { + transform: scale(1); + opacity: 1; + } + 50% { + transform: scale(1.15); + opacity: 0.8; + } +} +@keyframes rotate-slow { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} +/* -------------------------------------------- */ +/* Attack Result Message */ +/* -------------------------------------------- */ +.message .attack-result, +.attack-result { + padding: 8px; + border-radius: 8px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.4); + margin: 8px 0; +} +.message .attack-result.attack-success, +.attack-result.attack-success { + background: linear-gradient(to bottom, rgba(139, 0, 0, 0.2) 0%, rgba(100, 0, 0, 0.3) 100%); + border: 2px solid rgba(220, 20, 60, 0.6); +} +.message .attack-result.attack-failure, +.attack-result.attack-failure { + background: linear-gradient(to bottom, rgba(0, 100, 139, 0.2) 0%, rgba(0, 70, 100, 0.3) 100%); + border: 2px solid rgba(70, 130, 180, 0.6); +} +.message .attack-result h3, +.attack-result h3 { + margin: 0 0 8px 0; + color: #000; + font-size: calc(var(--font-size-standard) * 1.2); + font-weight: 700; + text-align: center; + text-transform: uppercase; + letter-spacing: 1px; +} +.message .attack-result h3 i, +.attack-result h3 i { + margin-right: 8px; +} +.message .attack-result .combat-comparison, +.attack-result .combat-comparison { + display: flex; + flex-direction: column; + align-items: stretch; + gap: 6px; + margin-bottom: 8px; + padding: 8px; + background: rgba(0, 0, 0, 0.3); + border-radius: 6px; +} +.message .attack-result .combat-comparison .combat-side, +.attack-result .combat-comparison .combat-side { + flex: 1; + padding: 8px; + border-radius: 6px; + display: flex; + flex-direction: column; + gap: 4px; +} +.message .attack-result .combat-comparison .combat-side.winner, +.attack-result .combat-comparison .combat-side.winner { + background: rgba(0, 255, 0, 0.1); + border: 2px solid rgba(0, 255, 0, 0.4); +} +.message .attack-result .combat-comparison .combat-side.loser, +.attack-result .combat-comparison .combat-side.loser { + background: rgba(255, 0, 0, 0.1); + border: 2px solid rgba(255, 0, 0, 0.3); +} +.message .attack-result .combat-comparison .combat-side .side-label, +.attack-result .combat-comparison .combat-side .side-label { + font-size: calc(var(--font-size-standard) * 0.8); + color: #ddd; + text-transform: uppercase; + letter-spacing: 1px; + font-weight: 600; + text-align: center; +} +.message .attack-result .combat-comparison .combat-side .side-info, +.attack-result .combat-comparison .combat-side .side-info { + display: flex; + justify-content: space-between; + align-items: center; + gap: 8px; +} +.message .attack-result .combat-comparison .combat-side .side-name, +.attack-result .combat-comparison .combat-side .side-name { + font-size: calc(var(--font-size-standard) * 1); + color: #fff; + font-weight: 700; +} +.message .attack-result .combat-comparison .combat-side .side-roll, +.attack-result .combat-comparison .combat-side .side-roll { + font-size: calc(var(--font-size-standard) * 1.5); + color: #f4d03f; + font-weight: 700; +} +.message .attack-result .combat-comparison .combat-vs, +.attack-result .combat-comparison .combat-vs { + font-size: calc(var(--font-size-standard) * 1.2); + color: #f4d03f; + font-weight: 700; + padding: 4px 0; + text-align: center; +} +.message .attack-result .combat-result-text, +.attack-result .combat-result-text { + text-align: center; + font-size: calc(var(--font-size-standard) * 1.1); + color: #fff; + margin-bottom: 8px; + padding: 8px; + background: rgba(0, 0, 0, 0.2); + border-radius: 6px; + font-weight: 600; +} +.message .attack-result .combat-result-text i, +.attack-result .combat-result-text i { + margin-right: 8px; +} +.message .attack-result .combat-result-text strong, +.attack-result .combat-result-text strong { + color: #f4d03f; + font-weight: 700; +} +.message .attack-result .attack-result-damage, +.attack-result .attack-result-damage { + display: flex; + gap: 8px; + justify-content: center; +} +.message .attack-result .attack-result-damage .roll-damage-btn, +.attack-result .attack-result-damage .roll-damage-btn { + padding: 10px 16px; + background: linear-gradient(to bottom, #8b0000 0%, #660000 100%); + border: 1px solid #ff0000; + border-radius: 6px; + color: #f0e6d2; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); +} +.message .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%); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.4); + transform: translateY(-2px); +} +.message .attack-result .attack-result-damage .roll-damage-btn:active, +.attack-result .attack-result-damage .roll-damage-btn:active { + transform: translateY(0); +} +.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 { position: absolute; diff --git a/lang/en.json b/lang/en.json index e6665a7..7ed8c22 100644 --- a/lang/en.json +++ b/lang/en.json @@ -283,6 +283,7 @@ "Label": { "agility": "Dexterity", "applyDamage": "Apply damage to:", + "selectTarget": "Select target for attack:", "rollDamage": "Roll Damage", "gotoToken": "Go to token", "combatAction": "Combat action", @@ -876,6 +877,17 @@ "withArmor": "With Armor DR only", "withAll": "With Armor + Shield DR", "damage": "damage" + }, + "DamageApplied": { + "subtitle": "suffered damage", + "damageDealt": "Damage dealt", + "from": "from" + }, + "ProgressionMessage": { + "canAct": "ready to act!", + "cannotAct": "cannot act this second", + "diceResult": "Dice result", + "progressionCount": "Progression count:" } }, "TYPES": { diff --git a/lethal-fantasy.mjs b/lethal-fantasy.mjs index 3139339..e868730 100644 --- a/lethal-fantasy.mjs +++ b/lethal-fantasy.mjs @@ -110,6 +110,9 @@ function preLocalizeConfig() { Hooks.once("ready", function () { console.info("LETHAL FANTASY | Ready") + // Initialiser la table des résultats D30 + documents.D30Roll.initialize() + if (!SYSTEM.DEV_MODE) { registerWorldCount("lethalFantasy") } @@ -186,22 +189,148 @@ Hooks.on(hookName, (message, html, data) => { }) } - // Gestionnaire pour les boutons de jet de dégâts - $(html).find(".damage-roll-btn").click(async (event) => { + // Gestion du survol et du clic sur les boutons de défense + $(html).find(".request-defense-btn").hover( + function (event) { + // Mouse enter - select the token and pan to it + let tokenId = $(this).data("token-id") + if (tokenId) { + let token = canvas.tokens.get(tokenId) + if (token) { + token.control({ releaseOthers: true }) + canvas.animatePan(token.center) + } + } + }, + function (event) { + // Mouse leave - release selection + canvas.tokens.releaseAll() + } + ) + + // Gestionnaire pour les boutons de demande de défense + $(html).find(".request-defense-btn").off("click").on("click", (event) => { + event.preventDefault() + event.stopPropagation() + + const button = $(event.currentTarget) + const combatantId = button.data("combatant-id") + const tokenId = button.data("token-id") + + // Récupérer le combattant soit du combat, soit directement du token + let combatant = null + let token = null + + if (game.combat && combatantId) { + combatant = game.combat.combatants.get(combatantId) + } + + // Si pas de combattant trouvé, chercher le token directement + if (!combatant && tokenId) { + token = canvas.tokens.get(tokenId) + if (token) { + // Créer un pseudo-combattant avec les infos du token + combatant = { + actor: token.actor, + name: token.name, + token: token, + actorId: token.actorId + } + } + } + + if (!combatant) return + + // Récupérer les informations de l'attaquant depuis le message + const attackerName = message.rolls[0]?.actorName || "Unknown" + const attackerId = message.rolls[0]?.actorId + const weaponName = message.rolls[0]?.rollName || "weapon" + const attackRoll = message.rolls[0]?.rollTotal || 0 + 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 attackRollKey = message.rolls[0]?.rollTarget?.rollKey + + // Préparer le message de demande de défense + const defenseMsg = { + type: "requestDefense", + attackerName: attackerName, + attackerId: attackerId, + defenderName: defenderName, + weaponName: weaponName, + attackRoll: attackRoll, + attackWeaponId: attackWeaponId, + attackRollType: attackRollType, + attackRollKey: attackRollKey, + combatantId: combatantId, + tokenId: tokenId + } + + // Envoyer le message socket à l'utilisateur contrôlant le combatant + const owners = game.users.filter(u => + combatant.actor.testUserPermission(u, "OWNER") + ) + + // Récupérer l'acteur attaquant pour vérifier qui l'a lancé + const attacker = game.actors.get(attackerId) + const attackerOwners = attacker ? game.users.filter(u => attacker.testUserPermission(u, "OWNER")).map(u => u.id) : [] + + let messageSent = false + owners.forEach(owner => { + // Ne pas afficher le dialogue à l'attaquant lui-même s'il contrôle aussi le défenseur + if (attackerOwners.includes(owner.id) && owner.id === game.user.id) { + // Ne rien faire - on ne veut pas que l'attaquant se défende contre lui-même + return + } + + if (owner.id === game.user.id) { + // Si l'utilisateur actuel est le propriétaire du défenseur (mais pas l'attaquant), appeler directement + LethalFantasyUtils.showDefenseRequest({ ...defenseMsg, userId: owner.id }) + messageSent = true + } else { + // Sinon, envoyer via socket + game.socket.emit(`system.${SYSTEM.id}`, { + ...defenseMsg, + userId: owner.id + }) + messageSent = true + } + }) + + // Notification pour l'attaquant + if (messageSent) { + ui.notifications.info(`Defense request sent to ${defenderName}'s controller`) + } + }) + + // Gestionnaire pour les boutons de jet de dégâts (armes et résultats de combat) + $(html).find(".damage-roll-btn, .roll-damage-btn").off("click").on("click", async (event) => { + event.preventDefault() + event.stopPropagation() + const button = $(event.currentTarget) const weaponId = button.data("weapon-id") + const attackKey = button.data("attack-key") + let attackerId = button.data("attacker-id") + const defenderId = button.data("defender-id") const damageType = button.data("damage-type") const damageFormula = button.data("damage-formula") const damageModifier = button.data("damage-modifier") const isMonster = button.data("is-monster") - // Récupérer l'acteur qui a fait le jet initial - const actor = game.actors.get(message.rolls[0]?.actorId) + // Récupérer l'acteur (soit depuis le message, soit depuis attackerId) + let actor = attackerId ? game.actors.get(attackerId) : game.actors.get(message.rolls[0]?.actorId) if (!actor) { ui.notifications.error("Actor not found") return } + // Pour les boutons de résultat de combat (monster damage) + if (damageType === "monster" && attackKey) { + await actor.system.prepareMonsterRoll("monster-damage", attackKey, undefined, undefined, undefined, defenderId) + return + } + // Pour les monstres, utiliser prepareMonsterRoll if (isMonster || actor.type === "monster") { await actor.system.prepareMonsterRoll("monster-damage", weaponId, undefined, undefined, damageModifier) @@ -217,7 +346,21 @@ Hooks.on(hookName, (message, html, data) => { // Lancer les dégâts avec la bonne méthode const rollType = damageType === "small" ? "weapon-damage-small" : "weapon-damage-medium" - await actor.prepareRoll(rollType, weaponId) + await actor.prepareRoll(rollType, weaponId, undefined, defenderId) + }) + + // Masquer les boutons de dommages dans les messages de résultat de combat si l'utilisateur n'est pas l'attaquant + $(html).find(".roll-damage-btn").each(function() { + const button = $(this) + const attackerId = button.data("attacker-id") + + if (attackerId) { + const attacker = game.actors.get(attackerId) + // Masquer le bouton si l'utilisateur n'est pas GM et ne possède pas l'attaquant + if (!game.user.isGM && !attacker?.testUserPermission(game.user, "OWNER")) { + button.hide() + } + } }) }) @@ -225,6 +368,209 @@ Hooks.on("getCombatTrackerEntryContext", (html, options) => { LethalFantasyUtils.pushCombatOptions(html, options); }); +// Hook pour ajouter les données d'attaque au message de défense +Hooks.on("preCreateChatMessage", (message) => { + const rollType = message.rolls[0]?.options?.rollType + + // Si c'est un message de défense et qu'on a des données en attente + if ((rollType === "weapon-defense" || rollType === "monster-defense") && game.lethalFantasy?.nextDefenseData) { + // Ajouter les données dans les flags du message + message.updateSource({ + [`flags.${SYSTEM.id}.attackData`]: game.lethalFantasy.nextDefenseData + }) + + console.log("Added attack data to defense message:", game.lethalFantasy.nextDefenseData) + + // Nettoyer + delete game.lethalFantasy.nextDefenseData + } +}) + +// Hook global pour gérer l'offre de Grit à l'attaquant après une défense +Hooks.on("createChatMessage", async (message) => { + const rollType = message.rolls[0]?.options?.rollType + + console.log("Defense hook checking message, rollType:", rollType) + + // Vérifier si c'est un message de défense + if (rollType !== "weapon-defense" && rollType !== "monster-defense") return + + // Récupérer les données d'attaque depuis les flags + const attackData = message.flags?.[SYSTEM.id]?.attackData + + console.log("Defense message confirmed, attackData:", attackData) + + if (!attackData) { + console.log("No attack data found in message flags") + return + } + + const { attackerId, attackRoll, attackerName, defenderName, attackWeaponId, attackRollType, attackRollKey, defenderId } = attackData + let defenseRoll = message.rolls[0]?.options?.rollTotal || message.rolls[0]?.total || 0 + + console.log("Processing defense:", { attackRoll, defenseRoll, attackerId, defenderId }) + + // Attendre l'animation 3D + if (game?.dice3d) { + await game.dice3d.waitFor3DAnimationByMessageID(message.id) + } + + // Récupérer le défenseur et l'attaquant + const defender = game.actors.get(defenderId) + const attacker = game.actors.get(attackerId) + + // Si le défenseur est un personnage qui perd, proposer Grit/Luck (seulement s'il a des points) + // Seulement si l'utilisateur actuel est le propriétaire du défenseur + let defenderHandledBonus = false + if (defender?.type === "character" && defenseRoll < attackRoll && !game.user.isGM && defender.isOwner) { + const hasGritOrLuck = (defender.system.grit.current > 0) || (defender.system.luck.current > 0) + + if (hasGritOrLuck) { + const bonusRoll = await LethalFantasyUtils.offerGritLuckBonus( + defender, + attackRoll, + defenseRoll, + attackerName, + defenderName + ) + if (bonusRoll > 0) { + defenseRoll += bonusRoll + } + } + defenderHandledBonus = true + } + + let attackRollFinal = attackRoll + let attackerHandledBonus = false + + // Si l'attaquant est un personnage qui perd et a du Grit + // Seulement si l'utilisateur actuel est le propriétaire de l'attaquant (pas le MJ) + if (attacker?.type === "character" && attackRollFinal <= defenseRoll && attacker.system.grit.current > 0) { + // Vérifier si l'utilisateur est un propriétaire non-GM de l'attaquant + const isAttackerOwner = !game.user.isGM && attacker.testUserPermission(game.user, "OWNER") + + if (isAttackerOwner) { + console.log("Offering Grit to attacker") + + const attackBonus = await LethalFantasyUtils.offerAttackerGritBonus( + attacker, + attackRollFinal, + defenseRoll, + attackerName, + defenderName + ) + + attackRollFinal += attackBonus + attackerHandledBonus = true + } else { + console.log("Not attacker owner or is GM, skipping Grit offer") + } + } + + // Créer le message de comparaison - uniquement par le client qui a géré le dernier bonus + // Priorité: attaquant si il a géré le bonus, sinon défenseur si il a géré le bonus, sinon défenseur + const shouldCreateMessage = attackerHandledBonus || (!attackerHandledBonus && defenderHandledBonus) || (!attackerHandledBonus && !defenderHandledBonus && defender.isOwner) + + if (shouldCreateMessage) { + console.log("Creating comparison message", { attackerHandledBonus, defenderHandledBonus, isDefenderOwner: defender.isOwner }) + + await LethalFantasyUtils.compareAttackDefense({ + attackerName, + attackerId, + attackRoll: attackRollFinal, + attackWeaponId, + attackRollType, + attackRollKey, + defenderName, + defenderId, + defenseRoll + }) + } else { + console.log("Skipping message creation", { attackerHandledBonus, defenderHandledBonus }) + } +}) + +// Hook pour appliquer automatiquement les dégâts si une cible est définie +Hooks.on("createChatMessage", async (message) => { + // Vérifier si c'est un message de dégâts avec un defenderId + const defenderId = message.rolls[0]?.options?.defenderId + const isDamage = message.rolls[0]?.options?.rollData?.isDamage + + console.log("Auto-damage hook:", { defenderId, isDamage, rollType: message.rolls[0]?.options?.rollType }) + + if (!defenderId || !isDamage) return + + // Récupérer l'attaquant depuis le roll + const attackerId = message.rolls[0]?.options?.actorId + const attacker = attackerId ? game.actors.get(attackerId) : null + + // Déterminer qui doit appliquer les dégâts : + // 1. Si l'attaquant a un propriétaire joueur, seul ce joueur applique + // 2. Si l'attaquant n'a que le MJ comme propriétaire (monstre), seul le MJ applique + const attackerOwners = attacker ? game.users.filter(u => + !u.isGM && attacker.testUserPermission(u, "OWNER") + ) : [] + + let shouldApplyDamage = false + if (attackerOwners.length > 0) { + // L'attaquant a des propriétaires joueurs, seul le premier propriétaire applique + shouldApplyDamage = attackerOwners[0].id === game.user.id + } else { + // L'attaquant n'a que le MJ, seul le MJ applique + shouldApplyDamage = game.user.isGM + } + + if (!shouldApplyDamage) { + console.log("Auto-damage hook: Not responsible for applying damage, skipping") + return + } + + console.log("Auto-damage hook: Applying damage as responsible user") + + // Attendre l'animation 3D avant d'appliquer les dégâts + if (game?.dice3d) { + await game.dice3d.waitFor3DAnimationByMessageID(message.id) + } + + // Récupérer le défenseur + const defender = game.actors.get(defenderId) + if (!defender) { + console.warn("Defender not found:", defenderId) + return + } + + // Récupérer les dégâts (utiliser rollTotal qui contient le total calculé) + const damageTotal = message.rolls[0]?.options?.rollTotal || message.rolls[0]?.total || 0 + const weaponName = message.rolls[0]?.options?.rollName || "Unknown Weapon" + const attackerName = message.rolls[0]?.options?.actorName || "Unknown Attacker" + + // Calculer les DR + const armorDR = defender.computeDamageReduction() || 0 + + // Appliquer les dégâts avec armure DR par défaut + const finalDamage = Math.max(0, damageTotal - armorDR) + await defender.applyDamage(-finalDamage) + + // Créer un message de confirmation + const messageContent = await foundry.applications.handlebars.renderTemplate( + "systems/fvtt-lethal-fantasy/templates/damage-applied-message.hbs", + { + targetName: defender.name, + damage: finalDamage, + drText: armorDR > 0 ? `Armor DR: ${armorDR}` : "", + weaponName: weaponName, + attackerName: attackerName, + rawDamage: damageTotal + } + ) + + await ChatMessage.create({ + content: messageContent, + speaker: ChatMessage.getSpeaker({ actor: defender }) + }) +}) + + /** * Create a macro when dropping an entity on the hotbar * Item - open roll dialog diff --git a/module/config/d30_results_tables.json b/module/config/d30_results_tables.json new file mode 100644 index 0000000..e94d0fa --- /dev/null +++ b/module/config/d30_results_tables.json @@ -0,0 +1,152 @@ +{ + "d30_dice_results": { + "30": { + "melee_attack": "Possible Lethal or Vital Strike or Add D20E to Attack", + "ranged_attack": "Possible Lethal or Vital Strike or Add D20E to Attack", + "melee_defense": "Possible Flawless or Legendary Defense or Add D20E to Defense", + "arcane_spell_attack": "Possible Lethal or Vital Magical Strike or Add D20E to Spell Attack", + "arcane_spell_defense": "Possible Spell Catastrophe or adds D20E to Spell Defense", + "skill_rolls": "Skill Succeeds Regardless of Opposing Roll / Success at highest level / Matching 30s cancel each other out" + }, + "29": { + "melee_attack": "Gain 1 Grit", + "ranged_attack": "Gain 1 Grit", + "melee_defense": "Gain 1 Grit", + "arcane_spell_attack": "Gain 1 Grit", + "arcane_spell_defense": "Gain 1 Grit", + "skill_rolls": "Gain 1 Grit" + }, + "28": { + "melee_attack": "Shield Destruction", + "ranged_attack": "empty", + "melee_defense": "empty", + "arcane_spell_attack": "empty", + "arcane_spell_defense": "empty", + "skill_rolls": "empty" + }, + "27": { + "melee_attack": "Granted D6 (1-6) Attack Modifier for This Melee Attack", + "ranged_attack": "Granted D6 (1-6) Attack Modifier for This Ranged Attack", + "melee_defense": "Granted 1 Luck dice for Use in This Combat Only", + "arcane_spell_attack": "No Spell Lethargy (the Aether Approves)", + "arcane_spell_defense": "Caster Suffers Severe pain and will be under a flash of pain for 1D6E seconds", + "skill_rolls": "Granted D6 (1-6) Skill Modifier for this Skill Attempt" + }, + "26": { + "melee_attack": "Shield Destruction", + "ranged_attack": "empty", + "melee_defense": "empty", + "arcane_spell_attack": "empty", + "arcane_spell_defense": "empty", + "skill_rolls": "empty" + }, + "25": { + "melee_attack": "Bleed, Knock-Back on Hit", + "ranged_attack": "Bleed", + "melee_defense": "Kick, Punch or Shield Bash", + "arcane_spell_attack": "empty", + "arcane_spell_defense": "empty", + "skill_rolls": "Add 1 to Skill Roll" + }, + "21": { + "melee_attack": "Hit Inflicts Flash of Pain 1D6E seconds", + "ranged_attack": "Hit Inflicts Flash of Pain 1D6E seconds", + "melee_defense": "Defender Recovers or ignores any flash of pain", + "arcane_spell_attack": "Magical Damage inflicts Flash of pain 1D6E seconds", + "arcane_spell_defense": "Caster Suffers Severe pain and will be under a flash of pain for 1D6E seconds", + "skill_rolls": "empty" + }, + "20": { + "melee_attack": "Possible Vicious Strike. Bleed, Knock-back on Hit", + "ranged_attack": "Possible Vicious Strike. Bleeding wound inflicted on hit.", + "melee_defense": "Possible 20/20 defense (avoids Any Attack Except a Lethal Strike). Grants a Kick, Punch or Shield Bash counter", + "arcane_spell_attack": "Possible Vicious Application of a Magical Attack", + "arcane_spell_defense": "Possible 20/20 Spell defense (Saves Against Any Magical Attack Except a Lethal Magical Strike)", + "skill_rolls": "20 Added to Skill Roll" + }, + "15": { + "melee_attack": "Bleed, Knock-back on Hit", + "ranged_attack": "Bleed", + "melee_defense": "Kick, Punch or Shield Bash", + "arcane_spell_attack": "empty", + "arcane_spell_defense": "empty", + "skill_rolls": "Add 1 to Skill Roll" + }, + "13": { + "melee_attack": "empty", + "ranged_attack": "empty", + "melee_defense": "empty", + "arcane_spell_attack": "empty", + "arcane_spell_defense": "empty", + "skill_rolls": "empty" + }, + "11": { + "melee_attack": "Flurry Attack or Hit to Miss", + "ranged_attack": "Roll 2x Damage Dice", + "melee_defense": "empty", + "arcane_spell_attack": "empty", + "arcane_spell_defense": "empty", + "skill_rolls": "empty" + }, + "10": { + "melee_attack": "Bleed, Knock-back on Hit", + "ranged_attack": "Bleed", + "melee_defense": "Kick, Punch or Shield Bash", + "arcane_spell_attack": "empty", + "arcane_spell_defense": "empty", + "skill_rolls": "Add 1 to Skill Roll" + }, + "8": { + "melee_attack": "Mulligan, Can Choose to Re-roll This Attack", + "ranged_attack": "Mulligan, Can Choose to Re-Roll This Attack", + "melee_defense": "Mulligan, Can Choose to Re-Roll This Defense", + "arcane_spell_attack": "Mulligan, Can Re-Roll This Spell Attack", + "arcane_spell_defense": "Mulligan, Can Re-Roll This Spell Defense", + "skill_rolls": "Mulligan, Can Re-Roll This Skill roll" + }, + "7": { + "melee_attack": "Flurry Attack on Hit or Miss", + "ranged_attack": "Roll 2x Double Damage Dice", + "melee_defense": "empty", + "arcane_spell_attack": "empty", + "arcane_spell_defense": "empty", + "skill_rolls": "empty" + }, + "5": { + "melee_attack": "Bleed, Knock-back on Hit", + "ranged_attack": "Bleed", + "melee_defense": "Kick, Punch, or Shield Bash", + "arcane_spell_attack": "empty", + "arcane_spell_defense": "empty", + "skill_rolls": "Add 1 to Skill Roll" + }, + "3": { + "melee_attack": "Triple Damage", + "ranged_attack": "Triple Damage", + "melee_defense": "DR Tripled including Shield", + "arcane_spell_attack": "Triple Damage on Spell Damage", + "arcane_spell_defense": "D12 Added to Spell Defense Modifier", + "skill_rolls": "empty" + }, + "2": { + "melee_attack": "Double Damage", + "ranged_attack": "Double Damage", + "melee_defense": "DR Doubled including Shield", + "arcane_spell_attack": "Double Damage on Spell Damage", + "arcane_spell_defense": "D6 Added to Spell Defense Modifier", + "skill_rolls": "empty" + }, + "1": { + "melee_attack": "empty", + "ranged_attack": "Possible Fumble Ranged ammo is broken unrecoverable", + "melee_defense": "empty", + "arcane_spell_attack": "Possible Spell Calamity or Catastrophe", + "arcane_spell_defense": "empty", + "skill_rolls": "empty" + } + }, + "definitions": { + "flash_of_pain": "Causes the victim to defend with disfavor. They can only walk and cannot attack, cast spells, call miracles or perform skills.", + "shield_destruction_condition": "Occurs only if damage exceeds the shields DR." + } +} diff --git a/module/documents/_module.mjs b/module/documents/_module.mjs index cb4a51f..1e4e148 100644 --- a/module/documents/_module.mjs +++ b/module/documents/_module.mjs @@ -2,3 +2,4 @@ export { default as LethalFantasyActor } from "./actor.mjs" export { default as LethalFantasyItem } from "./item.mjs" export { default as LethalFantasyRoll } from "./roll.mjs" export { default as LethalFantasyChatMessage } from "./chat-message.mjs" +export { default as D30Roll } from "./d30-roll.mjs" diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index fda652b..91e26aa 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -153,8 +153,8 @@ export default class LethalFantasyActor extends Actor { } /* *************************************************/ - async prepareRoll(rollType, rollKey, rollDice) { - console.log("Preparing roll", rollType, rollKey, rollDice) + async prepareRoll(rollType, rollKey, rollDice, defenderId) { + console.log("Preparing roll", rollType, rollKey, rollDice, defenderId) let rollTarget switch (rollType) { case "granted": @@ -268,7 +268,7 @@ export default class LethalFantasyActor extends Actor { rollTarget.magicUser = this.system.biodata.magicUser rollTarget.actorModifiers = foundry.utils.duplicate(this.system.modifiers) rollTarget.actorLevel = this.system.biodata.level - await this.system.roll(rollType, rollTarget) + await this.system.roll(rollType, rollTarget, defenderId) } } diff --git a/module/documents/d30-roll.mjs b/module/documents/d30-roll.mjs new file mode 100644 index 0000000..eb6eac8 --- /dev/null +++ b/module/documents/d30-roll.mjs @@ -0,0 +1,215 @@ +/** + * Classe pour gérer les résultats du D30 dans Lethal Fantasy + */ +export default class D30Roll { + /** + * Table des résultats D30 chargée depuis le fichier JSON + * @type {Object} + */ + static resultsTable = null + + /** + * Définitions des conditions spéciales + * @type {Object} + */ + static definitions = null + + /** + * Types de jets supportés + * @type {Object} + */ + static ROLL_TYPES = { + MELEE_ATTACK: "melee_attack", + RANGED_ATTACK: "ranged_attack", + MELEE_DEFENSE: "melee_defense", + ARCANE_SPELL_ATTACK: "arcane_spell_attack", + ARCANE_SPELL_DEFENSE: "arcane_spell_defense", + SKILL_ROLLS: "skill_rolls" + } + + /** + * Initialise la classe en chargeant la table des résultats + * @returns {Promise} + */ + static async initialize() { + try { + const response = await fetch("systems/fvtt-lethal-fantasy/module/config/d30_results_tables.json") + const data = await response.json() + + this.resultsTable = data.d30_dice_results + this.definitions = data.definitions + + console.log("D30Roll | D30 results table loaded successfully") + } catch (error) { + console.error("D30Roll | Error loading D30 table:", error) + ui.notifications.error("Unable to load D30 results table") + } + } + + /** + * Récupère le résultat d'un jet de D30 + * @param {number} diceValue La valeur du dé (1-30) + * @param {string} rollType Le type de jet externe (ex: "weapon-attack", "spell-attack", etc.) + * @param {Object} weapon L'arme ou l'objet utilisé (optionnel, nécessaire pour certains types) + * @returns {string|null} Le résultat correspondant ou null si vide/non trouvé + */ + static getResult(diceValue, rollType, weapon = null) { + if (!this.resultsTable) { + console.warn("D30Roll | Results table is not initialized. Call D30Roll.initialize() first.") + return null + } + + // Validation des paramètres + if (diceValue < 1 || diceValue > 30) { + console.warn(`D30Roll | Invalid dice value: ${diceValue}. Must be between 1 and 30.`) + return null + } + + // Convert external rollType to internal rollType + const internalType = this.convertToInternalType(rollType, weapon) + + if (!internalType) { + console.warn(`D30Roll | Could not convert roll type: ${rollType}`) + return null + } + + if (!Object.values(this.ROLL_TYPES).includes(internalType)) { + console.warn(`D30Roll | Invalid internal roll type: ${internalType}`) + return null + } + + const resultEntry = this.resultsTable[diceValue] + if (!resultEntry) { + console.warn(`D30Roll | No entry found for value ${diceValue}`) + return null + } + + const result = resultEntry[internalType] + + // Retourne null si le résultat est "empty" + if (result === "empty" || !result) { + return null + } + + return result + } + + /** + * Convertit un rollType externe en rollType interne + * @param {string} externalType Le type de jet externe (ex: "weapon-attack") + * @param {Object} weapon L'arme ou l'objet utilisé (optionnel) + * @returns {string|null} Le type interne correspondant ou null + */ + static convertToInternalType(externalType, weapon = null) { + // Attack types - need weapon to determine if melee or ranged + if (externalType === "weapon-attack") { + if (!weapon) { + console.warn("D30Roll | Weapon object required for weapon-attack type") + return this.ROLL_TYPES.MELEE_ATTACK // Default to melee + } + return weapon.system?.weaponType === "ranged" + ? this.ROLL_TYPES.RANGED_ATTACK + : this.ROLL_TYPES.MELEE_ATTACK + } + + // Monster attacks - default to melee + if (externalType === "monster-attack") { + // Check if weapon object has range information + if (weapon?.system?.weaponType === "ranged") { + return this.ROLL_TYPES.RANGED_ATTACK + } + return this.ROLL_TYPES.MELEE_ATTACK + } + + // Defense types + if (externalType === "weapon-defense" || externalType === "monster-defense") { + return this.ROLL_TYPES.MELEE_DEFENSE + } + + // Spell types + if (externalType === "spell-attack" || externalType === "spell" || externalType === "spell-power") { + return this.ROLL_TYPES.ARCANE_SPELL_ATTACK + } + + // Skill types + if (externalType === "skill" || externalType === "monster-skill" || + externalType === "save" || externalType === "challenge") { + return this.ROLL_TYPES.SKILL_ROLLS + } + + // If no match, return null + console.warn(`D30Roll | Unknown external roll type: ${externalType}`) + return null + } + + /** + * Récupère toutes les informations pour une valeur de dé donnée + * @param {number} diceValue La valeur du dé (1-30) + * @returns {Object|null} Tous les résultats pour cette valeur ou null + */ + static getAllResultsForValue(diceValue) { + if (!this.resultsTable) { + console.warn("D30Roll | Results table is not initialized.") + return null + } + + if (diceValue < 1 || diceValue > 30) { + console.warn(`D30Roll | Invalid dice value: ${diceValue}`) + return null + } + + return this.resultsTable[diceValue] + } + + /** + * Récupère la définition d'une condition spéciale + * @param {string} definitionKey La clé de la définition (ex: "flash_of_pain") + * @returns {string|null} La définition ou null + */ + static getDefinition(definitionKey) { + if (!this.definitions) { + console.warn("D30Roll | Definitions are not initialized.") + return null + } + + return this.definitions[definitionKey] || null + } + + /** + * Vérifie si un résultat est vide + * @param {string} result Le résultat à vérifier + * @returns {boolean} True si le résultat est vide + */ + static isEmptyResult(result) { + return !result || result === "empty" + } + + /** + * Récupère un résultat formaté pour l'affichage + * @param {number} diceValue La valeur du dé (1-30) + * @param {string} rollType Le type de jet externe + * @param {Object} weapon L'arme ou l'objet utilisé (optionnel) + * @returns {Object} Un objet avec le résultat et des informations de formatage + */ + static getFormattedResult(diceValue, rollType, weapon = null) { + const result = this.getResult(diceValue, rollType, weapon) + const internalType = this.convertToInternalType(rollType, weapon) + + return { + value: diceValue, + rollType: rollType, + internalType: internalType, + result: result, + isEmpty: this.isEmptyResult(result), + hasResult: !this.isEmptyResult(result) + } + } + + /** + * Vérifie si la table est chargée + * @returns {boolean} True si la table est chargée + */ + static isInitialized() { + return this.resultsTable !== null && this.definitions !== null + } +} diff --git a/module/documents/roll.mjs b/module/documents/roll.mjs index 506b351..78cd800 100644 --- a/module/documents/roll.mjs +++ b/module/documents/roll.mjs @@ -1,5 +1,6 @@ import { SYSTEM } from "../config/system.mjs" import LethalFantasyUtils from "../utils.mjs" +import D30Roll from "./d30-roll.mjs" export default class LethalFantasyRoll extends Roll { /** @@ -92,6 +93,10 @@ export default class LethalFantasyRoll extends Roll { return this.options.D30result } + get D30message() { + return this.options.D30message + } + get badResult() { return this.options.badResult } @@ -100,6 +105,10 @@ export default class LethalFantasyRoll extends Roll { return this.options.rollData } + get defenderId() { + return this.options.defenderId + } + /** * Prompt the user with a dialog to configure and execute a roll. * @@ -544,6 +553,14 @@ export default class LethalFantasyRoll extends Roll { game.dice3d.showForRoll(rollD30, game.user, true) } options.D30result = rollD30.total + + // Récupérer le message D30 correspondant + const d30Message = D30Roll.getResult( + rollD30.total, + options.rollType, + options.rollTarget?.weapon + ) + options.D30message = d30Message } let rollTotal = 0 @@ -599,8 +616,10 @@ export default class LethalFantasyRoll extends Roll { rollBase.options.rollTarget = options.rollTarget rollBase.options.titleFormula = titleFormula rollBase.options.D30result = options.D30result + rollBase.options.D30message = options.D30message rollBase.options.badResult = badResult rollBase.options.rollData = foundry.utils.duplicate(rollData) + rollBase.options.defenderId = options.defenderId /** * A hook event that fires after the roll has been made. @@ -862,8 +881,17 @@ export default class LethalFantasyRoll extends Roll { let toCompare = Math.min(currentAction.progressionCount, max) if (roll.total <= toCompare) { // Notify that the player can act now with a chat message - let message = game.i18n.format("LETHALFANTASY.Notifications.messageLethargyOK", { name: combatant.actor.name, spellName: currentAction.name, roll: roll.total }) - ChatMessage.create({ content: message, speaker: ChatMessage.getSpeaker({ actor: combatant.actor }) }) + const messageContent = await foundry.applications.handlebars.renderTemplate( + "systems/fvtt-lethal-fantasy/templates/progression-message.hbs", + { + success: true, + actorName: combatant.actor.name, + weaponName: currentAction.name, + rollResult: roll.total, + isLethargy: true + } + ) + ChatMessage.create({ content: messageContent, speaker: ChatMessage.getSpeaker({ actor: combatant.actor }) }) // Update the combatant progression count await combatant.setFlag(SYSTEM.id, "currentAction", "") // Display the action selection window again @@ -872,8 +900,18 @@ export default class LethalFantasyRoll extends Roll { // Notify that the player cannot act now with a chat message currentAction.progressionCount += 1 await combatant.setFlag(SYSTEM.id, "currentAction", foundry.utils.duplicate(currentAction)) - let message = game.i18n.format("LETHALFANTASY.Notifications.messageLethargyKO", { name: combatant.actor.name, spellName: currentAction.name, roll: roll.total }) - ChatMessage.create({ content: message, speaker: ChatMessage.getSpeaker({ actor: combatant.actor }) }) + const messageContent = await foundry.applications.handlebars.renderTemplate( + "systems/fvtt-lethal-fantasy/templates/progression-message.hbs", + { + success: false, + actorName: combatant.actor.name, + weaponName: currentAction.name, + rollResult: roll.total, + progressionCount: currentAction.progressionCount, + isLethargy: true + } + ) + ChatMessage.create({ content: messageContent, speaker: ChatMessage.getSpeaker({ actor: combatant.actor }) }) } } } @@ -919,16 +957,33 @@ export default class LethalFantasyRoll extends Roll { if (roll.total <= max) { // Notify that the player can act now with a chat message - let message = game.i18n.format("LETHALFANTASY.Notifications.messageProgressionOK", { isMonster, name: combatant.actor.name, weapon: currentAction.name, roll: roll.total }) - ChatMessage.create({ content: message, speaker: ChatMessage.getSpeaker({ actor: combatant.actor }) }) + const messageContent = await foundry.applications.handlebars.renderTemplate( + "systems/fvtt-lethal-fantasy/templates/progression-message.hbs", + { + success: true, + actorName: combatant.actor.name, + weaponName: currentAction.name, + rollResult: roll.total + } + ) + ChatMessage.create({ content: messageContent, speaker: ChatMessage.getSpeaker({ actor: combatant.actor }) }) await combatant.setFlag(SYSTEM.id, "currentAction", "") combatant.actor.prepareRoll(currentAction.type === "weapon" ? "weapon-attack" : "spell-attack", currentAction._id) } else { // Notify that the player cannot act now with a chat message currentAction.progressionCount += 1 combatant.setFlag(SYSTEM.id, "currentAction", foundry.utils.duplicate(currentAction)) - let message = game.i18n.format("LETHALFANTASY.Notifications.messageProgressionKO", { isMonster, name: combatant.actor.name, weapon: currentAction.name, roll: roll.total }) - ChatMessage.create({ content: message, speaker: ChatMessage.getSpeaker({ actor: combatant.actor }) }) + const messageContent = await foundry.applications.handlebars.renderTemplate( + "systems/fvtt-lethal-fantasy/templates/progression-message.hbs", + { + success: false, + actorName: combatant.actor.name, + weaponName: currentAction.name, + rollResult: roll.total, + progressionCount: currentAction.progressionCount + } + ) + ChatMessage.create({ content: messageContent, speaker: ChatMessage.getSpeaker({ actor: combatant.actor }) }) } } } @@ -1132,10 +1187,28 @@ export default class LethalFantasyRoll extends Roll { async _getChatCardData(isPrivate) { // Générer la liste des combatants de la scène let combatants = [] - if (game?.combat?.combatants && this.rollData?.isDamage) { - for (let c of game.combat.combatants) { - if (c.actorId !== this.actorId) { - combatants.push({ id: c.id, name: c.name }) + let isAttack = this.type === "weapon-attack" || this.type === "monster-attack" || this.type === "spell-attack" || this.type === "miracle-attack" + if (this.rollData?.isDamage || isAttack) { + // D'abord, ajouter les combattants du combat actif + if (game?.combat?.combatants) { + for (let c of game.combat.combatants) { + if (c.actorId !== this.actorId) { + combatants.push({ id: c.id, name: c.name, tokenId: c.token.id }) + } + } + } + + // Ensuite, ajouter tous les tokens de la scène active qui ne sont pas déjà dans la liste + if (canvas?.scene?.tokens) { + const existingTokenIds = new Set(combatants.map(c => c.tokenId)) + for (let token of canvas.scene.tokens) { + if (token.actorId !== this.actorId && !existingTokenIds.has(token.id)) { + combatants.push({ + id: token.id, + name: token.name, + tokenId: token.id + }) + } } } } @@ -1184,11 +1257,16 @@ export default class LethalFantasyRoll extends Roll { targetName: this.targetName, targetArmor: this.targetArmor, D30result: this.D30result, + D30message: this.D30message, badResult: this.badResult, rollData: this.rollData, isPrivate: isPrivate, combatants: combatants, - weaponDamageOptions: weaponDamageOptions + weaponDamageOptions: weaponDamageOptions, + isAttack: isAttack, + defenderId: this.defenderId, + // Vérifier si l'utilisateur peut sélectionner une cible (est GM ou possède l'acteur) + canSelectTarget: game.user.isGM || game.actors.get(this.actorId)?.testUserPermission(game.user, "OWNER") } cardData.cssClass = cardData.css.join(" ") cardData.tooltip = isPrivate ? "" : await this.getTooltip() diff --git a/module/models/character.mjs b/module/models/character.mjs index a99546b..0e7dc9e 100644 --- a/module/models/character.mjs +++ b/module/models/character.mjs @@ -273,7 +273,7 @@ export default class LethalFantasyCharacter extends foundry.abstract.TypeDataMod * @param {"="|"+"|"++"|"-"|"--"} rollAdvantage If there is an avantage (+), a disadvantage (-), a double advantage (++), a double disadvantage (--) or a normal roll (=). * @returns {Promise} - A promise that resolves to null if the roll is cancelled. */ - async roll(rollType, rollTarget) { + async roll(rollType, rollTarget, defenderId) { const hasTarget = false let roll = await LethalFantasyRoll.prompt({ rollType, @@ -282,7 +282,8 @@ export default class LethalFantasyCharacter extends foundry.abstract.TypeDataMod actorName: this.parent.name, actorImage: this.parent.img, hasTarget, - target: false + target: false, + defenderId }) if (!roll) return null @@ -311,6 +312,13 @@ export default class LethalFantasyCharacter extends foundry.abstract.TypeDataMod } async rollProgressionDice(combatId, combatantId, rollProgressionCount) { + let combatant = game.combats.get(combatId)?.combatants?.get(combatantId) + + // Don't roll if the combatant is defeated + if (combatant?.isDefeated) { + ui.notifications.warn(`${this.parent.name} is defeated and cannot attack.`) + return + } // Get all weapons from the actor let weapons = this.parent.items.filter(i => i.type === "weapon" && i.system.weaponType === "melee") diff --git a/module/models/monster.mjs b/module/models/monster.mjs index 36c2471..ec5eeb0 100644 --- a/module/models/monster.mjs +++ b/module/models/monster.mjs @@ -122,6 +122,10 @@ export default class LethalFantasyMonster extends foundry.abstract.TypeDataModel shieldDamageReduction: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }), shieldDefenseDice: new fields.StringField({ required: true, nullable: false, initial: "d4" }) }) + schema.combatHTH = new fields.SchemaField({ + attack1: attackField("1"), + attack2: attackField("2") + }) return schema @@ -137,7 +141,7 @@ export default class LethalFantasyMonster extends foundry.abstract.TypeDataModel * @param {"="|"+"|"++"|"-"|"--"} rollAdvantage If there is an avantage (+), a disadvantage (-), a double advantage (++), a double disadvantage (--) or a normal roll (=). * @returns {Promise} - A promise that resolves to null if the roll is cancelled. */ - async roll(rollType, rollTarget) { + async roll(rollType, rollTarget, defenderId = undefined) { const hasTarget = false let roll = await LethalFantasyRoll.prompt({ rollType, @@ -146,14 +150,15 @@ export default class LethalFantasyMonster extends foundry.abstract.TypeDataModel actorName: this.parent.name, actorImage: this.parent.img, hasTarget, - target: false + target: false, + defenderId }) if (!roll) return null await roll.toMessage({}, { rollMode: roll.options.rollMode }) } - async prepareMonsterRoll(rollType, rollKey, rollDice = undefined, tokenId = undefined, damageModifier = undefined) { + async prepareMonsterRoll(rollType, rollKey, rollDice = undefined, tokenId = undefined, damageModifier = undefined, defenderId = undefined) { let rollTarget switch (rollType) { case "monster-attack": @@ -166,6 +171,18 @@ export default class LethalFantasyMonster extends foundry.abstract.TypeDataModel rollTarget.damageModifier = damageModifier } break + case "monster-attack-hth": + case "monster-defense-hth": + case "monster-damage-hth": + rollTarget = foundry.utils.duplicate(this.combatHTH[rollKey]) + rollTarget.rollKey = rollKey + // Si damageModifier est fourni (depuis le chat), l'utiliser au lieu de celui de la fiche + if (damageModifier !== undefined && rollType === "monster-damage-hth") { + rollTarget.damageModifier = damageModifier + } + // Convertir le type de roll pour utiliser les mêmes handlers que les attaques normales + rollType = rollType.replace("-hth", "") + break case "monster-skill": rollTarget = foundry.utils.duplicate(this.resists[rollKey]) rollTarget.rollKey = rollKey @@ -237,7 +254,7 @@ export default class LethalFantasyMonster extends foundry.abstract.TypeDataModel if (rollTarget) { rollTarget.tokenId = tokenId console.log(rollTarget) - await this.roll(rollType, rollTarget) + await this.roll(rollType, rollTarget, defenderId) } } @@ -261,6 +278,13 @@ export default class LethalFantasyMonster extends foundry.abstract.TypeDataModel } async rollProgressionDice(combatId, combatantId) { + let combatant = game.combats.get(combatId)?.combatants?.get(combatantId) + + // Don't roll if the combatant is defeated + if (combatant?.isDefeated) { + ui.notifications.warn(`${this.parent.name} is defeated and cannot attack.`) + return + } const rollModes = foundry.utils.duplicate(CONFIG.Dice.rollModes) const fieldRollMode = new foundry.data.fields.StringField({ @@ -271,7 +295,6 @@ export default class LethalFantasyMonster extends foundry.abstract.TypeDataModel let roll = new Roll("1D12") await roll.evaluate() - let combatant = game.combats.get(combatId)?.combatants?.get(combatantId) let msg = await roll.toMessage({ flavor: `Progression Roll for ${this.parent.name}` }) if (game?.dice3d) { @@ -283,8 +306,16 @@ export default class LethalFantasyMonster extends foundry.abstract.TypeDataModel let attack = this.attacks[key] if (attack.enabled && attack.attackScore > 0 && attack.attackScore === roll.total) { hasAttack = true - let message = game.i18n.format("LETHALFANTASY.Notifications.messageProgressionOKMonster", { isMonster: true, name: this.parent.name, weapon: attack.name, roll: roll.total }) - ChatMessage.create({ content: message, speaker: ChatMessage.getSpeaker({ actor: this.parent }) }) + const messageContent = await foundry.applications.handlebars.renderTemplate( + "systems/fvtt-lethal-fantasy/templates/progression-message.hbs", + { + success: true, + actorName: this.parent.name, + weaponName: attack.name, + rollResult: roll.total + } + ) + ChatMessage.create({ content: messageContent, speaker: ChatMessage.getSpeaker({ actor: this.parent }) }) let token = combatant?.token this.prepareMonsterRoll("monster-attack", key, undefined, token?.id) if (token?.object) { @@ -293,9 +324,41 @@ export default class LethalFantasyMonster extends foundry.abstract.TypeDataModel } } } + // Check Hand To Hand attacks as well if (!hasAttack) { - let message = game.i18n.format("LETHALFANTASY.Notifications.messageProgressionKOMonster", { isMonster: true, name: this.parent.name, roll: roll.total }) - ChatMessage.create({ content: message, speaker: ChatMessage.getSpeaker({ actor: this.parent }) }) + for (let key in this.combatHTH) { + let attack = this.combatHTH[key] + if (attack.enabled && attack.attackScore > 0 && attack.attackScore === roll.total) { + hasAttack = true + const messageContent = await foundry.applications.handlebars.renderTemplate( + "systems/fvtt-lethal-fantasy/templates/progression-message.hbs", + { + success: true, + actorName: this.parent.name, + weaponName: `${attack.name} (HTH)`, + rollResult: roll.total + } + ) + ChatMessage.create({ content: messageContent, speaker: ChatMessage.getSpeaker({ actor: this.parent }) }) + let token = combatant?.token + this.prepareMonsterRoll("monster-attack-hth", key, undefined, token?.id) + if (token?.object) { + token.object?.control({ releaseOthers: true }); + return canvas.animatePan(token.object.center); + } + } + } + } + if (!hasAttack) { + const messageContent = await foundry.applications.handlebars.renderTemplate( + "systems/fvtt-lethal-fantasy/templates/progression-message.hbs", + { + success: false, + actorName: this.parent.name, + rollResult: roll.total + } + ) + ChatMessage.create({ content: messageContent, speaker: ChatMessage.getSpeaker({ actor: this.parent }) }) } } diff --git a/module/utils.mjs b/module/utils.mjs index a282176..2c659d0 100644 --- a/module/utils.mjs +++ b/module/utils.mjs @@ -1,3 +1,9 @@ +import { SYSTEM } from "./config/system.mjs" + +// Map temporaire pour stocker les données d'attaque en attente de défense +if (!globalThis.pendingDefenses) { + globalThis.pendingDefenses = new Map() +} export default class LethalFantasyUtils { @@ -75,9 +81,468 @@ export default class LethalFantasyUtils { actor = game.actors.get(msg.actorId) actor.system.rollProgressionDice(msg.combatId, msg.combatantId, msg.rollProgressionCount) break + case "requestDefense": + // Vérifier si le message est destiné à cet utilisateur + if (msg.userId === game.user.id) { + LethalFantasyUtils.showDefenseRequest(msg) + } + break + case "offerAttackerGrit": + // Vérifier si le message est destiné à cet utilisateur + if (msg.userId === game.user.id) { + LethalFantasyUtils.handleAttackerGritOffer(msg) + } + break } } + /* -------------------------------------------- */ + static async handleAttackerGritOffer(msg) { + const { attackerId, attackRoll, defenseRoll, attackerName, defenderName, attackWeaponId, attackRollType, attackRollKey, defenderId } = msg + + const attacker = game.actors.get(attackerId) + if (!attacker) { + console.warn("Attacker not found:", attackerId) + return + } + + const attackBonus = await LethalFantasyUtils.offerAttackerGritBonus( + attacker, + attackRoll, + defenseRoll, + attackerName, + defenderName + ) + + const attackRollFinal = attackRoll + attackBonus + + // Maintenant créer le message de comparaison + await LethalFantasyUtils.compareAttackDefense({ + attackerName, + attackerId, + attackRoll: attackRollFinal, + attackWeaponId, + attackRollType, + attackRollKey, + defenderName, + defenderId, + defenseRoll + }) + } + + /* -------------------------------------------- */ + static async showDefenseRequest(msg) { + const attackerName = msg.attackerName + const attackerId = msg.attackerId + const defenderName = msg.defenderName + const weaponName = msg.weaponName || "attack" + const attackRoll = msg.attackRoll + const attackWeaponId = msg.attackWeaponId + const attackRollType = msg.attackRollType + const attackRollKey = msg.attackRollKey + const combatantId = msg.combatantId + const tokenId = msg.tokenId + + // Récupérer le défenseur - essayer d'abord depuis le combat, puis depuis le token + let defender = null + + if (game.combat && combatantId) { + const combatant = game.combat.combatants.get(combatantId) + if (combatant) { + defender = combatant.actor + } + } + + // Si pas trouvé dans le combat, chercher le token directement + if (!defender && tokenId) { + const token = canvas.tokens.get(tokenId) + if (token) { + defender = token.actor + } + } + + if (!defender) { + ui.notifications.error("Defender actor not found") + return + } + + const isMonster = defender.type === "monster" + +// Pour les monstres, récupérer les attaques activées + if (isMonster) { + const enabledAttacks = Object.entries(defender.system.attacks).filter(([key, attack]) => attack.enabled) + + if (enabledAttacks.length === 0) { + ui.notifications.warn("No enabled attacks available for defense") + return + } + + // Créer le contenu du dialogue pour monstre + let attacksHTML = enabledAttacks.map(([key, attack]) => + `` + ).join("") + + const content = ` +
+
+

${attackerName} attacks ${defenderName} with ${weaponName}!

+

Attack roll: ${attackRoll}

+
+
+ + +
+
+ ` + + // Afficher le dialogue + const result = await foundry.applications.api.DialogV2.wait({ + window: { title: "Defense Roll" }, + classes: ["lethalfantasy"], + content, + buttons: [ + { + label: "Roll Defense", + icon: "fa-solid fa-shield", + callback: (event, button, dialog) => { + const attackKey = button.form.elements.attackKey.value + return attackKey + }, + }, + ], + rejectClose: false + }) + + // Si l'utilisateur a validé, lancer le jet de défense + if (result) { + // Stocker temporairement les données pour le hook preCreateChatMessage + game.lethalFantasy = game.lethalFantasy || {} + game.lethalFantasy.nextDefenseData = { + attackerId, + attackRoll, + attackerName, + defenderName, + attackWeaponId, + attackRollType, + attackRollKey, + defenderId: defender.id + } + + console.log("Storing defense data for monster:", defender.id) + + defender.system.prepareMonsterRoll("monster-defense", result) + } + return + } + + // Pour les personnages, récupérer les armes équipées + const equippedWeapons = defender.items.filter(i => + i.type === "weapon" && i.system.equipped === true + ) + + if (equippedWeapons.length === 0) { + ui.notifications.warn("No equipped weapons for defense") + return + } + + // Créer le contenu du dialogue pour personnage + let weaponsHTML = equippedWeapons.map(w => + `` + ).join("") + + const content = ` +
+
+

${attackerName} attacks ${defenderName} with ${weaponName}!

+

Attack roll: ${attackRoll}

+
+
+ + +
+
+ ` + + // Afficher le dialogue + const result = await foundry.applications.api.DialogV2.wait({ + window: { title: "Defense Roll" }, + classes: ["lethalfantasy"], + content, + buttons: [ + { + label: "Roll Defense", + icon: "fa-solid fa-shield", + callback: (event, button, dialog) => { + const weaponId = button.form.elements.weaponId.value + return weaponId + }, + }, + ], + rejectClose: false + }) + + // Si l'utilisateur a validé, lancer le jet de défense + if (result) { + // Stocker temporairement les données pour le hook preCreateChatMessage + game.lethalFantasy = game.lethalFantasy || {} + game.lethalFantasy.nextDefenseData = { + attackerId, + attackRoll, + attackerName, + defenderName, + attackWeaponId, + attackRollType, + attackRollKey, + defenderId: defender.id + } + + console.log("Storing defense data for character:", defender.id) + + defender.prepareRoll("weapon-defense", result) + } + } + + /* -------------------------------------------- */ + static async offerGritLuckBonus(defender, attackRoll, currentDefenseRoll, attackerName, defenderName) { + let totalBonus = 0 + let keepOffering = true + + while (keepOffering && currentDefenseRoll + totalBonus < attackRoll) { + const currentGrit = defender.system.grit.current + const currentLuck = defender.system.luck.current + + // Si plus de points disponibles, sortir + if (currentGrit <= 0 && currentLuck <= 0) { + break + } + + 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: "continue", + label: "Continue (no bonus)", + icon: "fa-solid fa-forward", + callback: () => "continue" + }) + + const content = ` +
+
+

${attackerName} rolled ${attackRoll}

+

${defenderName} currently has ${currentDefenseRoll + totalBonus}

+ ${totalBonus > 0 ? `

Bonus already added: +${totalBonus}

` : ''} +
+

You are losing! Spend Grit or Luck to add 1D6 to your defense?

+
+ ` + + const choice = await foundry.applications.api.DialogV2.wait({ + window: { title: "Defend with Grit or Luck" }, + classes: ["lethalfantasy"], + content, + buttons, + rejectClose: false + }) + + if (!choice || choice === "continue") { + keepOffering = false + break + } + + // Lancer 1D6 + const bonusRoll = new Roll("1d6") + await bonusRoll.evaluate() + + if (game?.dice3d) { + await game.dice3d.showForRoll(bonusRoll, game.user, true) + } + + totalBonus += bonusRoll.total + + // Déduire le point de Grit ou Luck + if (choice === "grit") { + await defender.update({ "system.grit.current": currentGrit - 1 }) + await ChatMessage.create({ + content: `

${defenderName} spends 1 Grit and rolls ${bonusRoll.total}! (Total defense bonus: +${totalBonus})

`, + speaker: ChatMessage.getSpeaker({ actor: defender }) + }) + } else if (choice === "luck") { + await defender.update({ "system.luck.current": currentLuck - 1 }) + await ChatMessage.create({ + content: `

${defenderName} spends 1 Luck and rolls ${bonusRoll.total}! (Total defense bonus: +${totalBonus})

`, + speaker: ChatMessage.getSpeaker({ actor: defender }) + }) + } + } + + return totalBonus + } + + /* -------------------------------------------- */ + static async offerAttackerGritBonus(attacker, currentAttackRoll, defenseRoll, attackerName, defenderName) { + let totalBonus = 0 + let keepOffering = true + + while (keepOffering && currentAttackRoll + totalBonus <= defenseRoll) { + const currentGrit = attacker.system.grit.current + + // Si plus de points de Grit disponibles, sortir + if (currentGrit <= 0) { + break + } + + const buttons = [ + { + action: "grit", + label: `Spend 1 Grit (+1D6) [${currentGrit} left]`, + icon: "fa-solid fa-fist-raised", + callback: () => "grit" + }, + { + action: "continue", + label: "Continue (no bonus)", + icon: "fa-solid fa-forward", + callback: () => "continue" + } + ] + + const content = ` +
+
+

${attackerName} currently has ${currentAttackRoll + totalBonus}

+

${defenderName} rolled ${defenseRoll}

+ ${totalBonus > 0 ? `

Bonus already added: +${totalBonus}

` : ''} +
+

You are losing! Spend Grit to add 1D6 to your attack?

+
+ ` + + const choice = await foundry.applications.api.DialogV2.wait({ + window: { title: "Attack with Grit" }, + classes: ["lethalfantasy"], + content, + buttons, + rejectClose: false + }) + + if (!choice || choice === "continue") { + keepOffering = false + break + } + + // Lancer 1D6 + const bonusRoll = new Roll("1d6") + await bonusRoll.evaluate() + + if (game?.dice3d) { + await game.dice3d.showForRoll(bonusRoll, game.user, true) + } + + totalBonus += bonusRoll.total + + // Déduire le point de Grit + await attacker.update({ "system.grit.current": currentGrit - 1 }) + await ChatMessage.create({ + content: `

${attackerName} spends 1 Grit and rolls ${bonusRoll.total}! (Total attack bonus: +${totalBonus})

`, + speaker: ChatMessage.getSpeaker({ actor: attacker }) + }) + } + + return totalBonus + } + + /* -------------------------------------------- */ + static async compareAttackDefense(data) { + console.log("compareAttackDefense called with:", data) + const isAttackWin = data.attackRoll > data.defenseRoll + console.log("isAttackWin:", isAttackWin, "attackRoll:", data.attackRoll, "defenseRoll:", data.defenseRoll) + + let damageButton = "" + if (isAttackWin && (data.attackWeaponId || data.attackRollKey)) { + console.log("Creating damage button. defenderId:", data.defenderId) + // Déterminer le type de dégâts à lancer + if (data.attackRollType === "weapon-attack") { + damageButton = ` +
+ + +
+ ` + } else if (data.attackRollType === "monster-attack") { + damageButton = ` +
+ +
+ ` + } + } + + const resultMessage = ` +
+

Combat Result

+
+
+
Attacker
+
+
${data.attackerName}
+
${data.attackRoll}
+
+
+
VS
+
+
Defender
+
+
${data.defenderName}
+
${data.defenseRoll}
+
+
+
+
+ ${isAttackWin ? + ` ${data.attackerName} hits ${data.defenderName}!` : + ` ${data.defenderName} parries the attack!` + } +
+ ${damageButton} +
+ ` + + console.log("Creating combat result message...") + await ChatMessage.create({ + content: resultMessage, + speaker: { alias: "Combat System" } + }) + console.log("Combat result message created!") + } + static registerHandlebarsHelpers() { Handlebars.registerHelper('isNull', function (val) { @@ -328,16 +793,26 @@ export default class LethalFantasyUtils { // Message de confirmation let drText = "" if (result.drType === "armor") { - drText = ` (Armor DR: ${armorDR})` + drText = `Armor DR: ${armorDR}` } else if (result.drType === "all") { - drText = ` (Total DR: ${totalDR})` + drText = `Total DR: ${totalDR}` } + const messageContent = await foundry.applications.handlebars.renderTemplate( + "systems/fvtt-lethal-fantasy/templates/damage-applied-message.hbs", + { + targetName: targetActor.name, + damage: result.damage, + drText: drText, + weaponName: weaponName + } + ) + ChatMessage.create({ user: game.user.id, speaker: { alias: targetActor.name }, rollMode: "gmroll", - content: `${targetActor.name} takes ${result.damage} damage${drText} from ${weaponName}` + content: messageContent }) } } diff --git a/packs-system/lf-equipment/000500.log b/packs-system/lf-equipment/000529.log similarity index 100% rename from packs-system/lf-equipment/000500.log rename to packs-system/lf-equipment/000529.log diff --git a/packs-system/lf-equipment/CURRENT b/packs-system/lf-equipment/CURRENT index b8caea6..e20a3af 100644 --- a/packs-system/lf-equipment/CURRENT +++ b/packs-system/lf-equipment/CURRENT @@ -1 +1 @@ -MANIFEST-000498 +MANIFEST-000527 diff --git a/packs-system/lf-equipment/LOG b/packs-system/lf-equipment/LOG index 9c8637b..c5d290f 100644 --- a/packs-system/lf-equipment/LOG +++ b/packs-system/lf-equipment/LOG @@ -1,15 +1,8 @@ -2026/01/12-14:22:56.040680 7fd462ffd6c0 Recovering log #496 -2026/01/12-14:22:56.051263 7fd462ffd6c0 Delete type=3 #494 -2026/01/12-14:22:56.051335 7fd462ffd6c0 Delete type=0 #496 -2026/01/12-14:27:05.789368 7fd4627fc6c0 Level-0 table #501: started -2026/01/12-14:27:05.793070 7fd4627fc6c0 Level-0 table #501: 26835 bytes OK -2026/01/12-14:27:05.799160 7fd4627fc6c0 Delete type=0 #499 -2026/01/12-14:27:05.815143 7fd4627fc6c0 Manual compaction at level-0 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end) -2026/01/12-14:27:05.815173 7fd4627fc6c0 Manual compaction at level-1 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at '!items!zFrygJ2TnrxchBai' @ 1178 : 1 -2026/01/12-14:27:05.815179 7fd4627fc6c0 Compacting 1@1 + 1@2 files -2026/01/12-14:27:05.821137 7fd4627fc6c0 Generated table #502@1: 486 keys, 220368 bytes -2026/01/12-14:27:05.821151 7fd4627fc6c0 Compacted 1@1 + 1@2 files => 220368 bytes -2026/01/12-14:27:05.827440 7fd4627fc6c0 compacted to: files[ 0 0 1 0 0 0 0 ] -2026/01/12-14:27:05.827535 7fd4627fc6c0 Delete type=2 #489 -2026/01/12-14:27:05.828221 7fd4627fc6c0 Delete type=2 #501 -2026/01/12-14:27:05.839529 7fd4627fc6c0 Manual compaction at level-1 from '!items!zFrygJ2TnrxchBai' @ 1178 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end) +2026/01/19-20:32:07.348929 7f14d8ff96c0 Recovering log #525 +2026/01/19-20:32:07.358983 7f14d8ff96c0 Delete type=3 #523 +2026/01/19-20:32:07.359059 7f14d8ff96c0 Delete type=0 #525 +2026/01/19-22:34:04.043006 7f1243fff6c0 Level-0 table #530: started +2026/01/19-22:34:04.043050 7f1243fff6c0 Level-0 table #530: 0 bytes OK +2026/01/19-22:34:04.052889 7f1243fff6c0 Delete type=0 #528 +2026/01/19-22:34:04.082744 7f1243fff6c0 Manual compaction at level-0 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end) +2026/01/19-22:34:04.092309 7f1243fff6c0 Manual compaction at level-1 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end) diff --git a/packs-system/lf-equipment/LOG.old b/packs-system/lf-equipment/LOG.old index 56c081e..c029838 100644 --- a/packs-system/lf-equipment/LOG.old +++ b/packs-system/lf-equipment/LOG.old @@ -1,8 +1,8 @@ -2026/01/04-22:23:04.765915 7f93ebfff6c0 Recovering log #492 -2026/01/04-22:23:04.775983 7f93ebfff6c0 Delete type=3 #490 -2026/01/04-22:23:04.776035 7f93ebfff6c0 Delete type=0 #492 -2026/01/04-22:40:50.964348 7f93e9ffb6c0 Level-0 table #497: started -2026/01/04-22:40:50.964370 7f93e9ffb6c0 Level-0 table #497: 0 bytes OK -2026/01/04-22:40:50.975783 7f93e9ffb6c0 Delete type=0 #495 -2026/01/04-22:40:50.986019 7f93e9ffb6c0 Manual compaction at level-0 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end) -2026/01/04-22:40:50.986046 7f93e9ffb6c0 Manual compaction at level-1 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end) +2026/01/19-20:23:50.466717 7f14da7fc6c0 Recovering log #521 +2026/01/19-20:23:50.476745 7f14da7fc6c0 Delete type=3 #519 +2026/01/19-20:23:50.476798 7f14da7fc6c0 Delete type=0 #521 +2026/01/19-20:32:02.937243 7f1243fff6c0 Level-0 table #526: started +2026/01/19-20:32:02.937265 7f1243fff6c0 Level-0 table #526: 0 bytes OK +2026/01/19-20:32:02.943907 7f1243fff6c0 Delete type=0 #524 +2026/01/19-20:32:02.956456 7f1243fff6c0 Manual compaction at level-0 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end) +2026/01/19-20:32:02.956498 7f1243fff6c0 Manual compaction at level-1 from '!folders!ATr9wZhg5uTVTksM' @ 72057594037927935 : 1 .. '!items!zw9RQocTdz3HRjZK' @ 0 : 0; will stop at (end) diff --git a/packs-system/lf-equipment/MANIFEST-000498 b/packs-system/lf-equipment/MANIFEST-000498 deleted file mode 100644 index fd530eb64a657792b0458d10a4ef5177ecfd6153..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 382 zcmcZ@cTupNfss)vC$%g!CnZVGsj?)sJhM2}IX|}`u_&=5zlfDlUNN&IHMdwX#Ud*; z+_j`C+%dc;+bt@DF^h!(1lXBgG9OyRtEiZkpOcbWRIKP2Qe;^km62{*8WI+gUF^#! z23E%aGpMTEGAJ-VIV7dZ*dr(_%9}9_V$eJOt<$*~7@0mZb22c0VP<*F!F}M~#vq`` z7m&y|W|n!J?2KQTFJFYa$-vY-Bs?#~-M=#2&oa@YkTDlx6x=GeqRMnHqmaC!isTHZ q#7xFn5c#4_HxAXo4E_!>7~#(<9D3M*Rr_!j^Nt!>)? diff --git a/packs-system/lf-equipment/MANIFEST-000527 b/packs-system/lf-equipment/MANIFEST-000527 new file mode 100644 index 0000000000000000000000000000000000000000..21fb3e666115d9c9c614da411fe9fd786ef62fea GIT binary patch literal 178 zcmeA`pgoS$2eSd>_jU&P8Nub5eqnp><`lh)C@?=cB&EvOBPc7%n=uVyP|PHLF>VG%rU@*Z49pW*SmtrM?)h~GBr*{s IG8rTS0BdYAR{#J2 literal 0 HcmV?d00001 diff --git a/packs-system/lf-gifts/000498.log b/packs-system/lf-gifts/000526.log similarity index 100% rename from packs-system/lf-gifts/000498.log rename to packs-system/lf-gifts/000526.log diff --git a/packs-system/lf-gifts/CURRENT b/packs-system/lf-gifts/CURRENT index a7ef44f..f17feb2 100644 --- a/packs-system/lf-gifts/CURRENT +++ b/packs-system/lf-gifts/CURRENT @@ -1 +1 @@ -MANIFEST-000496 +MANIFEST-000524 diff --git a/packs-system/lf-gifts/LOG b/packs-system/lf-gifts/LOG index f8f9290..3abffa2 100644 --- a/packs-system/lf-gifts/LOG +++ b/packs-system/lf-gifts/LOG @@ -1,8 +1,8 @@ -2026/01/12-14:22:56.057091 7fd4637fe6c0 Recovering log #494 -2026/01/12-14:22:56.067553 7fd4637fe6c0 Delete type=3 #492 -2026/01/12-14:22:56.067610 7fd4637fe6c0 Delete type=0 #494 -2026/01/12-14:27:05.799253 7fd4627fc6c0 Level-0 table #499: started -2026/01/12-14:27:05.799272 7fd4627fc6c0 Level-0 table #499: 0 bytes OK -2026/01/12-14:27:05.805798 7fd4627fc6c0 Delete type=0 #497 -2026/01/12-14:27:05.815153 7fd4627fc6c0 Manual compaction at level-0 from '!folders!yPWGvxHJbDNHVSnY' @ 72057594037927935 : 1 .. '!items!x5gLtqlW4sdDmHTd' @ 0 : 0; will stop at (end) -2026/01/12-14:27:05.828314 7fd4627fc6c0 Manual compaction at level-1 from '!folders!yPWGvxHJbDNHVSnY' @ 72057594037927935 : 1 .. '!items!x5gLtqlW4sdDmHTd' @ 0 : 0; will stop at (end) +2026/01/19-20:32:07.362345 7f14d9ffb6c0 Recovering log #522 +2026/01/19-20:32:07.372537 7f14d9ffb6c0 Delete type=3 #520 +2026/01/19-20:32:07.372596 7f14d9ffb6c0 Delete type=0 #522 +2026/01/19-22:34:04.165591 7f1243fff6c0 Level-0 table #527: started +2026/01/19-22:34:04.165637 7f1243fff6c0 Level-0 table #527: 0 bytes OK +2026/01/19-22:34:04.175509 7f1243fff6c0 Delete type=0 #525 +2026/01/19-22:34:04.175734 7f1243fff6c0 Manual compaction at level-0 from '!folders!yPWGvxHJbDNHVSnY' @ 72057594037927935 : 1 .. '!items!x5gLtqlW4sdDmHTd' @ 0 : 0; will stop at (end) +2026/01/19-22:34:04.175767 7f1243fff6c0 Manual compaction at level-1 from '!folders!yPWGvxHJbDNHVSnY' @ 72057594037927935 : 1 .. '!items!x5gLtqlW4sdDmHTd' @ 0 : 0; will stop at (end) diff --git a/packs-system/lf-gifts/LOG.old b/packs-system/lf-gifts/LOG.old index 561b908..dd912e9 100644 --- a/packs-system/lf-gifts/LOG.old +++ b/packs-system/lf-gifts/LOG.old @@ -1,8 +1,8 @@ -2026/01/04-22:23:04.780744 7f93eb7fe6c0 Recovering log #490 -2026/01/04-22:23:04.790510 7f93eb7fe6c0 Delete type=3 #488 -2026/01/04-22:23:04.790582 7f93eb7fe6c0 Delete type=0 #490 -2026/01/04-22:40:50.951234 7f93e9ffb6c0 Level-0 table #495: started -2026/01/04-22:40:50.951256 7f93e9ffb6c0 Level-0 table #495: 0 bytes OK -2026/01/04-22:40:50.964192 7f93e9ffb6c0 Delete type=0 #493 -2026/01/04-22:40:50.986007 7f93e9ffb6c0 Manual compaction at level-0 from '!folders!yPWGvxHJbDNHVSnY' @ 72057594037927935 : 1 .. '!items!x5gLtqlW4sdDmHTd' @ 0 : 0; will stop at (end) -2026/01/04-22:40:50.986038 7f93e9ffb6c0 Manual compaction at level-1 from '!folders!yPWGvxHJbDNHVSnY' @ 72057594037927935 : 1 .. '!items!x5gLtqlW4sdDmHTd' @ 0 : 0; will stop at (end) +2026/01/19-20:23:50.482057 7f14d8ff96c0 Recovering log #518 +2026/01/19-20:23:50.491682 7f14d8ff96c0 Delete type=3 #516 +2026/01/19-20:23:50.491756 7f14d8ff96c0 Delete type=0 #518 +2026/01/19-20:32:02.944004 7f1243fff6c0 Level-0 table #523: started +2026/01/19-20:32:02.944026 7f1243fff6c0 Level-0 table #523: 0 bytes OK +2026/01/19-20:32:02.950204 7f1243fff6c0 Delete type=0 #521 +2026/01/19-20:32:02.956469 7f1243fff6c0 Manual compaction at level-0 from '!folders!yPWGvxHJbDNHVSnY' @ 72057594037927935 : 1 .. '!items!x5gLtqlW4sdDmHTd' @ 0 : 0; will stop at (end) +2026/01/19-20:32:02.956510 7f1243fff6c0 Manual compaction at level-1 from '!folders!yPWGvxHJbDNHVSnY' @ 72057594037927935 : 1 .. '!items!x5gLtqlW4sdDmHTd' @ 0 : 0; will stop at (end) diff --git a/packs-system/lf-gifts/MANIFEST-000496 b/packs-system/lf-gifts/MANIFEST-000524 similarity index 77% rename from packs-system/lf-gifts/MANIFEST-000496 rename to packs-system/lf-gifts/MANIFEST-000524 index 28e77616c4aea622540f559b248daf5f553780a5..b1b9087609ee619907b6f4f0eec3b74d697464fc 100644 GIT binary patch delta 43 tcmey)_?>aWWtC|O>wj}IFf#SBa56CWv9RoBTpx2H1|-r45}5!J0RTib3`76` delta 43 tcmey)_?>aWWt9TEu6}L?My8LL<14Ty=PquLQqRE3OctDlhwRC(VIA4}UHlFD5_qzx0G9u8PlkrWjZ@-D3$3qVqH z{b@(FOMx1grSu2j$pX&v2rzP^aaH0m0&`eQaNS(Orzh1#cWH5Hx!XZwAV{=hDQe4J za~mXxY7Aw&c;W}F-Ca>$QC8-rOI@DIQm123Wcdhhu{Eef)8(#Gy3FNt2#h4R=qXU= zJFIvcp^cSrZ*x=Jmy`pe%|oHy7AeW*N;v4GzBH0`;t1JEyoL3R;U+hZNF|}a0Vwgk zh{{rlX%2@6!LyU|o=LoB36#_M_p2xI+8)2Jy{(lD_*#O^Pgd8p^<)pBSCs8yNGS#n zB$QtoVIchys@YjI0w#MN15Q(>;yVtl@GqtBI3zpPu*LKIt1eQCb79qgm=T1eX&WQO zn2;ppC?jWCGn~2v7v_lELk2Q3BnbLfCd&aw^x=pgTufrUKlTECdl_M5Y<7{tl#_92 z>_#OnwwRR@V>y-_iWieT*+1i8B=ek(2xCW=EbTzTno%*vt$z;^lpBaIb%XrZv9lo; z-2JYo#43^u%aVoe_2H=*#7Ul+{`~o=>7@%PMzEOwG^hvX!$m=27cH`zi^;Aim*5t6 zl~X}zD~;)(S0@N0LwyD0qXU*E;3LNH7C*d+s$M2nw|A&fgiq)vP+1%yF5^@Kmg=~k zrECVFRA#OL>$a6K#?#H?^5^RId}T0We9qR zDclYZVJ{fP2&sOArzTC85CwrKAP8Ddk}M}MNs{I5U*Nh3Gc4LEJaa*3Ia_vE*PBgo)F75X*lX z<)q@dXV!Cb51`6c2$ne`ds?Kda_j6rd)oay16@g1(Sbd!2}kv|={>C#r~w~l)NrkT zrLYM`#x1MwXs;pv4}ka45B|7^%iu^Fl`2j%uY7U?Rvc)*YRhZjhg_`@f6K66I?>|_ z_($QlSe|)pR&&V+3>}|>=Q1LO{bkK*9-7nEKj$pOsEq0wKDgx+)}P?Kx_KU l{=uIz0H$$d-28Q6{0(Ji11?~(%lJqC&T8q8l{1^L@IP_7P}Kkc delta 1091 zcmXw$e@q*77{ATH+lbf^1Sbp zPx36ES6@A^9=rf6(EaRQuxmBV%1LZQv@%TxpQNipzT>RBvAZEw4Noti3K&+w*tq>~ zv#OmIDV2P|3lAhMQ{D207dmw(Ko#uB$STbe!$Mrzw9DBw5UFmoW|bDoPzBTFwi~)JVNd zwDph%8;OZ_|26=2&-1t>+6`w&m<*m9o^%b7U>0U*PG| z%7zwmQXIc3uiKzi*0~@hKR5_$^dI1=@22&DHct{D-;hE!jnmC0g8dUOiy`*Y>hVHH zS~DID3)D4YdX_s`h{PBh294YinmAVE*+X$^IR z*ga!91Vkc$3sH7)4aBVuAe?9hxg*3Ge<9qwQ((^!5X~?s&#@?uzJcYV*T!x}N5^K~ z8>e{_^Se$dmkSp7s13ZpVzi;AFdJv5H;SSHinGVMxHubOqf)L6>oY`&PM~(0H*{)X znTF>PZT%cbyE{y^z(e(rbzZoND~Q9Ntgl;pF)prs0hfdxYF_@#2NC>G{>BG02PQQ{ zCFUq}m@!F!dodwm&cU%D2fw%8gYP&}j$VYIg&H9(J0#JUCWMf~UF<4P(!>Rxr*bID zu0@Dpc?E>wEH_C_i0&3F7Bcm$#xSeogdJ|3q>u})Jj@srcp+ZePF8xTWW3Q*p@m2CSL*kCnVVlRT=+%&7>~dR@+z`tArDYwE7z z;VB|VGxt&QsmwdoaVFp^_Bw04C7spB0}U>CPVuhG^U6D1l5efRjFg=R$v^1 diff --git a/packs-system/lf-skills/000501.log b/packs-system/lf-skills/000531.log similarity index 100% rename from packs-system/lf-skills/000501.log rename to packs-system/lf-skills/000531.log diff --git a/packs-system/lf-skills/CURRENT b/packs-system/lf-skills/CURRENT index 12cf8c5..b4b63bc 100644 --- a/packs-system/lf-skills/CURRENT +++ b/packs-system/lf-skills/CURRENT @@ -1 +1 @@ -MANIFEST-000499 +MANIFEST-000529 diff --git a/packs-system/lf-skills/LOG b/packs-system/lf-skills/LOG index 1a0c482..0baef5b 100644 --- a/packs-system/lf-skills/LOG +++ b/packs-system/lf-skills/LOG @@ -1,15 +1,8 @@ -2026/01/12-14:22:56.026652 7fd478fff6c0 Recovering log #497 -2026/01/12-14:22:56.036317 7fd478fff6c0 Delete type=3 #495 -2026/01/12-14:22:56.036373 7fd478fff6c0 Delete type=0 #497 -2026/01/12-14:27:05.805895 7fd4627fc6c0 Level-0 table #502: started -2026/01/12-14:27:05.809094 7fd4627fc6c0 Level-0 table #502: 1650 bytes OK -2026/01/12-14:27:05.815051 7fd4627fc6c0 Delete type=0 #500 -2026/01/12-14:27:05.815162 7fd4627fc6c0 Manual compaction at level-0 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end) -2026/01/12-14:27:05.828330 7fd4627fc6c0 Manual compaction at level-1 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at '!items!3cU5wW47VwlEPNqj' @ 736 : 1 -2026/01/12-14:27:05.828340 7fd4627fc6c0 Compacting 1@1 + 1@2 files -2026/01/12-14:27:05.833408 7fd4627fc6c0 Generated table #503@1: 91 keys, 118712 bytes -2026/01/12-14:27:05.833433 7fd4627fc6c0 Compacted 1@1 + 1@2 files => 118712 bytes -2026/01/12-14:27:05.839289 7fd4627fc6c0 compacted to: files[ 0 0 1 0 0 0 0 ] -2026/01/12-14:27:05.839370 7fd4627fc6c0 Delete type=2 #494 -2026/01/12-14:27:05.839480 7fd4627fc6c0 Delete type=2 #502 -2026/01/12-14:27:05.852151 7fd4627fc6c0 Manual compaction at level-1 from '!items!3cU5wW47VwlEPNqj' @ 736 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end) +2026/01/19-20:32:07.335896 7f14d97fa6c0 Recovering log #527 +2026/01/19-20:32:07.346053 7f14d97fa6c0 Delete type=3 #525 +2026/01/19-20:32:07.346120 7f14d97fa6c0 Delete type=0 #527 +2026/01/19-22:34:04.092377 7f1243fff6c0 Level-0 table #532: started +2026/01/19-22:34:04.092412 7f1243fff6c0 Level-0 table #532: 0 bytes OK +2026/01/19-22:34:04.101864 7f1243fff6c0 Delete type=0 #530 +2026/01/19-22:34:04.133480 7f1243fff6c0 Manual compaction at level-0 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end) +2026/01/19-22:34:04.133550 7f1243fff6c0 Manual compaction at level-1 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end) diff --git a/packs-system/lf-skills/LOG.old b/packs-system/lf-skills/LOG.old index b4c1186..166a01f 100644 --- a/packs-system/lf-skills/LOG.old +++ b/packs-system/lf-skills/LOG.old @@ -1,8 +1,8 @@ -2026/01/04-22:23:04.753572 7f93ea7fc6c0 Recovering log #492 -2026/01/04-22:23:04.763198 7f93ea7fc6c0 Delete type=3 #490 -2026/01/04-22:23:04.763251 7f93ea7fc6c0 Delete type=0 #492 -2026/01/04-22:40:50.941164 7f93e9ffb6c0 Level-0 table #498: started -2026/01/04-22:40:50.941212 7f93e9ffb6c0 Level-0 table #498: 0 bytes OK -2026/01/04-22:40:50.951122 7f93e9ffb6c0 Delete type=0 #496 -2026/01/04-22:40:50.985987 7f93e9ffb6c0 Manual compaction at level-0 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end) -2026/01/04-22:40:50.986053 7f93e9ffb6c0 Manual compaction at level-1 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end) +2026/01/19-20:23:50.451679 7f14d9ffb6c0 Recovering log #523 +2026/01/19-20:23:50.462356 7f14d9ffb6c0 Delete type=3 #521 +2026/01/19-20:23:50.462416 7f14d9ffb6c0 Delete type=0 #523 +2026/01/19-20:32:02.930994 7f1243fff6c0 Level-0 table #528: started +2026/01/19-20:32:02.931037 7f1243fff6c0 Level-0 table #528: 0 bytes OK +2026/01/19-20:32:02.937118 7f1243fff6c0 Delete type=0 #526 +2026/01/19-20:32:02.956440 7f1243fff6c0 Manual compaction at level-0 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end) +2026/01/19-20:32:02.956479 7f1243fff6c0 Manual compaction at level-1 from '!folders!7j8H7DbmBb9Uza2X' @ 72057594037927935 : 1 .. '!items!zt8s7564ep1La4XQ' @ 0 : 0; will stop at (end) diff --git a/packs-system/lf-skills/MANIFEST-000499 b/packs-system/lf-skills/MANIFEST-000499 deleted file mode 100644 index 755fa856dcdee80798d5a35c8df4d12f0da89077..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 381 zcmaFxfbV@f10$nUPHI_dPD+xVQ)NkNd1i5{bAE0?Vo_pAei199ykcfaYHqP&sG)B` zR!~T}X-Y*-SzeeU<2@z@5MXC|$9!ZqyP{%ReojhiQL&#)nJ{Ai&P_hxzn;c16Xs{G623qGCn!EDH~Fm!w>$B+JmMM574C zD-eY+gQ`j_ip@>UOi~LBeG*L~0vS(2Z^YGEHLPWMH1m!t#)H&kK$kkjP|^ I$W)LB0Q$BwTL1t6 literal 0 HcmV?d00001 diff --git a/packs-system/lf-spells-miracles/000198.log b/packs-system/lf-spells-miracles/000226.log similarity index 100% rename from packs-system/lf-spells-miracles/000198.log rename to packs-system/lf-spells-miracles/000226.log diff --git a/packs-system/lf-spells-miracles/CURRENT b/packs-system/lf-spells-miracles/CURRENT index 8071610..4d5019e 100644 --- a/packs-system/lf-spells-miracles/CURRENT +++ b/packs-system/lf-spells-miracles/CURRENT @@ -1 +1 @@ -MANIFEST-000196 +MANIFEST-000224 diff --git a/packs-system/lf-spells-miracles/LOG b/packs-system/lf-spells-miracles/LOG index c20388d..8f09a2a 100644 --- a/packs-system/lf-spells-miracles/LOG +++ b/packs-system/lf-spells-miracles/LOG @@ -1,8 +1,8 @@ -2026/01/12-14:22:56.089280 7fd478fff6c0 Recovering log #194 -2026/01/12-14:22:56.099242 7fd478fff6c0 Delete type=3 #192 -2026/01/12-14:22:56.099341 7fd478fff6c0 Delete type=0 #194 -2026/01/12-14:27:05.839539 7fd4627fc6c0 Level-0 table #199: started -2026/01/12-14:27:05.839559 7fd4627fc6c0 Level-0 table #199: 0 bytes OK -2026/01/12-14:27:05.845408 7fd4627fc6c0 Delete type=0 #197 -2026/01/12-14:27:05.858412 7fd4627fc6c0 Manual compaction at level-0 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end) -2026/01/12-14:27:05.864408 7fd4627fc6c0 Manual compaction at level-1 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end) +2026/01/19-20:32:07.386680 7f14d97fa6c0 Recovering log #222 +2026/01/19-20:32:07.397058 7f14d97fa6c0 Delete type=3 #220 +2026/01/19-20:32:07.397159 7f14d97fa6c0 Delete type=0 #222 +2026/01/19-22:34:04.082756 7f1243fff6c0 Level-0 table #227: started +2026/01/19-22:34:04.082796 7f1243fff6c0 Level-0 table #227: 0 bytes OK +2026/01/19-22:34:04.092156 7f1243fff6c0 Delete type=0 #225 +2026/01/19-22:34:04.092321 7f1243fff6c0 Manual compaction at level-0 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end) +2026/01/19-22:34:04.092355 7f1243fff6c0 Manual compaction at level-1 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end) diff --git a/packs-system/lf-spells-miracles/LOG.old b/packs-system/lf-spells-miracles/LOG.old index 698fa74..9759632 100644 --- a/packs-system/lf-spells-miracles/LOG.old +++ b/packs-system/lf-spells-miracles/LOG.old @@ -1,8 +1,8 @@ -2026/01/04-22:23:04.807684 7f93ebfff6c0 Recovering log #190 -2026/01/04-22:23:04.818050 7f93ebfff6c0 Delete type=3 #188 -2026/01/04-22:23:04.818118 7f93ebfff6c0 Delete type=0 #190 -2026/01/04-22:40:51.016662 7f93e9ffb6c0 Level-0 table #195: started -2026/01/04-22:40:51.016694 7f93e9ffb6c0 Level-0 table #195: 0 bytes OK -2026/01/04-22:40:51.026652 7f93e9ffb6c0 Delete type=0 #193 -2026/01/04-22:40:51.026790 7f93e9ffb6c0 Manual compaction at level-0 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end) -2026/01/04-22:40:51.026806 7f93e9ffb6c0 Manual compaction at level-1 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end) +2026/01/19-20:23:50.510934 7f14d97fa6c0 Recovering log #218 +2026/01/19-20:23:50.520501 7f14d97fa6c0 Delete type=3 #216 +2026/01/19-20:23:50.520578 7f14d97fa6c0 Delete type=0 #218 +2026/01/19-20:32:02.976313 7f1243fff6c0 Level-0 table #223: started +2026/01/19-20:32:02.976359 7f1243fff6c0 Level-0 table #223: 0 bytes OK +2026/01/19-20:32:02.982272 7f1243fff6c0 Delete type=0 #221 +2026/01/19-20:32:02.982509 7f1243fff6c0 Manual compaction at level-0 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end) +2026/01/19-20:32:02.982604 7f1243fff6c0 Manual compaction at level-1 from '!folders!37mu4dxsSuftlnmP' @ 72057594037927935 : 1 .. '!items!zKOpU34oLziGJW6y' @ 0 : 0; will stop at (end) diff --git a/packs-system/lf-spells-miracles/MANIFEST-000196 b/packs-system/lf-spells-miracles/MANIFEST-000224 similarity index 72% rename from packs-system/lf-spells-miracles/MANIFEST-000196 rename to packs-system/lf-spells-miracles/MANIFEST-000224 index 6fd8cb727e453de1116731088207fed1ce704f89..d282505166f4b0968cba6fdc189ba2b36b6dfa92 100644 GIT binary patch delta 43 tcmdnUxRG%}uZqm+yOG=sj7$$1IT@HAF|thJsE@W%28lcZi97*`000x%3ikj2 delta 43 scmdnUxRG%}uZnVb&jM}+My8{ToD9sz7+EH9*d=;3f<%siL{5N20RC(WLjV8( diff --git a/packs-system/lf-vulnerabilities/000497.log b/packs-system/lf-vulnerabilities/000525.log similarity index 100% rename from packs-system/lf-vulnerabilities/000497.log rename to packs-system/lf-vulnerabilities/000525.log diff --git a/packs-system/lf-vulnerabilities/CURRENT b/packs-system/lf-vulnerabilities/CURRENT index eff3500..26263b0 100644 --- a/packs-system/lf-vulnerabilities/CURRENT +++ b/packs-system/lf-vulnerabilities/CURRENT @@ -1 +1 @@ -MANIFEST-000495 +MANIFEST-000523 diff --git a/packs-system/lf-vulnerabilities/LOG b/packs-system/lf-vulnerabilities/LOG index 346ed89..47ec9a3 100644 --- a/packs-system/lf-vulnerabilities/LOG +++ b/packs-system/lf-vulnerabilities/LOG @@ -1,8 +1,8 @@ -2026/01/12-14:22:56.075339 7fd463fff6c0 Recovering log #493 -2026/01/12-14:22:56.084731 7fd463fff6c0 Delete type=3 #491 -2026/01/12-14:22:56.084798 7fd463fff6c0 Delete type=0 #493 -2026/01/12-14:27:05.782342 7fd4627fc6c0 Level-0 table #498: started -2026/01/12-14:27:05.782393 7fd4627fc6c0 Level-0 table #498: 0 bytes OK -2026/01/12-14:27:05.789274 7fd4627fc6c0 Delete type=0 #496 -2026/01/12-14:27:05.815132 7fd4627fc6c0 Manual compaction at level-0 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end) -2026/01/12-14:27:05.828301 7fd4627fc6c0 Manual compaction at level-1 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end) +2026/01/19-20:32:07.374730 7f14d8ff96c0 Recovering log #521 +2026/01/19-20:32:07.384387 7f14d8ff96c0 Delete type=3 #519 +2026/01/19-20:32:07.384466 7f14d8ff96c0 Delete type=0 #521 +2026/01/19-22:34:04.031975 7f1243fff6c0 Level-0 table #526: started +2026/01/19-22:34:04.032019 7f1243fff6c0 Level-0 table #526: 0 bytes OK +2026/01/19-22:34:04.042831 7f1243fff6c0 Delete type=0 #524 +2026/01/19-22:34:04.082725 7f1243fff6c0 Manual compaction at level-0 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end) +2026/01/19-22:34:04.092294 7f1243fff6c0 Manual compaction at level-1 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end) diff --git a/packs-system/lf-vulnerabilities/LOG.old b/packs-system/lf-vulnerabilities/LOG.old index 433f88a..cd785fd 100644 --- a/packs-system/lf-vulnerabilities/LOG.old +++ b/packs-system/lf-vulnerabilities/LOG.old @@ -1,8 +1,8 @@ -2026/01/04-22:23:04.794525 7f93ea7fc6c0 Recovering log #489 -2026/01/04-22:23:04.804229 7f93ea7fc6c0 Delete type=3 #487 -2026/01/04-22:23:04.804296 7f93ea7fc6c0 Delete type=0 #489 -2026/01/04-22:40:50.975950 7f93e9ffb6c0 Level-0 table #494: started -2026/01/04-22:40:50.975985 7f93e9ffb6c0 Level-0 table #494: 0 bytes OK -2026/01/04-22:40:50.985836 7f93e9ffb6c0 Delete type=0 #492 -2026/01/04-22:40:50.986029 7f93e9ffb6c0 Manual compaction at level-0 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end) -2026/01/04-22:40:50.986073 7f93e9ffb6c0 Manual compaction at level-1 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end) +2026/01/19-20:23:50.495445 7f14d9ffb6c0 Recovering log #517 +2026/01/19-20:23:50.506507 7f14d9ffb6c0 Delete type=3 #515 +2026/01/19-20:23:50.506583 7f14d9ffb6c0 Delete type=0 #517 +2026/01/19-20:32:02.950374 7f1243fff6c0 Level-0 table #522: started +2026/01/19-20:32:02.950415 7f1243fff6c0 Level-0 table #522: 0 bytes OK +2026/01/19-20:32:02.956303 7f1243fff6c0 Delete type=0 #520 +2026/01/19-20:32:02.956488 7f1243fff6c0 Manual compaction at level-0 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end) +2026/01/19-20:32:02.956546 7f1243fff6c0 Manual compaction at level-1 from '!folders!mnO9OzE7BEE2KDfh' @ 72057594037927935 : 1 .. '!items!zkK6ixtCsCw3RH9X' @ 0 : 0; will stop at (end) diff --git a/packs-system/lf-vulnerabilities/MANIFEST-000495 b/packs-system/lf-vulnerabilities/MANIFEST-000523 similarity index 72% rename from packs-system/lf-vulnerabilities/MANIFEST-000495 rename to packs-system/lf-vulnerabilities/MANIFEST-000523 index cd8f572246e54b8d1963818dedfd9888b1554a7f..50f611b58609b9301638ae65a1e25f919fb612ad 100644 GIT binary patch delta 41 rcmdnMxPfs(pHkXAwlFRRMy4JXP6p;)7M3j0fGNK~0=*!CexLvV div:first-child { font-weight: bold; - margin-bottom: 8px; - font-size: calc(var(--font-size-standard) * 0.95); + margin-bottom: 10px; + font-size: calc(var(--font-size-standard) * 1); text-align: center; + color: #d4af37; + text-transform: uppercase; + letter-spacing: 1px; } .combatants-grid { display: grid; grid-template-columns: repeat(2, 1fr); - gap: 6px; - margin-top: 5px; - padding: 8px; - background-color: rgba(0, 0, 0, 0.1); - border-radius: 5px; + gap: 8px; - .apply-wounds-btn { - padding: 6px 10px; + .apply-wounds-btn, + .request-defense-btn { + padding: 8px 12px; background: linear-gradient(to bottom, #5a5850 0%, #4b4a44 100%); border: 1px solid #2b2a24; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); - color: #f0f0e0; + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.08); + color: #f0e6d2; cursor: pointer; - border-radius: 4px; + border-radius: 6px; font-size: calc(var(--font-size-standard) * 0.85); - font-weight: 500; + font-weight: 600; text-align: center; transition: all 0.2s ease; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; + position: relative; + + &::after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 0; + height: 0; + border-radius: 50%; + background: rgba(255, 255, 255, 0.2); + transform: translate(-50%, -50%); + transition: width 0.4s, height 0.4s; + } + + &:hover::after { + width: 200%; + height: 200%; + } &:hover { background: linear-gradient(to bottom, #6a6860 0%, #5a5850 100%); - box-shadow: 0 3px 6px rgba(0, 0, 0, 0.4); - transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.12); + transform: translateY(-2px); border-color: #3b3a34; } &:active { transform: translateY(0); - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4), inset 0 1px 3px rgba(0, 0, 0, 0.3); } } } } } + + // Style pour la section attack-targets (identique à damage-result) + .attack-targets { + margin: 0 8px 10px 8px; + background: linear-gradient(135deg, rgba(80, 80, 80, 0.15) 0%, rgba(60, 60, 60, 0.2) 100%); + border: 1px solid rgba(100, 100, 100, 0.3); + border-radius: 8px; + + ul { + padding: 0; + margin: 0; + } + + .li-select-target { + list-style: none; + margin: 0; + padding: 0; + + > div:first-child { + font-weight: bold; + margin-bottom: 10px; + font-size: calc(var(--font-size-standard) * 1); + text-align: center; + color: #d4af37; + text-transform: uppercase; + letter-spacing: 1px; + } + + .combatants-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 8px; + } + } + } + + .d30-message { + margin-top: 0.5rem; + padding: 0.5rem; + background-color: rgba(139, 0, 0, 0.1); + border-left: 3px solid var(--color-level-error); + border-radius: 3px; + font-size: calc(var(--font-size-standard) * 0.95); + font-style: italic; + color: var(--color-dark-1); + } + } +} + +/* -------------------------------------------- */ +/* Damage Applied Message */ +/* -------------------------------------------- */ +.message .fvtt-lethal-fantasy.damage-applied-message, +.fvtt-lethal-fantasy.damage-applied-message { + background: linear-gradient(135deg, rgba(220, 20, 60, 0.1) 0%, rgba(139, 0, 0, 0.15) 100%); + border: 1px solid rgba(220, 20, 60, 0.3); + border-radius: 6px; + padding: 0.75rem; + margin: 0.25rem 0; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15); + + .damage-header { + display: flex; + align-items: center; + gap: 0.75rem; + margin-bottom: 0.75rem; + padding-bottom: 0.5rem; + border-bottom: 1px solid rgba(220, 20, 60, 0.2); + + .damage-icon { + font-size: 2rem; + color: #dc143c; + text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); + animation: heartbeat 2s ease-in-out infinite; + } + + .damage-info { + flex: 1; + + .target-name { + font-size: calc(var(--font-size-standard) * 1.1); + font-weight: bold; + color: #2a1a1a; + text-shadow: 0 1px 2px rgba(255, 255, 255, 0.5); + } + + .damage-subtitle { + font-size: calc(var(--font-size-standard) * 0.85); + color: #5a3a3a; + font-style: italic; + } + } + } + + .damage-details { + display: flex; + flex-direction: column; + gap: 0.5rem; + + .damage-amount { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.5rem; + background: linear-gradient(90deg, rgba(255, 255, 255, 0.4), rgba(255, 255, 255, 0.2)); + border-radius: 4px; + border-left: 3px solid #dc143c; + + .damage-label { + font-weight: 600; + color: #3a2a2a; + } + + .damage-value { + font-size: calc(var(--font-size-standard) * 1.4); + font-weight: bold; + color: #dc143c; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); + } + + .damage-raw { + font-size: calc(var(--font-size-standard) * 0.85); + color: #6a4a4a; + margin-left: 6px; + font-style: italic; + } + } + + .damage-reduction, + .damage-source { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.35rem 0.5rem; + background-color: rgba(255, 255, 255, 0.3); + border-radius: 3px; + font-size: calc(var(--font-size-standard) * 0.95); + color: #3a2a2a; + + i { + color: #8b4513; + font-size: calc(var(--font-size-standard) * 0.9); + } + + strong { + font-weight: 700; + color: #2a1a1a; + } + } + + .damage-reduction i { + color: #4a6fa5; + } + } +} + +@keyframes heartbeat { + 0%, 100% { + transform: scale(1); + } + 10%, 30% { + transform: scale(1.1); + } + 20%, 40% { + transform: scale(1); + } +} + +/* -------------------------------------------- */ +/* Progression Message */ +/* -------------------------------------------- */ +.message .fvtt-lethal-fantasy.progression-message, +.fvtt-lethal-fantasy.progression-message { + border-radius: 4px; + padding: 0.4rem 0.5rem; + margin: 0.2rem 0; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12); + + &.progression-success { + background: linear-gradient(135deg, rgba(34, 139, 34, 0.1) 0%, rgba(0, 100, 0, 0.15) 100%); + border: 1px solid rgba(34, 139, 34, 0.3); + } + + &.progression-failure { + background: linear-gradient(135deg, rgba(255, 140, 0, 0.1) 0%, rgba(184, 134, 11, 0.15) 100%); + border: 1px solid rgba(255, 140, 0, 0.3); + } + + .progression-header { + display: flex; + align-items: center; + gap: 0.5rem; + margin-bottom: 0.4rem; + padding-bottom: 0.3rem; + + .progression-success & { + border-bottom: 1px solid rgba(34, 139, 34, 0.2); + } + + .progression-failure & { + border-bottom: 1px solid rgba(255, 140, 0, 0.2); + } + + .progression-icon { + font-size: 1.5rem; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); + + .progression-success & { + color: #228b22; + animation: pulse-success 2s ease-in-out infinite; + } + + .progression-failure & { + color: #ff8c00; + animation: rotate-slow 3s linear infinite; + } + } + + .progression-info { + flex: 1; + + .actor-name { + font-size: calc(var(--font-size-standard) * 1.0); + font-weight: bold; + color: #2a1a1a; + text-shadow: 0 1px 2px rgba(255, 255, 255, 0.5); + line-height: 1.2; + } + + .progression-subtitle { + font-size: calc(var(--font-size-standard) * 0.8); + font-style: italic; + line-height: 1.2; + margin-top: 0.1rem; + + .progression-success & { + color: #2d5a2d; + } + + .progression-failure & { + color: #8b6914; + } + } + } + } + + .progression-details { + display: flex; + flex-direction: column; + gap: 0.3rem; + + .progression-weapon, + .progression-roll, + .progression-count { + display: flex; + align-items: center; + gap: 0.4rem; + padding: 0.25rem 0.4rem; + background-color: rgba(255, 255, 255, 0.3); + border-radius: 3px; + font-size: calc(var(--font-size-standard) * 0.9); + color: #3a2a2a; + line-height: 1.2; + + i { + font-size: calc(var(--font-size-standard) * 0.85); + } + + strong { + font-weight: 700; + color: #2a1a1a; + } + } + + .progression-weapon { + border-left: 3px solid #8b4513; + + i { + color: #8b4513; + } + } + + .progression-roll { + .progression-success & { + border-left: 3px solid #228b22; + } + + .progression-failure & { + border-left: 3px solid #ff8c00; + } + + .roll-label { + font-weight: 600; + } + + .roll-value { + font-size: calc(var(--font-size-standard) * 1.1); + font-weight: bold; + + .progression-success & { + color: #228b22; + } + + .progression-failure & { + color: #ff8c00; + } + } + + i { + .progression-success & { + color: #228b22; + } + + .progression-failure & { + color: #ff8c00; + } + } + } + + .progression-count { + border-left: 3px solid #4a6fa5; + + i { + color: #4a6fa5; + } + } + } +} + +@keyframes pulse-success { + 0%, 100% { + transform: scale(1); + opacity: 1; + } + 50% { + transform: scale(1.15); + opacity: 0.8; + } +} + +@keyframes rotate-slow { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +/* -------------------------------------------- */ +/* Attack Result Message */ +/* -------------------------------------------- */ +.message .attack-result, +.attack-result { + padding: 8px; + border-radius: 8px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.4); + margin: 8px 0; + + &.attack-success { + background: linear-gradient(to bottom, rgba(139, 0, 0, 0.2) 0%, rgba(100, 0, 0, 0.3) 100%); + border: 2px solid rgba(220, 20, 60, 0.6); + } + + &.attack-failure { + background: linear-gradient(to bottom, rgba(0, 100, 139, 0.2) 0%, rgba(0, 70, 100, 0.3) 100%); + border: 2px solid rgba(70, 130, 180, 0.6); + } + + h3 { + margin: 0 0 8px 0; + color: #000; + font-size: calc(var(--font-size-standard) * 1.2); + font-weight: 700; + text-align: center; + text-transform: uppercase; + letter-spacing: 1px; + + i { + margin-right: 8px; + } + } + + .combat-comparison { + display: flex; + flex-direction: column; + align-items: stretch; + gap: 6px; + margin-bottom: 8px; + padding: 8px; + background: rgba(0, 0, 0, 0.3); + border-radius: 6px; + + .combat-side { + flex: 1; + padding: 8px; + border-radius: 6px; + display: flex; + flex-direction: column; + gap: 4px; + + &.winner { + background: rgba(0, 255, 0, 0.1); + border: 2px solid rgba(0, 255, 0, 0.4); + } + + &.loser { + background: rgba(255, 0, 0, 0.1); + border: 2px solid rgba(255, 0, 0, 0.3); + } + + .side-label { + font-size: calc(var(--font-size-standard) * 0.8); + color: #ddd; + text-transform: uppercase; + letter-spacing: 1px; + font-weight: 600; + text-align: center; + } + + .side-info { + display: flex; + justify-content: space-between; + align-items: center; + gap: 8px; + } + + .side-name { + font-size: calc(var(--font-size-standard) * 1); + color: #fff; + font-weight: 700; + } + + .side-roll { + font-size: calc(var(--font-size-standard) * 1.5); + color: #f4d03f; + font-weight: 700; + } + } + + .combat-vs { + font-size: calc(var(--font-size-standard) * 1.2); + color: #f4d03f; + font-weight: 700; + padding: 4px 0; + text-align: center; + } + } + + .combat-result-text { + text-align: center; + font-size: calc(var(--font-size-standard) * 1.1); + color: #fff; + margin-bottom: 8px; + padding: 8px; + background: rgba(0, 0, 0, 0.2); + border-radius: 6px; + font-weight: 600; + + i { + margin-right: 8px; + } + + strong { + color: #f4d03f; + font-weight: 700; + } + } + + .attack-result-damage { + display: flex; + gap: 8px; + justify-content: center; + + .roll-damage-btn { + padding: 10px 16px; + background: linear-gradient(to bottom, #8b0000 0%, #660000 100%); + border: 1px solid #ff0000; + border-radius: 6px; + color: #f0e6d2; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); + + &:hover { + background: linear-gradient(to bottom, #a00000 0%, #7b0000 100%); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.4); + transform: translateY(-2px); + } + + &:active { + transform: translateY(0); + } + + i { + margin-right: 6px; + } + } + } } diff --git a/templates/chat-message.hbs b/templates/chat-message.hbs index a02e038..73006c3 100644 --- a/templates/chat-message.hbs +++ b/templates/chat-message.hbs @@ -1,193 +1,291 @@ {{!log 'chat-message' this}} {{!log 'weaponDamageOptions' weaponDamageOptions}}
-
-
- -
- -
- {{actingCharName}} - {{upperFirst rollName}} - - {{#if (match rollType "attack")}} - Attack roll ! - {{/if}} - {{#if (match rollType "defense")}} - Defense roll ! - {{/if}} - - {{#if (eq rollData.favor "favor")}} - Favor roll - {{/if}} - {{#if (eq rollData.favor "disfavor")}} - Disfavor roll - {{/if}} - {{#if badResult}} - {{localize "LETHALFANTASY.Label.otherResult"}} - : - {{badResult}} - {{/if}} - - {{#if rollTarget.weapon}} - {{rollTarget.weapon.name}} - {{/if}} - - {{#if rollData.isDamage}} - Damage Roll - {{/if}} - {{#if rollData.damageSmall}} - {{localize - "LETHALFANTASY.Label.weapon-damage-small" - }} - {{/if}} - {{#if rollData.damageMedium}} - {{localize - "LETHALFANTASY.Label.weapon-damage-medium" - }} - {{/if}} - - {{#if rollData.letItFly}} - Let It Fly attack ! - {{/if}} - {{#if rollData.pointBlank}} - Point Blank Range Attack ! - {{/if}} - {{#if rollData.beyondSkill}} - Beyond Skill Range Attack ! - {{/if}} - - Formula : {{titleFormula}} - - {{#each diceResults as |result|}} - {{result.dice}} : {{result.value}} - {{/each}} - + +
+
+
+ +
+
+
{{actingCharName}}
+
+ {{#if (match rollType "attack")}} + + {{/if}} + {{#if (match rollType "defense")}} + + {{/if}} + {{#if rollData.isDamage}} + + {{/if}} + {{#if (eq rollType "skill")}} + + {{/if}} + {{upperFirst rollName}} +
+
- {{#if isSave}} -
- {{#if (eq resultType "success")}} - {{#if isPrivate}}?{{else}}{{localize - "LETHALFANTASY.Roll.success" - }}{{/if}} - {{else}} - {{#if isPrivate}}?{{else}}{{localize - "LETHALFANTASY.Roll.failure" - }}{{/if}} - {{/if}} -
- {{/if}} - {{#if isResource}} -
- {{#if (eq resultType "success")}} - {{#if isPrivate}}?{{else}}{{localize - "LETHALFANTASY.Roll.success" - }}{{/if}} - {{else}} - {{#if isPrivate}}?{{else}}{{localize "LETHALFANTASY.Roll.failure"}}{{#if - isFailure - }} ({{localize "LETHALFANTASY.Roll.resourceLost"}}){{/if}}{{/if}} - {{/if}} -
- {{/if}} - {{#if isDamage}} -
- {{#if (and isGM hasTarget)}} - {{{localize - "LETHALFANTASY.Roll.displayArmor" - targetName=targetName - targetArmor=targetArmor - realDamage=realDamage - }}} - {{/if}} -
- {{/if}} - {{#unless isPrivate}} -
-

{{total}}

-
- {{#if D30result}} -
-

D30 result: {{D30result}}

+ +
+ {{#if rollTarget.weapon}} +
+ + {{rollTarget.weapon.name}}
{{/if}} - {{/unless}} + {{#if (eq rollData.favor "favor")}} +
+ + Favor +
+ {{/if}} + {{#if (eq rollData.favor "disfavor")}} +
+ + Disfavor +
+ {{/if}} - {{#if weaponDamageOptions}} -
-
{{localize - "LETHALFANTASY.Label.rollDamage" - }}
-
- {{#if weaponDamageOptions.isMonster}} - - {{else}} - {{#if weaponDamageOptions.damageS}} - + {{#if rollData.letItFly}} +
+ + Let It Fly! +
+ {{/if}} + {{#if rollData.pointBlank}} +
+ + Point Blank +
+ {{/if}} + {{#if rollData.beyondSkill}} +
+ + Beyond Skill +
+ {{/if}} + + {{#if rollData.damageSmall}} +
+ + {{localize "LETHALFANTASY.Label.weapon-damage-small"}} +
+ {{/if}} + {{#if rollData.damageMedium}} +
+ + {{localize "LETHALFANTASY.Label.weapon-damage-medium"}} +
+ {{/if}} + + {{#if badResult}} +
+ {{localize "LETHALFANTASY.Label.otherResult"}}: {{badResult}} +
+ {{/if}} +
+ + +
+
+ + {{titleFormula}} +
+ {{#if diceResults}} +
+ {{#each diceResults as |result|}} +
+ {{result.dice}} + + {{result.value}} +
+ {{/each}} +
+ {{/if}} +
+ + {{#unless isPrivate}} +
+
+
Total
+
+ {{total}} +
+
+ + {{#if D30result}} +
+
+ + D30 Special + {{D30result}} +
+ {{#if D30message}} +
+ + {{D30message}} +
{{/if}} - {{#if weaponDamageOptions.damageM}} - +
+ {{/if}} +
+ + {{#if isSave}} +
+ {{#if (eq resultType "success")}} + + {{localize "LETHALFANTASY.Roll.success"}} + {{else}} + + {{localize "LETHALFANTASY.Roll.failure"}} + {{/if}} +
+ {{/if}} + + {{#if isResource}} +
+ {{#if (eq resultType "success")}} + + {{localize "LETHALFANTASY.Roll.success"}} + {{else}} + + {{localize "LETHALFANTASY.Roll.failure"}} + {{#if isFailure}} + ({{localize "LETHALFANTASY.Roll.resourceLost"}}) {{/if}} {{/if}}
+ {{/if}} + + {{#if isDamage}} + {{#if (and isGM hasTarget)}} +
+ + {{{localize + "LETHALFANTASY.Roll.displayArmor" + targetName=targetName + targetArmor=targetArmor + realDamage=realDamage + }}} +
+ {{/if}} + {{/if}} + {{else}} +
+ + Private Roll
+ {{/unless}} + + {{#if weaponDamageOptions}} + {{#if canSelectTarget}} +
+
+ {{#if weaponDamageOptions.isMonster}} + + {{else}} + {{#if weaponDamageOptions.damageS}} + + {{/if}} + {{#if weaponDamageOptions.damageM}} + + {{/if}} + {{/if}} +
+
+ {{/if}} {{/if}} {{#if rollData.isDamage}} -
-
    -
  • -
    {{localize "LETHALFANTASY.Label.applyDamage"}}
    -
    - {{#each combatants}} - - {{/each}} -
    -
  • -
-
+ {{#if defenderId}} +
+
+ + Damage automatically applied to defender (with Armor DR) +
+
+ {{else}} +
+
    +
  • +
    {{localize "LETHALFANTASY.Label.applyDamage"}}
    +
    + {{#each combatants}} + + {{/each}} +
    +
  • +
+
+ {{/if}} + {{/if}} + + {{#if isAttack}} + {{#if canSelectTarget}} +
+
    +
  • +
    {{localize "LETHALFANTASY.Label.selectTarget"}}
    +
    + {{#each combatants}} + + {{/each}} +
    +
  • +
+
+ {{/if}} {{/if}}
\ No newline at end of file diff --git a/templates/damage-applied-message.hbs b/templates/damage-applied-message.hbs new file mode 100644 index 0000000..7532006 --- /dev/null +++ b/templates/damage-applied-message.hbs @@ -0,0 +1,36 @@ +
+
+
+ +
+
+
{{targetName}}
+
{{localize "LETHALFANTASY.DamageApplied.subtitle"}}
+
+
+ +
+
+ {{localize "LETHALFANTASY.DamageApplied.damageDealt"}} + {{damage}} + {{#if rawDamage}} + ({{rawDamage}} raw) + {{/if}} +
+ + {{#if drText}} +
+ + {{drText}} +
+ {{/if}} + +
+ + {{localize "LETHALFANTASY.DamageApplied.from"}} + {{#if attackerName}}{{attackerName}} with {{/if}} + {{weaponName}} + +
+
+
diff --git a/templates/monster-combat.hbs b/templates/monster-combat.hbs index 784e035..11f2c16 100644 --- a/templates/monster-combat.hbs +++ b/templates/monster-combat.hbs @@ -22,7 +22,7 @@ {{#each system.attacks as |item key|}}
- +
@@ -65,4 +65,54 @@ {{/each}}
+ +
+ Hand To Hand Attacks +
+ {{#each system.combatHTH as |item key|}} +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+ + + + + + + + + + + + + +
+ +
+ {{/each}} +
+
\ No newline at end of file diff --git a/templates/monster-main.hbs b/templates/monster-main.hbs index dc0561c..09a1e76 100644 --- a/templates/monster-main.hbs +++ b/templates/monster-main.hbs @@ -291,13 +291,6 @@ disabled=isPlayMode data-char-id="int" }} - - {{formField - systemFields.characteristics.fields.int.fields.percent - value=system.characteristics.int.percent - disabled=isPlayMode - type="number" - }}
{{localize "LETHALFANTASY.Label.dex"}} @@ -307,13 +300,6 @@ disabled=isPlayMode data-char-id="wis" }} - - {{formField - systemFields.characteristics.fields.dex.fields.percent - value=system.characteristics.dex.percent - disabled=isPlayMode - type="number" - }}
{{localize "LETHALFANTASY.Label.Movement"}} diff --git a/templates/progression-message.hbs b/templates/progression-message.hbs new file mode 100644 index 0000000..194c3e3 --- /dev/null +++ b/templates/progression-message.hbs @@ -0,0 +1,44 @@ +
+
+
+ {{#if success}} + + {{else}} + + {{/if}} +
+
+
{{actorName}}
+
+ {{#if success}} + {{localize "LETHALFANTASY.ProgressionMessage.canAct"}} + {{else}} + {{localize "LETHALFANTASY.ProgressionMessage.cannotAct"}} + {{/if}} +
+
+
+ +
+ {{#if weaponName}} +
+ + {{weaponName}} +
+ {{/if}} + + {{#if rollResult}} +
+ {{localize "LETHALFANTASY.ProgressionMessage.diceResult"}} + {{rollResult}} +
+ {{/if}} + + {{#if progressionCount}} +
+ + {{localize "LETHALFANTASY.ProgressionMessage.progressionCount"}} {{progressionCount}} +
+ {{/if}} +
+