From 19b472de00352d1d09db49cc44892b2c9835056b Mon Sep 17 00:00:00 2001 From: "Tim Evans (slaguru666)" Date: Tue, 14 Apr 2026 22:38:18 +0100 Subject: [PATCH] feat: player permissions enforcement, CSS overhaul, actor/chat template updates - Enforce sidebar directory restrictions for non-GM players across actors, scenes, journal, tables, compendium, and combat - Major CSS refactor in mosh.css (layout, sizing, colour passes) - Actor sheet JS: extended item handling and sheet render logic - Actor data model updates in actor.js - Item sheet additions in item-sheet.js - Updated chat templates: itemRoll, modifyActor, modifyItem, reload, rollCheck, rollTable for improved formatting and clarity - Actor/creature sheet HTML restructured - Compendium pack DB files rotated (LevelDB log/manifest housekeeping) Co-authored-by: slaguru666 --- css/mosh.css | 1832 +++++++---------- module/actor/actor-sheet.js | 136 +- module/actor/actor.js | 306 +-- module/item/item-sheet.js | 44 + module/mosh.js | 254 ++- .../conditions_0e/{000062.log => 000076.log} | 0 packs/conditions_0e/CURRENT | 2 +- packs/conditions_0e/LOG | 6 +- packs/conditions_0e/LOG.old | 10 +- .../{MANIFEST-000061 => MANIFEST-000075} | Bin 153 -> 153 bytes .../conditions_1e/{000062.log => 000076.log} | 0 packs/conditions_1e/CURRENT | 2 +- packs/conditions_1e/LOG | 6 +- packs/conditions_1e/LOG.old | 10 +- .../{MANIFEST-000061 => MANIFEST-000075} | Bin 153 -> 153 bytes .../{000062.log => 000076.log} | 0 packs/items_maintenance_1e/CURRENT | 2 +- packs/items_maintenance_1e/LOG | 6 +- packs/items_maintenance_1e/LOG.old | 10 +- .../{MANIFEST-000061 => MANIFEST-000075} | Bin 154 -> 154 bytes .../{000062.log => 000076.log} | 0 packs/macros_hotbar_0e/CURRENT | 2 +- packs/macros_hotbar_0e/LOG | 6 +- packs/macros_hotbar_0e/LOG.old | 10 +- .../{MANIFEST-000061 => MANIFEST-000075} | Bin 156 -> 156 bytes .../{000062.log => 000076.log} | 0 packs/macros_hotbar_1e/CURRENT | 2 +- packs/macros_hotbar_1e/LOG | 6 +- packs/macros_hotbar_1e/LOG.old | 10 +- .../{MANIFEST-000061 => MANIFEST-000075} | Bin 156 -> 156 bytes .../{000062.log => 000076.log} | 0 packs/macros_triggered_0e/CURRENT | 2 +- packs/macros_triggered_0e/LOG | 6 +- packs/macros_triggered_0e/LOG.old | 10 +- .../{MANIFEST-000061 => MANIFEST-000075} | Bin 157 -> 157 bytes .../{000064.log => 000078.log} | 0 packs/macros_triggered_1e/CURRENT | 2 +- packs/macros_triggered_1e/LOG | 6 +- packs/macros_triggered_1e/LOG.old | 10 +- .../{MANIFEST-000063 => MANIFEST-000077} | Bin 158 -> 158 bytes .../rolltables_0e/{000062.log => 000076.log} | 0 packs/rolltables_0e/CURRENT | 2 +- packs/rolltables_0e/LOG | 6 +- packs/rolltables_0e/LOG.old | 10 +- .../{MANIFEST-000061 => MANIFEST-000075} | Bin 208 -> 208 bytes .../rolltables_1e/{000062.log => 000076.log} | 0 packs/rolltables_1e/CURRENT | 2 +- packs/rolltables_1e/LOG | 6 +- packs/rolltables_1e/LOG.old | 10 +- .../{MANIFEST-000061 => MANIFEST-000075} | Bin 208 -> 208 bytes templates/actor/actor-sheet.html | 356 +--- templates/actor/creature-sheet.html | 2 +- templates/chat/itemRoll.html | 109 +- templates/chat/modifyActor.html | 34 +- templates/chat/modifyItem.html | 30 +- templates/chat/reload.html | 31 +- templates/chat/rollCheck.html | 144 +- templates/chat/rollTable.html | 95 +- 58 files changed, 1738 insertions(+), 1797 deletions(-) rename packs/conditions_0e/{000062.log => 000076.log} (100%) rename packs/conditions_0e/{MANIFEST-000061 => MANIFEST-000075} (83%) rename packs/conditions_1e/{000062.log => 000076.log} (100%) rename packs/conditions_1e/{MANIFEST-000061 => MANIFEST-000075} (83%) rename packs/items_maintenance_1e/{000062.log => 000076.log} (100%) rename packs/items_maintenance_1e/{MANIFEST-000061 => MANIFEST-000075} (83%) rename packs/macros_hotbar_0e/{000062.log => 000076.log} (100%) rename packs/macros_hotbar_0e/{MANIFEST-000061 => MANIFEST-000075} (65%) rename packs/macros_hotbar_1e/{000062.log => 000076.log} (100%) rename packs/macros_hotbar_1e/{MANIFEST-000061 => MANIFEST-000075} (82%) rename packs/macros_triggered_0e/{000062.log => 000076.log} (100%) rename packs/macros_triggered_0e/{MANIFEST-000061 => MANIFEST-000075} (81%) rename packs/macros_triggered_1e/{000064.log => 000078.log} (100%) rename packs/macros_triggered_1e/{MANIFEST-000063 => MANIFEST-000077} (81%) rename packs/rolltables_0e/{000062.log => 000076.log} (100%) rename packs/rolltables_0e/{MANIFEST-000061 => MANIFEST-000075} (92%) rename packs/rolltables_1e/{000062.log => 000076.log} (100%) rename packs/rolltables_1e/{MANIFEST-000061 => MANIFEST-000075} (92%) diff --git a/css/mosh.css b/css/mosh.css index bebf858..175bf29 100644 --- a/css/mosh.css +++ b/css/mosh.css @@ -63,6 +63,11 @@ background: none; } +body.sla-pause-active #pause { + opacity: 0 !important; + pointer-events: none !important; +} + #pause figcaption { font-family: "Roboto", sans-serif; color: #ffffff; @@ -70,6 +75,72 @@ padding-left: 6px; } +body > .sla-pause-root { + position: fixed; + inset: 0; + z-index: 9999; + display: flex; + align-items: center; + justify-content: center; + pointer-events: none; + background: + radial-gradient(circle at center, rgba(109, 0, 0, 0.08), transparent 28%), + rgba(0, 0, 0, 0.16); +} + +body > .sla-pause-root .sla-pause-stage { + width: min(34vw, 420px); + height: min(42vw, 500px); + display: flex; + align-items: center; + justify-content: center; +} + +body > .sla-pause-root .sla-pause-svg { + width: 100%; + height: 100%; + overflow: visible; + filter: drop-shadow(0 0 18px rgba(170, 24, 24, 0.26)); +} + +body > .sla-pause-root .sla-pause-standby { + animation: slaPausePulse 1.75s ease-in-out infinite; + text-shadow: 0 0 10px rgba(255, 100, 100, 0.45); +} + +body > .sla-pause-root .sla-pause-scan { + animation: slaPauseScan 2.8s ease-in-out infinite; +} + +body > .sla-pause-root .sla-pause-logo-core { + animation: slaPauseLogoPulse 2.6s ease-in-out infinite; +} + +body > .sla-pause-root .sla-pause-core-glow { + animation: slaPauseCoreGlow 2.8s ease-in-out infinite; +} + +@keyframes slaPausePulse { + 0%, 100% { opacity: 0.42; } + 50% { opacity: 1; } +} + +@keyframes slaPauseScan { + 0% { opacity: 0.08; } + 50% { opacity: 0.34; } + 100% { opacity: 0.08; } +} + +@keyframes slaPauseLogoPulse { + 0%, 100% { opacity: 0.86; filter: brightness(0.95); } + 50% { opacity: 1; filter: brightness(1.18); } +} + +@keyframes slaPauseCoreGlow { + 0%, 100% { opacity: 0.62; } + 50% { opacity: 0.92; } +} + /* Paragraph */ p { margin: 0em 0; @@ -474,7 +545,7 @@ p { font-family: "Roboto", sans-serif; font-size: 11pt; font-weight: 500; - color: #444; + color: grey; } .mosh .resource-label { @@ -1574,7 +1645,7 @@ p { .mosh .headergrid { display: grid; - grid-template-columns: 1.2fr 1.2fr 1.1fr 0.95fr 1fr 1fr 1fr; + grid-template-columns: 1.15fr 1.15fr 1fr 0.9fr 0.95fr 0.95fr 0.95fr 0.9fr; grid-template-rows: auto auto auto; gap: 0.35rem 0.5rem; padding: 1.45em 1em 1em; @@ -1646,7 +1717,7 @@ p { } .mosh .header-field-lad { - grid-column: 1 / span 3; + grid-column: 1 / span 2; grid-row: 3; align-self: start; } @@ -1657,7 +1728,7 @@ p { } .mosh .header-field-race { - grid-column: 6 / span 2; + grid-column: 6 / span 3; grid-row: 2; } @@ -1676,12 +1747,21 @@ p { grid-row: 3; } +.mosh .header-field-rest { + grid-column: 3; + grid-row: 3; + align-self: start; + justify-self: start; + width: 100%; + max-width: 78px; +} + .mosh .sla-header-checkbox { display: inline-flex; align-items: center; gap: 0.45rem; min-height: 2rem; - padding: 0.25rem 0.9rem; + padding: 0.2rem 0.7rem; border: 2px solid #111; border-radius: 999px; background: #fff; @@ -1703,6 +1783,21 @@ p { .mosh .sla-header-checkbox span { line-height: 1; white-space: nowrap; + font-size: 0.86rem; + letter-spacing: 0.05em; +} + +.mosh .sla-header-rest-pill { + min-width: 0; + width: 100%; + justify-content: center; + align-self: start; + background: linear-gradient(180deg, #4aa8ff 0%, #1b64d8 100%) !important; + color: #f4fbff !important; + border: 1px solid rgba(134, 209, 255, 0.9) !important; + box-shadow: 0 0 0 1px rgba(9, 29, 56, 0.45), 0 0 14px rgba(58, 152, 255, 0.28); + padding: 0.28rem 0.55rem !important; + font-size: 0.86rem !important; } .mosh .header-field-compact .headerinputfield { @@ -1746,7 +1841,8 @@ p { height: auto; padding: 6px; border-radius: 10px; - border: 0; + border: 2px solid rgba(102, 209, 255, 0.95); + box-shadow: 0 0 0 1px rgba(8, 18, 30, 0.9), 0 0 14px rgba(54, 176, 255, 0.35); } .mosh .rollcontainer { @@ -1843,15 +1939,7 @@ p { margin-top: -8px; margin-bottom: 3px; padding: 5px; - color: #444; -} - -/* Skill roll dialog — scrollable list so Roll button is always visible */ -.macro-popup-dialog .skill-dialog-scroll { - max-height: min(420px, 55vh); - overflow-y: auto; - margin-bottom: 4px; - padding-right: 2px; + color: grey; } .mosh .rollattribute { @@ -2325,130 +2413,11 @@ p { font-weight: 700; } -.mosh .sla-flux-stage.stable { background: rgba(81, 163, 109, 0.25); color: #1a5c35; } -.mosh .sla-flux-stage.charged { background: rgba(203, 161, 68, 0.25); color: #5a3f00; } -.mosh .sla-flux-stage.frayed { background: rgba(194, 112, 55, 0.28); color: #6b2e00; } -.mosh .sla-flux-stage.breach { background: rgba(161, 59, 59, 0.32); color: #6b1414; } -.mosh .sla-flux-stage.catastrophic { background: rgba(99, 31, 31, 0.5); color: #3d0000; } - -/* ── Vevaphon Instability ─────────────────────────────────────── */ -.mosh .sla-instability-bar-wrap { - position: relative; - margin-top: 10px; - height: 22px; - border-radius: 999px; - overflow: hidden; - background: rgba(17, 17, 17, 0.1); - border: 1px solid rgba(255, 255, 255, 0.12); -} - -.mosh .sla-instability-bar { - height: 100%; - border-radius: inherit; - background: linear-gradient(90deg, rgba(81, 163, 109, 0.9), rgba(203, 161, 68, 0.92)); - transition: width 0.3s ease; -} - -.mosh .sla-instability-bar-label { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - display: flex; - align-items: center; - justify-content: center; - font-family: "Josefin Sans", "Roboto", sans-serif; - font-size: 0.72rem; - font-weight: 700; - letter-spacing: 0.06em; - pointer-events: none; - text-shadow: 0 1px 2px rgba(0,0,0,0.55); - color: #fff; -} - -.mosh .sla-instability-bar-label.instability-critical { - color: #ffe0e0; - text-shadow: 0 1px 3px rgba(80,0,0,0.8); -} - -.mosh .sla-instability-bar.instability-elevated { - background: linear-gradient(90deg, rgba(203, 161, 68, 0.92), rgba(194, 112, 55, 0.96)); -} - -.mosh .sla-instability-bar.instability-critical { - background: linear-gradient(90deg, rgba(194, 112, 55, 0.96), rgba(161, 59, 59, 0.98)); -} - -.mosh .sla-instability-tag.instability-nominal { - background: rgba(81, 163, 109, 0.18); - color: #1a5c35; - border-color: rgba(81, 163, 109, 0.35); -} - -.mosh .sla-instability-tag.instability-elevated { - background: rgba(203, 161, 68, 0.22); - color: #5a3f00; - border-color: rgba(203, 161, 68, 0.45); -} - -.mosh .sla-instability-tag.instability-critical { - background: rgba(161, 59, 59, 0.28); - color: #6b1414; - border-color: rgba(161, 59, 59, 0.55); - font-weight: 700; -} - -.mosh .sla-morph-form-buttons { - display: flex; - flex-direction: column; - gap: 8px; - margin-top: 10px; -} - -.mosh .sla-morph-shift { - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 2px; - padding: 8px 12px; - border: 1px solid rgba(255, 255, 255, 0.12); - background: linear-gradient(180deg, #3a3f50, #22262e); - color: #d8dde6; - font-family: "Josefin Sans", "Roboto", sans-serif; - border-radius: 4px; - cursor: pointer; - text-align: left; - transition: background 0.15s ease; - width: 100%; -} - -.mosh .sla-morph-shift:hover:not([disabled]) { - background: linear-gradient(180deg, #4f5568, #303642); -} - -.mosh .sla-morph-shift[disabled] { - background: linear-gradient(180deg, rgba(100, 160, 80, 0.3), rgba(60, 110, 50, 0.3)); - border-color: rgba(100, 160, 80, 0.5); - color: #1a5c35; - cursor: default; -} - -.mosh .sla-morph-btn-name { - font-size: 0.85rem; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.06em; -} - -.mosh .sla-morph-btn-meta { - font-size: 0.7rem; - opacity: 0.75; - text-transform: none; - letter-spacing: 0; - font-family: inherit; -} -/* ── End Vevaphon ─────────────────────────────────────────────── */ +.mosh .sla-flux-stage.stable { background: rgba(81, 163, 109, 0.25); color: #dff7e7; } +.mosh .sla-flux-stage.charged { background: rgba(203, 161, 68, 0.25); color: #fff2c8; } +.mosh .sla-flux-stage.frayed { background: rgba(194, 112, 55, 0.28); color: #ffe2cc; } +.mosh .sla-flux-stage.breach { background: rgba(161, 59, 59, 0.32); color: #ffd7d7; } +.mosh .sla-flux-stage.catastrophic { background: rgba(99, 31, 31, 0.5); color: #fff0f0; } .mosh .sla-action-row button, .mosh .sla-generator button, @@ -2781,6 +2750,17 @@ p { border-color: rgba(42, 20, 15, 0.28) !important; } +.window-app.sla-window-theme-legacy .mosh .textarea-input, +.window-app.sla-window-theme-legacy .mosh .trauma-response-box { + background: #fffaf1 !important; + color: #17120f !important; + border: 1px solid rgba(42, 20, 15, 0.22) !important; +} + +.window-app.sla-window-theme-legacy .mosh .trauma-response-box::placeholder { + color: rgba(31, 23, 18, 0.48) !important; +} + .window-app.sla-window-theme-legacy .mosh .mainstatlabel, .window-app.sla-window-theme-legacy .mosh .stresslabel, .window-app.sla-window-theme-legacy .mosh .items-list .item-header { @@ -3042,12 +3022,61 @@ p { } .mosh .trauma-response-box { + display: block; + width: 100%; min-height: 92px; - max-height: 132px; + max-height: 150px; + padding: 10px 12px; + box-sizing: border-box; font-size: 0.92rem; line-height: 1.25; overflow-y: auto; resize: vertical; + border-radius: 12px; +} + +.mosh .sla-trauma-panel { + display: grid; + grid-template-rows: auto auto minmax(112px, auto); + row-gap: 8px; + min-width: 0; +} + +.mosh .sla-trauma-controls { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + flex-wrap: nowrap; +} + +.mosh .sla-trauma-toggle { + display: inline-flex; + align-items: center; + gap: 0.45rem; + padding: 0.2rem 0.55rem; + border-radius: 999px; + border: 1px solid rgba(255, 255, 255, 0.14); + background: rgba(14, 20, 28, 0.72); + color: #e7eef6; + font-size: 0.67rem; + font-weight: 700; + letter-spacing: 0.05em; + text-transform: uppercase; + white-space: nowrap; +} + +.mosh .sla-trauma-toggle input[type="checkbox"] { + margin: 0; + accent-color: #3f92ff; +} + +.mosh .sla-trauma-controls .sla-inline-mini-button { + min-width: 0; + padding: 0.2rem 0.7rem; + font-size: 0.67rem; + line-height: 1.1; + white-space: nowrap; } .mosh .sla-stat-heading { @@ -3286,6 +3315,17 @@ p { border-color: rgba(17, 22, 27, 0.24) !important; } +.window-app.sla-window-theme-modern .mosh .textarea-input, +.window-app.sla-window-theme-modern .mosh .trauma-response-box { + background: #11161d !important; + color: #f4f7fa !important; + border: 1px solid rgba(212, 190, 148, 0.24) !important; +} + +.window-app.sla-window-theme-modern .mosh .trauma-response-box::placeholder { + color: rgba(244, 247, 250, 0.52) !important; +} + .mosh.sla-sheet-theme-modern .textarea-input { background: #11161d; color: #f4f7fa; @@ -3444,7 +3484,7 @@ p { line-height: 1; cursor: pointer; position: relative; - top: -4px; + top: -1px; } .mosh .sla-health-label-row { @@ -3453,7 +3493,7 @@ p { align-items: center; justify-content: center; gap: 8px; - min-height: 18px; + min-height: 24px; } .mosh .sla-health-label-row .resource-label { @@ -4048,6 +4088,494 @@ p { flex-direction: column; } } + +/* SLA chat outcome redesign */ +.chat-message .mosh.sla-chat-shell, +.mosh.sla-chat-shell { + --sla-chat-bg: linear-gradient(180deg, #151318 0%, #0e0d11 100%); + --sla-chat-surface: rgba(32, 25, 32, 0.92); + --sla-chat-panel: rgba(17, 21, 28, 0.94); + --sla-chat-panel-soft: rgba(27, 33, 43, 0.92); + --sla-chat-ink: #f4efe7; + --sla-chat-muted: #d2c5b8; + --sla-chat-accent: #cf3148; + --sla-chat-accent-soft: rgba(207, 49, 72, 0.18); + --sla-chat-line: rgba(207, 49, 72, 0.48); + --sla-chat-glow: rgba(207, 49, 72, 0.22); +} + +.chat-message .mosh.sla-chat-shell .rollcontainer, +.mosh.sla-chat-shell .rollcontainer { + background: var(--sla-chat-bg); + border: 1px solid var(--sla-chat-line); + border-radius: 18px; + padding: 10px 10px 12px; + color: var(--sla-chat-ink); + box-shadow: 0 10px 24px rgba(0, 0, 0, 0.22), inset 0 0 0 1px rgba(255, 255, 255, 0.03); + overflow: hidden; +} + +.chat-message .mosh.sla-chat-shell .rollcontainer .body, +.chat-message .mosh.sla-chat-shell .description, +.chat-message .mosh.sla-chat-shell .description .body, +.mosh.sla-chat-shell .rollcontainer .body, +.mosh.sla-chat-shell .description, +.mosh.sla-chat-shell .description .body { + color: var(--sla-chat-ink); +} + +.mosh.sla-chat-shell .sla-chat-hero { + display: grid; + grid-template-columns: minmax(0, 1fr) 62px; + gap: 10px; + align-items: stretch; + margin-bottom: 10px; +} + +.mosh.sla-chat-shell .sla-chat-hero-main { + min-width: 0; +} + +.mosh.sla-chat-shell .sla-chat-kicker { + margin-bottom: 6px; + color: #ffcf9a; + font-family: "Futura PT Cond Bold", "Josefin Sans", "Roboto", sans-serif; + font-size: 0.72rem; + text-transform: uppercase; + letter-spacing: 0.14em; + opacity: 0.95; +} + +.mosh.sla-chat-shell .sla-chat-kicker-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + margin-bottom: 6px; +} + +.mosh.sla-chat-shell .sla-chat-meta-row { + display: inline-flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; + justify-content: flex-end; +} + +.mosh.sla-chat-shell .sla-chat-deptline { + display: inline-flex; + align-items: center; + gap: 6px; + flex-shrink: 0; +} + +.mosh.sla-chat-shell .sla-chat-emblem { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 32px; + height: 22px; + padding: 0 8px; + border-radius: 999px; + border: 1px solid var(--sla-chat-line); + background: linear-gradient(180deg, rgba(255,255,255,0.08), rgba(255,255,255,0.02)); + color: var(--sla-chat-ink); + font-size: 0.62rem; + font-family: "Futura PT Cond Bold", "Josefin Sans", "Roboto", sans-serif; + letter-spacing: 0.14em; + text-transform: uppercase; + box-shadow: 0 0 0 1px rgba(255,255,255,0.03), 0 0 10px var(--sla-chat-glow); +} + +.mosh.sla-chat-shell .sla-chat-dept { + color: var(--sla-chat-muted); + font-size: 0.68rem; + font-family: "Futura PT Cond Bold", "Josefin Sans", "Roboto", sans-serif; + letter-spacing: 0.12em; + text-transform: uppercase; +} + +.mosh.sla-chat-shell .sla-chat-type-badge { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 38px; + height: 22px; + padding: 0 8px; + border-radius: 8px; + border: 1px solid rgba(255,255,255,0.14); + font-size: 0.62rem; + font-family: "Futura PT Cond Bold", "Josefin Sans", "Roboto", sans-serif; + letter-spacing: 0.16em; + text-transform: uppercase; + color: #fff7ef; + box-shadow: inset 0 0 0 1px rgba(255,255,255,0.04), 0 0 12px rgba(0,0,0,0.18); +} + +.mosh.sla-chat-shell .sla-chat-type-normal { + background: linear-gradient(180deg, rgba(49, 72, 99, 0.98), rgba(21, 34, 48, 0.98)); + border-color: rgba(128, 182, 232, 0.34); +} + +.mosh.sla-chat-shell .sla-chat-type-attack { + background: linear-gradient(180deg, rgba(120, 34, 38, 0.98), rgba(61, 18, 22, 0.98)); + border-color: rgba(239, 112, 112, 0.36); +} + +.mosh.sla-chat-shell .sla-chat-type-panic { + background: linear-gradient(180deg, rgba(132, 65, 24, 0.98), rgba(68, 35, 12, 0.98)); + border-color: rgba(255, 181, 110, 0.36); +} + +.mosh.sla-chat-shell .sla-chat-type-wound { + background: linear-gradient(180deg, rgba(128, 18, 46, 0.98), rgba(67, 10, 25, 0.98)); + border-color: rgba(230, 88, 128, 0.38); +} + +.mosh.sla-chat-shell .sla-chat-type-ebb { + background: linear-gradient(180deg, rgba(22, 98, 94, 0.98), rgba(11, 47, 45, 0.98)); + border-color: rgba(106, 208, 193, 0.38); +} + +.mosh.sla-chat-shell .sla-chat-type-heal { + background: linear-gradient(180deg, rgba(22, 98, 58, 0.98), rgba(12, 48, 29, 0.98)); + border-color: rgba(118, 223, 159, 0.38); +} + +.mosh.sla-chat-shell .sla-chat-type-reload { + background: linear-gradient(180deg, rgba(110, 87, 28, 0.98), rgba(58, 45, 13, 0.98)); + border-color: rgba(235, 205, 116, 0.38); +} + +.mosh.sla-chat-shell .sla-chat-type-table { + background: linear-gradient(180deg, rgba(72, 57, 112, 0.98), rgba(36, 28, 58, 0.98)); + border-color: rgba(161, 135, 235, 0.34); +} + +.mosh.sla-chat-shell .sla-chat-type-item { + background: linear-gradient(180deg, rgba(66, 66, 76, 0.98), rgba(28, 28, 36, 0.98)); + border-color: rgba(180, 180, 195, 0.3); +} + +.chat-message .mosh.sla-chat-shell .rollweaponh1, +.mosh.sla-chat-shell .rollweaponh1 { + min-height: 0; + margin: 0; + padding: 10px 12px; + border-radius: 12px; + border: 1px solid var(--sla-chat-line); + background: linear-gradient(180deg, rgba(40, 18, 24, 0.98), rgba(22, 18, 28, 0.98)); + color: #fff7ef; + font-size: 1.02rem; + line-height: 1.08; + white-space: normal; + overflow-wrap: normal; + word-break: normal; + hyphens: none; + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.04); +} + +.mosh.sla-chat-shell .sla-chat-hero-title-tight { + font-size: 0.9rem; + line-height: 1.14; +} + +.mosh.sla-chat-shell .sla-chat-hero-media { + display: flex; + align-items: center; + justify-content: center; + padding: 8px; + border-radius: 12px; + border: 1px solid var(--sla-chat-line); + background: linear-gradient(180deg, rgba(18, 25, 36, 0.96), rgba(9, 13, 18, 0.96)); +} + +.mosh.sla-chat-shell .roll-image { + width: 44px; + height: 44px; + object-fit: cover; + border-radius: 10px; + border: 1px solid rgba(255, 255, 255, 0.14); +} + +.mosh.sla-chat-shell .sla-chat-summary { + margin: 0 0 10px; + padding: 10px 12px; + border-radius: 12px; + background: linear-gradient(180deg, rgba(245, 236, 226, 0.98), rgba(229, 217, 203, 0.98)); + color: #16120f; + border: 1px solid rgba(255, 219, 176, 0.48); + font-size: 0.98rem; + line-height: 1.28; +} + +.mosh.sla-chat-shell .sla-chat-summary strong { + color: #0f0c0a; +} + +.mosh.sla-chat-shell .sla-chat-rollframe { + margin: 0 0 10px; +} + +.mosh.sla-chat-shell .sla-roll-result, +.mosh.sla-chat-shell .dice-roll, +.mosh.sla-chat-shell .dice-result, +.mosh.sla-chat-shell .roll-result, +.mosh.sla-chat-shell .dice-total, +.mosh.sla-chat-shell .dice-formula { + color: #f4efe7; +} + +.mosh.sla-chat-shell .dice-formula { + background: linear-gradient(180deg, rgba(24, 57, 77, 0.98), rgba(12, 33, 49, 0.98)); + border: 1px solid rgba(88, 181, 219, 0.52); + border-radius: 10px; + color: #d9f3ff; + box-shadow: inset 0 0 0 1px rgba(255,255,255,0.04); +} + +.mosh.sla-chat-shell .dice-total, +.mosh.sla-chat-shell .roll-result { + background: linear-gradient(180deg, rgba(23, 28, 37, 0.98), rgba(12, 15, 20, 0.98)); + border: 1px solid rgba(228, 195, 121, 0.48); + border-radius: 12px; + color: #fff5df; + box-shadow: 0 0 0 1px rgba(255,255,255,0.03), 0 0 12px rgba(228, 195, 121, 0.12); +} + +.mosh.sla-chat-shell .sla-roll-outcome { + border-radius: 10px; + padding: 6px 10px; + font-weight: 700; +} + +.mosh.sla-chat-shell .sla-roll-outcome.sla-success, +.mosh.sla-chat-shell .dice-total.sla-crit-success { + background: linear-gradient(180deg, rgba(10, 84, 46, 0.95), rgba(10, 55, 31, 0.95)); + color: #dcffe7; + border-color: rgba(119, 225, 154, 0.45); +} + +.mosh.sla-chat-shell .sla-roll-outcome.sla-failure, +.mosh.sla-chat-shell .dice-total.sla-crit-failure { + background: linear-gradient(180deg, rgba(112, 20, 24, 0.95), rgba(67, 13, 18, 0.95)); + color: #ffe2e2; + border-color: rgba(255, 133, 133, 0.45); +} + +.mosh.sla-chat-shell .sla-chat-section { + margin: 0 0 10px; + padding: 10px 12px; + border: 1px solid rgba(207, 49, 72, 0.28); + border-radius: 12px; + background: var(--sla-chat-panel); + box-shadow: inset 0 0 0 1px rgba(255,255,255,0.03); +} + +.mosh.sla-chat-shell .sla-chat-section-title { + margin-bottom: 7px; + color: #ffd8a3; + font-size: 0.73rem; + letter-spacing: 0.16em; +} + +.mosh.sla-chat-shell .sla-chat-section-success { + background: linear-gradient(180deg, rgba(18, 58, 36, 0.96), rgba(12, 35, 23, 0.96)); + border-color: rgba(96, 213, 141, 0.35); +} + +.mosh.sla-chat-shell .sla-chat-section-danger { + background: linear-gradient(180deg, rgba(74, 20, 26, 0.96), rgba(45, 14, 17, 0.96)); + border-color: rgba(231, 104, 104, 0.38); +} + +.mosh.sla-chat-shell .sla-chat-section-heading { + display: flex; + justify-content: flex-end; + margin-bottom: 0.45rem; +} + +.mosh.sla-chat-shell .sla-chat-severity-pill { + display: inline-flex; + align-items: center; + padding: 0.18rem 0.62rem; + border-radius: 999px; + border: 1px solid rgba(255,255,255,0.24); + text-transform: uppercase; + letter-spacing: 0.14em; + font-size: 0.68rem; + font-weight: 800; + box-shadow: inset 0 1px 0 rgba(255,255,255,0.12); +} + +.mosh.sla-chat-shell .sla-chat-section-danger.sla-severity-minor { + background: linear-gradient(180deg, rgba(92, 57, 18, 0.96), rgba(55, 34, 12, 0.96)); + border-color: rgba(255, 190, 74, 0.5); +} + +.mosh.sla-chat-shell .sla-chat-section-danger.sla-severity-severe { + background: linear-gradient(180deg, rgba(90, 18, 26, 0.97), rgba(52, 10, 15, 0.97)); + border-color: rgba(255, 91, 91, 0.52); +} + +.mosh.sla-chat-shell .sla-chat-section-danger.sla-severity-critical { + background: + radial-gradient(circle at top right, rgba(181, 237, 255, 0.12), transparent 42%), + linear-gradient(180deg, rgba(102, 8, 28, 0.98), rgba(45, 8, 21, 0.98)); + border-color: rgba(204, 237, 255, 0.84); + box-shadow: inset 0 0 0 1px rgba(255,255,255,0.06), 0 0 14px rgba(162, 231, 255, 0.08); +} + +.mosh.sla-chat-shell .sla-chat-section-target { + background: linear-gradient(180deg, rgba(28, 35, 48, 0.96), rgba(15, 20, 28, 0.96)); + border-color: rgba(96, 165, 214, 0.28); +} + +.mosh.sla-chat-shell .sla-chat-section-damage { + background: linear-gradient(180deg, rgba(58, 21, 26, 0.98), rgba(33, 13, 16, 0.98)); + border-color: rgba(237, 122, 122, 0.34); +} + +.mosh.sla-chat-shell .sla-chat-section-heal { + background: linear-gradient(180deg, rgba(18, 59, 42, 0.98), rgba(11, 34, 25, 0.98)); + border-color: rgba(96, 210, 160, 0.34); +} + +.mosh.sla-chat-shell .sla-chat-section-ebb-meta { + background: linear-gradient(180deg, rgba(24, 53, 51, 0.98), rgba(13, 29, 28, 0.98)); + border-color: rgba(106, 208, 193, 0.28); +} + +.mosh.sla-chat-shell .sla-chat-divider { + margin: 0 0 8px; + color: #ffcf9a; + font-size: 0.72rem; + font-family: "Futura PT Cond Bold", "Josefin Sans", "Roboto", sans-serif; + text-transform: uppercase; + letter-spacing: 0.16em; +} + +.mosh.sla-chat-shell .sla-chat-chip { + background: linear-gradient(180deg, rgba(246, 197, 84, 0.95), rgba(194, 132, 22, 0.95)); + color: #18120d; + border: 1px solid rgba(255,255,255,0.14); +} + +.mosh.sla-chat-shell .sla-chat-list li { + margin-bottom: 6px; +} + +.mosh.sla-chat-shell .sla-chat-wound-block { + margin-top: 10px; + padding-top: 10px; + border-top: 1px solid rgba(255,255,255,0.08); +} + +.mosh.sla-chat-shell .sla-chat-wound-link { + margin-top: 6px; +} + +.mosh.sla-chat-shell .sla-effect-damage { + color: #ff9d8d; +} + +.mosh.sla-chat-shell .sla-effect-heal { + color: #97f0bb; +} + +.mosh.sla-chat-shell.sla-chat-shell-ebb { + --sla-chat-bg: linear-gradient(180deg, #112726 0%, #0b1516 100%); + --sla-chat-accent: #2dc1b3; + --sla-chat-accent-soft: rgba(45, 193, 179, 0.18); + --sla-chat-line: rgba(70, 205, 188, 0.44); + --sla-chat-glow: rgba(70, 205, 188, 0.22); +} + +.mosh.sla-chat-shell.sla-chat-shell-reload, +.mosh.sla-chat-shell.sla-chat-shell-item-update, +.mosh.sla-chat-shell.sla-chat-shell-actor-update { + --sla-chat-line: rgba(214, 187, 122, 0.34); + --sla-chat-bg: linear-gradient(180deg, #18181b 0%, #101014 100%); +} + +.mosh.sla-chat-shell.sla-chat-shell-item { + --sla-chat-line: rgba(178, 135, 228, 0.34); + --sla-chat-bg: linear-gradient(180deg, #17131b 0%, #0e0d12 100%); +} + +.mosh.sla-chat-shell.sla-chat-variant-sla { + --sla-chat-accent: #cf3148; + --sla-chat-accent-soft: rgba(207, 49, 72, 0.18); + --sla-chat-line: rgba(207, 49, 72, 0.48); + --sla-chat-glow: rgba(207, 49, 72, 0.22); +} + +.mosh.sla-chat-shell.sla-chat-variant-mort { + --sla-chat-bg: linear-gradient(180deg, #181416 0%, #0f0c0d 100%); + --sla-chat-panel: rgba(25, 20, 22, 0.95); + --sla-chat-accent: #b09aa7; + --sla-chat-accent-soft: rgba(176, 154, 167, 0.14); + --sla-chat-line: rgba(176, 154, 167, 0.42); + --sla-chat-glow: rgba(176, 154, 167, 0.2); +} + +.mosh.sla-chat-shell.sla-chat-variant-shiver { + --sla-chat-bg: linear-gradient(180deg, #131b23 0%, #0b1016 100%); + --sla-chat-panel: rgba(16, 25, 35, 0.95); + --sla-chat-accent: #78b8ff; + --sla-chat-accent-soft: rgba(120, 184, 255, 0.16); + --sla-chat-line: rgba(120, 184, 255, 0.42); + --sla-chat-glow: rgba(120, 184, 255, 0.2); +} + +.mosh.sla-chat-shell.sla-chat-variant-darknight { + --sla-chat-bg: linear-gradient(180deg, #18131f 0%, #0d0b11 100%); + --sla-chat-panel: rgba(23, 18, 31, 0.95); + --sla-chat-accent: #8b73ff; + --sla-chat-accent-soft: rgba(139, 115, 255, 0.16); + --sla-chat-line: rgba(139, 115, 255, 0.42); + --sla-chat-glow: rgba(139, 115, 255, 0.2); +} + +.mosh.sla-chat-shell.sla-chat-variant-tek { + --sla-chat-bg: linear-gradient(180deg, #141c1d 0%, #0b1112 100%); + --sla-chat-panel: rgba(18, 29, 29, 0.95); + --sla-chat-accent: #52d3be; + --sla-chat-accent-soft: rgba(82, 211, 190, 0.16); + --sla-chat-line: rgba(82, 211, 190, 0.42); + --sla-chat-glow: rgba(82, 211, 190, 0.22); +} + +.mosh.sla-chat-shell.sla-chat-variant-ops { + --sla-chat-bg: linear-gradient(180deg, #1a1712 0%, #100d09 100%); + --sla-chat-panel: rgba(28, 23, 16, 0.95); + --sla-chat-accent: #e1b15d; + --sla-chat-accent-soft: rgba(225, 177, 93, 0.16); + --sla-chat-line: rgba(225, 177, 93, 0.42); + --sla-chat-glow: rgba(225, 177, 93, 0.2); +} + +.mosh.sla-chat-shell.sla-chat-variant-ebb { + --sla-chat-bg: linear-gradient(180deg, #112726 0%, #0b1516 100%); + --sla-chat-panel: rgba(13, 33, 33, 0.95); + --sla-chat-accent: #2dc1b3; + --sla-chat-accent-soft: rgba(45, 193, 179, 0.18); + --sla-chat-line: rgba(70, 205, 188, 0.44); + --sla-chat-glow: rgba(70, 205, 188, 0.22); +} + +.mosh .sla-wound-effect-link { + border: 1px solid rgba(255, 193, 118, 0.55); + background: linear-gradient(180deg, rgba(45, 23, 14, 0.95), rgba(27, 14, 9, 0.95)); + color: #ffd6a0; +} + +.mosh .sla-wound-effect-link:hover, +.mosh .sla-wound-effect-link:focus { + background: linear-gradient(180deg, rgba(79, 36, 16, 0.98), rgba(44, 20, 9, 0.98)); + color: #fff0d1; + box-shadow: 0 0 0 1px rgba(255, 193, 118, 0.22), 0 0 14px rgba(255, 193, 118, 0.16); +} .mosh .description, .mosh .description .body, .mosh .rollcontainer .body { @@ -4073,6 +4601,46 @@ p { background: rgba(141, 31, 24, 0.08); } +.mosh .sla-chat-section-heading { + display: flex; + justify-content: flex-end; + margin-bottom: 0.35rem; +} + +.mosh .sla-chat-severity-pill { + display: inline-flex; + align-items: center; + padding: 0.18rem 0.62rem; + border-radius: 999px; + border: 1px solid rgba(0,0,0,0.12); + text-transform: uppercase; + letter-spacing: 0.14em; + font-size: 0.68rem; + font-weight: 800; +} + +.mosh .sla-severity-minor, +.mosh .sla-chat-severity-pill.sla-severity-minor { + background: linear-gradient(180deg, rgba(255, 216, 140, 0.32), rgba(255, 189, 79, 0.18)); + border-color: rgba(224, 152, 24, 0.42); + color: #9b5b00; +} + +.mosh .sla-severity-severe, +.mosh .sla-chat-severity-pill.sla-severity-severe { + background: linear-gradient(180deg, rgba(255, 145, 145, 0.24), rgba(233, 63, 63, 0.14)); + border-color: rgba(179, 27, 27, 0.34); + color: #9e1717; +} + +.mosh .sla-severity-critical, +.mosh .sla-chat-severity-pill.sla-severity-critical { + background: linear-gradient(180deg, rgba(232, 247, 255, 0.94), rgba(196, 233, 255, 0.82)); + border-color: rgba(84, 145, 199, 0.42); + color: #611120; + box-shadow: 0 0 14px rgba(126, 196, 245, 0.16); +} + .mosh .sla-chat-section-target { border-color: rgba(35, 73, 98, 0.35); background: rgba(35, 73, 98, 0.06); @@ -4155,6 +4723,25 @@ p { color: #136235; } +.mosh .severity[data-severity="1"], +.mosh .severity[data-severity="2"] { + color: #ffca61; +} + +.mosh .severity[data-severity="3"], +.mosh .severity[data-severity="4"] { + color: #ff7b7b; +} + +.mosh .severity[data-severity="5"], +.mosh .severity[data-severity="6"], +.mosh .severity[data-severity="7"], +.mosh .severity[data-severity="8"], +.mosh .severity[data-severity="9"] { + color: #eef9ff; + text-shadow: 0 0 10px rgba(162, 231, 255, 0.25); +} + .mosh .sla-chat-chip { display: inline-block; margin-bottom: 8px; @@ -4215,16 +4802,12 @@ p { } .mosh.sla-dossier-sheet .headergrid { - grid-template-columns: repeat(4, minmax(0, 1fr)); - grid-template-rows: auto auto auto auto; + grid-template-columns: 1.15fr 1.15fr 0.9fr 0.95fr 0.95fr 0.95fr; + grid-template-rows: auto auto auto; gap: 0.45rem 0.7rem; padding: 1.2rem 1rem 0.95rem; } -.mosh.sla-dossier-sheet .headergrid { - grid-template-columns: 1.1fr 1.1fr 0.9fr 1fr 1fr; -} - .mosh.sla-dossier-sheet .sla-sheet-wordmark { grid-column: 1 / -1; text-align: left; @@ -4244,7 +4827,7 @@ p { } .mosh.sla-dossier-sheet .header-field-lad { - grid-column: 1 / span 2; + grid-column: 1; grid-row: 3; align-self: start; } @@ -4255,23 +4838,32 @@ p { } .mosh.sla-dossier-sheet .header-field-race { - grid-column: 5; + grid-column: 5 / span 2; grid-row: 2; } .mosh.sla-dossier-sheet .header-field-sla-rank { - grid-column: 3; - grid-row: 4; + grid-column: 4; + grid-row: 3; } .mosh.sla-dossier-sheet .header-field-height { - grid-column: 4; - grid-row: 4; + grid-column: 5; + grid-row: 3; } .mosh.sla-dossier-sheet .header-field-weight { - grid-column: 5; - grid-row: 4; + grid-column: 6; + grid-row: 3; +} + +.mosh.sla-dossier-sheet .header-field-rest { + grid-column: 2; + grid-row: 3; + align-self: start; + justify-self: start; + width: 100%; + max-width: 78px; } .mosh.sla-dossier-sheet .abilities, @@ -4483,931 +5075,3 @@ p { background: #1a120c; border: 1px solid #8a5a31; } - -/* ============================================================ - SLA PASS 4 — SCL Controls, Session Tracking, Contacts, Rest - ============================================================ */ - -/* SCL fractional control */ -.sla-scl-control { - display: flex; - flex-direction: column; - gap: 4px; -} - -.sla-scl-input { - width: 100%; -} - -.sla-scl-buttons { - display: flex; - gap: 3px; -} - -.sla-scl-buttons .sla-scl-adjust { - flex: 1; - padding: 2px 0; - font-size: 0.72rem; - font-weight: 700; - border: 1px solid rgba(170, 2, 0, 0.4); - background: rgba(170, 2, 0, 0.12); - color: #cc3333; - border-radius: 3px; - cursor: pointer; - transition: background 0.15s; - line-height: 1.4; -} - -.sla-scl-buttons .sla-scl-adjust:hover { - background: rgba(170, 2, 0, 0.28); - color: #f0f0f0; -} - -/* Trauma Response / Session Ability Tracking */ -.sla-trauma-header-row { - display: flex; - align-items: center; - justify-content: space-between; - flex-wrap: wrap; - gap: 6px; - margin-bottom: 4px; -} - -.sla-session-ability-row { - display: flex; - align-items: center; - gap: 8px; -} - -.sla-session-ability-label { - display: flex; - align-items: center; - gap: 5px; - font-size: 0.75rem; - color: #aaa; - cursor: pointer; - user-select: none; -} - -.sla-session-ability-label input[type="checkbox"] { - margin: 0; - cursor: pointer; -} - -.sla-session-ability-label span { - white-space: nowrap; -} - -.sla-new-session { - font-size: 0.72rem; - padding: 2px 8px; - border: 1px solid rgba(170, 2, 0, 0.4); - background: rgba(170, 2, 0, 0.12); - color: #cc3333; - border-radius: 3px; - cursor: pointer; - transition: background 0.15s; - white-space: nowrap; -} - -.sla-new-session:hover { - background: rgba(170, 2, 0, 0.28); - color: #f0f0f0; -} - -/* Rest button — in save-action-strip */ -.sla-rest-button { - /* inherits .sla-sheet-action but add a distinct colour */ - background: rgba(40, 90, 150, 0.18) !important; - border-color: rgba(60, 120, 200, 0.45) !important; - color: #7ab8f5 !important; -} - -.sla-rest-button:hover { - background: rgba(40, 90, 150, 0.36) !important; - color: #d8eaff !important; -} - -/* Contacts Tab */ -.sla-contacts-toolbar { - display: flex; - gap: 8px; - margin-bottom: 12px; -} - -.sla-contacts-list { - display: flex; - flex-direction: column; - gap: 8px; -} - -.sla-contact-card { - background: rgba(255, 255, 255, 0.04); - border: 1px solid rgba(255, 255, 255, 0.08); - border-radius: 6px; - padding: 10px 12px; -} - -.sla-contact-header { - display: flex; - align-items: center; - gap: 10px; -} - -.sla-contact-disposition { - width: 10px; - height: 10px; - border-radius: 50%; - flex-shrink: 0; -} - -.sla-contact-disposition-ally { - background: #4eb765; - box-shadow: 0 0 6px rgba(78, 183, 101, 0.5); -} - -.sla-contact-disposition-neutral { - background: #aaa; -} - -.sla-contact-disposition-hostile { - background: #cc5050; - box-shadow: 0 0 6px rgba(204, 80, 80, 0.5); -} - -.sla-contact-disposition-unknown { - background: #777; - border: 1px dashed #aaa; -} - -.sla-contact-name { - font-weight: 700; - font-size: 0.9rem; - color: #f0f0f0; - flex: 1; -} - -.sla-contact-role { - font-size: 0.78rem; - color: #999; - font-style: italic; - flex: 1; -} - -.sla-contact-controls { - display: flex; - gap: 6px; - margin-left: auto; -} - -.sla-contact-controls button { - background: transparent; - border: none; - color: #888; - cursor: pointer; - padding: 2px 4px; - font-size: 0.8rem; - transition: color 0.15s; -} - -.sla-contact-controls button:hover { - color: #f0f0f0; -} - -.sla-contact-controls .sla-contact-delete:hover { - color: #cc5050; -} - -.sla-contact-notes { - margin-top: 6px; - font-size: 0.8rem; - color: #bbb; - line-height: 1.5; - border-top: 1px solid rgba(255, 255, 255, 0.06); - padding-top: 6px; -} - -/* LAD + Rest button row in header */ -.sla-lad-rest-row { - display: flex; - align-items: center; - gap: 8px; - flex-wrap: wrap; -} - -.sla-header-rest-button { - font-size: 0.72rem !important; - padding: 2px 9px !important; - border-radius: 3px !important; - font-weight: 600 !important; - cursor: pointer !important; - white-space: nowrap !important; - line-height: 1.5 !important; -} - -/* ============================================================ - SLA — Header grid: LAD · Credits · SCL · Height · Weight · Rest - ============================================================ */ - -/* 8-column grid — col 8 holds the Rest button at the far right */ -.mosh .headergrid { - grid-template-columns: 1.1fr 1.3fr 0.85fr 0.85fr 0.85fr 0.85fr 0.85fr 0.9fr !important; -} - -/* Row 2: Name (1-4), Class (5-6), Race (7-8) */ -.mosh .headernamegrid, -.mosh .header-field-name { - grid-column: 1 / span 4 !important; - grid-row: 2; -} -.mosh .header-field-class { - grid-column: 5 / span 2 !important; - grid-row: 2; -} -.mosh .header-field-race { - grid-column: 7 / span 2 !important; - grid-row: 2; -} - -/* Row 3: LAD (1) · Credits (2) · SCL (3) · Height (4) · Weight (5) · Rest (6-8) */ -.mosh .header-field-lad { - grid-column: 1 !important; - grid-row: 3; - align-self: start; -} -.mosh .header-field-credits { - grid-column: 2 !important; - grid-row: 3; -} -.mosh .header-field-sla-rank { - grid-column: 3 !important; - grid-row: 3; -} -.mosh .header-field-height { - grid-column: 4 !important; - grid-row: 3; -} -.mosh .header-field-weight { - grid-column: 5 !important; - grid-row: 3; -} -.mosh .header-field-rest { - grid-column: 6 / span 3 !important; - grid-row: 3; - align-self: end; -} - -/* Rest header pill — blue rounded, matching Wound/Panic shape */ -.mosh .sla-header-rest-pill { - background: linear-gradient(180deg, #3a8fd1, #1a5fa0) !important; - color: #e8f4ff !important; - border-color: rgba(100, 180, 255, 0.3) !important; - min-width: 0 !important; - width: 100%; - padding: 6px 10px !important; - font-size: 0.82rem !important; -} -.mosh .sla-header-rest-pill:hover { - background: linear-gradient(180deg, #4ea8f0, #2472bc) !important; - box-shadow: 0 0 12px rgba(60, 140, 255, 0.4) !important; - color: #fff !important; -} - -/* Dossier sheet overrides */ -.mosh.sla-dossier-sheet .header-field-lad { - grid-column: 1 !important; -} -.mosh.sla-dossier-sheet .header-field-credits { - grid-column: 2 !important; -} -.mosh.sla-dossier-sheet .header-field-rest { - grid-column: 6 / span 3 !important; -} - -/* ============================================================ - Portrait — static neon blue border (no animation) - ============================================================ */ -.mosh .sla-portrait-static { - outline: 2px solid #3a9fd6; - outline-offset: 2px; - box-shadow: 0 0 8px 2px rgba(58, 159, 214, 0.55); - border-radius: 6px; -} - -/* ============================================================ - Activity log textarea - ============================================================ */ -.sla-activity-log-textarea { - font-family: 'Roboto Mono', 'Courier New', monospace !important; - font-size: 0.75rem !important; - line-height: 1.55 !important; - overflow-y: auto !important; - resize: vertical !important; -} - -/* ============================================================ - SLA Industries Pause Screen Animation - ============================================================ */ - -/* Make the pause overlay a centered flex column — only when visible. - Do NOT set display here globally: Foundry controls show/hide via - its own display:none and we must not override that with !important. */ -#pause:not([hidden]):not(.hidden) { - display: flex !important; - flex-direction: column !important; - align-items: center !important; - justify-content: center !important; -} - -/* Nuke all of Foundry's default pause children */ -#pause > *:not(.sla-pause-root) { - display: none !important; -} -#pause figure, -#pause h3, -#pause img, -#pause canvas, -#pause figure *, -#pause .fas, -#pause .fa-cog { - display: none !important; -} - -/* Root container for the injected SVG — full centered column */ -.sla-pause-root { - display: flex !important; - align-items: center !important; - justify-content: center !important; - width: 100% !important; - height: 100% !important; - position: absolute !important; - top: 0 !important; - left: 0 !important; -} - -/* Main SVG — large and prominent */ -.sla-pause-svg { - width: min(360px, 60vw) !important; - height: auto !important; - overflow: visible !important; -} - -/* ── Outer ring: rotates clockwise ── */ -.sla-ring-outer { - transform-origin: 0px 0px; - animation: sla-spin-cw 12s linear infinite; -} - -/* ── Inner ring: rotates counter-clockwise ── */ -.sla-ring-inner { - transform-origin: 0px 0px; - animation: sla-spin-ccw 8s linear infinite; -} - -/* ── Scan line: sweeps clockwise like a radar ── */ -.sla-pause-scan { - transform-origin: 0px 0px; - animation: sla-spin-cw 4s linear infinite; -} - -/* ── Logo pulse ── */ -.sla-pause-logo-text { - animation: sla-logo-pulse 3s ease-in-out infinite; -} - -/* ── "STAND BY" blinking ── */ -.sla-pause-standby { - animation: sla-standby-blink 1.8s step-start infinite; -} - -/* ─── Keyframes ─────────────────────────────────────────── */ - -@keyframes sla-spin-cw { - from { transform: rotate(0deg); } - to { transform: rotate(360deg); } -} - -@keyframes sla-spin-ccw { - from { transform: rotate(0deg); } - to { transform: rotate(-360deg); } -} - -@keyframes sla-logo-pulse { - 0%, 100% { fill: #ff2222; filter: drop-shadow(0 0 4px #ff2222); } - 50% { fill: #ff5555; filter: drop-shadow(0 0 10px #ff4444); } -} - -@keyframes sla-standby-blink { - 0%, 60% { opacity: 1; } - 61%, 100% { opacity: 0; } -} -/* ============================================================ - SLA Industries — Custom UI Icon Set (v2 — scoped selectors) - Replaces Foundry's Font Awesome icons via CSS mask-image - ============================================================ */ - -/* ── Sidebar Tabs ─────────────────────────────────────────── */ - -/* chat — chat */ -#sidebar-tabs [data-tab="chat"] i, -#sidebar-tabs a[data-tab="chat"] i, -nav#sidebar-tabs [data-tab="chat"] i { - display: inline-block !important; - position: relative !important; - width: 1.25em !important; - height: 1.25em !important; - vertical-align: middle !important; -} -#sidebar-tabs [data-tab="chat"] i::before, -#sidebar-tabs a[data-tab="chat"] i::before, -nav#sidebar-tabs [data-tab="chat"] i::before { - content: '' !important; - position: absolute !important; - inset: 0 !important; - background-color: currentColor !important; - -webkit-mask-image: url('../icons/ui/chat.svg') !important; - mask-image: url('../icons/ui/chat.svg') !important; - -webkit-mask-size: contain !important; - mask-size: contain !important; - -webkit-mask-repeat: no-repeat !important; - mask-repeat: no-repeat !important; - -webkit-mask-position: center !important; - mask-position: center !important; -} - -/* combat — combat */ -#sidebar-tabs [data-tab="combat"] i, -#sidebar-tabs a[data-tab="combat"] i, -nav#sidebar-tabs [data-tab="combat"] i { - display: inline-block !important; - position: relative !important; - width: 1.25em !important; - height: 1.25em !important; - vertical-align: middle !important; -} -#sidebar-tabs [data-tab="combat"] i::before, -#sidebar-tabs a[data-tab="combat"] i::before, -nav#sidebar-tabs [data-tab="combat"] i::before { - content: '' !important; - position: absolute !important; - inset: 0 !important; - background-color: currentColor !important; - -webkit-mask-image: url('../icons/ui/combat.svg') !important; - mask-image: url('../icons/ui/combat.svg') !important; - -webkit-mask-size: contain !important; - mask-size: contain !important; - -webkit-mask-repeat: no-repeat !important; - mask-repeat: no-repeat !important; - -webkit-mask-position: center !important; - mask-position: center !important; -} - -/* scenes — scenes */ -#sidebar-tabs [data-tab="scenes"] i, -#sidebar-tabs a[data-tab="scenes"] i, -nav#sidebar-tabs [data-tab="scenes"] i { - display: inline-block !important; - position: relative !important; - width: 1.25em !important; - height: 1.25em !important; - vertical-align: middle !important; -} -#sidebar-tabs [data-tab="scenes"] i::before, -#sidebar-tabs a[data-tab="scenes"] i::before, -nav#sidebar-tabs [data-tab="scenes"] i::before { - content: '' !important; - position: absolute !important; - inset: 0 !important; - background-color: currentColor !important; - -webkit-mask-image: url('../icons/ui/scenes.svg') !important; - mask-image: url('../icons/ui/scenes.svg') !important; - -webkit-mask-size: contain !important; - mask-size: contain !important; - -webkit-mask-repeat: no-repeat !important; - mask-repeat: no-repeat !important; - -webkit-mask-position: center !important; - mask-position: center !important; -} - -/* actors — actors */ -#sidebar-tabs [data-tab="actors"] i, -#sidebar-tabs a[data-tab="actors"] i, -nav#sidebar-tabs [data-tab="actors"] i { - display: inline-block !important; - position: relative !important; - width: 1.25em !important; - height: 1.25em !important; - vertical-align: middle !important; -} -#sidebar-tabs [data-tab="actors"] i::before, -#sidebar-tabs a[data-tab="actors"] i::before, -nav#sidebar-tabs [data-tab="actors"] i::before { - content: '' !important; - position: absolute !important; - inset: 0 !important; - background-color: currentColor !important; - -webkit-mask-image: url('../icons/ui/actors.svg') !important; - mask-image: url('../icons/ui/actors.svg') !important; - -webkit-mask-size: contain !important; - mask-size: contain !important; - -webkit-mask-repeat: no-repeat !important; - mask-repeat: no-repeat !important; - -webkit-mask-position: center !important; - mask-position: center !important; -} - -/* items — items */ -#sidebar-tabs [data-tab="items"] i, -#sidebar-tabs a[data-tab="items"] i, -nav#sidebar-tabs [data-tab="items"] i { - display: inline-block !important; - position: relative !important; - width: 1.25em !important; - height: 1.25em !important; - vertical-align: middle !important; -} -#sidebar-tabs [data-tab="items"] i::before, -#sidebar-tabs a[data-tab="items"] i::before, -nav#sidebar-tabs [data-tab="items"] i::before { - content: '' !important; - position: absolute !important; - inset: 0 !important; - background-color: currentColor !important; - -webkit-mask-image: url('../icons/ui/items.svg') !important; - mask-image: url('../icons/ui/items.svg') !important; - -webkit-mask-size: contain !important; - mask-size: contain !important; - -webkit-mask-repeat: no-repeat !important; - mask-repeat: no-repeat !important; - -webkit-mask-position: center !important; - mask-position: center !important; -} - -/* journal — journal */ -#sidebar-tabs [data-tab="journal"] i, -#sidebar-tabs a[data-tab="journal"] i, -nav#sidebar-tabs [data-tab="journal"] i { - display: inline-block !important; - position: relative !important; - width: 1.25em !important; - height: 1.25em !important; - vertical-align: middle !important; -} -#sidebar-tabs [data-tab="journal"] i::before, -#sidebar-tabs a[data-tab="journal"] i::before, -nav#sidebar-tabs [data-tab="journal"] i::before { - content: '' !important; - position: absolute !important; - inset: 0 !important; - background-color: currentColor !important; - -webkit-mask-image: url('../icons/ui/journal.svg') !important; - mask-image: url('../icons/ui/journal.svg') !important; - -webkit-mask-size: contain !important; - mask-size: contain !important; - -webkit-mask-repeat: no-repeat !important; - mask-repeat: no-repeat !important; - -webkit-mask-position: center !important; - mask-position: center !important; -} - -/* tables — tables */ -#sidebar-tabs [data-tab="tables"] i, -#sidebar-tabs a[data-tab="tables"] i, -nav#sidebar-tabs [data-tab="tables"] i { - display: inline-block !important; - position: relative !important; - width: 1.25em !important; - height: 1.25em !important; - vertical-align: middle !important; -} -#sidebar-tabs [data-tab="tables"] i::before, -#sidebar-tabs a[data-tab="tables"] i::before, -nav#sidebar-tabs [data-tab="tables"] i::before { - content: '' !important; - position: absolute !important; - inset: 0 !important; - background-color: currentColor !important; - -webkit-mask-image: url('../icons/ui/tables.svg') !important; - mask-image: url('../icons/ui/tables.svg') !important; - -webkit-mask-size: contain !important; - mask-size: contain !important; - -webkit-mask-repeat: no-repeat !important; - mask-repeat: no-repeat !important; - -webkit-mask-position: center !important; - mask-position: center !important; -} - -/* playlists — playlists */ -#sidebar-tabs [data-tab="playlists"] i, -#sidebar-tabs a[data-tab="playlists"] i, -nav#sidebar-tabs [data-tab="playlists"] i { - display: inline-block !important; - position: relative !important; - width: 1.25em !important; - height: 1.25em !important; - vertical-align: middle !important; -} -#sidebar-tabs [data-tab="playlists"] i::before, -#sidebar-tabs a[data-tab="playlists"] i::before, -nav#sidebar-tabs [data-tab="playlists"] i::before { - content: '' !important; - position: absolute !important; - inset: 0 !important; - background-color: currentColor !important; - -webkit-mask-image: url('../icons/ui/playlists.svg') !important; - mask-image: url('../icons/ui/playlists.svg') !important; - -webkit-mask-size: contain !important; - mask-size: contain !important; - -webkit-mask-repeat: no-repeat !important; - mask-repeat: no-repeat !important; - -webkit-mask-position: center !important; - mask-position: center !important; -} - -/* compendium — compendium */ -#sidebar-tabs [data-tab="compendium"] i, -#sidebar-tabs a[data-tab="compendium"] i, -nav#sidebar-tabs [data-tab="compendium"] i { - display: inline-block !important; - position: relative !important; - width: 1.25em !important; - height: 1.25em !important; - vertical-align: middle !important; -} -#sidebar-tabs [data-tab="compendium"] i::before, -#sidebar-tabs a[data-tab="compendium"] i::before, -nav#sidebar-tabs [data-tab="compendium"] i::before { - content: '' !important; - position: absolute !important; - inset: 0 !important; - background-color: currentColor !important; - -webkit-mask-image: url('../icons/ui/compendium.svg') !important; - mask-image: url('../icons/ui/compendium.svg') !important; - -webkit-mask-size: contain !important; - mask-size: contain !important; - -webkit-mask-repeat: no-repeat !important; - mask-repeat: no-repeat !important; - -webkit-mask-position: center !important; - mask-position: center !important; -} - -/* settings — settings */ -#sidebar-tabs [data-tab="settings"] i, -#sidebar-tabs a[data-tab="settings"] i, -nav#sidebar-tabs [data-tab="settings"] i { - display: inline-block !important; - position: relative !important; - width: 1.25em !important; - height: 1.25em !important; - vertical-align: middle !important; -} -#sidebar-tabs [data-tab="settings"] i::before, -#sidebar-tabs a[data-tab="settings"] i::before, -nav#sidebar-tabs [data-tab="settings"] i::before { - content: '' !important; - position: absolute !important; - inset: 0 !important; - background-color: currentColor !important; - -webkit-mask-image: url('../icons/ui/settings.svg') !important; - mask-image: url('../icons/ui/settings.svg') !important; - -webkit-mask-size: contain !important; - mask-size: contain !important; - -webkit-mask-repeat: no-repeat !important; - mask-repeat: no-repeat !important; - -webkit-mask-position: center !important; - mask-position: center !important; -} - -/* ── Scene Controls (left toolbar) ────────────────────────── */ - -/* token — token */ -#controls [data-control="token"] > i, -#controls li[data-control="token"] > i, -#controls .scene-control[data-control="token"] > i, -ol#controls [data-control="token"] > i { - display: inline-block !important; - position: relative !important; - width: 1.25em !important; - height: 1.25em !important; - vertical-align: middle !important; -} -#controls [data-control="token"] > i::before, -#controls li[data-control="token"] > i::before, -#controls .scene-control[data-control="token"] > i::before, -ol#controls [data-control="token"] > i::before { - content: '' !important; - position: absolute !important; - inset: 0 !important; - background-color: currentColor !important; - -webkit-mask-image: url('../icons/ui/token.svg') !important; - mask-image: url('../icons/ui/token.svg') !important; - -webkit-mask-size: contain !important; - mask-size: contain !important; - -webkit-mask-repeat: no-repeat !important; - mask-repeat: no-repeat !important; - -webkit-mask-position: center !important; - mask-position: center !important; -} - -/* measure — measure */ -#controls [data-control="measure"] > i, -#controls li[data-control="measure"] > i, -#controls .scene-control[data-control="measure"] > i, -ol#controls [data-control="measure"] > i { - display: inline-block !important; - position: relative !important; - width: 1.25em !important; - height: 1.25em !important; - vertical-align: middle !important; -} -#controls [data-control="measure"] > i::before, -#controls li[data-control="measure"] > i::before, -#controls .scene-control[data-control="measure"] > i::before, -ol#controls [data-control="measure"] > i::before { - content: '' !important; - position: absolute !important; - inset: 0 !important; - background-color: currentColor !important; - -webkit-mask-image: url('../icons/ui/measure.svg') !important; - mask-image: url('../icons/ui/measure.svg') !important; - -webkit-mask-size: contain !important; - mask-size: contain !important; - -webkit-mask-repeat: no-repeat !important; - mask-repeat: no-repeat !important; - -webkit-mask-position: center !important; - mask-position: center !important; -} - -/* tiles — tiles */ -#controls [data-control="tiles"] > i, -#controls li[data-control="tiles"] > i, -#controls .scene-control[data-control="tiles"] > i, -ol#controls [data-control="tiles"] > i { - display: inline-block !important; - position: relative !important; - width: 1.25em !important; - height: 1.25em !important; - vertical-align: middle !important; -} -#controls [data-control="tiles"] > i::before, -#controls li[data-control="tiles"] > i::before, -#controls .scene-control[data-control="tiles"] > i::before, -ol#controls [data-control="tiles"] > i::before { - content: '' !important; - position: absolute !important; - inset: 0 !important; - background-color: currentColor !important; - -webkit-mask-image: url('../icons/ui/tiles.svg') !important; - mask-image: url('../icons/ui/tiles.svg') !important; - -webkit-mask-size: contain !important; - mask-size: contain !important; - -webkit-mask-repeat: no-repeat !important; - mask-repeat: no-repeat !important; - -webkit-mask-position: center !important; - mask-position: center !important; -} - -/* drawings — drawings */ -#controls [data-control="drawings"] > i, -#controls li[data-control="drawings"] > i, -#controls .scene-control[data-control="drawings"] > i, -ol#controls [data-control="drawings"] > i { - display: inline-block !important; - position: relative !important; - width: 1.25em !important; - height: 1.25em !important; - vertical-align: middle !important; -} -#controls [data-control="drawings"] > i::before, -#controls li[data-control="drawings"] > i::before, -#controls .scene-control[data-control="drawings"] > i::before, -ol#controls [data-control="drawings"] > i::before { - content: '' !important; - position: absolute !important; - inset: 0 !important; - background-color: currentColor !important; - -webkit-mask-image: url('../icons/ui/drawings.svg') !important; - mask-image: url('../icons/ui/drawings.svg') !important; - -webkit-mask-size: contain !important; - mask-size: contain !important; - -webkit-mask-repeat: no-repeat !important; - mask-repeat: no-repeat !important; - -webkit-mask-position: center !important; - mask-position: center !important; -} - -/* walls — walls */ -#controls [data-control="walls"] > i, -#controls li[data-control="walls"] > i, -#controls .scene-control[data-control="walls"] > i, -ol#controls [data-control="walls"] > i { - display: inline-block !important; - position: relative !important; - width: 1.25em !important; - height: 1.25em !important; - vertical-align: middle !important; -} -#controls [data-control="walls"] > i::before, -#controls li[data-control="walls"] > i::before, -#controls .scene-control[data-control="walls"] > i::before, -ol#controls [data-control="walls"] > i::before { - content: '' !important; - position: absolute !important; - inset: 0 !important; - background-color: currentColor !important; - -webkit-mask-image: url('../icons/ui/walls.svg') !important; - mask-image: url('../icons/ui/walls.svg') !important; - -webkit-mask-size: contain !important; - mask-size: contain !important; - -webkit-mask-repeat: no-repeat !important; - mask-repeat: no-repeat !important; - -webkit-mask-position: center !important; - mask-position: center !important; -} - -/* lighting — lighting */ -#controls [data-control="lighting"] > i, -#controls li[data-control="lighting"] > i, -#controls .scene-control[data-control="lighting"] > i, -ol#controls [data-control="lighting"] > i { - display: inline-block !important; - position: relative !important; - width: 1.25em !important; - height: 1.25em !important; - vertical-align: middle !important; -} -#controls [data-control="lighting"] > i::before, -#controls li[data-control="lighting"] > i::before, -#controls .scene-control[data-control="lighting"] > i::before, -ol#controls [data-control="lighting"] > i::before { - content: '' !important; - position: absolute !important; - inset: 0 !important; - background-color: currentColor !important; - -webkit-mask-image: url('../icons/ui/lighting.svg') !important; - mask-image: url('../icons/ui/lighting.svg') !important; - -webkit-mask-size: contain !important; - mask-size: contain !important; - -webkit-mask-repeat: no-repeat !important; - mask-repeat: no-repeat !important; - -webkit-mask-position: center !important; - mask-position: center !important; -} - -/* sounds — sounds */ -#controls [data-control="sounds"] > i, -#controls li[data-control="sounds"] > i, -#controls .scene-control[data-control="sounds"] > i, -ol#controls [data-control="sounds"] > i { - display: inline-block !important; - position: relative !important; - width: 1.25em !important; - height: 1.25em !important; - vertical-align: middle !important; -} -#controls [data-control="sounds"] > i::before, -#controls li[data-control="sounds"] > i::before, -#controls .scene-control[data-control="sounds"] > i::before, -ol#controls [data-control="sounds"] > i::before { - content: '' !important; - position: absolute !important; - inset: 0 !important; - background-color: currentColor !important; - -webkit-mask-image: url('../icons/ui/sounds.svg') !important; - mask-image: url('../icons/ui/sounds.svg') !important; - -webkit-mask-size: contain !important; - mask-size: contain !important; - -webkit-mask-repeat: no-repeat !important; - mask-repeat: no-repeat !important; - -webkit-mask-position: center !important; - mask-position: center !important; -} - -/* notes — notes */ -#controls [data-control="notes"] > i, -#controls li[data-control="notes"] > i, -#controls .scene-control[data-control="notes"] > i, -ol#controls [data-control="notes"] > i { - display: inline-block !important; - position: relative !important; - width: 1.25em !important; - height: 1.25em !important; - vertical-align: middle !important; -} -#controls [data-control="notes"] > i::before, -#controls li[data-control="notes"] > i::before, -#controls .scene-control[data-control="notes"] > i::before, -ol#controls [data-control="notes"] > i::before { - content: '' !important; - position: absolute !important; - inset: 0 !important; - background-color: currentColor !important; - -webkit-mask-image: url('../icons/ui/notes.svg') !important; - mask-image: url('../icons/ui/notes.svg') !important; - -webkit-mask-size: contain !important; - mask-size: contain !important; - -webkit-mask-repeat: no-repeat !important; - mask-repeat: no-repeat !important; - -webkit-mask-position: center !important; - mask-position: center !important; -} diff --git a/module/actor/actor-sheet.js b/module/actor/actor-sheet.js index 2009de0..27497fc 100644 --- a/module/actor/actor-sheet.js +++ b/module/actor/actor-sheet.js @@ -141,6 +141,30 @@ function decorateSkillWithTraitPreview(skill, traitRows = []) { return skill; } +function getPurchasableItemTotalCost(itemData = {}) { + const system = itemData?.system ?? itemData?.data ?? {}; + const unitCost = Number(system.cost ?? 0) || 0; + const quantity = Math.max(1, Number(system.quantity ?? 1) || 1); + return Math.round(unitCost * quantity * 100) / 100; +} + +function isAmmoPurchaseItem(itemData = {}) { + const system = itemData?.system ?? itemData?.data ?? {}; + const sla = system?.sla ?? {}; + const name = String(itemData?.name ?? "").trim().toLowerCase(); + const category = String(sla.category ?? "").trim().toLowerCase(); + const calibre = String(sla.calibre ?? "").trim(); + const ammoTag = String(sla.ammoTag ?? "").trim(); + return category === "ammunition" || Boolean((calibre && ammoTag) || name.startsWith("ammo:")); +} + +function formatSlaTimestamp() { + return new Intl.DateTimeFormat(undefined, { + dateStyle: "short", + timeStyle: "short" + }).format(new Date()); +} + const SLA_SPECIES_RULES = { Human: [ { key: "adaptable", label: "Adaptable", summary: "Once per session, reroll one Stat check and take the better result.", optional: false, default: true } @@ -677,19 +701,26 @@ export class MothershipActorSheet extends foundry.appv1.sheets.ActorSheet { //Quantity adjuster html.on('mousedown', '.item-quantity', ev => { const li = ev.currentTarget.closest(".item"); - //const item = duplicate(this.actor.getEmbeddedDocument("Item", li.dataset.itemId)) - var item; - item = foundry.utils.duplicate(this.actor.getEmbeddedDocument("Item", li.dataset.itemId)); - - let amount = item.system.quantity; + const sourceItem = this.actor.getEmbeddedDocument("Item", li.dataset.itemId); + if (!sourceItem) return; + const item = foundry.utils.duplicate(sourceItem); + const amount = Math.max(0, Number(item.system?.quantity ?? 0) || 0); + let delta = 0; - if (event.button == 0) { - item.system.quantity = Number(amount) + 1; - } else if (event.button == 2) { - item.system.quantity = Number(amount) - 1; + if (ev.button === 0) { + delta = 1; + } else if (ev.button === 2) { + delta = -1; } - this.actor.updateEmbeddedDocuments('Item', [item]); + if (!delta) return; + item.system.quantity = Math.max(0, amount + delta); + + this.actor.updateEmbeddedDocuments('Item', [item]).then(async () => { + if (delta > 0) { + await this._applySlaQuantityIncreaseDebit(sourceItem, delta); + } + }); }); //Severity adjuster @@ -1197,6 +1228,91 @@ export class MothershipActorSheet extends foundry.appv1.sheets.ActorSheet { /* -------------------------------------------- */ + async _onDrop(event) { + const data = TextEditor.getDragEventData(event); + if (data?.type !== "Item") return super._onDrop(event); + + const droppedItem = await Item.implementation.fromDropData(data).catch(() => null); + if (!droppedItem) return super._onDrop(event); + + if (droppedItem.parent?.uuid === this.actor.uuid) { + return super._onDrop(event); + } + + const shouldDebitPurchase = !droppedItem.parent || droppedItem.parent.documentName !== "Actor"; + const itemData = droppedItem.toObject(); + delete itemData._id; + delete itemData.folder; + const created = await this.actor.createEmbeddedDocuments("Item", [itemData], { + slaPurchaseDebit: true, + slaPurchaseSource: data.uuid ?? droppedItem.uuid ?? "" + }); + if (shouldDebitPurchase) { + await this._applySlaPurchaseDebit(created); + } + return created; + } + + async _applySlaPurchaseDebit(createdDocs = []) { + const documents = Array.isArray(createdDocs) ? createdDocs : [createdDocs]; + const total = Math.round(documents.reduce((sum, doc) => sum + getPurchasableItemTotalCost(doc?.toObject?.() ?? doc), 0) * 100) / 100; + if (!total) return; + + const currentCredits = Number(this.actor.system?.credits?.value ?? 0) || 0; + const newCredits = Math.round((currentCredits - total) * 100) / 100; + const updateData = { "system.credits.value": newCredits }; + + const itemNames = documents.map((doc) => doc?.name).filter(Boolean).join(", "); + const ammoDocs = documents + .map((doc) => doc?.toObject?.() ?? doc) + .filter((doc) => isAmmoPurchaseItem(doc)); + if (ammoDocs.length) { + const stamp = formatSlaTimestamp(); + const currentLedger = String(this.actor.system?.sla?.ammoLedger?.value ?? "").trim(); + const purchaseEntries = ammoDocs.map((doc) => { + const system = doc?.system ?? doc?.data ?? {}; + const quantity = Math.max(1, Number(system.quantity ?? 1) || 1); + const unitCost = Number(system.cost ?? 0) || 0; + const lineCost = Math.round(unitCost * quantity * 100) / 100; + return `[${stamp}] PURCHASE | ${doc?.name ?? "Ammo"} | ${quantity} round${quantity === 1 ? "" : "s"} | -${lineCost} cR | balance ${newCredits} cR`; + }); + const ledgerLines = [...purchaseEntries, ...currentLedger.split("\n").filter(Boolean)].slice(0, 60); + updateData["system.sla.ammoLedger.value"] = ledgerLines.join("\n"); + } + + await this.actor.update(updateData); + + ui.notifications.info(`${this.actor.name}: deducted ${total} cR for ${itemNames || "purchased item"}. Credits now ${newCredits} cR.`); + await this.actor.addSlaActivityLog?.(`Purchased ${itemNames || "item"} for ${total} cR. Credits now ${newCredits} cR.`); + } + + async _applySlaQuantityIncreaseDebit(itemDoc, increase = 0) { + const added = Math.max(0, Number(increase ?? 0) || 0); + if (!added || !itemDoc) return; + + const unitCost = Number(itemDoc.system?.cost ?? 0) || 0; + const total = Math.round(unitCost * added * 100) / 100; + if (!total) return; + + const currentCredits = Number(this.actor.system?.credits?.value ?? 0) || 0; + const newCredits = Math.round((currentCredits - total) * 100) / 100; + const updateData = { "system.credits.value": newCredits }; + + if (isAmmoPurchaseItem(itemDoc)) { + const stamp = formatSlaTimestamp(); + const currentLedger = String(this.actor.system?.sla?.ammoLedger?.value ?? "").trim(); + const entry = `[${stamp}] PURCHASE | ${itemDoc.name ?? "Ammo"} | ${added} round${added === 1 ? "" : "s"} | -${total} cR | balance ${newCredits} cR`; + const ledgerLines = [entry, ...currentLedger.split("\n").filter(Boolean)].slice(0, 60); + updateData["system.sla.ammoLedger.value"] = ledgerLines.join("\n"); + } + + await this.actor.update(updateData); + ui.notifications.info(`${this.actor.name}: deducted ${total} cR for ${added} more ${itemDoc.name ?? "item"}. Credits now ${newCredits} cR.`); + await this.actor.addSlaActivityLog?.(`Purchased +${added} ${itemDoc.name ?? "item"} for ${total} cR. Credits now ${newCredits} cR.`); + } + + /* -------------------------------------------- */ + /** * Handle creating a new Owned Item for the actor using initial data defined in the HTML dataset * @param {Event} event The originating click event diff --git a/module/actor/actor.js b/module/actor/actor.js index 1b43f51..5681894 100644 --- a/module/actor/actor.js +++ b/module/actor/actor.js @@ -402,14 +402,6 @@ export class MothershipActor extends Actor { // Prepare Character type specific data _deriveCharacter() { const system = this.system; - //clamp stress.value to stress.min so stale stored values never show below the floor - if (system.other?.stress) { - const stressMin = Number(system.other.stress.min ?? 0); - const stressVal = Number(system.other.stress.value ?? 0); - if (Number.isFinite(stressMin) && Number.isFinite(stressVal) && stressVal < stressMin) { - system.other.stress.value = stressMin; - } - } //armor points + damage reduction //init vars let armorPoints = 0; @@ -497,44 +489,11 @@ export class MothershipActor extends Actor { } async useSlaDrug(itemId) { - const result = await SLADrugSystem.useDrug({ actor: this, itemId }); - if (result?.ok !== false) { - const item = this.items.get(itemId); - if (item) await this.addSlaActivityLog(`Drug used: ${item.name}.`); - } - return result; + return SLADrugSystem.useDrug({ actor: this, itemId }); } async closeSlaDrug(itemId = "", drug = "") { - const result = await SLADrugSystem.closeDrug({ actor: this, itemId, drug }); - if (result?.ok !== false) { - const drugName = drug || (itemId ? (this.items.get(itemId)?.name ?? "Unknown drug") : "Unknown drug"); - await this.addSlaActivityLog(`Drug effect ended: ${drugName}.`); - } - return result; - } - - /** - * Append a timestamped line to the operative's activity log. - * Keeps newest entries at the top and trims to ~10 KB. - */ - async addSlaActivityLog(entry) { - try { - const now = new Date(); - const ts = now.toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit" }); - const line = `[${ts}] ${entry}`; - const current = String(this.system?.sla?.activityLog?.value ?? ""); - const combined = current ? `${line}\n${current}` : line; - // Trim oldest lines until under 10 KB - const MAX = 10240; - if (combined.length > MAX) { - const lines = combined.split("\n"); - while (lines.join("\n").length > MAX && lines.length > 1) lines.pop(); - await this.update({ "system.sla.activityLog.value": lines.join("\n") }); - } else { - await this.update({ "system.sla.activityLog.value": combined }); - } - } catch (_) { /* log failures are non-fatal */ } + return SLADrugSystem.closeDrug({ actor: this, itemId, drug }); } //central flavor text library for all chat messages @@ -640,9 +599,123 @@ export class MothershipActor extends Actor { return rollStringParsed; } + clampSlaRollTarget(rollTarget, rollString = "") { + const numericTarget = Number(rollTarget); + if (!Number.isFinite(numericTarget)) return rollTarget; + const usesD100 = String(rollString ?? "").includes("1d100"); + if (!usesD100) return numericTarget; + return Math.min(98, numericTarget); + } + + isSlaAutomaticFailure(total, rollString = "") { + const numericTotal = Number(total); + if (!Number.isFinite(numericTotal)) return false; + const usesD100 = String(rollString ?? "").includes("1d100"); + return usesD100 && numericTotal === 99; + } + + getSlaChatVariant(context = {}) { + const actorClass = String(this.system?.class?.value ?? "").trim(); + const employer = String(this.system?.sla?.employer?.value ?? "").trim(); + const sector = String(this.system?.sla?.sector?.value ?? "").trim(); + const skill = String(context?.skill ?? "").trim(); + const skillCategory = String(context?.skillCategory ?? "").trim(); + const tableName = String(context?.tableName ?? "").trim(); + const header = String(context?.msgHeader ?? "").trim(); + const weaponName = String(context?.weapon?.name ?? "").trim(); + const itemName = String(context?.item?.name ?? "").trim(); + const extra = String(context?.extra ?? "").trim(); + + const haystack = [ + actorClass, + employer, + sector, + skill, + skillCategory, + tableName, + header, + weaponName, + itemName, + extra + ].join(" ").toLowerCase(); + + if (context?.isSlaEbbSkill || /\bebb\b|ebon|brain\swaste|flux|telekinesis|blast|awareness/.test(haystack)) { + return { key: "ebb", label: "Ebb", emblem: "EBB" }; + } + if (/\bdark\s?night\b/.test(haystack)) { + return { key: "darknight", label: "DarkNight", emblem: "DN" }; + } + if (/\bshiver\b/.test(haystack)) { + return { key: "shiver", label: "Shiver", emblem: "SV" }; + } + if (/\bmort\b|mortuary|undertaker|corpse|cadaver/.test(haystack)) { + return { key: "mort", label: "Mort", emblem: "MT" }; + } + if (/\btek\b|technical|engineer|engineering|medic|medical|science|research/.test(haystack)) { + return { key: "tek", label: "Tek", emblem: "TK" }; + } + if (/\bops\b|operations|opscon|ops\scon|operative|squad/.test(haystack)) { + return { key: "ops", label: "Ops", emblem: "OP" }; + } + return { key: "sla", label: "SLA", emblem: "SLA" }; + } + + getSlaSeverityMeta({ severity = null, tableName = "", specialRoll = "", tableResultNumber = null } = {}) { + const rawSeverity = Number(severity); + if (Number.isFinite(rawSeverity)) { + if (rawSeverity >= 5) return { key: "critical", label: "Critical", cssClass: "sla-severity-critical" }; + if (rawSeverity >= 3) return { key: "severe", label: "Severe", cssClass: "sla-severity-severe" }; + return { key: "minor", label: "Minor", cssClass: "sla-severity-minor" }; + } + + const name = String(tableName ?? "").toLowerCase(); + const special = String(specialRoll ?? "").toLowerCase(); + const numericResult = Number(tableResultNumber); + + if (!Number.isFinite(numericResult)) { + return { key: "minor", label: "Minor", cssClass: "sla-severity-minor" }; + } + + const isPanic = special === "paniccheck" || name.includes("panic"); + const isWound = name.includes("wound"); + + if (isPanic) { + if (numericResult >= 16) return { key: "critical", label: "Catastrophic", cssClass: "sla-severity-critical" }; + if (numericResult >= 9) return { key: "severe", label: "Severe", cssClass: "sla-severity-severe" }; + return { key: "minor", label: "Minor", cssClass: "sla-severity-minor" }; + } + + if (isWound) { + if (numericResult >= 8) return { key: "critical", label: "Critical", cssClass: "sla-severity-critical" }; + if (numericResult >= 4) return { key: "severe", label: "Severe", cssClass: "sla-severity-severe" }; + return { key: "minor", label: "Minor", cssClass: "sla-severity-minor" }; + } + + return { key: "minor", label: "Minor", cssClass: "sla-severity-minor" }; + } + + async addSlaActivityLog(entry, { maxEntries = 40 } = {}) { + const text = String(entry ?? "").trim(); + if (!text || this.type !== "character") return null; + + const timestamp = new Date().toLocaleString("en-GB", { + day: "2-digit", + month: "2-digit", + year: "2-digit", + hour: "2-digit", + minute: "2-digit" + }); + const currentLog = String(this.system?.sla?.activityLog?.value ?? "").trim(); + const existingLines = currentLog ? currentLog.split("\n").filter(Boolean) : []; + const nextLines = [`[${timestamp}] ${text}`, ...existingLines].slice(0, Math.max(1, maxEntries)); + await this.update({ "system.sla.activityLog.value": nextLines.join("\n") }); + return nextLines[0]; + } + //central roll parsing function | TAKES '1d100',[Foundry roll object],true,true,41,'<' | RETURNS enriched Foundry roll object parseRollResult(rollString, rollResult, zeroBased, checkCrit, rollTarget, comparison, specialRoll) { //init vars + rollTarget = this.clampSlaRollTarget(rollTarget, rollString); let doubles = new Set([0, 11, 22, 33, 44, 55, 66, 77, 88, 99]); let enrichedRollResult = rollResult; let rollFormula = enrichedRollResult.formula; @@ -711,29 +784,29 @@ export class MothershipActor extends Actor { if (rollString.includes("[")) { //set whether each die succeeded //die 0 - if (comparison === '<' && die0value < rollTarget && die0value < 90) { + if (comparison === '<' && die0value < rollTarget && !this.isSlaAutomaticFailure(die0value, rollString)) { die0success = true; } - if (comparison === '<=' && die0value <= rollTarget && die0value < 90) { + if (comparison === '<=' && die0value <= rollTarget && !this.isSlaAutomaticFailure(die0value, rollString)) { die0success = true; } - if (comparison === '>' && die0value > rollTarget && die0value < 90) { + if (comparison === '>' && die0value > rollTarget && !this.isSlaAutomaticFailure(die0value, rollString)) { die0success = true; } - if (comparison === '>=' && die0value >= rollTarget && die0value < 90) { + if (comparison === '>=' && die0value >= rollTarget && !this.isSlaAutomaticFailure(die0value, rollString)) { die0success = true; } //die 1 - if (comparison === '<' && die1value < rollTarget && die1value < 90) { + if (comparison === '<' && die1value < rollTarget && !this.isSlaAutomaticFailure(die1value, rollString)) { die1success = true; } - if (comparison === '<=' && die1value <= rollTarget && die1value < 90) { + if (comparison === '<=' && die1value <= rollTarget && !this.isSlaAutomaticFailure(die1value, rollString)) { die1success = true; } - if (comparison === '>' && die1value > rollTarget && die1value < 90) { + if (comparison === '>' && die1value > rollTarget && !this.isSlaAutomaticFailure(die1value, rollString)) { die1success = true; } - if (comparison === '>=' && die1value >= rollTarget && die1value < 90) { + if (comparison === '>=' && die1value >= rollTarget && !this.isSlaAutomaticFailure(die1value, rollString)) { die1success = true; } //set whether each die are a crit @@ -875,8 +948,8 @@ export class MothershipActor extends Actor { //add data point: detect success/failure if (rollTarget || rollTarget === 0) { //check for auto failure - if (enrichedRollResult.total >= 90) { - //result >= 90 is a failure + if (this.isSlaAutomaticFailure(enrichedRollResult.total, rollString)) { + // result 99 on d100 is always a failure enrichedRollResult.success = false; } else { //compare values based on compararison setting @@ -951,7 +1024,7 @@ export class MothershipActor extends Actor { compareIcon = ''; } //prepare formula - if (rollTarget) { + if (rollTarget || rollTarget === 0) { //show dice against target diceFormula = rollString + ' ' + compareIcon + ' ' + rollTarget; } else { @@ -1376,6 +1449,12 @@ export class MothershipActor extends Actor { } //generate chat message //prepare data + const severityMeta = this.getSlaSeverityMeta({ + tableName, + specialRoll, + tableResultNumber + }); + let messageData = { actor: this, tableResult: tableResult, @@ -1390,7 +1469,14 @@ export class MothershipActor extends Actor { woundText: woundText, secondRoll: secondRoll, parsedRollResult2: parsedRollResult2, - specialRoll: specialRoll + specialRoll: specialRoll, + severityMeta: severityMeta, + chatStyle: this.getSlaChatVariant({ + tableName, + msgHeader: tableName, + isSlaEbbSkill: false, + extra: specialRoll + }) }; //prepare template messageTemplate = 'systems/sla-mothership/templates/chat/rollTable.html'; @@ -1409,6 +1495,7 @@ export class MothershipActor extends Actor { }, { keepId: true }); + await this.addSlaActivityLog?.(`${tableName}: ${parsedRollResult.success ? "success" : "failure"} on ${rollString}${tableResultNumber !== null ? ` -> ${tableResultNumber}` : ""}.`); if (game.modules.get("dice-so-nice") && game.modules.get("dice-so-nice").active) { //log what was done slaDebug(`Rolled on table ID: ${tableId}, with: rollString:${rollString}, aimFor:${aimFor}, zeroBased:${zeroBased}, checkCrit:${checkCrit}, rollAgainst:${rollAgainst}, comparison:${comparison}`); @@ -1691,7 +1778,7 @@ export class MothershipActor extends Actor { window: {title: dlgTitle}, classes: ["macro-popup-dialog"], position: {width: 600}, - content: skillHeader + `
${skillList}
` + buttonDesc, + content: skillHeader + skillList + buttonDesc, buttons: [] }; //add adv/normal/dis buttons if we need a rollString @@ -2513,7 +2600,13 @@ export class MothershipActor extends Actor { targetingResult: targetingResult, needsDesc: needsDesc, woundEffect: woundEffect, - specialRoll: specialRoll + specialRoll: specialRoll, + chatStyle: this.getSlaChatVariant({ + weapon, + msgHeader, + isSlaEbbSkill: false, + extra: ammoContext?.label ?? "" + }) }; let chatData = { user: game.user.id, @@ -2528,6 +2621,7 @@ export class MothershipActor extends Actor { const content = await foundry.applications.handlebars.renderTemplate(template, messageData); chatData.content = content; await ChatMessage.create(chatData); + await this.addSlaActivityLog?.(`${weapon.name}: damage roll resolved.${targetingResult ? ` ${targetingResult.targetName} took ${targetingResult.finalDamage} damage.` : ""}`); //log what was done console.log(`Rolled damage on:${weapon.name}`); //return messageData @@ -2545,6 +2639,13 @@ export class MothershipActor extends Actor { } else { rollTarget = rollTargetOverride; } + rollTarget = this.clampSlaRollTarget(rollTarget, rollString); + if (effectiveRollBreakdown && (String(rollString ?? "").includes("1d100"))) { + effectiveRollBreakdown = { + ...effectiveRollBreakdown, + total: rollTarget + }; + } //roll the dice //parse the roll string let parsedRollString = this.parseRollString(rollString, aimFor); @@ -2707,9 +2808,9 @@ export class MothershipActor extends Actor { } if (ammoUsage) { const roundLabel = ammoUsage.shots === 1 ? 'round' : 'rounds'; - const ammoPrefix = `${ammoUsage.label} fire with ${ammoUsage.ammoLabel} spent ${ammoUsage.shots} ${roundLabel}. Magazine ${ammoUsage.loaded}/${weapon.system.shots}. Reserve ${ammoUsage.reserve}. Ammo cost ${ammoUsage.totalCost} cR. ${ammoUsage.ammoRule}`; + const ammoPrefix = `${ammoUsage.label} fire with ${ammoUsage.ammoLabel} spent ${ammoUsage.shots} ${roundLabel}. Magazine ${ammoUsage.loaded}/${weapon.system.shots}. Reserve ${ammoUsage.reserve}. Firing spend ${ammoUsage.totalCost} cR. ${ammoUsage.ammoRule}`; const accountingText = ammoAccounting - ? ` Credits now ${ammoAccounting.newCredits} cR. Running ammo spend ${ammoAccounting.spendTotal} cR.` + ? ` Credits now ${ammoAccounting.newCredits} cR. Running firing spend ${ammoAccounting.spendTotal} cR.` : ""; flavorText = flavorText ? `${ammoPrefix}${accountingText}
${flavorText}` : `${ammoPrefix}${accountingText}`; } @@ -2999,7 +3100,14 @@ export class MothershipActor extends Actor { targetingResult: targetingResult, firstEdition: game.settings.get('sla-mothership', 'firstEdition'), useCalm: game.settings.get('sla-mothership', 'useCalm'), - androidPanic: game.settings.get('sla-mothership', 'androidPanic') + androidPanic: game.settings.get('sla-mothership', 'androidPanic'), + chatStyle: this.getSlaChatVariant({ + weapon, + skill, + skillCategory: String(attackContext?.skillCategory ?? "").trim(), + msgHeader, + isSlaEbbSkill + }) }; //prepare template messageTemplate = 'systems/sla-mothership/templates/chat/rollCheck.html'; @@ -3018,6 +3126,11 @@ export class MothershipActor extends Actor { }, { keepId: true }); + const skillLabel = String(skill ?? "").trim(); + const actionLabel = weapon + ? weapon.name + : (skillLabel ? `${attributeLabel} / ${skillLabel}` : attributeLabel); + await this.addSlaActivityLog?.(`${actionLabel}: ${parsedRollResult.success ? "success" : "failure"} (${parsedRollResult.total} vs ${rollTarget}).`); //is DSN active? if (game.modules.get("dice-so-nice") && game.modules.get("dice-so-nice").active) { //log what was done @@ -3040,18 +3153,6 @@ export class MothershipActor extends Actor { await this.rollTable("panicCheck", null, null, null, null, null, null); } } - // Activity log - if (this.type === "character" && specialRoll !== "damage") { - try { - const rollLabel = skill && skill !== "none" ? skill : (attribute ? String(attribute).charAt(0).toUpperCase() + String(attribute).slice(1) : "Roll"); - const resultNum = Number(parsedRollResult?.total ?? 0); - const outcome = parsedRollResult?.success - ? (parsedRollResult.critical ? "Critical success" : "Pass") - : (parsedRollResult?.critical ? "Critical failure" : "Fail"); - const critTag = parsedRollResult?.critical ? " ★" : ""; - await this.addSlaActivityLog(`${rollLabel}: rolled ${resultNum} vs ${rollTarget} — ${outcome}${critTag}`); - } catch (_) {} - } return [messageData]; } @@ -4812,57 +4913,12 @@ export class MothershipActor extends Actor { getSlaSpeciesMinimumWounds() { const species = String(this.system?.sla?.species?.value ?? "").trim(); - if (["Frother", "Shaktar", "Stormer 313 Malice", "Stormer 711 Xeno", "Stormer Vevaphon"].includes(species)) { + if (["Frother", "Shaktar", "Stormer 313 Malice", "Stormer 711 Xeno"].includes(species)) { return 3; } return 2; } - isSlaVevaphon() { - const species = String(this.system?.sla?.species?.value ?? "").trim(); - return species === "Stormer Vevaphon"; - } - - getSlaVevaphonInstability() { - return Math.max(0, Math.min(12, Number(this.system?.sla?.vevaphonInstability?.value ?? 0) || 0)); - } - - getSlaVevaphonMorphForm() { - return String(this.system?.sla?.morphForm?.value ?? "").trim() || "Brute Form"; - } - - async setSlaVevaphonMorphForm(formName) { - if (!this.isSlaVevaphon()) return null; - const validForms = ["Brute Form", "Stalker Form", "Raptor Form"]; - const resolved = validForms.find((entry) => entry === formName) ?? formName; - const currentInstability = this.getSlaVevaphonInstability(); - const newInstability = Math.min(12, currentInstability + 1); - await this.update({ - "system.sla.morphForm.value": resolved, - "system.sla.vevaphonInstability.value": newInstability - }); - const instabilityWarning = newInstability >= 10 - ? " ⚠ Instability critical — Morph Panic risk on failed Sanity save." - : newInstability >= 6 - ? " Instability elevated — minor morph effects active." - : ""; - ui.notifications.info(`${this.name} shifts to ${resolved}. Instability: ${newInstability}/12.${instabilityWarning}`); - return { morphForm: resolved, instability: newInstability }; - } - - async adjustSlaVevaphonInstability(delta = 1) { - if (!this.isSlaVevaphon()) return null; - const current = this.getSlaVevaphonInstability(); - const next = Math.max(0, Math.min(12, current + delta)); - await this.update({ "system.sla.vevaphonInstability.value": next }); - if (next >= 12) { - ui.notifications.warn(`${this.name} has reached maximum Instability (12). The Vevaphon is lost — immediate retirement or death.`); - } else if (next >= 10) { - ui.notifications.warn(`${this.name} — Instability ${next}/12. Morph Panic risk: failed Sanity save triggers a Morph Panic event.`); - } - return next; - } - async enforceSlaSpeciesWounds() { if (this.type !== "character") return null; const minimumWounds = this.getSlaSpeciesMinimumWounds(); @@ -4961,14 +5017,7 @@ export class MothershipActor extends Actor { return this.items .filter((item) => { const name = String(item.name ?? "").trim(); - if (item.type === "ability") { - // Vevaphon morph form abilities are NOT Ebb items — exclude them - if (item.system?.sla?.morphForm === true) return false; - // Also exclude by name so hand-placed morph abilities are safe even without the flag - const morphNames = ["Brute Form", "Stalker Form", "Raptor Form"]; - if (morphNames.includes(name)) return false; - return true; - } + if (item.type === "ability") return true; if (item.type !== "skill") return false; return name === "Formulate" || name === "Biofeedback" || name === "Ebb (Core)" || name.startsWith("Ebb "); }) @@ -5344,8 +5393,6 @@ export class MothershipActor extends Actor { if (shouldPanic) { await this.rollTable("panicCheck", null, null, null, null, null, null); } - const saveOutcome = saveResult ? (saveResult.success ? "Pass" : "Fail") : "n/a"; - await this.addSlaActivityLog(`Flux ${forcePanic ? "Panic forced" : "Save"} — Fear save ${saveOutcome}. Flux ${this.system?.sla?.flux?.value ?? 0}/${this.system?.sla?.flux?.max ?? 0}.${shouldPanic ? " Panic triggered." : ""}`); return { saveResult, state, panicTriggered: shouldPanic }; } @@ -5531,7 +5578,8 @@ export class MothershipActor extends Actor { insert: rollInsert, onlyDesc: true, swapNameDesc, - swapName + swapName, + severityMeta: this.getSlaSeverityMeta({ severity: item?.system?.severity }) }; const chatData = { diff --git a/module/item/item-sheet.js b/module/item/item-sheet.js index 00769f1..727eee7 100644 --- a/module/item/item-sheet.js +++ b/module/item/item-sheet.js @@ -1,5 +1,22 @@ import { SLADrugSystem } from "../sla-drug-system.js"; +function isAmmoPurchaseItem(itemData = {}) { + const system = itemData?.system ?? {}; + const sla = system?.sla ?? {}; + const name = String(itemData?.name ?? "").trim().toLowerCase(); + const category = String(sla.category ?? "").trim().toLowerCase(); + const calibre = String(sla.calibre ?? "").trim(); + const ammoTag = String(sla.ammoTag ?? "").trim(); + return category === "ammunition" || Boolean((calibre && ammoTag) || name.startsWith("ammo:")); +} + +function formatSlaTimestamp() { + return new Intl.DateTimeFormat(undefined, { + dateStyle: "short", + timeStyle: "short" + }).format(new Date()); +} + /** * Extend the basic ItemSheet with some very simple modifications * @extends {ItemSheet} @@ -128,11 +145,38 @@ export class MothershipItemSheet extends foundry.appv1.sheets.ItemSheet { async _updateObject(event, formData) { const updateData = foundry.utils.expandObject(formData); + const currentQty = Math.max(0, Number(this.item.system?.quantity ?? 0) || 0); if (Object.prototype.hasOwnProperty.call(updateData?.system ?? {}, "weight")) { const parsed = Number(updateData.system.weight ?? 0) || 0; updateData.system.weight = Math.round(parsed * 10) / 10; } await this.item.update(updateData, { diff: false }); + + const nextQtyRaw = updateData?.system?.quantity; + const nextQty = nextQtyRaw === undefined ? currentQty : Math.max(0, Number(nextQtyRaw ?? 0) || 0); + const increase = Math.max(0, nextQty - currentQty); + const actor = this.item.parent; + if (!increase || actor?.documentName !== "Actor") return; + + const unitCost = Number(updateData?.system?.cost ?? this.item.system?.cost ?? 0) || 0; + const total = Math.round(unitCost * increase * 100) / 100; + if (!total) return; + + const currentCredits = Number(actor.system?.credits?.value ?? 0) || 0; + const newCredits = Math.round((currentCredits - total) * 100) / 100; + const updateActorData = { "system.credits.value": newCredits }; + + if (isAmmoPurchaseItem(this.item.toObject())) { + const stamp = formatSlaTimestamp(); + const currentLedger = String(actor.system?.sla?.ammoLedger?.value ?? "").trim(); + const entry = `[${stamp}] PURCHASE | ${this.item.name ?? "Ammo"} | ${increase} round${increase === 1 ? "" : "s"} | -${total} cR | balance ${newCredits} cR`; + const ledgerLines = [entry, ...currentLedger.split("\n").filter(Boolean)].slice(0, 60); + updateActorData["system.sla.ammoLedger.value"] = ledgerLines.join("\n"); + } + + await actor.update(updateActorData); + ui.notifications.info(`${actor.name}: deducted ${total} cR for ${increase} more ${this.item.name ?? "item"}. Credits now ${newCredits} cR.`); + await actor.addSlaActivityLog?.(`Purchased +${increase} ${this.item.name ?? "item"} for ${total} cR. Credits now ${newCredits} cR.`); } } diff --git a/module/mosh.js b/module/mosh.js index 3915934..a554c46 100644 --- a/module/mosh.js +++ b/module/mosh.js @@ -175,16 +175,37 @@ Hooks.once('init', async function () { Hooks.on("renderActorDirectory", (app, html) => { installSlaSidebarButtons(app, html); + if (!game.user?.isGM) enforceSlaPlayerDirectoryRestrictions("actors", html); }); Hooks.on("renderSidebarTab", (app, html) => { const isActorsTab = app?.tabName === "actors" || app?.options?.id === "actors"; - if (!isActorsTab) return; - installSlaSidebarButtons(app, html); + if (isActorsTab) installSlaSidebarButtons(app, html); + if (!game.user?.isGM) { + hideSlaRestrictedSidebarTabs(); + enforceSlaPlayerSidebarTabRestrictions(app, html); + } + }); + + Hooks.on("renderSceneDirectory", (_app, html) => { + if (!game.user?.isGM) enforceSlaPlayerDirectoryRestrictions("scenes", html); + }); + + Hooks.on("renderJournalDirectory", (_app, html) => { + if (!game.user?.isGM) enforceSlaPlayerDirectoryRestrictions("journal", html); + }); + + Hooks.on("renderRollTableDirectory", (_app, html) => { + if (!game.user?.isGM) enforceSlaPlayerDirectoryRestrictions("tables", html); + }); + + Hooks.on("renderCompendiumDirectory", (_app, html) => { + if (!game.user?.isGM) enforceSlaPlayerDirectoryRestrictions("compendium", html); }); Hooks.on("renderCombatTracker", (app, html) => { installSlaCombatTrackerControls(app, html); + if (!game.user?.isGM) enforceSlaPlayerDirectoryRestrictions("combat", html); }); Hooks.on("updateCombat", async (combat, changed) => { @@ -247,6 +268,7 @@ Hooks.once("ready", async function () { } patchSlaInitiativeSystem(); + if (!game.user?.isGM) hideSlaRestrictedSidebarTabs(); }); @@ -1254,33 +1276,19 @@ export function formatCreditsNumber(num) { // SLA Industries Pause Screen Animation // ───────────────────────────────────────────────────────────────────────────── +let slaPauseRefreshHandle = null; + function injectSlaPauseAnimation() { - const pauseEl = document.querySelector('#pause'); - if (!pauseEl) return; - - // Don't inject twice - if (pauseEl.querySelector('.sla-pause-root')) return; - - // Aggressively hide ALL of Foundry's default pause content - // Hide every direct child that isn't our injected element - Array.from(pauseEl.children).forEach(child => { - child.style.setProperty('display', 'none', 'important'); - }); - // Also kill any descendant with animation or image content - pauseEl.querySelectorAll('figure, img, svg, h3, p, .fas, .fa-cog, canvas').forEach(el => { - el.style.setProperty('display', 'none', 'important'); - }); + document.body?.classList.add("sla-pause-active"); + if (document.querySelector('.sla-pause-root')) { + return; + } const svgHTML = `
- +
+ - @@ -1288,7 +1296,6 @@ function injectSlaPauseAnimation() { - @@ -1296,23 +1303,17 @@ function injectSlaPauseAnimation() { - - - - - - - + - + - - @@ -1339,9 +1338,8 @@ function injectSlaPauseAnimation() { - - + - - - - - + - SLA - - - - STAND BY - - + >PLEASE STAND BY SLA INDUSTRIES - - +
`; + document.body?.insertAdjacentHTML('beforeend', svgHTML); +} - // Inject directly into the pause element (not into figure, which we hid) - pauseEl.insertAdjacentHTML('beforeend', svgHTML); +function removeSlaPauseAnimation() { + document.querySelector(".sla-pause-root")?.remove(); + document.body?.classList.remove("sla-pause-active"); +} + +function queueSlaPauseRefresh(delay = 0) { + window.clearTimeout(slaPauseRefreshHandle); + slaPauseRefreshHandle = window.setTimeout(() => { + if (!game?.paused) return; + injectSlaPauseAnimation(); + }, delay); } Hooks.once('ready', () => { - // Inject if game is already paused on load - if (game.paused) injectSlaPauseAnimation(); -}); - -// Use the pauseGame hook exclusively — don't use a MutationObserver -// which could fight Foundry's own show/hide logic on the #pause element. -Hooks.on('pauseGame', (paused) => { - if (paused) { - // Two-frame delay — Foundry renders the pause figure asynchronously - requestAnimationFrame(() => requestAnimationFrame(() => injectSlaPauseAnimation())); - } else { - // On unpause: remove our injected content so it can be re-injected cleanly next time - const slaRoot = document.querySelector('#pause .sla-pause-root'); - if (slaRoot) slaRoot.remove(); - // Restore Foundry's original pause children (they were hidden inline, unhide them) - const pauseEl = document.querySelector('#pause'); - if (pauseEl) { - pauseEl.querySelectorAll(':scope > *:not(.sla-pause-root)').forEach(child => { - child.style.removeProperty('display'); - }); - } + if (game.paused) { + queueSlaPauseRefresh(0); + queueSlaPauseRefresh(60); + queueSlaPauseRefresh(180); } }); +Hooks.on('pauseGame', (paused) => { + if (paused) { + requestAnimationFrame(() => requestAnimationFrame(() => injectSlaPauseAnimation())); + queueSlaPauseRefresh(0); + queueSlaPauseRefresh(60); + queueSlaPauseRefresh(180); + queueSlaPauseRefresh(360); + } else { + removeSlaPauseAnimation(); + } +}); + +function getSlaHtmlRoot(html) { + return html instanceof HTMLElement ? html : html?.[0] ?? null; +} + +function hideSlaRestrictedSidebarTabs() { + [ + "#sidebar-tabs [data-tab='tables']", + "#sidebar-tabs [aria-controls='tables']", + "#sidebar-tabs [data-tab='compendium']", + "#sidebar-tabs [aria-controls='compendium']" + ].forEach((selector) => { + document.querySelectorAll(selector).forEach((el) => { + el.closest("li")?.remove(); + if (el instanceof HTMLElement) el.remove(); + }); + }); +} + +function removeSlaElements(root, selectors = []) { + selectors.forEach((selector) => { + root.querySelectorAll(selector).forEach((el) => el.remove()); + }); +} + +function pruneEmptySlaFolders(root) { + const folders = Array.from(root.querySelectorAll(".directory-item.folder")); + folders.reverse().forEach((folder) => { + const visibleDocuments = Array.from(folder.querySelectorAll(".directory-item.document, li.document")).filter((node) => node.offsetParent !== null); + const visibleFolders = Array.from(folder.querySelectorAll(":scope > .directory-list > .directory-item.folder, :scope > ol > li.folder")).filter((node) => node.offsetParent !== null); + if (!visibleDocuments.length && !visibleFolders.length) folder.remove(); + }); +} + +function shouldShowSlaPlayerJournal(entry) { + if (!entry) return false; + const name = String(entry.name ?? "").trim(); + if (/^operative handbook$/i.test(name)) return true; + if (entry.getFlag?.("sla-mothership", "playerVisible") === true) return true; + return entry.testUserPermission?.(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER) ?? false; +} + +function filterSlaJournalDirectory(root) { + const rows = Array.from(root.querySelectorAll("[data-entry-id], [data-document-id]")); + rows.forEach((row) => { + const id = row.dataset.entryId ?? row.dataset.documentId; + if (!id) return; + const entry = game.journal?.get?.(id); + if (!shouldShowSlaPlayerJournal(entry)) row.remove(); + }); + pruneEmptySlaFolders(root); +} + +function enforceSlaPlayerDirectoryRestrictions(kind, html) { + const root = getSlaHtmlRoot(html); + if (!root || game.user?.isGM) return; + + removeSlaElements(root, [ + ".directory-header .header-actions", + ".directory-header .action-buttons", + ".sla-sidebar-actions", + ".qbm-scene-directory-actions", + ".qbm-launch", + ".sla-world-tools-launch", + ".sla-create-operative", + ".sla-create-random-operative", + ".sla-refresh-content", + ".sla-gear-cache-open", + ".sla-briefing-desk-open", + ".sla-bpn-dispatch-open", + ".sla-npc-director-open", + ".sla-npc-director-actor-open", + ".sla-ops-clock-open", + ".npp-generate-sidebar-btn" + ]); + + if (kind === "journal") { + filterSlaJournalDirectory(root); + } + + if (kind === "tables" || kind === "compendium") { + root.innerHTML = ""; + } +} + +function enforceSlaPlayerSidebarTabRestrictions(app, html) { + const tabName = app?.tabName ?? app?.options?.id ?? ""; + if (tabName === "actors") return enforceSlaPlayerDirectoryRestrictions("actors", html); + if (tabName === "scenes") return enforceSlaPlayerDirectoryRestrictions("scenes", html); + if (tabName === "journal") return enforceSlaPlayerDirectoryRestrictions("journal", html); + if (tabName === "tables") return enforceSlaPlayerDirectoryRestrictions("tables", html); + if (tabName === "compendium") return enforceSlaPlayerDirectoryRestrictions("compendium", html); + if (tabName === "combat") return enforceSlaPlayerDirectoryRestrictions("combat", html); +} + // ───────────────────────────────────────────────────────────────────────────── // SLA Industries — UI Icon Injection // Replaces Foundry's Font Awesome icons in the sidebar and scene controls diff --git a/packs/conditions_0e/000062.log b/packs/conditions_0e/000076.log similarity index 100% rename from packs/conditions_0e/000062.log rename to packs/conditions_0e/000076.log diff --git a/packs/conditions_0e/CURRENT b/packs/conditions_0e/CURRENT index ebafc63..d2ea14c 100644 --- a/packs/conditions_0e/CURRENT +++ b/packs/conditions_0e/CURRENT @@ -1 +1 @@ -MANIFEST-000061 +MANIFEST-000075 diff --git a/packs/conditions_0e/LOG b/packs/conditions_0e/LOG index b1a5f08..261434a 100644 --- a/packs/conditions_0e/LOG +++ b/packs/conditions_0e/LOG @@ -1,3 +1,3 @@ -2026/04/10-15:21:30.814878 16f8db000 Recovering log #60 -2026/04/10-15:21:30.815292 16f8db000 Delete type=0 #60 -2026/04/10-15:21:30.815311 16f8db000 Delete type=3 #59 +2026/04/14-21:44:03.701190 170dcb000 Recovering log #73 +2026/04/14-21:44:03.701635 170dcb000 Delete type=3 #71 +2026/04/14-21:44:03.701777 170dcb000 Delete type=0 #73 diff --git a/packs/conditions_0e/LOG.old b/packs/conditions_0e/LOG.old index a6d36eb..146d54c 100644 --- a/packs/conditions_0e/LOG.old +++ b/packs/conditions_0e/LOG.old @@ -1,3 +1,7 @@ -2026/04/10-15:15:40.939578 17164b000 Recovering log #58 -2026/04/10-15:15:40.939865 17164b000 Delete type=0 #58 -2026/04/10-15:15:40.939883 17164b000 Delete type=3 #57 +2026/04/14-14:29:42.528096 1725ef000 Recovering log #70 +2026/04/14-14:29:42.528525 1725ef000 Delete type=0 #70 +2026/04/14-14:29:42.528564 1725ef000 Delete type=3 #69 +2026/04/14-21:41:04.230515 172707000 Level-0 table #74: started +2026/04/14-21:41:04.230654 172707000 Level-0 table #74: 0 bytes OK +2026/04/14-21:41:04.230837 172707000 Delete type=0 #72 +2026/04/14-21:41:04.231926 172707000 Manual compaction at level-0 from '!items!1r6ruvSdxn2bHHIk' @ 72057594037927935 : 1 .. '!items!zbKI2RCl5h3vowoy' @ 0 : 0; will stop at (end) diff --git a/packs/conditions_0e/MANIFEST-000061 b/packs/conditions_0e/MANIFEST-000075 similarity index 83% rename from packs/conditions_0e/MANIFEST-000061 rename to packs/conditions_0e/MANIFEST-000075 index 537bb9b09f4f0eb6f90083ee17c289b4cd8ac0d8..ebc3835cbd2f8a105f35f9b23ee5d50a6a713ac4 100644 GIT binary patch delta 22 dcmbQqIFoTg7k|m>cYiq;7@2%H8JK-pJOEj!24nyL delta 22 dcmbQqIFoTg7yrL=3*tB!7@6!i8JO)^JOEd+1_%HE diff --git a/packs/conditions_1e/000062.log b/packs/conditions_1e/000076.log similarity index 100% rename from packs/conditions_1e/000062.log rename to packs/conditions_1e/000076.log diff --git a/packs/conditions_1e/CURRENT b/packs/conditions_1e/CURRENT index ebafc63..d2ea14c 100644 --- a/packs/conditions_1e/CURRENT +++ b/packs/conditions_1e/CURRENT @@ -1 +1 @@ -MANIFEST-000061 +MANIFEST-000075 diff --git a/packs/conditions_1e/LOG b/packs/conditions_1e/LOG index ae14291..d380f18 100644 --- a/packs/conditions_1e/LOG +++ b/packs/conditions_1e/LOG @@ -1,3 +1,3 @@ -2026/04/10-15:21:30.816908 16e0b7000 Recovering log #60 -2026/04/10-15:21:30.817298 16e0b7000 Delete type=0 #60 -2026/04/10-15:21:30.817326 16e0b7000 Delete type=3 #59 +2026/04/14-21:44:03.703209 171de3000 Recovering log #73 +2026/04/14-21:44:03.703506 171de3000 Delete type=3 #71 +2026/04/14-21:44:03.703538 171de3000 Delete type=0 #73 diff --git a/packs/conditions_1e/LOG.old b/packs/conditions_1e/LOG.old index 48efe1b..0736811 100644 --- a/packs/conditions_1e/LOG.old +++ b/packs/conditions_1e/LOG.old @@ -1,3 +1,7 @@ -2026/04/10-15:15:40.941390 170e3f000 Recovering log #58 -2026/04/10-15:15:40.941631 170e3f000 Delete type=0 #58 -2026/04/10-15:15:40.941649 170e3f000 Delete type=3 #57 +2026/04/14-14:29:42.530946 171de3000 Recovering log #70 +2026/04/14-14:29:42.531303 171de3000 Delete type=0 #70 +2026/04/14-14:29:42.531327 171de3000 Delete type=3 #69 +2026/04/14-21:41:04.231427 172707000 Level-0 table #74: started +2026/04/14-21:41:04.231573 172707000 Level-0 table #74: 0 bytes OK +2026/04/14-21:41:04.231822 172707000 Delete type=0 #72 +2026/04/14-21:41:04.231960 172707000 Manual compaction at level-0 from '!items!0ZAwhewvLBwX3opf' @ 72057594037927935 : 1 .. '!items!z0avrplKe6M0RcXk' @ 0 : 0; will stop at (end) diff --git a/packs/conditions_1e/MANIFEST-000061 b/packs/conditions_1e/MANIFEST-000075 similarity index 83% rename from packs/conditions_1e/MANIFEST-000061 rename to packs/conditions_1e/MANIFEST-000075 index 06d240e345af33183779e40e0609032954423872..15c94ee812827958555a9a0333698e8e2e8a3f71 100644 GIT binary patch delta 22 dcmbQqIFoTg7rzDDuWAkkMkXIl24-KD6aYuQ1t|ak delta 22 dcmbQqIFoTg7yl-$mIoXRj7)Z%49xZ{DF9Cn1)2Z= diff --git a/packs/items_maintenance_1e/000062.log b/packs/items_maintenance_1e/000076.log similarity index 100% rename from packs/items_maintenance_1e/000062.log rename to packs/items_maintenance_1e/000076.log diff --git a/packs/items_maintenance_1e/CURRENT b/packs/items_maintenance_1e/CURRENT index ebafc63..d2ea14c 100644 --- a/packs/items_maintenance_1e/CURRENT +++ b/packs/items_maintenance_1e/CURRENT @@ -1 +1 @@ -MANIFEST-000061 +MANIFEST-000075 diff --git a/packs/items_maintenance_1e/LOG b/packs/items_maintenance_1e/LOG index 5acdb97..a3567c1 100644 --- a/packs/items_maintenance_1e/LOG +++ b/packs/items_maintenance_1e/LOG @@ -1,3 +1,3 @@ -2026/04/10-15:21:30.818908 16f0cf000 Recovering log #60 -2026/04/10-15:21:30.819298 16f0cf000 Delete type=0 #60 -2026/04/10-15:21:30.819316 16f0cf000 Delete type=3 #59 +2026/04/14-21:44:03.705381 1725ef000 Recovering log #73 +2026/04/14-21:44:03.705808 1725ef000 Delete type=3 #71 +2026/04/14-21:44:03.705977 1725ef000 Delete type=0 #73 diff --git a/packs/items_maintenance_1e/LOG.old b/packs/items_maintenance_1e/LOG.old index 59129a3..fa6dc6a 100644 --- a/packs/items_maintenance_1e/LOG.old +++ b/packs/items_maintenance_1e/LOG.old @@ -1,3 +1,7 @@ -2026/04/10-15:15:40.942933 17164b000 Recovering log #58 -2026/04/10-15:15:40.943347 17164b000 Delete type=0 #58 -2026/04/10-15:15:40.943366 17164b000 Delete type=3 #57 +2026/04/14-14:29:42.533049 170dcb000 Recovering log #70 +2026/04/14-14:29:42.533339 170dcb000 Delete type=0 #70 +2026/04/14-14:29:42.533362 170dcb000 Delete type=3 #69 +2026/04/14-21:41:04.229609 172707000 Level-0 table #74: started +2026/04/14-21:41:04.229909 172707000 Level-0 table #74: 0 bytes OK +2026/04/14-21:41:04.230444 172707000 Delete type=0 #72 +2026/04/14-21:41:04.231906 172707000 Manual compaction at level-0 from '!items!1HNvL7XEUcMQbLps' @ 72057594037927935 : 1 .. '!items!zRzb3Laiuw4ac6p0' @ 0 : 0; will stop at (end) diff --git a/packs/items_maintenance_1e/MANIFEST-000061 b/packs/items_maintenance_1e/MANIFEST-000075 similarity index 83% rename from packs/items_maintenance_1e/MANIFEST-000061 rename to packs/items_maintenance_1e/MANIFEST-000075 index 902aabcc4bd0e5f24af45947626bcdc1fdc5668f..d7b78bb6c7146669167c87af0a4e0df989e69c23 100644 GIT binary patch delta 23 ecmbQmIE!&Ymq67Lt>v5yj7&b949vbPCl~=zHwDfB delta 23 fcmbQmIE!&Ym%#L!8XGtn7@6!i8JO)^PA~!hR?`Lx diff --git a/packs/macros_hotbar_0e/000062.log b/packs/macros_hotbar_0e/000076.log similarity index 100% rename from packs/macros_hotbar_0e/000062.log rename to packs/macros_hotbar_0e/000076.log diff --git a/packs/macros_hotbar_0e/CURRENT b/packs/macros_hotbar_0e/CURRENT index ebafc63..d2ea14c 100644 --- a/packs/macros_hotbar_0e/CURRENT +++ b/packs/macros_hotbar_0e/CURRENT @@ -1 +1 @@ -MANIFEST-000061 +MANIFEST-000075 diff --git a/packs/macros_hotbar_0e/LOG b/packs/macros_hotbar_0e/LOG index 138a272..46d709f 100644 --- a/packs/macros_hotbar_0e/LOG +++ b/packs/macros_hotbar_0e/LOG @@ -1,3 +1,3 @@ -2026/04/10-15:21:30.820489 16f0cf000 Recovering log #60 -2026/04/10-15:21:30.820764 16f0cf000 Delete type=0 #60 -2026/04/10-15:21:30.820786 16f0cf000 Delete type=3 #59 +2026/04/14-21:44:03.707592 170dcb000 Recovering log #73 +2026/04/14-21:44:03.708035 170dcb000 Delete type=3 #71 +2026/04/14-21:44:03.708118 170dcb000 Delete type=0 #73 diff --git a/packs/macros_hotbar_0e/LOG.old b/packs/macros_hotbar_0e/LOG.old index 63901a4..4f05c38 100644 --- a/packs/macros_hotbar_0e/LOG.old +++ b/packs/macros_hotbar_0e/LOG.old @@ -1,3 +1,7 @@ -2026/04/10-15:15:40.944425 171e57000 Recovering log #58 -2026/04/10-15:15:40.944787 171e57000 Delete type=0 #58 -2026/04/10-15:15:40.944804 171e57000 Delete type=3 #57 +2026/04/14-14:29:42.534696 1725ef000 Recovering log #70 +2026/04/14-14:29:42.534932 1725ef000 Delete type=0 #70 +2026/04/14-14:29:42.534950 1725ef000 Delete type=3 #69 +2026/04/14-21:41:04.230890 172707000 Level-0 table #74: started +2026/04/14-21:41:04.231026 172707000 Level-0 table #74: 0 bytes OK +2026/04/14-21:41:04.231365 172707000 Delete type=0 #72 +2026/04/14-21:41:04.231944 172707000 Manual compaction at level-0 from '!macros!2xRvRAixuocjCm40' @ 72057594037927935 : 1 .. '!macros!ilBclUmPwcd7aNQK' @ 0 : 0; will stop at (end) diff --git a/packs/macros_hotbar_0e/MANIFEST-000061 b/packs/macros_hotbar_0e/MANIFEST-000075 similarity index 65% rename from packs/macros_hotbar_0e/MANIFEST-000061 rename to packs/macros_hotbar_0e/MANIFEST-000075 index 5240d7584652f7b3fa13a31e8e980f75e372e4f5..2442f2f3b29c8f2143df86d42685292df4daf55b 100644 GIT binary patch delta 22 dcmbQkIEQgUFaPWL4v8ELj7&b949vbPLI6-s1y}$8 delta 22 dcmbQkIEQgUFMnf=-yaSJMkYH>24;H}AplRD1y}$8 diff --git a/packs/macros_hotbar_1e/000062.log b/packs/macros_hotbar_1e/000076.log similarity index 100% rename from packs/macros_hotbar_1e/000062.log rename to packs/macros_hotbar_1e/000076.log diff --git a/packs/macros_hotbar_1e/CURRENT b/packs/macros_hotbar_1e/CURRENT index ebafc63..d2ea14c 100644 --- a/packs/macros_hotbar_1e/CURRENT +++ b/packs/macros_hotbar_1e/CURRENT @@ -1 +1 @@ -MANIFEST-000061 +MANIFEST-000075 diff --git a/packs/macros_hotbar_1e/LOG b/packs/macros_hotbar_1e/LOG index 931a35e..74aa1c1 100644 --- a/packs/macros_hotbar_1e/LOG +++ b/packs/macros_hotbar_1e/LOG @@ -1,3 +1,3 @@ -2026/04/10-15:21:30.821825 16e0b7000 Recovering log #60 -2026/04/10-15:21:30.822206 16e0b7000 Delete type=0 #60 -2026/04/10-15:21:30.822238 16e0b7000 Delete type=3 #59 +2026/04/14-21:44:03.709360 171de3000 Recovering log #73 +2026/04/14-21:44:03.709675 171de3000 Delete type=3 #71 +2026/04/14-21:44:03.709706 171de3000 Delete type=0 #73 diff --git a/packs/macros_hotbar_1e/LOG.old b/packs/macros_hotbar_1e/LOG.old index 8e5f9fa..dbbf01f 100644 --- a/packs/macros_hotbar_1e/LOG.old +++ b/packs/macros_hotbar_1e/LOG.old @@ -1,3 +1,7 @@ -2026/04/10-15:15:40.946179 170e3f000 Recovering log #58 -2026/04/10-15:15:40.946914 170e3f000 Delete type=0 #58 -2026/04/10-15:15:40.946937 170e3f000 Delete type=3 #57 +2026/04/14-14:29:42.536078 171de3000 Recovering log #70 +2026/04/14-14:29:42.536336 171de3000 Delete type=0 #70 +2026/04/14-14:29:42.536359 171de3000 Delete type=3 #69 +2026/04/14-21:41:04.232630 172707000 Level-0 table #74: started +2026/04/14-21:41:04.232740 172707000 Level-0 table #74: 0 bytes OK +2026/04/14-21:41:04.232976 172707000 Delete type=0 #72 +2026/04/14-21:41:04.234457 172707000 Manual compaction at level-0 from '!macros!2u4a5gEogtLd3XQz' @ 72057594037927935 : 1 .. '!macros!trU0DtBJvJCPddsQ' @ 0 : 0; will stop at (end) diff --git a/packs/macros_hotbar_1e/MANIFEST-000061 b/packs/macros_hotbar_1e/MANIFEST-000075 similarity index 82% rename from packs/macros_hotbar_1e/MANIFEST-000061 rename to packs/macros_hotbar_1e/MANIFEST-000075 index b9561f966fac4093b9d6946f2faa09e2d4f5b316..7c81c4e07569ca1e4419df93a2f67dff43013ced 100644 GIT binary patch delta 22 dcmbQkIEQgUFaJLkR$~qZMkXIl24-ItF#tu51d9Lw delta 22 dcmbQkIEQgUFMkPVxfBNjBa> diff --git a/packs/macros_triggered_1e/000064.log b/packs/macros_triggered_1e/000078.log similarity index 100% rename from packs/macros_triggered_1e/000064.log rename to packs/macros_triggered_1e/000078.log diff --git a/packs/macros_triggered_1e/CURRENT b/packs/macros_triggered_1e/CURRENT index e8c0266..c7a124b 100644 --- a/packs/macros_triggered_1e/CURRENT +++ b/packs/macros_triggered_1e/CURRENT @@ -1 +1 @@ -MANIFEST-000063 +MANIFEST-000077 diff --git a/packs/macros_triggered_1e/LOG b/packs/macros_triggered_1e/LOG index 2051e05..1bf42e2 100644 --- a/packs/macros_triggered_1e/LOG +++ b/packs/macros_triggered_1e/LOG @@ -1,3 +1,3 @@ -2026/04/10-15:21:30.992926 16f8db000 Recovering log #62 -2026/04/10-15:21:30.993656 16f8db000 Delete type=0 #62 -2026/04/10-15:21:30.993730 16f8db000 Delete type=3 #61 +2026/04/14-21:44:03.713901 1725ef000 Recovering log #75 +2026/04/14-21:44:03.714351 1725ef000 Delete type=0 #75 +2026/04/14-21:44:03.714384 1725ef000 Delete type=3 #73 diff --git a/packs/macros_triggered_1e/LOG.old b/packs/macros_triggered_1e/LOG.old index 6d552d1..a57ff02 100644 --- a/packs/macros_triggered_1e/LOG.old +++ b/packs/macros_triggered_1e/LOG.old @@ -1,3 +1,7 @@ -2026/04/10-15:15:40.966053 17164b000 Recovering log #60 -2026/04/10-15:15:40.967356 17164b000 Delete type=0 #60 -2026/04/10-15:15:40.967388 17164b000 Delete type=3 #59 +2026/04/14-14:29:42.538470 1725ef000 Recovering log #72 +2026/04/14-14:29:42.538719 1725ef000 Delete type=3 #71 +2026/04/14-14:29:42.538747 1725ef000 Delete type=0 #72 +2026/04/14-21:41:04.233774 172707000 Level-0 table #76: started +2026/04/14-21:41:04.233930 172707000 Level-0 table #76: 0 bytes OK +2026/04/14-21:41:04.234304 172707000 Delete type=0 #74 +2026/04/14-21:41:04.234513 172707000 Manual compaction at level-0 from '!macros!06NSvDYTtmkeUtLq' @ 72057594037927935 : 1 .. '!macros!zLRsweb35Lw0NMev' @ 0 : 0; will stop at (end) diff --git a/packs/macros_triggered_1e/MANIFEST-000063 b/packs/macros_triggered_1e/MANIFEST-000077 similarity index 81% rename from packs/macros_triggered_1e/MANIFEST-000063 rename to packs/macros_triggered_1e/MANIFEST-000077 index 2c618664fe0beeb5bb51a077febde999c95f0806..a3f62cc0e42b6ac259b4a6c14009a68d14199623 100644 GIT binary patch delta 23 ecmbQoIFE5cpFk0lbu=dfBazDvtW(HdT diff --git a/packs/rolltables_0e/000062.log b/packs/rolltables_0e/000076.log similarity index 100% rename from packs/rolltables_0e/000062.log rename to packs/rolltables_0e/000076.log diff --git a/packs/rolltables_0e/CURRENT b/packs/rolltables_0e/CURRENT index ebafc63..d2ea14c 100644 --- a/packs/rolltables_0e/CURRENT +++ b/packs/rolltables_0e/CURRENT @@ -1 +1 @@ -MANIFEST-000061 +MANIFEST-000075 diff --git a/packs/rolltables_0e/LOG b/packs/rolltables_0e/LOG index 9682b85..1ba6a86 100644 --- a/packs/rolltables_0e/LOG +++ b/packs/rolltables_0e/LOG @@ -1,3 +1,3 @@ -2026/04/10-15:21:30.995778 16e8c3000 Recovering log #60 -2026/04/10-15:21:30.996525 16e8c3000 Delete type=0 #60 -2026/04/10-15:21:30.996566 16e8c3000 Delete type=3 #59 +2026/04/14-21:44:03.716230 1715d7000 Recovering log #73 +2026/04/14-21:44:03.716588 1715d7000 Delete type=3 #71 +2026/04/14-21:44:03.716645 1715d7000 Delete type=0 #73 diff --git a/packs/rolltables_0e/LOG.old b/packs/rolltables_0e/LOG.old index 364944e..c2aeca3 100644 --- a/packs/rolltables_0e/LOG.old +++ b/packs/rolltables_0e/LOG.old @@ -1,3 +1,7 @@ -2026/04/10-15:15:40.968762 17164b000 Recovering log #58 -2026/04/10-15:15:40.968983 17164b000 Delete type=0 #58 -2026/04/10-15:15:40.969099 17164b000 Delete type=3 #57 +2026/04/14-14:29:42.540072 170dcb000 Recovering log #70 +2026/04/14-14:29:42.540350 170dcb000 Delete type=0 #70 +2026/04/14-14:29:42.540376 170dcb000 Delete type=3 #69 +2026/04/14-21:41:04.233064 172707000 Level-0 table #74: started +2026/04/14-21:41:04.233246 172707000 Level-0 table #74: 0 bytes OK +2026/04/14-21:41:04.233613 172707000 Delete type=0 #72 +2026/04/14-21:41:04.234486 172707000 Manual compaction at level-0 from '!tables!1vCm4ElRPotQXgNB' @ 72057594037927935 : 1 .. '!tables.results!kqKpQAXyLTEEyz6Z.xdwwswzfoPuVifvw' @ 0 : 0; will stop at (end) diff --git a/packs/rolltables_0e/MANIFEST-000061 b/packs/rolltables_0e/MANIFEST-000075 similarity index 92% rename from packs/rolltables_0e/MANIFEST-000061 rename to packs/rolltables_0e/MANIFEST-000075 index a035233aa97d7e0cf3fdb6970fb7f77b90b1a2ee..55cbbdc72076e0c8c7672c083058de7a3e112155 100644 GIT binary patch delta 23 fcmcb>c!6=k0f8IO!kIZ47@2%H8JK-pE-(TBV>c!6=k0fDbmBY8L(7@6!i8JO)^E-(TBU^NC@ diff --git a/packs/rolltables_1e/000062.log b/packs/rolltables_1e/000076.log similarity index 100% rename from packs/rolltables_1e/000062.log rename to packs/rolltables_1e/000076.log diff --git a/packs/rolltables_1e/CURRENT b/packs/rolltables_1e/CURRENT index ebafc63..d2ea14c 100644 --- a/packs/rolltables_1e/CURRENT +++ b/packs/rolltables_1e/CURRENT @@ -1 +1 @@ -MANIFEST-000061 +MANIFEST-000075 diff --git a/packs/rolltables_1e/LOG b/packs/rolltables_1e/LOG index 4321787..0505a84 100644 --- a/packs/rolltables_1e/LOG +++ b/packs/rolltables_1e/LOG @@ -1,3 +1,3 @@ -2026/04/10-15:21:30.998700 16e8c3000 Recovering log #60 -2026/04/10-15:21:30.999246 16e8c3000 Delete type=0 #60 -2026/04/10-15:21:30.999287 16e8c3000 Delete type=3 #59 +2026/04/14-21:44:03.718522 170dcb000 Recovering log #73 +2026/04/14-21:44:03.718938 170dcb000 Delete type=3 #71 +2026/04/14-21:44:03.718979 170dcb000 Delete type=0 #73 diff --git a/packs/rolltables_1e/LOG.old b/packs/rolltables_1e/LOG.old index c4ef3d3..bf97735 100644 --- a/packs/rolltables_1e/LOG.old +++ b/packs/rolltables_1e/LOG.old @@ -1,3 +1,7 @@ -2026/04/10-15:15:40.970322 172663000 Recovering log #58 -2026/04/10-15:15:40.970657 172663000 Delete type=0 #58 -2026/04/10-15:15:40.970680 172663000 Delete type=3 #57 +2026/04/14-14:29:42.542090 1715d7000 Recovering log #70 +2026/04/14-14:29:42.542385 1715d7000 Delete type=0 #70 +2026/04/14-14:29:42.542403 1715d7000 Delete type=3 #69 +2026/04/14-21:41:04.234643 172707000 Level-0 table #74: started +2026/04/14-21:41:04.234976 172707000 Level-0 table #74: 0 bytes OK +2026/04/14-21:41:04.235710 172707000 Delete type=0 #72 +2026/04/14-21:41:04.240761 172707000 Manual compaction at level-0 from '!tables!31YibfjueXuZdNLb' @ 72057594037927935 : 1 .. '!tables.results!ypcoikqHLhnc9tNs.zJkYrssfrdWBSeRm' @ 0 : 0; will stop at (end) diff --git a/packs/rolltables_1e/MANIFEST-000061 b/packs/rolltables_1e/MANIFEST-000075 similarity index 92% rename from packs/rolltables_1e/MANIFEST-000061 rename to packs/rolltables_1e/MANIFEST-000075 index d05bdd51502d78f05f611436fce66fd2f007d243..a76fd5712dc5a0e12f3e89ba0c6614df8093f21e 100644 GIT binary patch delta 23 ecmcb>c!6=k0Rb5U4rfjVMkXIl24-KDZ7cvwjs*z- delta 23 ecmcb>c!6=k0Rh9moIackj7)Z%49xZ{+gJctyam|+ diff --git a/templates/actor/actor-sheet.html b/templates/actor/actor-sheet.html index a69f3b3..6df6f39 100644 --- a/templates/actor/actor-sheet.html +++ b/templates/actor/actor-sheet.html @@ -12,7 +12,7 @@ from 1 to 12 and will create that number of columns. --}}
-
@@ -32,6 +32,10 @@ L.A.D.
+
+
 
+ +
{{ localize 'Mosh.Credits' }}
-
-
S.C.L
+
+
S.C.L.
-
+
Height
-
+
Weight
-
-
 
- -
-
@@ -206,16 +205,14 @@
{{/if}} -
-
-
{{ localize 'Mosh.TraumaResponse' }}
-
- - -
+
+
{{ localize 'Mosh.TraumaResponse' }}
+
+ +
@@ -390,12 +387,11 @@ {{ localize 'Mosh.Skills' }} {{ localize 'Mosh.Weapons' }} {{ localize 'Mosh.Items' }} + Drugs SLA Traits Special Rules {{#if isEbbUser}}Ebb{{/if}} - {{#if isVevaphon}}Morph{{/if}} - Contacts {{ localize 'Mosh.Bio' }} {{ localize 'Mosh.Notes' }} @@ -403,48 +399,6 @@ {{!-- Sheet Body --}}
- {{!-- Contacts Tab --}} -
-
-
-
-
Operative Network
-
Contacts & Associates
-
-
- {{slaContactCount}} contact{{#unless (compare 'int' slaContactCount '===' 1)}}s{{/unless}} -
-
- -
- -
- - {{#if slaContacts.length}} -
- {{#each slaContacts as |contact|}} -
-
-
-
{{contact.name}}
-
{{contact.role}}
-
- - -
-
- {{#if contact.notes}} -
{{contact.notes}}
- {{/if}} -
- {{/each}} -
- {{else}} -
No contacts yet. Add allies, fixers, squad members, and other NPCs the operative knows.
- {{/if}} -
-
- {{!-- Biography Tab --}}
{{editor enriched.biography target="system.biography" button=true editable=true}} @@ -452,14 +406,14 @@ {{!-- Notes Tab --}}
-
Ammo Spend Ledger
-
Total spent: {{system.sla.ammoSpendTotal.value}} cR
- +
Operation Log
+
Recent rolls, outcomes, and field actions for this operative.
+
-
-
Operative Activity Log
-
Rolls, rests, drug use, panic — automatically recorded. Newest first.
- +
+
Ammo Economy Ledger
+
Firing spend total: {{system.sla.ammoSpendTotal.value}} cR. Purchases are logged below as separate entries.
+
{{editor enriched.notes target="system.notes" button=true editable=true}}
@@ -474,7 +428,6 @@ {{system.sla.bpn.value}} SCL {{system.sla.scl.value}} {{#if isEbbUser}}Ebb {{system.sla.ebbRating.value}}{{/if}} - {{#if isVevaphon}}Instability {{system.sla.vevaphonInstability.value}}/12{{/if}}
@@ -539,15 +492,7 @@
-
- -
- - - - -
-
+
@@ -885,112 +830,19 @@
{{/if}} - {{#if isVevaphon}} -
-
-
-
-
Stormer Vevaphon
-
Polymorphic Form Control
-
-
- Form: {{system.sla.morphForm.value}} - Instability {{system.sla.vevaphonInstability.value}} / 12 - Sanity {{system.stats.sanity.value}} -
-
- -
-
-
Instability Track
-
-
-
{{system.sla.vevaphonInstability.value}} / 12
-
-
- {{#if vevaphonInstabilityCritical}}⚠ CRITICAL — Morph Panic risk on every failed Sanity save.{{/if}} - {{#if vevaphonInstabilityElevated}}Instability elevated — minor morph effects trigger each scene.{{/if}} - {{#unless vevaphonInstabilityElevated}}Instability nominal. Morphing is available without additional risk.{{/unless}} -
-
- - -
-
At 12, the Vevaphon is lost — immediate retirement or death.
-
-
-
Active Morph Form
-
{{system.sla.morphForm.value}}
-
Each morph shift costs 1 Instability. Changing form mid-scene requires a Sanity save at Instability 6+.
-
- - - -
-
-
- -
-
-
Morph Panic Reference
-
Triggered at Instability 10+ on a failed Sanity save. Roll 1d6:
-
-
1. Cascade Shift — random morph form, +1 Instability.
-
2. Identity Bleed — forgets name and callsign until end of scene.
-
3. Partial Lock — one limb freezes. -10 physical rolls using that limb.
-
4. Tissue Rejection — 1d5 damage, armour does not apply.
-
5. Threat Imprint — locked on nearest visible target.
-
6. Full Reversion — base form, lose morph bonuses, -1 Instability.
-
-
-
-
Morph Notes
- -
-
-
-
- {{/if}} {{!-- Owned Items Tab --}}
- {{#if drugAlerts.length}} -
- {{#each drugAlerts as |alert id|}} -
-
-
{{alert.name}}
-
- {{alert.stateLabel}} - {{#if alert.duration}}{{alert.duration}}{{/if}} -
-
-
{{alert.summary}}
- -
- {{/each}} -
- {{/if}} - {{#if system.settings.firstEdition}}
  1. {{ localize 'Mosh.ArmorName' }}
    -
    {{ localize 'Mosh.AP' }}
    -
    {{ localize 'Mosh.DR' }}
    -
    {{ localize 'Mosh.Speed' }}
    -
    {{ localize 'Mosh.Oxygen' }}
    -
    Carry
    +
    {{ localize 'Mosh.AP' }}
    +
    {{ localize 'Mosh.DR' }}
    +
    {{ localize 'Mosh.Speed' }}
    +
    {{ localize 'Mosh.Oxygen' }}
    +
    Carry
    @@ -1027,10 +879,10 @@
  2. {{ localize 'Mosh.ArmorName' }}
    -
    {{ localize 'Mosh.SaveBonus' }}
    -
    {{ localize 'Mosh.Speed' }}
    -
    {{ localize 'Mosh.Oxygen' }}
    -
    Carry
    +
    {{ localize 'Mosh.SaveBonus' }}
    +
    {{ localize 'Mosh.Speed' }}
    +
    {{ localize 'Mosh.Oxygen' }}
    +
    Carry
    @@ -1062,57 +914,15 @@
{{/if}} - -
    -
  1. -
    -
    Drug
    -
    Qty
    -
    Rating
    -
    Value
    -
    Weight
    -
    Carry
    -
    State
    -
    Duration
    -
    - Add -
    -
  2. - {{#each drugRows as |drug id|}} -
  3. -
    -
    {{drug.name}}{{drug.summary}}
    -
    {{drug.quantity}}
    -
    {{drug.rating}}
    -
    {{drug.cost}}
    -
    {{drug.slaCarry.totalWeight}} kg
    -
    -
    - - -
    -
    -
    {{drug.statusLabel}}
    -
    {{drug.duration}}
    -
    - - - - -
    -
  4. - {{/each}} -
-
  1. -
    {{ localize 'Mosh.ItemName' }}
    -
    {{ localize 'Mosh.Quantity' }}
    -
    {{ localize 'Mosh.Weight' }}
    -
    Carry
    -
    {{ localize 'Mosh.Value' }}
    +
    {{ localize 'Mosh.ItemName' }}
    +
    {{ localize 'Mosh.Quantity' }}
    +
    {{ localize 'Mosh.Weight' }}
    +
    Carry
    +
    {{ localize 'Mosh.Value' }}
    @@ -1144,6 +954,66 @@
+
+ {{#if drugAlerts.length}} +
+ {{#each drugAlerts as |alert id|}} +
+
+
{{alert.name}}
+
+ {{alert.stateLabel}} + {{#if alert.duration}}{{alert.duration}}{{/if}} +
+
+
{{alert.summary}}
+ +
+ {{/each}} +
+ {{/if}} + +
    +
  1. +
    +
    Drug
    +
    Qty
    +
    Rating
    +
    Value
    +
    Weight
    +
    Carry
    +
    State
    +
    Duration
    +
    + Add +
    +
  2. + {{#each drugRows as |drug id|}} +
  3. +
    +
    {{drug.name}}{{drug.summary}}
    +
    {{drug.quantity}}
    +
    {{drug.rating}}
    +
    {{drug.cost}}
    +
    {{drug.slaCarry.totalWeight}} kg
    +
    +
    + + +
    +
    +
    {{drug.statusLabel}}
    +
    {{drug.duration}}
    +
    + + + + +
    +
  4. + {{/each}} +
+
{{!-- Owned Skills Tab --}}
@@ -1154,10 +1024,10 @@
  1. -
    {{ localize 'Mosh.SkillName' }}
    -
    Category
    -
    {{ localize 'Mosh.SkillRank' }}
    -
    {{ localize 'Mosh.SkillBonus' }}
    +
    {{ localize 'Mosh.SkillName' }}
    +
    Category
    +
    {{ localize 'Mosh.SkillRank' }}
    +
    {{ localize 'Mosh.SkillBonus' }}
    @@ -1216,7 +1086,7 @@
  2. {{condition.name}}
    -
    {{condition.system.severity}}
    +
    {{condition.system.severity}}
    {{#if condition.system.treatment.html}}
    @@ -1244,7 +1114,7 @@
    Weapons Readiness
    -
    Ammo stacks: {{slaAmmoInventory.stackCount}} | Total loose rounds: {{slaAmmoInventory.roundCount}} | Running ammo spend: {{system.sla.ammoSpendTotal.value}} cR
    +
    Ammo stacks: {{slaAmmoInventory.stackCount}} | Total loose rounds: {{slaAmmoInventory.roundCount}} | Running firing spend: {{system.sla.ammoSpendTotal.value}} cR
    Carry: {{slaCarry.current}} / {{slaCarry.capacity}} kg | {{slaCarry.state}}
    @@ -1254,13 +1124,13 @@
    1. -
      {{ localize 'Mosh.WeaponName' }}
      -
      {{ localize 'Mosh.Damage' }}
      -
      {{ localize 'Mosh.Ammo' }}
      -
      {{ localize 'Mosh.Shots' }}
      -
      Mode
      -
      Carry
      -
      {{ localize 'Mosh.Range' }}
      +
      {{ localize 'Mosh.WeaponName' }}
      +
      {{ localize 'Mosh.Damage' }}
      +
      {{ localize 'Mosh.Ammo' }}
      +
      {{ localize 'Mosh.Shots' }}
      +
      Mode
      +
      Carry
      +
      {{ localize 'Mosh.Range' }}
      diff --git a/templates/actor/creature-sheet.html b/templates/actor/creature-sheet.html index 550fd56..7226eb1 100644 --- a/templates/actor/creature-sheet.html +++ b/templates/actor/creature-sheet.html @@ -391,7 +391,7 @@
    2. {{condition.name}}
      -
      {{condition.system.severity}}
      +
      {{condition.system.severity}}
      {{#if condition.system.treatment.html}}
      diff --git a/templates/chat/itemRoll.html b/templates/chat/itemRoll.html index 18733a6..dd94692 100644 --- a/templates/chat/itemRoll.html +++ b/templates/chat/itemRoll.html @@ -1,88 +1,97 @@ -
      -
      - {{#if swapNameDesc}} -
      {{swapName}}
      - {{else}} - {{#if (compare 'int' item.name.length '>=' 22)}} -
      {{item.name}}
      - {{else}} -
      {{item.name}}
      - {{/if}} - {{/if}} - {{#if (compare 'str' item.type '===' 'ability')}} - {{#if (compare 'str' actor.img '===' 'icons/svg/mystery-man.svg')}} -
      - {{else}} -
      - {{/if}} - {{else}} -
      - {{/if}} -
      +
      +
      +
      +
      Item Detail
      +
      + {{#if (compare 'str' item.system.sla.impact '===' 'heal')}} + HLG + {{else if item.system.roll}} + ATK + {{else if (compare 'str' item.type '===' 'ability')}} + ABL + {{else}} + ITM + {{/if}} +
      +
      + {{#if swapNameDesc}} +
      {{swapName}}
      + {{else}} + {{#if (compare 'int' item.name.length '>=' 22)}} +
      {{item.name}}
      + {{else}} +
      {{item.name}}
      + {{/if}} + {{/if}} +
      +
      + {{#if (compare 'str' item.type '===' 'ability')}} + {{#if (compare 'str' actor.img '===' 'icons/svg/mystery-man.svg')}} + + {{else}} + + {{/if}} + {{else}} + + {{/if}} +
      +
      + {{#if item.system.roll}} -
      +
      {{#if (compare 'str' item.system.sla.impact '===' 'heal')}}
      You restore [[{{item.system.roll}}[damage]]] points of healing.
      {{else}}
      You inflict [[{{item.system.roll}}[damage]]] points of damage.
      {{/if}}
      -
      -
      -
      {{/if}} + {{#if item.system.severity}} -
      -
      {{ localize 'Mosh.Severity' }}: {{item.system.severity}}
      +
      +
      + {{severityMeta.label}} +
      +
      {{ localize 'Mosh.Severity' }}: {{item.system.severity}}
      -
      -
      -
      {{/if}} + {{#if item.system.armorPoints}} -
      -
      {{ localize 'Mosh.ArmorPoints' }}: {{item.system.armorPoints}}
      +
      +
      {{ localize 'Mosh.ArmorPoints' }}: {{item.system.armorPoints}}
      -
      -
      -
      {{/if}} + {{#if item.system.damageReduction}} -
      -
      {{ localize 'Mosh.DMGReduction' }}: {{item.system.damageReduction}}
      +
      +
      {{ localize 'Mosh.DMGReduction' }}: {{item.system.damageReduction}}
      -
      -
      -
      {{/if}} + {{#if swapNameDesc}} -
      +
      {{{item.name}}}
      -
      {{else if (compare 'str' item.system.type '===' 'repair')}} -
      +
      {{#if item.system.major}}
      {{ localize 'Mosh.MajorRepair' }}
      {{else}}
      {{ localize 'Mosh.MinorRepair' }}
      {{/if}}
      -
      -
      -
      -
      +
      {{{item.name}}}
      -
      {{else}} -
      +
      {{{item.system.description}}}
      -
      {{/if}}
      +
      diff --git a/templates/chat/modifyActor.html b/templates/chat/modifyActor.html index d25639f..cb38cf5 100644 --- a/templates/chat/modifyActor.html +++ b/templates/chat/modifyActor.html @@ -1,28 +1,32 @@ -
      -
      - {{#if (compare 'int' msgHeader.length '>=' 22)}} -
      {{{msgHeader}}}
      - {{else}} -
      {{{msgHeader}}}
      - {{/if}} -
      +
      +
      +
      Actor Update
      + {{#if (compare 'int' msgHeader.length '>=' 22)}} +
      {{{msgHeader}}}
      + {{else}} +
      {{{msgHeader}}}
      + {{/if}} +
      +
      + +
      -
      +
      {{{msgFlavor}}} {{#if modRollString}} -
      - {{{parsedRollResult.rollHtml}}} - {{else}} -

      +
      {{{parsedRollResult.rollHtml}}}
      {{/if}} {{{msgOutcome}}} {{#if halfDamage}} -

      @UUID[Compendium.mosh.macros_hotbar_0e.eyGvbBi1BhWSgfUP]{Make a Panic Check} +
      +
      @UUID[Compendium.mosh.macros_hotbar_0e.eyGvbBi1BhWSgfUP]{Make a Panic Check}
      +
      {{/if}}
      -
      \ No newline at end of file +
      diff --git a/templates/chat/modifyItem.html b/templates/chat/modifyItem.html index fe61dbd..297eced 100644 --- a/templates/chat/modifyItem.html +++ b/templates/chat/modifyItem.html @@ -1,16 +1,26 @@ -
      -
      - {{#if (compare 'int' msgHeader.length '>=' 22)}} -
      {{{msgHeader}}}
      - {{else}} -
      {{{msgHeader}}}
      - {{/if}} -
      +
      +
      +
      +
      Item Update
      +
      + UPD +
      +
      + {{#if (compare 'int' msgHeader.length '>=' 22)}} +
      {{{msgHeader}}}
      + {{else}} +
      {{{msgHeader}}}
      + {{/if}} +
      +
      + +
      -
      +
      {{{flavorText}}}
      -
      \ No newline at end of file +
      diff --git a/templates/chat/reload.html b/templates/chat/reload.html index 16f644c..b5d628d 100644 --- a/templates/chat/reload.html +++ b/templates/chat/reload.html @@ -1,17 +1,26 @@ -
      -
      - {{#if (compare 'int' item.name.length '>=' 22)}} -
      {{{item.name}}}
      - {{else}} -
      {{{item.name}}}
      - {{/if}} -
      +
      +
      +
      +
      Reload Action
      +
      + RLD +
      +
      + {{#if (compare 'int' item.name.length '>=' 22)}} +
      {{{item.name}}}
      + {{else}} +
      {{{item.name}}}
      + {{/if}} +
      +
      + +
      -
      +
      {{{msgBody}}}
      -
      -
      \ No newline at end of file +
      diff --git a/templates/chat/rollCheck.html b/templates/chat/rollCheck.html index b36d0a3..41f396d 100644 --- a/templates/chat/rollCheck.html +++ b/templates/chat/rollCheck.html @@ -1,38 +1,58 @@ -
      -
      - {{#if (compare 'int' msgHeader.length '>=' 22)}} -
      {{{msgHeader}}}
      - {{else}} -
      {{{msgHeader}}}
      - {{/if}} -
      +
      +
      +
      +
      {{#if (compare 'str' specialRoll '===' 'damage')}}Attack Roll{{else if isSlaEbbSkill}}Ebb Check{{else}}Normal Roll{{/if}}
      +
      + {{#if (compare 'str' specialRoll '===' 'damage')}} + ATK + {{else if isSlaEbbSkill}} + EBB + {{else}} + CHK + {{/if}} +
      {{chatStyle.emblem}}{{chatStyle.label}}
      +
      +
      + {{#if (compare 'int' msgHeader.length '>=' 22)}} +
      {{{msgHeader}}}
      + {{else}} +
      {{{msgHeader}}}
      + {{/if}} +
      +
      + +
      + {{#if (compare 'str' specialRoll '!==' 'damage')}} - {{{parsedRollResult.outcomeHtml}}} -
      -
      - {{#if (compare 'int' parsedRollResult.total '>=' 90)}} - {{ localize 'Mosh.YouRolled90OrHigher' }} - {{else}} - {{#if isSlaEbbSkill}} +
      + {{#if (compare 'int' parsedRollResult.total '>=' 90)}} + {{ localize 'Mosh.YouRolled90OrHigher' }} + {{else}} + {{#if isSlaEbbSkill}} {{#if (compare 'int' skillValue '>' 0)}} You {{{outcomeVerb}}} {{{comparisonText}}} your {{{attribute}}} plus the {{{skill}}} discipline. {{else}} You {{{outcomeVerb}}} {{{comparisonText}}} your {{{attribute}}} while channeling {{{skill}}}. {{/if}} - {{else}} + {{else}} {{#if (compare 'int' skillValue '>' 0)}} You {{{outcomeVerb}}} {{{comparisonText}}} your {{{attribute}}} plus {{{skill}}} skill bonus. {{else}} You {{{outcomeVerb}}} {{{comparisonText}}} your {{{attribute}}}. {{/if}} - {{/if}} {{/if}} -
      + {{/if}}
      - {{{parsedRollResult.rollHtml}}} + +
      + {{{parsedRollResult.outcomeHtml}}} + {{{parsedRollResult.rollHtml}}} +
      + {{#if isSlaEbbSkill}}
      Ebb Discipline
      @@ -51,78 +71,50 @@
      {{/if}} {{/if}} -
      + {{#if (compare 'str' specialRoll '===' 'damage')}} -
      {{{flavorText}}}
      +
      {{{flavorText}}}
      {{#if needsDesc}} -
      -
      -
      -
      DESCRIPTION
      -
      -
      -
      -
      +
      Description
      +
      {{{weapon.system.description}}}
      {{#if woundEffect}} -
      -
      {{ localize 'Mosh.WoundEffect' }} -
      - {{{woundEffect}}} +
      + {{ localize 'Mosh.WoundEffect' }} +
      {{/if}} - {{/if}}
      -
      + {{/if}} {{else}} {{#if parsedRollResult.success}} + {{#if flavorText}}
      {{{flavorText}}}
      {{/if}} + {{#if criticalSuccessOptions.length}} +
      +
      Critical Success Options
      +
        + {{#each criticalSuccessOptions as |option|}} +
      1. {{option}}
      2. + {{/each}} +
      +
      + {{/if}} {{#if weapon}} -
      {{{flavorText}}}
      - {{#if criticalSuccessOptions.length}} -
      -
      Critical Success Options
      -
        - {{#each criticalSuccessOptions as |option|}} -
      1. {{option}}
      2. - {{/each}} -
      -
      - {{/if}} {{#if needsDesc}} -
      -
      -
      -
      {{ localize 'Mosh.Description' }}
      -
      -
      -
      -
      +
      {{ localize 'Mosh.Description' }}
      +
      {{{weapon.system.description}}}
      {{#if woundEffect}} -
      -
      {{ localize 'Mosh.WoundEffect' }} -
      - {{{woundEffect}}} +
      + {{ localize 'Mosh.WoundEffect' }} +
      {{/if}} - {{/if}} -
      -
      - {{else}} -
      {{{flavorText}}}
      - {{#if criticalSuccessOptions.length}} -
      -
      Critical Success Options
      -
        - {{#each criticalSuccessOptions as |option|}} -
      1. {{option}}
      2. - {{/each}} -
      {{/if}} {{/if}} {{else}} - {{#if flavorText}}
      {{{flavorText}}}
      {{/if}} + {{#if flavorText}}
      {{{flavorText}}}
      {{/if}} {{#if criticalFailureTable}}
      {{criticalFailureTable.label}}
      @@ -132,15 +124,18 @@
      {{/if}} {{#if critFail}} -

      @UUID[Compendium.mosh.macros_hotbar_1e.IgWvaabVqzKDzLL4]{Make a Panic Check} +
      +
      @UUID[Compendium.mosh.macros_hotbar_1e.IgWvaabVqzKDzLL4]{Make a Panic Check}
      +
      {{/if}} {{/if}} {{/if}} + {{#if targetingResult}}
      Target Resolution
      {{targetingResult.targetName}} took {{targetingResult.finalDamage}} damage.
      -
      Rolled {{targetingResult.rawDamage}}. Armour reduced {{targetingResult.armourReduced}}{{#if targetingResult.armourPiercing}} after AP/anti-armour effects{{/if}}.
      +
      Rolled {{targetingResult.rawDamage}}. Armour reduced {{targetingResult.armourReduced}}{{#if targetingResult.armourPiercing}} after AP or anti-armour effects{{/if}}.
      {{#if targetingResult.additionalTargets}}
      {{targetingResult.additionalTargets}} additional targeted token(s) were ignored for this automatic application.
      {{/if}} @@ -152,6 +147,5 @@ {{/if}}
      {{/if}} -
      diff --git a/templates/chat/rollTable.html b/templates/chat/rollTable.html index 11e1d4a..f7ba0f3 100644 --- a/templates/chat/rollTable.html +++ b/templates/chat/rollTable.html @@ -1,17 +1,45 @@ -
      -
      -
      {{{tableName}}}
      -
      +
      +
      +
      +
      Table Roll
      +
      + {{#if (compare 'str' specialRoll '===' 'panicCheck')}} + PNC + {{else if (compare 'str' tableName '===' 'Gunshot Wound')}} + WND + {{else if (compare 'str' tableName '===' 'Bleeding Wound')}} + WND + {{else if (compare 'str' tableName '===' 'Fire & Explosives Wound')}} + WND + {{else if (compare 'str' tableName '===' 'Gore & Massive Wound')}} + WND + {{else}} + TBL + {{/if}} +
      {{chatStyle.emblem}}{{chatStyle.label}}
      +
      +
      +
      {{{tableName}}}
      +
      +
      + +
      - {{{parsedRollResult.outcomeHtml}}} -
      + +
      {{{msgDesc}}}
      - {{{parsedRollResult.rollHtml}}} + +
      + {{{parsedRollResult.outcomeHtml}}} + {{{parsedRollResult.rollHtml}}} +
      + {{#if secondRoll}} -
      +
      {{#if (compare 'str' specialRoll '===' 'panicCheck')}}
      {{ localize 'Mosh.RollingForPanicEffect' }}
      {{/if}} @@ -19,34 +47,57 @@
      {{ localize 'Mosh.RollingForMaintenanceIssue' }}
      {{/if}}
      - {{{parsedRollResult2.rollHtml}}} +
      + {{{parsedRollResult2.rollHtml}}} +
      {{/if}} + {{#if parsedRollResult.success}} -
      {{{flavorText}}}
      +
      {{{flavorText}}}
      {{else}} {{#if woundText}} - {{{woundText}}}

      +
      +
      + {{severityMeta.label}} +
      + {{{woundText}}} +
      {{/if}} {{#if (compare 'str' tableResultType '===' 'document')}} -
        - {{#each tableResult}} -
      1. - - -
      2. - {{/each}} -
      +
      +
      Linked Results
      +
        + {{#each tableResult}} +
      1. + + +
      2. + {{/each}} +
      +
      {{/if}} {{#if (compare 'str' tableResultType '===' 'text')}} {{#if tableResultEdited}} -
      {{{tableResultEdited}}}{{{tableResultFooter}}}
      +
      +
      + {{severityMeta.label}} +
      + {{{tableResultEdited}}}{{{tableResultFooter}}} +
      {{else}} -
      {{{tableResult.[0].description}}}{{{tableResultFooter}}}
      +
      +
      + {{severityMeta.label}} +
      + {{{tableResult.[0].description}}}{{{tableResultFooter}}} +
      {{/if}} {{/if}} {{#if (compare 'str' specialRoll '===' 'maintenanceCheck')}} {{#if critFail}} -

      @UUID[Compendium.mosh.macros_hotbar_1e.IgWvaabVqzKDzLL4]{Make a Panic Check} +
      +
      @UUID[Compendium.mosh.macros_hotbar_1e.IgWvaabVqzKDzLL4]{Make a Panic Check}
      +
      {{/if}} {{/if}} {{/if}}