diff --git a/assets/icons/creature-trait.svg b/assets/icons/creature-trait.svg new file mode 100644 index 0000000..a72cb72 --- /dev/null +++ b/assets/icons/creature-trait.svg @@ -0,0 +1,13 @@ + diff --git a/assets/ui/icons/resonation.svg b/assets/ui/icons/resonation.svg new file mode 100644 index 0000000..8663d01 --- /dev/null +++ b/assets/ui/icons/resonation.svg @@ -0,0 +1,13 @@ + diff --git a/css/mgne.css b/css/mgne.css index 141d5a5..1b8bebd 100644 --- a/css/mgne.css +++ b/css/mgne.css @@ -167,6 +167,39 @@ border: 1px solid rgba(196, 154, 69, 0.16); border-radius: calc(8px - 4px); } +.application.mgne fieldset .editor, +.application.mgne fieldset .editor-content, +.application.mgne fieldset .ProseMirror, +.application.mgne fieldset .document-editor { + color: #d9c6ae; + background: transparent; +} +.application.mgne fieldset .editor-content p, +.application.mgne fieldset .ProseMirror p, +.application.mgne fieldset .editor-content li, +.application.mgne fieldset .ProseMirror li { + color: #d9c6ae; +} +.application.mgne fieldset .editor-content h1, +.application.mgne fieldset .ProseMirror h1, +.application.mgne fieldset .editor-content h2, +.application.mgne fieldset .ProseMirror h2, +.application.mgne fieldset .editor-content h3, +.application.mgne fieldset .ProseMirror h3 { + color: #c49a45; +} +.application.mgne fieldset .editor-content .is-empty::before, +.application.mgne fieldset .ProseMirror .is-empty::before { + color: rgba(196, 154, 69, 0.4); +} +.application.mgne fieldset .editor-menu button { + color: #ab8b68; + background: rgba(19, 16, 15, 0.55); +} +.application.mgne fieldset .editor-menu button:hover { + color: #d9c6ae; + background: rgba(196, 154, 69, 0.2); +} .application.mgne legend { font-family: "CastorTwoMGNE", "Palatino Linotype", serif; text-transform: uppercase; @@ -337,10 +370,15 @@ min-width: 0; } .application.mgne .condition-value-grid { - grid-template-columns: max-content 1fr; + grid-template-columns: max-content minmax(auto, 9rem); align-items: center; margin-bottom: 0.6rem; } +.application.mgne .condition-value-grid select { + width: auto; + min-width: 5rem; + max-width: 9rem; +} .application.mgne .condition-flag-grid { grid-template-columns: repeat(auto-fill, minmax(7rem, 1fr)); gap: 0.3rem 0.6rem; @@ -375,7 +413,6 @@ .application.mgne .resource-box-compact > label { font-size: 0.68rem; letter-spacing: 0.11em; - color: #ab8b68; } .application.mgne .resource-box-inline { flex-direction: row; @@ -610,25 +647,6 @@ justify-content: flex-start; margin-top: 0.12rem; } -.application.mgne .resonation-blocked-banner { - display: flex; - align-items: center; - justify-content: space-between; - gap: 0.4rem; - margin-top: 0.2rem; - padding: 0.28rem 0.5rem; - border-radius: 4px; - background: rgba(180, 40, 20, 0.22); - border-left: 3px solid rgba(220, 80, 40, 0.7); - color: #e69062; - font-size: 0.73rem; -} -.application.mgne .resonation-blocked-banner .btn-clear-block { - flex-shrink: 0; - padding: 0.1rem 0.5rem; - font-size: 0.7rem; - cursor: pointer; -} .application.mgne .resource-box-actions-rest { gap: 0.32rem; flex-wrap: wrap; @@ -695,10 +713,22 @@ gap: 0.35rem; align-items: center; width: fit-content; + padding: 0.1rem 0.3rem 0.1rem 0.2rem; + border-radius: 4px; + transition: background 0.15s, color 0.15s; } .application.mgne .checkbox-line.active { - color: #dd6b2d; + color: #f5dfa0; font-weight: 600; + text-shadow: 0 0 8px rgba(196, 154, 69, 0.55); + background: rgba(196, 154, 69, 0.14); + border: 1px solid rgba(196, 154, 69, 0.28); + margin: -1px; +} +.application.mgne .checkbox-line:not(.active) { + color: #ab8b68; + border: 1px solid transparent; + margin: -1px; } .application.mgne .check-grid input[type="checkbox"], .application.mgne .checkbox-line input[type="checkbox"] { @@ -787,6 +817,572 @@ grid-template-columns: 1fr; } } +.item-weight-badge { + display: inline-flex; + align-items: center; + padding: 0 0.45em; + border-radius: 3px; + font-family: "LoraMGNE", "Book Antiqua", serif; + font-size: 0.7em; + font-weight: 700; + letter-spacing: 0.04em; + text-transform: uppercase; + white-space: nowrap; + border: 1px solid transparent; +} +.item-weight-badge.weight-trivial { + color: rgba(171, 139, 104, 0.45); + border-color: rgba(171, 139, 104, 0.2); +} +.item-weight-badge.weight-light { + color: #ab8b68; + border-color: rgba(171, 139, 104, 0.35); + background: rgba(171, 139, 104, 0.08); +} +.item-weight-badge.weight-normal { + color: #c49a45; + border-color: rgba(196, 154, 69, 0.4); + background: rgba(196, 154, 69, 0.1); +} +.item-weight-badge.weight-heavy { + color: #7f1d17; + border-color: rgba(127, 29, 23, 0.45); + background: rgba(127, 29, 23, 0.12); +} +.load-display { + font-weight: 700; + letter-spacing: 0.04em; + cursor: default; +} +.load-overloaded { + color: #7f1d17 !important; + text-shadow: 0 0 6px rgba(127, 29, 23, 0.5); +} +.resource-box-overloaded { + border-color: rgba(127, 29, 23, 0.6) !important; + background: rgba(127, 29, 23, 0.1) !important; +} +.resource-box-overloaded .resource-label-accent { + color: #7f1d17 !important; +} +.item-name[data-tooltip] { + cursor: help; +} +.item-name[data-tooltip]:hover { + text-decoration: underline dotted rgba(196, 154, 69, 0.55); +} +#tooltip.mgne-item-tooltip { + max-width: 22rem; + font-family: "LoraMGNE", "Book Antiqua", serif; + font-size: 0.82rem; + line-height: 1.5; + color: #ccb292; + background: rgba(51, 38, 30, 0.66); + border: 1px solid rgba(196, 154, 69, 0.35); + border-radius: 4px; + padding: 0.5rem 0.7rem; + box-shadow: 0 4px 14px rgba(0, 0, 0, 0.55); +} +.item-broken-badge { + display: inline-block; + margin-left: 0.3em; + padding: 0.05em 0.4em; + font-size: 0.65em; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.06em; + color: #7f1d17; + background: rgba(127, 29, 23, 0.14); + border: 1px solid rgba(127, 29, 23, 0.45); + border-radius: 3px; + vertical-align: middle; +} +.item-burned-badge { + color: #c49a45; + background: rgba(196, 154, 69, 0.12); + border-color: rgba(196, 154, 69, 0.4); +} +.item-durability-badge { + display: inline-flex; + align-items: center; + gap: 0.25em; + padding: 0.1em 0.45em; + font-size: 0.7em; + font-weight: 700; + color: rgba(171, 139, 104, 0.65); + border: 1px solid rgba(171, 139, 104, 0.2); + border-radius: 3px; + white-space: nowrap; + letter-spacing: 0.03em; +} +.item-durability-badge.durability-broken { + color: #7f1d17; + border-color: rgba(127, 29, 23, 0.4); + background: rgba(127, 29, 23, 0.1); + text-decoration: line-through; +} +.item-row-broken { + opacity: 0.6; + filter: grayscale(0.4); +} +.item-row-broken .item-name { + text-decoration: line-through; + color: rgba(204, 178, 146, 0.55); +} +.weapon-properties-grid { + display: flex; + flex-wrap: wrap; + gap: 0.35rem 0.6rem; + padding: 0.5rem 0.6rem; + background: rgba(171, 139, 104, 0.04); + border: 1px solid rgba(171, 139, 104, 0.12); + border-radius: 4px; + margin-bottom: 0.5rem; +} +.weapon-properties-grid .property-check { + display: flex; + align-items: center; + gap: 0.3em; + font-family: "LoraMGNE", "Book Antiqua", serif; + font-size: 0.82rem; + color: #ccb292; + cursor: pointer; + white-space: nowrap; + padding: 0.15em 0.4em; + border-radius: 3px; + transition: background 0.15s; +} +.weapon-properties-grid .property-check:hover { + background: rgba(196, 154, 69, 0.08); +} +.weapon-properties-grid .property-check input[type="checkbox"] { + accent-color: #c49a45; +} +.weapon-property-badge { + display: inline-block; + margin-left: 0.3em; + padding: 0.05em 0.35em; + font-size: 0.62em; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.05em; + color: rgba(196, 154, 69, 0.85); + background: rgba(196, 154, 69, 0.1); + border: 1px solid rgba(196, 154, 69, 0.3); + border-radius: 3px; + vertical-align: middle; +} +.creature-trait-trigger { + font-size: 0.82em; + color: rgba(204, 178, 146, 0.65); + font-style: italic; + margin-left: 0.2em; +} +.creature-traits-container { + border: 1px solid rgba(127, 29, 23, 0.35); + border-left: 3px solid rgba(127, 29, 23, 0.65); + border-radius: 4px; + background: linear-gradient(135deg, rgba(127, 29, 23, 0.05), transparent 60%), rgba(64, 47, 37, 0.85); + padding: 0.6rem 0.75rem; + display: flex; + flex-direction: column; + gap: 0.5rem; +} +.creature-traits-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.5rem; + border-bottom: 1px solid rgba(127, 29, 23, 0.25); + padding-bottom: 0.4rem; + margin-bottom: 0.1rem; +} +.creature-traits-header h3 { + margin: 0; + font-family: "CastorTwoMGNE", "Palatino Linotype", serif; + font-size: 0.82rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.08em; + color: rgba(127, 29, 23, 0.9); +} +.creature-traits-header button { + font-size: 0.72rem; + padding: 0.15em 0.6em; + background: rgba(127, 29, 23, 0.12); + border: 1px solid rgba(127, 29, 23, 0.35); + color: rgba(127, 29, 23, 0.8); + border-radius: 3px; + cursor: pointer; + transition: background 0.15s, color 0.15s; +} +.creature-traits-header button:hover { + background: rgba(127, 29, 23, 0.22); + color: #aa271f; +} +.creature-traits-list { + display: flex; + flex-direction: column; + gap: 0.3rem; +} +.creature-trait-card { + background: rgba(19, 16, 15, 0.3); + border: 1px solid rgba(127, 29, 23, 0.2); + border-radius: 3px; + padding: 0.35rem 0.5rem; + transition: background 0.15s, border-color 0.15s; +} +.creature-trait-card:hover { + background: rgba(127, 29, 23, 0.08); + border-color: rgba(127, 29, 23, 0.35); +} +.creature-trait-card-header { + display: flex; + align-items: center; + gap: 0.5rem; + flex-wrap: wrap; +} +.creature-trait-name { + display: flex; + align-items: center; + gap: 0.35em; + font-family: "LoraMGNE", "Book Antiqua", serif; + font-size: 0.88rem; + font-weight: 600; + color: #ccb292; + flex: 1; + min-width: 0; + cursor: default; +} +.creature-trait-name i { + color: rgba(127, 29, 23, 0.7); + font-size: 0.75em; +} +.creature-trait-trigger { + font-size: 0.75rem; + font-style: italic; + color: rgba(204, 178, 146, 0.55); + white-space: nowrap; +} +.creature-trait-card .item-actions { + display: flex; + gap: 0.25rem; + margin-left: auto; + flex-shrink: 0; +} +.creature-trait-card .item-actions button { + width: 1.6rem; + height: 1.6rem; + padding: 0; + display: flex; + align-items: center; + justify-content: center; + background: transparent; + border: 1px solid rgba(171, 139, 104, 0.18); + color: rgba(171, 139, 104, 0.5); + border-radius: 3px; + cursor: pointer; + font-size: 0.72rem; + transition: background 0.15s, color 0.15s, border-color 0.15s; +} +.creature-trait-card .item-actions button:hover { + background: rgba(171, 139, 104, 0.1); + color: #ab8b68; + border-color: rgba(171, 139, 104, 0.35); +} +.creature-meta-row { + display: flex; + align-items: center; + gap: 1rem; + flex-wrap: wrap; + padding: 0.45rem 0.6rem; + background: rgba(64, 47, 37, 0.5); + border: 1px solid rgba(183, 70, 31, 0.2); + border-radius: 4px; +} +.creature-meta-label { + font-family: "CastorTwoMGNE", "Palatino Linotype", serif; + font-size: 0.72rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.07em; + color: rgba(183, 70, 31, 0.8); + white-space: nowrap; +} +.creature-type-group { + display: flex; + align-items: center; + gap: 0.75rem; + flex: 1; +} +.creature-type-checkbox { + display: flex; + align-items: center; + gap: 0.3em; + font-size: 0.83rem; + color: #ccb292; + cursor: pointer; + user-select: none; +} +.creature-type-checkbox input[type="checkbox"] { + accent-color: #b7461f; + width: 0.9rem; + height: 0.9rem; + cursor: pointer; +} +.creature-number-group { + display: flex; + align-items: center; + gap: 0.4rem; + flex-shrink: 0; +} +.creature-number-input { + width: 4rem; + text-align: center; + font-family: "LoraMGNE", "Book Antiqua", serif; + font-size: 0.9rem; + color: #ccb292; + background: rgba(17, 12, 10, 0.72); + border: 1px solid rgba(183, 70, 31, 0.3); + border-radius: 3px; + padding: 0.15em 0.3em; +} +.creature-number-input:focus { + outline: none; + border-color: rgba(183, 70, 31, 0.6); + background: rgba(17, 12, 10, 0.9); +} +.creature-action-table-section .action-table-drop-zone { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.5rem 0.75rem; + border-radius: 4px; + min-height: 2.4rem; + font-size: 0.88rem; + color: #ccb292; +} +.creature-action-table-section .action-table-drop-zone i { + font-size: 1em; + color: #b7461f; +} +.creature-action-table-section .action-table-drop-zone .action-table-name { + font-weight: 600; + font-family: "CastorTwoMGNE", "Palatino Linotype", serif; + color: #ccb292; +} +.creature-action-table-section .action-table-drop-zone.drop-hint { + border: 2px dashed rgba(183, 70, 31, 0.35); + background: rgba(183, 70, 31, 0.04); + color: rgba(204, 178, 146, 0.5); + font-style: italic; + justify-content: center; + cursor: default; + transition: background 0.15s, border-color 0.15s; +} +.creature-action-table-section .action-table-drop-zone.drop-hint i { + color: rgba(183, 70, 31, 0.5); +} +.creature-action-table-section .action-table-drop-zone.drop-hint:hover, +.creature-action-table-section .action-table-drop-zone.drop-hint.drag-over { + background: rgba(183, 70, 31, 0.09); + border-color: rgba(183, 70, 31, 0.6); + color: rgba(204, 178, 146, 0.7); +} +.creature-action-table-section .action-table-drop-zone.drop-hint:hover i, +.creature-action-table-section .action-table-drop-zone.drop-hint.drag-over i { + color: #b7461f; +} +.creature-action-table-section .action-table-buttons { + display: flex; + gap: 0.3rem; + align-items: center; +} +.creature-action-table-section .action-table-buttons button { + font-size: 0.75rem; + padding: 0.15em 0.55em; + background: rgba(183, 70, 31, 0.1); + border: 1px solid rgba(183, 70, 31, 0.3); + color: rgba(183, 70, 31, 0.8); + border-radius: 3px; + cursor: pointer; + transition: background 0.15s, color 0.15s; + display: flex; + align-items: center; + gap: 0.3em; +} +.creature-action-table-section .action-table-buttons button:hover { + background: rgba(183, 70, 31, 0.22); + color: #b7461f; +} +.application.mgne.party .party-credits-row { + display: flex; + align-items: center; + gap: 0.6rem; + margin-top: 0.5rem; + padding: 0.4rem 0.6rem; + border: 1px solid rgba(196, 154, 69, 0.35); + border-radius: 6px; + background: rgba(17, 12, 10, 0.7); +} +.application.mgne.party .party-credits-stepper { + display: flex; + align-items: center; + gap: 0.3rem; +} +.application.mgne.party .credits-input { + width: 5rem; + text-align: center; + font-size: 1.05rem; + font-weight: 700; + font-family: "CastorTwoMGNE", "Palatino Linotype", serif; + color: #c49a45; + background: rgba(19, 16, 15, 0.6); + border: 1px solid rgba(196, 154, 69, 0.3); + border-radius: 6px; + padding: 0.2rem 0.4rem; +} +.application.mgne.party .credits-symbol { + color: #c49a45; + font-family: "CastorTwoMGNE", "Palatino Linotype", serif; + font-size: 1.1rem; + font-weight: 700; + margin: 0 0.1rem; +} +.application.mgne.party .credits-btn { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 2rem; + padding: 0.15rem 0.4rem; + font-size: 0.75rem; + font-weight: 700; + border: 1px solid rgba(171, 139, 104, 0.28); + border-radius: 6px; + background: rgba(17, 12, 10, 0.8); + color: #ab8b68; + cursor: pointer; + user-select: none; + transition: background 0.15s, border-color 0.15s; +} +.application.mgne.party .credits-btn:hover { + background: rgba(196, 154, 69, 0.18); + border-color: rgba(196, 154, 69, 0.55); + color: #c49a45; +} +.application.mgne.party .party-member-list { + display: flex; + flex-direction: column; + gap: 0.25rem; + margin-top: 0.4rem; +} +.application.mgne.party .party-member-row { + display: grid; + grid-template-columns: 2rem 1fr 5rem 6rem 5.5rem; + gap: 0.5rem; + align-items: center; + padding: 0.3rem 0.4rem; + border-radius: 6px; +} +.application.mgne.party .party-member-row.party-member-header { + color: #7e664f; + font-size: 0.72rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.05em; + border-bottom: 1px solid rgba(171, 139, 104, 0.18); + padding-bottom: 0.25rem; + margin-bottom: 0.1rem; +} +.application.mgne.party .party-member-row:not(.party-member-header) { + background: rgba(17, 12, 10, 0.75); + border: 1px solid transparent; +} +.application.mgne.party .party-member-row:not(.party-member-header):hover { + border-color: rgba(196, 154, 69, 0.22); + background: rgba(17, 12, 10, 0.9); +} +.application.mgne.party .party-member-portrait { + width: 2rem; + height: 2rem; + object-fit: cover; + border-radius: 3px; + border: 1px solid rgba(171, 139, 104, 0.25); +} +.application.mgne.party .party-member-name a { + color: #ccb292; + font-weight: 700; + font-size: 0.9rem; +} +.application.mgne.party .party-member-name a:hover { + color: #c49a45; +} +.application.mgne.party .party-member-hp { + color: #dd6b2d; + font-family: "CastorTwoMGNE", "Palatino Linotype", serif; + font-size: 0.82rem; + font-weight: 700; + text-align: center; +} +.application.mgne.party .party-member-type { + color: #7e664f; + font-size: 0.78rem; + font-style: italic; +} +.application.mgne.party .party-loot-list { + display: flex; + flex-direction: column; + gap: 0.25rem; + margin-top: 0.4rem; +} +.application.mgne.party .party-loot-row { + display: grid; + grid-template-columns: 2rem 1fr 7rem 4rem; + gap: 0.5rem; + align-items: center; + padding: 0.3rem 0.4rem; + border-radius: 6px; +} +.application.mgne.party .party-loot-row.party-loot-header { + color: #7e664f; + font-size: 0.72rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.05em; + border-bottom: 1px solid rgba(171, 139, 104, 0.18); + padding-bottom: 0.25rem; + margin-bottom: 0.1rem; +} +.application.mgne.party .party-loot-row:not(.party-loot-header) { + background: rgba(17, 12, 10, 0.75); + border: 1px solid transparent; +} +.application.mgne.party .party-loot-row:not(.party-loot-header):hover { + border-color: rgba(196, 154, 69, 0.22); + background: rgba(17, 12, 10, 0.9); +} +.application.mgne.party .item-portrait { + width: 2rem; + height: 2rem; + object-fit: cover; + border-radius: 3px; + border: 1px solid rgba(171, 139, 104, 0.25); +} +.application.mgne.party .item-type-label { + color: #7e664f; + font-size: 0.78rem; + font-style: italic; +} +.application.mgne.party .party-drop-hint { + color: rgba(126, 102, 79, 0.7); + font-size: 0.75rem; + font-style: italic; + text-align: center; + margin-top: 0.5rem; + padding: 0.3rem; + border: 1px dashed rgba(171, 139, 104, 0.18); + border-radius: 6px; +} .application.mgne.roll-dialog .window-content { padding: 0.55rem; background: radial-gradient(circle at top left, rgba(221, 107, 45, 0.14), transparent 26%), linear-gradient(180deg, rgba(27, 21, 18, 0.26), rgba(19, 16, 15, 0.22)), url("../assets/ui/page_background.webp") center center / cover no-repeat, linear-gradient(180deg, #1b1512, #13100f 120%); @@ -895,52 +1491,117 @@ } .mgne-chat-card .chat-formula { color: #52453c; - font-size: 0.72rem; - font-style: italic; - margin: 0; } .mgne-chat-card .chat-formula code { - color: #ab8b68; - background: rgba(19, 16, 15, 0.65); - padding: 0.08em 0.38em; + color: #ccb292; + background: rgba(19, 16, 15, 0.75); + padding: 0.1em 0.42em; border-radius: 3px; - font-style: normal; } -.mgne-chat-card .chat-outcome { +.mgne-chat-card .chat-result-line { display: flex; justify-content: space-between; - align-items: center; - gap: 0.6rem; + align-items: end; + padding: 0.38rem 0.55rem; + border: 1px solid rgba(196, 154, 69, 0.28); + border-radius: 6px; + background: rgba(19, 16, 15, 0.78); +} +.mgne-chat-card .chat-result-label { + font-family: "CastorTwoMGNE", "Palatino Linotype", serif; + text-transform: uppercase; + letter-spacing: 0.08em; + font-size: 0.62rem; + color: #ccb292; +} +.mgne-chat-card .chat-result-total { + font-family: "CastorTwoMGNE", "Palatino Linotype", serif; + font-size: 1.08rem; + color: #ccb292; +} +.mgne-chat-card .chat-outcome.has-tooltip, +.mgne-chat-card .chat-damage-total.has-tooltip { + cursor: pointer; + user-select: none; +} +.mgne-chat-card .chat-outcome.has-tooltip:hover, +.mgne-chat-card .chat-damage-total.has-tooltip:hover { + filter: brightness(1.12); +} +.mgne-chat-card .chat-outcome .chat-tooltip-icon, +.mgne-chat-card .chat-damage-total .chat-tooltip-icon { + opacity: 0.55; + font-size: 0.75em; + margin-left: 0.3rem; + vertical-align: middle; + transition: opacity 0.15s; +} +.mgne-chat-card .chat-outcome.tooltip-open .chat-tooltip-icon, +.mgne-chat-card .chat-damage-total.tooltip-open .chat-tooltip-icon { + opacity: 1; +} +.mgne-chat-card .chat-dice-tooltip { + margin-top: 0.4rem; + padding: 0.4rem 0.55rem; + background: rgba(19, 16, 15, 0.85); + border: 1px solid rgba(171, 139, 104, 0.22); + border-radius: 6px; +} +.mgne-chat-card .chat-dice-tooltip .dice-roll { + display: flex; + flex-direction: column; + gap: 0.2rem; +} +.mgne-chat-card .chat-dice-tooltip .dice-formula { + font-family: "Courier New", monospace; + font-size: 0.72rem; + color: rgba(204, 178, 146, 0.65); +} +.mgne-chat-card .chat-dice-tooltip .dice-tooltip { + font-size: 0.78rem; +} +.mgne-chat-card .chat-dice-tooltip .dice { + margin: 0.15rem 0; +} +.mgne-chat-card .chat-dice-tooltip .die-icon, +.mgne-chat-card .chat-dice-tooltip .die-face { + color: #c49a45; + font-size: 0.8rem; +} +.mgne-chat-card .chat-dice-tooltip .part-formula { + color: rgba(204, 178, 146, 0.7); + font-size: 0.72rem; +} +.mgne-chat-card .chat-dice-tooltip .part-total { + color: #ccb292; + font-weight: 600; +} +.mgne-chat-card .chat-dice-tooltip .max { + color: #82b0a6; +} +.mgne-chat-card .chat-dice-tooltip .min { + color: #e48a59; +} +.mgne-chat-card .chat-dice-tooltip .dice-total { + font-family: "CastorTwoMGNE", serif; + font-size: 1.05rem; + color: #c49a45; + text-align: right; + border-top: 1px solid rgba(171, 139, 104, 0.2); + padding-top: 0.2rem; + margin-top: 0.15rem; +} +.mgne-chat-card .chat-outcome { + font-family: "CastorTwoMGNE", "Palatino Linotype", serif; + text-transform: uppercase; + letter-spacing: 0.08em; margin: 0; - padding: 0.42rem 0.65rem 0.42rem 0.8rem; + padding: 0.3rem 0.55rem; border-radius: 6px; background: rgba(19, 16, 15, 0.72); border-left: 3px solid rgba(221, 107, 45, 0.7); -} -.mgne-chat-card .chat-outcome-label { - font-family: "CastorTwoMGNE", "Palatino Linotype", serif; - text-transform: uppercase; - letter-spacing: 0.1em; - font-size: 0.88rem; color: #e69062; - line-height: 1.1; -} -.mgne-chat-card .chat-outcome-total { - font-family: "CastorTwoMGNE", "Palatino Linotype", serif; - font-size: 1.7rem; - line-height: 1; - color: rgba(204, 178, 146, 0.88); - text-shadow: 0 0 18px rgba(221, 107, 45, 0.18); - flex-shrink: 0; -} -.mgne-chat-card .chat-damage-total { - text-align: center; - font-family: "CastorTwoMGNE", "Palatino Linotype", serif; - font-size: 2.2rem; - line-height: 1; - color: #e38450; - text-shadow: 0 0 20px rgba(183, 70, 31, 0.3); - padding: 0.3rem 0; + font-size: 0.8rem; } .mgne-chat-card .chat-special { padding: 0.42rem 0.55rem; @@ -948,26 +1609,6 @@ background: rgba(17, 12, 10, 0.85); color: #ccb292; } -.mgne-chat-card .chat-omen-remind { - padding: 0.38rem 0.55rem; - border-left: 3px solid rgba(196, 154, 69, 0.6); - background: rgba(30, 22, 8, 0.82); - color: #c9a84c; - font-size: 0.75rem; - margin: 0; - font-style: italic; -} -.mgne-chat-card .chat-omen-neutralize { - border-left-color: rgba(79, 125, 115, 0.7); - color: #9bc0b8; -} -.mgne-chat-card .chat-omen-used { - padding: 0.28rem 0.55rem; - color: #c9a84c; - font-size: 0.74rem; - margin: 0; - font-style: italic; -} .mgne-chat-card.outcome-critical-success, .mgne-chat-card.outcome-success, .mgne-chat-card.outcome-steady { @@ -976,18 +1617,8 @@ .mgne-chat-card.outcome-critical-success .chat-outcome, .mgne-chat-card.outcome-success .chat-outcome, .mgne-chat-card.outcome-steady .chat-outcome { - border-left-color: rgba(79, 125, 115, 0.8); -} -.mgne-chat-card.outcome-critical-success .chat-outcome-label, -.mgne-chat-card.outcome-success .chat-outcome-label, -.mgne-chat-card.outcome-steady .chat-outcome-label { color: #9bc0b8; -} -.mgne-chat-card.outcome-critical-success .chat-outcome-total, -.mgne-chat-card.outcome-success .chat-outcome-total, -.mgne-chat-card.outcome-steady .chat-outcome-total { - color: rgba(155, 192, 184, 0.9); - text-shadow: 0 0 18px rgba(79, 125, 115, 0.3); + border-left-color: rgba(79, 125, 115, 0.8); } .mgne-chat-card.outcome-failure, .mgne-chat-card.outcome-broken, @@ -997,15 +1628,11 @@ .mgne-chat-card.outcome-failure .chat-outcome, .mgne-chat-card.outcome-broken .chat-outcome, .mgne-chat-card.outcome-fumble .chat-outcome { + color: #eaa37c; border-left-color: rgba(221, 107, 45, 0.8); } -.mgne-chat-card.outcome-failure .chat-outcome-label, -.mgne-chat-card.outcome-broken .chat-outcome-label, -.mgne-chat-card.outcome-fumble .chat-outcome-label { - color: #eaa37c; -} -.mgne-chat-card.mode-apply-damage .chat-damage-total, -.mgne-chat-card.mode-damage .chat-damage-total { +.mgne-chat-card.mode-apply-damage .chat-result-total, +.mgne-chat-card.mode-damage .chat-result-total { color: #e38450; } .mgne-chat-card .chat-card-actions { diff --git a/fvtt-machine-gods-noxian-expanse.mjs b/fvtt-machine-gods-noxian-expanse.mjs index 8ac472c..efe755d 100644 --- a/fvtt-machine-gods-noxian-expanse.mjs +++ b/fvtt-machine-gods-noxian-expanse.mjs @@ -20,6 +20,7 @@ Hooks.once("init", () => { character: models.MGNECharacter, creature: models.MGNECreature, companion: models.MGNECompanion, + party: models.MGNEParty, } CONFIG.Combat.documentClass = documents.MGNECombat @@ -32,6 +33,7 @@ Hooks.once("init", () => { "resonance-core": models.MGNEResonanceCore, artifact: models.MGNEArtifact, feature: models.MGNEFeature, + "creature-trait": models.MGNECreatureTrait, } foundry.applications.sheets.ActorSheetV2 && foundry.documents.collections.Actors.unregisterSheet( @@ -43,6 +45,7 @@ Hooks.once("init", () => { foundry.documents.collections.Actors.registerSheet(SYSTEM_ID, applications.MGNECharacterSheet, { types: ["character"], makeDefault: true, label: SYSTEM.actorTypes.character.label }) foundry.documents.collections.Actors.registerSheet(SYSTEM_ID, applications.MGNECreatureSheet, { types: ["creature"], makeDefault: true, label: SYSTEM.actorTypes.creature.label }) foundry.documents.collections.Actors.registerSheet(SYSTEM_ID, applications.MGNECompanionSheet, { types: ["companion"], makeDefault: true, label: SYSTEM.actorTypes.companion.label }) + foundry.documents.collections.Actors.registerSheet(SYSTEM_ID, applications.MGNEPartySheet, { types: ["party"], makeDefault: true, label: SYSTEM.actorTypes.party.label }) foundry.applications.sheets.ItemSheetV2 && foundry.documents.collections.Items.unregisterSheet( "core", @@ -57,8 +60,15 @@ Hooks.once("init", () => { foundry.documents.collections.Items.registerSheet(SYSTEM_ID, applications.MGNEResonanceCoreSheet, { types: ["resonance-core"], makeDefault: true, label: SYSTEM.itemTypes["resonance-core"].label }) foundry.documents.collections.Items.registerSheet(SYSTEM_ID, applications.MGNEArtifactSheet, { types: ["artifact"], makeDefault: true, label: SYSTEM.itemTypes.artifact.label }) foundry.documents.collections.Items.registerSheet(SYSTEM_ID, applications.MGNEFeatureSheet, { types: ["feature"], makeDefault: true, label: SYSTEM.itemTypes.feature.label }) + foundry.documents.collections.Items.registerSheet(SYSTEM_ID, applications.MGNECreatureTraitSheet, { types: ["creature-trait"], makeDefault: true, label: SYSTEM.itemTypes["creature-trait"].label }) Handlebars.registerHelper("isEqual", (left, right) => left === right) + Handlebars.registerHelper("includes", (collection, value) => { + if (!collection) return false + if (collection instanceof Set) return collection.has(value) + if (Array.isArray(collection)) return collection.includes(value) + return false + }) }) Hooks.once("setup", () => { @@ -111,6 +121,17 @@ Hooks.on("renderChatMessageHTML", (message, element) => { const root = element instanceof HTMLElement ? element : element?.[0] if (!root) return + // Dice tooltip toggle + root.querySelectorAll("[data-action='toggle-dice-tooltip']").forEach(trigger => { + trigger.addEventListener("click", () => { + const tooltip = trigger.closest(".chat-card-body")?.querySelector(".chat-dice-tooltip") + if (!tooltip) return + const isHidden = tooltip.hidden + tooltip.hidden = !isHidden + trigger.classList.toggle("tooltip-open", isHidden) + }) + }) + root.querySelectorAll(".mgne-roll-damage-btn").forEach(btn => { btn.addEventListener("click", async () => { const actorId = btn.dataset.actorId diff --git a/lang/en.json b/lang/en.json index 3beb84e..54e2dd7 100644 --- a/lang/en.json +++ b/lang/en.json @@ -4,7 +4,8 @@ "ActorTypes": { "character": "Character", "creature": "Creature", - "companion": "Companion" + "companion": "Companion", + "party": "Party" }, "ItemTypes": { "weapon": "Weapon", @@ -13,7 +14,8 @@ "equipment": "Equipment", "resonance-core": "Resonance Core", "artifact": "Artifact", - "feature": "Feature" + "feature": "Feature", + "creature-trait": "Creature Trait" }, "Abilities": { "agility": "Agility", @@ -37,6 +39,24 @@ "melee": "Melee", "ranged": "Ranged" }, + "WeaponProperties": { + "ammo": "Ammo", + "awkward": "Awkward", + "binding": "Binding", + "durant": "Durant", + "finesse": "Finesse", + "fling": "Fling", + "fragile": "Fragile", + "glinting": "Glinting", + "overbearing": "Overbearing", + "parrying": "Parrying", + "precise": "Precise", + "razored": "Razored", + "ringing": "Ringing", + "two-handed": "Two-Handed", + "unwieldy": "Unwieldy", + "versatile": "Versatile" + }, "Resonations": { "accelerate": "Accelerate", "blast": "Blast", @@ -71,7 +91,9 @@ "daily": "Daily Resources", "equipment": "Equipment", "features": "Features", - "notes": "Notes" + "notes": "Notes", + "members": "Members", + "loot": "Loot" }, "Character": { "Background": "Background", @@ -92,6 +114,7 @@ "ResonancePerDay": "Resonance per Day", "ArtifactSync": "Artifact Sync", "CarryingCapacity": "Carrying Capacity", + "Load": "Load", "Rations": "Rations", "Kiffol": "Kiffol", "Weapons": "Weapons", @@ -231,22 +254,23 @@ }, "Creature": { "Special": "Special", + "Traits": "Traits", + "AddTrait": "Add Trait", + "Type": "Type", + "Number": "Number", + "ActionTable": "Action Table", + "RollAction": "Roll Action", + "DropTableHint": "Drop a Roll Table here to link it", + "ClearTable": "Clear", + "OpenTable": "Open Table", + "NoTableLinked": "No action table is linked to this creature.", + "TableNotFound": "The linked action table could not be found.", + "Types": { + "Human": "Human", + "Construct": "Construct", + "Animal": "Animal" + }, "FIELDS": { - "abilities": { - "label": "Abilities", - "agility": { - "label": "Agility" - }, - "presence": { - "label": "Presence" - }, - "strength": { - "label": "Strength" - }, - "toughness": { - "label": "Toughness" - } - }, "hp": { "label": "HP", "value": { @@ -265,20 +289,17 @@ "label": "Armor Die" } }, - "attack": { - "label": "Attack", - "damage": { - "label": "Damage" - } + "creatureType": { + "label": "Type" + }, + "number": { + "label": "Number" + }, + "actionTableUuid": { + "label": "Action Table" }, "description": { "label": "Description" - }, - "special": { - "label": "Special" - }, - "notes": { - "label": "Notes" } } }, @@ -354,6 +375,27 @@ } } }, + "Party": { + "Members": "Members", + "Loot": "Shared Loot", + "Credits": "Credits", + "MoveUp": "Move Up", + "MoveDown": "Move Down", + "RemoveMember": "Remove Member", + "DropMemberHint": "Drop a Character or Companion actor here to add them to the party.", + "DropLootHint": "Drop items here to add them to the shared loot.", + "FIELDS": { + "memberRefs": { + "label": "Members" + }, + "credits": { + "label": "Credits" + }, + "notes": { + "label": "Notes" + } + } + }, "DataModel": { "abilities": { "agility": { @@ -373,6 +415,7 @@ "Common": { "Attack": "Attack", "ArmorDie": "Armor Die", + "ArmorSave": "Armor Save", "ArtifactId": "Artifact Id", "Broken": "Broken", "BurnedOut": "Burned Out", @@ -383,6 +426,7 @@ "Current": "Current", "Damage": "Damage", "Depleted": "Depleted", + "Durability": "Durability", "Defense": "Defense", "Delete": "Delete", "Description": "Description", @@ -416,15 +460,23 @@ "Unsynchronized": "Unsynchronized", "Usage": "Usage", "UsageDie": "Usage Die", + "DurabilityDie": "Durability Die", + "RollDurability": "Roll Durability", "Used": "Used", - "Value": "Value" + "Value": "Value", + "Weight": "Weight", + "Name": "Name", + "Type": "Type" }, "Empty": { "NoArtifacts": "No artifacts yet.", "NoEquipment": "No equipment yet.", "NoFeatures": "No features yet.", "NoResonanceCores": "No resonance cores yet.", - "NoWeapons": "No weapons yet." + "NoTraits": "No traits yet.", + "NoWeapons": "No weapons yet.", + "NoMembers": "No members yet. Drop a character or companion here.", + "NoLoot": "No shared loot yet. Drop items here." }, "RulesSnapshot": { "Checks": "Checks use d20 + ability vs DR.", @@ -462,6 +514,8 @@ "ItemBroken": "{item} is broken.", "ItemBurnedOut": "{item} is burned out.", "ItemDepleted": "{item} is already depleted.", + "ItemDurabilityDepleted": "{item} durability is already depleted.", + "NoArmorEquipped": "No armor or shield equipped.", "ResonancePerDayReached": "{actor} has already used all resonations for today.", "ResonationBlocked": "{actor} cannot invoke Resonations — feedback block active (1 hour). Clear it manually once the hour has passed.", "ResonationFeedbackBlocked": "Feedback! {actor} suffers D2 damage (bypasses armor) and cannot invoke Resonations for 1 hour.", @@ -473,6 +527,7 @@ "ApplyDamageTo": "Apply {amount} damage to {target}", "AppliedDamageText": "Applied damage: {amount}.", "ArmorAbsorbed": "Armor absorbed {amount}.", + "ArmorSave": "Armor Save", "ArmorDegradedCritical": "Critical: {item} armor downgraded to {die}.", "ArmorNothingToDegrade": "Critical: no armor to downgrade.", "AttackFumble": "Attack fumble: the weapon breaks.", @@ -504,6 +559,9 @@ "ItemAttackLabel": "{item} Attack", "ItemDamageLabel": "{item} Damage", "ItemNowDepleted": "The item is depleted.", + "ItemNowBroken": "The item is now broken.", + "ItemDurabilityLabel": "{item} Durability", + "DurabilityLabel": "{item} Durability Check", "ItemUsageLabel": "{item} Usage", "MoraleBrokenText": "The actor breaks down and may flee, surrender, or grant +d4 damage to opponents.", "MoraleCheck": "Morale Check", @@ -536,8 +594,10 @@ "Mode": { "action": "Action", "apply-damage": "Damage", + "armor": "Armor", "check": "Check", "damage": "Damage", + "durability": "Durability", "flee": "Fleeing", "generic": "Roll", "morale": "Morale", @@ -573,6 +633,12 @@ }, "broken": { "label": "Broken" + }, + "durabilityDie": { + "label": "Durability Die" + }, + "weight": { + "label": "Weight" } } }, @@ -592,6 +658,12 @@ }, "broken": { "label": "Broken" + }, + "durabilityDie": { + "label": "Durability Die" + }, + "weight": { + "label": "Weight" } } }, @@ -611,6 +683,12 @@ }, "broken": { "label": "Broken" + }, + "durabilityDie": { + "label": "Durability Die" + }, + "weight": { + "label": "Weight" } } }, @@ -636,6 +714,15 @@ }, "consumable": { "label": "Consumable" + }, + "broken": { + "label": "Broken" + }, + "durabilityDie": { + "label": "Durability Die" + }, + "weight": { + "label": "Weight" } } }, @@ -652,6 +739,15 @@ }, "burnedOut": { "label": "Burned Out" + }, + "broken": { + "label": "Broken" + }, + "durabilityDie": { + "label": "Durability Die" + }, + "weight": { + "label": "Weight" } } }, @@ -674,6 +770,12 @@ }, "synchronizedTo": { "label": "Synchronized To" + }, + "durabilityDie": { + "label": "Durability Die" + }, + "weight": { + "label": "Weight" } } }, @@ -686,13 +788,22 @@ "label": "Feature Id" } } + }, + "CreatureTrait": { + "Trigger": "Trigger", + "TriggerPlaceholder": "e.g. When the creature attacks, on a critical hit…", + "FIELDS": { + "description": { "label": "Description" }, + "trigger": { "label": "Trigger" } + } } }, "TYPES": { "Actor": { "character": "Character", "creature": "Creature", - "companion": "Companion" + "companion": "Companion", + "party": "Party" }, "Item": { "weapon": "Weapon", @@ -701,7 +812,8 @@ "equipment": "Equipment", "resonance-core": "Resonance Core", "artifact": "Artifact", - "feature": "Feature" + "feature": "Feature", + "creature-trait": "Creature Trait" } } } diff --git a/less/base.less b/less/base.less index 78002de..13fa953 100644 --- a/less/base.less +++ b/less/base.less @@ -157,6 +157,31 @@ margin: 0; } +// ProseMirror / toggled editor inside fieldset — enforce contrast on dark bg +.application.mgne fieldset { + .editor, + .editor-content, + .ProseMirror, + .document-editor { + color: lighten(@parchment, 8%); + background: transparent; + } + .editor-content, + .ProseMirror { + p, li { color: lighten(@parchment, 8%); } + h1, h2, h3 { color: @gold-acid; } + .is-empty::before { color: fade(@gold-acid, 40%); } + } + .editor-menu button { + color: @bone; + background: fade(@bg-void, 55%); + &:hover { + color: lighten(@parchment, 8%); + background: fade(@gold-acid, 20%); + } + } +} + .application.mgne legend { .caps-heading(); color: @gold-acid; diff --git a/less/chat.less b/less/chat.less index 5b47304..ce7c19e 100644 --- a/less/chat.less +++ b/less/chat.less @@ -85,6 +85,67 @@ color: @parchment; } +.mgne-chat-card .chat-outcome, +.mgne-chat-card .chat-damage-total { + &.has-tooltip { + cursor: pointer; + user-select: none; + &:hover { filter: brightness(1.12); } + } + .chat-tooltip-icon { + opacity: 0.55; + font-size: 0.75em; + margin-left: 0.3rem; + vertical-align: middle; + transition: opacity 0.15s; + } + &.tooltip-open .chat-tooltip-icon { opacity: 1; } +} + +// Dice tooltip reveal panel +.mgne-chat-card .chat-dice-tooltip { + margin-top: 0.4rem; + padding: 0.4rem 0.55rem; + background: fade(@bg-void, 85%); + border: 1px solid fade(@bone, 22%); + border-radius: @radius-sm; + + // Foundry's standard dice-roll markup + .dice-roll { + display: flex; + flex-direction: column; + gap: 0.2rem; + } + .dice-formula { + font-family: "Courier New", monospace; + font-size: 0.72rem; + color: fade(@parchment, 65%); + } + .dice-tooltip { + font-size: 0.78rem; + } + .dice { + margin: 0.15rem 0; + } + .die-icon, .die-face { + color: @gold-acid; + font-size: 0.8rem; + } + .part-formula { color: fade(@parchment, 70%); font-size: 0.72rem; } + .part-total { color: @parchment; font-weight: 600; } + .max { color: lighten(@verdigris, 20%); } + .min { color: lighten(@ember-bright, 10%); } + .dice-total { + font-family: "CastorTwoMGNE", serif; + font-size: 1.05rem; + color: @gold-acid; + text-align: right; + border-top: 1px solid fade(@bone, 20%); + padding-top: 0.2rem; + margin-top: 0.15rem; + } +} + .mgne-chat-card .chat-outcome { .caps-heading(); margin: 0; diff --git a/less/sheets.less b/less/sheets.less index e3ec222..a37094e 100644 --- a/less/sheets.less +++ b/less/sheets.less @@ -123,9 +123,15 @@ } .application.mgne .condition-value-grid { - grid-template-columns: max-content 1fr; + grid-template-columns: max-content minmax(auto, 9rem); align-items: center; margin-bottom: 0.6rem; + + select { + width: auto; + min-width: 5rem; + max-width: 9rem; + } } .application.mgne .condition-flag-grid { @@ -530,10 +536,23 @@ gap: 0.35rem; align-items: center; width: fit-content; + padding: 0.1rem 0.3rem 0.1rem 0.2rem; + border-radius: 4px; + transition: background 0.15s, color 0.15s; &.active { - color: @ember-bright; + color: #f5dfa0; font-weight: 600; + text-shadow: 0 0 8px fade(@gold-acid, 55%); + background: fade(@gold-acid, 14%); + border: 1px solid fade(@gold-acid, 28%); + margin: -1px; // compensate border so layout doesn't shift + } + + &:not(.active) { + color: @bone; + border: 1px solid transparent; + margin: -1px; } } @@ -641,3 +660,652 @@ grid-template-columns: 1fr; } } + +// ─── Weight badges ──────────────────────────────────────────────────────────── +.item-weight-badge { + display: inline-flex; + align-items: center; + padding: 0 0.45em; + border-radius: 3px; + font-family: @font-body; + font-size: 0.7em; + font-weight: 700; + letter-spacing: 0.04em; + text-transform: uppercase; + white-space: nowrap; + border: 1px solid transparent; + + &.weight-trivial { + color: fade(@bone, 45%); + border-color: fade(@bone, 20%); + } + + &.weight-light { + color: @bone; + border-color: fade(@bone, 35%); + background: fade(@bone, 8%); + } + + &.weight-normal { + color: @gold-acid; + border-color: fade(@gold-acid, 40%); + background: fade(@gold-acid, 10%); + } + + &.weight-heavy { + color: @blood; + border-color: fade(@blood, 45%); + background: fade(@blood, 12%); + } +} + +// ─── Load display ───────────────────────────────────────────────────────────── +.load-display { + font-weight: 700; + letter-spacing: 0.04em; + cursor: default; +} + +.load-overloaded { + color: @blood !important; + text-shadow: 0 0 6px fade(@blood, 50%); +} + +.resource-box-overloaded { + border-color: fade(@blood, 60%) !important; + background: fade(@blood, 10%) !important; + + .resource-label-accent { + color: @blood !important; + } +} + +// ─── Item description tooltip ───────────────────────────────────────────────── +.item-name[data-tooltip] { + cursor: help; + &:hover { text-decoration: underline dotted fade(@gold-acid, 55%); } +} + +#tooltip.mgne-item-tooltip { + max-width: 22rem; + font-family: @font-body; + font-size: 0.82rem; + line-height: 1.5; + color: @parchment; + background: darken(@bg-panel-soft, 4%); + border: 1px solid fade(@gold-acid, 35%); + border-radius: 4px; + padding: 0.5rem 0.7rem; + box-shadow: 0 4px 14px fade(#000, 55%); +} + +// ─── Broken / Durability badges ─────────────────────────────────────────────── +.item-broken-badge { + display: inline-block; + margin-left: 0.3em; + padding: 0.05em 0.4em; + font-size: 0.65em; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.06em; + color: @blood; + background: fade(@blood, 14%); + border: 1px solid fade(@blood, 45%); + border-radius: 3px; + vertical-align: middle; +} + +.item-burned-badge { + color: @gold-acid; + background: fade(@gold-acid, 12%); + border-color: fade(@gold-acid, 40%); +} + +.item-durability-badge { + display: inline-flex; + align-items: center; + gap: 0.25em; + padding: 0.1em 0.45em; + font-size: 0.7em; + font-weight: 700; + color: fade(@bone, 65%); + border: 1px solid fade(@bone, 20%); + border-radius: 3px; + white-space: nowrap; + letter-spacing: 0.03em; + + &.durability-broken { + color: @blood; + border-color: fade(@blood, 40%); + background: fade(@blood, 10%); + text-decoration: line-through; + } +} + +.item-row-broken { + opacity: 0.6; + filter: grayscale(0.4); + + .item-name { + text-decoration: line-through; + color: fade(@parchment, 55%); + } +} + +// ─── Weapon properties ──────────────────────────────────────────────────────── +.weapon-properties-grid { + display: flex; + flex-wrap: wrap; + gap: 0.35rem 0.6rem; + padding: 0.5rem 0.6rem; + background: fade(@bone, 4%); + border: 1px solid fade(@bone, 12%); + border-radius: 4px; + margin-bottom: 0.5rem; + + .property-check { + display: flex; + align-items: center; + gap: 0.3em; + font-family: @font-body; + font-size: 0.82rem; + color: @parchment; + cursor: pointer; + white-space: nowrap; + padding: 0.15em 0.4em; + border-radius: 3px; + transition: background 0.15s; + + &:hover { background: fade(@gold-acid, 8%); } + + input[type="checkbox"] { accent-color: @gold-acid; } + } +} + +.weapon-property-badge { + display: inline-block; + margin-left: 0.3em; + padding: 0.05em 0.35em; + font-size: 0.62em; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.05em; + color: fade(@gold-acid, 85%); + background: fade(@gold-acid, 10%); + border: 1px solid fade(@gold-acid, 30%); + border-radius: 3px; + vertical-align: middle; +} + +// ─── Creature trait trigger ─────────────────────────────────────────────────── +.creature-trait-trigger { + font-size: 0.82em; + color: fade(@parchment, 65%); + font-style: italic; + margin-left: 0.2em; +} + +// ─── Creature traits container ──────────────────────────────────────────────── +.creature-traits-container { + border: 1px solid fade(@blood, 35%); + border-left: 3px solid fade(@blood, 65%); + border-radius: 4px; + background: linear-gradient(135deg, fade(@blood, 5%), transparent 60%), + fade(@bg-panel-soft, 85%); + padding: 0.6rem 0.75rem; + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.creature-traits-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.5rem; + border-bottom: 1px solid fade(@blood, 25%); + padding-bottom: 0.4rem; + margin-bottom: 0.1rem; + + h3 { + margin: 0; + font-family: @font-display; + font-size: 0.82rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.08em; + color: fade(@blood, 90%); + } + + button { + font-size: 0.72rem; + padding: 0.15em 0.6em; + background: fade(@blood, 12%); + border: 1px solid fade(@blood, 35%); + color: fade(@blood, 80%); + border-radius: 3px; + cursor: pointer; + transition: background 0.15s, color 0.15s; + + &:hover { + background: fade(@blood, 22%); + color: lighten(@blood, 10%); + } + } +} + +.creature-traits-list { + display: flex; + flex-direction: column; + gap: 0.3rem; +} + +.creature-trait-card { + background: fade(@bg-void, 30%); + border: 1px solid fade(@blood, 20%); + border-radius: 3px; + padding: 0.35rem 0.5rem; + transition: background 0.15s, border-color 0.15s; + + &:hover { + background: fade(@blood, 8%); + border-color: fade(@blood, 35%); + } +} + +.creature-trait-card-header { + display: flex; + align-items: center; + gap: 0.5rem; + flex-wrap: wrap; +} + +.creature-trait-name { + display: flex; + align-items: center; + gap: 0.35em; + font-family: @font-body; + font-size: 0.88rem; + font-weight: 600; + color: @parchment; + flex: 1; + min-width: 0; + cursor: default; + + i { color: fade(@blood, 70%); font-size: 0.75em; } +} + +.creature-trait-trigger { + font-size: 0.75rem; + font-style: italic; + color: fade(@parchment, 55%); + white-space: nowrap; +} + +.creature-trait-card .item-actions { + display: flex; + gap: 0.25rem; + margin-left: auto; + flex-shrink: 0; + + button { + width: 1.6rem; + height: 1.6rem; + padding: 0; + display: flex; + align-items: center; + justify-content: center; + background: transparent; + border: 1px solid fade(@bone, 18%); + color: fade(@bone, 50%); + border-radius: 3px; + cursor: pointer; + font-size: 0.72rem; + transition: background 0.15s, color 0.15s, border-color 0.15s; + + &:hover { + background: fade(@bone, 10%); + color: @bone; + border-color: fade(@bone, 35%); + } + } +} + +// ============================================================ +// CREATURE SHEET — META ROW + ACTION TABLE +// ============================================================ + +.creature-meta-row { + display: flex; + align-items: center; + gap: 1rem; + flex-wrap: wrap; + padding: 0.45rem 0.6rem; + background: fade(@bg-panel-soft, 50%); + border: 1px solid fade(@ember, 20%); + border-radius: 4px; +} + +.creature-meta-label { + font-family: @font-display; + font-size: 0.72rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.07em; + color: fade(@ember, 80%); + white-space: nowrap; +} + +.creature-type-group { + display: flex; + align-items: center; + gap: 0.75rem; + flex: 1; +} + +.creature-type-checkbox { + display: flex; + align-items: center; + gap: 0.3em; + font-size: 0.83rem; + color: @parchment; + cursor: pointer; + user-select: none; + + input[type="checkbox"] { + accent-color: @ember; + width: 0.9rem; + height: 0.9rem; + cursor: pointer; + } +} + +.creature-number-group { + display: flex; + align-items: center; + gap: 0.4rem; + flex-shrink: 0; +} + +.creature-number-input { + width: 4rem; + text-align: center; + font-family: @font-body; + font-size: 0.9rem; + color: @parchment; + background: @bg-input; + border: 1px solid fade(@ember, 30%); + border-radius: 3px; + padding: 0.15em 0.3em; + + &:focus { + outline: none; + border-color: fade(@ember, 60%); + background: fade(@bg-input, 90%); + } +} + +.creature-action-table-section { + .action-table-drop-zone { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.5rem 0.75rem; + border-radius: 4px; + min-height: 2.4rem; + font-size: 0.88rem; + color: @parchment; + + i { font-size: 1em; color: @ember; } + + .action-table-name { + font-weight: 600; + font-family: @font-display; + color: @parchment; + } + + &.drop-hint { + border: 2px dashed fade(@ember, 35%); + background: fade(@ember, 4%); + color: fade(@parchment, 50%); + font-style: italic; + justify-content: center; + cursor: default; + transition: background 0.15s, border-color 0.15s; + + i { color: fade(@ember, 50%); } + + &:hover, &.drag-over { + background: fade(@ember, 9%); + border-color: fade(@ember, 60%); + color: fade(@parchment, 70%); + i { color: @ember; } + } + } + } + + .action-table-buttons { + display: flex; + gap: 0.3rem; + align-items: center; + + button { + font-size: 0.75rem; + padding: 0.15em 0.55em; + background: fade(@ember, 10%); + border: 1px solid fade(@ember, 30%); + color: fade(@ember, 80%); + border-radius: 3px; + cursor: pointer; + transition: background 0.15s, color 0.15s; + display: flex; + align-items: center; + gap: 0.3em; + + &:hover { + background: fade(@ember, 22%); + color: @ember; + } + } + } +} + +// ============================================================ +// PARTY ACTOR SHEET +// ============================================================ + +.application.mgne.party { + + .party-credits-row { + display: flex; + align-items: center; + gap: 0.6rem; + margin-top: 0.5rem; + padding: 0.4rem 0.6rem; + border: 1px solid fade(@gold-acid, 35%); + border-radius: @radius-sm; + background: fade(@bg-input, 70%); + } + + .party-credits-stepper { + display: flex; + align-items: center; + gap: 0.3rem; + } + + .credits-input { + width: 5rem; + text-align: center; + font-size: 1.05rem; + font-weight: 700; + font-family: @font-display; + color: @gold-acid; + background: fade(@bg-void, 60%); + border: 1px solid fade(@gold-acid, 30%); + border-radius: @radius-sm; + padding: 0.2rem 0.4rem; + } + + .credits-symbol { + color: @gold-acid; + font-family: @font-display; + font-size: 1.1rem; + font-weight: 700; + margin: 0 0.1rem; + } + + .credits-btn { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 2rem; + padding: 0.15rem 0.4rem; + font-size: 0.75rem; + font-weight: 700; + border: 1px solid fade(@bone, 28%); + border-radius: @radius-sm; + background: fade(@bg-input, 80%); + color: @bone; + cursor: pointer; + user-select: none; + transition: background 0.15s, border-color 0.15s; + + &:hover { + background: fade(@gold-acid, 18%); + border-color: fade(@gold-acid, 55%); + color: @gold-acid; + } + } +} + +// ── Party member list ───────────────────────────────────────── +.application.mgne.party .party-member-list { + display: flex; + flex-direction: column; + gap: 0.25rem; + margin-top: 0.4rem; +} + +.application.mgne.party .party-member-row { + display: grid; + grid-template-columns: 2rem 1fr 5rem 6rem 5.5rem; + gap: 0.5rem; + align-items: center; + padding: 0.3rem 0.4rem; + border-radius: @radius-sm; + + &.party-member-header { + color: @dust; + font-size: 0.72rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.05em; + border-bottom: 1px solid fade(@bone, 18%); + padding-bottom: 0.25rem; + margin-bottom: 0.1rem; + } + + &:not(.party-member-header) { + background: fade(@bg-input, 75%); + border: 1px solid transparent; + + &:hover { + border-color: fade(@gold-acid, 22%); + background: fade(@bg-input, 90%); + } + } +} + +.application.mgne.party .party-member-portrait { + width: 2rem; + height: 2rem; + object-fit: cover; + border-radius: 3px; + border: 1px solid fade(@bone, 25%); +} + +.application.mgne.party .party-member-name a { + color: @parchment; + font-weight: 700; + font-size: 0.9rem; + + &:hover { color: @gold-acid; } +} + +.application.mgne.party .party-member-hp { + color: @ember-bright; + font-family: @font-display; + font-size: 0.82rem; + font-weight: 700; + text-align: center; +} + +.application.mgne.party .party-member-type { + color: @dust; + font-size: 0.78rem; + font-style: italic; +} + +// ── Party loot list ──────────────────────────────────────────── +.application.mgne.party .party-loot-list { + display: flex; + flex-direction: column; + gap: 0.25rem; + margin-top: 0.4rem; +} + +.application.mgne.party .party-loot-row { + display: grid; + grid-template-columns: 2rem 1fr 7rem 4rem; + gap: 0.5rem; + align-items: center; + padding: 0.3rem 0.4rem; + border-radius: @radius-sm; + + &.party-loot-header { + color: @dust; + font-size: 0.72rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.05em; + border-bottom: 1px solid fade(@bone, 18%); + padding-bottom: 0.25rem; + margin-bottom: 0.1rem; + } + + &:not(.party-loot-header) { + background: fade(@bg-input, 75%); + border: 1px solid transparent; + + &:hover { + border-color: fade(@gold-acid, 22%); + background: fade(@bg-input, 90%); + } + } +} + +.application.mgne.party .item-portrait { + width: 2rem; + height: 2rem; + object-fit: cover; + border-radius: 3px; + border: 1px solid fade(@bone, 25%); +} + +.application.mgne.party .item-type-label { + color: @dust; + font-size: 0.78rem; + font-style: italic; +} + +// ── Party drop hint ──────────────────────────────────────────── +.application.mgne.party .party-drop-hint { + color: fade(@dust, 70%); + font-size: 0.75rem; + font-style: italic; + text-align: center; + margin-top: 0.5rem; + padding: 0.3rem; + border: 1px dashed fade(@bone, 18%); + border-radius: @radius-sm; +} diff --git a/module/applications/_module.mjs b/module/applications/_module.mjs index 4d29649..387d4f3 100644 --- a/module/applications/_module.mjs +++ b/module/applications/_module.mjs @@ -3,6 +3,7 @@ export { default as MGNEItemSheet } from "./sheets/base-item-sheet.mjs" export { default as MGNECharacterSheet } from "./sheets/character-sheet.mjs" export { default as MGNECreatureSheet } from "./sheets/creature-sheet.mjs" export { default as MGNECompanionSheet } from "./sheets/companion-sheet.mjs" +export { default as MGNEPartySheet } from "./sheets/party-sheet.mjs" export { default as MGNEWeaponSheet } from "./sheets/weapon-sheet.mjs" export { default as MGNEArmorSheet } from "./sheets/armor-sheet.mjs" export { default as MGNEShieldSheet } from "./sheets/shield-sheet.mjs" @@ -10,3 +11,4 @@ export { default as MGNEEquipmentSheet } from "./sheets/equipment-sheet.mjs" export { default as MGNEResonanceCoreSheet } from "./sheets/resonance-core-sheet.mjs" export { default as MGNEArtifactSheet } from "./sheets/artifact-sheet.mjs" export { default as MGNEFeatureSheet } from "./sheets/feature-sheet.mjs" +export { default as MGNECreatureTraitSheet } from "./sheets/creature-trait-sheet.mjs" diff --git a/module/applications/sheets/base-actor-sheet.mjs b/module/applications/sheets/base-actor-sheet.mjs index 0ffe176..c1c891a 100644 --- a/module/applications/sheets/base-actor-sheet.mjs +++ b/module/applications/sheets/base-actor-sheet.mjs @@ -64,6 +64,8 @@ export default class MGNEActorSheet extends HandlebarsApplicationMixin(foundry.a switch (rollType) { case "ability": return this.document.rollAbility(target.dataset.abilityId) + case "armor": + return this.document.rollArmorSave() case "defense": return this.document.rollDefense() case "weapon": @@ -78,6 +80,8 @@ export default class MGNEActorSheet extends HandlebarsApplicationMixin(foundry.a return this.document.rollResonation(itemId) case "morale": return this.document.rollMorale() + case "durability": + return this.document.rollDurability(itemId) case "usage": return this.document.rollUsage(itemId) default: diff --git a/module/applications/sheets/character-sheet.mjs b/module/applications/sheets/character-sheet.mjs index 67cac91..e67224a 100644 --- a/module/applications/sheets/character-sheet.mjs +++ b/module/applications/sheets/character-sheet.mjs @@ -2,6 +2,10 @@ import MGNEActorSheet from "./base-actor-sheet.mjs" import { SYSTEM } from "../../config/system.mjs" import { buildCharacterSelectOptions } from "./select-options.mjs" +export function stripHtml(html) { + return (html ?? "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim() +} + export default class MGNECharacterSheet extends MGNEActorSheet { static DEFAULT_OPTIONS = { classes: ["character"], @@ -79,16 +83,16 @@ export default class MGNECharacterSheet extends MGNEActorSheet { break case "equipment": context.tab = context.tabs.equipment - context.weapons = doc.itemTypes.weapon - context.armors = doc.itemTypes.armor - context.shields = doc.itemTypes.shield - context.equipmentItems = doc.itemTypes.equipment - context.cores = doc.itemTypes["resonance-core"] - context.artifacts = doc.itemTypes.artifact + context.weapons = doc.itemTypes.weapon.map(i => ({ ...i, tooltip: stripHtml(i.system.description) })) + context.armors = doc.itemTypes.armor.map(i => ({ ...i, tooltip: stripHtml(i.system.description) })) + context.shields = doc.itemTypes.shield.map(i => ({ ...i, tooltip: stripHtml(i.system.description) })) + context.equipmentItems = doc.itemTypes.equipment.map(i => ({ ...i, tooltip: stripHtml(i.system.description) })) + context.cores = doc.itemTypes["resonance-core"].map(i => ({ ...i, tooltip: stripHtml(i.system.description) })) + context.artifacts = doc.itemTypes.artifact.map(i => ({ ...i, tooltip: stripHtml(i.system.description) })) break case "features": context.tab = context.tabs.features - context.features = doc.itemTypes.feature + context.features = doc.itemTypes.feature.map(i => ({ ...i, tooltip: stripHtml(i.system.description) })) break case "notes": context.tab = context.tabs.notes diff --git a/module/applications/sheets/companion-sheet.mjs b/module/applications/sheets/companion-sheet.mjs index 06bc4b5..8e924c6 100644 --- a/module/applications/sheets/companion-sheet.mjs +++ b/module/applications/sheets/companion-sheet.mjs @@ -1,5 +1,4 @@ import MGNEActorSheet from "./base-actor-sheet.mjs" -import { SYSTEM } from "../../config/system.mjs" export default class MGNECompanionSheet extends MGNEActorSheet { static DEFAULT_OPTIONS = { @@ -13,14 +12,4 @@ export default class MGNECompanionSheet extends MGNEActorSheet { static PARTS = { main: { template: "systems/fvtt-machine-gods-noxian-expanse/templates/companion-main.hbs" }, } - - async _prepareContext() { - const context = await super._prepareContext() - context.abilityList = SYSTEM.abilityOrder.map(id => ({ - id, - ...SYSTEM.abilities[id], - value: context.source.system.abilities?.[id]?.value ?? 0, - })) - return context - } } diff --git a/module/applications/sheets/creature-sheet.mjs b/module/applications/sheets/creature-sheet.mjs index 5f48f4c..a2444ff 100644 --- a/module/applications/sheets/creature-sheet.mjs +++ b/module/applications/sheets/creature-sheet.mjs @@ -1,12 +1,17 @@ import MGNEActorSheet from "./base-actor-sheet.mjs" -import { SYSTEM } from "../../config/system.mjs" +import { stripHtml } from "./character-sheet.mjs" export default class MGNECreatureSheet extends MGNEActorSheet { static DEFAULT_OPTIONS = { classes: ["creature"], position: { width: 760, - height: 640, + height: 680, + }, + actions: { + rollActionTable: MGNECreatureSheet.prototype._rollActionTable, + clearActionTable: MGNECreatureSheet.prototype._clearActionTable, + openActionTable: MGNECreatureSheet.prototype._openActionTable, }, } @@ -14,13 +19,67 @@ export default class MGNECreatureSheet extends MGNEActorSheet { main: { template: "systems/fvtt-machine-gods-noxian-expanse/templates/creature-main.hbs" }, } + _processSubmitData(event, form, submitData) { + // Foundry sends null for unchecked checkboxes in a SetField array — strip them + if (Array.isArray(submitData.system?.creatureType)) { + submitData.system.creatureType = submitData.system.creatureType.filter(v => v != null && v !== "") + } + return super._processSubmitData(event, form, submitData) + } + async _prepareContext() { const context = await super._prepareContext() - context.abilityList = SYSTEM.abilityOrder.map(id => ({ - id, - ...SYSTEM.abilities[id], - value: context.source.system.abilities?.[id]?.value ?? 0, + context.traits = (this.document.itemTypes["creature-trait"] ?? []) + .map(i => ({ ...i, tooltip: stripHtml(i.system.description) })) + + // Resolve linked action table + const uuid = this.document.system.actionTableUuid + if (uuid) { + const table = await fromUuid(uuid).catch(() => null) + context.actionTable = table ? { name: table.name, uuid } : null + } else { + context.actionTable = null + } + + // Build creature type checkboxes + const typeSet = this.document.system.creatureType ?? new Set() + context.creatureTypes = ["human", "construct", "animal"].map(key => ({ + key, + label: game.i18n.localize(`MGNE.Creature.Types.${key.charAt(0).toUpperCase() + key.slice(1)}`), + checked: typeSet.has(key), })) + return context } + + async _onDrop(event) { + const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event) + if (data?.type === "RollTable") { + const table = await fromUuid(data.uuid) + if (table) { + await this.document.update({ "system.actionTableUuid": data.uuid }) + return + } + } + return super._onDrop(event) + } + + async _rollActionTable() { + const uuid = this.document.system.actionTableUuid + if (!uuid) return ui.notifications.warn(game.i18n.localize("MGNE.Creature.NoTableLinked")) + const table = await fromUuid(uuid).catch(() => null) + if (!table) return ui.notifications.warn(game.i18n.localize("MGNE.Creature.TableNotFound")) + await table.draw() + } + + async _clearActionTable() { + await this.document.update({ "system.actionTableUuid": "" }) + } + + async _openActionTable() { + const uuid = this.document.system.actionTableUuid + if (!uuid) return + const table = await fromUuid(uuid).catch(() => null) + if (table) table.sheet.render(true) + } } diff --git a/module/applications/sheets/creature-trait-sheet.mjs b/module/applications/sheets/creature-trait-sheet.mjs new file mode 100644 index 0000000..63c6617 --- /dev/null +++ b/module/applications/sheets/creature-trait-sheet.mjs @@ -0,0 +1,7 @@ +import MGNEItemSheet from "./base-item-sheet.mjs" + +export default class MGNECreatureTraitSheet extends MGNEItemSheet { + static PARTS = { + main: { template: "systems/fvtt-machine-gods-noxian-expanse/templates/creature-trait.hbs" }, + } +} diff --git a/module/applications/sheets/party-sheet.mjs b/module/applications/sheets/party-sheet.mjs new file mode 100644 index 0000000..ca969e9 --- /dev/null +++ b/module/applications/sheets/party-sheet.mjs @@ -0,0 +1,162 @@ +import MGNEActorSheet from "./base-actor-sheet.mjs" + +const SYSTEM_ID = "fvtt-machine-gods-noxian-expanse" + +const PARTY_LOOT_TYPES = new Set(["weapon", "armor", "shield", "equipment", "resonance-core", "artifact"]) + +export default class MGNEPartySheet extends MGNEActorSheet { + /** @override */ + static DEFAULT_OPTIONS = { + classes: ["party"], + position: { width: 820, height: 640 }, + actions: { + openMember: MGNEPartySheet.#onOpenMember, + removeMember: MGNEPartySheet.#onRemoveMember, + moveMemberUp: MGNEPartySheet.#onMoveMemberUp, + moveMemberDown: MGNEPartySheet.#onMoveMemberDown, + adjustCredits: MGNEPartySheet.#onAdjustCredits, + }, + } + + /** @override */ + static PARTS = { + main: { template: `systems/${SYSTEM_ID}/templates/party-main.hbs` }, + tabs: { template: `systems/${SYSTEM_ID}/templates/party-tabs.hbs` }, + members: { template: `systems/${SYSTEM_ID}/templates/party-members.hbs` }, + loot: { template: `systems/${SYSTEM_ID}/templates/party-loot.hbs` }, + notes: { template: `systems/${SYSTEM_ID}/templates/party-notes.hbs` }, + } + + tabGroups = { sheet: "members" } + + #getTabs() { + const tabs = { + members: { id: "members", group: "sheet", label: game.i18n.localize("MGNE.Tabs.members") }, + loot: { id: "loot", group: "sheet", label: game.i18n.localize("MGNE.Tabs.loot") }, + notes: { id: "notes", group: "sheet", label: game.i18n.localize("MGNE.Tabs.notes") }, + } + for (const tab of Object.values(tabs)) { + tab.active = this.tabGroups[tab.group] === tab.id + tab.cssClass = tab.active ? "active" : "" + } + return tabs + } + + /** @override */ + async _prepareContext() { + const context = await super._prepareContext() + context.tabs = this.#getTabs() + return context + } + + /** @override */ + async _preparePartContext(partId, context) { + const doc = this.document + switch (partId) { + case "members": { + context.tab = context.tabs.members + // Build member list using actorId for moves; store refIdx (position in + // memberRefs) so move actions always operate on the correct slot even + // when some refs point to deleted actors. + const refs = doc.system.memberRefs ?? [] + const members = [] + for (let refIdx = 0; refIdx < refs.length; refIdx++) { + const actor = game.actors?.get(refs[refIdx].id) + if (!actor) continue + members.push({ + id: actor.id, + refIdx, + name: actor.name, + img: actor.img, + type: actor.type, + typeLabel: game.i18n.localize(`TYPES.Actor.${actor.type}`), + hp: actor.system.hp + ? `${actor.system.hp.value ?? "—"}/${actor.system.hp.max ?? "—"}` + : "—", + }) + } + // isFirst/isLast based on visible list, but swap uses refIdx + for (let vi = 0; vi < members.length; vi++) { + members[vi].isFirst = vi === 0 + members[vi].isLast = vi === members.length - 1 + } + context.members = members + break + } + + case "loot": { + context.tab = context.tabs.loot + context.lootItems = doc.items.contents + .filter(i => PARTY_LOOT_TYPES.has(i.type)) + .map(i => ({ + id: i.id, + img: i.img, + name: i.name, + typeLabel: game.i18n.localize(`TYPES.Item.${i.type}`), + })) + break + } + + case "notes": + context.tab = context.tabs.notes + break + } + return context + } + + /** @override */ + async _onDrop(event) { + if (!this.isEditable) return + const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event) + + if (data.type === "Actor") { + const actor = await fromUuid(data.uuid) + if (!actor || !["character", "companion"].includes(actor.type)) return + const refs = foundry.utils.deepClone(this.document.system.memberRefs ?? []) + if (refs.some(r => r.id === actor.id)) return + refs.push({ id: actor.id }) + return this.document.update({ "system.memberRefs": refs }) + } + + if (data.type === "Item") { + const item = await fromUuid(data.uuid) + if (!item || !PARTY_LOOT_TYPES.has(item.type)) return + return this.document.createEmbeddedDocuments("Item", [item.toObject()]) + } + } + + // ── Actions ───────────────────────────────────────────────────────────────── + + static async #onOpenMember(_event, target) { + const actor = game.actors?.get(target.dataset.actorId) + if (actor) actor.sheet.render(true) + } + + static async #onRemoveMember(_event, target) { + const id = target.dataset.actorId + const refs = (this.document.system.memberRefs ?? []).filter(r => r.id !== id) + await this.document.update({ "system.memberRefs": refs }) + } + + static async #onMoveMemberUp(_event, target) { + const refIdx = parseInt(target.dataset.refIdx, 10) + if (refIdx <= 0) return + const refs = foundry.utils.deepClone(this.document.system.memberRefs ?? []); + [refs[refIdx - 1], refs[refIdx]] = [refs[refIdx], refs[refIdx - 1]] + await this.document.update({ "system.memberRefs": refs }) + } + + static async #onMoveMemberDown(_event, target) { + const refIdx = parseInt(target.dataset.refIdx, 10) + const refs = foundry.utils.deepClone(this.document.system.memberRefs ?? []) + if (refIdx >= refs.length - 1) return; + [refs[refIdx], refs[refIdx + 1]] = [refs[refIdx + 1], refs[refIdx]] + await this.document.update({ "system.memberRefs": refs }) + } + + static async #onAdjustCredits(_event, target) { + const delta = parseInt(target.dataset.delta, 10) + const current = this.document.system.credits ?? 0 + await this.document.update({ "system.credits": Math.max(0, current + delta) }) + } +} diff --git a/module/applications/sheets/select-options.mjs b/module/applications/sheets/select-options.mjs index e7762b7..0967c16 100644 --- a/module/applications/sheets/select-options.mjs +++ b/module/applications/sheets/select-options.mjs @@ -31,12 +31,13 @@ export function buildSharedSelectOptions() { armorPenalties: numericOptions(0, 6), shieldPenalties: numericOptions(0, 4), weaponCategories: objectOptions(SYSTEM.weaponCategories), + weaponProperties: Object.entries(SYSTEM.weaponProperties).map(([key, p]) => ({ value: key, label: p.label, hint: p.hint })), + weightCategories: objectOptions(SYSTEM.weightCategories), usageDice: objectOptions(SYSTEM.usageDieChoices), armorDice: objectOptions(SYSTEM.armorDieChoices), omenDice: objectOptions(SYSTEM.omenDieChoices), resonanceList: objectOptions(SYSTEM.resonanceList), equipmentSubtypes: objectOptions(SYSTEM.equipmentSubtypes), - artifactIds: objectOptions(SYSTEM.artifactChoices), featureIds: objectOptions(SYSTEM.featureChoices), } } diff --git a/module/config/system.mjs b/module/config/system.mjs index 07e8a59..a731c18 100644 --- a/module/config/system.mjs +++ b/module/config/system.mjs @@ -28,6 +28,7 @@ export const SYSTEM = { character: { id: "character", label: "Character" }, creature: { id: "creature", label: "Creature" }, companion: { id: "companion", label: "Companion" }, + party: { id: "party", label: "Party" }, }, itemTypes: { weapon: { id: "weapon", label: "Weapon", icon: itemIcon("weapon") }, @@ -37,6 +38,7 @@ export const SYSTEM = { "resonance-core": { id: "resonance-core", label: "Resonance Core", icon: itemIcon("resonance-core") }, artifact: { id: "artifact", label: "Artifact", icon: itemIcon("artifact") }, feature: { id: "feature", label: "Feature", icon: itemIcon("feature") }, + "creature-trait": { id: "creature-trait", label: "Creature Trait", icon: itemIcon("creature-trait") }, }, abilities: { agility: { id: "agility", label: "Agility" }, @@ -63,10 +65,37 @@ export const SYSTEM = { armorDieChoices: dieChoiceLabels(["d12", "d10", "d8", "d6", "d4", "d2", "0"]), omenDice: ["d2", "d4", "d6", "d8"], omenDieChoices: dieChoiceLabels(["d2", "d4", "d6", "d8"]), + weightCategories: { + trivial: "Trivial", + light: "Light", + normal: "Normal", + heavy: "Heavy", + }, weaponCategories: { melee: "Melee", ranged: "Ranged", }, + weaponProperties: { + ammo: { label: "Ammo", hint: "Requires ammunition. Improvised in melee or without ammo." }, + awkward: { label: "Awkward", hint: "+1 DR to attacks." }, + binding: { label: "Binding", hint: "Beat DR by 4+ on same-size or smaller target: inflict Restrained." }, + durant: { label: "Durant", hint: "Durability die is D8." }, + finesse: { label: "Finesse", hint: "Attacks can use either Presence or Strength." }, + fling: { label: "Fling", hint: "Can make ranged attacks. Roll D4 Usage Die to avoid losing it." }, + fragile: { label: "Fragile", hint: "Durability die is D4." }, + glinting: { label: "Glinting", hint: "After attacking, next attack vs same target this combat gains -1 DR." }, + overbearing:{ label: "Overbearing",hint: "+2 damage." }, + parrying: { label: "Parrying", hint: "-1 DR to avoiding melee attacks while wielding." }, + precise: { label: "Precise", hint: "-1 DR to hit." }, + razored: { label: "Razored", hint: "Beat DR by 4+: inflict Bleeding (1)." }, + ringing: { label: "Ringing", hint: "Beat DR by 4+ and target not Stunned: inflict Stunned (1)." }, + "two-handed":{ label: "Two-Handed",hint: "Requires both hands." }, + unwieldy: { label: "Unwieldy", hint: "Cannot attack more than once per Round." }, + versatile: { label: "Versatile", hint: "Two-handed: -1 DR to hit and +1 damage." }, + }, + get weaponPropertyLabels() { + return Object.fromEntries(Object.entries(this.weaponProperties).map(([k, v]) => [k, v.label])) + }, resonanceList: { accelerate: "Accelerate", blast: "Blast", diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index 9690084..741e9e4 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -93,6 +93,10 @@ export default class MGNEActor extends Actor { return result } + async rollArmorSave() { + return MGNERoll.rollArmorSave(this) + } + async rollWeapon(itemId) { const item = this.items.get(itemId) if (!item) return null @@ -129,7 +133,10 @@ export default class MGNEActor extends Actor { async rollProfileAttack() { const attackBaseLabel = normalizeGenericActionLabel(this.system.attack?.label ?? t("MGNE.Common.Attack"), t("MGNE.Common.Attack")) const attackLabel = formatActionLabel(attackBaseLabel, t("MGNE.Common.Attack")) - const result = await this.rollAbility("strength", { + // Creatures have no ability scores; ability value defaults to 0 via roll.mjs + const result = await MGNERoll.promptCheck({ + actor: this, + abilityId: "strength", label: attackLabel, rollType: "attack", }) @@ -220,6 +227,12 @@ export default class MGNEActor extends Actor { return item.rollUsage() } + async rollDurability(itemId) { + const item = this.items.get(itemId) + if (!item) return null + return MGNERoll.rollDurability(item) + } + async quickRest() { const roll = await (new Roll("1d4")).evaluate() const hp = this.system.hp?.value ?? 0 diff --git a/module/documents/roll.mjs b/module/documents/roll.mjs index c33bbff..e92f8ff 100644 --- a/module/documents/roll.mjs +++ b/module/documents/roll.mjs @@ -57,11 +57,15 @@ async function renderCard(context) { const normalizedEyebrow = `${eyebrow}`.trim().toLowerCase() const normalizedLabel = `${context.label ?? ""}`.trim().toLowerCase() + // Render dice tooltip HTML if a roll was provided + const diceTooltip = context._roll ? await context._roll.render() : null + return foundry.applications.handlebars.renderTemplate(`systems/${SYSTEM_ID}/templates/chat-message.hbs`, { ...context, modeClass: context.mode ?? "generic", eyebrow: "", outcomeClass, + diceTooltip, }) } @@ -156,6 +160,7 @@ export default class MGNERoll { damageItemId: showDamageButton ? item.id : null, damageFormula: showDamageButton ? (item.system.damage || "1") : null, damageCritical: showDamageButton && critical, + _roll: roll, }) await ChatMessage.create({ @@ -181,6 +186,7 @@ export default class MGNERoll { total: roll.total, outcome: broken ? t("MGNE.Roll.OutcomeBroken") : t("MGNE.Roll.OutcomeSteady"), specialText: broken ? t("MGNE.Roll.MoraleBrokenText") : "", + _roll: roll, }) await ChatMessage.create({ @@ -236,6 +242,7 @@ export default class MGNERoll { showApplyButton: true, damageTotal: roll.total, damageCritical: isCritical, + _roll: roll, }) await ChatMessage.create({ @@ -267,6 +274,7 @@ export default class MGNERoll { showApplyButton: true, damageTotal: roll.total, damageCritical: isCritical, + _roll: roll, }) await ChatMessage.create({ @@ -278,6 +286,39 @@ export default class MGNERoll { return { roll } } + static async rollArmorSave(actor) { + const formula = actor.getArmorRollFormula() + const items = actor.getEquippedArmorItems() + + if (formula === "0") { + ui.notifications.warn(t("MGNE.Notification.NoArmorEquipped")) + return null + } + + const roll = await (new Roll(formula)).evaluate() + const armorNames = items.map(i => i.name).join(" + ") + + const contentHtml = await renderCard({ + mode: "armor", + actorName: actor.name, + actorImg: actor.img, + label: t("MGNE.Roll.ArmorSave"), + subtitle: armorNames, + formula: roll.formula, + total: roll.total, + outcome: f("MGNE.Roll.ArmorAbsorbed", { amount: roll.total }), + _roll: roll, + }) + + await ChatMessage.create({ + speaker: ChatMessage.getSpeaker({ actor }), + rolls: [roll], + content: contentHtml, + }) + + return roll + } + static async rollUsage(item) { const currentDie = item.system.usageDie if (!currentDie || currentDie === "depleted") { @@ -303,6 +344,7 @@ export default class MGNERoll { total: roll.total, outcome: depleted ? f("MGNE.Roll.DowngradedTo", { die: nextDie.toUpperCase() }) : t("MGNE.Roll.NoChange"), specialText: depleted && nextDie === "depleted" ? t("MGNE.Roll.ItemNowDepleted") : "", + _roll: roll, }) await ChatMessage.create({ @@ -314,6 +356,48 @@ export default class MGNERoll { return { roll, depleted, nextDie } } + static async rollDurability(item) { + const currentDie = item.system.durabilityDie + if (!currentDie || currentDie === "depleted") { + ui.notifications.warn(f("MGNE.Notification.ItemDurabilityDepleted", { item: item.name })) + return null + } + + const roll = await (new Roll(`1${currentDie}`)).evaluate() + const degraded = roll.total <= 2 + const nextDie = degraded ? stepDownDie(currentDie) : currentDie + const nowBroken = degraded && nextDie === "depleted" + const updates = { "system.durabilityDie": nextDie } + if (nowBroken) { + updates["system.broken"] = true + if ("equipped" in (item.system ?? {})) updates["system.equipped"] = false + } + await item.update(updates) + + const contentHtml = await renderCard({ + mode: "durability", + actorName: item.parent?.name ?? item.name, + actorImg: item.img, + label: f("MGNE.Roll.DurabilityLabel", { item: item.name }), + subtitle: f("MGNE.Roll.CurrentDie", { die: currentDie.toUpperCase() }), + formula: roll.formula, + total: roll.total, + outcome: degraded + ? f("MGNE.Roll.DowngradedTo", { die: nextDie.toUpperCase() }) + : t("MGNE.Roll.NoChange"), + specialText: nowBroken ? t("MGNE.Roll.ItemNowBroken") : "", + _roll: roll, + }) + + await ChatMessage.create({ + speaker: ChatMessage.getSpeaker({ actor: item.parent ?? null }), + rolls: [roll], + content: contentHtml, + }) + + return { roll, degraded, nextDie, nowBroken } + } + static async applyDamageCard({ actor, sourceActor = null, sourceItem = null, amount, armorRoll = null, appliedDamage, newHp, breakText = "", defenseFumbleText = "", criticalArmorText = "" }) { const contentHtml = await renderCard({ mode: "apply-damage", @@ -335,6 +419,7 @@ export default class MGNERoll { criticalArmorText, breakText ? f("MGNE.Roll.BreakText", { text: breakText }) : "", ]), + _roll: armorRoll ?? null, }) await ChatMessage.create({ @@ -359,6 +444,7 @@ export default class MGNERoll { total: roll.total, outcome, specialText, + _roll: roll, }) await ChatMessage.create({ diff --git a/module/models/_module.mjs b/module/models/_module.mjs index 7ed6577..7f6ba3b 100644 --- a/module/models/_module.mjs +++ b/module/models/_module.mjs @@ -1,6 +1,7 @@ export { default as MGNECharacter } from "./character.mjs" export { default as MGNECreature } from "./creature.mjs" export { default as MGNECompanion } from "./companion.mjs" +export { default as MGNEParty } from "./party.mjs" export { default as MGNEWeapon } from "./weapon.mjs" export { default as MGNEArmor } from "./armor.mjs" export { default as MGNEShield } from "./shield.mjs" @@ -8,3 +9,4 @@ export { default as MGNEEquipment } from "./equipment.mjs" export { default as MGNEResonanceCore } from "./resonance-core.mjs" export { default as MGNEArtifact } from "./artifact.mjs" export { default as MGNEFeature } from "./feature.mjs" +export { default as MGNECreatureTrait } from "./creature-trait.mjs" diff --git a/module/models/armor.mjs b/module/models/armor.mjs index a3a3cec..6b46869 100644 --- a/module/models/armor.mjs +++ b/module/models/armor.mjs @@ -12,8 +12,16 @@ export default class MGNEArmor extends foundry.abstract.TypeDataModel { choices: SYSTEM.armorDieChoices, }), penalty: numberField(0, 0, 6), + weight: new foundry.data.fields.StringField({ + required: true, nullable: false, initial: "heavy", + choices: SYSTEM.weightCategories, + }), equipped: booleanField(false), broken: booleanField(false), + durabilityDie: new foundry.data.fields.StringField({ + required: true, nullable: false, initial: "d6", + choices: SYSTEM.usageDieChoices, + }), } } diff --git a/module/models/artifact.mjs b/module/models/artifact.mjs index 1ce67f1..feb6419 100644 --- a/module/models/artifact.mjs +++ b/module/models/artifact.mjs @@ -5,12 +5,6 @@ export default class MGNEArtifact extends foundry.abstract.TypeDataModel { static defineSchema() { return { description: htmlField(""), - artifactId: new foundry.data.fields.StringField({ - required: true, - nullable: false, - initial: "shiver-lens", - choices: SYSTEM.artifactChoices, - }), synchronized: booleanField(false), synchronizedTo: stringField(""), usageDie: new foundry.data.fields.StringField({ @@ -20,6 +14,14 @@ export default class MGNEArtifact extends foundry.abstract.TypeDataModel { choices: SYSTEM.usageDieChoices, }), broken: booleanField(false), + durabilityDie: new foundry.data.fields.StringField({ + required: true, nullable: false, initial: "d6", + choices: SYSTEM.usageDieChoices, + }), + weight: new foundry.data.fields.StringField({ + required: true, nullable: false, initial: "normal", + choices: SYSTEM.weightCategories, + }), } } diff --git a/module/models/character.mjs b/module/models/character.mjs index eb4eab1..53c4f64 100644 --- a/module/models/character.mjs +++ b/module/models/character.mjs @@ -45,6 +45,34 @@ export default class MGNECharacter extends foundry.abstract.TypeDataModel { this.syncLimit = Math.max(0, this.abilities.toughness?.value ?? 0) this.syncRemaining = Math.max(0, this.syncLimit - (this.artifactSync.used ?? 0)) this.armorFormula = this.parent?.getArmorRollFormula?.() ?? "0" + + // Compute current load per RAW: + // trivial = 0, light = 10 per slot, normal = 1, heavy = fills remaining capacity (max 1) + let normalLoad = 0 + let lightCount = 0 + let heavyCount = 0 + for (const item of (this.parent?.items ?? [])) { + if (item.system?.carried === false) continue // not being carried + const w = item.system?.weight ?? "normal" + if (w === "trivial") continue + else if (w === "light") lightCount++ + else if (w === "normal") normalLoad++ + else if (w === "heavy") heavyCount++ + } + normalLoad += Math.floor(lightCount / 10) + this.lightItemCount = lightCount + this.heavyItemCount = heavyCount + + if (heavyCount >= 2) { + // Can't carry two heavy items — automatically overloaded + this.currentLoad = this.carryCapacity + (heavyCount - 1) + } else if (heavyCount === 1) { + // Heavy fills remaining capacity; other items fit alongside it + this.currentLoad = Math.max(normalLoad, this.carryCapacity) + } else { + this.currentLoad = normalLoad + } + this.overloaded = this.currentLoad > this.carryCapacity } /** @override */ diff --git a/module/models/companion.mjs b/module/models/companion.mjs index e4f41a6..94fd13d 100644 --- a/module/models/companion.mjs +++ b/module/models/companion.mjs @@ -1,12 +1,11 @@ import { SYSTEM } from "../config/system.mjs" -import { abilitySchema, htmlField, numberField, stringField, trackSchema } from "./shared.mjs" +import { htmlField, numberField, stringField, trackSchema } from "./shared.mjs" export default class MGNECompanion extends foundry.abstract.TypeDataModel { static defineSchema() { const fields = foundry.data.fields return { - abilities: abilitySchema(), hp: trackSchema(1, 1), morale: numberField(7, 2, 12), armor: new fields.SchemaField({ diff --git a/module/models/creature-trait.mjs b/module/models/creature-trait.mjs new file mode 100644 index 0000000..d797859 --- /dev/null +++ b/module/models/creature-trait.mjs @@ -0,0 +1,13 @@ +import { htmlField, stringField } from "./shared.mjs" + +export default class MGNECreatureTrait extends foundry.abstract.TypeDataModel { + static defineSchema() { + return { + description: htmlField(""), + trigger: stringField(""), + } + } + + /** @override */ + static LOCALIZATION_PREFIXES = ["MGNE.CreatureTrait"] +} diff --git a/module/models/creature.mjs b/module/models/creature.mjs index bd3d877..b5bed97 100644 --- a/module/models/creature.mjs +++ b/module/models/creature.mjs @@ -1,27 +1,39 @@ import { SYSTEM } from "../config/system.mjs" -import { abilitySchema, htmlField, numberField, stringField, trackSchema } from "./shared.mjs" +import { htmlField, numberField, stringField, trackSchema } from "./shared.mjs" + +const CREATURE_TYPES = ["human", "construct", "animal"] export default class MGNECreature extends foundry.abstract.TypeDataModel { static defineSchema() { const fields = foundry.data.fields return { - abilities: abilitySchema(), hp: trackSchema(1, 1), morale: numberField(7, 2, 12), armor: new fields.SchemaField({ die: new fields.StringField({ required: true, nullable: false, initial: "0", choices: SYSTEM.armorDieChoices }), }), - attack: new fields.SchemaField({ - label: stringField("Attack"), - damage: stringField("1d4"), - }), + creatureType: new fields.SetField( + new fields.StringField({ required: true, choices: CREATURE_TYPES }), + { required: true, nullable: false, initial: [] } + ), + number: stringField("1"), + actionTableUuid: stringField(""), description: htmlField(""), - special: htmlField(""), - notes: htmlField(""), } } /** @override */ static LOCALIZATION_PREFIXES = ["MGNE.Creature"] + + /** @override */ + static migrateData(source) { + // Remove old attack field if present (no longer part of the schema) + if ("attack" in source) delete source.attack + // Form submissions send null for unchecked checkboxes in array fields — filter them out + if (Array.isArray(source.creatureType)) { + source.creatureType = source.creatureType.filter(v => v != null && v !== "") + } + return super.migrateData(source) + } } diff --git a/module/models/equipment.mjs b/module/models/equipment.mjs index 5d251e0..95b4a45 100644 --- a/module/models/equipment.mjs +++ b/module/models/equipment.mjs @@ -21,6 +21,15 @@ export default class MGNEEquipment extends foundry.abstract.TypeDataModel { choices: SYSTEM.usageDieChoices, }), consumable: booleanField(false), + broken: booleanField(false), + durabilityDie: new foundry.data.fields.StringField({ + required: true, nullable: false, initial: "d6", + choices: SYSTEM.usageDieChoices, + }), + weight: new foundry.data.fields.StringField({ + required: true, nullable: false, initial: "normal", + choices: SYSTEM.weightCategories, + }), } } diff --git a/module/models/party.mjs b/module/models/party.mjs new file mode 100644 index 0000000..c566190 --- /dev/null +++ b/module/models/party.mjs @@ -0,0 +1,20 @@ +import { htmlField } from "./shared.mjs" + +export default class MGNEParty extends foundry.abstract.TypeDataModel { + static defineSchema() { + const fields = foundry.data.fields + + return { + memberRefs: new fields.ArrayField( + new fields.SchemaField({ + id: new fields.StringField({ required: true, nullable: false, blank: false }), + }) + ), + credits: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0, min: 0 }), + notes: htmlField(""), + } + } + + /** @override */ + static LOCALIZATION_PREFIXES = ["MGNE.Party"] +} diff --git a/module/models/resonance-core.mjs b/module/models/resonance-core.mjs index ca0771a..0f3768e 100644 --- a/module/models/resonance-core.mjs +++ b/module/models/resonance-core.mjs @@ -18,6 +18,15 @@ export default class MGNEResonanceCore extends foundry.abstract.TypeDataModel { choices: SYSTEM.usageDieChoices, }), burnedOut: booleanField(false), + broken: booleanField(false), + durabilityDie: new foundry.data.fields.StringField({ + required: true, nullable: false, initial: "d6", + choices: SYSTEM.usageDieChoices, + }), + weight: new foundry.data.fields.StringField({ + required: true, nullable: false, initial: "trivial", + choices: SYSTEM.weightCategories, + }), } } diff --git a/module/models/shield.mjs b/module/models/shield.mjs index 4249887..9f766bd 100644 --- a/module/models/shield.mjs +++ b/module/models/shield.mjs @@ -12,8 +12,16 @@ export default class MGNEShield extends foundry.abstract.TypeDataModel { choices: SYSTEM.armorDieChoices, }), penalty: numberField(0, 0, 4), + weight: new foundry.data.fields.StringField({ + required: true, nullable: false, initial: "normal", + choices: SYSTEM.weightCategories, + }), equipped: booleanField(false), broken: booleanField(false), + durabilityDie: new foundry.data.fields.StringField({ + required: true, nullable: false, initial: "d6", + choices: SYSTEM.usageDieChoices, + }), } } diff --git a/module/models/weapon.mjs b/module/models/weapon.mjs index b38d727..e71e6bf 100644 --- a/module/models/weapon.mjs +++ b/module/models/weapon.mjs @@ -13,7 +13,14 @@ export default class MGNEWeapon extends foundry.abstract.TypeDataModel { }), damage: stringField("1d4"), range: stringField("Touch"), - properties: stringField(""), + properties: new foundry.data.fields.SetField( + new foundry.data.fields.StringField({ required: true, nullable: false, blank: false }), + { required: true, nullable: false, initial: [] } + ), + weight: new foundry.data.fields.StringField({ + required: true, nullable: false, initial: "normal", + choices: SYSTEM.weightCategories, + }), usageDie: new foundry.data.fields.StringField({ required: true, nullable: false, @@ -23,9 +30,29 @@ export default class MGNEWeapon extends foundry.abstract.TypeDataModel { quantity: numberField(1, 0), equipped: booleanField(false), broken: booleanField(false), + durabilityDie: new foundry.data.fields.StringField({ + required: true, nullable: false, initial: "d6", + choices: SYSTEM.usageDieChoices, + }), } } /** @override */ static LOCALIZATION_PREFIXES = ["MGNE.Weapon"] + + /** + * Migrate old string-based properties field to the new SetField format. + * @override + */ + static migrateData(source) { + // Old data stored properties as a plain string; convert to empty array + if (typeof source.properties === "string") { + source.properties = [] + } + // Remove any null/undefined/blank entries that may have crept in + if (Array.isArray(source.properties)) { + source.properties = source.properties.filter(p => p != null && p !== "") + } + return super.migrateData(source) + } } diff --git a/packs-system/armor/000017.log b/packs-system/armor/000017.log deleted file mode 100644 index e69de29..0000000 diff --git a/packs-system/armor/000019.ldb b/packs-system/armor/000024.ldb similarity index 74% rename from packs-system/armor/000019.ldb rename to packs-system/armor/000024.ldb index 3319ca1..923b9a9 100644 Binary files a/packs-system/armor/000019.ldb and b/packs-system/armor/000024.ldb differ diff --git a/packs-system/armor/000026.log b/packs-system/armor/000026.log new file mode 100644 index 0000000..ad4dc3c Binary files /dev/null and b/packs-system/armor/000026.log differ diff --git a/packs-system/armor/CURRENT b/packs-system/armor/CURRENT index 42c62b6..f622090 100644 --- a/packs-system/armor/CURRENT +++ b/packs-system/armor/CURRENT @@ -1 +1 @@ -MANIFEST-000015 +MANIFEST-000025 diff --git a/packs-system/armor/LOG b/packs-system/armor/LOG index 7e7b701..e69de29 100644 --- a/packs-system/armor/LOG +++ b/packs-system/armor/LOG @@ -1,15 +0,0 @@ -2026/05/10-10:14:24.425893 7ff663fff6c0 Recovering log #12 -2026/05/10-10:14:24.435701 7ff663fff6c0 Delete type=3 #10 -2026/05/10-10:14:24.435782 7ff663fff6c0 Delete type=0 #12 -2026/05/10-10:15:09.159943 7ff6637fe6c0 Level-0 table #18: started -2026/05/10-10:15:09.164167 7ff6637fe6c0 Level-0 table #18: 1354 bytes OK -2026/05/10-10:15:09.170159 7ff6637fe6c0 Delete type=0 #16 -2026/05/10-10:15:09.193554 7ff6637fe6c0 Manual compaction at level-0 from '!items!mgne-arm-chainshirt' @ 72057594037927935 : 1 .. '!items!null' @ 0 : 0; will stop at (end) -2026/05/10-10:15:09.193605 7ff6637fe6c0 Manual compaction at level-1 from '!items!mgne-arm-chainshirt' @ 72057594037927935 : 1 .. '!items!null' @ 0 : 0; will stop at '!items!null' @ 25 : 1 -2026/05/10-10:15:09.193611 7ff6637fe6c0 Compacting 1@1 + 1@2 files -2026/05/10-10:15:09.197820 7ff6637fe6c0 Generated table #19@1: 1 keys, 685 bytes -2026/05/10-10:15:09.197839 7ff6637fe6c0 Compacted 1@1 + 1@2 files => 685 bytes -2026/05/10-10:15:09.204193 7ff6637fe6c0 compacted to: files[ 0 0 2 0 0 0 0 ] -2026/05/10-10:15:09.204306 7ff6637fe6c0 Delete type=2 #14 -2026/05/10-10:15:09.204434 7ff6637fe6c0 Delete type=2 #18 -2026/05/10-10:15:09.221921 7ff6637fe6c0 Manual compaction at level-1 from '!items!null' @ 25 : 1 .. '!items!null' @ 0 : 0; will stop at (end) diff --git a/packs-system/armor/LOG.old b/packs-system/armor/LOG.old index c247e7c..2297a0f 100644 --- a/packs-system/armor/LOG.old +++ b/packs-system/armor/LOG.old @@ -1,15 +1,3 @@ -2026/05/10-00:06:25.472144 7fe7209fd6c0 Recovering log #8 -2026/05/10-00:06:25.483556 7fe7209fd6c0 Delete type=3 #6 -2026/05/10-00:06:25.483623 7fe7209fd6c0 Delete type=0 #8 -2026/05/10-09:35:40.443132 7fe6d37fe6c0 Level-0 table #13: started -2026/05/10-09:35:40.446256 7fe6d37fe6c0 Level-0 table #13: 1354 bytes OK -2026/05/10-09:35:40.453014 7fe6d37fe6c0 Delete type=0 #11 -2026/05/10-09:35:40.470483 7fe6d37fe6c0 Manual compaction at level-0 from '!items!mgne-arm-chainshirt' @ 72057594037927935 : 1 .. '!items!null' @ 0 : 0; will stop at (end) -2026/05/10-09:35:40.476731 7fe6d37fe6c0 Manual compaction at level-1 from '!items!mgne-arm-chainshirt' @ 72057594037927935 : 1 .. '!items!null' @ 0 : 0; will stop at '!items!null' @ 17 : 1 -2026/05/10-09:35:40.476735 7fe6d37fe6c0 Compacting 1@1 + 1@2 files -2026/05/10-09:35:40.479976 7fe6d37fe6c0 Generated table #14@1: 1 keys, 685 bytes -2026/05/10-09:35:40.480043 7fe6d37fe6c0 Compacted 1@1 + 1@2 files => 685 bytes -2026/05/10-09:35:40.486274 7fe6d37fe6c0 compacted to: files[ 0 0 2 0 0 0 0 ] -2026/05/10-09:35:40.486346 7fe6d37fe6c0 Delete type=2 #9 -2026/05/10-09:35:40.486470 7fe6d37fe6c0 Delete type=2 #13 -2026/05/10-09:35:40.504107 7fe6d37fe6c0 Manual compaction at level-1 from '!items!null' @ 17 : 1 .. '!items!null' @ 0 : 0; will stop at (end) +2026/05/17-15:40:00.779700 7f1642bfd6c0 Recovering log #22 +2026/05/17-15:40:00.789740 7f1642bfd6c0 Delete type=3 #20 +2026/05/17-15:40:00.789807 7f1642bfd6c0 Delete type=0 #22 diff --git a/packs-system/armor/MANIFEST-000015 b/packs-system/armor/MANIFEST-000015 deleted file mode 100644 index bfb024e..0000000 Binary files a/packs-system/armor/MANIFEST-000015 and /dev/null differ diff --git a/packs-system/armor/MANIFEST-000025 b/packs-system/armor/MANIFEST-000025 new file mode 100644 index 0000000..e2efbc4 Binary files /dev/null and b/packs-system/armor/MANIFEST-000025 differ diff --git a/packs-system/companions/000017.log b/packs-system/companions/000017.log deleted file mode 100644 index e69de29..0000000 diff --git a/packs-system/companions/000019.ldb b/packs-system/companions/000024.ldb similarity index 92% rename from packs-system/companions/000019.ldb rename to packs-system/companions/000024.ldb index f9d0b98..6fbe886 100644 Binary files a/packs-system/companions/000019.ldb and b/packs-system/companions/000024.ldb differ diff --git a/packs-system/companions/000026.log b/packs-system/companions/000026.log new file mode 100644 index 0000000..eae1ba5 Binary files /dev/null and b/packs-system/companions/000026.log differ diff --git a/packs-system/companions/CURRENT b/packs-system/companions/CURRENT index 42c62b6..f622090 100644 --- a/packs-system/companions/CURRENT +++ b/packs-system/companions/CURRENT @@ -1 +1 @@ -MANIFEST-000015 +MANIFEST-000025 diff --git a/packs-system/companions/LOG b/packs-system/companions/LOG index f45d8f3..12fa1e1 100644 --- a/packs-system/companions/LOG +++ b/packs-system/companions/LOG @@ -1,15 +1,3 @@ -2026/05/10-10:14:24.400732 7ff671fef6c0 Recovering log #12 -2026/05/10-10:14:24.411244 7ff671fef6c0 Delete type=3 #10 -2026/05/10-10:14:24.411312 7ff671fef6c0 Delete type=0 #12 -2026/05/10-10:15:09.101581 7ff6637fe6c0 Level-0 table #18: started -2026/05/10-10:15:09.104730 7ff6637fe6c0 Level-0 table #18: 4899 bytes OK -2026/05/10-10:15:09.111016 7ff6637fe6c0 Delete type=0 #16 -2026/05/10-10:15:09.121512 7ff6637fe6c0 Manual compaction at level-0 from '!actors!mgne-comp-beguiled-noble' @ 72057594037927935 : 1 .. '!actors!null' @ 0 : 0; will stop at (end) -2026/05/10-10:15:09.141299 7ff6637fe6c0 Manual compaction at level-1 from '!actors!mgne-comp-beguiled-noble' @ 72057594037927935 : 1 .. '!actors!null' @ 0 : 0; will stop at '!actors!null' @ 13 : 1 -2026/05/10-10:15:09.141307 7ff6637fe6c0 Compacting 1@1 + 1@2 files -2026/05/10-10:15:09.144336 7ff6637fe6c0 Generated table #19@1: 1 keys, 1984 bytes -2026/05/10-10:15:09.144349 7ff6637fe6c0 Compacted 1@1 + 1@2 files => 1984 bytes -2026/05/10-10:15:09.150185 7ff6637fe6c0 compacted to: files[ 0 0 2 0 0 0 0 ] -2026/05/10-10:15:09.150242 7ff6637fe6c0 Delete type=2 #14 -2026/05/10-10:15:09.150350 7ff6637fe6c0 Delete type=2 #18 -2026/05/10-10:15:09.159856 7ff6637fe6c0 Manual compaction at level-1 from '!actors!null' @ 13 : 1 .. '!actors!null' @ 0 : 0; will stop at (end) +2026/05/17-15:40:00.750239 7f16423fc6c0 Recovering log #22 +2026/05/17-15:40:00.760411 7f16423fc6c0 Delete type=3 #20 +2026/05/17-15:40:00.760461 7f16423fc6c0 Delete type=0 #22 diff --git a/packs-system/companions/LOG.old b/packs-system/companions/LOG.old index 77cf8b7..ca669bc 100644 --- a/packs-system/companions/LOG.old +++ b/packs-system/companions/LOG.old @@ -1,15 +1,15 @@ -2026/05/10-00:06:25.447076 7fe7211fe6c0 Recovering log #8 -2026/05/10-00:06:25.457896 7fe7211fe6c0 Delete type=3 #6 -2026/05/10-00:06:25.457952 7fe7211fe6c0 Delete type=0 #8 -2026/05/10-09:35:40.366450 7fe6d37fe6c0 Level-0 table #13: started -2026/05/10-09:35:40.369656 7fe6d37fe6c0 Level-0 table #13: 4899 bytes OK -2026/05/10-09:35:40.375570 7fe6d37fe6c0 Delete type=0 #11 -2026/05/10-09:35:40.404682 7fe6d37fe6c0 Manual compaction at level-0 from '!actors!mgne-comp-beguiled-noble' @ 72057594037927935 : 1 .. '!actors!null' @ 0 : 0; will stop at (end) -2026/05/10-09:35:40.404711 7fe6d37fe6c0 Manual compaction at level-1 from '!actors!mgne-comp-beguiled-noble' @ 72057594037927935 : 1 .. '!actors!null' @ 0 : 0; will stop at '!actors!null' @ 9 : 1 -2026/05/10-09:35:40.404715 7fe6d37fe6c0 Compacting 1@1 + 1@2 files -2026/05/10-09:35:40.407947 7fe6d37fe6c0 Generated table #14@1: 1 keys, 1984 bytes -2026/05/10-09:35:40.407960 7fe6d37fe6c0 Compacted 1@1 + 1@2 files => 1984 bytes -2026/05/10-09:35:40.413847 7fe6d37fe6c0 compacted to: files[ 0 0 2 0 0 0 0 ] -2026/05/10-09:35:40.413959 7fe6d37fe6c0 Delete type=2 #9 -2026/05/10-09:35:40.414504 7fe6d37fe6c0 Delete type=2 #13 -2026/05/10-09:35:40.433958 7fe6d37fe6c0 Manual compaction at level-1 from '!actors!null' @ 9 : 1 .. '!actors!null' @ 0 : 0; will stop at (end) +2026/05/17-13:26:05.090885 7f16423fc6c0 Recovering log #17 +2026/05/17-13:26:05.101315 7f16423fc6c0 Delete type=3 #15 +2026/05/17-13:26:05.101364 7f16423fc6c0 Delete type=0 #17 +2026/05/17-15:39:46.172949 7f1641bfb6c0 Level-0 table #23: started +2026/05/17-15:39:46.177184 7f1641bfb6c0 Level-0 table #23: 4899 bytes OK +2026/05/17-15:39:46.183241 7f1641bfb6c0 Delete type=0 #21 +2026/05/17-15:39:46.215307 7f1641bfb6c0 Manual compaction at level-0 from '!actors!mgne-comp-beguiled-noble' @ 72057594037927935 : 1 .. '!actors!null' @ 0 : 0; will stop at (end) +2026/05/17-15:39:46.224653 7f1641bfb6c0 Manual compaction at level-1 from '!actors!mgne-comp-beguiled-noble' @ 72057594037927935 : 1 .. '!actors!null' @ 0 : 0; will stop at '!actors!null' @ 17 : 1 +2026/05/17-15:39:46.224663 7f1641bfb6c0 Compacting 1@1 + 1@2 files +2026/05/17-15:39:46.227820 7f1641bfb6c0 Generated table #24@1: 1 keys, 1984 bytes +2026/05/17-15:39:46.227830 7f1641bfb6c0 Compacted 1@1 + 1@2 files => 1984 bytes +2026/05/17-15:39:46.233855 7f1641bfb6c0 compacted to: files[ 0 0 2 0 0 0 0 ] +2026/05/17-15:39:46.233946 7f1641bfb6c0 Delete type=2 #19 +2026/05/17-15:39:46.234305 7f1641bfb6c0 Delete type=2 #23 +2026/05/17-15:39:46.253812 7f1641bfb6c0 Manual compaction at level-1 from '!actors!null' @ 17 : 1 .. '!actors!null' @ 0 : 0; will stop at (end) diff --git a/packs-system/companions/MANIFEST-000015 b/packs-system/companions/MANIFEST-000015 deleted file mode 100644 index 07084a5..0000000 Binary files a/packs-system/companions/MANIFEST-000015 and /dev/null differ diff --git a/packs-system/companions/MANIFEST-000025 b/packs-system/companions/MANIFEST-000025 new file mode 100644 index 0000000..0b8db3e Binary files /dev/null and b/packs-system/companions/MANIFEST-000025 differ diff --git a/packs-system/features/000017.log b/packs-system/features/000017.log deleted file mode 100644 index e69de29..0000000 diff --git a/packs-system/features/000019.ldb b/packs-system/features/000024.ldb similarity index 79% rename from packs-system/features/000019.ldb rename to packs-system/features/000024.ldb index adcad4b..39041c5 100644 Binary files a/packs-system/features/000019.ldb and b/packs-system/features/000024.ldb differ diff --git a/packs-system/features/000026.log b/packs-system/features/000026.log new file mode 100644 index 0000000..84089a2 Binary files /dev/null and b/packs-system/features/000026.log differ diff --git a/packs-system/features/CURRENT b/packs-system/features/CURRENT index 42c62b6..f622090 100644 --- a/packs-system/features/CURRENT +++ b/packs-system/features/CURRENT @@ -1 +1 @@ -MANIFEST-000015 +MANIFEST-000025 diff --git a/packs-system/features/LOG b/packs-system/features/LOG index f9f1246..c5881e4 100644 --- a/packs-system/features/LOG +++ b/packs-system/features/LOG @@ -1,15 +1,3 @@ -2026/05/10-10:14:24.387289 7ff663fff6c0 Recovering log #12 -2026/05/10-10:14:24.397491 7ff663fff6c0 Delete type=3 #10 -2026/05/10-10:14:24.397540 7ff663fff6c0 Delete type=0 #12 -2026/05/10-10:15:09.082119 7ff6637fe6c0 Level-0 table #18: started -2026/05/10-10:15:09.085424 7ff6637fe6c0 Level-0 table #18: 10412 bytes OK -2026/05/10-10:15:09.092167 7ff6637fe6c0 Delete type=0 #16 -2026/05/10-10:15:09.121491 7ff6637fe6c0 Manual compaction at level-0 from '!items!mgne-feat-11' @ 72057594037927935 : 1 .. '!items!null' @ 0 : 0; will stop at (end) -2026/05/10-10:15:09.121551 7ff6637fe6c0 Manual compaction at level-1 from '!items!mgne-feat-11' @ 72057594037927935 : 1 .. '!items!null' @ 0 : 0; will stop at '!items!null' @ 109 : 1 -2026/05/10-10:15:09.121558 7ff6637fe6c0 Compacting 1@1 + 1@2 files -2026/05/10-10:15:09.124679 7ff6637fe6c0 Generated table #19@1: 1 keys, 727 bytes -2026/05/10-10:15:09.124714 7ff6637fe6c0 Compacted 1@1 + 1@2 files => 727 bytes -2026/05/10-10:15:09.130639 7ff6637fe6c0 compacted to: files[ 0 0 2 0 0 0 0 ] -2026/05/10-10:15:09.130747 7ff6637fe6c0 Delete type=2 #14 -2026/05/10-10:15:09.130828 7ff6637fe6c0 Delete type=2 #18 -2026/05/10-10:15:09.159837 7ff6637fe6c0 Manual compaction at level-1 from '!items!null' @ 109 : 1 .. '!items!null' @ 0 : 0; will stop at (end) +2026/05/17-15:40:00.736377 7f1643bff6c0 Recovering log #22 +2026/05/17-15:40:00.746313 7f1643bff6c0 Delete type=3 #20 +2026/05/17-15:40:00.746417 7f1643bff6c0 Delete type=0 #22 diff --git a/packs-system/features/LOG.old b/packs-system/features/LOG.old index d65b463..d9e32fe 100644 --- a/packs-system/features/LOG.old +++ b/packs-system/features/LOG.old @@ -1,15 +1,15 @@ -2026/05/10-00:06:25.433796 7fe7209fd6c0 Recovering log #8 -2026/05/10-00:06:25.444473 7fe7209fd6c0 Delete type=3 #6 -2026/05/10-00:06:25.444528 7fe7209fd6c0 Delete type=0 #8 -2026/05/10-09:35:40.395081 7fe6d37fe6c0 Level-0 table #13: started -2026/05/10-09:35:40.398418 7fe6d37fe6c0 Level-0 table #13: 10411 bytes OK -2026/05/10-09:35:40.404589 7fe6d37fe6c0 Delete type=0 #11 -2026/05/10-09:35:40.414572 7fe6d37fe6c0 Manual compaction at level-0 from '!items!mgne-feat-11' @ 72057594037927935 : 1 .. '!items!null' @ 0 : 0; will stop at (end) -2026/05/10-09:35:40.433971 7fe6d37fe6c0 Manual compaction at level-1 from '!items!mgne-feat-11' @ 72057594037927935 : 1 .. '!items!null' @ 0 : 0; will stop at '!items!null' @ 73 : 1 -2026/05/10-09:35:40.433978 7fe6d37fe6c0 Compacting 1@1 + 1@2 files -2026/05/10-09:35:40.437087 7fe6d37fe6c0 Generated table #14@1: 1 keys, 727 bytes -2026/05/10-09:35:40.437108 7fe6d37fe6c0 Compacted 1@1 + 1@2 files => 727 bytes -2026/05/10-09:35:40.442891 7fe6d37fe6c0 compacted to: files[ 0 0 2 0 0 0 0 ] -2026/05/10-09:35:40.442951 7fe6d37fe6c0 Delete type=2 #9 -2026/05/10-09:35:40.443056 7fe6d37fe6c0 Delete type=2 #13 -2026/05/10-09:35:40.453127 7fe6d37fe6c0 Manual compaction at level-1 from '!items!null' @ 73 : 1 .. '!items!null' @ 0 : 0; will stop at (end) +2026/05/17-13:26:05.076690 7f1643bff6c0 Recovering log #17 +2026/05/17-13:26:05.087217 7f1643bff6c0 Delete type=3 #15 +2026/05/17-13:26:05.087270 7f1643bff6c0 Delete type=0 #17 +2026/05/17-15:39:46.194394 7f1641bfb6c0 Level-0 table #23: started +2026/05/17-15:39:46.197937 7f1641bfb6c0 Level-0 table #23: 10414 bytes OK +2026/05/17-15:39:46.204111 7f1641bfb6c0 Delete type=0 #21 +2026/05/17-15:39:46.215335 7f1641bfb6c0 Manual compaction at level-0 from '!items!mgne-feat-11' @ 72057594037927935 : 1 .. '!items!null' @ 0 : 0; will stop at (end) +2026/05/17-15:39:46.234408 7f1641bfb6c0 Manual compaction at level-1 from '!items!mgne-feat-11' @ 72057594037927935 : 1 .. '!items!null' @ 0 : 0; will stop at '!items!null' @ 145 : 1 +2026/05/17-15:39:46.234419 7f1641bfb6c0 Compacting 1@1 + 1@2 files +2026/05/17-15:39:46.238287 7f1641bfb6c0 Generated table #24@1: 1 keys, 727 bytes +2026/05/17-15:39:46.238317 7f1641bfb6c0 Compacted 1@1 + 1@2 files => 727 bytes +2026/05/17-15:39:46.244549 7f1641bfb6c0 compacted to: files[ 0 0 2 0 0 0 0 ] +2026/05/17-15:39:46.244607 7f1641bfb6c0 Delete type=2 #19 +2026/05/17-15:39:46.244675 7f1641bfb6c0 Delete type=2 #23 +2026/05/17-15:39:46.253828 7f1641bfb6c0 Manual compaction at level-1 from '!items!null' @ 145 : 1 .. '!items!null' @ 0 : 0; will stop at (end) diff --git a/packs-system/features/MANIFEST-000015 b/packs-system/features/MANIFEST-000015 deleted file mode 100644 index eb5ab7e..0000000 Binary files a/packs-system/features/MANIFEST-000015 and /dev/null differ diff --git a/packs-system/features/MANIFEST-000025 b/packs-system/features/MANIFEST-000025 new file mode 100644 index 0000000..fce892b Binary files /dev/null and b/packs-system/features/MANIFEST-000025 differ diff --git a/packs-system/names/000018.log b/packs-system/names/000018.log deleted file mode 100644 index e69de29..0000000 diff --git a/packs-system/names/000020.ldb b/packs-system/names/000020.ldb deleted file mode 100644 index d65bd53..0000000 Binary files a/packs-system/names/000020.ldb and /dev/null differ diff --git a/packs-system/names/000025.ldb b/packs-system/names/000025.ldb new file mode 100644 index 0000000..af4aca0 Binary files /dev/null and b/packs-system/names/000025.ldb differ diff --git a/packs-system/names/000027.log b/packs-system/names/000027.log new file mode 100644 index 0000000..b19c65d Binary files /dev/null and b/packs-system/names/000027.log differ diff --git a/packs-system/names/CURRENT b/packs-system/names/CURRENT index 32108be..8b15215 100644 --- a/packs-system/names/CURRENT +++ b/packs-system/names/CURRENT @@ -1 +1 @@ -MANIFEST-000016 +MANIFEST-000026 diff --git a/packs-system/names/LOG b/packs-system/names/LOG index 302fd28..5d53886 100644 --- a/packs-system/names/LOG +++ b/packs-system/names/LOG @@ -1,15 +1,3 @@ -2026/05/10-10:14:24.437910 7ff6717ee6c0 Recovering log #13 -2026/05/10-10:14:24.448329 7ff6717ee6c0 Delete type=3 #11 -2026/05/10-10:14:24.448394 7ff6717ee6c0 Delete type=0 #13 -2026/05/10-10:15:09.176410 7ff6637fe6c0 Level-0 table #19: started -2026/05/10-10:15:09.181126 7ff6637fe6c0 Level-0 table #19: 36394 bytes OK -2026/05/10-10:15:09.187189 7ff6637fe6c0 Delete type=0 #17 -2026/05/10-10:15:09.193581 7ff6637fe6c0 Manual compaction at level-0 from '!tables!mgne-tbl-armor' @ 72057594037927935 : 1 .. '!tables.results!zuw1vPYn2wNmreKL' @ 0 : 0; will stop at (end) -2026/05/10-10:15:09.204527 7ff6637fe6c0 Manual compaction at level-1 from '!tables!mgne-tbl-armor' @ 72057594037927935 : 1 .. '!tables.results!zuw1vPYn2wNmreKL' @ 0 : 0; will stop at '!tables.results!zuw1vPYn2wNmreKL' @ 1225 : 1 -2026/05/10-10:15:09.204538 7ff6637fe6c0 Compacting 1@1 + 1@2 files -2026/05/10-10:15:09.208350 7ff6637fe6c0 Generated table #20@1: 436 keys, 40367 bytes -2026/05/10-10:15:09.208369 7ff6637fe6c0 Compacted 1@1 + 1@2 files => 40367 bytes -2026/05/10-10:15:09.215216 7ff6637fe6c0 compacted to: files[ 0 0 1 0 0 0 0 ] -2026/05/10-10:15:09.215286 7ff6637fe6c0 Delete type=2 #15 -2026/05/10-10:15:09.215397 7ff6637fe6c0 Delete type=2 #19 -2026/05/10-10:15:09.221933 7ff6637fe6c0 Manual compaction at level-1 from '!tables.results!zuw1vPYn2wNmreKL' @ 1225 : 1 .. '!tables.results!zuw1vPYn2wNmreKL' @ 0 : 0; will stop at (end) +2026/05/17-15:40:00.792467 7f16423fc6c0 Recovering log #23 +2026/05/17-15:40:00.805587 7f16423fc6c0 Delete type=3 #21 +2026/05/17-15:40:00.805652 7f16423fc6c0 Delete type=0 #23 diff --git a/packs-system/names/LOG.old b/packs-system/names/LOG.old index d1f848e..e4bc91c 100644 --- a/packs-system/names/LOG.old +++ b/packs-system/names/LOG.old @@ -1,15 +1,15 @@ -2026/05/10-00:06:25.485605 7fe7211fe6c0 Recovering log #8 -2026/05/10-00:06:25.496011 7fe7211fe6c0 Delete type=3 #6 -2026/05/10-00:06:25.496075 7fe7211fe6c0 Delete type=0 #8 -2026/05/10-09:35:40.453137 7fe6d37fe6c0 Level-0 table #14: started -2026/05/10-09:35:40.457072 7fe6d37fe6c0 Level-0 table #14: 36470 bytes OK -2026/05/10-09:35:40.463127 7fe6d37fe6c0 Delete type=0 #12 -2026/05/10-09:35:40.476711 7fe6d37fe6c0 Manual compaction at level-0 from '!tables!mgne-tbl-armor' @ 72057594037927935 : 1 .. '!tables.results!zGPTmr9d4kGBxfWN' @ 0 : 0; will stop at (end) -2026/05/10-09:35:40.486568 7fe6d37fe6c0 Manual compaction at level-1 from '!tables!mgne-tbl-armor' @ 72057594037927935 : 1 .. '!tables.results!zGPTmr9d4kGBxfWN' @ 0 : 0; will stop at '!tables.results!zku5SGHgSA4RLSlW' @ 660 : 0 -2026/05/10-09:35:40.486576 7fe6d37fe6c0 Compacting 1@1 + 1@2 files -2026/05/10-09:35:40.490562 7fe6d37fe6c0 Generated table #15@1: 436 keys, 40440 bytes -2026/05/10-09:35:40.490577 7fe6d37fe6c0 Compacted 1@1 + 1@2 files => 40440 bytes -2026/05/10-09:35:40.496634 7fe6d37fe6c0 compacted to: files[ 0 0 1 0 0 0 0 ] -2026/05/10-09:35:40.496732 7fe6d37fe6c0 Delete type=2 #10 -2026/05/10-09:35:40.496846 7fe6d37fe6c0 Delete type=2 #14 -2026/05/10-09:35:40.504136 7fe6d37fe6c0 Manual compaction at level-1 from '!tables.results!zku5SGHgSA4RLSlW' @ 660 : 0 .. '!tables.results!zGPTmr9d4kGBxfWN' @ 0 : 0; will stop at (end) +2026/05/17-13:26:05.132672 7f16423fc6c0 Recovering log #18 +2026/05/17-13:26:05.142773 7f16423fc6c0 Delete type=3 #16 +2026/05/17-13:26:05.142824 7f16423fc6c0 Delete type=0 #18 +2026/05/17-15:39:46.374557 7f1641bfb6c0 Level-0 table #24: started +2026/05/17-15:39:46.380380 7f1641bfb6c0 Level-0 table #24: 36276 bytes OK +2026/05/17-15:39:46.386967 7f1641bfb6c0 Delete type=0 #22 +2026/05/17-15:39:46.393402 7f1641bfb6c0 Manual compaction at level-0 from '!tables!mgne-tbl-armor' @ 72057594037927935 : 1 .. '!tables.results!zjYp8qq96SSCUgBi' @ 0 : 0; will stop at (end) +2026/05/17-15:39:46.403700 7f1641bfb6c0 Manual compaction at level-1 from '!tables!mgne-tbl-armor' @ 72057594037927935 : 1 .. '!tables.results!zjYp8qq96SSCUgBi' @ 0 : 0; will stop at '!tables.results!zuw1vPYn2wNmreKL' @ 1530 : 0 +2026/05/17-15:39:46.403711 7f1641bfb6c0 Compacting 1@1 + 1@2 files +2026/05/17-15:39:46.422351 7f1641bfb6c0 Generated table #25@1: 436 keys, 40426 bytes +2026/05/17-15:39:46.422374 7f1641bfb6c0 Compacted 1@1 + 1@2 files => 40426 bytes +2026/05/17-15:39:46.428514 7f1641bfb6c0 compacted to: files[ 0 0 1 0 0 0 0 ] +2026/05/17-15:39:46.428618 7f1641bfb6c0 Delete type=2 #20 +2026/05/17-15:39:46.428754 7f1641bfb6c0 Delete type=2 #24 +2026/05/17-15:39:46.428833 7f1641bfb6c0 Manual compaction at level-1 from '!tables.results!zuw1vPYn2wNmreKL' @ 1530 : 0 .. '!tables.results!zjYp8qq96SSCUgBi' @ 0 : 0; will stop at (end) diff --git a/packs-system/names/MANIFEST-000016 b/packs-system/names/MANIFEST-000016 deleted file mode 100644 index fd682e6..0000000 Binary files a/packs-system/names/MANIFEST-000016 and /dev/null differ diff --git a/packs-system/names/MANIFEST-000026 b/packs-system/names/MANIFEST-000026 new file mode 100644 index 0000000..8425194 Binary files /dev/null and b/packs-system/names/MANIFEST-000026 differ diff --git a/packs-system/resonations/000017.log b/packs-system/resonations/000017.log deleted file mode 100644 index e69de29..0000000 diff --git a/packs-system/resonations/000019.ldb b/packs-system/resonations/000024.ldb similarity index 84% rename from packs-system/resonations/000019.ldb rename to packs-system/resonations/000024.ldb index 905dec0..ab4250b 100644 Binary files a/packs-system/resonations/000019.ldb and b/packs-system/resonations/000024.ldb differ diff --git a/packs-system/resonations/000026.log b/packs-system/resonations/000026.log new file mode 100644 index 0000000..e906f55 Binary files /dev/null and b/packs-system/resonations/000026.log differ diff --git a/packs-system/resonations/CURRENT b/packs-system/resonations/CURRENT index 42c62b6..f622090 100644 --- a/packs-system/resonations/CURRENT +++ b/packs-system/resonations/CURRENT @@ -1 +1 @@ -MANIFEST-000015 +MANIFEST-000025 diff --git a/packs-system/resonations/LOG b/packs-system/resonations/LOG index af090dd..eec0c31 100644 --- a/packs-system/resonations/LOG +++ b/packs-system/resonations/LOG @@ -1,15 +1,3 @@ -2026/05/10-10:14:24.373627 7ff6717ee6c0 Recovering log #12 -2026/05/10-10:14:24.384402 7ff6717ee6c0 Delete type=3 #10 -2026/05/10-10:14:24.384452 7ff6717ee6c0 Delete type=0 #12 -2026/05/10-10:15:09.092296 7ff6637fe6c0 Level-0 table #18: started -2026/05/10-10:15:09.095463 7ff6637fe6c0 Level-0 table #18: 7117 bytes OK -2026/05/10-10:15:09.101458 7ff6637fe6c0 Delete type=0 #16 -2026/05/10-10:15:09.121502 7ff6637fe6c0 Manual compaction at level-0 from '!items!mgne-res-accelerate' @ 72057594037927935 : 1 .. '!items!null' @ 0 : 0; will stop at (end) -2026/05/10-10:15:09.130878 7ff6637fe6c0 Manual compaction at level-1 from '!items!mgne-res-accelerate' @ 72057594037927935 : 1 .. '!items!null' @ 0 : 0; will stop at '!items!null' @ 61 : 1 -2026/05/10-10:15:09.130883 7ff6637fe6c0 Compacting 1@1 + 1@2 files -2026/05/10-10:15:09.133861 7ff6637fe6c0 Generated table #19@1: 1 keys, 910 bytes -2026/05/10-10:15:09.133880 7ff6637fe6c0 Compacted 1@1 + 1@2 files => 910 bytes -2026/05/10-10:15:09.141020 7ff6637fe6c0 compacted to: files[ 0 0 2 0 0 0 0 ] -2026/05/10-10:15:09.141125 7ff6637fe6c0 Delete type=2 #14 -2026/05/10-10:15:09.141229 7ff6637fe6c0 Delete type=2 #18 -2026/05/10-10:15:09.159847 7ff6637fe6c0 Manual compaction at level-1 from '!items!null' @ 61 : 1 .. '!items!null' @ 0 : 0; will stop at (end) +2026/05/17-15:40:00.721783 7f16433fe6c0 Recovering log #22 +2026/05/17-15:40:00.732595 7f16433fe6c0 Delete type=3 #20 +2026/05/17-15:40:00.732660 7f16433fe6c0 Delete type=0 #22 diff --git a/packs-system/resonations/LOG.old b/packs-system/resonations/LOG.old index 027e28f..c5d6c91 100644 --- a/packs-system/resonations/LOG.old +++ b/packs-system/resonations/LOG.old @@ -1,15 +1,15 @@ -2026/05/10-00:06:25.421327 7fe7219ff6c0 Recovering log #8 -2026/05/10-00:06:25.431320 7fe7219ff6c0 Delete type=3 #6 -2026/05/10-00:06:25.431377 7fe7219ff6c0 Delete type=0 #8 -2026/05/10-09:35:40.375666 7fe6d37fe6c0 Level-0 table #13: started -2026/05/10-09:35:40.378697 7fe6d37fe6c0 Level-0 table #13: 7117 bytes OK -2026/05/10-09:35:40.385574 7fe6d37fe6c0 Delete type=0 #11 -2026/05/10-09:35:40.404692 7fe6d37fe6c0 Manual compaction at level-0 from '!items!mgne-res-accelerate' @ 72057594037927935 : 1 .. '!items!null' @ 0 : 0; will stop at (end) -2026/05/10-09:35:40.423667 7fe6d37fe6c0 Manual compaction at level-1 from '!items!mgne-res-accelerate' @ 72057594037927935 : 1 .. '!items!null' @ 0 : 0; will stop at '!items!null' @ 41 : 1 -2026/05/10-09:35:40.423674 7fe6d37fe6c0 Compacting 1@1 + 1@2 files -2026/05/10-09:35:40.426694 7fe6d37fe6c0 Generated table #14@1: 1 keys, 910 bytes -2026/05/10-09:35:40.426710 7fe6d37fe6c0 Compacted 1@1 + 1@2 files => 910 bytes -2026/05/10-09:35:40.433702 7fe6d37fe6c0 compacted to: files[ 0 0 2 0 0 0 0 ] -2026/05/10-09:35:40.433797 7fe6d37fe6c0 Delete type=2 #9 -2026/05/10-09:35:40.433894 7fe6d37fe6c0 Delete type=2 #13 -2026/05/10-09:35:40.443122 7fe6d37fe6c0 Manual compaction at level-1 from '!items!null' @ 41 : 1 .. '!items!null' @ 0 : 0; will stop at (end) +2026/05/17-13:26:05.061355 7f1642bfd6c0 Recovering log #17 +2026/05/17-13:26:05.072897 7f1642bfd6c0 Delete type=3 #15 +2026/05/17-13:26:05.072969 7f1642bfd6c0 Delete type=0 #17 +2026/05/17-15:39:46.204241 7f1641bfb6c0 Level-0 table #23: started +2026/05/17-15:39:46.207933 7f1641bfb6c0 Level-0 table #23: 7117 bytes OK +2026/05/17-15:39:46.215151 7f1641bfb6c0 Delete type=0 #21 +2026/05/17-15:39:46.215347 7f1641bfb6c0 Manual compaction at level-0 from '!items!mgne-res-accelerate' @ 72057594037927935 : 1 .. '!items!null' @ 0 : 0; will stop at (end) +2026/05/17-15:39:46.244725 7f1641bfb6c0 Manual compaction at level-1 from '!items!mgne-res-accelerate' @ 72057594037927935 : 1 .. '!items!null' @ 0 : 0; will stop at '!items!null' @ 81 : 1 +2026/05/17-15:39:46.244731 7f1641bfb6c0 Compacting 1@1 + 1@2 files +2026/05/17-15:39:46.247722 7f1641bfb6c0 Generated table #24@1: 1 keys, 910 bytes +2026/05/17-15:39:46.247731 7f1641bfb6c0 Compacted 1@1 + 1@2 files => 910 bytes +2026/05/17-15:39:46.253408 7f1641bfb6c0 compacted to: files[ 0 0 2 0 0 0 0 ] +2026/05/17-15:39:46.253506 7f1641bfb6c0 Delete type=2 #19 +2026/05/17-15:39:46.253663 7f1641bfb6c0 Delete type=2 #23 +2026/05/17-15:39:46.253842 7f1641bfb6c0 Manual compaction at level-1 from '!items!null' @ 81 : 1 .. '!items!null' @ 0 : 0; will stop at (end) diff --git a/packs-system/resonations/MANIFEST-000015 b/packs-system/resonations/MANIFEST-000015 deleted file mode 100644 index 95d761b..0000000 Binary files a/packs-system/resonations/MANIFEST-000015 and /dev/null differ diff --git a/packs-system/resonations/MANIFEST-000025 b/packs-system/resonations/MANIFEST-000025 new file mode 100644 index 0000000..b6dd3c5 Binary files /dev/null and b/packs-system/resonations/MANIFEST-000025 differ diff --git a/packs-system/weapons/000017.log b/packs-system/weapons/000017.log deleted file mode 100644 index e69de29..0000000 diff --git a/packs-system/weapons/000019.ldb b/packs-system/weapons/000024.ldb similarity index 71% rename from packs-system/weapons/000019.ldb rename to packs-system/weapons/000024.ldb index 41ee4fd..1142f51 100644 Binary files a/packs-system/weapons/000019.ldb and b/packs-system/weapons/000024.ldb differ diff --git a/packs-system/weapons/000026.log b/packs-system/weapons/000026.log new file mode 100644 index 0000000..f09842e Binary files /dev/null and b/packs-system/weapons/000026.log differ diff --git a/packs-system/weapons/CURRENT b/packs-system/weapons/CURRENT index 42c62b6..f622090 100644 --- a/packs-system/weapons/CURRENT +++ b/packs-system/weapons/CURRENT @@ -1 +1 @@ -MANIFEST-000015 +MANIFEST-000025 diff --git a/packs-system/weapons/LOG b/packs-system/weapons/LOG index 77b5198..dca9fa9 100644 --- a/packs-system/weapons/LOG +++ b/packs-system/weapons/LOG @@ -1,15 +1,3 @@ -2026/05/10-10:14:24.413909 7ff671fef6c0 Recovering log #12 -2026/05/10-10:14:24.424039 7ff671fef6c0 Delete type=3 #10 -2026/05/10-10:14:24.424092 7ff671fef6c0 Delete type=0 #12 -2026/05/10-10:15:09.111128 7ff6637fe6c0 Level-0 table #18: started -2026/05/10-10:15:09.115029 7ff6637fe6c0 Level-0 table #18: 1965 bytes OK -2026/05/10-10:15:09.121359 7ff6637fe6c0 Delete type=0 #16 -2026/05/10-10:15:09.121532 7ff6637fe6c0 Manual compaction at level-0 from '!items!mgne-wpn-club' @ 72057594037927935 : 1 .. '!items!null' @ 0 : 0; will stop at (end) -2026/05/10-10:15:09.150422 7ff6637fe6c0 Manual compaction at level-1 from '!items!mgne-wpn-club' @ 72057594037927935 : 1 .. '!items!null' @ 0 : 0; will stop at '!items!null' @ 37 : 1 -2026/05/10-10:15:09.150429 7ff6637fe6c0 Compacting 1@1 + 1@2 files -2026/05/10-10:15:09.153398 7ff6637fe6c0 Generated table #19@1: 1 keys, 626 bytes -2026/05/10-10:15:09.153408 7ff6637fe6c0 Compacted 1@1 + 1@2 files => 626 bytes -2026/05/10-10:15:09.159589 7ff6637fe6c0 compacted to: files[ 0 0 2 0 0 0 0 ] -2026/05/10-10:15:09.159695 7ff6637fe6c0 Delete type=2 #14 -2026/05/10-10:15:09.159778 7ff6637fe6c0 Delete type=2 #18 -2026/05/10-10:15:09.159866 7ff6637fe6c0 Manual compaction at level-1 from '!items!null' @ 37 : 1 .. '!items!null' @ 0 : 0; will stop at (end) +2026/05/17-15:40:00.764360 7f16423fc6c0 Recovering log #22 +2026/05/17-15:40:00.775885 7f16423fc6c0 Delete type=3 #20 +2026/05/17-15:40:00.775980 7f16423fc6c0 Delete type=0 #22 diff --git a/packs-system/weapons/LOG.old b/packs-system/weapons/LOG.old index c1eaa27..1f08e71 100644 --- a/packs-system/weapons/LOG.old +++ b/packs-system/weapons/LOG.old @@ -1,15 +1,15 @@ -2026/05/10-00:06:25.460392 7fe7211fe6c0 Recovering log #8 -2026/05/10-00:06:25.470093 7fe7211fe6c0 Delete type=3 #6 -2026/05/10-00:06:25.470147 7fe7211fe6c0 Delete type=0 #8 -2026/05/10-09:35:40.385681 7fe6d37fe6c0 Level-0 table #13: started -2026/05/10-09:35:40.389078 7fe6d37fe6c0 Level-0 table #13: 1965 bytes OK -2026/05/10-09:35:40.394971 7fe6d37fe6c0 Delete type=0 #11 -2026/05/10-09:35:40.404700 7fe6d37fe6c0 Manual compaction at level-0 from '!items!mgne-wpn-club' @ 72057594037927935 : 1 .. '!items!null' @ 0 : 0; will stop at (end) -2026/05/10-09:35:40.414585 7fe6d37fe6c0 Manual compaction at level-1 from '!items!mgne-wpn-club' @ 72057594037927935 : 1 .. '!items!null' @ 0 : 0; will stop at '!items!null' @ 25 : 1 -2026/05/10-09:35:40.414592 7fe6d37fe6c0 Compacting 1@1 + 1@2 files -2026/05/10-09:35:40.417590 7fe6d37fe6c0 Generated table #14@1: 1 keys, 626 bytes -2026/05/10-09:35:40.417600 7fe6d37fe6c0 Compacted 1@1 + 1@2 files => 626 bytes -2026/05/10-09:35:40.423435 7fe6d37fe6c0 compacted to: files[ 0 0 2 0 0 0 0 ] -2026/05/10-09:35:40.423523 7fe6d37fe6c0 Delete type=2 #9 -2026/05/10-09:35:40.423607 7fe6d37fe6c0 Delete type=2 #13 -2026/05/10-09:35:40.443110 7fe6d37fe6c0 Manual compaction at level-1 from '!items!null' @ 25 : 1 .. '!items!null' @ 0 : 0; will stop at (end) +2026/05/17-13:26:05.105018 7f16423fc6c0 Recovering log #17 +2026/05/17-13:26:05.114474 7f16423fc6c0 Delete type=3 #15 +2026/05/17-13:26:05.114527 7f16423fc6c0 Delete type=0 #17 +2026/05/17-15:39:46.183373 7f1641bfb6c0 Level-0 table #23: started +2026/05/17-15:39:46.187330 7f1641bfb6c0 Level-0 table #23: 1965 bytes OK +2026/05/17-15:39:46.194074 7f1641bfb6c0 Delete type=0 #21 +2026/05/17-15:39:46.215323 7f1641bfb6c0 Manual compaction at level-0 from '!items!mgne-wpn-club' @ 72057594037927935 : 1 .. '!items!null' @ 0 : 0; will stop at (end) +2026/05/17-15:39:46.215362 7f1641bfb6c0 Manual compaction at level-1 from '!items!mgne-wpn-club' @ 72057594037927935 : 1 .. '!items!null' @ 0 : 0; will stop at '!items!null' @ 49 : 1 +2026/05/17-15:39:46.215370 7f1641bfb6c0 Compacting 1@1 + 1@2 files +2026/05/17-15:39:46.218482 7f1641bfb6c0 Generated table #24@1: 1 keys, 626 bytes +2026/05/17-15:39:46.218497 7f1641bfb6c0 Compacted 1@1 + 1@2 files => 626 bytes +2026/05/17-15:39:46.224247 7f1641bfb6c0 compacted to: files[ 0 0 2 0 0 0 0 ] +2026/05/17-15:39:46.224396 7f1641bfb6c0 Delete type=2 #19 +2026/05/17-15:39:46.224564 7f1641bfb6c0 Delete type=2 #23 +2026/05/17-15:39:46.253795 7f1641bfb6c0 Manual compaction at level-1 from '!items!null' @ 49 : 1 .. '!items!null' @ 0 : 0; will stop at (end) diff --git a/packs-system/weapons/MANIFEST-000015 b/packs-system/weapons/MANIFEST-000015 deleted file mode 100644 index 08b70ae..0000000 Binary files a/packs-system/weapons/MANIFEST-000015 and /dev/null differ diff --git a/packs-system/weapons/MANIFEST-000025 b/packs-system/weapons/MANIFEST-000025 new file mode 100644 index 0000000..b57f97e Binary files /dev/null and b/packs-system/weapons/MANIFEST-000025 differ diff --git a/system.json b/system.json index 2d78841..f6530a3 100644 --- a/system.json +++ b/system.json @@ -35,9 +35,7 @@ }, "creature": { "htmlFields": [ - "description", - "special", - "notes" + "description" ] }, "companion": { @@ -45,6 +43,11 @@ "description", "notes" ] + }, + "party": { + "htmlFields": [ + "notes" + ] } }, "Item": { @@ -82,6 +85,11 @@ "htmlFields": [ "description" ] + }, + "creature-trait": { + "htmlFields": [ + "description" + ] } } }, diff --git a/templates/armor.hbs b/templates/armor.hbs index 15735d5..93c7f00 100644 --- a/templates/armor.hbs +++ b/templates/armor.hbs @@ -5,7 +5,7 @@ -