diff --git a/css/cde-theme.css b/css/cde-theme.css
index 75a307b..332c205 100644
--- a/css/cde-theme.css
+++ b/css/cde-theme.css
@@ -986,9 +986,11 @@ section.npc .cde-neon-tabs .item.active {
gap: 6px;
padding: 6px 8px 4px;
border-top: 1px solid #1a2436;
+ pointer-events: auto;
}
.cde-chat-app-buttons .cde-chat-btn {
flex: 1 1 0;
+ pointer-events: auto;
display: flex;
align-items: center;
justify-content: center;
@@ -1024,19 +1026,25 @@ section.npc .cde-neon-tabs .item.active {
padding: 12px;
}
.cde-loksyu-standalone .cde-loksyu-elements {
- display: grid;
- grid-template-columns: repeat(5, 1fr);
+ display: flex;
+ flex-wrap: wrap;
gap: 8px;
- margin-bottom: 12px;
+ justify-content: center;
+ margin-bottom: 10px;
}
.cde-loksyu-standalone .cde-lok-card {
+ flex: 0 0 calc(33.333% - 6px);
+ min-width: 140px;
+ max-width: 160px;
background: rgba(16, 22, 34, 0.8);
border: 1px solid #1a2436;
border-radius: 6px;
- padding: 8px 6px;
+ padding: 10px 8px;
display: flex;
flex-direction: column;
gap: 6px;
+ min-width: 0;
+ overflow: hidden;
transition: border-color 0.2s;
}
.cde-loksyu-standalone .cde-lok-card--wood:hover {
@@ -1151,8 +1159,10 @@ section.npc .cde-neon-tabs .item.active {
margin: 6px 0;
}
.cde-loksyu-standalone .cde-loksyu-visual-row .cde-lok-visual {
- max-width: 100%;
- max-height: 80px;
+ max-width: 120px;
+ max-height: 160px;
+ width: auto;
+ height: auto;
opacity: 0.6;
}
.cde-loksyu-standalone .cde-lok-footer {
@@ -1201,11 +1211,12 @@ section.npc .cde-neon-tabs .item.active {
font-family: serif;
}
.cde-tinji-standalone .cde-tinji-label {
- font-size: 10px;
+ font-size: 11px;
font-family: "Orbitron", sans-serif;
text-transform: uppercase;
letter-spacing: 0.12em;
- color: #7d94b8;
+ color: #e2e8f4;
+ opacity: 0.75;
}
.cde-tinji-standalone .cde-tinji-counter {
display: flex;
@@ -1214,12 +1225,12 @@ section.npc .cde-neon-tabs .item.active {
margin: 4px 0;
}
.cde-tinji-standalone .cde-tinji-step {
- width: 28px;
- height: 28px;
- background: #101622;
- border: 1px solid #1a2436;
+ width: 30px;
+ height: 30px;
+ background: #0d1520;
+ border: 1px solid #263853;
border-radius: 50%;
- color: #7d94b8;
+ color: #e2e8f4;
font-size: 18px;
cursor: pointer;
display: flex;
@@ -1230,9 +1241,9 @@ section.npc .cde-neon-tabs .item.active {
padding: 0;
}
.cde-tinji-standalone .cde-tinji-step:hover {
- background: rgba(255, 61, 90, 0.15);
+ background: rgba(255, 61, 90, 0.25);
border-color: #ff3d5a;
- color: #ff3d5a;
+ color: #e2e8f4;
}
.cde-tinji-standalone .cde-tinji-direct {
width: 72px;
@@ -1268,58 +1279,247 @@ section.npc .cde-neon-tabs .item.active {
display: flex;
align-items: center;
gap: 5px;
- padding: 5px 12px;
+ padding: 6px 12px;
font-size: 11px;
+ font-weight: 600;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
+ letter-spacing: 0.03em;
}
.cde-tinji-standalone .cde-tinji-spend-btn {
- background: rgba(255, 61, 90, 0.15);
+ background: rgba(255, 61, 90, 0.25);
border: 1px solid #ff3d5a;
- color: #ff3d5a;
+ color: #e2e8f4;
}
.cde-tinji-standalone .cde-tinji-spend-btn:hover {
- background: rgba(255, 61, 90, 0.3);
+ background: rgba(255, 61, 90, 0.45);
+ border-color: #ff7085;
}
.cde-tinji-standalone .cde-tinji-spend-btn[disabled] {
- opacity: 0.4;
+ opacity: 0.55;
cursor: not-allowed;
pointer-events: none;
}
.cde-tinji-standalone .cde-tinji-reset-btn {
- background: #101622;
- border: 1px solid #1a2436;
- color: #7d94b8;
+ background: #0d1520;
+ border: 1px solid #263853;
+ color: #e2e8f4;
+ opacity: 0.85;
}
.cde-tinji-standalone .cde-tinji-reset-btn:hover {
border-color: #e2e8f4;
- color: #e2e8f4;
+ opacity: 1;
}
.cde-tinji-standalone .cde-tinji-visual {
width: 90px;
height: auto;
- opacity: 0.6;
+ opacity: 0.85;
flex-shrink: 0;
}
.cde-tinji-spend-msg {
display: flex;
align-items: center;
- gap: 6px;
- font-size: 12px;
- padding: 6px 8px;
- background: rgba(255, 61, 90, 0.08);
- border-left: 3px solid #ff3d5a;
+ gap: 8px;
+ font-size: 13px;
+ padding: 8px 10px;
+ background: rgba(255, 61, 90, 0.18);
+ border: 1px solid rgba(255, 61, 90, 0.35);
+ border-left: 4px solid #ff3d5a;
border-radius: 4px;
color: #e2e8f4;
}
+.cde-tinji-spend-msg .cde-tinji-icon {
+ font-size: 18px;
+ color: #ff3d5a;
+ text-shadow: 0 0 6px rgba(255, 61, 90, 0.7);
+ flex-shrink: 0;
+ font-weight: 700;
+ line-height: 1;
+}
.cde-tinji-spend-msg i {
color: #ff3d5a;
+ font-size: 15px;
+ filter: drop-shadow(0 0 4px rgba(255, 61, 90, 0.6));
+ flex-shrink: 0;
+}
+.cde-tinji-spend-msg strong {
+ color: #ff3d5a;
+ font-size: 13px;
+ letter-spacing: 0.02em;
}
.cde-tinji-spend-msg .cde-tinji-remain {
margin-left: auto;
+ font-size: 11px;
+ color: rgba(226, 232, 244, 0.65);
+ font-style: italic;
+ white-space: nowrap;
+}
+.cde-loksyu-draw-msg {
+ display: flex;
+ flex-direction: column;
+ gap: 5px;
+ font-size: 12px;
+ padding: 9px 11px;
+ background: linear-gradient(135deg, rgba(0, 212, 212, 0.1) 0%, rgba(16, 22, 34, 0.95) 100%);
+ border: 1px solid rgba(0, 212, 212, 0.4);
+ border-left: 4px solid #00d4d4;
+ border-radius: 5px;
+ color: #e2e8f4;
+ box-shadow: 0 2px 8px rgba(0, 212, 212, 0.12);
+}
+.cde-loksyu-draw-msg .cde-loksyu-draw-header {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 5px;
+ font-size: 13px;
+ line-height: 1.4;
+}
+.cde-loksyu-draw-msg .cde-loksyu-draw-aspect-icon {
+ width: 20px;
+ height: 20px;
+ object-fit: contain;
+ filter: drop-shadow(0 0 4px rgba(0, 212, 212, 0.6));
+ flex-shrink: 0;
+}
+.cde-loksyu-draw-msg .cde-loksyu-draw-user {
+ font-weight: 700;
+ color: #00d4d4;
+ letter-spacing: 0.03em;
+}
+.cde-loksyu-draw-msg .cde-loksyu-draw-action {
+ color: rgba(226, 232, 244, 0.65);
+ font-size: 12px;
+ font-style: italic;
+}
+.cde-loksyu-draw-msg .cde-loksyu-draw-type {
+ font-weight: 700;
+ font-size: 13px;
+ color: #c9a227;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ text-shadow: 0 0 6px rgba(201, 162, 39, 0.4);
+}
+.cde-loksyu-draw-msg .cde-loksyu-draw-from {
+ color: rgba(226, 232, 244, 0.6);
+ font-size: 11px;
+}
+.cde-loksyu-draw-msg .cde-loksyu-draw-from em {
+ color: #00d4d4;
+ font-style: normal;
+ font-weight: 600;
+}
+.cde-loksyu-draw-msg .cde-loksyu-draw-footer {
+ display: flex;
+ align-items: center;
+ gap: 5px;
+ font-size: 11px;
+ color: rgba(226, 232, 244, 0.55);
+ padding-top: 4px;
+ border-top: 1px solid rgba(0, 212, 212, 0.2);
+}
+.cde-loksyu-draw-msg .cde-loksyu-draw-footer i {
+ color: #00d4d4;
font-size: 10px;
- color: #7d94b8;
+}
+.cde-loksyu-draw-msg .cde-loksyu-draw-footer .cde-loksyu-remain {
+ color: #00d4d4;
+ font-weight: 700;
+}
+.cde-roll-actions {
+ margin-top: 10px;
+ padding-top: 8px;
+ border-top: 1px solid rgba(26, 36, 54, 0.6);
+}
+.cde-roll-actions .cde-roll-actions-title {
+ font-size: 10px;
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ color: rgba(226, 232, 244, 0.5);
+ margin-bottom: 6px;
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
+.cde-roll-actions .cde-roll-actions-title i {
+ font-size: 9px;
+}
+.cde-roll-actions .cde-roll-actions-btns {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 5px;
+}
+.cde-roll-actions .cde-roll-action-btn {
+ display: flex;
+ align-items: center;
+ gap: 5px;
+ padding: 4px 8px;
+ font-size: 11px;
+ font-family: "Courier New", Courier, monospace;
+ border-radius: 3px;
+ border: 1px solid rgba(26, 36, 54, 0.8);
+ background: rgba(16, 22, 34, 0.9);
+ color: #e2e8f4;
+ cursor: pointer;
+ transition: background 0.15s, border-color 0.15s, box-shadow 0.15s;
+ pointer-events: auto;
+ line-height: 1.4;
+}
+.cde-roll-actions .cde-roll-action-btn:hover:not(:disabled) {
+ border-color: rgba(0, 212, 212, 0.7);
+ background: rgba(0, 212, 212, 0.12);
+ box-shadow: 0 0 6px rgba(0, 212, 212, 0.25);
+ color: #00d4d4;
+}
+.cde-roll-actions .cde-roll-action-btn:disabled {
+ opacity: 0.35;
+ cursor: not-allowed;
+}
+.cde-roll-actions .cde-roll-action-btn .cde-roll-action-icon {
+ width: 16px;
+ height: 16px;
+ object-fit: contain;
+ filter: drop-shadow(0 0 2px rgba(0, 212, 212, 0.4));
+}
+.cde-roll-actions .cde-roll-action-btn .cde-roll-action-label {
+ flex: 1;
+}
+.cde-roll-actions .cde-roll-action-btn .cde-roll-action-count {
+ background: rgba(26, 36, 54, 0.6);
+ border-radius: 2px;
+ padding: 1px 4px;
+ font-size: 10px;
+ font-weight: 700;
+ min-width: 18px;
+ text-align: center;
+}
+.cde-roll-actions .cde-roll-action-btn .cde-roll-action-tinji-char {
+ font-size: 14px;
+ line-height: 1;
+ color: #ff3d5a;
+ text-shadow: 0 0 4px rgba(255, 61, 90, 0.6);
+}
+.cde-roll-actions .cde-roll-action-btn.cde-roll-action--success:hover:not(:disabled) {
+ border-color: rgba(201, 162, 39, 0.7);
+ background: rgba(201, 162, 39, 0.1);
+ box-shadow: 0 0 6px rgba(201, 162, 39, 0.25);
+ color: #c9a227;
+}
+.cde-roll-actions .cde-roll-action-btn.cde-roll-action--faste:hover:not(:disabled) {
+ border-color: rgba(0, 212, 212, 0.7);
+ background: rgba(0, 212, 212, 0.1);
+ box-shadow: 0 0 6px rgba(0, 212, 212, 0.25);
+ color: #00d4d4;
+}
+.cde-roll-actions .cde-roll-action-btn.cde-roll-action--tinji:hover:not(:disabled) {
+ border-color: rgba(255, 61, 90, 0.7);
+ background: rgba(255, 61, 90, 0.12);
+ box-shadow: 0 0 6px rgba(255, 61, 90, 0.3);
+ color: #ff3d5a;
+}
+.cde-roll-actions .cde-roll-action-btn.cde-roll-action--tinji:hover:not(:disabled) .cde-roll-action-count {
+ background: rgba(255, 61, 90, 0.25);
}
.cde-loksyu-grid {
display: grid;
@@ -3189,6 +3389,38 @@ ol.item-list li.item .item-controls a.item-control:hover {
letter-spacing: 0.1em;
color: #7d94b8;
}
+.cde-roll-result .cde-rr-hero .cde-rr-loksyu-bonus {
+ display: inline-flex;
+ align-items: center;
+ gap: 3px;
+ font-size: 13px;
+ font-weight: 700;
+ padding: 2px 6px;
+ border-radius: 3px;
+ animation: cde-pulse-in 0.3s ease;
+}
+.cde-roll-result .cde-rr-hero .cde-rr-loksyu-bonus.cde-rr-loksyu-bonus--success {
+ background: rgba(201, 162, 39, 0.2);
+ border: 1px solid rgba(201, 162, 39, 0.5);
+ color: #c9a227;
+ text-shadow: 0 0 6px rgba(201, 162, 39, 0.6);
+}
+.cde-roll-result .cde-rr-hero .cde-rr-loksyu-bonus.cde-rr-loksyu-bonus--faste {
+ background: rgba(0, 212, 212, 0.15);
+ border: 1px solid rgba(0, 212, 212, 0.4);
+ color: #00d4d4;
+ text-shadow: 0 0 6px rgba(0, 212, 212, 0.5);
+}
+@keyframes cde-pulse-in {
+ from {
+ opacity: 0;
+ transform: scale(0.7);
+ }
+ to {
+ opacity: 1;
+ transform: scale(1);
+ }
+}
.cde-roll-result .cde-rr-details {
display: flex;
flex-direction: column;
@@ -3212,6 +3444,17 @@ ol.item-list li.item .item-controls a.item-control:hover {
text-align: center;
text-shadow: 0 0 6px currentColor;
}
+.cde-roll-result .cde-rr-row .cde-rr-loksyu-bonus {
+ font-size: 10px;
+ font-weight: 700;
+ padding: 1px 4px;
+ border-radius: 2px;
+ background: rgba(0, 212, 212, 0.15);
+ border: 1px solid rgba(0, 212, 212, 0.4);
+ color: #00d4d4;
+ text-shadow: 0 0 4px rgba(0, 212, 212, 0.5);
+ flex-shrink: 0;
+}
.cde-roll-result .cde-rr-row .cde-rr-icon {
width: 22px;
height: 22px;
diff --git a/css/cde-theme.less b/css/cde-theme.less
index 081a451..f47fdf2 100644
--- a/css/cde-theme.less
+++ b/css/cde-theme.less
@@ -925,50 +925,135 @@ section.npc .cde-neon-tabs .item.active { color: @cde-supernatural; borde
}
// NPC supernatural item cards
-.cde-supernatural-list {
- .cde-supernatural-item {
- background: fade(@cde-surface, 60%);
- border: 1px solid @cde-border;
- border-radius: 4px;
- margin-bottom: 6px;
- padding: 6px;
- list-style: none;
+// ── Supernatural abilities card (NPC) ───────────────────────────────────────
+
+.cde-super-add-row {
+ display: flex;
+ justify-content: flex-end;
+ padding: 4px 0 8px;
+}
+
+.cde-super-add-btn {
+ display: inline-flex;
+ align-items: center;
+ gap: 5px;
+ font-size: 11px;
+ font-family: "Averia", sans-serif;
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ color: @cde-muted;
+ cursor: pointer;
+ padding: 4px 10px;
+ border-radius: @cde-radius;
+ border: 1px solid @cde-border;
+ transition: color 0.12s, border-color 0.12s, background 0.12s;
+
+ i { font-size: 10px; }
+
+ &:hover { color: @cde-supernatural; border-color: @cde-supernatural; background: fade(@cde-supernatural, 8%); }
+}
+
+.cde-super-card {
+ border: 1px solid @cde-border;
+ border-left: 3px solid @cde-supernatural;
+ border-radius: @cde-radius;
+ background: fade(@cde-surface, 70%);
+ margin-bottom: 10px;
+ overflow: hidden;
+ transition: box-shadow 0.15s;
+
+ &:hover {
+ box-shadow: 0 0 8px fade(@cde-supernatural, 20%);
+
+ .cde-super-controls { opacity: 1; }
}
+}
- .cde-supernatural-header {
- align-items: center;
- gap: 8px;
+.cde-super-header {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 8px 10px;
+}
- img { border-radius: 4px; flex-shrink: 0; }
- }
+.cde-super-img {
+ width: 28px;
+ height: 28px;
+ object-fit: contain;
+ border-radius: 4px;
+ flex-shrink: 0;
+}
- .cde-supernatural-info {
- flex: 1 1 0;
+.cde-super-info {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 3px;
+ min-width: 0;
+}
+
+.cde-super-name {
+ font-size: 14px;
+ font-weight: 700;
+ font-family: "Averia", sans-serif;
+ color: @cde-text;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.cde-super-meta {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
+
+.cde-super-controls {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ opacity: 0;
+ transition: opacity 0.12s;
+ flex-shrink: 0;
+
+ a {
display: flex;
align-items: center;
- gap: 6px;
- min-width: 0;
-
- .cde-supernatural-name {
- font-size: 12px;
- font-weight: 600;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
- }
-
- .cde-supernatural-desc {
- font-size: 10px;
+ justify-content: center;
+ width: 22px;
+ height: 22px;
+ border-radius: 3px;
color: @cde-muted;
- margin-top: 4px;
- padding-left: 36px;
- line-height: 1.4;
+ cursor: pointer;
+ transition: color 0.12s, background 0.12s;
- p { margin: 0; }
+ i { font-size: 11px; }
+
+ &:hover { color: @cde-text; background: fade(@cde-border-hi, 30%); }
}
}
+.cde-super-desc {
+ padding: 6px 12px 8px 46px;
+ border-top: 1px solid fade(@cde-border, 60%);
+ background: fade(@cde-surface, 40%);
+ font-size: 11px;
+ color: @cde-muted;
+ line-height: 1.5;
+
+ p { margin: 0 0 4px; &:last-child { margin-bottom: 0; } }
+ em { color: @cde-text; }
+ strong { color: @cde-supernatural; }
+}
+
+.cde-super-empty {
+ padding: 16px;
+ text-align: center;
+ font-size: 12px;
+ color: @cde-muted;
+ font-style: italic;
+}
+
// NPC vitality / hei tracker
.cde-npc-tracks {
margin-top: @cde-gap;
@@ -1040,9 +1125,11 @@ section.npc .cde-neon-tabs .item.active { color: @cde-supernatural; borde
gap: 6px;
padding: 6px 8px 4px;
border-top: 1px solid @cde-border;
+ pointer-events: auto; // sidebar has pointer-events:none — must override
.cde-chat-btn {
flex: 1 1 0;
+ pointer-events: auto;
display: flex;
align-items: center;
justify-content: center;
@@ -1086,20 +1173,26 @@ section.npc .cde-neon-tabs .item.active { color: @cde-supernatural; borde
}
.cde-loksyu-elements {
- display: grid;
- grid-template-columns: repeat(5, 1fr);
+ display: flex;
+ flex-wrap: wrap;
gap: 8px;
- margin-bottom: 12px;
+ justify-content: center;
+ margin-bottom: 10px;
}
.cde-lok-card {
+ flex: 0 0 calc(33.333% - 6px);
+ min-width: 140px;
+ max-width: 160px;
background: fade(@cde-surface, 80%);
border: 1px solid @cde-border;
border-radius: 6px;
- padding: 8px 6px;
+ padding: 10px 8px;
display: flex;
flex-direction: column;
gap: 6px;
+ min-width: 0;
+ overflow: hidden;
transition: border-color 0.2s;
&--wood { &:hover { border-color: #4a9e3f; } .cde-lok-input:focus { border-bottom-color: #4a9e3f; } }
@@ -1191,8 +1284,10 @@ section.npc .cde-neon-tabs .item.active { color: @cde-supernatural; borde
margin: 6px 0;
.cde-lok-visual {
- max-width: 100%;
- max-height: 80px;
+ max-width: 120px;
+ max-height: 160px;
+ width: auto;
+ height: auto;
opacity: 0.6;
}
}
@@ -1254,11 +1349,12 @@ section.npc .cde-neon-tabs .item.active { color: @cde-supernatural; borde
}
.cde-tinji-label {
- font-size: 10px;
+ font-size: 11px;
font-family: "Orbitron", sans-serif;
text-transform: uppercase;
letter-spacing: 0.12em;
- color: @cde-muted;
+ color: @cde-text;
+ opacity: 0.75;
}
.cde-tinji-counter {
@@ -1269,12 +1365,12 @@ section.npc .cde-neon-tabs .item.active { color: @cde-supernatural; borde
}
.cde-tinji-step {
- width: 28px;
- height: 28px;
- background: @cde-surface;
- border: 1px solid @cde-border;
+ width: 30px;
+ height: 30px;
+ background: @cde-surface2;
+ border: 1px solid @cde-border-hi;
border-radius: 50%;
- color: @cde-muted;
+ color: @cde-text;
font-size: 18px;
cursor: pointer;
display: flex;
@@ -1285,9 +1381,9 @@ section.npc .cde-neon-tabs .item.active { color: @cde-supernatural; borde
padding: 0;
&:hover {
- background: fade(@cde-kungfu, 15%);
+ background: fade(@cde-kungfu, 25%);
border-color: @cde-kungfu;
- color: @cde-kungfu;
+ color: @cde-text;
}
}
@@ -1324,34 +1420,37 @@ section.npc .cde-neon-tabs .item.active { color: @cde-supernatural; borde
display: flex;
align-items: center;
gap: 5px;
- padding: 5px 12px;
+ padding: 6px 12px;
font-size: 11px;
+ font-weight: 600;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
+ letter-spacing: 0.03em;
}
.cde-tinji-spend-btn {
- background: fade(@cde-kungfu, 15%);
+ background: fade(@cde-kungfu, 25%);
border: 1px solid @cde-kungfu;
- color: @cde-kungfu;
+ color: @cde-text;
- &:hover { background: fade(@cde-kungfu, 30%); }
- &[disabled] { opacity: 0.4; cursor: not-allowed; pointer-events: none; }
+ &:hover { background: fade(@cde-kungfu, 45%); border-color: lighten(@cde-kungfu, 10%); }
+ &[disabled] { opacity: 0.55; cursor: not-allowed; pointer-events: none; }
}
.cde-tinji-reset-btn {
- background: @cde-surface;
- border: 1px solid @cde-border;
- color: @cde-muted;
+ background: @cde-surface2;
+ border: 1px solid @cde-border-hi;
+ color: @cde-text;
+ opacity: 0.85;
- &:hover { border-color: @cde-text; color: @cde-text; }
+ &:hover { border-color: @cde-text; opacity: 1; }
}
.cde-tinji-visual {
width: 90px;
height: auto;
- opacity: 0.6;
+ opacity: 0.85;
flex-shrink: 0;
}
}
@@ -1360,20 +1459,241 @@ section.npc .cde-neon-tabs .item.active { color: @cde-supernatural; borde
.cde-tinji-spend-msg {
display: flex;
align-items: center;
- gap: 6px;
- font-size: 12px;
- padding: 6px 8px;
- background: fade(@cde-kungfu, 8%);
- border-left: 3px solid @cde-kungfu;
+ gap: 8px;
+ font-size: 13px;
+ padding: 8px 10px;
+ background: fade(@cde-kungfu, 18%);
+ border: 1px solid fade(@cde-kungfu, 35%);
+ border-left: 4px solid @cde-kungfu;
border-radius: 4px;
color: @cde-text;
- i { color: @cde-kungfu; }
+ .cde-tinji-icon {
+ font-size: 18px;
+ color: @cde-kungfu;
+ text-shadow: 0 0 6px fade(@cde-kungfu, 70%);
+ flex-shrink: 0;
+ font-weight: 700;
+ line-height: 1;
+ }
+
+ i {
+ color: @cde-kungfu;
+ font-size: 15px;
+ filter: drop-shadow(0 0 4px fade(@cde-kungfu, 60%));
+ flex-shrink: 0;
+ }
+
+ strong {
+ color: @cde-kungfu;
+ font-size: 13px;
+ letter-spacing: 0.02em;
+ }
.cde-tinji-remain {
margin-left: auto;
+ font-size: 11px;
+ color: fade(@cde-text, 65%);
+ font-style: italic;
+ white-space: nowrap;
+ }
+}
+
+// Post-roll Loksyu draw message — rich notification card
+.cde-loksyu-draw-msg {
+ display: flex;
+ flex-direction: column;
+ gap: 5px;
+ font-size: 12px;
+ padding: 9px 11px;
+ background: linear-gradient(135deg, fade(@cde-item, 10%) 0%, fade(@cde-surface, 95%) 100%);
+ border: 1px solid fade(@cde-item, 40%);
+ border-left: 4px solid @cde-item;
+ border-radius: 5px;
+ color: @cde-text;
+ box-shadow: 0 2px 8px fade(@cde-item, 12%);
+
+ .cde-loksyu-draw-header {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 5px;
+ font-size: 13px;
+ line-height: 1.4;
+ }
+
+ .cde-loksyu-draw-aspect-icon {
+ width: 20px;
+ height: 20px;
+ object-fit: contain;
+ filter: drop-shadow(0 0 4px fade(@cde-item, 60%));
+ flex-shrink: 0;
+ }
+
+ .cde-loksyu-draw-user {
+ font-weight: 700;
+ color: @cde-item;
+ letter-spacing: 0.03em;
+ }
+
+ .cde-loksyu-draw-action {
+ color: fade(@cde-text, 65%);
+ font-size: 12px;
+ font-style: italic;
+ }
+
+ .cde-loksyu-draw-type {
+ font-weight: 700;
+ font-size: 13px;
+ color: #c9a227;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ text-shadow: 0 0 6px fade(#c9a227, 40%);
+ }
+
+ .cde-loksyu-draw-from {
+ color: fade(@cde-text, 60%);
+ font-size: 11px;
+
+ em {
+ color: @cde-item;
+ font-style: normal;
+ font-weight: 600;
+ }
+ }
+
+ .cde-loksyu-draw-footer {
+ display: flex;
+ align-items: center;
+ gap: 5px;
+ font-size: 11px;
+ color: fade(@cde-text, 55%);
+ padding-top: 4px;
+ border-top: 1px solid fade(@cde-item, 20%);
+
+ i {
+ color: @cde-item;
+ font-size: 10px;
+ }
+
+ .cde-loksyu-remain {
+ color: @cde-item;
+ font-weight: 700;
+ }
+ }
+}
+
+// Post-roll action buttons section inside a roll-result card
+.cde-roll-actions {
+ margin-top: 10px;
+ padding-top: 8px;
+ border-top: 1px solid fade(@cde-border, 60%);
+
+ .cde-roll-actions-title {
font-size: 10px;
- color: @cde-muted;
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ color: fade(@cde-text, 50%);
+ margin-bottom: 6px;
+ display: flex;
+ align-items: center;
+ gap: 4px;
+
+ i { font-size: 9px; }
+ }
+
+ .cde-roll-actions-btns {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 5px;
+ }
+
+ .cde-roll-action-btn {
+ display: flex;
+ align-items: center;
+ gap: 5px;
+ padding: 4px 8px;
+ font-size: 11px;
+ font-family: "Courier New", Courier, monospace;
+ border-radius: 3px;
+ border: 1px solid fade(@cde-border, 80%);
+ background: fade(@cde-surface, 90%);
+ color: @cde-text;
+ cursor: pointer;
+ transition: background 0.15s, border-color 0.15s, box-shadow 0.15s;
+ pointer-events: auto;
+ line-height: 1.4;
+
+ &:hover:not(:disabled) {
+ border-color: fade(@cde-item, 70%);
+ background: fade(@cde-item, 12%);
+ box-shadow: 0 0 6px fade(@cde-item, 25%);
+ color: @cde-item;
+ }
+
+ &:disabled {
+ opacity: 0.35;
+ cursor: not-allowed;
+ }
+
+ .cde-roll-action-icon {
+ width: 16px;
+ height: 16px;
+ object-fit: contain;
+ filter: drop-shadow(0 0 2px fade(@cde-item, 40%));
+ }
+
+ .cde-roll-action-label {
+ flex: 1;
+ }
+
+ .cde-roll-action-count {
+ background: fade(@cde-border, 60%);
+ border-radius: 2px;
+ padding: 1px 4px;
+ font-size: 10px;
+ font-weight: 700;
+ min-width: 18px;
+ text-align: center;
+ }
+
+ .cde-roll-action-tinji-char {
+ font-size: 14px;
+ line-height: 1;
+ color: @cde-kungfu;
+ text-shadow: 0 0 4px fade(@cde-kungfu, 60%);
+ }
+
+ &.cde-roll-action--success {
+ &:hover:not(:disabled) {
+ border-color: fade(#c9a227, 70%);
+ background: fade(#c9a227, 10%);
+ box-shadow: 0 0 6px fade(#c9a227, 25%);
+ color: #c9a227;
+ }
+ }
+
+ &.cde-roll-action--faste {
+ &:hover:not(:disabled) {
+ border-color: fade(@cde-item, 70%);
+ background: fade(@cde-item, 10%);
+ box-shadow: 0 0 6px fade(@cde-item, 25%);
+ color: @cde-item;
+ }
+ }
+
+ &.cde-roll-action--tinji {
+ &:hover:not(:disabled) {
+ border-color: fade(@cde-kungfu, 70%);
+ background: fade(@cde-kungfu, 12%);
+ box-shadow: 0 0 6px fade(@cde-kungfu, 30%);
+ color: @cde-kungfu;
+
+ .cde-roll-action-count {
+ background: fade(@cde-kungfu, 25%);
+ }
+ }
+ }
}
}
@@ -3214,6 +3534,37 @@ ol.item-list {
color: @cde-muted;
}
}
+
+ // Loksyu bonus badge (shown when dice drawn from Loksyu)
+ .cde-rr-loksyu-bonus {
+ display: inline-flex;
+ align-items: center;
+ gap: 3px;
+ font-size: 13px;
+ font-weight: 700;
+ padding: 2px 6px;
+ border-radius: 3px;
+ animation: cde-pulse-in 0.3s ease;
+
+ &.cde-rr-loksyu-bonus--success {
+ background: fade(#c9a227, 20%);
+ border: 1px solid fade(#c9a227, 50%);
+ color: #c9a227;
+ text-shadow: 0 0 6px fade(#c9a227, 60%);
+ }
+
+ &.cde-rr-loksyu-bonus--faste {
+ background: fade(@cde-item, 15%);
+ border: 1px solid fade(@cde-item, 40%);
+ color: @cde-item;
+ text-shadow: 0 0 6px fade(@cde-item, 50%);
+ }
+ }
+ }
+
+ @keyframes cde-pulse-in {
+ from { opacity: 0; transform: scale(0.7); }
+ to { opacity: 1; transform: scale(1); }
}
// ---- Detail rows ----
@@ -3242,6 +3593,19 @@ ol.item-list {
text-shadow: 0 0 6px currentColor;
}
+ // Inline Loksyu bonus badge in detail rows
+ .cde-rr-loksyu-bonus {
+ font-size: 10px;
+ font-weight: 700;
+ padding: 1px 4px;
+ border-radius: 2px;
+ background: fade(@cde-item, 15%);
+ border: 1px solid fade(@cde-item, 40%);
+ color: @cde-item;
+ text-shadow: 0 0 4px fade(@cde-item, 50%);
+ flex-shrink: 0;
+ }
+
.cde-rr-icon {
width: 22px;
height: 22px;
diff --git a/dist/system.js b/dist/system.js
index b90255d..43bce93 100644
--- a/dist/system.js
+++ b/dist/system.js
@@ -2,9 +2,7 @@
var SYSTEM_ID = "fvtt-chroniques-de-l-etrange";
var ACTOR_TYPES = {
character: "character",
- npc: "npc",
- tinji: "tinji",
- loksyu: "loksyu"
+ npc: "npc"
};
var ITEM_TYPES = {
item: "item",
@@ -345,40 +343,6 @@ var NpcDataModel = class extends foundry.abstract.TypeDataModel {
}
};
-// src/data/actors/tinji.js
-var TinjiDataModel = class extends foundry.abstract.TypeDataModel {
- static defineSchema() {
- const { fields } = foundry.data;
- const numberField = (initial = 0, extra = {}) => new fields.NumberField({ required: true, nullable: false, integer: true, initial, ...extra });
- const htmlField = (initial = "") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true });
- return {
- value: numberField(0, { min: 0 }),
- description: htmlField("")
- };
- }
-};
-
-// src/data/actors/loksyu.js
-var LoksyuDataModel = class extends foundry.abstract.TypeDataModel {
- static defineSchema() {
- const { fields } = foundry.data;
- const numberField = (initial = 0, extra = {}) => new fields.NumberField({ required: true, nullable: false, integer: true, initial, ...extra });
- const htmlField = (initial = "") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true });
- const polarity = () => new fields.SchemaField({
- yin: new fields.SchemaField({ value: numberField(0, { min: 0 }) }),
- yang: new fields.SchemaField({ value: numberField(0, { min: 0 }) })
- });
- return {
- fire: polarity(),
- earth: polarity(),
- metal: polarity(),
- water: polarity(),
- wood: polarity(),
- description: htmlField("")
- };
- }
-};
-
// src/data/items/item.js
var EquipmentDataModel = class extends foundry.abstract.TypeDataModel {
static defineSchema() {
@@ -787,7 +751,7 @@ function registerHandlebarsHelpers() {
// src/ui/templates.js
async function preloadPartials() {
- return loadTemplates(TEMPLATE_PARTIALS);
+ return foundry.applications.handlebars.loadTemplates(TEMPLATE_PARTIALS);
}
// src/ui/initiative.js
@@ -930,6 +894,7 @@ async function rollInitiativeNPC(actor) {
}
// src/ui/apps/singletons.js
+var SYSTEM_ID2 = "fvtt-chroniques-de-l-etrange";
var WU_XING_CYCLE = {
wood: ["wood", "fire", "water", "earth", "metal"],
fire: ["fire", "earth", "wood", "metal", "water"],
@@ -944,20 +909,25 @@ var ASPECT_FACES = {
fire: [2, 7],
wood: [4, 9]
};
-async function getSingletonActor(type) {
- const existing = game.actors.find((a) => a.type === type);
- if (existing) return existing;
- if (!game.user.isGM) {
- ui.notifications.warn(game.i18n.localize(type === ACTOR_TYPES.loksyu ? "CDE.LoksyuNotFound" : "CDE.TinjiNotFound"));
- return null;
- }
- const nameKey = type === ACTOR_TYPES.loksyu ? "CDE.UpperCaseLoksyu" : "CDE.UpperCaseTinJi";
- const actor = await Actor.create({
- name: game.i18n.localize(nameKey),
- type,
- img: type === ACTOR_TYPES.loksyu ? "systems/fvtt-chroniques-de-l-etrange/images/loksyu_long.webp" : "systems/fvtt-chroniques-de-l-etrange/images/tinji.webp"
- });
- return actor ?? null;
+function getLoksyuData() {
+ return game.settings.get(SYSTEM_ID2, "loksyuData") ?? {
+ wood: { yin: 0, yang: 0 },
+ fire: { yin: 0, yang: 0 },
+ earth: { yin: 0, yang: 0 },
+ metal: { yin: 0, yang: 0 },
+ water: { yin: 0, yang: 0 }
+ };
+}
+async function setLoksyuData(data) {
+ await game.settings.set(SYSTEM_ID2, "loksyuData", data);
+ Hooks.callAll("cde:loksyuUpdated", data);
+}
+function getTinjiValue() {
+ return game.settings.get(SYSTEM_ID2, "tinjiData") ?? 0;
+}
+async function setTinjiValue(value) {
+ await game.settings.set(SYSTEM_ID2, "tinjiData", Math.max(0, value));
+ Hooks.callAll("cde:tinjiUpdated", Math.max(0, value));
}
async function updateLoksyuFromRoll(activeAspect, faces) {
const cycle = WU_XING_CYCLE[activeAspect];
@@ -968,20 +938,18 @@ async function updateLoksyuFromRoll(activeAspect, faces) {
const yinCount = faces[yinFace] ?? 0;
const yangCount = faces[yangFace] ?? 0;
if (yinCount === 0 && yangCount === 0) return;
- const actor = await getSingletonActor(ACTOR_TYPES.loksyu);
- if (!actor) return;
- const current = actor.system[lokAspect] ?? { yin: { value: 0 }, yang: { value: 0 } };
- await actor.update({
- [`system.${lokAspect}.yin.value`]: (current.yin.value ?? 0) + yinCount,
- [`system.${lokAspect}.yang.value`]: (current.yang.value ?? 0) + yangCount
- });
+ const data = getLoksyuData();
+ const current = data[lokAspect] ?? { yin: 0, yang: 0 };
+ data[lokAspect] = {
+ yin: (current.yin ?? 0) + yinCount,
+ yang: (current.yang ?? 0) + yangCount
+ };
+ await setLoksyuData(data);
}
async function updateTinjiFromRoll(count) {
if (!count || count <= 0) return;
- const actor = await getSingletonActor(ACTOR_TYPES.tinji);
- if (!actor) return;
- const current = actor.system.value ?? 0;
- await actor.update({ "system.value": current + count });
+ const current = getTinjiValue();
+ await setTinjiValue(current + count);
}
// src/ui/rolling.js
@@ -1172,7 +1140,10 @@ async function sendResultMessage(actor, resultData, roll, rollMode) {
speaker: ChatMessage.getSpeaker({ actor }),
content: html,
rolls: [roll],
- rollMode
+ rollMode,
+ flags: {
+ "fvtt-chroniques-de-l-etrange": { rollResult: { ...resultData } }
+ }
});
}
var ROLL_MODES = ["roll", "gmroll", "blindroll", "selfroll"];
@@ -1206,43 +1177,43 @@ async function rollForActor(actor, rollKey) {
title = game.i18n.localize(`CDE.${skillLibel.charAt(0).toUpperCase() + skillLibel.slice(1)}`);
break;
case "skill":
- numberofdice = sys.skills[skillLibel]?.value ?? 0;
- title = game.i18n.localize(sys.skills[skillLibel]?.label ?? "CDE.Roll");
+ numberofdice = sys.skills?.[skillLibel]?.value ?? 0;
+ title = game.i18n.localize(sys.skills?.[skillLibel]?.label ?? "CDE.Roll");
break;
case "special":
- numberofdice = sys.skills[skillLibel]?.value ?? 0;
- title = game.i18n.localize(sys.skills[skillLibel]?.label ?? "CDE.Roll");
+ numberofdice = sys.skills?.[skillLibel]?.value ?? 0;
+ title = game.i18n.localize(sys.skills?.[skillLibel]?.label ?? "CDE.Roll");
title += ` [${game.i18n.localize("CDE.Speciality")}]`;
isSpecial = true;
- if (!sys.skills[skillLibel]?.specialities) {
+ if (!sys.skills?.[skillLibel]?.specialities) {
ui.notifications.warn(game.i18n.localize("CDE.Error2"));
return;
}
break;
case "resource":
- numberofdice = sys.resources[skillLibel]?.value ?? 0;
- title = game.i18n.localize(sys.resources[skillLibel]?.label ?? "CDE.Roll");
+ numberofdice = sys.resources?.[skillLibel]?.value ?? 0;
+ title = game.i18n.localize(sys.resources?.[skillLibel]?.label ?? "CDE.Roll");
break;
case "field":
- numberofdice = sys.resources[skillLibel]?.value ?? 0;
- title = game.i18n.localize(sys.resources[skillLibel]?.label ?? "CDE.Roll");
+ numberofdice = sys.resources?.[skillLibel]?.value ?? 0;
+ title = game.i18n.localize(sys.resources?.[skillLibel]?.label ?? "CDE.Roll");
title += ` [${game.i18n.localize("CDE.Field")}]`;
isSpecial = true;
- if (!sys.resources[skillLibel]?.specialities) {
+ if (!sys.resources?.[skillLibel]?.specialities) {
ui.notifications.warn(game.i18n.localize("CDE.Error4"));
return;
}
break;
case "magic":
- numberofdice = sys.magics[skillLibel]?.value ?? 0;
+ numberofdice = sys.magics?.[skillLibel]?.value ?? 0;
isMagic = true;
title = game.i18n.localize(MAGIC_I18N_KEYS[skillLibel] ?? "CDE.Magics");
break;
case "magicspecial":
- numberofdice = sys.magics[skillLibel]?.value ?? 0;
+ numberofdice = sys.magics?.[skillLibel]?.value ?? 0;
isMagicSpecial = true;
isMagic = true;
- if (!sys.magics[skillLibel]?.speciality?.[specialLibel]?.check) {
+ if (!sys.magics?.[skillLibel]?.speciality?.[specialLibel]?.check) {
ui.notifications.warn(game.i18n.localize("CDE.Error6"));
return;
}
@@ -1255,8 +1226,8 @@ async function rollForActor(actor, rollKey) {
return;
}
const kfSkill = kfItem.system.skill ?? "kungfu";
- numberofdice = sys.skills[kfSkill]?.value ?? 0;
- title = `${kfItem.name} [${game.i18n.localize(sys.skills[kfSkill]?.label ?? "CDE.KungFu")}]`;
+ numberofdice = sys.skills?.[kfSkill]?.value ?? 0;
+ title = `${kfItem.name} [${game.i18n.localize(sys.skills?.[kfSkill]?.label ?? "CDE.KungFu")}]`;
kfDefaultAspect = ASPECT_NAMES.indexOf(kfItem.system.aspect ?? "metal");
if (kfDefaultAspect < 0) kfDefaultAspect = 0;
break;
@@ -1269,7 +1240,7 @@ async function rollForActor(actor, rollKey) {
}
const wpType = wpItem.system.weaponType ?? "melee";
const wpSkill = WEAPON_TYPE_SKILL[wpType] ?? "kungfu";
- numberofdice = sys.skills[wpSkill]?.value ?? 0;
+ numberofdice = sys.skills?.[wpSkill]?.value ?? 0;
const wpAspectRaw = wpItem.system.damageAspect ?? "metal";
const wpAspectIdx = WEAPON_ASPECT_INDEX[wpAspectRaw] ?? 0;
const wpRange = wpItem.system.range ?? "contact";
@@ -1280,7 +1251,7 @@ async function rollForActor(actor, rollKey) {
firearm: "CDE.WeaponFirearm"
};
const wParams = await showWeaponPrompt({
- title: `${wpItem.name} [${game.i18n.localize(sys.skills[wpSkill]?.label ?? "CDE.WeaponRoll")}]`,
+ title: `${wpItem.name} [${game.i18n.localize(sys.skills?.[wpSkill]?.label ?? "CDE.WeaponRoll")}]`,
numberofdice,
weaponName: wpItem.name,
weaponTypeLabel: WEAPON_TYPE_LABELS[wpType] ?? "CDE.Weapon",
@@ -1297,7 +1268,7 @@ async function rollForActor(actor, rollKey) {
});
if (!wParams) return;
const wpChosenSkill = wParams.weaponskill ?? wpSkill;
- const wpSkillDice = sys.skills[wpChosenSkill]?.value ?? 0;
+ const wpSkillDice = sys.skills?.[wpChosenSkill]?.value ?? 0;
const wpAspFinal = Number(wParams.aspect ?? wpAspectIdx);
const wpAspectDice = sys.aspect[ASPECT_NAMES[wpAspFinal]]?.value ?? 0;
const wpRangeMalus = RANGE_MALUS[wParams.effectiverange ?? "contact"] ?? 0;
@@ -1430,7 +1401,7 @@ async function rollForActor(actor, rollKey) {
bonusAuspicious = Number(params.bonusauspiciousdice ?? 0);
rollDifficulty = Math.max(1, Number(params.rolldifficulty ?? 1));
throwMode = Number(params.typeofthrow ?? 0);
- const aspectDice = sys.aspect[ASPECT_NAMES[aspectIndex]]?.value ?? 0;
+ const aspectDice = sys.aspect?.[ASPECT_NAMES[aspectIndex]]?.value ?? 0;
const bonusSpec = Number(params.bonusmalusspeciality ?? 0);
const heiDice = Number(params.heispend ?? 0);
numberofdice = numberofdice + aspectDice + bonusMalus + 1 + bonusSpec + heiDice;
@@ -1440,7 +1411,7 @@ async function rollForActor(actor, rollKey) {
const woundMalus = Number(params.woundmalus ?? 0);
bonusAuspicious = Number(params.bonusauspiciousdice ?? 0);
throwMode = Number(params.typeofthrow ?? 0);
- const aspectDice = typeLibel !== "aspect" ? sys.aspect[ASPECT_NAMES[aspectIndex]]?.value ?? 0 : 0;
+ const aspectDice = typeLibel !== "aspect" ? sys.aspect?.[ASPECT_NAMES[aspectIndex]]?.value ?? 0 : 0;
numberofdice = numberofdice + aspectDice + bonusMalus - woundMalus;
if (isSpecial) numberofdice += 1;
}
@@ -1533,7 +1504,7 @@ var CDEBaseActorSheet = class _CDEBaseActorSheet extends HandlebarsApplicationMi
return this.document.name;
}
async _prepareContext() {
- const descriptionHTML = await TextEditor.enrichHTML(this.document.system.description ?? "", { async: true });
+ const descriptionHTML = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description ?? "", { async: true });
const cssClass = this.options.classes?.join(" ") ?? "";
return {
actor: this.document,
@@ -1830,259 +1801,6 @@ var CDENpcSheet = class extends CDEBaseActorSheet {
}
};
-// src/ui/apps/tinji-app.js
-var SYSTEM_ID2 = "fvtt-chroniques-de-l-etrange";
-var CDETinjiApp = class _CDETinjiApp extends foundry.applications.api.HandlebarsApplicationMixin(
- foundry.applications.api.ApplicationV2
-) {
- static DEFAULT_OPTIONS = {
- id: "cde-tinji-app",
- tag: "div",
- window: {
- title: "CDE.TinJi2",
- icon: "fas fa-star",
- resizable: false
- },
- classes: ["cde-app", "cde-tinji-standalone"],
- position: { width: 320, height: "auto" },
- actions: {
- increment: _CDETinjiApp.#onIncrement,
- decrement: _CDETinjiApp.#onDecrement,
- reset: _CDETinjiApp.#onReset,
- spend: _CDETinjiApp.#onSpend
- }
- };
- static PARTS = {
- main: {
- template: `systems/${SYSTEM_ID2}/templates/apps/cde-tinji-app.html`
- }
- };
- /** @type {Actor|null} */
- #actor = null;
- /** @type {Function|null} */
- #updateHook = null;
- static open() {
- const existing = Object.values(foundry.applications.instances ?? {}).find(
- (app2) => app2 instanceof _CDETinjiApp
- );
- if (existing) {
- existing.bringToFront();
- return existing;
- }
- const app = new _CDETinjiApp();
- app.render(true);
- return app;
- }
- async _prepareContext() {
- this.#actor = await getSingletonActor(ACTOR_TYPES.tinji);
- if (!this.#actor) return { hasActor: false, value: 0 };
- return {
- hasActor: true,
- canEdit: this.#actor.isOwner,
- value: this.#actor.system.value ?? 0
- };
- }
- _onRender(context, options) {
- super._onRender(context, options);
- this.#bindDirectInput();
- this.#updateHook = Hooks.on("updateActor", (actor) => {
- if (actor.id === this.#actor?.id) this.render();
- });
- }
- _onClose(options) {
- if (this.#updateHook !== null) {
- Hooks.off("updateActor", this.#updateHook);
- this.#updateHook = null;
- }
- super._onClose(options);
- }
- #bindDirectInput() {
- const input = this.element?.querySelector("input.cde-tinji-direct");
- if (!input) return;
- input.addEventListener("change", async (ev) => {
- const val = parseInt(ev.currentTarget.value, 10);
- if (!isNaN(val) && this.#actor) {
- await this.#actor.update({ "system.value": Math.max(0, val) });
- }
- });
- }
- static async #onIncrement() {
- if (!this.#actor) return;
- const current = this.#actor.system.value ?? 0;
- await this.#actor.update({ "system.value": current + 1 });
- }
- static async #onDecrement() {
- if (!this.#actor) return;
- const current = this.#actor.system.value ?? 0;
- if (current <= 0) return;
- await this.#actor.update({ "system.value": current - 1 });
- }
- static async #onReset() {
- if (!this.#actor) return;
- await this.#actor.update({ "system.value": 0 });
- }
- /** Spend 1 Tin Ji die and announce it in chat */
- static async #onSpend() {
- if (!this.#actor) return;
- const current = this.#actor.system.value ?? 0;
- if (current <= 0) {
- ui.notifications.warn(game.i18n.localize("CDE.TinjiEmpty"));
- return;
- }
- await this.#actor.update({ "system.value": current - 1 });
- ChatMessage.create({
- user: game.user.id,
- content: `
-
- ${game.i18n.localize("CDE.TinJi2")}
- ${game.i18n.format("CDE.TinjiSpent", { name: game.user.name })}
- (${current - 1} ${game.i18n.localize("CDE.TinjiRemaining")})
-
`
- });
- }
-};
-
-// src/ui/sheets/actors/tinji.js
-var CDETinjiSheet = class extends CDEBaseActorSheet {
- static DEFAULT_OPTIONS = {
- classes: ["tinji"]
- };
- static PARTS = {
- main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/actor/cde-tinji-sheet.html" }
- };
- tabGroups = { primary: "tinji" };
- /** Redirect any direct actor-sheet open to the standalone app instead */
- async _onFirstRender(context, options) {
- await this.close({ animate: false });
- CDETinjiApp.open();
- }
-};
-
-// src/ui/apps/loksyu-app.js
-var SYSTEM_ID3 = "fvtt-chroniques-de-l-etrange";
-var CDELoksyuApp = class _CDELoksyuApp extends foundry.applications.api.HandlebarsApplicationMixin(
- foundry.applications.api.ApplicationV2
-) {
- static DEFAULT_OPTIONS = {
- id: "cde-loksyu-app",
- tag: "div",
- window: {
- title: "CDE.Loksyu",
- icon: "fas fa-yin-yang",
- resizable: false
- },
- classes: ["cde-app", "cde-loksyu-standalone"],
- position: { width: 540, height: "auto" },
- actions: {
- resetElement: _CDELoksyuApp.#onResetElement,
- resetAll: _CDELoksyuApp.#onResetAll
- }
- };
- static PARTS = {
- main: {
- template: `systems/${SYSTEM_ID3}/templates/apps/cde-loksyu-app.html`
- }
- };
- /** @type {Actor|null} */
- #actor = null;
- /** @type {Function|null} bound hook handler */
- #updateHook = null;
- /** Singleton accessor — open or bring to front */
- static open() {
- const existing = Object.values(foundry.applications.instances ?? {}).find(
- (app2) => app2 instanceof _CDELoksyuApp
- );
- if (existing) {
- existing.bringToFront();
- return existing;
- }
- const app = new _CDELoksyuApp();
- app.render(true);
- return app;
- }
- async _prepareContext() {
- this.#actor = await getSingletonActor(ACTOR_TYPES.loksyu);
- if (!this.#actor) return { hasActor: false };
- const sys = this.#actor.system;
- const ELEMENTS = [
- { key: "wood", nameKey: "CDE.Wood", qualKey: "CDE.WoodQualities", img: `systems/${SYSTEM_ID3}/images/cde_bois.webp` },
- { key: "fire", nameKey: "CDE.Fire", qualKey: "CDE.FireQualities", img: `systems/${SYSTEM_ID3}/images/cde_feu.webp` },
- { key: "earth", nameKey: "CDE.Earth", qualKey: "CDE.EarthQualities", img: `systems/${SYSTEM_ID3}/images/cde_terre.webp` },
- { key: "metal", nameKey: "CDE.Metal", qualKey: "CDE.MetalQualities", img: `systems/${SYSTEM_ID3}/images/cde_metal.webp` },
- { key: "water", nameKey: "CDE.Water", qualKey: "CDE.WaterQualities", img: `systems/${SYSTEM_ID3}/images/cde_eau.webp` }
- ];
- return {
- hasActor: true,
- canEdit: this.#actor.isOwner,
- elements: ELEMENTS.map((el) => ({
- ...el,
- yang: sys[el.key]?.yang?.value ?? 0,
- yin: sys[el.key]?.yin?.value ?? 0
- }))
- };
- }
- _onRender(context, options) {
- super._onRender(context, options);
- this.#bindInputs();
- this.#updateHook = Hooks.on("updateActor", (actor) => {
- if (actor.id === this.#actor?.id) this.render();
- });
- }
- _onClose(options) {
- if (this.#updateHook !== null) {
- Hooks.off("updateActor", this.#updateHook);
- this.#updateHook = null;
- }
- super._onClose(options);
- }
- #bindInputs() {
- const inputs = this.element?.querySelectorAll("input[data-field]");
- if (!inputs?.length) return;
- inputs.forEach((input) => {
- input.addEventListener("change", async (ev) => {
- const field = ev.currentTarget.dataset.field;
- const val = parseInt(ev.currentTarget.value, 10);
- if (!field || isNaN(val)) return;
- await this.#actor?.update({ [field]: Math.max(0, val) });
- });
- });
- }
- static async #onResetElement(event, target) {
- const key = target.dataset.element;
- if (!key || !this.#actor) return;
- await this.#actor.update({
- [`system.${key}.yin.value`]: 0,
- [`system.${key}.yang.value`]: 0
- });
- }
- static async #onResetAll(_event, _target) {
- if (!this.#actor) return;
- const KEYS = ["wood", "fire", "earth", "metal", "water"];
- const update = {};
- for (const k of KEYS) {
- update[`system.${k}.yin.value`] = 0;
- update[`system.${k}.yang.value`] = 0;
- }
- await this.#actor.update(update);
- }
-};
-
-// src/ui/sheets/actors/loksyu.js
-var CDELoksyuSheet = class extends CDEBaseActorSheet {
- static DEFAULT_OPTIONS = {
- classes: ["loksyu"]
- };
- static PARTS = {
- main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/actor/cde-loksyu-sheet.html" }
- };
- tabGroups = { primary: "loksyu" };
- /** Redirect any direct actor-sheet open to the standalone app instead */
- async _onFirstRender(context, options) {
- await this.close({ animate: false });
- CDELoksyuApp.open();
- }
-};
-
// src/ui/sheets/items/base.js
var { HandlebarsApplicationMixin: HandlebarsApplicationMixin2 } = foundry.applications.api;
var CDEBaseItemSheet = class _CDEBaseItemSheet extends HandlebarsApplicationMixin2(foundry.applications.sheets.ItemSheetV2) {
@@ -2101,8 +1819,8 @@ var CDEBaseItemSheet = class _CDEBaseItemSheet extends HandlebarsApplicationMixi
}
async _prepareContext() {
const cssClass = this.options.classes?.join(" ") ?? "";
- const enrichedDescription = await TextEditor.enrichHTML(this.document.system.description ?? "", { async: true });
- const enrichedNotes = await TextEditor.enrichHTML(this.document.system.notes ?? "", { async: true });
+ const enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description ?? "", { async: true });
+ const enrichedNotes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.notes ?? "", { async: true });
return {
item: this.document,
system: this.document.system,
@@ -2261,6 +1979,205 @@ var CDEIngredientSheet = class extends CDEBaseItemSheet {
};
};
+// src/ui/apps/loksyu-app.js
+var SYSTEM_ID3 = "fvtt-chroniques-de-l-etrange";
+var CDELoksyuApp = class _CDELoksyuApp extends foundry.applications.api.HandlebarsApplicationMixin(
+ foundry.applications.api.ApplicationV2
+) {
+ static DEFAULT_OPTIONS = {
+ id: "cde-loksyu-app",
+ tag: "div",
+ window: {
+ title: "CDE.Loksyu",
+ icon: "fas fa-yin-yang",
+ resizable: false
+ },
+ classes: ["cde-app", "cde-loksyu-standalone"],
+ position: { width: 520, height: "auto" },
+ actions: {
+ resetElement: _CDELoksyuApp.#onResetElement,
+ resetAll: _CDELoksyuApp.#onResetAll
+ }
+ };
+ static PARTS = {
+ main: {
+ template: `systems/${SYSTEM_ID3}/templates/apps/cde-loksyu-app.html`
+ }
+ };
+ /** @type {Function|null} bound hook handler */
+ _updateHook = null;
+ /** Singleton accessor — open or bring to front */
+ static open() {
+ const existing = Array.from(foundry.applications.instances.values()).find(
+ (app2) => app2 instanceof _CDELoksyuApp
+ );
+ if (existing) {
+ existing.bringToFront();
+ return existing;
+ }
+ const app = new _CDELoksyuApp();
+ app.render(true);
+ return app;
+ }
+ async _prepareContext() {
+ const sys = getLoksyuData();
+ const ELEMENTS = [
+ { key: "wood", nameKey: "CDE.Wood", qualKey: "CDE.WoodQualities", img: `systems/${SYSTEM_ID3}/images/cde_bois.webp` },
+ { key: "fire", nameKey: "CDE.Fire", qualKey: "CDE.FireQualities", img: `systems/${SYSTEM_ID3}/images/cde_feu.webp` },
+ { key: "earth", nameKey: "CDE.Earth", qualKey: "CDE.EarthQualities", img: `systems/${SYSTEM_ID3}/images/cde_terre.webp` },
+ { key: "metal", nameKey: "CDE.Metal", qualKey: "CDE.MetalQualities", img: `systems/${SYSTEM_ID3}/images/cde_metal.webp` },
+ { key: "water", nameKey: "CDE.Water", qualKey: "CDE.WaterQualities", img: `systems/${SYSTEM_ID3}/images/cde_eau.webp` }
+ ];
+ return {
+ canEdit: game.user.isGM,
+ elements: ELEMENTS.map((el) => ({
+ ...el,
+ yang: sys[el.key]?.yang ?? 0,
+ yin: sys[el.key]?.yin ?? 0
+ }))
+ };
+ }
+ _onRender(context, options) {
+ super._onRender(context, options);
+ this.#bindInputs();
+ this._updateHook = Hooks.on("cde:loksyuUpdated", () => this.render());
+ }
+ _onClose(options) {
+ if (this._updateHook !== null) {
+ Hooks.off("cde:loksyuUpdated", this._updateHook);
+ this._updateHook = null;
+ }
+ super._onClose(options);
+ }
+ #bindInputs() {
+ const inputs = this.element?.querySelectorAll("input[data-field]");
+ if (!inputs?.length) return;
+ inputs.forEach((input) => {
+ input.addEventListener("change", async (ev) => {
+ const field = ev.currentTarget.dataset.field;
+ const val = parseInt(ev.currentTarget.value, 10);
+ if (!field || isNaN(val)) return;
+ const [aspect, dim] = field.split(".");
+ if (!aspect || !dim) return;
+ const data = getLoksyuData();
+ if (!data[aspect]) data[aspect] = { yin: 0, yang: 0 };
+ data[aspect][dim] = Math.max(0, val);
+ await setLoksyuData(data);
+ });
+ });
+ }
+ static async #onResetElement(event, target) {
+ const key = target.dataset.element;
+ if (!key) return;
+ const data = getLoksyuData();
+ data[key] = { yin: 0, yang: 0 };
+ await setLoksyuData(data);
+ }
+ static async #onResetAll(_event, _target) {
+ const KEYS = ["wood", "fire", "earth", "metal", "water"];
+ const data = getLoksyuData();
+ for (const k of KEYS) data[k] = { yin: 0, yang: 0 };
+ await setLoksyuData(data);
+ }
+};
+
+// src/ui/apps/tinji-app.js
+var SYSTEM_ID4 = "fvtt-chroniques-de-l-etrange";
+var CDETinjiApp = class _CDETinjiApp extends foundry.applications.api.HandlebarsApplicationMixin(
+ foundry.applications.api.ApplicationV2
+) {
+ static DEFAULT_OPTIONS = {
+ id: "cde-tinji-app",
+ tag: "div",
+ window: {
+ title: "CDE.TinJi2",
+ icon: "fas fa-star",
+ resizable: false
+ },
+ classes: ["cde-app", "cde-tinji-standalone"],
+ position: { width: 320, height: "auto" },
+ actions: {
+ increment: _CDETinjiApp.#onIncrement,
+ decrement: _CDETinjiApp.#onDecrement,
+ reset: _CDETinjiApp.#onReset,
+ spend: _CDETinjiApp.#onSpend
+ }
+ };
+ static PARTS = {
+ main: {
+ template: `systems/${SYSTEM_ID4}/templates/apps/cde-tinji-app.html`
+ }
+ };
+ /** @type {Function|null} */
+ _updateHook = null;
+ static open() {
+ const existing = Array.from(foundry.applications.instances.values()).find(
+ (app2) => app2 instanceof _CDETinjiApp
+ );
+ if (existing) {
+ existing.bringToFront();
+ return existing;
+ }
+ const app = new _CDETinjiApp();
+ app.render(true);
+ return app;
+ }
+ async _prepareContext() {
+ return {
+ canEdit: game.user.isGM,
+ value: getTinjiValue()
+ };
+ }
+ _onRender(context, options) {
+ super._onRender(context, options);
+ this.#bindDirectInput();
+ this._updateHook = Hooks.on("cde:tinjiUpdated", () => this.render());
+ }
+ _onClose(options) {
+ if (this._updateHook !== null) {
+ Hooks.off("cde:tinjiUpdated", this._updateHook);
+ this._updateHook = null;
+ }
+ super._onClose(options);
+ }
+ #bindDirectInput() {
+ const input = this.element?.querySelector("input.cde-tinji-direct");
+ if (!input) return;
+ input.addEventListener("change", async (ev) => {
+ const val = parseInt(ev.currentTarget.value, 10);
+ if (!isNaN(val)) await setTinjiValue(val);
+ });
+ }
+ static async #onIncrement() {
+ await setTinjiValue(getTinjiValue() + 1);
+ }
+ static async #onDecrement() {
+ const current = getTinjiValue();
+ if (current <= 0) return;
+ await setTinjiValue(current - 1);
+ }
+ static async #onReset() {
+ await setTinjiValue(0);
+ }
+ static async #onSpend() {
+ const current = getTinjiValue();
+ if (current <= 0) {
+ ui.notifications.warn(game.i18n.localize("CDE.TinjiEmpty"));
+ return;
+ }
+ await setTinjiValue(current - 1);
+ ChatMessage.create({
+ user: game.user.id,
+ content: `
+
+ ${game.i18n.localize("CDE.TinJi2")}
+ ${game.i18n.format("CDE.TinjiSpent", { name: game.user.name })}
+ (${current - 1} ${game.i18n.localize("CDE.TinjiRemaining")})
+
`
+ });
+ }
+};
+
// src/migration.js
var MIGRATION_VERSION = "3.0.0";
function registerSettings() {
@@ -2271,6 +2188,20 @@ function registerSettings() {
type: String,
default: "0.0.0"
});
+ game.settings.register(SYSTEM_ID, "loksyuData", {
+ name: "Loksyu Data",
+ scope: "world",
+ config: false,
+ type: Object,
+ default: { wood: { yin: 0, yang: 0 }, fire: { yin: 0, yang: 0 }, earth: { yin: 0, yang: 0 }, metal: { yin: 0, yang: 0 }, water: { yin: 0, yang: 0 } }
+ });
+ game.settings.register(SYSTEM_ID, "tinjiData", {
+ name: "TinJi Data",
+ scope: "world",
+ config: false,
+ type: Number,
+ default: 0
+ });
}
async function migrateIfNeeded() {
const current = MIGRATION_VERSION;
@@ -2372,17 +2303,185 @@ function migrateItemData(item) {
return updateData;
}
+// src/ui/roll-actions.js
+var SYSTEM_ID5 = "fvtt-chroniques-de-l-etrange";
+var RESULT_TEMPLATE3 = "systems/fvtt-chroniques-de-l-etrange/templates/form/cde-dice-result.html";
+var WU_XING_CYCLE3 = {
+ wood: ["wood", "fire", "water", "earth", "metal"],
+ fire: ["fire", "earth", "wood", "metal", "water"],
+ earth: ["earth", "metal", "fire", "water", "wood"],
+ metal: ["metal", "water", "earth", "wood", "fire"],
+ water: ["water", "wood", "metal", "fire", "earth"]
+};
+var ASPECT_LABELS2 = {
+ metal: "CDE.Metal",
+ water: "CDE.Water",
+ earth: "CDE.Earth",
+ fire: "CDE.Fire",
+ wood: "CDE.Wood"
+};
+var ASPECT_ICONS2 = {
+ metal: "systems/fvtt-chroniques-de-l-etrange/images/cde_metal.webp",
+ water: "systems/fvtt-chroniques-de-l-etrange/images/cde_eau.webp",
+ earth: "systems/fvtt-chroniques-de-l-etrange/images/cde_terre.webp",
+ fire: "systems/fvtt-chroniques-de-l-etrange/images/cde_feu.webp",
+ wood: "systems/fvtt-chroniques-de-l-etrange/images/cde_bois.webp"
+};
+function injectRollActions(message, html) {
+ const rollCard = html.querySelector(".cde-roll-result");
+ if (!rollCard) return;
+ const aspect = rollCard.dataset.aspect;
+ if (!aspect || !WU_XING_CYCLE3[aspect]) return;
+ refreshRollActions(rollCard, aspect, message);
+}
+function refreshRollActions(rollCard, aspect, message) {
+ rollCard.querySelector(".cde-roll-actions")?.remove();
+ const cycle = WU_XING_CYCLE3[aspect];
+ const fasteAspect = cycle[1];
+ const loksyu = getLoksyuData();
+ const tinji = getTinjiValue();
+ const successAvail = (loksyu[aspect]?.yin ?? 0) + (loksyu[aspect]?.yang ?? 0);
+ const fasteAvail = (loksyu[fasteAspect]?.yin ?? 0) + (loksyu[fasteAspect]?.yang ?? 0);
+ const isGM = game.user.isGM;
+ const hasSomething = successAvail > 0 || fasteAvail > 0 || isGM && tinji > 0;
+ if (!hasSomething) return;
+ const aspLabel = game.i18n.localize(ASPECT_LABELS2[aspect]);
+ const fasteLabel = game.i18n.localize(ASPECT_LABELS2[fasteAspect]);
+ let btns = "";
+ if (successAvail > 0) {
+ btns += ``;
+ }
+ if (fasteAvail > 0) {
+ btns += ``;
+ }
+ if (isGM && tinji > 0) {
+ btns += ``;
+ }
+ const wrapper = document.createElement("div");
+ wrapper.className = "cde-roll-actions";
+ wrapper.innerHTML = `
+
+
+ ${game.i18n.localize("CDE.PostRollActions")}
+
+ ${btns}
+ `;
+ rollCard.appendChild(wrapper);
+ wrapper.addEventListener("click", async (ev) => {
+ const btn = ev.target.closest("[data-action]");
+ if (!btn || btn.disabled) return;
+ const action = btn.dataset.action;
+ if (action === "loksyu-success") {
+ await _drawFromLoksyu(message, aspect, "success", aspLabel);
+ } else if (action === "loksyu-faste") {
+ await _drawFromLoksyu(message, fasteAspect, "faste", fasteLabel);
+ } else if (action === "tinji") {
+ await _spendTinjiPostRoll();
+ }
+ if (action === "tinji") refreshRollActions(rollCard, aspect, message);
+ });
+}
+async function _drawFromLoksyu(message, aspect, type, aspectLabel) {
+ const data = getLoksyuData();
+ const entry = data[aspect] ?? { yin: 0, yang: 0 };
+ const total = entry.yin + entry.yang;
+ if (total <= 0) {
+ ui.notifications.warn(game.i18n.localize("CDE.LoksyuEmpty"));
+ return;
+ }
+ if (entry.yang > 0) entry.yang--;
+ else entry.yin--;
+ data[aspect] = entry;
+ await setLoksyuData(data);
+ const flags = message?.flags?.[SYSTEM_ID5];
+ if (flags?.rollResult && message.isOwner) {
+ const updated = foundry.utils.deepClone(flags.rollResult);
+ if (type === "success") {
+ updated.successesdice = (updated.successesdice ?? 0) + 1;
+ updated.loksyuBonusSuc = (updated.loksyuBonusSuc ?? 0) + 1;
+ if (updated.damageBase) updated.totalDamage = updated.successesdice * updated.damageBase;
+ } else {
+ updated.auspiciousdice = (updated.auspiciousdice ?? 0) + 1;
+ updated.loksyuBonusFaste = (updated.loksyuBonusFaste ?? 0) + 1;
+ }
+ const newHtml = await foundry.applications.handlebars.renderTemplate(RESULT_TEMPLATE3, updated);
+ await message.update({
+ content: newHtml,
+ [`flags.${SYSTEM_ID5}.rollResult`]: updated
+ });
+ }
+ const remain = entry.yin + entry.yang;
+ const typeLabel = type === "success" ? game.i18n.localize("CDE.Successes") : game.i18n.localize("CDE.AuspiciousDie");
+ ChatMessage.create({
+ user: game.user.id,
+ content: `
+
+
+
`
+ });
+}
+async function _spendTinjiPostRoll() {
+ if (!game.user.isGM) return;
+ const current = getTinjiValue();
+ if (current <= 0) {
+ ui.notifications.warn(game.i18n.localize("CDE.TinjiEmpty"));
+ return;
+ }
+ await setTinjiValue(current - 1);
+ ChatMessage.create({
+ user: game.user.id,
+ content: `
+ \u5929
+
+ ${game.user.name} ${game.i18n.localize("CDE.TinjiSpent").replace("{name}", game.user.name)}
+
+ (${current - 1} ${game.i18n.localize("CDE.TinjiRemaining")})
+
`
+ });
+}
+function refreshAllRollActions() {
+ document.querySelectorAll(".chat-message .cde-roll-result[data-aspect]").forEach((card) => {
+ const aspect = card.dataset.aspect;
+ if (!aspect || !WU_XING_CYCLE3[aspect]) return;
+ const msgEl = card.closest("[data-message-id]");
+ const msgId = msgEl?.dataset?.messageId;
+ const message = msgId ? game.messages.get(msgId) : null;
+ refreshRollActions(card, aspect, message);
+ });
+}
+
// src/system.js
Hooks.once("i18nInit", preLocalizeConfig);
Hooks.once("init", async () => {
console.info(`CHRONIQUESDELETRANGE | Initializing ${SYSTEM_ID}`);
registerSettings();
game.system.CONST = { MAGICS, SUBTYPES };
+ game.cde = { CDELoksyuApp, CDETinjiApp };
CONFIG.Actor.dataModels = {
[ACTOR_TYPES.character]: CharacterDataModel,
- [ACTOR_TYPES.npc]: NpcDataModel,
- [ACTOR_TYPES.tinji]: TinjiDataModel,
- [ACTOR_TYPES.loksyu]: LoksyuDataModel
+ [ACTOR_TYPES.npc]: NpcDataModel
};
CONFIG.Item.dataModels = {
[ITEM_TYPES.item]: EquipmentDataModel,
@@ -2398,8 +2497,8 @@ Hooks.once("init", async () => {
CONFIG.Item.documentClass = CDEItem;
CONFIG.ChatMessage.documentClass = CDEMessage;
configureRuntime();
- foundry.applications.apps.DocumentSheetConfig.unregisterSheet(Actor, "core", ActorSheet);
- foundry.applications.apps.DocumentSheetConfig.unregisterSheet(Item, "core", ItemSheet);
+ foundry.applications.apps.DocumentSheetConfig.unregisterSheet(Actor, "core", foundry.appv1.sheets.ActorSheet);
+ foundry.applications.apps.DocumentSheetConfig.unregisterSheet(Item, "core", foundry.appv1.sheets.ItemSheet);
foundry.applications.apps.DocumentSheetConfig.registerSheet(Actor, SYSTEM_ID, CDECharacterSheet, {
types: [ACTOR_TYPES.character],
makeDefault: true,
@@ -2410,16 +2509,6 @@ Hooks.once("init", async () => {
makeDefault: true,
label: "CDE NPC Sheet (V2)"
});
- foundry.applications.apps.DocumentSheetConfig.registerSheet(Actor, SYSTEM_ID, CDETinjiSheet, {
- types: [ACTOR_TYPES.tinji],
- makeDefault: true,
- label: "CDE Tinji Sheet (V2)"
- });
- foundry.applications.apps.DocumentSheetConfig.registerSheet(Actor, SYSTEM_ID, CDELoksyuSheet, {
- types: [ACTOR_TYPES.loksyu],
- makeDefault: true,
- label: "CDE Loksyu Sheet (V2)"
- });
foundry.applications.apps.DocumentSheetConfig.registerSheet(Item, SYSTEM_ID, CDEItemSheet, {
types: [ITEM_TYPES.item],
makeDefault: true,
@@ -2470,22 +2559,35 @@ Hooks.once("ready", async () => {
await migrateIfNeeded();
});
Hooks.on("renderChatLog", (_app, html) => {
- const el = html instanceof HTMLElement ? html : html[0];
- const controls = el?.querySelector?.(".chat-controls");
- if (!controls) return;
+ const el = html instanceof HTMLElement ? html : html[0] ?? html;
+ if (!el?.querySelector) return;
+ if (el.querySelector(".cde-chat-app-buttons")) return;
const wrapper = document.createElement("div");
wrapper.classList.add("cde-chat-app-buttons");
wrapper.innerHTML = `
-