diff --git a/css/les-oublies.css b/css/les-oublies.css index 08dcf3a..8df000f 100644 --- a/css/les-oublies.css +++ b/css/les-oublies.css @@ -70,23 +70,39 @@ --lo-shadow: rgba(0, 0, 0, 0.42); --lo-line: rgba(110, 77, 53, 0.4); --lo-glow: rgba(207, 176, 106, 0.22); + --lo-space-2xs: 0.25rem; + --lo-space-xs: 0.4rem; + --lo-space-sm: 0.55rem; + --lo-space-md: 0.72rem; + --lo-space-lg: 0.9rem; + --lo-space-xl: 1rem; + --lo-radius-sm: 10px; + --lo-radius-md: 12px; + --lo-radius-lg: 14px; + --lo-radius-xl: 16px; + --lo-font-body: 0.94rem; + --lo-font-meta: 0.84rem; + --lo-font-label: 0.68rem; + --lo-font-button: 0.68rem; + --lo-control-height: 1.95rem; + --lo-number-width: 4.75rem; } .fvtt-les-oublies.sheet { color: var(--lo-ink); font-family: "Cormorant Garamond", Georgia, serif; background: radial-gradient(circle at top left, rgba(120, 103, 63, 0.22), transparent 28%), radial-gradient(circle at top right, rgba(78, 107, 76, 0.18), transparent 24%), linear-gradient(180deg, rgba(14, 22, 18, 0.95), rgba(23, 31, 26, 0.96)); - position: relative; } .fvtt-les-oublies.sheet .window-content { background: linear-gradient(180deg, rgba(12, 19, 16, 0.92), rgba(22, 29, 25, 0.96)), radial-gradient(circle at 20% 10%, rgba(207, 176, 106, 0.08), transparent 26%); color: var(--lo-ink); + overflow-x: hidden; + overflow-y: auto; } .fvtt-les-oublies { color: var(--lo-ink); - padding: 0.4rem; - position: relative; } .fvtt-les-oublies .les-oublies-sheet { + padding: var(--lo-space-xs); position: relative; } .fvtt-les-oublies .les-oublies-sheet::before { @@ -100,12 +116,12 @@ } .fvtt-les-oublies .hero-banner { display: grid; - grid-template-columns: 110px 1fr; - gap: 1.2rem; + grid-template-columns: 96px 1fr; + gap: var(--lo-space-xl); align-items: stretch; - margin-bottom: 1.1rem; - padding: 1rem 1.1rem 1.1rem; - border-radius: 18px; + margin-bottom: var(--lo-space-xl); + padding: 0.85rem 0.95rem 0.9rem; + border-radius: var(--lo-radius-xl); background: linear-gradient(135deg, rgba(250, 240, 217, 0.98), rgba(232, 214, 182, 0.92)), linear-gradient(180deg, rgba(207, 176, 106, 0.18), rgba(109, 41, 34, 0.08)); border: 1px solid rgba(207, 176, 106, 0.4); box-shadow: 0 18px 40px var(--lo-shadow), inset 0 1px 0 rgba(255, 250, 242, 0.7), inset 0 0 0 1px rgba(121, 80, 51, 0.08); @@ -121,11 +137,11 @@ pointer-events: none; } .fvtt-les-oublies .profile-img { - width: 110px; - height: 132px; + width: 96px; + height: 118px; object-fit: cover; border: 2px solid rgba(85, 55, 34, 0.7); - border-radius: 14px; + border-radius: var(--lo-radius-lg); background: linear-gradient(180deg, #291e17, #5f4435); box-shadow: 0 14px 24px rgba(27, 14, 9, 0.35), 0 0 0 3px rgba(255, 246, 226, 0.35); position: relative; @@ -136,13 +152,14 @@ flex: 1; position: relative; z-index: 1; + padding-right: 2.5rem; } .fvtt-les-oublies .sheet-kicker { margin: 0 0 0.18rem; color: var(--lo-blood); font-family: "Cinzel", serif; - font-size: 0.78rem; - letter-spacing: 0.24em; + font-size: 0.72rem; + letter-spacing: 0.2em; text-transform: uppercase; } .fvtt-les-oublies .sheet-title { @@ -152,7 +169,7 @@ .fvtt-les-oublies .sheet-title input, .fvtt-les-oublies .header-fields h1 input { font-family: "IM Fell English SC", "Cinzel", serif; - font-size: clamp(2rem, 2.6vw, 2.8rem); + font-size: clamp(1.75rem, 2.2vw, 2.35rem); letter-spacing: 0.04em; color: #2b1b14; background: transparent; @@ -162,15 +179,56 @@ height: auto; } .fvtt-les-oublies .sheet-subtitle { - margin: 0.35rem 0 0; + margin: var(--lo-space-xs) 0 0; color: var(--lo-ink-soft); - font-size: 1.05rem; + font-size: var(--lo-font-body); font-style: italic; } .fvtt-les-oublies .sheet-grid { display: grid; - gap: 1rem; - margin-bottom: 1rem; + gap: var(--lo-space-lg); + margin-bottom: var(--lo-space-lg); +} +.fvtt-les-oublies .sheet-tabs { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: var(--lo-space-xs); + margin: 0 0 var(--lo-space-lg); +} +.fvtt-les-oublies .sheet-tab-button { + display: inline-flex; + align-items: center; + gap: var(--lo-space-xs); + min-height: 1.9rem; + padding: 0.4rem 0.72rem; + border-radius: 999px; + border: 1px solid rgba(207, 176, 106, 0.35); + background: rgba(250, 240, 217, 0.2); + color: #f4e8cf; + font-family: "Cinzel", serif; + font-size: var(--lo-font-button); + letter-spacing: 0.07em; + text-transform: uppercase; + transition: background 120ms ease, transform 120ms ease, border-color 120ms ease; +} +.fvtt-les-oublies .sheet-tab-button:hover, +.fvtt-les-oublies .sheet-tab-button:focus { + background: rgba(250, 240, 217, 0.3); + border-color: rgba(207, 176, 106, 0.6); + transform: translateY(-1px); +} +.fvtt-les-oublies .sheet-tab-button.active { + background: linear-gradient(135deg, rgba(250, 240, 217, 0.94), rgba(232, 214, 182, 0.88)); + color: #392319; + border-color: rgba(207, 176, 106, 0.78); + box-shadow: 0 8px 20px rgba(0, 0, 0, 0.18); +} +.fvtt-les-oublies .sheet-tab { + display: none; +} +.fvtt-les-oublies .sheet-tab.active { + display: block; } .fvtt-les-oublies .sheet-grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); @@ -178,9 +236,9 @@ .fvtt-les-oublies .sheet-card { background: linear-gradient(180deg, var(--lo-panel), var(--lo-panel-heavy)), linear-gradient(135deg, rgba(255, 255, 255, 0.24), transparent); border: 1px solid rgba(133, 99, 74, 0.5); - border-radius: 16px; - padding: 1rem 1rem 0.95rem; - margin-bottom: 1rem; + border-radius: var(--lo-radius-xl); + padding: 0.82rem 0.82rem 0.78rem; + margin-bottom: var(--lo-space-lg); box-shadow: 0 12px 28px rgba(0, 0, 0, 0.18), inset 0 1px 0 rgba(255, 251, 243, 0.6), inset 0 0 0 1px rgba(255, 243, 218, 0.18); position: relative; overflow: hidden; @@ -200,14 +258,14 @@ color: #3a251a; } .fvtt-les-oublies .sheet-card h2 { - margin: 0 0 0.75rem; - padding-bottom: 0.35rem; + margin: 0 0 var(--lo-space-md); + padding-bottom: var(--lo-space-xs); border-bottom: 1px solid rgba(109, 41, 34, 0.18); - font-size: 1rem; + font-size: 0.92rem; } .fvtt-les-oublies .sheet-card h3 { - margin: 0 0 0.55rem; - font-size: 0.84rem; + margin: 0 0 var(--lo-space-sm); + font-size: 0.78rem; } .fvtt-les-oublies .summary-card { background: linear-gradient(180deg, rgba(246, 235, 210, 0.94), rgba(228, 213, 179, 0.95)), linear-gradient(135deg, rgba(207, 176, 106, 0.16), transparent 60%); @@ -221,12 +279,46 @@ .fvtt-les-oublies .notes-card { background: linear-gradient(180deg, rgba(240, 229, 207, 0.98), rgba(223, 207, 177, 0.95)); } +.fvtt-les-oublies .identity-card--compact { + padding: 0.62rem 0.72rem 0.68rem; +} +.fvtt-les-oublies .identity-card--compact h2 { + margin-bottom: 0.5rem; + padding-bottom: 0.2rem; + font-size: 0.82rem; +} +.fvtt-les-oublies .identity-card--compact .identity-grid { + display: grid !important; + grid-template-columns: repeat(2, minmax(0, 1fr)) !important; + gap: 0.35rem 0.65rem; +} +.fvtt-les-oublies .identity-card--compact .field-row { + gap: 0.4rem; + margin-bottom: 0; + min-width: 0; +} +.fvtt-les-oublies .identity-card--compact .field-row label { + min-width: 4.25rem; + font-size: 0.62rem; + letter-spacing: 0.05em; +} +.fvtt-les-oublies .identity-card--compact .field-row input[type="number"] { + flex: 0 0 3.5rem; + width: 3.5rem; + min-width: 3.5rem; +} +.fvtt-les-oublies .identity-card--compact .field-row input[type="text"] { + min-width: 0; +} +.fvtt-les-oublies .identity-card--compact .field-row input[type="checkbox"] { + margin: 0; +} .fvtt-les-oublies .sheet-actions, .fvtt-les-oublies .embed-buttons, .fvtt-les-oublies .item-controls, .fvtt-les-oublies .section-title-row { display: flex; - gap: 0.5rem; + gap: var(--lo-space-xs); align-items: center; justify-content: space-between; flex-wrap: wrap; @@ -235,32 +327,32 @@ .fvtt-les-oublies .profile-cell { display: flex; align-items: center; - gap: 0.65rem; - margin-bottom: 0.65rem; + gap: var(--lo-space-sm); + margin-bottom: var(--lo-space-sm); } .fvtt-les-oublies .field-row label, .fvtt-les-oublies .profile-cell label { min-width: 10rem; font-family: "Cinzel", serif; - font-size: 0.74rem; + font-size: var(--lo-font-label); font-weight: 700; - letter-spacing: 0.08em; + letter-spacing: 0.07em; text-transform: uppercase; color: #51392b; } .fvtt-les-oublies .field-row span { font-family: "Cinzel", serif; - font-size: 0.84rem; + font-size: var(--lo-font-meta); color: var(--lo-blood); } .fvtt-les-oublies .profile-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 0.75rem 1rem; + gap: var(--lo-space-sm) var(--lo-space-lg); } .fvtt-les-oublies .profile-cell { - padding: 0.7rem 0.8rem; - border-radius: 14px; + padding: 0.55rem 0.65rem; + border-radius: var(--lo-radius-lg); background: linear-gradient(180deg, rgba(255, 250, 243, 0.7), rgba(230, 214, 185, 0.6)); border: 1px solid rgba(130, 98, 71, 0.2); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.5); @@ -268,20 +360,51 @@ flex-wrap: wrap; } .fvtt-les-oublies .group-block + .group-block { - margin-top: 1rem; + margin-top: var(--lo-space-lg); +} +.fvtt-les-oublies .group-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--lo-space-sm); + margin-bottom: var(--lo-space-sm); + flex-wrap: wrap; +} +.fvtt-les-oublies .profile-badge { + display: inline-flex; + align-items: center; + gap: var(--lo-space-xs); + margin: 0; + padding: 0.3rem 0.45rem; + border-radius: 999px; + border: 1px solid rgba(118, 85, 58, 0.24); + background: rgba(255, 250, 243, 0.72); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.56); +} +.fvtt-les-oublies .profile-badge span { + font-family: "Cinzel", serif; + font-size: var(--lo-font-label); + font-weight: 700; + letter-spacing: 0.07em; + text-transform: uppercase; + color: #5f4332; +} +.fvtt-les-oublies .profile-badge input[type="number"] { + width: 3.2rem; + min-width: 3.2rem; } .fvtt-les-oublies .item-list { display: flex; flex-direction: column; - gap: 0.65rem; + gap: var(--lo-space-sm); } .fvtt-les-oublies .item-card { display: flex; justify-content: space-between; - gap: 1rem; - padding: 0.8rem 0.9rem; + gap: var(--lo-space-lg); + padding: 0.62rem 0.72rem; border: 1px solid rgba(118, 85, 58, 0.22); - border-radius: 14px; + border-radius: var(--lo-radius-lg); background: linear-gradient(180deg, rgba(255, 252, 246, 0.78), rgba(239, 229, 206, 0.78)), linear-gradient(135deg, rgba(207, 176, 106, 0.08), transparent); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.56), 0 6px 14px rgba(0, 0, 0, 0.08); position: relative; @@ -289,16 +412,16 @@ .fvtt-les-oublies .item-card::before { content: ""; position: absolute; - left: 0.55rem; - top: 0.6rem; - bottom: 0.6rem; + left: 0.45rem; + top: 0.48rem; + bottom: 0.48rem; width: 3px; border-radius: 999px; background: linear-gradient(180deg, var(--lo-gold), var(--lo-blood)); opacity: 0.8; } .fvtt-les-oublies .item-card > div:first-child { - padding-left: 0.6rem; + padding-left: 0.5rem; } .fvtt-les-oublies .item-card strong, .fvtt-les-oublies .reference-list strong { @@ -306,33 +429,230 @@ letter-spacing: 0.04em; color: #3d281d; } +.fvtt-les-oublies .skills-ledger-card { + padding: 0.72rem 0.76rem 0.68rem; +} +.fvtt-les-oublies .skills-ledger-card .section-title-row { + margin-bottom: 0.45rem; +} +.fvtt-les-oublies .skills-group + .skills-group { + margin-top: 0.7rem; + padding-top: 0.55rem; + border-top: 1px solid rgba(111, 84, 55, 0.14); +} +.fvtt-les-oublies .skills-columns { + display: grid !important; + grid-template-columns: repeat(2, minmax(0, 1fr)) !important; + gap: 0.8rem; + align-items: start; +} +.fvtt-les-oublies .skills-column { + min-width: 0; +} +.fvtt-les-oublies .skills-ledger-card .group-header { + margin-bottom: 0.4rem; + gap: 0.45rem; +} +.fvtt-les-oublies .skills-ledger-card .group-header h3 { + margin: 0; + font-size: 0.72rem; +} +.fvtt-les-oublies .skills-ledger-card .profile-badge { + padding: 0.2rem 0.38rem; + gap: 0.32rem; +} +.fvtt-les-oublies .skills-ledger-card .profile-badge span { + font-size: 0.58rem; +} +.fvtt-les-oublies .skills-ledger-card .profile-badge input[type="number"] { + width: 2.8rem; + min-width: 2.8rem; +} +.fvtt-les-oublies .skills-item-list { + gap: 0.35rem; +} +.fvtt-les-oublies .skill-card { + gap: 0.45rem; + padding: 0.42rem 0.58rem; +} +.fvtt-les-oublies .skill-card::before { + left: 0.34rem; + top: 0.34rem; + bottom: 0.34rem; +} +.fvtt-les-oublies .skill-card > div:first-child { + padding-left: 0.38rem; +} +.fvtt-les-oublies .skill-card-main { + display: flex; + align-items: center; + gap: 0.45rem; + min-width: 0; + flex-wrap: wrap; +} +.fvtt-les-oublies .skill-card strong { + font-size: 0.72rem; + line-height: 1.1; +} +.fvtt-les-oublies .skill-summary { + color: #5c4334; + font-size: 0.66rem; + line-height: 1.1; +} +.fvtt-les-oublies .skill-controls { + gap: 0.28rem; + flex-wrap: nowrap; +} +.fvtt-les-oublies .skills-ledger-card .item-controls button { + min-height: 1.6rem; + padding: 0.22rem 0.48rem; + font-size: 0.58rem; +} +@media (max-width: 980px) { + .fvtt-les-oublies .skills-columns { + grid-template-columns: minmax(0, 1fr) !important; + } +} .fvtt-les-oublies .reference-list { margin: 0; - padding-left: 1.2rem; + padding-left: 1rem; } .fvtt-les-oublies .reference-list li + li { - margin-top: 0.35rem; + margin-top: var(--lo-space-xs); +} +.fvtt-les-oublies .creation-slots { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: var(--lo-space-sm); + margin-bottom: var(--lo-space-md); +} +.fvtt-les-oublies .creation-slots--header { + margin: var(--lo-space-sm) 0 0; + gap: var(--lo-space-xs); +} +.fvtt-les-oublies .creation-slot { + display: flex; + flex-direction: column; + gap: var(--lo-space-sm); + min-height: 8.75rem; + padding: 0.72rem; + border-radius: var(--lo-radius-lg); + border: 1px dashed rgba(109, 41, 34, 0.32); + background: linear-gradient(180deg, rgba(255, 252, 246, 0.84), rgba(237, 226, 203, 0.8)), linear-gradient(135deg, rgba(207, 176, 106, 0.08), transparent); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.56), 0 4px 12px rgba(0, 0, 0, 0.06); +} +.fvtt-les-oublies .creation-slot.is-filled { + border-style: solid; + border-color: rgba(118, 85, 58, 0.24); +} +.fvtt-les-oublies .creation-slot.is-empty { + justify-content: center; + background: linear-gradient(180deg, rgba(248, 241, 228, 0.7), rgba(231, 219, 194, 0.74)), repeating-linear-gradient(-45deg, rgba(109, 41, 34, 0.035), rgba(109, 41, 34, 0.035) 8px, transparent 8px, transparent 16px); +} +.fvtt-les-oublies .creation-slot-header, +.fvtt-les-oublies .creation-slot-body { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: var(--lo-space-sm); +} +.fvtt-les-oublies .creation-slot-body { + flex: 1; +} +.fvtt-les-oublies .creation-slot-kicker { + margin: 0 0 0.15rem; + font-family: "Cinzel", serif; + font-size: var(--lo-font-label); + font-weight: 700; + letter-spacing: 0.08em; + text-transform: uppercase; + color: #6a4d38; +} +.fvtt-les-oublies .creation-slot-name { + display: block; + font-family: "Cinzel", serif; + font-size: 0.95rem; + color: #352116; +} +.fvtt-les-oublies .creation-slot-image { + width: 3rem; + height: 3rem; + object-fit: cover; + border-radius: var(--lo-radius-md); + border: 1px solid rgba(118, 85, 58, 0.24); + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.12); +} +.fvtt-les-oublies .creation-slot .item-controls { + justify-content: flex-end; +} +.fvtt-les-oublies .creation-slot .item-controls button { + min-height: 1.55rem; + padding: 0.2rem 0.5rem; +} +.fvtt-les-oublies .creation-slot .help-text { + margin: 0; + flex: 1; +} +.fvtt-les-oublies .creation-slot--compact { + min-height: auto; + padding: 0.42rem 0.5rem; + gap: var(--lo-space-xs); +} +.fvtt-les-oublies .creation-slot--compact .creation-slot-header { + align-items: center; +} +.fvtt-les-oublies .creation-slot--compact .creation-slot-kicker { + margin-bottom: 0.05rem; + font-size: 0.6rem; +} +.fvtt-les-oublies .creation-slot--compact .creation-slot-name { + font-size: 0.78rem; + line-height: 1.1; +} +.fvtt-les-oublies .item-controls--compact { + gap: 0.2rem; +} +.fvtt-les-oublies .item-controls--compact button { + min-height: 1.3rem; + padding: 0.08rem 0.38rem; + font-size: 0.56rem; } .fvtt-les-oublies .help-text { color: var(--lo-ink-soft); - font-size: 0.96rem; + font-size: var(--lo-font-meta); font-style: italic; } .fvtt-les-oublies .mode-button, -.fvtt-les-oublies button { +.fvtt-les-oublies .window-content button { cursor: pointer; border: 1px solid rgba(99, 61, 40, 0.45); border-radius: 999px; background: linear-gradient(180deg, #2e3f34, #18231d); color: #f2e5c8; font-family: "Cinzel", serif; - font-size: 0.72rem; - letter-spacing: 0.12em; + font-size: var(--lo-font-button); + letter-spacing: 0.1em; text-transform: uppercase; - padding: 0.5rem 0.9rem; + min-height: 1.9rem; + padding: 0.38rem 0.72rem; box-shadow: 0 8px 18px rgba(0, 0, 0, 0.18), inset 0 1px 0 rgba(255, 255, 255, 0.08); transition: transform 120ms ease, box-shadow 120ms ease, border-color 120ms ease; } +.fvtt-les-oublies .mode-button--icon { + position: absolute; + top: 0; + right: 0; + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 1.85rem; + min-height: 1.85rem; + padding: 0; + z-index: 2; +} +.fvtt-les-oublies .mode-button--icon i { + pointer-events: none; +} .fvtt-les-oublies .les-oublies-roll-dialog { color: var(--lo-ink); } @@ -343,6 +663,9 @@ .fvtt-les-oublies .les-oublies-roll-dialog .field-row select { flex: 1; } +.fvtt-les-oublies .les-oublies-roll-dialog .field-row input[type="number"] { + flex: 0 0 var(--lo-number-width); +} .fvtt-les-oublies button:hover, .fvtt-les-oublies button:focus { transform: translateY(-1px); @@ -355,14 +678,19 @@ .fvtt-les-oublies textarea { background: linear-gradient(180deg, rgba(255, 251, 244, 0.96), rgba(238, 226, 201, 0.96)); border: 1px solid rgba(113, 79, 56, 0.42); - border-radius: 10px; + border-radius: var(--lo-radius-sm); color: #2e1f18; font-family: "Cormorant Garamond", Georgia, serif; - font-size: 1rem; - min-height: 2.1rem; - padding: 0.2rem 0.65rem; + font-size: var(--lo-font-body); + min-height: var(--lo-control-height); + padding: 0.1rem 0.55rem; box-shadow: inset 0 1px 3px rgba(72, 46, 30, 0.08); } +.fvtt-les-oublies input[type="number"] { + width: var(--lo-number-width); + min-width: var(--lo-number-width); + text-align: center; +} .fvtt-les-oublies input[type="checkbox"] { accent-color: #6d2922; } @@ -371,12 +699,11 @@ .fvtt-les-oublies .sheet-card .editor-container, .fvtt-les-oublies prose-mirror { background: rgba(255, 252, 246, 0.62); - border-radius: 12px; + border-radius: var(--lo-radius-md); } -.fvtt-les-oublies .prosemirror, .fvtt-les-oublies prose-mirror { border: 1px solid rgba(111, 84, 55, 0.18); - padding: 0.6rem 0.7rem; + padding: 0.45rem 0.55rem; } .fvtt-les-oublies a, .fvtt-les-oublies button[data-action="rollProfile"], @@ -396,7 +723,8 @@ } @media (max-width: 900px) { .fvtt-les-oublies .sheet-grid-2, - .fvtt-les-oublies .profile-grid { + .fvtt-les-oublies .profile-grid, + .fvtt-les-oublies .creation-slots { grid-template-columns: 1fr; } .fvtt-les-oublies .hero-banner { @@ -407,6 +735,253 @@ max-width: 110px; } } +.application.dialog:has(.les-oublies-roll-dialog) { + --lo-bg-deep: #0f1714; + --lo-bg-moss: #1a2820; + --lo-bg-night: #18211b; + --lo-panel: rgba(247, 237, 218, 0.92); + --lo-panel-heavy: rgba(236, 222, 196, 0.94); + --lo-panel-soft: rgba(255, 250, 241, 0.76); + --lo-ink: #221610; + --lo-ink-soft: #584336; + --lo-gold: #cfb06a; + --lo-blood: #6d2922; + --lo-line: rgba(110, 77, 53, 0.4); + --lo-shadow: rgba(0, 0, 0, 0.42); + color: var(--lo-ink); + background: radial-gradient(circle at top left, rgba(120, 103, 63, 0.22), transparent 28%), radial-gradient(circle at top right, rgba(78, 107, 76, 0.18), transparent 24%), linear-gradient(180deg, rgba(14, 22, 18, 0.95), rgba(23, 31, 26, 0.96)); +} +.application.dialog:has(.les-oublies-roll-dialog) .window-header { + background: linear-gradient(180deg, rgba(12, 19, 16, 0.96), rgba(19, 27, 22, 0.98)), radial-gradient(circle at 20% 0, rgba(207, 176, 106, 0.12), transparent 30%); + color: #f4e8cf; + border-bottom: 1px solid rgba(207, 176, 106, 0.24); +} +.application.dialog:has(.les-oublies-roll-dialog) .window-title { + font-family: "Cinzel", serif; + letter-spacing: 0.08em; + text-transform: uppercase; +} +.application.dialog:has(.les-oublies-roll-dialog) .window-content { + padding: 0; + background: linear-gradient(180deg, rgba(12, 19, 16, 0.94), rgba(22, 29, 25, 0.98)), radial-gradient(circle at 18% 10%, rgba(207, 176, 106, 0.08), transparent 28%); + color: var(--lo-ink); +} +.application.dialog:has(.les-oublies-roll-dialog--attack) .window-content { + max-height: min(78vh, 52rem) !important; + overflow-y: auto !important; + overflow-x: hidden !important; +} +.application.dialog:has(.les-oublies-roll-dialog) .window-footer, +.application.dialog:has(.les-oublies-roll-dialog) .form-footer { + padding: 0.75rem 0.9rem 0.9rem; + background: linear-gradient(180deg, rgba(18, 24, 20, 0.96), rgba(12, 17, 14, 0.98)); + border-top: 1px solid rgba(207, 176, 106, 0.18); +} +.application.dialog:has(.les-oublies-roll-dialog) .window-footer button, +.application.dialog:has(.les-oublies-roll-dialog) .form-footer button { + cursor: pointer; + border: 1px solid rgba(99, 61, 40, 0.45); + border-radius: 999px; + background: linear-gradient(180deg, #2e3f34, #18231d); + color: #f2e5c8; + font-family: "Cinzel", serif; + font-size: 0.68rem; + letter-spacing: 0.1em; + text-transform: uppercase; + min-height: 1.95rem; + padding: 0.38rem 0.8rem; + box-shadow: 0 8px 18px rgba(0, 0, 0, 0.18), inset 0 1px 0 rgba(255, 255, 255, 0.08); +} +.application.dialog:has(.les-oublies-roll-dialog) .window-footer button:hover, +.application.dialog:has(.les-oublies-roll-dialog) .window-footer button:focus, +.application.dialog:has(.les-oublies-roll-dialog) .form-footer button:hover, +.application.dialog:has(.les-oublies-roll-dialog) .form-footer button:focus { + transform: translateY(-1px); + border-color: rgba(207, 176, 106, 0.75); + box-shadow: 0 12px 22px rgba(0, 0, 0, 0.22), 0 0 0 1px rgba(207, 176, 106, 0.24); +} +.les-oublies-roll-dialog { + display: grid; + gap: 0.8rem; + padding: 0.9rem; + color: var(--lo-ink, #221610); + background: linear-gradient(180deg, rgba(12, 19, 16, 0.3), rgba(22, 29, 25, 0.18)); +} +.les-oublies-roll-dialog .sheet-grid { + margin-bottom: 0; +} +.les-oublies-roll-dialog--attack { + gap: 0.65rem; + padding: 0.72rem; +} +.les-oublies-roll-dialog--attack .attack-dialog-grid { + align-items: start; +} +.les-oublies-roll-dialog .item-list { + display: flex; + flex-direction: column; + gap: 0.55rem; +} +.les-oublies-roll-dialog .item-card { + display: flex; + justify-content: space-between; + gap: 0.8rem; + padding: 0.62rem 0.72rem; + border: 1px solid rgba(118, 85, 58, 0.22); + border-radius: 14px; + background: linear-gradient(180deg, rgba(255, 252, 246, 0.78), rgba(239, 229, 206, 0.78)), linear-gradient(135deg, rgba(207, 176, 106, 0.08), transparent); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.56), 0 6px 14px rgba(0, 0, 0, 0.08); + position: relative; +} +.les-oublies-roll-dialog .item-card::before { + content: ""; + position: absolute; + left: 0.45rem; + top: 0.48rem; + bottom: 0.48rem; + width: 3px; + border-radius: 999px; + background: linear-gradient(180deg, var(--lo-gold), var(--lo-blood)); + opacity: 0.8; +} +.les-oublies-roll-dialog .item-card > div:first-child { + padding-left: 0.5rem; +} +.les-oublies-roll-dialog .item-card strong { + font-family: "Cinzel", serif; + letter-spacing: 0.04em; + color: #3d281d; +} +.les-oublies-roll-dialog .sheet-card { + margin-bottom: 0; + background: linear-gradient(180deg, rgba(247, 237, 218, 0.95), rgba(236, 222, 196, 0.96)), linear-gradient(135deg, rgba(255, 255, 255, 0.24), transparent); + border: 1px solid rgba(133, 99, 74, 0.5); + border-radius: 16px; + padding: 0.85rem 0.9rem 0.8rem; + box-shadow: 0 12px 28px rgba(0, 0, 0, 0.18), inset 0 1px 0 rgba(255, 251, 243, 0.6), inset 0 0 0 1px rgba(255, 243, 218, 0.18); + position: relative; + overflow: hidden; +} +.les-oublies-roll-dialog .sheet-card::before { + content: ""; + position: absolute; + inset: 0; + background: linear-gradient(180deg, rgba(255, 255, 255, 0.14), transparent 22%), radial-gradient(circle at 100% 0, rgba(207, 176, 106, 0.1), transparent 26%); + pointer-events: none; +} +.les-oublies-roll-dialog .sheet-card > * { + position: relative; + z-index: 1; +} +.les-oublies-roll-dialog .sheet-card h2 { + margin: 0 0 0.65rem; + padding-bottom: 0.35rem; + border-bottom: 1px solid rgba(109, 41, 34, 0.18); + font-family: "Cinzel", serif; + font-size: 0.9rem; + letter-spacing: 0.08em; + text-transform: uppercase; + color: #3a251a; +} +.les-oublies-roll-dialog .field-row { + display: flex; + align-items: center; + gap: 0.55rem; + margin-bottom: 0.55rem; +} +.les-oublies-roll-dialog--attack .dialog-field-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 0.4rem 0.65rem; +} +.les-oublies-roll-dialog--attack .field-row { + gap: 0.4rem; + margin-bottom: 0; + min-width: 0; +} +.les-oublies-roll-dialog--attack .field-row--wide { + grid-column: 1 / -1; +} +.les-oublies-roll-dialog--attack .field-row--toggle input[type="checkbox"] { + margin-left: auto; +} +.les-oublies-roll-dialog .field-row label { + min-width: 8.5rem; + font-family: "Cinzel", serif; + font-size: 0.68rem; + font-weight: 700; + letter-spacing: 0.07em; + text-transform: uppercase; + color: #51392b; +} +.les-oublies-roll-dialog--attack .field-row label { + min-width: 6.2rem; + font-size: 0.62rem; + letter-spacing: 0.05em; +} +.les-oublies-roll-dialog .field-row input, +.les-oublies-roll-dialog .field-row select { + flex: 1; +} +.les-oublies-roll-dialog--attack .field-row input[type="text"], +.les-oublies-roll-dialog--attack .field-row select { + min-width: 0; +} +.les-oublies-roll-dialog .field-row input[type="number"] { + flex: 0 0 4.75rem; +} +.les-oublies-roll-dialog--attack .field-row input[type="number"] { + flex: 0 0 4.1rem; + width: 4.1rem; + min-width: 4.1rem; +} +@media (max-width: 900px) { + .les-oublies-roll-dialog--attack .sheet-grid, + .les-oublies-roll-dialog--attack .dialog-field-grid { + grid-template-columns: minmax(0, 1fr); + } +} +.les-oublies-roll-dialog input[type="text"], +.les-oublies-roll-dialog input[type="number"], +.les-oublies-roll-dialog select, +.les-oublies-roll-dialog textarea { + background: linear-gradient(180deg, rgba(255, 251, 244, 0.96), rgba(238, 226, 201, 0.96)); + border: 1px solid rgba(113, 79, 56, 0.42); + border-radius: 10px; + color: #2e1f18; + font-family: "Cormorant Garamond", Georgia, serif; + font-size: 0.94rem; + min-height: 1.95rem; + padding: 0.1rem 0.55rem; + box-shadow: inset 0 1px 3px rgba(72, 46, 30, 0.08); +} +.les-oublies-roll-dialog input[type="checkbox"] { + accent-color: #6d2922; +} +.les-oublies-roll-dialog .help-text { + margin: 0.55rem 0 0; + padding: 0.55rem 0.65rem; + border-radius: 12px; + background: rgba(255, 249, 239, 0.56); + border: 1px solid rgba(118, 85, 58, 0.14); + color: #5b4638; + font-size: 0.84rem; + font-style: italic; +} +.les-oublies-roll-dialog .target-status { + font-style: normal; + line-height: 1.35; +} +.les-oublies-roll-dialog .target-status[data-state="empty"] { + background: rgba(120, 54, 29, 0.11); + border-color: rgba(120, 54, 29, 0.28); + color: #6f2b22; +} +.les-oublies-roll-dialog .target-status[data-state="selected"] { + background: rgba(38, 89, 68, 0.12); + border-color: rgba(38, 89, 68, 0.28); + color: #24483a; +} .chat-message .les-oublies-chat-card, .chat-popout .les-oublies-chat-card, #chat-log .les-oublies-chat-card { @@ -420,9 +995,9 @@ color: var(--lo-chat-ink); background: linear-gradient(180deg, var(--lo-chat-bg-top), var(--lo-chat-bg-bottom)), linear-gradient(135deg, rgba(207, 176, 106, 0.18), transparent 72%); border: 1px solid rgba(133, 99, 74, 0.45); - border-radius: 18px; - padding: 0.9rem 1rem; - margin: 0.2rem 0; + border-radius: var(--lo-radius-lg); + padding: 0.54rem 0.64rem; + margin: 0.12rem 0; box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.56), 0 10px 28px rgba(0, 0, 0, 0.14); position: relative; overflow: hidden; @@ -450,8 +1025,8 @@ .chat-popout .chat-card-banner, #chat-log .chat-card-banner { display: grid; - grid-template-columns: 3rem 1fr auto; - gap: 0.8rem; + grid-template-columns: 2.2rem 1fr auto; + gap: 0.45rem; align-items: center; position: relative; z-index: 1; @@ -459,10 +1034,10 @@ .chat-message .chat-card-portrait, .chat-popout .chat-card-portrait, #chat-log .chat-card-portrait { - width: 3rem; - height: 3rem; + width: 2.2rem; + height: 2.2rem; object-fit: cover; - border-radius: 14px; + border-radius: var(--lo-radius-lg); border: 1px solid rgba(91, 60, 39, 0.5); box-shadow: 0 8px 18px rgba(0, 0, 0, 0.16); background: linear-gradient(180deg, #2c1d16, #6b4a37); @@ -472,43 +1047,115 @@ #chat-log .chat-card-kicker { margin: 0; font-family: "Cinzel", serif; - font-size: 0.66rem; - letter-spacing: 0.18em; + font-size: 0.54rem; + letter-spacing: 0.11em; text-transform: uppercase; color: var(--lo-chat-blood); } .chat-message .chat-card-header h3, .chat-popout .chat-card-header h3, #chat-log .chat-card-header h3 { - margin: 0.08rem 0 0; + margin: 0.02rem 0 0; font-family: "Cinzel", serif; - font-size: 1rem; - letter-spacing: 0.06em; + font-size: 0.8rem; + letter-spacing: 0.05em; text-transform: uppercase; color: #3a251a; } .chat-message .chat-card-subtitle, .chat-popout .chat-card-subtitle, #chat-log .chat-card-subtitle { - margin: 0.2rem 0 0; + margin: 0.08rem 0 0; color: var(--lo-chat-soft); - font-size: 0.95rem; + font-size: 0.74rem; + line-height: 1.2; +} +.chat-message .chat-card-banner--confrontation, +.chat-popout .chat-card-banner--confrontation, +#chat-log .chat-card-banner--confrontation { + grid-template-columns: 2.2rem minmax(0, 1fr); + grid-template-areas: "portrait heading" "portrait outcome"; + align-items: start; + gap: 0.38rem 0.55rem; +} +.chat-message .chat-card-banner--confrontation .chat-card-portrait, +.chat-popout .chat-card-banner--confrontation .chat-card-portrait, +#chat-log .chat-card-banner--confrontation .chat-card-portrait { + grid-area: portrait; +} +.chat-message .chat-card-banner--confrontation .chat-card-heading, +.chat-popout .chat-card-banner--confrontation .chat-card-heading, +#chat-log .chat-card-banner--confrontation .chat-card-heading { + grid-area: heading; + min-width: 0; +} +.chat-message .chat-card-banner--confrontation .chat-card-outcome, +.chat-popout .chat-card-banner--confrontation .chat-card-outcome, +#chat-log .chat-card-banner--confrontation .chat-card-outcome { + grid-area: outcome; + min-width: 0; +} +.chat-message .chat-card-meta-row, +.chat-popout .chat-card-meta-row, +#chat-log .chat-card-meta-row { + display: flex; + flex-wrap: wrap; + gap: 0.28rem; + margin-bottom: 0.18rem; +} +.chat-message .chat-card-pill, +.chat-popout .chat-card-pill, +#chat-log .chat-card-pill { + display: inline-flex; + align-items: center; + padding: 0.12rem 0.38rem; + border-radius: 999px; + background: rgba(109, 41, 34, 0.08); + border: 1px solid rgba(109, 41, 34, 0.16); + color: var(--lo-chat-blood); + font-family: "Cinzel", serif; + font-size: 0.52rem; + letter-spacing: 0.08em; + text-transform: uppercase; + line-height: 1.1; +} +.chat-message .chat-card-pill--soft, +.chat-popout .chat-card-pill--soft, +#chat-log .chat-card-pill--soft { + background: rgba(255, 250, 241, 0.78); + color: var(--lo-chat-soft); + border-color: rgba(124, 96, 74, 0.18); +} +.chat-message .chat-card-header--confrontation h3, +.chat-popout .chat-card-header--confrontation h3, +#chat-log .chat-card-header--confrontation h3 { + margin-top: 0; + font-size: 0.82rem; + line-height: 1.15; +} +.chat-message .chat-card-header--confrontation .chat-card-badge, +.chat-popout .chat-card-header--confrontation .chat-card-badge, +#chat-log .chat-card-header--confrontation .chat-card-badge { + display: inline-flex; + justify-content: center; + max-width: none; } .chat-message .chat-card-badge, .chat-popout .chat-card-badge, #chat-log .chat-card-badge { align-self: start; - padding: 0.38rem 0.65rem; + padding: 0.18rem 0.42rem; border-radius: 999px; border: 1px solid var(--lo-chat-line); background: rgba(255, 249, 239, 0.7); color: #3a251a; font-family: "Cinzel", serif; - font-size: 0.72rem; - letter-spacing: 0.08em; + font-size: 0.56rem; + letter-spacing: 0.05em; text-transform: uppercase; text-align: center; - max-width: 13rem; + max-width: 10rem; + line-height: 1.15; } .chat-message .chat-card-badge.success, .chat-popout .chat-card-badge.success, @@ -536,65 +1183,76 @@ .chat-message .chat-card-body p, .chat-popout .chat-card-body p, #chat-log .chat-card-body p { - margin: 0.45rem 0 0; + margin: 0.32rem 0 0; } .chat-message .roll-summary-grid, .chat-popout .roll-summary-grid, #chat-log .roll-summary-grid { display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 0.55rem; - margin: 0.85rem 0 0.8rem; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 0.28rem 0.38rem; + margin: 0.42rem 0 0.38rem; } .chat-message .roll-summary-grid div, .chat-popout .roll-summary-grid div, #chat-log .roll-summary-grid div { - padding: 0.58rem 0.68rem; - border-radius: 12px; - background: rgba(255, 250, 241, 0.68); - border: 1px solid var(--lo-chat-line); + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 0.45rem; + padding: 0.22rem 0.42rem; + border-radius: var(--lo-radius-md); + background: rgba(255, 252, 245, 0.9); + border: 1px solid rgba(114, 80, 55, 0.2); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.58); } .chat-message .roll-summary-grid span, .chat-popout .roll-summary-grid span, #chat-log .roll-summary-grid span { - display: block; + display: inline; font-family: "Cinzel", serif; - font-size: 0.66rem; - letter-spacing: 0.08em; + font-size: 0.52rem; + letter-spacing: 0.05em; text-transform: uppercase; color: var(--lo-chat-blood); + line-height: 1.1; } .chat-message .roll-summary-grid strong, .chat-popout .roll-summary-grid strong, #chat-log .roll-summary-grid strong { - font-size: 1.08rem; + flex: 0 0 auto; + font-size: 0.82rem; color: var(--lo-chat-ink); + line-height: 1; + text-align: right; } .chat-message .roll-formula, .chat-popout .roll-formula, #chat-log .roll-formula { - margin-top: 0.2rem; - padding: 0.55rem 0.7rem; - border-radius: 12px; - background: rgba(255, 249, 239, 0.52); - border: 1px solid rgba(118, 85, 58, 0.14); + margin-top: var(--lo-space-2xs); + padding: 0.28rem 0.42rem; + border-radius: var(--lo-radius-md); + background: rgba(255, 249, 239, 0.82); + border: 1px solid rgba(118, 85, 58, 0.2); + line-height: 1.24; + font-size: 0.78rem; } .chat-message .dice-strip, .chat-popout .dice-strip, #chat-log .dice-strip { display: flex; flex-wrap: wrap; - gap: 0.55rem; - margin-top: 0.8rem; + gap: 0.28rem; + margin-top: 0.4rem; } .chat-message .die-chip, .chat-popout .die-chip, #chat-log .die-chip { - min-width: 8.4rem; - padding: 0.62rem 0.72rem; - border-radius: 13px; - background: rgba(247, 238, 221, 0.8); - border: 1px solid rgba(118, 85, 58, 0.18); + min-width: 5.7rem; + padding: 0.28rem 0.42rem; + border-radius: var(--lo-radius-md); + background: rgba(247, 238, 221, 0.92); + border: 1px solid rgba(118, 85, 58, 0.24); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.52); } .chat-message .die-chip.selected, @@ -613,36 +1271,50 @@ .chat-message .die-chip span, .chat-popout .die-chip span, #chat-log .die-chip span { - display: block; + display: inline; +} +.chat-message .die-chip strong, +.chat-popout .die-chip strong, +#chat-log .die-chip strong { + margin-right: 0.25rem; + font-size: 0.72rem; } .chat-message .die-chip span, .chat-popout .die-chip span, #chat-log .die-chip span { color: var(--lo-chat-ink); + font-size: 0.78rem; } .chat-message .die-chip em, .chat-popout .die-chip em, #chat-log .die-chip em { color: var(--lo-chat-blood); - font-size: 0.82rem; + display: block; + margin-top: 0.08rem; + font-size: 0.66rem; + line-height: 1.1; } .chat-message .chat-callouts, .chat-popout .chat-callouts, #chat-log .chat-callouts { display: flex; flex-wrap: wrap; - gap: 0.6rem; - margin-top: 0.8rem; + gap: 0.28rem; + margin-top: 0.4rem; } .chat-message .chat-callout, .chat-popout .chat-callout, #chat-log .chat-callout { - min-width: 10rem; - flex: 1 1 10rem; - padding: 0.62rem 0.75rem; - border-radius: 13px; - background: rgba(255, 250, 241, 0.62); - border: 1px solid var(--lo-chat-line); + min-width: 7rem; + flex: 1 1 7rem; + display: flex; + align-items: baseline; + flex-wrap: wrap; + gap: 0.2rem 0.36rem; + padding: 0.28rem 0.42rem; + border-radius: var(--lo-radius-md); + background: rgba(255, 250, 241, 0.84); + border: 1px solid rgba(114, 80, 55, 0.2); } .chat-message .chat-callout span, .chat-popout .chat-callout span, @@ -653,14 +1325,14 @@ .chat-message .chat-callout em, .chat-popout .chat-callout em, #chat-log .chat-callout em { - display: block; + display: inline; } .chat-message .chat-callout span, .chat-popout .chat-callout span, #chat-log .chat-callout span { font-family: "Cinzel", serif; - font-size: 0.66rem; - letter-spacing: 0.08em; + font-size: 0.52rem; + letter-spacing: 0.05em; text-transform: uppercase; color: var(--lo-chat-blood); } @@ -668,12 +1340,15 @@ .chat-popout .chat-callout strong, #chat-log .chat-callout strong { color: var(--lo-chat-ink); + font-size: 0.78rem; + line-height: 1.1; } .chat-message .chat-callout em, .chat-popout .chat-callout em, #chat-log .chat-callout em { color: var(--lo-chat-soft); - font-size: 0.88rem; + font-size: 0.68rem; + line-height: 1.1; } .chat-message .chat-callout.warning, .chat-popout .chat-callout.warning, @@ -684,17 +1359,18 @@ .chat-popout .confrontation-body, #chat-log .confrontation-body { display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 0.85rem; - margin-top: 0.8rem; + grid-template-columns: 1fr; + gap: 0.42rem; + margin-top: 0.42rem; } .chat-message .chat-side-card, .chat-popout .chat-side-card, #chat-log .chat-side-card { - padding: 0.82rem 0.88rem; - border-radius: 15px; - border: 1px solid var(--lo-chat-line); - background: rgba(255, 251, 245, 0.58); + padding: 0.42rem 0.5rem; + border-radius: var(--lo-radius-lg); + border: 1px solid rgba(114, 80, 55, 0.24); + background: rgba(255, 251, 245, 0.86); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.58), 0 6px 16px rgba(0, 0, 0, 0.08); } .chat-message .chat-side-card.is-success, .chat-popout .chat-side-card.is-success, @@ -711,25 +1387,25 @@ #chat-log .chat-side-head { display: flex; justify-content: space-between; - gap: 0.5rem; + gap: 0.35rem; align-items: baseline; - margin-bottom: 0.2rem; + margin-bottom: 0.08rem; } .chat-message .chat-side-head h2, .chat-popout .chat-side-head h2, #chat-log .chat-side-head h2 { margin: 0; font-family: "Cinzel", serif; - font-size: 0.92rem; + font-size: 0.8rem; text-transform: uppercase; - letter-spacing: 0.06em; + letter-spacing: 0.05em; color: #3a251a; } .chat-message .chat-side-mode, .chat-popout .chat-side-mode, #chat-log .chat-side-mode { color: var(--lo-chat-soft); - font-size: 0.86rem; + font-size: 0.72rem; font-style: italic; } @media (max-width: 720px) { @@ -746,10 +1422,7 @@ } .chat-message .roll-summary-grid, .chat-popout .roll-summary-grid, - #chat-log .roll-summary-grid, - .chat-message .confrontation-body, - .chat-popout .confrontation-body, - #chat-log .confrontation-body { + #chat-log .roll-summary-grid { grid-template-columns: 1fr 1fr; } } diff --git a/css/les-oublies.css.map b/css/les-oublies.css.map index a015f33..a6ae705 100644 --- a/css/les-oublies.css.map +++ b/css/les-oublies.css.map @@ -1 +1 @@ -{"version":3,"sources":["../https:/fonts.googleapis.com/css2?family=Cinzel:wght@400;600;700&family=Cormorant+Garamond:wght@400;500;600;700&family=IM+Fell+English+SC&display=swap","../components/sheets.less"],"names":[],"mappings":"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","file":"les-oublies.css","sourcesContent":[]} \ No newline at end of file +{"version":3,"sources":["../https:/fonts.googleapis.com/css2?family=Cinzel:wght@400;600;700&family=Cormorant+Garamond:wght@400;500;600;700&family=IM+Fell+English+SC&display=swap","../components/sheets.less"],"names":[],"mappings":"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","file":"les-oublies.css","sourcesContent":[]} \ No newline at end of file diff --git a/lang/fr.json b/lang/fr.json index 4b66c86..fd88021 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -40,7 +40,9 @@ "derivedOverview": "Synthèse", "embeddedItems": "Objets embarqués", "sourceData": "Données source", - "readOnlyCollection": "Cette liste est fournie par les données de référence du système." + "readOnlyCollection": "Cette liste est fournie par les données de référence du système.", + "edit": "Modifier", + "delete": "Retirer" }, "rolls": { "roll": "Lancer", @@ -71,6 +73,7 @@ "noDebt": "Aucune dette", "debtGain": "+1 dette de {type}", "notEnoughResource": "{actor} n'a pas assez de {resource}.", + "notEnoughResourceDetailed": "{actor} n'a pas assez de {resource} ({required} requis, {available} disponible(s)).", "exploded": "12 explosif", "naturalOne": "1 naturel : échec automatique.", "initiativeHint": "L'initiative utilise Rapidité / 0 puis arrondit le résultat final à la moitié supérieure, avec un plafond à 12.", diff --git a/less/components/sheets.less b/less/components/sheets.less index 776b05a..09b86b5 100644 --- a/less/components/sheets.less +++ b/less/components/sheets.less @@ -16,6 +16,22 @@ --lo-shadow: rgba(0, 0, 0, 0.42); --lo-line: rgba(110, 77, 53, 0.4); --lo-glow: rgba(207, 176, 106, 0.22); + --lo-space-2xs: 0.25rem; + --lo-space-xs: 0.4rem; + --lo-space-sm: 0.55rem; + --lo-space-md: 0.72rem; + --lo-space-lg: 0.9rem; + --lo-space-xl: 1rem; + --lo-radius-sm: 10px; + --lo-radius-md: 12px; + --lo-radius-lg: 14px; + --lo-radius-xl: 16px; + --lo-font-body: 0.94rem; + --lo-font-meta: 0.84rem; + --lo-font-label: 0.68rem; + --lo-font-button: 0.68rem; + --lo-control-height: 1.95rem; + --lo-number-width: 4.75rem; } .fvtt-les-oublies.sheet { @@ -25,7 +41,6 @@ radial-gradient(circle at top left, rgba(120, 103, 63, 0.22), transparent 28%), radial-gradient(circle at top right, rgba(78, 107, 76, 0.18), transparent 24%), linear-gradient(180deg, rgba(14, 22, 18, 0.95), rgba(23, 31, 26, 0.96)); - position: relative; } .fvtt-les-oublies.sheet .window-content { @@ -33,14 +48,15 @@ linear-gradient(180deg, rgba(12, 19, 16, 0.92), rgba(22, 29, 25, 0.96)), radial-gradient(circle at 20% 10%, rgba(207, 176, 106, 0.08), transparent 26%); color: var(--lo-ink); + overflow-x: hidden; + overflow-y: auto; } .fvtt-les-oublies { color: var(--lo-ink); - padding: 0.4rem; - position: relative; .les-oublies-sheet { + padding: var(--lo-space-xs); position: relative; } @@ -56,12 +72,12 @@ .hero-banner { display: grid; - grid-template-columns: 110px 1fr; - gap: 1.2rem; + grid-template-columns: 96px 1fr; + gap: var(--lo-space-xl); align-items: stretch; - margin-bottom: 1.1rem; - padding: 1rem 1.1rem 1.1rem; - border-radius: 18px; + margin-bottom: var(--lo-space-xl); + padding: 0.85rem 0.95rem 0.9rem; + border-radius: var(--lo-radius-xl); background: linear-gradient(135deg, rgba(250, 240, 217, 0.98), rgba(232, 214, 182, 0.92)), linear-gradient(180deg, rgba(207, 176, 106, 0.18), rgba(109, 41, 34, 0.08)); @@ -86,11 +102,11 @@ } .profile-img { - width: 110px; - height: 132px; + width: 96px; + height: 118px; object-fit: cover; border: 2px solid rgba(85, 55, 34, 0.7); - border-radius: 14px; + border-radius: var(--lo-radius-lg); background: linear-gradient(180deg, #291e17, #5f4435); box-shadow: 0 14px 24px rgba(27, 14, 9, 0.35), @@ -104,14 +120,15 @@ flex: 1; position: relative; z-index: 1; + padding-right: 2.5rem; } .sheet-kicker { margin: 0 0 0.18rem; color: var(--lo-blood); font-family: "Cinzel", serif; - font-size: 0.78rem; - letter-spacing: 0.24em; + font-size: 0.72rem; + letter-spacing: 0.2em; text-transform: uppercase; } @@ -123,7 +140,7 @@ .sheet-title input, .header-fields h1 input { font-family: "IM Fell English SC", "Cinzel", serif; - font-size: clamp(2rem, 2.6vw, 2.8rem); + font-size: clamp(1.75rem, 2.2vw, 2.35rem); letter-spacing: 0.04em; color: #2b1b14; background: transparent; @@ -134,16 +151,63 @@ } .sheet-subtitle { - margin: 0.35rem 0 0; + margin: var(--lo-space-xs) 0 0; color: var(--lo-ink-soft); - font-size: 1.05rem; + font-size: var(--lo-font-body); font-style: italic; } .sheet-grid { display: grid; - gap: 1rem; - margin-bottom: 1rem; + gap: var(--lo-space-lg); + margin-bottom: var(--lo-space-lg); + } + + .sheet-tabs { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: var(--lo-space-xs); + margin: 0 0 var(--lo-space-lg); + } + + .sheet-tab-button { + display: inline-flex; + align-items: center; + gap: var(--lo-space-xs); + min-height: 1.9rem; + padding: 0.4rem 0.72rem; + border-radius: 999px; + border: 1px solid rgba(207, 176, 106, 0.35); + background: rgba(250, 240, 217, 0.2); + color: #f4e8cf; + font-family: "Cinzel", serif; + font-size: var(--lo-font-button); + letter-spacing: 0.07em; + text-transform: uppercase; + transition: background 120ms ease, transform 120ms ease, border-color 120ms ease; + } + + .sheet-tab-button:hover, + .sheet-tab-button:focus { + background: rgba(250, 240, 217, 0.3); + border-color: rgba(207, 176, 106, 0.6); + transform: translateY(-1px); + } + + .sheet-tab-button.active { + background: linear-gradient(135deg, rgba(250, 240, 217, 0.94), rgba(232, 214, 182, 0.88)); + color: #392319; + border-color: rgba(207, 176, 106, 0.78); + box-shadow: 0 8px 20px rgba(0, 0, 0, 0.18); + } + + .sheet-tab { + display: none; + } + + .sheet-tab.active { + display: block; } .sheet-grid-2 { @@ -155,9 +219,9 @@ linear-gradient(180deg, var(--lo-panel), var(--lo-panel-heavy)), linear-gradient(135deg, rgba(255, 255, 255, 0.24), transparent); border: 1px solid rgba(133, 99, 74, 0.5); - border-radius: 16px; - padding: 1rem 1rem 0.95rem; - margin-bottom: 1rem; + border-radius: var(--lo-radius-xl); + padding: 0.82rem 0.82rem 0.78rem; + margin-bottom: var(--lo-space-lg); box-shadow: 0 12px 28px rgba(0, 0, 0, 0.18), inset 0 1px 0 rgba(255, 251, 243, 0.6), @@ -185,15 +249,15 @@ } .sheet-card h2 { - margin: 0 0 0.75rem; - padding-bottom: 0.35rem; + margin: 0 0 var(--lo-space-md); + padding-bottom: var(--lo-space-xs); border-bottom: 1px solid rgba(109, 41, 34, 0.18); - font-size: 1rem; + font-size: 0.92rem; } .sheet-card h3 { - margin: 0 0 0.55rem; - font-size: 0.84rem; + margin: 0 0 var(--lo-space-sm); + font-size: 0.78rem; } .summary-card { @@ -217,12 +281,54 @@ linear-gradient(180deg, rgba(240, 229, 207, 0.98), rgba(223, 207, 177, 0.95)); } + .identity-card--compact { + padding: 0.62rem 0.72rem 0.68rem; + } + + .identity-card--compact h2 { + margin-bottom: 0.5rem; + padding-bottom: 0.2rem; + font-size: 0.82rem; + } + + .identity-card--compact .identity-grid { + display: grid !important; + grid-template-columns: repeat(2, minmax(0, 1fr)) !important; + gap: 0.35rem 0.65rem; + } + + .identity-card--compact .field-row { + gap: 0.4rem; + margin-bottom: 0; + min-width: 0; + } + + .identity-card--compact .field-row label { + min-width: 4.25rem; + font-size: 0.62rem; + letter-spacing: 0.05em; + } + + .identity-card--compact .field-row input[type="number"] { + flex: 0 0 3.5rem; + width: 3.5rem; + min-width: 3.5rem; + } + + .identity-card--compact .field-row input[type="text"] { + min-width: 0; + } + + .identity-card--compact .field-row input[type="checkbox"] { + margin: 0; + } + .sheet-actions, .embed-buttons, .item-controls, .section-title-row { display: flex; - gap: 0.5rem; + gap: var(--lo-space-xs); align-items: center; justify-content: space-between; flex-wrap: wrap; @@ -232,36 +338,36 @@ .profile-cell { display: flex; align-items: center; - gap: 0.65rem; - margin-bottom: 0.65rem; + gap: var(--lo-space-sm); + margin-bottom: var(--lo-space-sm); } .field-row label, .profile-cell label { min-width: 10rem; font-family: "Cinzel", serif; - font-size: 0.74rem; + font-size: var(--lo-font-label); font-weight: 700; - letter-spacing: 0.08em; + letter-spacing: 0.07em; text-transform: uppercase; color: #51392b; } .field-row span { font-family: "Cinzel", serif; - font-size: 0.84rem; + font-size: var(--lo-font-meta); color: var(--lo-blood); } .profile-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 0.75rem 1rem; + gap: var(--lo-space-sm) var(--lo-space-lg); } .profile-cell { - padding: 0.7rem 0.8rem; - border-radius: 14px; + padding: 0.55rem 0.65rem; + border-radius: var(--lo-radius-lg); background: linear-gradient(180deg, rgba(255, 250, 243, 0.7), rgba(230, 214, 185, 0.6)); border: 1px solid rgba(130, 98, 71, 0.2); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.5); @@ -270,22 +376,57 @@ } .group-block + .group-block { - margin-top: 1rem; + margin-top: var(--lo-space-lg); + } + + .group-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--lo-space-sm); + margin-bottom: var(--lo-space-sm); + flex-wrap: wrap; + } + + .profile-badge { + display: inline-flex; + align-items: center; + gap: var(--lo-space-xs); + margin: 0; + padding: 0.3rem 0.45rem; + border-radius: 999px; + border: 1px solid rgba(118, 85, 58, 0.24); + background: rgba(255, 250, 243, 0.72); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.56); + } + + .profile-badge span { + font-family: "Cinzel", serif; + font-size: var(--lo-font-label); + font-weight: 700; + letter-spacing: 0.07em; + text-transform: uppercase; + color: #5f4332; + } + + .profile-badge input[type="number"] { + width: 3.2rem; + min-width: 3.2rem; } .item-list { display: flex; flex-direction: column; - gap: 0.65rem; + gap: var(--lo-space-sm); } .item-card { display: flex; justify-content: space-between; - gap: 1rem; - padding: 0.8rem 0.9rem; + gap: var(--lo-space-lg); + padding: 0.62rem 0.72rem; border: 1px solid rgba(118, 85, 58, 0.22); - border-radius: 14px; + border-radius: var(--lo-radius-lg); background: linear-gradient(180deg, rgba(255, 252, 246, 0.78), rgba(239, 229, 206, 0.78)), linear-gradient(135deg, rgba(207, 176, 106, 0.08), transparent); @@ -298,9 +439,9 @@ .item-card::before { content: ""; position: absolute; - left: 0.55rem; - top: 0.6rem; - bottom: 0.6rem; + left: 0.45rem; + top: 0.48rem; + bottom: 0.48rem; width: 3px; border-radius: 999px; background: linear-gradient(180deg, var(--lo-gold), var(--lo-blood)); @@ -308,7 +449,7 @@ } .item-card > div:first-child { - padding-left: 0.6rem; + padding-left: 0.5rem; } .item-card strong, @@ -318,23 +459,255 @@ color: #3d281d; } + .skills-ledger-card { + padding: 0.72rem 0.76rem 0.68rem; + } + + .skills-ledger-card .section-title-row { + margin-bottom: 0.45rem; + } + + .skills-group + .skills-group { + margin-top: 0.7rem; + padding-top: 0.55rem; + border-top: 1px solid rgba(111, 84, 55, 0.14); + } + + .skills-columns { + display: grid !important; + grid-template-columns: repeat(2, minmax(0, 1fr)) !important; + gap: 0.8rem; + align-items: start; + } + + .skills-column { + min-width: 0; + } + + .skills-ledger-card .group-header { + margin-bottom: 0.4rem; + gap: 0.45rem; + } + + .skills-ledger-card .group-header h3 { + margin: 0; + font-size: 0.72rem; + } + + .skills-ledger-card .profile-badge { + padding: 0.2rem 0.38rem; + gap: 0.32rem; + } + + .skills-ledger-card .profile-badge span { + font-size: 0.58rem; + } + + .skills-ledger-card .profile-badge input[type="number"] { + width: 2.8rem; + min-width: 2.8rem; + } + + .skills-item-list { + gap: 0.35rem; + } + + .skill-card { + gap: 0.45rem; + padding: 0.42rem 0.58rem; + } + + .skill-card::before { + left: 0.34rem; + top: 0.34rem; + bottom: 0.34rem; + } + + .skill-card > div:first-child { + padding-left: 0.38rem; + } + + .skill-card-main { + display: flex; + align-items: center; + gap: 0.45rem; + min-width: 0; + flex-wrap: wrap; + } + + .skill-card strong { + font-size: 0.72rem; + line-height: 1.1; + } + + .skill-summary { + color: #5c4334; + font-size: 0.66rem; + line-height: 1.1; + } + + .skill-controls { + gap: 0.28rem; + flex-wrap: nowrap; + } + + .skills-ledger-card .item-controls button { + min-height: 1.6rem; + padding: 0.22rem 0.48rem; + font-size: 0.58rem; + } + + @media (max-width: 980px) { + .skills-columns { + grid-template-columns: minmax(0, 1fr) !important; + } + } + .reference-list { margin: 0; - padding-left: 1.2rem; + padding-left: 1rem; } .reference-list li + li { - margin-top: 0.35rem; + margin-top: var(--lo-space-xs); + } + + .creation-slots { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: var(--lo-space-sm); + margin-bottom: var(--lo-space-md); + } + + .creation-slots--header { + margin: var(--lo-space-sm) 0 0; + gap: var(--lo-space-xs); + } + + .creation-slot { + display: flex; + flex-direction: column; + gap: var(--lo-space-sm); + min-height: 8.75rem; + padding: 0.72rem; + border-radius: var(--lo-radius-lg); + border: 1px dashed rgba(109, 41, 34, 0.32); + background: + linear-gradient(180deg, rgba(255, 252, 246, 0.84), rgba(237, 226, 203, 0.8)), + linear-gradient(135deg, rgba(207, 176, 106, 0.08), transparent); + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.56), + 0 4px 12px rgba(0, 0, 0, 0.06); + } + + .creation-slot.is-filled { + border-style: solid; + border-color: rgba(118, 85, 58, 0.24); + } + + .creation-slot.is-empty { + justify-content: center; + background: + linear-gradient(180deg, rgba(248, 241, 228, 0.7), rgba(231, 219, 194, 0.74)), + repeating-linear-gradient( + -45deg, + rgba(109, 41, 34, 0.035), + rgba(109, 41, 34, 0.035) 8px, + transparent 8px, + transparent 16px + ); + } + + .creation-slot-header, + .creation-slot-body { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: var(--lo-space-sm); + } + + .creation-slot-body { + flex: 1; + } + + .creation-slot-kicker { + margin: 0 0 0.15rem; + font-family: "Cinzel", serif; + font-size: var(--lo-font-label); + font-weight: 700; + letter-spacing: 0.08em; + text-transform: uppercase; + color: #6a4d38; + } + + .creation-slot-name { + display: block; + font-family: "Cinzel", serif; + font-size: 0.95rem; + color: #352116; + } + + .creation-slot-image { + width: 3rem; + height: 3rem; + object-fit: cover; + border-radius: var(--lo-radius-md); + border: 1px solid rgba(118, 85, 58, 0.24); + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.12); + } + + .creation-slot .item-controls { + justify-content: flex-end; + } + + .creation-slot .item-controls button { + min-height: 1.55rem; + padding: 0.2rem 0.5rem; + } + + .creation-slot .help-text { + margin: 0; + flex: 1; + } + + .creation-slot--compact { + min-height: auto; + padding: 0.42rem 0.5rem; + gap: var(--lo-space-xs); + } + + .creation-slot--compact .creation-slot-header { + align-items: center; + } + + .creation-slot--compact .creation-slot-kicker { + margin-bottom: 0.05rem; + font-size: 0.6rem; + } + + .creation-slot--compact .creation-slot-name { + font-size: 0.78rem; + line-height: 1.1; + } + + .item-controls--compact { + gap: 0.2rem; + } + + .item-controls--compact button { + min-height: 1.3rem; + padding: 0.08rem 0.38rem; + font-size: 0.56rem; } .help-text { color: var(--lo-ink-soft); - font-size: 0.96rem; + font-size: var(--lo-font-meta); font-style: italic; } .mode-button, - button { + .window-content button { cursor: pointer; border: 1px solid rgba(99, 61, 40, 0.45); border-radius: 999px; @@ -342,16 +715,34 @@ linear-gradient(180deg, #2e3f34, #18231d); color: #f2e5c8; font-family: "Cinzel", serif; - font-size: 0.72rem; - letter-spacing: 0.12em; + font-size: var(--lo-font-button); + letter-spacing: 0.1em; text-transform: uppercase; - padding: 0.5rem 0.9rem; + min-height: 1.9rem; + padding: 0.38rem 0.72rem; box-shadow: 0 8px 18px rgba(0, 0, 0, 0.18), inset 0 1px 0 rgba(255, 255, 255, 0.08); transition: transform 120ms ease, box-shadow 120ms ease, border-color 120ms ease; } + .mode-button--icon { + position: absolute; + top: 0; + right: 0; + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 1.85rem; + min-height: 1.85rem; + padding: 0; + z-index: 2; + } + + .mode-button--icon i { + pointer-events: none; + } + .les-oublies-roll-dialog { color: var(--lo-ink); @@ -363,6 +754,10 @@ .field-row select { flex: 1; } + + .field-row input[type="number"] { + flex: 0 0 var(--lo-number-width); + } } button:hover, @@ -380,15 +775,21 @@ textarea { background: linear-gradient(180deg, rgba(255, 251, 244, 0.96), rgba(238, 226, 201, 0.96)); border: 1px solid rgba(113, 79, 56, 0.42); - border-radius: 10px; + border-radius: var(--lo-radius-sm); color: #2e1f18; font-family: "Cormorant Garamond", Georgia, serif; - font-size: 1rem; - min-height: 2.1rem; - padding: 0.2rem 0.65rem; + font-size: var(--lo-font-body); + min-height: var(--lo-control-height); + padding: 0.1rem 0.55rem; box-shadow: inset 0 1px 3px rgba(72, 46, 30, 0.08); } + input[type="number"] { + width: var(--lo-number-width); + min-width: var(--lo-number-width); + text-align: center; + } + input[type="checkbox"] { accent-color: #6d2922; } @@ -398,13 +799,12 @@ .sheet-card .editor-container, prose-mirror { background: rgba(255, 252, 246, 0.62); - border-radius: 12px; + border-radius: var(--lo-radius-md); } - .prosemirror, prose-mirror { border: 1px solid rgba(111, 84, 55, 0.18); - padding: 0.6rem 0.7rem; + padding: 0.45rem 0.55rem; } a, @@ -433,7 +833,8 @@ @media (max-width: 900px) { .sheet-grid-2, - .profile-grid { + .profile-grid, + .creation-slots { grid-template-columns: 1fr; } @@ -448,6 +849,316 @@ } } +.application.dialog:has(.les-oublies-roll-dialog) { + --lo-bg-deep: #0f1714; + --lo-bg-moss: #1a2820; + --lo-bg-night: #18211b; + --lo-panel: rgba(247, 237, 218, 0.92); + --lo-panel-heavy: rgba(236, 222, 196, 0.94); + --lo-panel-soft: rgba(255, 250, 241, 0.76); + --lo-ink: #221610; + --lo-ink-soft: #584336; + --lo-gold: #cfb06a; + --lo-blood: #6d2922; + --lo-line: rgba(110, 77, 53, 0.4); + --lo-shadow: rgba(0, 0, 0, 0.42); + color: var(--lo-ink); + background: + radial-gradient(circle at top left, rgba(120, 103, 63, 0.22), transparent 28%), + radial-gradient(circle at top right, rgba(78, 107, 76, 0.18), transparent 24%), + linear-gradient(180deg, rgba(14, 22, 18, 0.95), rgba(23, 31, 26, 0.96)); +} + +.application.dialog:has(.les-oublies-roll-dialog) .window-header { + background: + linear-gradient(180deg, rgba(12, 19, 16, 0.96), rgba(19, 27, 22, 0.98)), + radial-gradient(circle at 20% 0, rgba(207, 176, 106, 0.12), transparent 30%); + color: #f4e8cf; + border-bottom: 1px solid rgba(207, 176, 106, 0.24); +} + +.application.dialog:has(.les-oublies-roll-dialog) .window-title { + font-family: "Cinzel", serif; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.application.dialog:has(.les-oublies-roll-dialog) .window-content { + padding: 0; + background: + linear-gradient(180deg, rgba(12, 19, 16, 0.94), rgba(22, 29, 25, 0.98)), + radial-gradient(circle at 18% 10%, rgba(207, 176, 106, 0.08), transparent 28%); + color: var(--lo-ink); +} + +.application.dialog:has(.les-oublies-roll-dialog--attack) .window-content { + max-height: min(78vh, 52rem) !important; + overflow-y: auto !important; + overflow-x: hidden !important; +} + +.application.dialog:has(.les-oublies-roll-dialog) .window-footer, +.application.dialog:has(.les-oublies-roll-dialog) .form-footer { + padding: 0.75rem 0.9rem 0.9rem; + background: + linear-gradient(180deg, rgba(18, 24, 20, 0.96), rgba(12, 17, 14, 0.98)); + border-top: 1px solid rgba(207, 176, 106, 0.18); +} + +.application.dialog:has(.les-oublies-roll-dialog) .window-footer button, +.application.dialog:has(.les-oublies-roll-dialog) .form-footer button { + cursor: pointer; + border: 1px solid rgba(99, 61, 40, 0.45); + border-radius: 999px; + background: linear-gradient(180deg, #2e3f34, #18231d); + color: #f2e5c8; + font-family: "Cinzel", serif; + font-size: 0.68rem; + letter-spacing: 0.1em; + text-transform: uppercase; + min-height: 1.95rem; + padding: 0.38rem 0.8rem; + box-shadow: + 0 8px 18px rgba(0, 0, 0, 0.18), + inset 0 1px 0 rgba(255, 255, 255, 0.08); +} + +.application.dialog:has(.les-oublies-roll-dialog) .window-footer button:hover, +.application.dialog:has(.les-oublies-roll-dialog) .window-footer button:focus, +.application.dialog:has(.les-oublies-roll-dialog) .form-footer button:hover, +.application.dialog:has(.les-oublies-roll-dialog) .form-footer button:focus { + transform: translateY(-1px); + border-color: rgba(207, 176, 106, 0.75); + box-shadow: + 0 12px 22px rgba(0, 0, 0, 0.22), + 0 0 0 1px rgba(207, 176, 106, 0.24); +} + +.les-oublies-roll-dialog { + display: grid; + gap: 0.8rem; + padding: 0.9rem; + color: var(--lo-ink, #221610); + background: + linear-gradient(180deg, rgba(12, 19, 16, 0.3), rgba(22, 29, 25, 0.18)); +} + +.les-oublies-roll-dialog .sheet-grid { + margin-bottom: 0; +} + +.les-oublies-roll-dialog--attack { + gap: 0.65rem; + padding: 0.72rem; +} + +.les-oublies-roll-dialog--attack .attack-dialog-grid { + align-items: start; +} + +.les-oublies-roll-dialog .item-list { + display: flex; + flex-direction: column; + gap: 0.55rem; +} + +.les-oublies-roll-dialog .item-card { + display: flex; + justify-content: space-between; + gap: 0.8rem; + padding: 0.62rem 0.72rem; + border: 1px solid rgba(118, 85, 58, 0.22); + border-radius: 14px; + background: + linear-gradient(180deg, rgba(255, 252, 246, 0.78), rgba(239, 229, 206, 0.78)), + linear-gradient(135deg, rgba(207, 176, 106, 0.08), transparent); + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.56), + 0 6px 14px rgba(0, 0, 0, 0.08); + position: relative; +} + +.les-oublies-roll-dialog .item-card::before { + content: ""; + position: absolute; + left: 0.45rem; + top: 0.48rem; + bottom: 0.48rem; + width: 3px; + border-radius: 999px; + background: linear-gradient(180deg, var(--lo-gold), var(--lo-blood)); + opacity: 0.8; +} + +.les-oublies-roll-dialog .item-card > div:first-child { + padding-left: 0.5rem; +} + +.les-oublies-roll-dialog .item-card strong { + font-family: "Cinzel", serif; + letter-spacing: 0.04em; + color: #3d281d; +} + +.les-oublies-roll-dialog .sheet-card { + margin-bottom: 0; + background: + linear-gradient(180deg, rgba(247, 237, 218, 0.95), rgba(236, 222, 196, 0.96)), + linear-gradient(135deg, rgba(255, 255, 255, 0.24), transparent); + border: 1px solid rgba(133, 99, 74, 0.5); + border-radius: 16px; + padding: 0.85rem 0.9rem 0.8rem; + box-shadow: + 0 12px 28px rgba(0, 0, 0, 0.18), + inset 0 1px 0 rgba(255, 251, 243, 0.6), + inset 0 0 0 1px rgba(255, 243, 218, 0.18); + position: relative; + overflow: hidden; +} + +.les-oublies-roll-dialog .sheet-card::before { + content: ""; + position: absolute; + inset: 0; + background: + linear-gradient(180deg, rgba(255, 255, 255, 0.14), transparent 22%), + radial-gradient(circle at 100% 0, rgba(207, 176, 106, 0.1), transparent 26%); + pointer-events: none; +} + +.les-oublies-roll-dialog .sheet-card > * { + position: relative; + z-index: 1; +} + +.les-oublies-roll-dialog .sheet-card h2 { + margin: 0 0 0.65rem; + padding-bottom: 0.35rem; + border-bottom: 1px solid rgba(109, 41, 34, 0.18); + font-family: "Cinzel", serif; + font-size: 0.9rem; + letter-spacing: 0.08em; + text-transform: uppercase; + color: #3a251a; +} + +.les-oublies-roll-dialog .field-row { + display: flex; + align-items: center; + gap: 0.55rem; + margin-bottom: 0.55rem; +} + +.les-oublies-roll-dialog--attack .dialog-field-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 0.4rem 0.65rem; +} + +.les-oublies-roll-dialog--attack .field-row { + gap: 0.4rem; + margin-bottom: 0; + min-width: 0; +} + +.les-oublies-roll-dialog--attack .field-row--wide { + grid-column: 1 / -1; +} + +.les-oublies-roll-dialog--attack .field-row--toggle input[type="checkbox"] { + margin-left: auto; +} + +.les-oublies-roll-dialog .field-row label { + min-width: 8.5rem; + font-family: "Cinzel", serif; + font-size: 0.68rem; + font-weight: 700; + letter-spacing: 0.07em; + text-transform: uppercase; + color: #51392b; +} + +.les-oublies-roll-dialog--attack .field-row label { + min-width: 6.2rem; + font-size: 0.62rem; + letter-spacing: 0.05em; +} + +.les-oublies-roll-dialog .field-row input, +.les-oublies-roll-dialog .field-row select { + flex: 1; +} + +.les-oublies-roll-dialog--attack .field-row input[type="text"], +.les-oublies-roll-dialog--attack .field-row select { + min-width: 0; +} + +.les-oublies-roll-dialog .field-row input[type="number"] { + flex: 0 0 4.75rem; +} + +.les-oublies-roll-dialog--attack .field-row input[type="number"] { + flex: 0 0 4.1rem; + width: 4.1rem; + min-width: 4.1rem; +} + +@media (max-width: 900px) { + .les-oublies-roll-dialog--attack .sheet-grid, + .les-oublies-roll-dialog--attack .dialog-field-grid { + grid-template-columns: minmax(0, 1fr); + } +} + +.les-oublies-roll-dialog input[type="text"], +.les-oublies-roll-dialog input[type="number"], +.les-oublies-roll-dialog select, +.les-oublies-roll-dialog textarea { + background: linear-gradient(180deg, rgba(255, 251, 244, 0.96), rgba(238, 226, 201, 0.96)); + border: 1px solid rgba(113, 79, 56, 0.42); + border-radius: 10px; + color: #2e1f18; + font-family: "Cormorant Garamond", Georgia, serif; + font-size: 0.94rem; + min-height: 1.95rem; + padding: 0.1rem 0.55rem; + box-shadow: inset 0 1px 3px rgba(72, 46, 30, 0.08); +} + +.les-oublies-roll-dialog input[type="checkbox"] { + accent-color: #6d2922; +} + +.les-oublies-roll-dialog .help-text { + margin: 0.55rem 0 0; + padding: 0.55rem 0.65rem; + border-radius: 12px; + background: rgba(255, 249, 239, 0.56); + border: 1px solid rgba(118, 85, 58, 0.14); + color: #5b4638; + font-size: 0.84rem; + font-style: italic; +} + +.les-oublies-roll-dialog .target-status { + font-style: normal; + line-height: 1.35; +} + +.les-oublies-roll-dialog .target-status[data-state="empty"] { + background: rgba(120, 54, 29, 0.11); + border-color: rgba(120, 54, 29, 0.28); + color: #6f2b22; +} + +.les-oublies-roll-dialog .target-status[data-state="selected"] { + background: rgba(38, 89, 68, 0.12); + border-color: rgba(38, 89, 68, 0.28); + color: #24483a; +} + .chat-message, .chat-popout, #chat-log { @@ -464,9 +1175,9 @@ linear-gradient(180deg, var(--lo-chat-bg-top), var(--lo-chat-bg-bottom)), linear-gradient(135deg, rgba(207, 176, 106, 0.18), transparent 72%); border: 1px solid rgba(133, 99, 74, 0.45); - border-radius: 18px; - padding: 0.9rem 1rem; - margin: 0.2rem 0; + border-radius: var(--lo-radius-lg); + padding: 0.54rem 0.64rem; + margin: 0.12rem 0; box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.56), 0 10px 28px rgba(0, 0, 0, 0.14); @@ -500,18 +1211,18 @@ .chat-card-banner { display: grid; - grid-template-columns: 3rem 1fr auto; - gap: 0.8rem; + grid-template-columns: 2.2rem 1fr auto; + gap: 0.45rem; align-items: center; position: relative; z-index: 1; } .chat-card-portrait { - width: 3rem; - height: 3rem; + width: 2.2rem; + height: 2.2rem; object-fit: cover; - border-radius: 14px; + border-radius: var(--lo-radius-lg); border: 1px solid rgba(91, 60, 39, 0.5); box-shadow: 0 8px 18px rgba(0, 0, 0, 0.16); background: linear-gradient(180deg, #2c1d16, #6b4a37); @@ -520,40 +1231,105 @@ .chat-card-kicker { margin: 0; font-family: "Cinzel", serif; - font-size: 0.66rem; - letter-spacing: 0.18em; + font-size: 0.54rem; + letter-spacing: 0.11em; text-transform: uppercase; color: var(--lo-chat-blood); } .chat-card-header h3 { - margin: 0.08rem 0 0; + margin: 0.02rem 0 0; font-family: "Cinzel", serif; - font-size: 1rem; - letter-spacing: 0.06em; + font-size: 0.8rem; + letter-spacing: 0.05em; text-transform: uppercase; color: #3a251a; } .chat-card-subtitle { - margin: 0.2rem 0 0; + margin: 0.08rem 0 0; color: var(--lo-chat-soft); - font-size: 0.95rem; + font-size: 0.74rem; + line-height: 1.2; + } + + .chat-card-banner--confrontation { + grid-template-columns: 2.2rem minmax(0, 1fr); + grid-template-areas: + "portrait heading" + "portrait outcome"; + align-items: start; + gap: 0.38rem 0.55rem; + } + + .chat-card-banner--confrontation .chat-card-portrait { + grid-area: portrait; + } + + .chat-card-banner--confrontation .chat-card-heading { + grid-area: heading; + min-width: 0; + } + + .chat-card-banner--confrontation .chat-card-outcome { + grid-area: outcome; + min-width: 0; + } + + .chat-card-meta-row { + display: flex; + flex-wrap: wrap; + gap: 0.28rem; + margin-bottom: 0.18rem; + } + + .chat-card-pill { + display: inline-flex; + align-items: center; + padding: 0.12rem 0.38rem; + border-radius: 999px; + background: rgba(109, 41, 34, 0.08); + border: 1px solid rgba(109, 41, 34, 0.16); + color: var(--lo-chat-blood); + font-family: "Cinzel", serif; + font-size: 0.52rem; + letter-spacing: 0.08em; + text-transform: uppercase; + line-height: 1.1; + } + + .chat-card-pill--soft { + background: rgba(255, 250, 241, 0.78); + color: var(--lo-chat-soft); + border-color: rgba(124, 96, 74, 0.18); + } + + .chat-card-header--confrontation h3 { + margin-top: 0; + font-size: 0.82rem; + line-height: 1.15; + } + + .chat-card-header--confrontation .chat-card-badge { + display: inline-flex; + justify-content: center; + max-width: none; } .chat-card-badge { align-self: start; - padding: 0.38rem 0.65rem; + padding: 0.18rem 0.42rem; border-radius: 999px; border: 1px solid var(--lo-chat-line); background: rgba(255, 249, 239, 0.7); color: #3a251a; font-family: "Cinzel", serif; - font-size: 0.72rem; - letter-spacing: 0.08em; + font-size: 0.56rem; + letter-spacing: 0.05em; text-transform: uppercase; text-align: center; - max-width: 13rem; + max-width: 10rem; + line-height: 1.15; } .chat-card-badge.success { @@ -576,58 +1352,69 @@ } .chat-card-body p { - margin: 0.45rem 0 0; + margin: 0.32rem 0 0; } .roll-summary-grid { display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 0.55rem; - margin: 0.85rem 0 0.8rem; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 0.28rem 0.38rem; + margin: 0.42rem 0 0.38rem; } .roll-summary-grid div { - padding: 0.58rem 0.68rem; - border-radius: 12px; - background: rgba(255, 250, 241, 0.68); - border: 1px solid var(--lo-chat-line); + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 0.45rem; + padding: 0.22rem 0.42rem; + border-radius: var(--lo-radius-md); + background: rgba(255, 252, 245, 0.9); + border: 1px solid rgba(114, 80, 55, 0.2); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.58); } .roll-summary-grid span { - display: block; + display: inline; font-family: "Cinzel", serif; - font-size: 0.66rem; - letter-spacing: 0.08em; + font-size: 0.52rem; + letter-spacing: 0.05em; text-transform: uppercase; color: var(--lo-chat-blood); + line-height: 1.1; } .roll-summary-grid strong { - font-size: 1.08rem; + flex: 0 0 auto; + font-size: 0.82rem; color: var(--lo-chat-ink); + line-height: 1; + text-align: right; } .roll-formula { - margin-top: 0.2rem; - padding: 0.55rem 0.7rem; - border-radius: 12px; - background: rgba(255, 249, 239, 0.52); - border: 1px solid rgba(118, 85, 58, 0.14); + margin-top: var(--lo-space-2xs); + padding: 0.28rem 0.42rem; + border-radius: var(--lo-radius-md); + background: rgba(255, 249, 239, 0.82); + border: 1px solid rgba(118, 85, 58, 0.2); + line-height: 1.24; + font-size: 0.78rem; } .dice-strip { display: flex; flex-wrap: wrap; - gap: 0.55rem; - margin-top: 0.8rem; + gap: 0.28rem; + margin-top: 0.4rem; } .die-chip { - min-width: 8.4rem; - padding: 0.62rem 0.72rem; - border-radius: 13px; - background: rgba(247, 238, 221, 0.8); - border: 1px solid rgba(118, 85, 58, 0.18); + min-width: 5.7rem; + padding: 0.28rem 0.42rem; + border-radius: var(--lo-radius-md); + background: rgba(247, 238, 221, 0.92); + border: 1px solid rgba(118, 85, 58, 0.24); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.52); } @@ -642,55 +1429,71 @@ .die-chip strong, .die-chip em, .die-chip span { - display: block; + display: inline; + } + + .die-chip strong { + margin-right: 0.25rem; + font-size: 0.72rem; } .die-chip span { color: var(--lo-chat-ink); + font-size: 0.78rem; } .die-chip em { color: var(--lo-chat-blood); - font-size: 0.82rem; + display: block; + margin-top: 0.08rem; + font-size: 0.66rem; + line-height: 1.1; } .chat-callouts { display: flex; flex-wrap: wrap; - gap: 0.6rem; - margin-top: 0.8rem; + gap: 0.28rem; + margin-top: 0.4rem; } .chat-callout { - min-width: 10rem; - flex: 1 1 10rem; - padding: 0.62rem 0.75rem; - border-radius: 13px; - background: rgba(255, 250, 241, 0.62); - border: 1px solid var(--lo-chat-line); + min-width: 7rem; + flex: 1 1 7rem; + display: flex; + align-items: baseline; + flex-wrap: wrap; + gap: 0.2rem 0.36rem; + padding: 0.28rem 0.42rem; + border-radius: var(--lo-radius-md); + background: rgba(255, 250, 241, 0.84); + border: 1px solid rgba(114, 80, 55, 0.2); } .chat-callout span, .chat-callout strong, .chat-callout em { - display: block; + display: inline; } .chat-callout span { font-family: "Cinzel", serif; - font-size: 0.66rem; - letter-spacing: 0.08em; + font-size: 0.52rem; + letter-spacing: 0.05em; text-transform: uppercase; color: var(--lo-chat-blood); } .chat-callout strong { color: var(--lo-chat-ink); + font-size: 0.78rem; + line-height: 1.1; } .chat-callout em { color: var(--lo-chat-soft); - font-size: 0.88rem; + font-size: 0.68rem; + line-height: 1.1; } .chat-callout.warning { @@ -699,16 +1502,19 @@ .confrontation-body { display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 0.85rem; - margin-top: 0.8rem; + grid-template-columns: 1fr; + gap: 0.42rem; + margin-top: 0.42rem; } .chat-side-card { - padding: 0.82rem 0.88rem; - border-radius: 15px; - border: 1px solid var(--lo-chat-line); - background: rgba(255, 251, 245, 0.58); + padding: 0.42rem 0.5rem; + border-radius: var(--lo-radius-lg); + border: 1px solid rgba(114, 80, 55, 0.24); + background: rgba(255, 251, 245, 0.86); + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.58), + 0 6px 16px rgba(0, 0, 0, 0.08); } .chat-side-card.is-success { @@ -722,23 +1528,23 @@ .chat-side-head { display: flex; justify-content: space-between; - gap: 0.5rem; + gap: 0.35rem; align-items: baseline; - margin-bottom: 0.2rem; + margin-bottom: 0.08rem; } .chat-side-head h2 { margin: 0; font-family: "Cinzel", serif; - font-size: 0.92rem; + font-size: 0.8rem; text-transform: uppercase; - letter-spacing: 0.06em; + letter-spacing: 0.05em; color: #3a251a; } .chat-side-mode { color: var(--lo-chat-soft); - font-size: 0.86rem; + font-size: 0.72rem; font-style: italic; } @@ -752,8 +1558,7 @@ justify-self: start; } - .roll-summary-grid, - .confrontation-body { + .roll-summary-grid { grid-template-columns: 1fr 1fr; } } diff --git a/modules/applications/sheets/arme-sheet.mjs b/modules/applications/sheets/arme-sheet.mjs index efb7a93..15db35a 100644 --- a/modules/applications/sheets/arme-sheet.mjs +++ b/modules/applications/sheets/arme-sheet.mjs @@ -3,7 +3,7 @@ import LesOubliesItemSheet from "./base-item-sheet.mjs" export default class LesOubliesArmeSheet extends LesOubliesItemSheet { static PARTS = { sheet: { - template: "systems/fvtt-les-oublies/templates/item-arme-sheet.hbs", + template: "systems/fvtt-les-oublies/templates/item-arme-sheet-v2.hbs", }, } } diff --git a/modules/applications/sheets/base-actor-sheet.mjs b/modules/applications/sheets/base-actor-sheet.mjs index 8506454..d4dcad1 100644 --- a/modules/applications/sheets/base-actor-sheet.mjs +++ b/modules/applications/sheets/base-actor-sheet.mjs @@ -32,9 +32,11 @@ export default class LesOubliesActorSheet extends HandlebarsApplicationMixin(fou rollSkill: LesOubliesActorSheet.#onRollSkill, useWeapon: LesOubliesActorSheet.#onUseWeapon, resolveWeaponDamage: LesOubliesActorSheet.#onResolveWeaponDamage, + toggleEquipped: LesOubliesActorSheet.#onToggleEquipped, useSpell: LesOubliesActorSheet.#onUseSpell, openCombatPreset: LesOubliesActorSheet.#onOpenCombatPreset, openThreadHarvest: LesOubliesActorSheet.#onOpenThreadHarvest, + openLinkedActor: LesOubliesActorSheet.#onOpenLinkedActor, }, } @@ -49,6 +51,14 @@ export default class LesOubliesActorSheet extends HandlebarsApplicationMixin(fou } async _prepareContext() { + const config = CONFIG.LESOUBLIES + const enriched = await LesOubliesUtility.prepareEnrichedHtml("Actor", this.document.type, this.document.system) + const choiceSets = { + profileOptions: config.profiles.map((profile) => ({ value: profile.id, label: profile.label })), + personnageSizeOptions: LesOubliesUtility.createRangeChoices(1, 4, config.sizes), + creatureSizeOptions: LesOubliesUtility.createRangeChoices(1, 8, config.sizes), + } + return { actor: this.document, system: this.document.system, @@ -59,8 +69,10 @@ export default class LesOubliesActorSheet extends HandlebarsApplicationMixin(fou isEditMode: this.isEditMode, isPlayMode: this.isPlayMode, isGM: game.user.isGM, - config: CONFIG.LESOUBLIES, - enrichedDescription: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.biodata?.description ?? this.document.system.description ?? "", { async: true }), + config, + choiceSets, + enriched, + enrichedDescription: foundry.utils.getProperty(enriched, "biodata.description") ?? foundry.utils.getProperty(enriched, "description") ?? "", } } @@ -99,6 +111,7 @@ export default class LesOubliesActorSheet extends HandlebarsApplicationMixin(fou static async #onEditImage(event, target) { const attr = target.dataset.edit const current = foundry.utils.getProperty(this.document, attr) + const FilePicker = foundry.applications.apps.FilePicker.implementation const fp = new FilePicker({ current, type: "image", @@ -113,11 +126,17 @@ export default class LesOubliesActorSheet extends HandlebarsApplicationMixin(fou const type = target.dataset.type if (!type) return const label = game.i18n.localize(`TYPES.Item.${type}`) - return this.document.createEmbeddedDocuments("Item", [{ + const itemData = { name: label, type, img: LesOubliesUtility.getDefaultItemImage(type), - }]) + } + if (type === "competence") { + itemData.system = { + profileKey: CONFIG.LESOUBLIES.profiles[0]?.id ?? "", + } + } + return this.document.createEmbeddedDocuments("Item", [itemData]) } static async #onEditItem(event, target) { @@ -168,6 +187,14 @@ export default class LesOubliesActorSheet extends HandlebarsApplicationMixin(fou await this.document.openDamageDialog({ itemId }) } + static async #onToggleEquipped(event, target) { + const itemId = target.dataset.itemId + if (!itemId) return + const item = this.document.items.get(itemId) + if (!item || !("equipped" in (item.system ?? {}))) return + await item.update({ "system.equipped": !item.system.equipped }) + } + static async #onUseSpell(event, target) { const itemId = target.dataset.itemId if (!itemId) return @@ -183,4 +210,11 @@ export default class LesOubliesActorSheet extends HandlebarsApplicationMixin(fou static async #onOpenThreadHarvest() { await this.document.openThreadHarvestDialog() } + + static async #onOpenLinkedActor(event, target) { + const actorId = target.dataset.actorId + if (!actorId) return + const actor = game.actors.get(actorId) + if (actor) actor.sheet.render(true) + } } diff --git a/modules/applications/sheets/base-item-sheet.mjs b/modules/applications/sheets/base-item-sheet.mjs index d056e4c..9cf49b2 100644 --- a/modules/applications/sheets/base-item-sheet.mjs +++ b/modules/applications/sheets/base-item-sheet.mjs @@ -1,5 +1,7 @@ const { HandlebarsApplicationMixin } = foundry.applications.api +import { LesOubliesUtility } from "../../les-oublies-utility.js" + export default class LesOubliesItemSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) { static SHEET_MODES = { EDIT: 0, PLAY: 1 } @@ -33,6 +35,62 @@ export default class LesOubliesItemSheet extends HandlebarsApplicationMixin(foun } async _prepareContext() { + const config = CONFIG.LESOUBLIES + const enriched = await LesOubliesUtility.prepareEnrichedHtml("Item", this.document.type, this.document.system) + const skillLabels = Object.fromEntries(Object.entries(config.skills).map(([key, skill]) => [key, skill.label])) + const choiceSets = { + profileOptions: config.profiles.map((profile) => ({ value: profile.id, label: profile.label })), + skillOptions: LesOubliesUtility.createChoices(Object.keys(config.skills), skillLabels), + spellSkillOptions: LesOubliesUtility.createChoices(["magie", "onirologie", "chimerisme"], skillLabels), + weaponCategoryOptions: LesOubliesUtility.ensureChoice( + LesOubliesUtility.createChoices(Object.keys(config.weaponCategoryLabels), config.weaponCategoryLabels), + this.document.system.category, + ), + weaponOriginOptions: LesOubliesUtility.ensureChoice( + LesOubliesUtility.createChoices(Object.keys(config.weaponOriginLabels), config.weaponOriginLabels), + this.document.system.origin, + ), + armorStateOptions: LesOubliesUtility.ensureChoice( + LesOubliesUtility.createChoices(Object.keys(config.armorStateLabels), config.armorStateLabels), + this.document.system.state, + ), + equipmentCategoryOptions: LesOubliesUtility.ensureChoice( + LesOubliesUtility.createChoices(Object.keys(config.equipmentCategoryLabels), config.equipmentCategoryLabels), + this.document.system.category, + ), + spellTraditionOptions: LesOubliesUtility.ensureChoice( + LesOubliesUtility.createChoices(Object.keys(config.spellTraditionLabels), config.spellTraditionLabels), + this.document.system.tradition, + ), + polarityOptions: LesOubliesUtility.ensureChoice( + LesOubliesUtility.createChoices(Object.keys(config.polarityLabels), config.polarityLabels), + this.document.system.polarity, + ), + stackingOptions: LesOubliesUtility.ensureChoice( + LesOubliesUtility.createChoices(Object.keys(config.stackingLabels), config.stackingLabels), + this.document.system.stacking, + ), + companyPowerScopeOptions: LesOubliesUtility.ensureChoice( + LesOubliesUtility.createChoices(Object.keys(config.companyPowerScopeLabels), config.companyPowerScopeLabels), + this.document.system.scope, + ), + companyPowerModeOptions: LesOubliesUtility.ensureChoice( + LesOubliesUtility.createChoices(Object.keys(config.companyPowerModeLabels), config.companyPowerModeLabels), + this.document.system.effectMode, + ), + raceSizeOptions: LesOubliesUtility.ensureChoice( + LesOubliesUtility.createRangeChoices(1, 4, config.sizes), + this.document.system.size, + ), + mainRaceOptions: LesOubliesUtility.ensureChoice( + LesOubliesUtility.sortByName(game.items.filter((item) => item.type === "race")).map((item) => ({ + value: item.name, + label: item.name, + })), + this.document.system.mainRace, + ), + } + return { item: this.document, system: this.document.system, @@ -43,8 +101,10 @@ export default class LesOubliesItemSheet extends HandlebarsApplicationMixin(foun isEditMode: this.isEditMode, isPlayMode: this.isPlayMode, isGM: game.user.isGM, - config: CONFIG.LESOUBLIES, - enrichedDescription: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description ?? "", { async: true }), + config, + choiceSets, + enriched, + enrichedDescription: foundry.utils.getProperty(enriched, "description") ?? "", } } diff --git a/modules/applications/sheets/compagnie-sheet.mjs b/modules/applications/sheets/compagnie-sheet.mjs index 82d7833..ec49c2c 100644 --- a/modules/applications/sheets/compagnie-sheet.mjs +++ b/modules/applications/sheets/compagnie-sheet.mjs @@ -1,9 +1,14 @@ import LesOubliesActorSheet from "./base-actor-sheet.mjs" +import { LesOubliesUtility } from "../../les-oublies-utility.js" export default class LesOubliesCompagnieSheet extends LesOubliesActorSheet { static DEFAULT_OPTIONS = { ...super.DEFAULT_OPTIONS, classes: [...super.DEFAULT_OPTIONS.classes, "compagnie"], + actions: { + ...super.DEFAULT_OPTIONS.actions, + switchTab: LesOubliesCompagnieSheet.#onSwitchTab, + }, window: { ...super.DEFAULT_OPTIONS.window, title: "TYPES.Actor.compagnie", @@ -12,12 +17,30 @@ export default class LesOubliesCompagnieSheet extends LesOubliesActorSheet { static PARTS = { sheet: { - template: "systems/fvtt-les-oublies/templates/actor-compagnie-sheet.hbs", + template: "systems/fvtt-les-oublies/templates/actor-compagnie-sheet-v4.hbs", }, } + _activeTab = "power" + + #getTabs() { + const tabs = { + power: { id: "power", label: "Pouvoir", icon: "fa-solid fa-burst" }, + members: { id: "members", label: "Membres & liens", icon: "fa-solid fa-people-group" }, + notes: { id: "notes", label: "Notes", icon: "fa-solid fa-feather-pointed" }, + } + + for (const tab of Object.values(tabs)) { + tab.active = this._activeTab === tab.id + tab.cssClass = tab.active ? "active" : "" + } + + return tabs + } + async _prepareContext() { const context = await super._prepareContext() + context.tabs = this.#getTabs() context.members = (this.document.system.memberIds ?? []).map((id) => game.actors.get(id)).filter(Boolean) context.captain = this.document.system.captainId ? game.actors.get(this.document.system.captainId) : null context.shadow = this.document.system.ombreDuTourmentId ? game.actors.get(this.document.system.ombreDuTourmentId) : null @@ -28,6 +51,19 @@ export default class LesOubliesCompagnieSheet extends LesOubliesActorSheet { sourceLabel: game.actors.get(link.sourceId)?.name ?? link.sourceId, targetLabel: game.actors.get(link.targetId)?.name ?? link.targetId, })) + const actorChoices = LesOubliesUtility.sortByName(game.actors.filter((actor) => actor.type === "personnage")).map((actor) => ({ + value: actor.id, + label: actor.name, + })) + context.choiceSets.captainOptions = LesOubliesUtility.ensureChoice(actorChoices, this.document.system.captainId, context.captain?.name) + context.choiceSets.shadowOptions = LesOubliesUtility.ensureChoice(actorChoices, this.document.system.ombreDuTourmentId, context.shadow?.name) return context } + + static #onSwitchTab(event, target) { + const tab = target.dataset.tab + if (!tab || this._activeTab === tab) return + this._activeTab = tab + this.render() + } } diff --git a/modules/applications/sheets/creature-sheet.mjs b/modules/applications/sheets/creature-sheet.mjs index 35f7e1f..abdce43 100644 --- a/modules/applications/sheets/creature-sheet.mjs +++ b/modules/applications/sheets/creature-sheet.mjs @@ -4,6 +4,10 @@ export default class LesOubliesCreatureSheet extends LesOubliesActorSheet { static DEFAULT_OPTIONS = { ...super.DEFAULT_OPTIONS, classes: [...super.DEFAULT_OPTIONS.classes, "creature"], + actions: { + ...super.DEFAULT_OPTIONS.actions, + switchTab: LesOubliesCreatureSheet.#onSwitchTab, + }, window: { ...super.DEFAULT_OPTIONS.window, title: "TYPES.Actor.creature", @@ -12,12 +16,31 @@ export default class LesOubliesCreatureSheet extends LesOubliesActorSheet { static PARTS = { sheet: { - template: "systems/fvtt-les-oublies/templates/actor-creature-sheet.hbs", + template: "systems/fvtt-les-oublies/templates/actor-creature-sheet-v5.hbs", }, } + _activeTab = "overview" + + #getTabs() { + const tabs = { + overview: { id: "overview", label: "Aperçu", icon: "fa-solid fa-dragon" }, + aptitudes: { id: "aptitudes", label: "Aptitudes", icon: "fa-solid fa-book-open" }, + combat: { id: "combat", label: "Combat & équipement", icon: "fa-solid fa-shield-halved" }, + notes: { id: "notes", label: "Notes", icon: "fa-solid fa-feather-pointed" }, + } + + for (const tab of Object.values(tabs)) { + tab.active = this._activeTab === tab.id + tab.cssClass = tab.active ? "active" : "" + } + + return tabs + } + async _prepareContext() { const context = await super._prepareContext() + context.tabs = this.#getTabs() context.derived = this.document.getDerivedOverview() context.skillGroups = this.document.getGroupedCompetences() context.spells = this.document.getEmbeddedItems("sortilege") @@ -26,4 +49,11 @@ export default class LesOubliesCreatureSheet extends LesOubliesActorSheet { context.equipment = this.document.getEmbeddedItems("equipement") return context } + + static #onSwitchTab(event, target) { + const tab = target.dataset.tab + if (!tab || this._activeTab === tab) return + this._activeTab = tab + this.render() + } } diff --git a/modules/applications/sheets/personnage-sheet.mjs b/modules/applications/sheets/personnage-sheet.mjs index 1074fb6..b03924e 100644 --- a/modules/applications/sheets/personnage-sheet.mjs +++ b/modules/applications/sheets/personnage-sheet.mjs @@ -1,9 +1,17 @@ import LesOubliesActorSheet from "./base-actor-sheet.mjs" +import { LesOubliesUtility } from "../../les-oublies-utility.js" export default class LesOubliesPersonnageSheet extends LesOubliesActorSheet { + static CREATION_DROP_TYPES = new Set(["race", "tribu", "metier"]) + static DEFAULT_OPTIONS = { ...super.DEFAULT_OPTIONS, classes: [...super.DEFAULT_OPTIONS.classes, "personnage"], + actions: { + ...super.DEFAULT_OPTIONS.actions, + switchTab: LesOubliesPersonnageSheet.#onSwitchTab, + removeCreationItem: LesOubliesPersonnageSheet.#onRemoveCreationItem, + }, window: { ...super.DEFAULT_OPTIONS.window, title: "TYPES.Actor.personnage", @@ -12,26 +20,121 @@ export default class LesOubliesPersonnageSheet extends LesOubliesActorSheet { static PARTS = { sheet: { - template: "systems/fvtt-les-oublies/templates/actor-personnage-sheet.hbs", + template: "systems/fvtt-les-oublies/templates/actor-personnage-sheet-v14.hbs", }, } + _activeTab = "overview" + + #getTabs() { + const tabs = { + overview: { id: "overview", label: "Portrait", icon: "fa-solid fa-id-card" }, + skills: { id: "skills", label: "Compétences", icon: "fa-solid fa-book-open" }, + actions: { id: "actions", label: "Combat & magie", icon: "fa-solid fa-wand-sparkles" }, + equipment: { id: "equipment", label: "Équipement", icon: "fa-solid fa-suitcase" }, + notes: { id: "notes", label: "Notes", icon: "fa-solid fa-feather-pointed" }, + } + + for (const tab of Object.values(tabs)) { + tab.active = this._activeTab === tab.id + tab.cssClass = tab.active ? "active" : "" + } + + return tabs + } + async _prepareContext() { const context = await super._prepareContext() + context.tabs = this.#getTabs() context.derived = this.document.getDerivedOverview() context.creation = { race: this.document.getCreationItem("race"), tribu: this.document.getCreationItem("tribu"), metier: this.document.getCreationItem("metier"), } - context.profileEntries = this.document.system.profils + context.creationSlots = [ + this.#buildCreationSlot("race", context.creation.race, "Glissez une race ici depuis un compendium ou le répertoire des objets."), + this.#buildCreationSlot("tribu", context.creation.tribu, "Glissez une tribu ici depuis un compendium ou le répertoire des objets."), + this.#buildCreationSlot("metier", context.creation.metier, "Glissez un métier ici depuis un compendium ou le répertoire des objets."), + ] context.skillGroups = this.document.getGroupedCompetences() + const splitIndex = Math.ceil(context.skillGroups.length / 2) + context.skillColumns = [ + context.skillGroups.slice(0, splitIndex), + context.skillGroups.slice(splitIndex), + ] context.spells = this.document.getEmbeddedItems("sortilege") context.weapons = this.document.getEmbeddedItems("arme") + context.equippedWeapons = context.weapons.filter((item) => item.system.equipped) context.armors = this.document.getEmbeddedItems("armure") context.equipment = this.document.getEmbeddedItems("equipement") context.companyPowers = this.document.getEmbeddedItems("pouvoircompagnie") context.activeCompanyPower = context.derived.compagnie?.getEmbeddedItems?.("pouvoircompagnie")?.[0] ?? null + context.choiceSets.companyOptions = LesOubliesUtility.ensureChoice( + LesOubliesUtility.sortByName(game.actors.filter((actor) => actor.type === "compagnie")).map((actor) => ({ + value: actor.id, + label: actor.name, + })), + this.document.system.references?.compagnieId, + context.derived.compagnie?.name, + ) return context } + + #buildCreationSlot(type, item, emptyHint) { + return { + type, + label: game.i18n.localize(`TYPES.Item.${type}`), + item, + emptyHint, + filledHint: "Déposez un autre élément du même type pour le remplacer.", + } + } + + async _onDrop(event) { + const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event) + if (data.type !== "Item" || !data.uuid) return super._onDrop(event) + + const item = await fromUuid(data.uuid) + if (!item) return + + const slot = event.target?.closest?.("[data-drop-creation-type]") + const slotType = slot?.dataset?.dropCreationType ?? "" + const inCreationZone = Boolean(event.target?.closest?.("[data-creation-drop-zone]")) + + if (!LesOubliesPersonnageSheet.CREATION_DROP_TYPES.has(item.type)) { + if (slot || inCreationZone) { + ui.notifications.warn("Seules les races, tribus et métiers peuvent être déposés dans cette zone.") + return + } + return super._onDrop(event) + } + + if (slotType && slotType !== item.type) { + ui.notifications.warn(`Déposez ici un élément de type ${game.i18n.localize(`TYPES.Item.${slotType}`)}.`) + return + } + + if (slot || inCreationZone) { + await this.document.assignCreationItem(item) + this.render() + return + } + + return super._onDrop(event) + } + + static #onSwitchTab(event, target) { + const tab = target.dataset.tab + if (!tab || this._activeTab === tab) return + this._activeTab = tab + this.render() + } + + static async #onRemoveCreationItem(event, target) { + const type = target.dataset.type + if (!LesOubliesPersonnageSheet.CREATION_DROP_TYPES.has(type)) return + await this.document.clearCreationItem(type) + this.render() + } } diff --git a/modules/les-oublies-actor.js b/modules/les-oublies-actor.js index 91ea626..f0e2d71 100644 --- a/modules/les-oublies-actor.js +++ b/modules/les-oublies-actor.js @@ -3,6 +3,8 @@ import { LesOubliesUtility } from "./les-oublies-utility.js" import { LesOubliesRolls } from "./les-oublies-rolls.js" export class LesOubliesActor extends Actor { + static CREATION_ITEM_TYPES = new Set(["race", "tribu", "metier"]) + prepareDerivedData() { super.prepareDerivedData() @@ -43,6 +45,12 @@ export class LesOubliesActor extends Actor { } getCreationItem(type) { + if (!LesOubliesActor.CREATION_ITEM_TYPES.has(type)) return this.items.find((item) => item.type === type) ?? null + const referenceId = this.system.references?.[`${type}Id`] ?? "" + if (referenceId) { + const referencedItem = this.items.get(referenceId) + if (referencedItem?.type === type) return referencedItem + } return this.items.find((item) => item.type === type) ?? null } @@ -51,6 +59,40 @@ export class LesOubliesActor extends Actor { return LesOubliesUtility.sortByName(items) } + async assignCreationItem(sourceItem) { + if (!sourceItem || !LesOubliesActor.CREATION_ITEM_TYPES.has(sourceItem.type)) return null + + const itemData = sourceItem.toObject() + delete itemData._id + + const existingIds = this.getEmbeddedItems(sourceItem.type).map((item) => item.id) + if (existingIds.length) { + await this.deleteEmbeddedDocuments("Item", existingIds, { renderSheet: false }) + } + + const [createdItem] = await this.createEmbeddedDocuments("Item", [itemData], { renderSheet: false }) + if (!createdItem) return null + + await this.update({ + [`system.references.${sourceItem.type}Id`]: createdItem.id, + }) + + return createdItem + } + + async clearCreationItem(type) { + if (!LesOubliesActor.CREATION_ITEM_TYPES.has(type)) return + + const existingIds = this.getEmbeddedItems(type).map((item) => item.id) + if (existingIds.length) { + await this.deleteEmbeddedDocuments("Item", existingIds, { renderSheet: false }) + } + + await this.update({ + [`system.references.${type}Id`]: "", + }) + } + getCompagnie() { const compagnieId = this.system.references?.compagnieId return compagnieId ? game.actors.get(compagnieId) ?? null : null @@ -83,6 +125,7 @@ export class LesOubliesActor extends Actor { getGroupedCompetences() { return LESOUBLIES_CONFIG.profiles.map((profile) => ({ ...profile, + profileValue: this.getProfileValue(profile.id), items: this.getCompetences().filter((entry) => entry.item.system.profileKey === profile.id), })) } diff --git a/modules/les-oublies-config.js b/modules/les-oublies-config.js index b9c3a03..16eac2d 100644 --- a/modules/les-oublies-config.js +++ b/modules/les-oublies-config.js @@ -59,6 +59,67 @@ export const SIZE_LABELS = { 4: "Grande", } +export const WEAPON_CATEGORY_LABELS = { + melee: "Mêlée", + tir: "Tir", + jet: "Jet", +} + +export const WEAPON_ORIGIN_LABELS = { + geant: "Géant", + petitPeuple: "Petit Peuple", +} + +export const WEAPON_SIZE_MODE_LABELS = { + fixe: "Fixe", + plage: "Plage", + variable: "Variable", +} + +export const ARMOR_STATE_LABELS = { + protege: "Protégé", + harnache: "Harnaché", + barde: "Bardé", +} + +export const EQUIPMENT_CATEGORY_LABELS = { + butin: "Butin", + ecriture: "Écriture", + monture: "Monture", + soin: "Soin", + survie: "Survie", + voyage: "Voyage", +} + +export const SPELL_TRADITION_LABELS = { + chimerisme: "Chimérisme", + farfadet: "Farfadet", + magie: "Magie", + onirologie: "Onirologie", +} + +export const POLARITY_LABELS = { + cauchemar: "Cauchemar", + songes: "Songes", +} + +export const STACKING_LABELS = { + "-": "—", + non: "Non", + oui: "Oui", +} + +export const COMPANY_POWER_SCOPE_LABELS = { + compagnie: "Compagnie", +} + +export const COMPANY_POWER_MODE_LABELS = { + passif: "Passif", + "préparation": "Préparation", + "réaction": "Réaction", + ressource: "Ressource", +} + export const ACTOR_IMAGES = { personnage: "icons/svg/mystery-man.svg", compagnie: "icons/svg/book.svg", @@ -84,6 +145,16 @@ export const LESOUBLIES_CONFIG = { profileLabels: PROFILE_LABELS, skills: SKILLS, sizes: SIZE_LABELS, + weaponCategoryLabels: WEAPON_CATEGORY_LABELS, + weaponOriginLabels: WEAPON_ORIGIN_LABELS, + weaponSizeModeLabels: WEAPON_SIZE_MODE_LABELS, + armorStateLabels: ARMOR_STATE_LABELS, + equipmentCategoryLabels: EQUIPMENT_CATEGORY_LABELS, + spellTraditionLabels: SPELL_TRADITION_LABELS, + polarityLabels: POLARITY_LABELS, + stackingLabels: STACKING_LABELS, + companyPowerScopeLabels: COMPANY_POWER_SCOPE_LABELS, + companyPowerModeLabels: COMPANY_POWER_MODE_LABELS, actorImages: ACTOR_IMAGES, itemImages: ITEM_IMAGES, } diff --git a/modules/les-oublies-main.js b/modules/les-oublies-main.js index 819e077..288951d 100644 --- a/modules/les-oublies-main.js +++ b/modules/les-oublies-main.js @@ -6,8 +6,22 @@ import { LesOubliesRolls } from "./les-oublies-rolls.js" import * as models from "./models/index.mjs" import * as sheets from "./applications/sheets/_module.mjs" +function ensureSystemStyles() { + const href = `systems/${game.system.id}/css/les-oublies.css` + const existingLink = document.querySelector(`link[href$="${href}"]`) + if (existingLink) return + + const link = document.createElement("link") + link.rel = "stylesheet" + link.type = "text/css" + link.href = href + link.dataset.systemStyle = game.system.id + document.head.append(link) +} + Hooks.once("init", function () { console.info("Les Oubliés | Initialisation du système") + ensureSystemStyles() CONFIG.Actor.documentClass = LesOubliesActor CONFIG.Actor.dataModels = { diff --git a/modules/les-oublies-rolls.js b/modules/les-oublies-rolls.js index 73a2540..873cb06 100644 --- a/modules/les-oublies-rolls.js +++ b/modules/les-oublies-rolls.js @@ -1,3 +1,5 @@ +import { LesOubliesUtility } from "./les-oublies-utility.js" + const PRIME_DEFINITIONS = [ { id: "none", @@ -187,6 +189,18 @@ const MOVEMENT_DIFFICULTIES = [ { value: -3, label: "Faire un détour (-3)" }, ] +const TEST_DIFFICULTIES = [ + { value: 12, label: "Exceptionnellement facile (+12)" }, + { value: 9, label: "Très facile (+9)" }, + { value: 6, label: "Facile (+6)" }, + { value: 3, label: "Avantageuse (+3)" }, + { value: 0, label: "Normale (+0)" }, + { value: -3, label: "Difficile (-3)" }, + { value: -6, label: "Très difficile (-6)" }, + { value: -9, label: "Extrêmement difficile (-9)" }, + { value: -12, label: "Presque impossible (-12)" }, +] + const HARVEST_SIDE_EFFECTS = { 1: "La main du personnage tremble plus ou moins violemment.", 2: "Le personnage n'arrive à trouver ni repos ni sommeil.", @@ -250,6 +264,8 @@ const PRESET_ACTIONS = { } export class LesOubliesRolls { + static #actorLocks = new Map() + static async openTestDialog(actor, preset = {}) { const data = await this.#promptTestOptions(actor, preset) if (!data || typeof data !== "object") return null @@ -284,7 +300,18 @@ export class LesOubliesRolls { static async openConfrontationDialog(actor, preset = {}) { const data = await this.#promptConfrontationOptions(actor, preset) if (!data || typeof data !== "object") return null - return this.#createConfrontationMessage(actor, data, preset.actionData ?? null) + const defenderActor = this.#resolveDialogTargetActor(data.defenderActorId, preset.targetActor ?? this.#getTargetActor()) + return this.#createConfrontationMessage(actor, { + ...data, + defenderLabel: defenderActor?.name ?? data.defenderLabel, + defenderScore: defenderActor + ? this.#getSkillScoreWithAlternatives(defenderActor, data.defenderSkill) + : Number(data.defenderScore ?? 0), + defenderSongesValue: defenderActor ? Number(defenderActor.system.songes?.value ?? 0) : Number(data.defenderSongesValue ?? 0), + defenderSongesPoints: defenderActor ? Number(defenderActor.system.songes?.points ?? 0) : Number(data.defenderSongesPoints ?? 0), + defenderCauchemarValue: defenderActor ? Number(defenderActor.system.cauchemar?.value ?? 0) : Number(data.defenderCauchemarValue ?? 0), + defenderCauchemarPoints: defenderActor ? Number(defenderActor.system.cauchemar?.points ?? 0) : Number(data.defenderCauchemarPoints ?? 0), + }, preset.actionData ?? null) } static async openAttackDialog(actor, { itemId = null, mode = null } = {}) { @@ -297,6 +324,11 @@ export class LesOubliesRolls { targetActor, }) if (!data) return null + const defenderActor = this.#resolveDialogTargetActor(data.defenderActorId, targetActor) + if (!defenderActor) { + ui.notifications.info("Aucune cible sélectionnée : choisissez un adversaire avant de lancer une attaque.") + return null + } const modifiers = this.#resolveModifierSelection(data.primeId, data.penaltyId, attackMode === "ranged" ? "rangedAttack" : "meleeAttack") const reactionOptions = this.#getAttackReactionOptions(data.attackerSkill) @@ -311,17 +343,17 @@ export class LesOubliesRolls { modifiers, targetLabel: data.defenderLabel, notes: data.notes?.trim() || "", - targetActor, - applyToTarget: Boolean(data.applyToTarget && targetActor), + targetActor: defenderActor, + applyToTarget: Boolean(data.applyToTarget && defenderActor), damageRequest: { actor, weapon, baseDamage: Number(data.baseDamage ?? 0), baseLabel: String(data.baseDamageLabel || weapon?.system?.damage || data.baseDamage || "0"), targetProtection: Number(data.targetProtection ?? 0), - targetLabel: String(data.defenderLabel || targetActor?.name || game.i18n.localize("LESOUBLIES.rolls.defender")), - targetActor, - applyToTarget: Boolean(data.applyToTarget && targetActor), + targetLabel: String(data.defenderLabel || defenderActor?.name || game.i18n.localize("LESOUBLIES.rolls.defender")), + targetActor: defenderActor, + applyToTarget: Boolean(data.applyToTarget && defenderActor), modifiers, }, extraContext: { @@ -339,17 +371,17 @@ export class LesOubliesRolls { attackerExtraDie: data.attackerExtraDie, attackerFinalModifier: modifiers.summary.finalModifier, defenderLabel: data.defenderLabel, - defenderScore: targetActor - ? this.#getSkillScoreWithAlternatives(targetActor, data.defenderSkill) + defenderScore: defenderActor + ? this.#getSkillScoreWithAlternatives(defenderActor, data.defenderSkill) : Number(data.defenderScore ?? 0), defenderDifficulty: Number(data.defenderDifficulty ?? 0), defenderRollMode: data.defenderRollMode, defenderExtraDie: data.defenderExtraDie, defenderFinalModifier: modifiers.summary.opponentFinalModifier, - defenderSongesValue: targetActor ? Number(targetActor.system.songes?.value ?? 0) : Number(data.defenderSongesValue ?? 0), - defenderSongesPoints: targetActor ? Number(targetActor.system.songes?.points ?? 0) : Number(data.defenderSongesPoints ?? 0), - defenderCauchemarValue: targetActor ? Number(targetActor.system.cauchemar?.value ?? 0) : Number(data.defenderCauchemarValue ?? 0), - defenderCauchemarPoints: targetActor ? Number(targetActor.system.cauchemar?.points ?? 0) : Number(data.defenderCauchemarPoints ?? 0), + defenderSongesValue: defenderActor ? Number(defenderActor.system.songes?.value ?? 0) : Number(data.defenderSongesValue ?? 0), + defenderSongesPoints: defenderActor ? Number(defenderActor.system.songes?.points ?? 0) : Number(data.defenderSongesPoints ?? 0), + defenderCauchemarValue: defenderActor ? Number(defenderActor.system.cauchemar?.value ?? 0) : Number(data.defenderCauchemarValue ?? 0), + defenderCauchemarPoints: defenderActor ? Number(defenderActor.system.cauchemar?.points ?? 0) : Number(data.defenderCauchemarPoints ?? 0), }, actionData) } @@ -407,33 +439,40 @@ export class LesOubliesRolls { const data = await this.#promptSpellOptions(actor, spell) if (!data) return null - - const skill = actor.getCompetenceByKey?.(spell.system.skillKey) ?? null - const skillBase = Number(skill?.system?.base ?? 0) - if (skillBase < 1) { - ui.notifications.warn(`Il faut au moins une base de 1 en ${this.#getSkillLabel(spell.system.skillKey)} pour activer ce sortilège.`) - return null - } - - const métierMatch = this.#actorMatchesSpellGrant(actor, spell) - const surcharge = !métierMatch && data.applyMetierSurcharge - const effectiveCost = Number(data.actualCost ?? 0) * (surcharge ? 2 : 1) - const paymentMode = String(data.paymentMode || "points") - if (paymentMode === "points") { - const resource = spell.system.polarity || "songes" - if (Number(actor.system?.[resource]?.points ?? 0) < effectiveCost) { - ui.notifications.warn(game.i18n.format("LESOUBLIES.rolls.notEnoughResource", { - resource: resource === "songes" ? game.i18n.localize("LESOUBLIES.ui.songes") : game.i18n.localize("LESOUBLIES.ui.cauchemar"), - actor: actor.name, - })) + const activation = await this.#withActorLock(`spell:${actor.id}`, async () => { + const skill = actor.getCompetenceByKey?.(spell.system.skillKey) ?? null + const skillBase = Number(skill?.system?.base ?? 0) + if (skillBase < 1) { + ui.notifications.warn(`Il faut au moins une base de 1 en ${this.#getSkillLabel(spell.system.skillKey)} pour activer ce sortilège.`) return null } - if (effectiveCost > 0) { - await actor.update({ - [`system.${resource}.points`]: Math.max(Number(actor.system?.[resource]?.points ?? 0) - effectiveCost, 0), - }) + + const métierMatch = this.#actorMatchesSpellGrant(actor, spell) + const surcharge = !métierMatch + const effectiveCost = Number(data.actualCost ?? 0) * (surcharge ? 2 : 1) + const paymentMode = String(data.paymentMode || "points") + if (paymentMode === "points") { + const resource = spell.system.polarity || "songes" + const available = Number(actor.system?.[resource]?.points ?? 0) + if (available < effectiveCost) { + ui.notifications.warn(game.i18n.format("LESOUBLIES.rolls.notEnoughResourceDetailed", { + resource: resource === "songes" ? game.i18n.localize("LESOUBLIES.ui.songes") : game.i18n.localize("LESOUBLIES.ui.cauchemar"), + actor: actor.name, + required: effectiveCost, + available, + })) + return null + } + if (effectiveCost > 0) { + await actor.update({ + [`system.${resource}.points`]: Math.max(available - effectiveCost, 0), + }) + } } - } + + return { métierMatch, surcharge, effectiveCost, paymentMode } + }) + if (!activation) return null const content = await foundry.applications.handlebars.renderTemplate( "systems/fvtt-les-oublies/templates/chat-spell-activation.hbs", @@ -442,14 +481,14 @@ export class LesOubliesRolls { spell, activation: { targetLabel: data.targetLabel?.trim() || "Sans cible précisée", - paymentMode, + paymentMode: activation.paymentMode, actualCost: Number(data.actualCost ?? 0), - effectiveCost, - costLabel: paymentMode === "points" - ? `${effectiveCost} point${effectiveCost > 1 ? "s" : ""} de ${spell.system.polarity === "cauchemar" ? "Cauchemar" : "Songes"}` - : `${effectiveCost} fil${effectiveCost > 1 ? "s" : ""} de ${spell.system.polarity === "cauchemar" ? "Cauchemar" : "Songes"}`, - métierMatch, - surcharge, + effectiveCost: activation.effectiveCost, + costLabel: activation.paymentMode === "points" + ? `${activation.effectiveCost} point${activation.effectiveCost > 1 ? "s" : ""} de ${spell.system.polarity === "cauchemar" ? "Cauchemar" : "Songes"}` + : `${activation.effectiveCost} fil${activation.effectiveCost > 1 ? "s" : ""} de ${spell.system.polarity === "cauchemar" ? "Cauchemar" : "Songes"}`, + métierMatch: activation.métierMatch, + surcharge: activation.surcharge, notes: data.notes?.trim() || "", }, }, @@ -495,6 +534,7 @@ export class LesOubliesRolls { const targetActor = this.#getTargetActor() const data = await this.#promptPresetConfrontationOptions(actor, preset, targetActor) if (!data) return null + const defenderActor = this.#resolveDialogTargetActor(data.defenderActorId, targetActor) const modifiers = this.#resolveModifierSelection(data.primeId, data.penaltyId, actionKey) const actionData = { @@ -504,8 +544,8 @@ export class LesOubliesRolls { hint: preset.hint, modifiers, notes: data.notes?.trim() || "", - targetLabel: data.defenderLabel, - targetActor, + targetLabel: defenderActor?.name ?? data.defenderLabel, + targetActor: defenderActor, applyToTarget: false, outcome: this.#buildPresetOutcome(actionKey, data), } @@ -518,18 +558,18 @@ export class LesOubliesRolls { attackerRollMode: data.attackerRollMode, attackerExtraDie: data.attackerExtraDie, attackerFinalModifier: modifiers.summary.finalModifier, - defenderLabel: data.defenderLabel, - defenderScore: targetActor - ? this.#getSkillScoreWithAlternatives(targetActor, preset.defenderSkillKey) + defenderLabel: defenderActor?.name ?? data.defenderLabel, + defenderScore: defenderActor + ? this.#getSkillScoreWithAlternatives(defenderActor, preset.defenderSkillKey) : Number(data.defenderScore ?? 0), defenderDifficulty: Number(data.defenderDifficulty ?? 0), defenderRollMode: data.defenderRollMode, defenderExtraDie: data.defenderExtraDie, defenderFinalModifier: modifiers.summary.opponentFinalModifier, - defenderSongesValue: targetActor ? Number(targetActor.system.songes?.value ?? 0) : Number(data.defenderSongesValue ?? 0), - defenderSongesPoints: targetActor ? Number(targetActor.system.songes?.points ?? 0) : Number(data.defenderSongesPoints ?? 0), - defenderCauchemarValue: targetActor ? Number(targetActor.system.cauchemar?.value ?? 0) : Number(data.defenderCauchemarValue ?? 0), - defenderCauchemarPoints: targetActor ? Number(targetActor.system.cauchemar?.points ?? 0) : Number(data.defenderCauchemarPoints ?? 0), + defenderSongesValue: defenderActor ? Number(defenderActor.system.songes?.value ?? 0) : Number(data.defenderSongesValue ?? 0), + defenderSongesPoints: defenderActor ? Number(defenderActor.system.songes?.points ?? 0) : Number(data.defenderSongesPoints ?? 0), + defenderCauchemarValue: defenderActor ? Number(defenderActor.system.cauchemar?.value ?? 0) : Number(data.defenderCauchemarValue ?? 0), + defenderCauchemarPoints: defenderActor ? Number(defenderActor.system.cauchemar?.points ?? 0) : Number(data.defenderCauchemarPoints ?? 0), }, actionData) } @@ -761,17 +801,19 @@ export class LesOubliesRolls { } static async #promptTestOptions(actor, preset = {}) { + const difficulty = Number(preset.difficulty ?? 0) const content = await foundry.applications.handlebars.renderTemplate( "systems/fvtt-les-oublies/templates/dialog-roll-test.hbs", { actor, rollModes: this.getRollModes(), extraDieModes: this.getExtraDieModes(), + difficultyOptions: this.#getDifficultyOptions(TEST_DIFFICULTIES, difficulty), resources: this.#getDialogResources(actor), values: { label: preset.label ?? "", score: Number(preset.score ?? 0), - difficulty: Number(preset.difficulty ?? 0), + difficulty, rollMode: preset.rollMode ?? this.getDefaultRollMode(actor), extraDie: preset.extraDie ?? "", }, @@ -816,10 +858,15 @@ export class LesOubliesRolls { static async #promptConfrontationOptions(actor, preset = {}) { const targetActor = preset.targetActor ?? this.#getTargetActor() + const defenderSkill = preset.defenderSkill ?? "esquive" const content = await foundry.applications.handlebars.renderTemplate( "systems/fvtt-les-oublies/templates/dialog-roll-confrontation.hbs", { actor, + targetActor, + targetStatus: this.#getConfrontationTargetStatus(targetActor), + targetOptions: this.#getConfrontationTargetOptions(actor, targetActor), + defenderSkillOptions: this.#getConfrontationSkillOptions(), rollModes: this.getRollModes(), extraDieModes: this.getExtraDieModes(), defaultRollMode: this.getDefaultRollMode(actor), @@ -837,7 +884,11 @@ export class LesOubliesRolls { attackerRollMode: preset.attackerRollMode ?? this.getDefaultRollMode(actor), attackerExtraDie: preset.attackerExtraDie ?? "", defenderLabel: targetActor?.name ?? preset.defenderLabel ?? game.i18n.localize("LESOUBLIES.rolls.defender"), - defenderScore: Number(preset.defenderScore ?? 0), + defenderActorId: targetActor?.id ?? "", + defenderSkill, + defenderScore: targetActor + ? this.#getSkillScoreWithAlternatives(targetActor, defenderSkill) + : Number(preset.defenderScore ?? 0), defenderDifficulty: Number(preset.defenderDifficulty ?? 0), defenderRollMode: preset.defenderRollMode ?? this.getDefaultRollMode(targetActor ?? actor), defenderExtraDie: preset.defenderExtraDie ?? "", @@ -851,6 +902,13 @@ export class LesOubliesRolls { title: game.i18n.localize("LESOUBLIES.rolls.dialogs.confrontationTitle"), }, content, + render: (_event, dialog) => { + this.#bindConfrontationTargetSelection(dialog, { + actor, + fallbackTargetActor: targetActor, + skillFieldName: "defenderSkill", + }) + }, buttons: [ { action: "roll", @@ -867,6 +925,8 @@ export class LesOubliesRolls { attackerDifficulty: Number(data.attackerDifficulty ?? 0), attackerRollMode: String(data.attackerRollMode || this.getDefaultRollMode(actor)), attackerExtraDie: String(data.attackerExtraDie || ""), + defenderActorId: String(data.defenderActorId || ""), + defenderSkill: String(data.defenderSkill || defenderSkill), defenderLabel: String(data.defenderLabel || targetActor?.name || game.i18n.localize("LESOUBLIES.rolls.defender")).trim(), defenderScore: Number(data.defenderScore ?? 0), defenderDifficulty: Number(data.defenderDifficulty ?? 0), @@ -892,7 +952,7 @@ export class LesOubliesRolls { const baseDamage = this.#getWeaponBaseDamage(actor, weapon) const baseDamageLabel = weapon?.system?.damage || String(baseDamage) const content = await foundry.applications.handlebars.renderTemplate( - "systems/fvtt-les-oublies/templates/dialog-roll-attack.hbs", + "systems/fvtt-les-oublies/templates/dialog-roll-attack-v2.hbs", { actor, weapon, @@ -905,6 +965,10 @@ export class LesOubliesRolls { cauchemarPoints: 0, }, targetActor, + targetStatus: this.#getConfrontationTargetStatus(targetActor, { requireTarget: true }), + targetOptions: this.#getConfrontationTargetOptions(actor, targetActor).map((entry, index) => ( + index === 0 ? { ...entry, label: "— Sélectionner un adversaire —" } : entry + )), rollModes: this.getRollModes(), extraDieModes: this.getExtraDieModes(), attackSkills: this.#getAttackSkillOptions(attackMode), @@ -917,6 +981,7 @@ export class LesOubliesRolls { attackerRollMode: this.getDefaultRollMode(actor), attackerExtraDie: "", defenderLabel: targetActor?.name ?? game.i18n.localize("LESOUBLIES.rolls.defender"), + defenderActorId: targetActor?.id ?? "", defenderSkill: attackMode === "ranged" ? "esquive" : "esquive", defenderScore: 0, defenderDifficulty: 0, @@ -940,6 +1005,14 @@ export class LesOubliesRolls { title: attackMode === "ranged" ? "Attaque à distance" : "Attaque de mêlée", }, content, + render: (_event, dialog) => { + this.#bindConfrontationTargetSelection(dialog, { + actor, + fallbackTargetActor: targetActor, + skillFieldName: "defenderSkill", + requireTarget: true, + }) + }, buttons: [ { action: "roll", @@ -949,12 +1022,19 @@ export class LesOubliesRolls { const form = this.#getDialogElement(dialog)?.querySelector("form") if (!form) return null const data = this.#formToObject(form) + const defenderActor = this.#resolveDialogTargetActor(data.defenderActorId, targetActor) + if (!defenderActor) { + ui.notifications.info("Aucune cible sélectionnée : choisissez un adversaire avant de lancer une attaque.") + dialog.close() + return null + } const difficultyPreset = Number(data.difficultyPreset ?? 0) const customDifficulty = Number(data.customDifficulty ?? 0) return { attackerSkill: String(data.attackerSkill || (attackMode === "ranged" ? "tir" : "melee")), attackerRollMode: String(data.attackerRollMode || this.getDefaultRollMode(actor)), attackerExtraDie: String(data.attackerExtraDie || ""), + defenderActorId: String(data.defenderActorId || ""), defenderLabel: String(data.defenderLabel || targetActor?.name || game.i18n.localize("LESOUBLIES.rolls.defender")).trim(), defenderSkill: String(data.defenderSkill || "esquive"), defenderScore: Number(data.defenderScore ?? 0), @@ -1044,17 +1124,22 @@ export class LesOubliesRolls { } static async #promptSpellOptions(actor, spell) { + const isMetierMatch = this.#actorMatchesSpellGrant(actor, spell) + const effectiveCost = Number(spell.system.cost ?? 0) * (isMetierMatch ? 1 : 2) + const polarityLabel = spell.system.polarity === "cauchemar" + ? game.i18n.localize("LESOUBLIES.ui.cauchemar") + : game.i18n.localize("LESOUBLIES.ui.songes") const content = await foundry.applications.handlebars.renderTemplate( "systems/fvtt-les-oublies/templates/dialog-spell-activation.hbs", { actor, spell, resources: this.#getDialogResources(actor), - isMetierMatch: this.#actorMatchesSpellGrant(actor, spell), + isMetierMatch, + effectiveCostLabel: `${effectiveCost} point${effectiveCost > 1 ? "s" : ""} de ${polarityLabel}`, values: { actualCost: Number(spell.system.cost ?? 0), paymentMode: "points", - applyMetierSurcharge: true, targetLabel: "", notes: "", }, @@ -1078,7 +1163,6 @@ export class LesOubliesRolls { return { actualCost: Number(data.actualCost ?? spell.system.cost ?? 0), paymentMode: String(data.paymentMode || "points"), - applyMetierSurcharge: data.applyMetierSurcharge === "on", targetLabel: String(data.targetLabel || ""), notes: String(data.notes || ""), } @@ -1164,6 +1248,9 @@ export class LesOubliesRolls { title: preset.title, hint: preset.hint, targetActor, + targetStatus: this.#getConfrontationTargetStatus(targetActor), + defenderSkillLabel: this.#getSkillLabel(preset.defenderSkillKey), + targetOptions: this.#getConfrontationTargetOptions(actor, targetActor), rollModes: this.getRollModes(), extraDieModes: this.getExtraDieModes(), attackerResources: this.#getDialogResources(actor), @@ -1178,12 +1265,15 @@ export class LesOubliesRolls { values: { attackerDifficulty: Number(preset.difficulty ?? 0), defenderLabel: targetActor?.name ?? game.i18n.localize("LESOUBLIES.rolls.defender"), + defenderActorId: targetActor?.id ?? "", defenderDifficulty: 0, attackerRollMode: this.getDefaultRollMode(actor), attackerExtraDie: "", defenderRollMode: this.getDefaultRollMode(targetActor ?? actor), defenderExtraDie: "", - defenderScore: 0, + defenderScore: targetActor + ? this.#getSkillScoreWithAlternatives(targetActor, preset.defenderSkillKey) + : 0, primeId: "none", penaltyId: "none", outcomeChoice: "", @@ -1201,6 +1291,13 @@ export class LesOubliesRolls { title: preset.title, }, content, + render: (_event, dialog) => { + this.#bindConfrontationTargetSelection(dialog, { + actor, + fallbackTargetActor: targetActor, + skillKey: preset.defenderSkillKey, + }) + }, buttons: [ { action: "roll", @@ -1212,6 +1309,7 @@ export class LesOubliesRolls { const data = this.#formToObject(form) return { attackerDifficulty: Number(data.attackerDifficulty ?? preset.difficulty ?? 0), + defenderActorId: String(data.defenderActorId || ""), defenderLabel: String(data.defenderLabel || targetActor?.name || game.i18n.localize("LESOUBLIES.rolls.defender")).trim(), defenderDifficulty: Number(data.defenderDifficulty ?? 0), attackerRollMode: String(data.attackerRollMode || this.getDefaultRollMode(actor)), @@ -1498,7 +1596,7 @@ export class LesOubliesRolls { static #getWeaponAttackMode(weapon) { const category = String(weapon?.system?.category || "").toLowerCase() - if (["distance", "ranged", "tir", "projectile"].some((keyword) => category.includes(keyword))) return "ranged" + if (["distance", "ranged", "tir", "projectile", "jet"].some((keyword) => category.includes(keyword))) return "ranged" return "melee" } @@ -1528,6 +1626,166 @@ export class LesOubliesRolls { ] } + static #getConfrontationTargetOptions(actor, selectedActor = null) { + const choices = LesOubliesUtility.sortByName( + game.actors.filter((candidate) => ( + ["creature", "personnage"].includes(candidate.type) + && candidate.id !== actor?.id + )), + ).map((candidate) => ({ + value: candidate.id, + label: `${candidate.name} — ${game.i18n.localize(`TYPES.Actor.${candidate.type}`)}`, + })) + + return [ + { value: "", label: "Saisie manuelle" }, + ...LesOubliesUtility.ensureChoice( + choices, + selectedActor?.id, + selectedActor ? `${selectedActor.name} — ${game.i18n.localize(`TYPES.Actor.${selectedActor.type}`)}` : null, + ), + ] + } + + static #getConfrontationSkillOptions() { + const skills = CONFIG.LESOUBLIES?.config?.skills ?? CONFIG.LESOUBLIES?.skills ?? {} + return Object.entries(skills) + .map(([value, data]) => ({ + value, + label: data.label ?? value, + })) + .sort((left, right) => left.label.localeCompare(right.label, "fr")) + } + + static #resolveDialogTargetActor(actorId, fallbackTargetActor = null) { + if (actorId !== undefined && actorId !== null && actorId !== "") { + return game.actors.get(String(actorId)) ?? null + } + if (actorId === "") return null + return fallbackTargetActor ?? null + } + + static #getConfrontationTargetStatus(targetActor = null, { requireTarget = false } = {}) { + if (!targetActor) { + return { + message: requireTarget + ? "Aucune cible n'est actuellement sélectionnée. Sélectionnez un adversaire dans la liste ci-dessous pour lancer l'attaque." + : "Aucune cible n'est actuellement sélectionnée. Choisissez un adversaire dans la liste ci-dessous ou conservez la saisie manuelle.", + state: "empty", + } + } + + return { + message: `Adversaire sélectionné : ${targetActor.name}. Ses valeurs de confrontation sont utilisées automatiquement.`, + state: "selected", + } + } + + static #bindConfrontationTargetSelection(dialog, { + actor, + fallbackTargetActor = null, + skillFieldName = null, + skillKey = null, + requireTarget = false, + } = {}) { + const root = this.#getDialogElement(dialog) + const form = root?.querySelector("form") + if (!form) return + + const actorField = form.elements.namedItem("defenderActorId") + if (!(actorField instanceof HTMLSelectElement)) return + + const labelField = form.elements.namedItem("defenderLabel") + const scoreField = form.elements.namedItem("defenderScore") + const rollModeField = form.elements.namedItem("defenderRollMode") + const songesValueField = form.elements.namedItem("defenderSongesValue") + const songesPointsField = form.elements.namedItem("defenderSongesPoints") + const cauchemarValueField = form.elements.namedItem("defenderCauchemarValue") + const cauchemarPointsField = form.elements.namedItem("defenderCauchemarPoints") + const skillField = skillFieldName ? form.elements.namedItem(skillFieldName) : null + const targetStatusField = root.querySelector("[data-target-status]") + + const defaultLabel = game.i18n.localize("LESOUBLIES.rolls.defender") + const getSelectedSkill = () => { + if (skillKey) return skillKey + if (skillField instanceof HTMLSelectElement) return String(skillField.value || "melee") + return "melee" + } + + const updateTargetFields = ({ preserveRollMode = false } = {}) => { + const targetActor = this.#resolveDialogTargetActor(actorField.value, fallbackTargetActor) + const hasActor = Boolean(targetActor) + const currentSkill = getSelectedSkill() + + if (targetStatusField instanceof HTMLElement) { + const targetStatus = this.#getConfrontationTargetStatus(targetActor, { requireTarget }) + targetStatusField.textContent = targetStatus.message + targetStatusField.dataset.state = targetStatus.state + } + + if (labelField instanceof HTMLInputElement) { + labelField.value = hasActor ? targetActor.name : (labelField.value || defaultLabel) + labelField.readOnly = hasActor + } + + if (scoreField instanceof HTMLInputElement) { + if (hasActor) { + scoreField.value = String(this.#getSkillScoreWithAlternatives(targetActor, currentSkill)) + } + scoreField.readOnly = hasActor + } + + if (rollModeField instanceof HTMLSelectElement && hasActor && !preserveRollMode) { + rollModeField.value = this.getDefaultRollMode(targetActor) + } + + const resourceValues = hasActor + ? { + songesValue: Number(targetActor.system.songes?.value ?? 0), + songesPoints: Number(targetActor.system.songes?.points ?? 0), + cauchemarValue: Number(targetActor.system.cauchemar?.value ?? 0), + cauchemarPoints: Number(targetActor.system.cauchemar?.points ?? 0), + } + : null + + const bindNumericField = (field, value) => { + if (!(field instanceof HTMLInputElement)) return + if (resourceValues) field.value = String(value) + field.readOnly = hasActor + } + + bindNumericField(songesValueField, resourceValues?.songesValue ?? 0) + bindNumericField(songesPointsField, resourceValues?.songesPoints ?? 0) + bindNumericField(cauchemarValueField, resourceValues?.cauchemarValue ?? 0) + bindNumericField(cauchemarPointsField, resourceValues?.cauchemarPoints ?? 0) + } + + actorField.addEventListener("change", () => updateTargetFields()) + if (skillField instanceof HTMLSelectElement) { + skillField.addEventListener("change", () => updateTargetFields({ preserveRollMode: true })) + } + updateTargetFields() + } + + static async #withActorLock(lockKey, callback) { + const previous = this.#actorLocks.get(lockKey) ?? Promise.resolve() + let release + const current = new Promise((resolve) => { + release = resolve + }) + const queued = previous.finally(() => current) + this.#actorLocks.set(lockKey, queued) + await previous + try { + return await callback() + } finally { + release() + if (this.#actorLocks.get(lockKey) === queued) { + this.#actorLocks.delete(lockKey) + } + } + } + static #getModifierOptions(type, actionType) { const source = type === "prime" ? PRIME_DEFINITIONS : PENALTY_DEFINITIONS return source @@ -1677,6 +1935,21 @@ export class LesOubliesRolls { return parts.length ? parts.join(" ") : "0" } + static #getDifficultyOptions(options, selectedValue = 0) { + const normalizedValue = Number(selectedValue ?? 0) + const entries = options.map((entry) => ({ + value: Number(entry.value ?? 0), + label: entry.label, + })) + if (!entries.some((entry) => entry.value === normalizedValue)) { + entries.push({ + value: normalizedValue, + label: `Personnalisée (${normalizedValue > 0 ? "+" : ""}${normalizedValue})`, + }) + } + return entries + } + static #getConfrontationOutcome(attacker, defender) { const attackerSuccess = attacker.success const defenderSuccess = defender.success diff --git a/modules/les-oublies-utility.js b/modules/les-oublies-utility.js index 7dfb87e..0ccc8e1 100644 --- a/modules/les-oublies-utility.js +++ b/modules/les-oublies-utility.js @@ -44,6 +44,29 @@ export class LesOubliesUtility { return ITEM_IMAGES[type] ?? "icons/svg/item-bag.svg" } + static createChoices(values = [], labels = {}) { + return values.map((value) => ({ + value, + label: labels[value] ?? String(value), + })) + } + + static createRangeChoices(min, max, labels = {}) { + return Array.from({ length: Math.max(max - min + 1, 0) }, (_, index) => min + index).map((value) => ({ + value, + label: labels[value] ?? String(value), + })) + } + + static ensureChoice(choices = [], value, label = null) { + if (value === undefined || value === null || value === "") return choices + if (choices.some((choice) => String(choice.value) === String(value))) return choices + return [{ + value, + label: label ?? `${value} (personnalisé)`, + }, ...choices] + } + static createEmptyProfiles() { return PROFILE_KEYS.reduce((profiles, key) => { profiles[key] = 0 @@ -73,4 +96,17 @@ export class LesOubliesUtility { static sortByName(documents = []) { return [...documents].sort((left, right) => left.name.localeCompare(right.name, "fr")) } + + static async prepareEnrichedHtml(documentName, type, systemData) { + const htmlFields = game.system.documentTypes?.[documentName]?.[type]?.htmlFields ?? [] + const enriched = {} + + for (const path of htmlFields) { + const value = foundry.utils.getProperty(systemData, path) ?? "" + const html = await foundry.applications.ux.TextEditor.implementation.enrichHTML(value, { async: true }) + foundry.utils.setProperty(enriched, path, html) + } + + return enriched + } } diff --git a/package-lock.json b/package-lock.json index 8b281bf..8ee68e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,8 @@ "devDependencies": { "gulp": "^5.0.0", "gulp-less": "^5.0.0", - "gulp-sourcemaps": "^3.0.0" + "gulp-sourcemaps": "^3.0.0", + "level": "^10.0.0" } }, "node_modules/@gulp-sourcemaps/identity-map": { @@ -136,6 +137,24 @@ "node": ">=10.13.0" } }, + "node_modules/abstract-level": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/abstract-level/-/abstract-level-3.1.1.tgz", + "integrity": "sha512-CW2gKbJFTuX1feMvOrvsVMmijAOgI9kg2Ie9Dq3gOcMt/dVVoVmqNlLcEUCT13NxHFMEajcUcVBIplbyDroDiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^6.0.3", + "is-buffer": "^2.0.5", + "level-supports": "^6.2.0", + "level-transcoder": "^1.0.1", + "maybe-combine-errors": "^1.0.0", + "module-error": "^1.0.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/acorn": { "version": "6.4.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", @@ -407,6 +426,16 @@ "node": ">=8" } }, + "node_modules/browser-level": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/browser-level/-/browser-level-3.0.0.tgz", + "integrity": "sha512-kGXtLh29jMwqKaskz5xeDLtCtN1KBz/DbQSqmvH7QdJiyGRC7RAM8PPg6gvUiNMa+wVnaxS9eSmEtP/f5ajOVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "abstract-level": "^3.1.0" + } + }, "node_modules/buffer": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", @@ -474,6 +503,23 @@ "fsevents": "~2.3.2" } }, + "node_modules/classic-level": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/classic-level/-/classic-level-3.0.0.tgz", + "integrity": "sha512-yGy8j8LjPbN0Bh3+ygmyYvrmskVita92pD/zCoalfcC9XxZj6iDtZTAnz+ot7GG8p9KLTG+MZ84tSA4AhkgVZQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "abstract-level": "^3.1.0", + "module-error": "^1.0.1", + "napi-macros": "^2.2.2", + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -1396,6 +1442,30 @@ "node": ">=8" } }, + "node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/is-core-module": { "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", @@ -1637,6 +1707,49 @@ "source-map": "~0.6.0" } }, + "node_modules/level": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/level/-/level-10.0.0.tgz", + "integrity": "sha512-aZJvdfRr/f0VBbSRF5C81FHON47ZsC2TkGxbBezXpGGXAUEL/s6+GP73nnhAYRSCIqUNsmJjfeOF4lzRDKbUig==", + "dev": true, + "license": "MIT", + "dependencies": { + "abstract-level": "^3.1.0", + "browser-level": "^3.0.0", + "classic-level": "^3.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/level" + } + }, + "node_modules/level-supports": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-6.2.0.tgz", + "integrity": "sha512-QNxVXP0IRnBmMsJIh+sb2kwNCYcKciQZJEt+L1hPCHrKNELllXhvrlClVHXBYZVT+a7aTSM6StgNXdAldoab3w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/level-transcoder": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/level-transcoder/-/level-transcoder-1.0.1.tgz", + "integrity": "sha512-t7bFwFtsQeD8cl8NIoQ2iwxA0CL/9IFw7/9gAjOonH0PWTTiRfY7Hq+Ejbsxh86tXobDQ6IOiddjNYIfOBs06w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^6.0.3", + "module-error": "^1.0.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/liftoff": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-5.0.1.tgz", @@ -1691,6 +1804,16 @@ "node": ">=0.10.0" } }, + "node_modules/maybe-combine-errors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/maybe-combine-errors/-/maybe-combine-errors-1.0.0.tgz", + "integrity": "sha512-eefp6IduNPT6fVdwPp+1NgD0PML1NU5P6j1Mj5nz1nidX8/sWY7119WL8vTAHgqfsY74TzW0w1XPgdYEKkGZ5A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/memoizee": { "version": "0.4.17", "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.17.tgz", @@ -1739,6 +1862,16 @@ "node": ">=4" } }, + "node_modules/module-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/module-error/-/module-error-1.0.2.tgz", + "integrity": "sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -1756,6 +1889,13 @@ "node": ">= 10.13.0" } }, + "node_modules/napi-macros": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.2.2.tgz", + "integrity": "sha512-hmEVtAGYzVQpCKdbQea4skABsdXW4RUh5t5mJ2zzqowJS2OyXZTU1KhDVFhx+NlWZ4ap9mqR9TcDO3LTTttd+g==", + "dev": true, + "license": "MIT" + }, "node_modules/needle": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/needle/-/needle-3.5.0.tgz", @@ -1781,6 +1921,18 @@ "dev": true, "license": "ISC" }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "dev": true, + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", diff --git a/package.json b/package.json index 0b28eef..62a6a50 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "private": true, "type": "module", "scripts": { - "build": "gulp build", + "build": "node scripts/build-compendiums.mjs && gulp build", + "build:packs": "node scripts/build-compendiums.mjs", "watch": "gulp watch" }, "author": "Copilot", @@ -13,6 +14,7 @@ "devDependencies": { "gulp": "^5.0.0", "gulp-less": "^5.0.0", + "level": "^10.0.0", "gulp-sourcemaps": "^3.0.0" } } diff --git a/packs-src/armes-sample.json b/packs-src/armes-sample.json deleted file mode 100644 index a2e5908..0000000 --- a/packs-src/armes-sample.json +++ /dev/null @@ -1,10 +0,0 @@ -[ - { "name": "Akinakas", "type": "arme", "img": "icons/svg/sword.svg", "system": { "description": "
Lance belgfolk à pique traversant les alliages.
", "category": "melee", "origin": "petitPeuple", "sizeMode": "variable", "sizeValue": 0, "sizeModifier": 0, "damage": "Taille +0", "range": "", "properties": ["Blessure précise"], "restrictedRace": "", "quantity": 1, "price": 900, "equipped": false } }, - { "name": "Arc", "type": "arme", "img": "icons/svg/sword.svg", "system": { "description": "", "category": "tir", "origin": "petitPeuple", "sizeMode": "variable", "sizeValue": 0, "sizeModifier": 0, "damage": "Taille +0", "range": "75", "properties": ["Encocher une nouvelle flèche est une action libre"], "restrictedRace": "", "quantity": 1, "price": 360, "equipped": false } }, - { "name": "Arbalète", "type": "arme", "img": "icons/svg/sword.svg", "system": { "description": "Arme de tir du Petit Peuple listée dans le tableau des armes, sans prix explicite dans les tables de tarifs du livre.
", "category": "tir", "origin": "petitPeuple", "sizeMode": "variable", "sizeValue": 0, "sizeModifier": 1, "damage": "Taille +1", "range": "100", "properties": ["Encocher un nouveau carreau est une action unique"], "restrictedRace": "", "quantity": 1, "price": 0, "equipped": false } }, - { "name": "Dague de Songiam", "type": "arme", "img": "icons/svg/sword.svg", "system": { "description": "Dague kobolde fine et discrète.
", "category": "melee", "origin": "petitPeuple", "sizeMode": "variable", "sizeValue": 0, "sizeModifier": -1, "damage": "Taille -1", "range": "", "properties": ["Discrétion +3 en cas de fouille", "Blessure précise"], "restrictedRace": "", "quantity": 1, "price": 270, "equipped": false } }, - { "name": "Espadon huvon", "type": "arme", "img": "icons/svg/sword.svg", "system": { "description": "Grande épée forgée pour la musculature des korrigans.
", "category": "melee", "origin": "petitPeuple", "sizeMode": "fixe", "sizeValue": 4, "sizeModifier": 0, "damage": "4", "range": "", "properties": ["Korrigans", "Force 1"], "restrictedRace": "Korrigan", "quantity": 1, "price": 900, "equipped": false } }, - { "name": "Grifdrachat", "type": "arme", "img": "icons/svg/sword.svg", "system": { "description": "Pointe de métal recourbée capable de se glisser dans les interstices des armures.
", "category": "melee", "origin": "geant", "sizeMode": "fixe", "sizeValue": 3, "sizeModifier": 0, "damage": "3", "range": "", "properties": ["Blessure précise"], "restrictedRace": "", "quantity": 1, "price": 540, "equipped": false } }, - { "name": "Lance plume", "type": "arme", "img": "icons/svg/sword.svg", "system": { "description": "Lance incrustée d'une plume taillée, conçue pour les charges.
", "category": "melee", "origin": "petitPeuple", "sizeMode": "variable", "sizeValue": 0, "sizeModifier": 1, "damage": "Taille +1", "range": "", "properties": ["Charge"], "restrictedRace": "", "quantity": 1, "price": 270, "equipped": false } }, - { "name": "Poignard", "type": "arme", "img": "icons/svg/sword.svg", "system": { "description": "", "category": "jet", "origin": "petitPeuple", "sizeMode": "variable", "sizeValue": 0, "sizeModifier": -1, "damage": "Taille -1", "range": "Taille x 5", "properties": [], "restrictedRace": "", "quantity": 1, "price": 90, "equipped": false } } -] diff --git a/packs-src/armes.json b/packs-src/armes.json new file mode 100644 index 0000000..9cb55ec --- /dev/null +++ b/packs-src/armes.json @@ -0,0 +1,38 @@ +[ + { "name": "Aiguille à coudre", "type": "arme", "img": "icons/svg/sword.svg", "system": { "description": "Objet de géant détourné en arme fine et redoutablement pointue.
", "notes": "", "category": "melee", "origin": "geant", "sizeMode": "fixe", "sizeValue": 2, "sizeModifier": 0, "damage": "2", "range": "", "properties": [], "restrictedRace": "", "quantity": 1, "price": 90, "equipped": false } }, + { "name": "Aiguille à tricoter", "type": "arme", "img": "icons/svg/sword.svg", "system": { "description": "Longue arme de géant reconvertie en lance massive. Le tableau lui accorde Charge et exige Force 1.
", "notes": "", "category": "melee", "origin": "geant", "sizeMode": "fixe", "sizeValue": 5, "sizeModifier": 0, "damage": "5", "range": "", "properties": ["Charge", "Force 1"], "restrictedRace": "", "quantity": 1, "price": 630, "equipped": false } }, + { "name": "Clef de géant", "type": "arme", "img": "icons/svg/sword.svg", "system": { "description": "Arme très prisée dans le milieu des mercenaires. Ces instruments d'acier de géant sont réputés d'une redoutable efficacité.
", "notes": "", "category": "melee", "origin": "geant", "sizeMode": "plage", "sizeValue": 3, "sizeModifier": 0, "damage": "3 à 5", "range": "", "properties": [], "restrictedRace": "", "quantity": 1, "price": 450, "equipped": false } }, + { "name": "Couteau de géant", "type": "arme", "img": "icons/svg/sword.svg", "system": { "description": "Grand couteau des géants utilisé comme arme longue du Petit Peuple.
", "notes": "", "category": "melee", "origin": "geant", "sizeMode": "fixe", "sizeValue": 5, "sizeModifier": 0, "damage": "5", "range": "", "properties": [], "restrictedRace": "", "quantity": 1, "price": 630, "equipped": false } }, + { "name": "Clou", "type": "arme", "img": "icons/svg/sword.svg", "system": { "description": "Simple clou de géant, efficace comme pointe ou pieu improvisé.
", "notes": "", "category": "melee", "origin": "geant", "sizeMode": "plage", "sizeValue": 2, "sizeModifier": 0, "damage": "2 à 5", "range": "", "properties": [], "restrictedRace": "", "quantity": 1, "price": 12, "equipped": false } }, + { "name": "Épingle à nourrice", "type": "arme", "img": "icons/svg/sword.svg", "system": { "description": "Broche de géant détournée en arme perçante. On en fait aussi parfois des grappins.
", "notes": "", "category": "melee", "origin": "geant", "sizeMode": "fixe", "sizeValue": 2, "sizeModifier": 0, "damage": "2", "range": "", "properties": [], "restrictedRace": "", "quantity": 1, "price": 360, "equipped": false } }, + { "name": "Fourchette", "type": "arme", "img": "icons/svg/sword.svg", "system": { "description": "Couvert de géant converti en arme d'estoc ou de hampe selon sa prise.
", "notes": "", "category": "melee", "origin": "geant", "sizeMode": "plage", "sizeValue": 4, "sizeModifier": 0, "damage": "4 à 5", "range": "", "properties": [], "restrictedRace": "", "quantity": 1, "price": 630, "equipped": false } }, + { "name": "Grifdrachat", "type": "arme", "img": "icons/svg/sword.svg", "system": { "description": "Pointe de métal recourbée en forme de griffe de drachat. Extrêmement pointue, elle peut se glisser dans la plupart des interstices des armures.
", "notes": "", "category": "melee", "origin": "geant", "sizeMode": "fixe", "sizeValue": 3, "sizeModifier": 0, "damage": "3", "range": "", "properties": ["Blessure précise"], "restrictedRace": "", "quantity": 1, "price": 540, "equipped": false } }, + { "name": "Hameçon des Marches", "type": "arme", "img": "icons/svg/sword.svg", "system": { "description": "Les vivitins utilisent volontiers ces gros hameçons de géants comme armes, tenus à la main ou montés au bout d'une hampe.
", "notes": "", "category": "melee", "origin": "geant", "sizeMode": "fixe", "sizeValue": 2, "sizeModifier": 0, "damage": "2", "range": "", "properties": [], "restrictedRace": "", "quantity": 1, "price": 21, "equipped": false } }, + { "name": "Marteau de tailleur", "type": "arme", "img": "icons/svg/sword.svg", "system": { "description": "Outil de géant lourd et massif, réemployé comme arme contondante. Le livre ne donne pas de tarif explicite pour cette entrée.
", "notes": "", "category": "melee", "origin": "geant", "sizeMode": "plage", "sizeValue": 4, "sizeModifier": 0, "damage": "4 à 5", "range": "", "properties": [], "restrictedRace": "", "quantity": 1, "price": 0, "equipped": false } }, + { "name": "Paire de ciseaux", "type": "arme", "img": "icons/svg/sword.svg", "system": { "description": "Grande paire de ciseaux de géant. Le tableau lui accorde Attaque multiple mais impose Force 3.
", "notes": "", "category": "melee", "origin": "geant", "sizeMode": "plage", "sizeValue": 4, "sizeModifier": 0, "damage": "4 à 5", "range": "", "properties": ["Attaque multiple", "Force 3"], "restrictedRace": "", "quantity": 1, "price": 540, "equipped": false } }, + + { "name": "Akinakas", "type": "arme", "img": "icons/svg/sword.svg", "system": { "description": "Arme prisee par l'élite des gardes de Crinios. Les belgfolks fixent à leur lance les piques prélevées sur des veuves des mers ; l'akinakas est réputé traverser les alliages.
", "notes": "", "category": "melee", "origin": "petitPeuple", "sizeMode": "variable", "sizeValue": 0, "sizeModifier": 0, "damage": "Taille +0", "range": "", "properties": ["Blessure précise"], "restrictedRace": "", "quantity": 1, "price": 900, "equipped": false } }, + { "name": "Arc", "type": "arme", "img": "icons/svg/sword.svg", "system": { "description": "Arme de tir du Petit Peuple. Encocher une nouvelle flèche est une action libre.
", "notes": "", "category": "tir", "origin": "petitPeuple", "sizeMode": "variable", "sizeValue": 0, "sizeModifier": 0, "damage": "Taille +0", "range": "75", "properties": ["Encocher une nouvelle flèche est une action libre"], "restrictedRace": "", "quantity": 1, "price": 360, "equipped": false } }, + { "name": "Arbalète", "type": "arme", "img": "icons/svg/sword.svg", "system": { "description": "Arme de tir du Petit Peuple. Encocher un nouveau carreau est une action unique. Le livre ne donne pas de tarif explicite dans les tableaux de prix.
", "notes": "", "category": "tir", "origin": "petitPeuple", "sizeMode": "variable", "sizeValue": 0, "sizeModifier": 1, "damage": "Taille +1", "range": "100", "properties": ["Encocher un nouveau carreau est une action unique"], "restrictedRace": "", "quantity": 1, "price": 0, "equipped": false } }, + { "name": "Bâton de marche", "type": "arme", "img": "icons/svg/sword.svg", "system": { "description": "Bâton robuste qui peut aussi servir d'arme d'appoint. Le tableau lui accorde la prime Blessure non létale.
", "notes": "", "category": "melee", "origin": "petitPeuple", "sizeMode": "variable", "sizeValue": 0, "sizeModifier": 1, "damage": "Taille +1", "range": "", "properties": ["Blessure non létale"], "restrictedRace": "", "quantity": 1, "price": 10, "equipped": false } }, + { "name": "Coup de poing", "type": "arme", "img": "icons/svg/sword.svg", "system": { "description": "Ensemble de bagues reliées par une barre métallique. Arme de bagarreur qui laisse des marques durables.
", "notes": "", "category": "melee", "origin": "petitPeuple", "sizeMode": "variable", "sizeValue": 0, "sizeModifier": -1, "damage": "Taille -1", "range": "", "properties": [], "restrictedRace": "", "quantity": 1, "price": 24, "equipped": false } }, + { "name": "Dague de Songiam", "type": "arme", "img": "icons/svg/sword.svg", "system": { "description": "Dague kobolde si fine et discrète qu'on peut la dissimuler sans éveiller les soupçons. On la surnomme la dague du dernier souffle.
", "notes": "", "category": "melee", "origin": "petitPeuple", "sizeMode": "variable", "sizeValue": 0, "sizeModifier": -1, "damage": "Taille -1", "range": "", "properties": ["Discrétion +3 (en cas de fouille)", "Blessure précise"], "restrictedRace": "", "quantity": 1, "price": 270, "equipped": false } }, + { "name": "Dandegéant", "type": "arme", "img": "icons/svg/sword.svg", "system": { "description": "Masse d'arme à deux mains utilisée notamment par les Huvons, avec une dent de géant à son extrémité.
", "notes": "", "category": "melee", "origin": "petitPeuple", "sizeMode": "fixe", "sizeValue": 4, "sizeModifier": 0, "damage": "4", "range": "", "properties": [], "restrictedRace": "", "quantity": 1, "price": 720, "equipped": false } }, + { "name": "Épée", "type": "arme", "img": "icons/svg/sword.svg", "system": { "description": "Épée standard du Petit Peuple, listée sans particularité spéciale dans le tableau.
", "notes": "", "category": "melee", "origin": "petitPeuple", "sizeMode": "variable", "sizeValue": 0, "sizeModifier": 0, "damage": "Taille +0", "range": "", "properties": [], "restrictedRace": "", "quantity": 1, "price": 360, "equipped": false } }, + { "name": "Espadon huvon", "type": "arme", "img": "icons/svg/sword.svg", "system": { "description": "Grande épée forgée par et pour les Korrigans des Huvons. Aussi longue qu'une aiguille à tricoter, elle réclame Force 1.
", "notes": "", "category": "melee", "origin": "petitPeuple", "sizeMode": "fixe", "sizeValue": 4, "sizeModifier": 0, "damage": "4", "range": "", "properties": ["Force 1"], "restrictedRace": "Korrigan", "quantity": 1, "price": 900, "equipped": false } }, + { "name": "Fronde", "type": "arme", "img": "icons/svg/sword.svg", "system": { "description": "Arme de tir simple du Petit Peuple.
", "notes": "", "category": "tir", "origin": "petitPeuple", "sizeMode": "variable", "sizeValue": 0, "sizeModifier": -1, "damage": "Taille -1", "range": "50", "properties": [], "restrictedRace": "", "quantity": 1, "price": 5, "equipped": false } }, + { "name": "Glaive", "type": "arme", "img": "icons/svg/sword.svg", "system": { "description": "Arme de taille du Petit Peuple, listée sans propriété particulière.
", "notes": "", "category": "melee", "origin": "petitPeuple", "sizeMode": "variable", "sizeValue": 0, "sizeModifier": 0, "damage": "Taille +0", "range": "", "properties": [], "restrictedRace": "", "quantity": 1, "price": 180, "equipped": false } }, + { "name": "Hache", "type": "arme", "img": "icons/svg/sword.svg", "system": { "description": "Hache standard du Petit Peuple.
", "notes": "", "category": "melee", "origin": "petitPeuple", "sizeMode": "variable", "sizeValue": 0, "sizeModifier": 0, "damage": "Taille +0", "range": "", "properties": [], "restrictedRace": "", "quantity": 1, "price": 270, "equipped": false } }, + { "name": "Hachette", "type": "arme", "img": "icons/svg/sword.svg", "system": { "description": "Petite hache pouvant être lancée. Le tableau lui donne une portée de Taille x 10.
", "notes": "", "category": "jet", "origin": "petitPeuple", "sizeMode": "variable", "sizeValue": 0, "sizeModifier": -1, "damage": "Taille -1", "range": "Taille x 10", "properties": [], "restrictedRace": "", "quantity": 1, "price": 180, "equipped": false } }, + { "name": "Hymalamort", "type": "arme", "img": "icons/svg/sword.svg", "system": { "description": "Gourdin hérissé de clous, sans doute l'arme issue de matériaux de géants la plus répandue. Brutale, elle demande de la force pour être arrachée du corps de l'adversaire.
", "notes": "", "category": "melee", "origin": "petitPeuple", "sizeMode": "variable", "sizeValue": 0, "sizeModifier": 0, "damage": "Taille +0", "range": "", "properties": [], "restrictedRace": "", "quantity": 1, "price": 24, "equipped": false } }, + { "name": "Lame coup de poing", "type": "arme", "img": "icons/svg/sword.svg", "system": { "description": "Variante du coup de poing dotée d'une large lame dans le prolongement de la main, souvent vue dans les arènes de Ciméria.
", "notes": "", "category": "melee", "origin": "petitPeuple", "sizeMode": "variable", "sizeValue": 0, "sizeModifier": 0, "damage": "Taille +0", "range": "", "properties": [], "restrictedRace": "", "quantity": 1, "price": 180, "equipped": false } }, + { "name": "Lame d’Ichtys", "type": "arme", "img": "icons/svg/sword.svg", "system": { "description": "Lame recourbée, symbole des Vivitins, remise aux prêtres d'Ichtys lors de leur intronisation. Les marins l'apprécient particulièrement.
", "notes": "", "category": "melee", "origin": "petitPeuple", "sizeMode": "variable", "sizeValue": 0, "sizeModifier": 0, "damage": "Taille +0", "range": "", "properties": [], "restrictedRace": "", "quantity": 1, "price": 360, "equipped": false } }, + { "name": "Lance plume", "type": "arme", "img": "icons/svg/sword.svg", "system": { "description": "Lance ornée d'une plume taillée et incrustée. Si elle suit immédiatement un engagement, elle bénéficie gratuitement de la prime Blessure grave via Charge.
", "notes": "", "category": "melee", "origin": "petitPeuple", "sizeMode": "variable", "sizeValue": 0, "sizeModifier": 1, "damage": "Taille +1", "range": "", "properties": ["Charge"], "restrictedRace": "", "quantity": 1, "price": 270, "equipped": false } }, + { "name": "Mains nues", "type": "arme", "img": "icons/svg/sword.svg", "system": { "description": "Entrée canonique du tableau des armes pour les attaques à mains nues.
", "notes": "", "category": "melee", "origin": "petitPeuple", "sizeMode": "variable", "sizeValue": 0, "sizeModifier": -1, "damage": "Taille -1", "range": "", "properties": ["Blessure légère"], "restrictedRace": "", "quantity": 1, "price": 0, "equipped": false } }, + { "name": "Masse", "type": "arme", "img": "icons/svg/sword.svg", "system": { "description": "Masse standard du Petit Peuple. Le tableau de prix la nomme Masse en os.
", "notes": "", "category": "melee", "origin": "petitPeuple", "sizeMode": "variable", "sizeValue": 0, "sizeModifier": 0, "damage": "Taille +0", "range": "", "properties": [], "restrictedRace": "", "quantity": 1, "price": 12, "equipped": false } }, + { "name": "Masse d’arme", "type": "arme", "img": "icons/svg/sword.svg", "system": { "description": "Masse d'arme du Petit Peuple, distincte de la simple masse.
", "notes": "", "category": "melee", "origin": "petitPeuple", "sizeMode": "variable", "sizeValue": 0, "sizeModifier": 0, "damage": "Taille +0", "range": "", "properties": [], "restrictedRace": "", "quantity": 1, "price": 360, "equipped": false } }, + { "name": "Poignard", "type": "arme", "img": "icons/svg/sword.svg", "system": { "description": "Arme courte qui peut aussi être jetée. Le tableau lui donne une portée de Taille x 5.
", "notes": "", "category": "jet", "origin": "petitPeuple", "sizeMode": "variable", "sizeValue": 0, "sizeModifier": -1, "damage": "Taille -1", "range": "Taille x 5", "properties": [], "restrictedRace": "", "quantity": 1, "price": 90, "equipped": false } }, + { "name": "Piolet", "type": "arme", "img": "icons/svg/sword.svg", "system": { "description": "Version martiale du piolet listée dans le tableau des armes. À distinguer du piolet de voyage vendu dans le matériel de voyage.
", "notes": "", "category": "melee", "origin": "petitPeuple", "sizeMode": "variable", "sizeValue": 0, "sizeModifier": 0, "damage": "Taille +0", "range": "", "properties": [], "restrictedRace": "", "quantity": 1, "price": 270, "equipped": false } }, + { "name": "Sabre sixt", "type": "arme", "img": "icons/svg/sword.svg", "system": { "description": "Arme de prédilection de la noblesse des Sixts, souvent ornée de pierres précieuses et chargée de prestige.
", "notes": "", "category": "melee", "origin": "petitPeuple", "sizeMode": "variable", "sizeValue": 0, "sizeModifier": 0, "damage": "Taille +0", "range": "", "properties": [], "restrictedRace": "", "quantity": 1, "price": 450, "equipped": false } }, + { "name": "Serpe", "type": "arme", "img": "icons/svg/sword.svg", "system": { "description": "Petite lame courbe du Petit Peuple.
", "notes": "", "category": "melee", "origin": "petitPeuple", "sizeMode": "variable", "sizeValue": 0, "sizeModifier": -1, "damage": "Taille -1", "range": "", "properties": [], "restrictedRace": "", "quantity": 1, "price": 135, "equipped": false } } +] diff --git a/packs-src/armures.json b/packs-src/armures.json new file mode 100644 index 0000000..4f0cd56 --- /dev/null +++ b/packs-src/armures.json @@ -0,0 +1,50 @@ +[ + { + "name": "Protégé", + "type": "armure", + "img": "icons/svg/shield.svg", + "system": { + "description": "État d'armure légère du livre de base. Il correspond à un personnage équipé de quelques pièces défensives seulement : rondache, casque, bouton de géant, cuir léger ou pièces disparates.
", + "notes": "Le chapitre 5 donne surtout une table de prix par pièce. Cette entrée sert de profil prêt à jouer fidèle à la règle : protection 1, malus physique 1, malus d'initiative 1.
", + "state": "protege", + "protection": 1, + "physicalPenalty": 1, + "initiativePenalty": 1, + "quantity": 1, + "price": 0, + "equipped": false + } + }, + { + "name": "Harnaché", + "type": "armure", + "img": "icons/svg/shield.svg", + "system": { + "description": "État d'armure intermédiaire du livre de base. Il représente un personnage réellement équipé : cuirasse, jambières, pavois ou ensemble cohérent de pièces de protection.
", + "notes": "Le livre ne fixe pas de mécanique détaillée par pièce ; il donne un état global. Cette entrée correspond au profil standard de protection 2.
", + "state": "harnache", + "protection": 2, + "physicalPenalty": 2, + "initiativePenalty": 2, + "quantity": 1, + "price": 0, + "equipped": false + } + }, + { + "name": "Bardé", + "type": "armure", + "img": "icons/svg/shield.svg", + "system": { + "description": "État d'armure lourde du livre de base. Il correspond à un personnage abondamment protégé, jusqu'à l'armure complète.
", + "notes": "Profil abstrait mais canonique : protection 3, malus physique 3, malus d'initiative 3. À utiliser pour refléter les personnages les plus couverts sans surdétailler chaque pièce.
", + "state": "barde", + "protection": 3, + "physicalPenalty": 3, + "initiativePenalty": 3, + "quantity": 1, + "price": 0, + "equipped": false + } + } +] diff --git a/packs-src/competences.json b/packs-src/competences.json index c258c0c..244b691 100644 --- a/packs-src/competences.json +++ b/packs-src/competences.json @@ -1,29 +1,461 @@ [ - { "name": "Arts", "type": "competence", "img": "icons/svg/book.svg", "system": { "description": "Compétence à domaines artistiques.
", "key": "arts", "profileKey": "artiste", "base": 0, "closed": false, "domainSkill": true, "domains": [], "fixedDomains": [], "exampleDomains": ["Danse", "Musique", "Peinture", "Poésie", "Sculpture"] } }, - { "name": "Empathie", "type": "competence", "img": "icons/svg/book.svg", "system": { "description": "", "key": "empathie", "profileKey": "artiste", "base": 0, "closed": false, "domainSkill": false, "domains": [], "fixedDomains": [], "exampleDomains": [] } }, - { "name": "Séduction", "type": "competence", "img": "icons/svg/book.svg", "system": { "description": "", "key": "seduction", "profileKey": "artiste", "base": 0, "closed": false, "domainSkill": false, "domains": [], "fixedDomains": [], "exampleDomains": [] } }, - { "name": "Athlétisme", "type": "competence", "img": "icons/svg/book.svg", "system": { "description": "", "key": "athletisme", "profileKey": "athlete", "base": 0, "closed": false, "domainSkill": false, "domains": [], "fixedDomains": [], "exampleDomains": [] } }, - { "name": "Rapidité", "type": "competence", "img": "icons/svg/book.svg", "system": { "description": "", "key": "rapidite", "profileKey": "athlete", "base": 0, "closed": false, "domainSkill": false, "domains": [], "fixedDomains": [], "exampleDomains": [] } }, - { "name": "Volonté", "type": "competence", "img": "icons/svg/book.svg", "system": { "description": "", "key": "volonte", "profileKey": "athlete", "base": 0, "closed": false, "domainSkill": false, "domains": [], "fixedDomains": [], "exampleDomains": [] } }, - { "name": "Sens", "type": "competence", "img": "icons/svg/book.svg", "system": { "description": "", "key": "sens", "profileKey": "chasseur", "base": 0, "closed": false, "domainSkill": false, "domains": [], "fixedDomains": [], "exampleDomains": [] } }, - { "name": "Survie", "type": "competence", "img": "icons/svg/book.svg", "system": { "description": "", "key": "survie", "profileKey": "chasseur", "base": 0, "closed": false, "domainSkill": false, "domains": [], "fixedDomains": [], "exampleDomains": [] } }, - { "name": "Tir", "type": "competence", "img": "icons/svg/book.svg", "system": { "description": "", "key": "tir", "profileKey": "chasseur", "base": 0, "closed": false, "domainSkill": false, "domains": [], "fixedDomains": [], "exampleDomains": [] } }, - { "name": "Artisanat", "type": "competence", "img": "icons/svg/book.svg", "system": { "description": "Compétence à domaines techniques.
", "key": "artisanat", "profileKey": "faiseur", "base": 0, "closed": false, "domainSkill": true, "domains": [], "fixedDomains": [], "exampleDomains": ["Construction", "Forge", "Mécanique", "Menuiserie", "Taille de pierre"] } }, - { "name": "Intellect", "type": "competence", "img": "icons/svg/book.svg", "system": { "description": "", "key": "intellect", "profileKey": "faiseur", "base": 0, "closed": false, "domainSkill": false, "domains": [], "fixedDomains": [], "exampleDomains": [] } }, - { "name": "Soins", "type": "competence", "img": "icons/svg/book.svg", "system": { "description": "", "key": "soins", "profileKey": "faiseur", "base": 0, "closed": false, "domainSkill": false, "domains": [], "fixedDomains": [], "exampleDomains": [] } }, - { "name": "Commandement", "type": "competence", "img": "icons/svg/book.svg", "system": { "description": "", "key": "commandement", "profileKey": "forceNature", "base": 0, "closed": false, "domainSkill": false, "domains": [], "fixedDomains": [], "exampleDomains": [] } }, - { "name": "Endurance", "type": "competence", "img": "icons/svg/book.svg", "system": { "description": "", "key": "endurance", "profileKey": "forceNature", "base": 0, "closed": false, "domainSkill": false, "domains": [], "fixedDomains": [], "exampleDomains": [] } }, - { "name": "Force", "type": "competence", "img": "icons/svg/book.svg", "system": { "description": "", "key": "force", "profileKey": "forceNature", "base": 0, "closed": false, "domainSkill": false, "domains": [], "fixedDomains": [], "exampleDomains": [] } }, - { "name": "Corps à corps", "type": "competence", "img": "icons/svg/book.svg", "system": { "description": "", "key": "corpsacorps", "profileKey": "guerrier", "base": 0, "closed": false, "domainSkill": false, "domains": [], "fixedDomains": [], "exampleDomains": [] } }, - { "name": "Mêlée", "type": "competence", "img": "icons/svg/book.svg", "system": { "description": "", "key": "melee", "profileKey": "guerrier", "base": 0, "closed": false, "domainSkill": false, "domains": [], "fixedDomains": [], "exampleDomains": [] } }, - { "name": "Montures", "type": "competence", "img": "icons/svg/book.svg", "system": { "description": "", "key": "montures", "profileKey": "guerrier", "base": 0, "closed": false, "domainSkill": false, "domains": [], "fixedDomains": [], "exampleDomains": [] } }, - { "name": "Chimérisme", "type": "competence", "img": "icons/svg/book.svg", "system": { "description": "", "key": "chimerisme", "profileKey": "mystique", "base": 0, "closed": true, "domainSkill": false, "domains": [], "fixedDomains": [], "exampleDomains": [] } }, - { "name": "Magie", "type": "competence", "img": "icons/svg/book.svg", "system": { "description": "", "key": "magie", "profileKey": "mystique", "base": 0, "closed": true, "domainSkill": false, "domains": [], "fixedDomains": [], "exampleDomains": [] } }, - { "name": "Onirologie", "type": "competence", "img": "icons/svg/book.svg", "system": { "description": "", "key": "onirologie", "profileKey": "mystique", "base": 0, "closed": true, "domainSkill": false, "domains": [], "fixedDomains": [], "exampleDomains": [] } }, - { "name": "Discrétion", "type": "competence", "img": "icons/svg/book.svg", "system": { "description": "", "key": "discretion", "profileKey": "ombre", "base": 0, "closed": false, "domainSkill": false, "domains": [], "fixedDomains": [], "exampleDomains": [] } }, - { "name": "Esquive", "type": "competence", "img": "icons/svg/book.svg", "system": { "description": "", "key": "esquive", "profileKey": "ombre", "base": 0, "closed": false, "domainSkill": false, "domains": [], "fixedDomains": [], "exampleDomains": [] } }, - { "name": "Subterfuge", "type": "competence", "img": "icons/svg/book.svg", "system": { "description": "", "key": "subterfuge", "profileKey": "ombre", "base": 0, "closed": false, "domainSkill": false, "domains": [], "fixedDomains": [], "exampleDomains": [] } }, - { "name": "Érudition", "type": "competence", "img": "icons/svg/book.svg", "system": { "description": "Compétence fermée à domaines de savoir.
", "key": "erudition", "profileKey": "savant", "base": 0, "closed": true, "domainSkill": true, "domains": [], "fixedDomains": [], "exampleDomains": ["Edenia", "Histoire", "Légendes", "Lettres", "Terra Incognita"] } }, - { "name": "Langues", "type": "competence", "img": "icons/svg/book.svg", "system": { "description": "Compétence fermée à domaines linguistiques.
", "key": "langues", "profileKey": "savant", "base": 0, "closed": true, "domainSkill": true, "domains": [], "fixedDomains": [], "exampleDomains": ["Chimérique", "Jargon des likias", "Latin", "Oc", "Vieux lutin"] } }, - { "name": "Stratégie", "type": "competence", "img": "icons/svg/book.svg", "system": { "description": "", "key": "strategie", "profileKey": "savant", "base": 0, "closed": false, "domainSkill": false, "domains": [], "fixedDomains": [], "exampleDomains": [] } } + { + "name": "Arts", + "type": "competence", + "img": "icons/svg/book.svg", + "system": { + "description": "Mesure l'affinité du personnage avec les domaines artistiques. Un test permet de créer une œuvre, en reconnaître les techniques, en estimer l'intérêt ou mobiliser l'histoire de l'art.
", + "notes": "Compétence à domaines : le nombre de domaines maîtrisés est égal à la base.
", + "key": "arts", + "profileKey": "artiste", + "base": 0, + "closed": false, + "domainSkill": true, + "domains": [], + "fixedDomains": [], + "exampleDomains": ["Architecture", "Calligraphie", "Chant", "Danse", "Dessin", "Littérature", "Musique", "Peinture", "Poésie", "Sculpture", "Théâtre"] + } + }, + { + "name": "Empathie", + "type": "competence", + "img": "icons/svg/book.svg", + "system": { + "description": "Permet de saisir les intentions de quelqu'un, ce qu'il ressent, s'il ment, ou encore l'état émotionnel d'un animal.
", + "notes": "", + "key": "empathie", + "profileKey": "artiste", + "base": 0, + "closed": false, + "domainSkill": false, + "domains": [], + "fixedDomains": [], + "exampleDomains": [] + } + }, + { + "name": "Séduction", + "type": "competence", + "img": "icons/svg/book.svg", + "system": { + "description": "Régit le charme, les négociations, le marchandage, le mensonge et la persuasion par l'éloquence.
", + "notes": "", + "key": "seduction", + "profileKey": "artiste", + "base": 0, + "closed": false, + "domainSkill": false, + "domains": [], + "fixedDomains": [], + "exampleDomains": [] + } + }, + { + "name": "Athlétisme", + "type": "competence", + "img": "icons/svg/book.svg", + "system": { + "description": "Regroupe les actions physiques qui exigent coordination, agilité, équilibre et souffle, comme nager, courir ou sauter.
", + "notes": "", + "key": "athletisme", + "profileKey": "athlete", + "base": 0, + "closed": false, + "domainSkill": false, + "domains": [], + "fixedDomains": [], + "exampleDomains": [] + } + }, + { + "name": "Rapidité", + "type": "competence", + "img": "icons/svg/book.svg", + "system": { + "description": "Gouverne les réflexes, les courses de vitesse pure et toutes les actions où la célérité est essentielle. Elle sert aussi à déterminer l'initiative.
", + "notes": "", + "key": "rapidite", + "profileKey": "athlete", + "base": 0, + "closed": false, + "domainSkill": false, + "domains": [], + "fixedDomains": [], + "exampleDomains": [] + } + }, + { + "name": "Volonté", + "type": "competence", + "img": "icons/svg/book.svg", + "system": { + "description": "Mesure la capacité à affirmer sa personnalité, garder son sang-froid et résister à la peur.
", + "notes": "", + "key": "volonte", + "profileKey": "athlete", + "base": 0, + "closed": false, + "domainSkill": false, + "domains": [], + "fixedDomains": [], + "exampleDomains": [] + } + }, + { + "name": "Sens", + "type": "competence", + "img": "icons/svg/book.svg", + "system": { + "description": "Permet d'être à l'écoute de son environnement : entendre, repérer un danger avant qu'il ne surgisse, suivre quelqu'un sans le perdre ou déceler des signes faibles.
", + "notes": "", + "key": "sens", + "profileKey": "chasseur", + "base": 0, + "closed": false, + "domainSkill": false, + "domains": [], + "fixedDomains": [], + "exampleDomains": [] + } + }, + { + "name": "Survie", + "type": "competence", + "img": "icons/svg/book.svg", + "system": { + "description": "Englobe la vie autonome en milieu sauvage : orientation, raccourcis, escalade, exploration de ruines, navigation aux étoiles, chasse, lecture de carte et pistage.
", + "notes": "", + "key": "survie", + "profileKey": "chasseur", + "base": 0, + "closed": false, + "domainSkill": false, + "domains": [], + "fixedDomains": [], + "exampleDomains": [] + } + }, + { + "name": "Tir", + "type": "competence", + "img": "icons/svg/book.svg", + "system": { + "description": "Régit l'usage des armes à distance.
", + "notes": "", + "key": "tir", + "profileKey": "chasseur", + "base": 0, + "closed": false, + "domainSkill": false, + "domains": [], + "fixedDomains": [], + "exampleDomains": [] + } + }, + { + "name": "Artisanat", + "type": "competence", + "img": "icons/svg/book.svg", + "system": { + "description": "Mesure l'habileté du personnage avec ses mains et des outils simples, pour fabriquer, réparer ou juger la qualité d'un objet.
", + "notes": "Compétence à domaines : le nombre de domaines maîtrisés est égal à la base.
", + "key": "artisanat", + "profileKey": "faiseur", + "base": 0, + "closed": false, + "domainSkill": true, + "domains": [], + "fixedDomains": [], + "exampleDomains": ["Enluminure", "Forge", "Mécanique", "Menuiserie", "Peinture", "Restauration d’œuvres d’art", "Serrurerie", "Taille de pierre"] + } + }, + { + "name": "Intellect", + "type": "competence", + "img": "icons/svg/book.svg", + "system": { + "description": "Regroupe les facultés de logique et de raisonnement. On l'utilise pour résoudre un problème, décrypter un message, jouer aux échecs ou démêler une énigme.
", + "notes": "", + "key": "intellect", + "profileKey": "faiseur", + "base": 0, + "closed": false, + "domainSkill": false, + "domains": [], + "fixedDomains": [], + "exampleDomains": [] + } + }, + { + "name": "Soins", + "type": "competence", + "img": "icons/svg/book.svg", + "system": { + "description": "Regroupe les premiers soins, les soins journaliers, le diagnostic des maladies, la prescription de remèdes et la chirurgie.
", + "notes": "", + "key": "soins", + "profileKey": "faiseur", + "base": 0, + "closed": false, + "domainSkill": false, + "domains": [], + "fixedDomains": [], + "exampleDomains": [] + } + }, + { + "name": "Commandement", + "type": "competence", + "img": "icons/svg/book.svg", + "system": { + "description": "Mesure l'aptitude à donner des ordres, inspirer loyauté ou peur, faire parler quelqu'un par intimidation ou soutenir un allié face à la peur.
", + "notes": "", + "key": "commandement", + "profileKey": "forceNature", + "base": 0, + "closed": false, + "domainSkill": false, + "domains": [], + "fixedDomains": [], + "exampleDomains": [] + } + }, + { + "name": "Endurance", + "type": "competence", + "img": "icons/svg/book.svg", + "system": { + "description": "Permet de résister à la fatigue, de maintenir un effort prolongé et de rester éveillé de longues périodes.
", + "notes": "", + "key": "endurance", + "profileKey": "forceNature", + "base": 0, + "closed": false, + "domainSkill": false, + "domains": [], + "fixedDomains": [], + "exampleDomains": [] + } + }, + { + "name": "Force", + "type": "competence", + "img": "icons/svg/book.svg", + "system": { + "description": "Régit les manifestations brutes de puissance physique : briser des liens, enfoncer une porte, soulever une charge ou tordre des barreaux.
", + "notes": "", + "key": "force", + "profileKey": "forceNature", + "base": 0, + "closed": false, + "domainSkill": false, + "domains": [], + "fixedDomains": [], + "exampleDomains": [] + } + }, + { + "name": "Corps à corps", + "type": "competence", + "img": "icons/svg/book.svg", + "system": { + "description": "Mesure la faculté d'utiliser mains, pieds, tête, coudes et prises pour blesser, immobiliser ou faire tomber un adversaire.
", + "notes": "", + "key": "corpsacorps", + "profileKey": "guerrier", + "base": 0, + "closed": false, + "domainSkill": false, + "domains": [], + "fixedDomains": [], + "exampleDomains": [] + } + }, + { + "name": "Mêlée", + "type": "competence", + "img": "icons/svg/book.svg", + "system": { + "description": "Mesure l'aptitude martiale avec une arme en main, qu'il s'agisse d'une lame, d'une arme d'hast ou d'une arme contondante.
", + "notes": "", + "key": "melee", + "profileKey": "guerrier", + "base": 0, + "closed": false, + "domainSkill": false, + "domains": [], + "fixedDomains": [], + "exampleDomains": [] + } + }, + { + "name": "Montures", + "type": "competence", + "img": "icons/svg/book.svg", + "system": { + "description": "Détermine la capacité à débourrer, dresser et conduire des montures. Un personnage ne peut guider que des montures dont la taille ne dépasse la sienne que de 1 point.
", + "notes": "", + "key": "montures", + "profileKey": "guerrier", + "base": 0, + "closed": false, + "domainSkill": false, + "domains": [], + "fixedDomains": [], + "exampleDomains": [] + } + }, + { + "name": "Chimérisme", + "type": "competence", + "img": "icons/svg/book.svg", + "system": { + "description": "Équivalent d'Érudition pour les sortilèges des doux rêveurs et des sœurs de l'effroi.
", + "notes": "Compétence fermée : avec une base de 0, tout test impliquant cette compétence est automatiquement raté.
", + "key": "chimerisme", + "profileKey": "mystique", + "base": 0, + "closed": true, + "domainSkill": false, + "domains": [], + "fixedDomains": [], + "exampleDomains": [] + } + }, + { + "name": "Magie", + "type": "competence", + "img": "icons/svg/book.svg", + "system": { + "description": "Équivalent d'Érudition pour les sortilèges des mages des Songes et des mages noirs.
", + "notes": "Compétence fermée : avec une base de 0, tout test impliquant cette compétence est automatiquement raté.
", + "key": "magie", + "profileKey": "mystique", + "base": 0, + "closed": true, + "domainSkill": false, + "domains": [], + "fixedDomains": [], + "exampleDomains": [] + } + }, + { + "name": "Onirologie", + "type": "competence", + "img": "icons/svg/book.svg", + "system": { + "description": "Équivalent d'Érudition pour les sortilèges des rêvirines et des sangfous, ainsi que pour la récolte de fils de Songes ou de Cauchemar et l'affrontement du Néphertine.
", + "notes": "Compétence fermée : avec une base de 0, tout test impliquant cette compétence est automatiquement raté.
", + "key": "onirologie", + "profileKey": "mystique", + "base": 0, + "closed": true, + "domainSkill": false, + "domains": [], + "fixedDomains": [], + "exampleDomains": [] + } + }, + { + "name": "Discrétion", + "type": "competence", + "img": "icons/svg/book.svg", + "system": { + "description": "Permet de se cacher, de dissimuler un objet ou de se déplacer sans se faire repérer, souvent en opposition à Sens.
", + "notes": "", + "key": "discretion", + "profileKey": "ombre", + "base": 0, + "closed": false, + "domainSkill": false, + "domains": [], + "fixedDomains": [], + "exampleDomains": [] + } + }, + { + "name": "Esquive", + "type": "competence", + "img": "icons/svg/book.svg", + "system": { + "description": "Met un personnage à l'abri des tirs ou des coups, aide à se libérer de liens et couvre cascades, acrobaties et voltige périlleuse.
", + "notes": "", + "key": "esquive", + "profileKey": "ombre", + "base": 0, + "closed": false, + "domainSkill": false, + "domains": [], + "fixedDomains": [], + "exampleDomains": [] + } + }, + { + "name": "Subterfuge", + "type": "competence", + "img": "icons/svg/book.svg", + "system": { + "description": "Art de faire prendre les apparences pour la réalité : déguisement, faux documents, pickpocket et tours de passe-passe.
", + "notes": "", + "key": "subterfuge", + "profileKey": "ombre", + "base": 0, + "closed": false, + "domainSkill": false, + "domains": [], + "fixedDomains": [], + "exampleDomains": [] + } + }, + { + "name": "Érudition", + "type": "competence", + "img": "icons/svg/book.svg", + "system": { + "description": "Regroupe les connaissances intellectuelles, leurs théories, leurs pratiques et leur histoire. Le domaine Lettres couvre la lecture, l'écriture et la recherche documentaire.
", + "notes": "Compétence fermée et à domaines : le nombre de domaines maîtrisés est égal à la base.
", + "key": "erudition", + "profileKey": "savant", + "base": 0, + "closed": true, + "domainSkill": true, + "domains": [], + "fixedDomains": [], + "exampleDomains": ["Catholicisme", "Culte de Dame Nature", "Géographie", "Histoire", "Judaïsme", "Légendes", "Lettres", "Protestantisme", "Terra Incognita"] + } + }, + { + "name": "Langues", + "type": "competence", + "img": "icons/svg/book.svg", + "system": { + "description": "Rassemble les facultés de parler, traduire et contextualiser une ou plusieurs langues. Elle limite aussi les autres compétences dès qu'elles s'appliquent à un texte ou discours dans une langue connue.
", + "notes": "Compétence fermée et à domaines : chaque langue est un domaine distinct, et le personnage est illettré par défaut hors domaine Lettres/formation appropriée.
", + "key": "langues", + "profileKey": "savant", + "base": 0, + "closed": true, + "domainSkill": true, + "domains": [], + "fixedDomains": [], + "exampleDomains": ["Chimérique", "Jargon des likias", "Latin", "Lutin", "Oc", "Vieux lutin", "Velu nuton"] + } + }, + { + "name": "Stratégie", + "type": "competence", + "img": "icons/svg/book.svg", + "system": { + "description": "Science de la définition d'objectifs et des moyens pour les atteindre. Elle sert à planifier une action complexe et à comprendre les buts d'une organisation ou d'un adversaire.
", + "notes": "", + "key": "strategie", + "profileKey": "savant", + "base": 0, + "closed": false, + "domainSkill": false, + "domains": [], + "fixedDomains": [], + "exampleDomains": [] + } + } ] diff --git a/packs-src/equipements.json b/packs-src/equipements.json new file mode 100644 index 0000000..11e90e8 --- /dev/null +++ b/packs-src/equipements.json @@ -0,0 +1,597 @@ +[ + { + "name": "Lampe à fée des nuits", + "type": "equipement", + "img": "icons/svg/chest.svg", + "system": { + "description": "Lampe inspirée des lanternes-tempête des géants. Elle diffuse une lumière froide sans chaleur grâce à une ou plusieurs fées des nuits capturées.
", + "notes": "La lumière décroît à mesure que la créature enfermée dépérit.
", + "category": "voyage", + "quantity": 1, + "price": 360, + "bonus": "Éclaire sans produire de chaleur", + "usage": "Éclairage d'expédition", + "lifespan": "Quelques mois", + "equipped": false, + "consumable": false + } + }, + { + "name": "Dé à coudre (brasero)", + "type": "equipement", + "img": "icons/svg/chest.svg", + "system": { + "description": "Dé à coudre en acier de géant utilisé comme brasero portatif, pratique pour ne laisser presque aucune trace de campement.
", + "notes": "", + "category": "voyage", + "quantity": 1, + "price": 15, + "bonus": "", + "usage": "Brasero de voyage", + "lifespan": "", + "equipped": false, + "consumable": false + } + }, + { + "name": "Grappin", + "type": "equipement", + "img": "icons/svg/chest.svg", + "system": { + "description": "Hameçon, épingle à nourrice ou broche de géant affûtée servant à l'escalade et, au besoin, au combat rapproché.
", + "notes": "", + "category": "voyage", + "quantity": 1, + "price": 6, + "bonus": "", + "usage": "Escalade, ancrage, franchissement", + "lifespan": "", + "equipped": false, + "consumable": false + } + }, + { + "name": "Corde", + "type": "equipement", + "img": "icons/svg/chest.svg", + "system": { + "description": "Segment de corde de haute qualité prélevé sur les cordages des navires des géants.
", + "notes": "", + "category": "voyage", + "quantity": 1, + "price": 3, + "bonus": "", + "usage": "Chaque segment mesure environ 50 à 70 cm", + "lifespan": "", + "equipped": false, + "consumable": false + } + }, + { + "name": "Piolet de voyage", + "type": "equipement", + "img": "icons/svg/chest.svg", + "system": { + "description": "Petite canne hérissée de piques, utile pour grimper sur les hauteurs du Giganti, dans les Drumes ou sur les poutres des maisons des géants.
", + "notes": "", + "category": "voyage", + "quantity": 1, + "price": 60, + "bonus": "+3 aux tests d'escalade", + "usage": "Ascension et progression en terrain escarpé", + "lifespan": "", + "equipped": false, + "consumable": false + } + }, + { + "name": "Rikilin", + "type": "equipement", + "img": "icons/svg/chest.svg", + "system": { + "description": "Chaussures de marche à crampons métalliques conçues pour l'escalade de bois, de poutres ou de surfaces raides.
", + "notes": "Elles sont lourdes et ne se portent en pratique que pour l'ascension.
", + "category": "voyage", + "quantity": 1, + "price": 0, + "bonus": "+3 aux tests d'escalade", + "usage": "Ascension d'armoires, meubles, poutres et surfaces similaires", + "lifespan": "", + "equipped": false, + "consumable": false + } + }, + { + "name": "Trousse de premiers soins", + "type": "equipement", + "img": "icons/svg/chest.svg", + "system": { + "description": "Petite sacoche de secours contenant bandages, toiles d'araignée cicatrisantes, plantes désinfectantes et huiles essentielles contre les parasites.
", + "notes": "", + "category": "soin", + "quantity": 1, + "price": 0, + "bonus": "", + "usage": "Premiers soins et traitement léger", + "lifespan": "", + "equipped": false, + "consumable": false + } + }, + { + "name": "Nécessaire d’entretien d’armes", + "type": "equipement", + "img": "icons/svg/chest.svg", + "system": { + "description": "Kit d'affûtage et de protection contre la corrosion, indispensable pour garder des armes fiables en Terra Incognita.
", + "notes": "", + "category": "survie", + "quantity": 1, + "price": 0, + "bonus": "", + "usage": "Entretien d'armes", + "lifespan": "", + "equipped": false, + "consumable": false + } + }, + { + "name": "Nécessaire d’écriture / dessins", + "type": "equipement", + "img": "icons/svg/chest.svg", + "system": { + "description": "Encre, plume et supports d'écriture pour prendre des notes, dessiner ou cartographier.
", + "notes": "Le livre le décrit comme un peu d'encre dans une fiole bien fermée, des parchemins et parfois du papier volé aux géants.
", + "category": "ecriture", + "quantity": 1, + "price": 0, + "bonus": "", + "usage": "Écriture, dessin, cartographie", + "lifespan": "", + "equipped": false, + "consumable": false + } + }, + { + "name": "Bougie", + "type": "equipement", + "img": "icons/svg/chest.svg", + "system": { + "description": "Bougie de géant, souvent plantée sur une lance ou une pique pour éclairer les voyages nocturnes.
", + "notes": "Le livre insiste sur le risque d'incendie.
", + "category": "butin", + "quantity": 1, + "price": 180, + "bonus": "", + "usage": "Éclairage", + "lifespan": "", + "equipped": false, + "consumable": true + } + }, + { + "name": "Chandelle", + "type": "equipement", + "img": "icons/svg/chest.svg", + "system": { + "description": "Source de lumière plus modeste que la bougie de géant, mais toujours utile en expédition.
", + "notes": "", + "category": "butin", + "quantity": 1, + "price": 90, + "bonus": "", + "usage": "Éclairage", + "lifespan": "", + "equipped": false, + "consumable": true + } + }, + { + "name": "Dé à coudre de géant", + "type": "equipement", + "img": "icons/svg/chest.svg", + "system": { + "description": "Dérobé aux géants, ce dé à coudre peut être revendu, détourné ou recyclé en brasero.
", + "notes": "", + "category": "butin", + "quantity": 1, + "price": 24, + "bonus": "", + "usage": "Contenant, brasero improvisé, bien de troc", + "lifespan": "", + "equipped": false, + "consumable": false + } + }, + { + "name": "Bouton", + "type": "equipement", + "img": "icons/svg/chest.svg", + "system": { + "description": "Bouton de géant récupéré comme bien de valeur, matériau ou future rondache improvisée.
", + "notes": "", + "category": "butin", + "quantity": 1, + "price": 30, + "bonus": "", + "usage": "Troc, artisanat, récupération", + "lifespan": "", + "equipped": false, + "consumable": false + } + }, + { + "name": "Cordages", + "type": "equipement", + "img": "icons/svg/chest.svg", + "system": { + "description": "Lot de bons cordages prélevés sur les navires des géants.
", + "notes": "", + "category": "butin", + "quantity": 1, + "price": 27, + "bonus": "", + "usage": "Réserve de corde de meilleure ampleur qu'un simple segment", + "lifespan": "", + "equipped": false, + "consumable": false + } + }, + { + "name": "Morceaux de miroir", + "type": "equipement", + "img": "icons/svg/chest.svg", + "system": { + "description": "Éclats de miroir géant, utiles autant pour l'artisanat que pour certains tours de lumière ou de repérage.
", + "notes": "", + "category": "butin", + "quantity": 1, + "price": 90, + "bonus": "", + "usage": "Troc, artisanat, signaux", + "lifespan": "", + "equipped": false, + "consumable": false + } + }, + { + "name": "Morceaux de verre", + "type": "equipement", + "img": "icons/svg/chest.svg", + "system": { + "description": "Éclats de verre géant récupérés pour la fabrication, le troc ou certaines improvisations dangereuses.
", + "notes": "", + "category": "butin", + "quantity": 1, + "price": 150, + "bonus": "", + "usage": "Troc, artisanat, découpe", + "lifespan": "", + "equipped": false, + "consumable": false + } + }, + { + "name": "Mouchoirs en soie", + "type": "equipement", + "img": "icons/svg/chest.svg", + "system": { + "description": "Morceaux de textile précieux dérobés aux géants, recherchés pour leur finesse et leur rareté.
", + "notes": "", + "category": "butin", + "quantity": 1, + "price": 180, + "bonus": "", + "usage": "Troc, vêtements, artisanat", + "lifespan": "", + "equipped": false, + "consumable": false + } + }, + { + "name": "Morceaux de tissus", + "type": "equipement", + "img": "icons/svg/chest.svg", + "system": { + "description": "Coupons de tissu géant particulièrement utiles pour la couture, le troc ou la fabrication d'abris improvisés.
", + "notes": "", + "category": "butin", + "quantity": 1, + "price": 150, + "bonus": "", + "usage": "Troc, couture, réparation", + "lifespan": "", + "equipped": false, + "consumable": false + } + }, + { + "name": "Morceaux de parchemin", + "type": "equipement", + "img": "icons/svg/chest.svg", + "system": { + "description": "Supports d'écriture volés aux géants, rares et précieux pour qui veut tenir journal, archives ou cartes.
", + "notes": "", + "category": "ecriture", + "quantity": 1, + "price": 120, + "bonus": "", + "usage": "Écriture, cartes, documents", + "lifespan": "", + "equipped": false, + "consumable": true + } + }, + { + "name": "Plume", + "type": "equipement", + "img": "icons/svg/chest.svg", + "system": { + "description": "Grande plume de géant pouvant servir à l'écriture, à l'apparat ou à certains bricolages.
", + "notes": "", + "category": "ecriture", + "quantity": 1, + "price": 630, + "bonus": "", + "usage": "Écriture, décoration, artisanat", + "lifespan": "", + "equipped": false, + "consumable": false + } + }, + { + "name": "Encrier", + "type": "equipement", + "img": "icons/svg/chest.svg", + "system": { + "description": "Encrier dérobé aux géants, précieux pour l'écriture et la cartographie.
", + "notes": "", + "category": "ecriture", + "quantity": 1, + "price": 150, + "bonus": "", + "usage": "Écriture et dessin", + "lifespan": "", + "equipped": false, + "consumable": false + } + }, + { + "name": "Bague", + "type": "equipement", + "img": "icons/svg/chest.svg", + "system": { + "description": "Bijou géant d'une valeur exceptionnelle dans l'économie du Petit Peuple.
", + "notes": "", + "category": "butin", + "quantity": 1, + "price": 18000, + "bonus": "", + "usage": "Trésor, rançon, signe de prestige", + "lifespan": "", + "equipped": false, + "consumable": false + } + }, + { + "name": "Boucle d’oreille", + "type": "equipement", + "img": "icons/svg/chest.svg", + "system": { + "description": "Parure géante extrêmement recherchée, autant comme richesse portable que comme matériau précieux.
", + "notes": "", + "category": "butin", + "quantity": 1, + "price": 900, + "bonus": "", + "usage": "Trésor, parure, matière première", + "lifespan": "", + "equipped": false, + "consumable": false + } + }, + { + "name": "Bécasse des marais dressée", + "type": "equipement", + "img": "icons/svg/chest.svg", + "system": { + "description": "Monture dressée utilisable par le Petit Peuple selon la table des prix.
", + "notes": "Pré-créée comme équipement faute de type d'item dédié aux montures.
", + "category": "monture", + "quantity": 1, + "price": 5400, + "bonus": "", + "usage": "Monture dressée", + "lifespan": "", + "equipped": false, + "consumable": false + } + }, + { + "name": "Blaireau dressé", + "type": "equipement", + "img": "icons/svg/chest.svg", + "system": { + "description": "Monture dressée listée dans la table des prix du chapitre 5.
", + "notes": "Pré-créée comme équipement faute de type d'item dédié aux montures.
", + "category": "monture", + "quantity": 1, + "price": 118000, + "bonus": "", + "usage": "Monture dressée", + "lifespan": "", + "equipped": false, + "consumable": false + } + }, + { + "name": "Chauve-souris dressée", + "type": "equipement", + "img": "icons/svg/chest.svg", + "system": { + "description": "Monture dressée volante de la table des prix.
", + "notes": "Pré-créée comme équipement faute de type d'item dédié aux montures.
", + "category": "monture", + "quantity": 1, + "price": 5400, + "bonus": "", + "usage": "Monture dressée", + "lifespan": "", + "equipped": false, + "consumable": false + } + }, + { + "name": "Corbeau / choucas dressé", + "type": "equipement", + "img": "icons/svg/chest.svg", + "system": { + "description": "Oiseau dressé mentionné dans la table des montures du livre de base.
", + "notes": "Pré-créé comme équipement faute de type d'item dédié aux montures.
", + "category": "monture", + "quantity": 1, + "price": 6300, + "bonus": "", + "usage": "Monture dressée", + "lifespan": "", + "equipped": false, + "consumable": false + } + }, + { + "name": "Crapaud / grenouille dressé", + "type": "equipement", + "img": "icons/svg/chest.svg", + "system": { + "description": "Amphibien dressé prévu par la table des montures.
", + "notes": "Pré-créé comme équipement faute de type d'item dédié aux montures.
", + "category": "monture", + "quantity": 1, + "price": 4500, + "bonus": "", + "usage": "Monture dressée", + "lifespan": "", + "equipped": false, + "consumable": false + } + }, + { + "name": "Escuriel dressé", + "type": "equipement", + "img": "icons/svg/chest.svg", + "system": { + "description": "Monture dressée listée dans le chapitre des prix.
", + "notes": "Pré-créée comme équipement faute de type d'item dédié aux montures.
", + "category": "monture", + "quantity": 1, + "price": 7200, + "bonus": "", + "usage": "Monture dressée", + "lifespan": "", + "equipped": false, + "consumable": false + } + }, + { + "name": "Fouine / belette dressée", + "type": "equipement", + "img": "icons/svg/chest.svg", + "system": { + "description": "Petite monture nerveuse mentionnée dans la table des montures dressées.
", + "notes": "Pré-créée comme équipement faute de type d'item dédié aux montures.
", + "category": "monture", + "quantity": 1, + "price": 6300, + "bonus": "", + "usage": "Monture dressée", + "lifespan": "", + "equipped": false, + "consumable": false + } + }, + { + "name": "Hibou dressé", + "type": "equipement", + "img": "icons/svg/chest.svg", + "system": { + "description": "Monture dressée nocturne du chapitre 5.
", + "notes": "Pré-créée comme équipement faute de type d'item dédié aux montures.
", + "category": "monture", + "quantity": 1, + "price": 3600, + "bonus": "", + "usage": "Monture dressée", + "lifespan": "", + "equipped": false, + "consumable": false + } + }, + { + "name": "Lézard dressé", + "type": "equipement", + "img": "icons/svg/chest.svg", + "system": { + "description": "Monture dressée reptilienne listée dans les prix.
", + "notes": "Pré-créée comme équipement faute de type d'item dédié aux montures.
", + "category": "monture", + "quantity": 1, + "price": 4500, + "bonus": "", + "usage": "Monture dressée", + "lifespan": "", + "equipped": false, + "consumable": false + } + }, + { + "name": "Pigeon dressé", + "type": "equipement", + "img": "icons/svg/chest.svg", + "system": { + "description": "Petit oiseau dressé, peu coûteux relativement aux autres montures du tableau.
", + "notes": "Pré-créé comme équipement faute de type d'item dédié aux montures.
", + "category": "monture", + "quantity": 1, + "price": 2700, + "bonus": "", + "usage": "Monture dressée", + "lifespan": "", + "equipped": false, + "consumable": false + } + }, + { + "name": "Rat dressé", + "type": "equipement", + "img": "icons/svg/chest.svg", + "system": { + "description": "Monture dressée fréquente ou du moins familière dans la table du livre.
", + "notes": "Pré-créée comme équipement faute de type d'item dédié aux montures.
", + "category": "monture", + "quantity": 1, + "price": 5400, + "bonus": "", + "usage": "Monture dressée", + "lifespan": "", + "equipped": false, + "consumable": false + } + }, + { + "name": "Souris / mulot / musaraigne dressé", + "type": "equipement", + "img": "icons/svg/chest.svg", + "system": { + "description": "Plus petite monture dressée de la table des prix.
", + "notes": "Pré-créée comme équipement faute de type d'item dédié aux montures.
", + "category": "monture", + "quantity": 1, + "price": 900, + "bonus": "", + "usage": "Monture dressée", + "lifespan": "", + "equipped": false, + "consumable": false + } + } +] diff --git a/packs-src/pouvoirs-compagnie.json b/packs-src/pouvoirs-compagnie.json index 272387e..87aed71 100644 --- a/packs-src/pouvoirs-compagnie.json +++ b/packs-src/pouvoirs-compagnie.json @@ -1,10 +1,138 @@ [ - { "name": "Ardeur belliqueuse", "type": "pouvoircompagnie", "img": "icons/svg/aura.svg", "system": { "description": "Les attaques au corps à corps et en mêlée infligent 1 point de dégâts supplémentaire.
", "scope": "compagnie", "effectMode": "passif", "ruleText": "Les dégâts des attaques au corps à corps et en mêlée augmentent de 1 point.
", "limitedUses": "Permanent", "resourceImpact": "", "activationCondition": "À portée de vue du capitaine ; le capitaine doit lui-même voir au moins un autre membre", "captainVisible": true, "captainNeedsWitness": true } }, - { "name": "Aube flamboyante", "type": "pouvoircompagnie", "img": "icons/svg/aura.svg", "system": { "description": "La compagnie récupère plus vite ses Songes à l'aube.
", "scope": "compagnie", "effectMode": "passif", "ruleText": "À l'aube, les Oubliés récupèrent 2 points de Songes au lieu de 1.
", "limitedUses": "À chaque aube", "resourceImpact": "Songes", "activationCondition": "À portée de vue du capitaine ; le capitaine doit lui-même voir au moins un autre membre", "captainVisible": true, "captainNeedsWitness": true } }, - { "name": "Levier", "type": "pouvoircompagnie", "img": "icons/svg/aura.svg", "system": { "description": "Permet des réussites spectaculaires quand les deux dés montrent le même nombre.
", "scope": "compagnie", "effectMode": "passif", "ruleText": "Si les deux d12 montrent le même nombre, le résultat naturel s'obtient en les additionnant, sauf sur double 1.
", "limitedUses": "Permanent", "resourceImpact": "", "activationCondition": "À portée de vue du capitaine ; le capitaine doit lui-même voir au moins un autre membre", "captainVisible": true, "captainNeedsWitness": true } }, - { "name": "Patience", "type": "pouvoircompagnie", "img": "icons/svg/aura.svg", "system": { "description": "Concentration avant test pour améliorer le résultat.
", "scope": "compagnie", "effectMode": "action", "ruleText": "Passer un round à se concentrer avant un test permet d'augmenter de 1 le résultat final.
", "limitedUses": "À volonté", "resourceImpact": "", "activationCondition": "À portée de vue du capitaine ; le capitaine doit lui-même voir au moins un autre membre", "captainVisible": true, "captainNeedsWitness": true } }, - { "name": "Protection", "type": "pouvoircompagnie", "img": "icons/svg/aura.svg", "system": { "description": "La compagnie bénéficie d'une armure naturelle.
", "scope": "compagnie", "effectMode": "passif", "ruleText": "Le pouvoir accorde une armure naturelle de 2 points.
", "limitedUses": "Permanent", "resourceImpact": "", "activationCondition": "À portée de vue du capitaine ; le capitaine doit lui-même voir au moins un autre membre", "captainVisible": true, "captainNeedsWitness": true } }, - { "name": "Resplendissance", "type": "pouvoircompagnie", "img": "icons/svg/aura.svg", "system": { "description": "Transforme les réussites parfaites en succès éblouissants.
", "scope": "compagnie", "effectMode": "passif", "ruleText": "Sur un 12, le dé est relancé mais le 12 remplace le nouveau résultat pour le calcul du résultat naturel.
", "limitedUses": "Permanent", "resourceImpact": "", "activationCondition": "À portée de vue du capitaine ; le capitaine doit lui-même voir au moins un autre membre", "captainVisible": true, "captainNeedsWitness": true } }, - { "name": "Sauvegarde", "type": "pouvoircompagnie", "img": "icons/svg/aura.svg", "system": { "description": "Permet de relancer un 1 naturel sur un test de compétence.
", "scope": "compagnie", "effectMode": "réaction", "ruleText": "Un 1 naturel peut être relancé une fois. Si un nouveau 1 est obtenu, il doit être conservé.
", "limitedUses": "Permanent", "resourceImpact": "", "activationCondition": "À portée de vue du capitaine ; le capitaine doit lui-même voir au moins un autre membre", "captainVisible": true, "captainNeedsWitness": true } }, - { "name": "Songes immanents", "type": "pouvoircompagnie", "img": "icons/svg/aura.svg", "system": { "description": "La compagnie possède un point de Songes partagé.
", "scope": "compagnie", "effectMode": "ressource", "ruleText": "La compagnie possède 1 point de Songes utilisable par un membre, régénéré à l'aube.
", "limitedUses": "1 par aube", "resourceImpact": "1 point de Songes partagé", "activationCondition": "À portée de vue du capitaine ; le capitaine doit lui-même voir au moins un autre membre", "captainVisible": true, "captainNeedsWitness": true } } + { + "name": "Ardeur belliqueuse", + "type": "pouvoircompagnie", + "img": "icons/svg/aura.svg", + "system": { + "description": "Pouvoir de compagnie offensif favorisant les assauts du groupe.
", + "notes": "", + "scope": "compagnie", + "effectMode": "passif", + "ruleText": "Les dégâts des attaques au corps à corps et en mêlée augmentent de 1 point.
", + "limitedUses": "", + "resourceImpact": "", + "activationCondition": "À portée de vue du capitaine ; le capitaine doit voir au moins un autre membre pour en bénéficier lui-même.", + "captainVisible": true, + "captainNeedsWitness": true + } + }, + { + "name": "Aube flamboyante", + "type": "pouvoircompagnie", + "img": "icons/svg/aura.svg", + "system": { + "description": "Pouvoir de compagnie axé sur la récupération de Songes.
", + "notes": "", + "scope": "compagnie", + "effectMode": "passif", + "ruleText": "À l'aube, les Oubliés de la compagnie récupèrent 2 points de Songes au lieu de 1 seul.
", + "limitedUses": "", + "resourceImpact": "+1 point de Songes récupéré à l'aube", + "activationCondition": "À portée de vue du capitaine ; le capitaine doit voir au moins un autre membre pour en bénéficier lui-même.", + "captainVisible": true, + "captainNeedsWitness": true + } + }, + { + "name": "Levier", + "type": "pouvoircompagnie", + "img": "icons/svg/aura.svg", + "system": { + "description": "Pouvoir de compagnie qui magnifie les doubles obtenus sur 2d12.
", + "notes": "", + "scope": "compagnie", + "effectMode": "passif", + "ruleText": "Lors d'un test réalisé avec 2d12, si les deux dés indiquent le même nombre, le résultat naturel est calculé en additionnant ces deux nombres, sauf sur deux 1 où le pouvoir reste sans effet.
", + "limitedUses": "", + "resourceImpact": "", + "activationCondition": "À portée de vue du capitaine ; le capitaine doit voir au moins un autre membre pour en bénéficier lui-même.", + "captainVisible": true, + "captainNeedsWitness": true + } + }, + { + "name": "Patience", + "type": "pouvoircompagnie", + "img": "icons/svg/aura.svg", + "system": { + "description": "Pouvoir de compagnie qui récompense la concentration avant l'action.
", + "notes": "", + "scope": "compagnie", + "effectMode": "préparation", + "ruleText": "Passer cinq secondes, soit un round en combat, à se concentrer avant un test de compétence permet d'augmenter de 1 le résultat final. Ce temps de concentration est une action unique réussie automatiquement.
", + "limitedUses": "", + "resourceImpact": "", + "activationCondition": "À portée de vue du capitaine ; le capitaine doit voir au moins un autre membre pour en bénéficier lui-même.", + "captainVisible": true, + "captainNeedsWitness": true + } + }, + { + "name": "Protection", + "type": "pouvoircompagnie", + "img": "icons/svg/aura.svg", + "system": { + "description": "Pouvoir de compagnie défensif accordant une armure naturelle.
", + "notes": "", + "scope": "compagnie", + "effectMode": "passif", + "ruleText": "Le pouvoir accorde une armure naturelle de 2 points.
", + "limitedUses": "", + "resourceImpact": "", + "activationCondition": "À portée de vue du capitaine ; le capitaine doit voir au moins un autre membre pour en bénéficier lui-même.", + "captainVisible": true, + "captainNeedsWitness": true + } + }, + { + "name": "Resplendissance", + "type": "pouvoircompagnie", + "img": "icons/svg/aura.svg", + "system": { + "description": "Pouvoir de compagnie qui sublime les réussites parfaites.
", + "notes": "", + "scope": "compagnie", + "effectMode": "passif", + "ruleText": "Lors d'un test, si le dé indique 12, le dé est relancé conformément aux règles habituelles. Cependant, le chiffre 12 remplace toujours le résultat obtenu au nouveau jet pour déterminer le résultat naturel. Si un nouveau 12 apparaît, le procédé continue jusqu'à obtention d'un autre chiffre.
", + "limitedUses": "", + "resourceImpact": "", + "activationCondition": "À portée de vue du capitaine ; le capitaine doit voir au moins un autre membre pour en bénéficier lui-même.", + "captainVisible": true, + "captainNeedsWitness": true + } + }, + { + "name": "Sauvegarde", + "type": "pouvoircompagnie", + "img": "icons/svg/aura.svg", + "system": { + "description": "Pouvoir de compagnie qui donne une seconde chance face au pire résultat naturel.
", + "notes": "", + "scope": "compagnie", + "effectMode": "réaction", + "ruleText": "Lors d'un test de compétence, le joueur qui obtient un résultat naturel de 1 peut relancer le dé. S'il obtient à nouveau un 1 naturel, il doit garder ce résultat.
", + "limitedUses": "", + "resourceImpact": "", + "activationCondition": "À portée de vue du capitaine ; le capitaine doit voir au moins un autre membre pour en bénéficier lui-même.", + "captainVisible": true, + "captainNeedsWitness": true + } + }, + { + "name": "Songes immanents", + "type": "pouvoircompagnie", + "img": "icons/svg/aura.svg", + "system": { + "description": "Pouvoir de compagnie doté de sa propre réserve de Songes.
", + "notes": "", + "scope": "compagnie", + "effectMode": "ressource", + "ruleText": "La compagnie possède 1 point de Songes. Un membre peut l'utiliser comme si c'était l'un des siens. Il n'est alors plus utilisable jusqu'à la prochaine aube où il se régénère.
", + "limitedUses": "1 point par aube", + "resourceImpact": "Réserve commune de 1 point de Songes", + "activationCondition": "À portée de vue du capitaine ; le capitaine doit voir au moins un autre membre pour en bénéficier lui-même.", + "captainVisible": true, + "captainNeedsWitness": true + } + } ] diff --git a/packs-src/sortileges-sample.json b/packs-src/sortileges-sample.json deleted file mode 100644 index 8460df9..0000000 --- a/packs-src/sortileges-sample.json +++ /dev/null @@ -1,10 +0,0 @@ -[ - { "name": "Chevelure de sirène", "type": "sortilege", "img": "icons/svg/daze.svg", "system": { "description": "Fouet scintillant qui gêne les adversaires touchés.
", "tradition": "magie", "skillKey": "magie", "polarity": "songes", "cost": 1, "costFormula": "", "variableCost": false, "preparation": "1 action libre", "duration": "1 combat", "range": "personnelle", "area": "-", "stacking": "non", "requiredDomains": [], "artsDomains": [], "effectsText": "Confère la prime Facilité à chaque attaque ; n'inflige pas de dégâts mais applique Difficulté.
", "ruleTags": ["combat", "altération"] } }, - { "name": "Dôme scintillant", "type": "sortilege", "img": "icons/svg/daze.svg", "system": { "description": "Dôme protecteur rendant les attaques à distance plus difficiles.
", "tradition": "magie", "skillKey": "magie", "polarity": "songes", "cost": 1, "costFormula": "", "variableCost": false, "preparation": "1 action gratuite", "duration": "1 combat", "range": "personnelle", "area": "10 cm", "stacking": "non", "requiredDomains": [], "artsDomains": [], "effectsText": "Les attaques à distance visant l'intérieur subissent un malus de -3.
", "ruleTags": ["protection", "zone"] } }, - { "name": "Liens de Songes", "type": "sortilege", "img": "icons/svg/daze.svg", "system": { "description": "Entrave lumineuse qui immobilise une cible.
", "tradition": "magie", "skillKey": "magie", "polarity": "songes", "cost": 2, "costFormula": "", "variableCost": false, "preparation": "1 action libre", "duration": "1 round", "range": "vue", "area": "cible", "stacking": "non", "requiredDomains": [], "artsDomains": [], "effectsText": "La cible ne peut plus agir physiquement sauf test de Force / -6.
", "ruleTags": ["contrôle"] } }, - { "name": "Pluie d'étoiles", "type": "sortilege", "img": "icons/svg/daze.svg", "system": { "description": "Nuée d'éclats infligeant des dégâts autour du mage.
", "tradition": "magie", "skillKey": "magie", "polarity": "songes", "cost": 1, "costFormula": "", "variableCost": false, "preparation": "1 action unique", "duration": "instantanée", "range": "personnelle", "area": "adversaires engagés", "stacking": "-", "requiredDomains": [], "artsDomains": [], "effectsText": "Inflige 2 points de dégâts à tous les adversaires engagés, sans protection d'armure.
", "ruleTags": ["dégâts", "zone"] } }, - { "name": "Armure obscurine", "type": "sortilege", "img": "icons/svg/daze.svg", "system": { "description": "L'ombre du mage absorbe les dégâts.
", "tradition": "magie", "skillKey": "magie", "polarity": "cauchemar", "cost": 1, "costFormula": "X", "variableCost": true, "preparation": "1 action unique", "duration": "1 combat", "range": "personnelle", "area": "-", "stacking": "oui", "requiredDomains": [], "artsDomains": [], "effectsText": "Absorbe X points de dégâts jusqu'à dissipation de l'ombre.
", "ruleTags": ["protection", "cauchemar"] } }, - { "name": "Aspect cauchemardesque", "type": "sortilege", "img": "icons/svg/daze.svg", "system": { "description": "Altère le visage pour intimider.
", "tradition": "magie", "skillKey": "magie", "polarity": "cauchemar", "cost": 1, "costFormula": "", "variableCost": false, "preparation": "1 action gratuite", "duration": "1 h", "range": "personnelle", "area": "-", "stacking": "non", "requiredDomains": [], "artsDomains": [], "effectsText": "Octroie +3 aux tests de Commandement pour intimider.
", "ruleTags": ["social", "cauchemar"] } }, - { "name": "Hirond'ailes", "type": "sortilege", "img": "icons/svg/daze.svg", "system": { "description": "Sortilège farfadet de vol personnel.
", "tradition": "farfadet", "skillKey": "magie", "polarity": "songes", "cost": 1, "costFormula": "", "variableCost": false, "preparation": "1 action unique", "duration": "utilisation", "range": "personnelle", "area": "-", "stacking": "non", "requiredDomains": [], "artsDomains": [], "effectsText": "Fait apparaître des ailes permettant de voler avec un équipement léger.
", "ruleTags": ["déplacement", "farfadet"] } }, - { "name": "Seconde peau", "type": "sortilege", "img": "icons/svg/daze.svg", "system": { "description": "Tatouage protecteur absorbant les blessures.
", "tradition": "farfadet", "skillKey": "magie", "polarity": "songes", "cost": 1, "costFormula": "X", "variableCost": true, "preparation": "1 action unique", "duration": "spéciale", "range": "toucher", "area": "1 être vivant", "stacking": "non", "requiredDomains": [], "artsDomains": [], "effectsText": "Absorbe les 2X prochains dégâts jusqu'à disparition du tatouage.
", "ruleTags": ["protection", "farfadet"] } } -] diff --git a/packs-src/sortileges.json b/packs-src/sortileges.json new file mode 100644 index 0000000..78c50fa --- /dev/null +++ b/packs-src/sortileges.json @@ -0,0 +1,1996 @@ +[ + { + "name": "Chevelure de sirène", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Un fouet scintillant aux lanières incrustées de diamants apparaît dans la main du personnage.
", + "notes": "", + "tradition": "magie", + "skillKey": "magie", + "polarity": "songes", + "cost": 1, + "costFormula": "", + "variableCost": false, + "preparation": "1 action libre", + "duration": "1 combat", + "range": "personnelle", + "area": "-", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Un fouet scintillant aux lanières incrustées de diamants apparaît dans la main du personnage. (Il peut le confier à autrui.) Il confère la prime Facilité à chaque attaque. Il n’inflige pas de dégâts, mais tout adversaire qu’il touche subit la pénalité Difficulté à toutes ses actions jusqu’à la fin du combat. Si un adversaire est touché plusieurs fois, chaque coup supplémentaire augmente cette pénalité de 1 point (-4, -5, -6, etc.).
", + "ruleTags": [ + "magie", + "songes" + ] + } + }, + { + "name": "Dôme scintillant", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le mage des Songes crée un dôme protecteur autour de lui dans un rayon de 10 cm.
", + "notes": "", + "tradition": "magie", + "skillKey": "magie", + "polarity": "songes", + "cost": 1, + "costFormula": "", + "variableCost": false, + "preparation": "1 action gratuite", + "duration": "1 combat", + "range": "personnelle", + "area": "10 cm", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le mage des Songes crée un dôme protecteur autour de lui dans un rayon de 10 cm. Le dôme est constitué d’une multitude de points scintillants qui rendent difficile la vision de ce qui se passe à l’intérieur. Les attaques à distance subissent un malus de -3 si elles ont pour cible une créature à l’intérieur, qui elle, n’est pas gênée et voie normalement. Le dôme ne se déplace pas, même si le mage peut le quitter à tout moment pour y revenir s’il le souhaite.
", + "ruleTags": [ + "magie", + "songes" + ] + } + }, + { + "name": "Éblouissement", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le mage des Songes produit un flash très puissant et très bref qui aveugle un ennemi engagé contre lui si le sort est lancé en combat ou une créature le regardant si le sort est lancé hors combat.
", + "notes": "", + "tradition": "magie", + "skillKey": "magie", + "polarity": "songes", + "cost": 1, + "costFormula": "", + "variableCost": false, + "preparation": "1 action libre", + "duration": "instantanée", + "range": "10 cm", + "area": "un être vivant", + "stacking": "-", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le mage des Songes produit un flash très puissant et très bref qui aveugle un ennemi engagé contre lui si le sort est lancé en combat ou une créature le regardant si le sort est lancé hors combat. La cible aveuglée ne retrouve que progressivement ses capacités visuelle : elle ne voit absolument rien le premier round et subit un malus de -6 à tous ses tests impliquant la vue, retrouve partiellement la vue au second round (malus de -3), au terme duquel elle recouvre enfin totalement la vision.
", + "ruleTags": [ + "magie", + "songes" + ] + } + }, + { + "name": "Feux follets", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le mage des Songes invoque une dizaine de feux follets qui l’entourent, le suivent et éclairent parfaitement autour de lui sur une distance de 10 cm, et plus faiblement sur une distance supplémentaire de 10 cm.
", + "notes": "", + "tradition": "magie", + "skillKey": "magie", + "polarity": "songes", + "cost": 1, + "costFormula": "", + "variableCost": false, + "preparation": "1 action libre", + "duration": "1 h", + "range": "personnelle", + "area": "-", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le mage des Songes invoque une dizaine de feux follets qui l’entourent, le suivent et éclairent parfaitement autour de lui sur une distance de 10 cm, et plus faiblement sur une distance supplémentaire de 10 cm.
", + "ruleTags": [ + "magie", + "songes" + ] + } + }, + { + "name": "Fusion élémentaire", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage se fond dans un élément naturel faisant au moins deux fois sa taille (sol de terre battue, arbre, glacier, etc.).
", + "notes": "", + "tradition": "magie", + "skillKey": "magie", + "polarity": "songes", + "cost": 1, + "costFormula": "", + "variableCost": false, + "preparation": "1 action unique", + "duration": "1 h", + "range": "personnelle", + "area": "-", + "stacking": "oui", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le personnage se fond dans un élément naturel faisant au moins deux fois sa taille (sol de terre battue, arbre, glacier, etc.). Il voit et entend ce qui se passe à l’extérieur. Il ne peut pas bouger. S’il esquisse le moindre geste, il est immédiatement expulsé, avant le terme du sortilège. Il peut augmenter la durée, en dépensant 1 point de Songes toutes les 15 minutes.
", + "ruleTags": [ + "magie", + "songes" + ] + } + }, + { + "name": "Liens de Songes", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le mage des Songes fait apparaître des liens de lumière, particulièrement résistants, autour des mains et des pieds de la personne qu’il souhaite attacher.
", + "notes": "", + "tradition": "magie", + "skillKey": "magie", + "polarity": "songes", + "cost": 2, + "costFormula": "", + "variableCost": false, + "preparation": "1 action libre", + "duration": "1 round", + "range": "vue", + "area": "cible", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le mage des Songes fait apparaître des liens de lumière, particulièrement résistants, autour des mains et des pieds de la personne qu’il souhaite attacher. La cible ne peux plus agir physiquement pendant la durée du sort à moins de réussir un test de Force / -6 pour briser les liens. Le sortilège peut être lancé sur un objet que personne ne pourra déplacer à moins de réussir un test de Force / -6.
", + "ruleTags": [ + "magie", + "songes" + ] + } + }, + { + "name": "Pluie d’étoiles", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le mage des Songes projette au-dessus de lui une myriade de morceaux de verre, telle une nuée d’étoiles, qui retombent sur le sol en formant un dôme.
", + "notes": "", + "tradition": "magie", + "skillKey": "magie", + "polarity": "songes", + "cost": 1, + "costFormula": "", + "variableCost": false, + "preparation": "1 action unique", + "duration": "instantanée", + "range": "personnelle", + "area": "adversaire(s) engagé(s)", + "stacking": "-", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le mage des Songes projette au-dessus de lui une myriade de morceaux de verre, telle une nuée d’étoiles, qui retombent sur le sol en formant un dôme. Les éclats de verre sont tranchants comme des lames de rasoir, mais n’affectent pas le lanceur de sort. En revanche, ils infligent 2 points de dégâts à tous les adversaires avec lesquels il est engagé. Les armures ne protègent pas de ces dommages.
", + "ruleTags": [ + "magie", + "songes" + ] + } + }, + { + "name": "Purification des Songes", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage place une main sur sa cible et l’irrigue de Songes.
", + "notes": "", + "tradition": "magie", + "skillKey": "magie", + "polarity": "songes", + "cost": 1, + "costFormula": "", + "variableCost": false, + "preparation": "1 action unique", + "duration": "instantanée", + "range": "toucher", + "area": "un être vivant volontaire possédant des dettes de Cauchemar et désirant les voir disparaître.", + "stacking": "-", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le personnage place une main sur sa cible et l’irrigue de Songes. Les dettes de Cauchemar s’effacent. Ce sortilège n’est utilisable qu’une seule fois par cible.
", + "ruleTags": [ + "magie", + "songes" + ] + } + }, + { + "name": "Régurgitat licornéen", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage ouvre sa bouche qui s’élargie en une gueule béante d’où jaillit une gerbe de lumière arc-en-ciel extrêmement corrosive pour les créatures du Cauchemar.
", + "notes": "", + "tradition": "magie", + "skillKey": "magie", + "polarity": "songes", + "cost": 1, + "costFormula": "", + "variableCost": false, + "preparation": "1 action unique", + "duration": "1 h", + "range": "1 m", + "area": "1 cible", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le personnage ouvre sa bouche qui s’élargie en une gueule béante d’où jaillit une gerbe de lumière arc-en-ciel extrêmement corrosive pour les créatures du Cauchemar. Si le magicien cible une créature du Cauchemar, celle-ci perd 2 points de vie. S’il cible un objet ou un autre être vivant, celui-ci ne pourra pas être touché par une créature du Cauchemar sans qu’elle subisse des dégâts (1 point de dégâts par round de contact), sauf si elle prend ses précautions (en portant des gants par exemple). Le liquide reste corrosif pendant une heure avant de devenir inoffensif.
", + "ruleTags": [ + "magie", + "songes" + ] + } + }, + { + "name": "Vision dans la pierre", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage plaque ses mains sur un mur de pierre, ou un obstacle naturel, et se concentre sur celui-ci.
", + "notes": "", + "tradition": "magie", + "skillKey": "magie", + "polarity": "songes", + "cost": 1, + "costFormula": "", + "variableCost": false, + "preparation": "1 action unique", + "duration": "variable", + "range": "toucher", + "area": "-", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le personnage plaque ses mains sur un mur de pierre, ou un obstacle naturel, et se concentre sur celui-ci. Il est alors en mesure de voir à travers, à condition que cet obstacle ne dépasse pas 50 cm d’épaisseur et qu’il ne soit pas composé d’alliages métalliques (acier, bronze…). Le sortilège ne permet pas d’entendre, ni sentir ce qui se trouve derrière l’obstacle. Lorsqu’il « projette » sa vision, le magicien ne peut pas voir ce qui se passe du côté de son corps. Le sort s’interrompt lorsque le personnage cesse sa concentration.
", + "ruleTags": [ + "magie", + "songes" + ] + } + }, + { + "name": "Eau du souvenir", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage pose les os d’une créature d’Edenia dans une surface d’eau, éventuellement répandue par le farfadet pour l’occasion.
", + "notes": "", + "tradition": "farfadet", + "skillKey": "magie", + "polarity": "songes", + "cost": 1, + "costFormula": "", + "variableCost": false, + "preparation": "1 action unique", + "duration": "instantanée", + "range": "toucher", + "area": "surface d’eau", + "stacking": "-", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le personnage pose les os d’une créature d’Edenia dans une surface d’eau, éventuellement répandue par le farfadet pour l’occasion. Des images de ce qui s’est passé à proximité s’y reflètent, au gré du meneur de jeu.
", + "ruleTags": [ + "farfadet", + "songes" + ] + } + }, + { + "name": "Hirond’ailes", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le tatouage du farfadet représente deux ailes, qui sortent du dos du porteur lors de l’activation et lui permettent de se déplacer dans les airs.
", + "notes": "", + "tradition": "farfadet", + "skillKey": "magie", + "polarity": "songes", + "cost": 1, + "costFormula": "", + "variableCost": false, + "preparation": "1 action unique", + "duration": "utilisation", + "range": "personnelle", + "area": "-", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le tatouage du farfadet représente deux ailes, qui sortent du dos du porteur lors de l’activation et lui permettent de se déplacer dans les airs. Celles-ci ne sont pas assez puissantes pour soulever plus que le personnage et un équipement léger. Elles disparaissent, ainsi que le tatouage, dès qu’il cesse de voler et se pose.
", + "ruleTags": [ + "farfadet", + "songes" + ] + } + }, + { + "name": "Œil du cœur", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage dessine un troisième œil sur son front.
", + "notes": "", + "tradition": "farfadet", + "skillKey": "magie", + "polarity": "songes", + "cost": 1, + "costFormula": "", + "variableCost": false, + "preparation": "1 action unique", + "duration": "instantanée", + "range": "vue", + "area": "un être vivant", + "stacking": "-", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le personnage dessine un troisième œil sur son front. (Il peut le faire avec son doigt sans obligatoirement faire un dessin apparent. S’il possède déjà un œil peint ou tatoué à cet endroit, la préparation consiste alors à repasser sur ce dessin.) Il capte le ressenti (joie, peur, tristesse, colère, etc.) de la cible et la raison de ce ressenti. La perception est plus ou moins précise, au gré du meneur de jeu.
", + "ruleTags": [ + "farfadet", + "songes" + ] + } + }, + { + "name": "Osreilles", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le farfadet frotte les os d’une créature d’Edenia les uns contre les autres près de son oreille.
", + "notes": "", + "tradition": "farfadet", + "skillKey": "magie", + "polarity": "songes", + "cost": 1, + "costFormula": "", + "variableCost": false, + "preparation": "1 action unique", + "duration": "variable", + "range": "personnelle", + "area": "-", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le farfadet frotte les os d’une créature d’Edenia les uns contre les autres près de son oreille. Tant qu’il fait cela, son ouïe est plus affutée. Ses tests pour écouter obtiennent un bonus de +6.
", + "ruleTags": [ + "farfadet", + "songes" + ] + } + }, + { + "name": "Seconde peau", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage trace un tatouage protecteur.
", + "notes": "", + "tradition": "farfadet", + "skillKey": "magie", + "polarity": "songes", + "cost": 1, + "costFormula": "X", + "variableCost": true, + "preparation": "1 action unique", + "duration": "spéciale", + "range": "toucher", + "area": "un être vivant", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le personnage trace un tatouage protecteur. Les 2X prochains points de dégâts touchant l’enveloppe corporelle (par exemple un coup reçu en combat ou le choc d’une chute) sont encaissés par le tatouage et non par la cible. Le tatouage s’estompe au fur et à mesure qu’il reçoit les blessures, ou en une seule fois en cas de dégâts importants. Il disparaît quand il atteint 2X points de dégâts.
", + "ruleTags": [ + "farfadet", + "songes" + ] + } + }, + { + "name": "Armure obscurine", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage utilise son ombre comme armure.
", + "notes": "", + "tradition": "magie", + "skillKey": "magie", + "polarity": "cauchemar", + "cost": 1, + "costFormula": "X", + "variableCost": true, + "preparation": "1 action unique", + "duration": "1 combat", + "range": "personnelle", + "area": "-", + "stacking": "oui", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le personnage utilise son ombre comme armure. Celle-ci prend vie et virevolte autour du lanceur de sort afin de se porter au-devant des coups le visant. Elle est capable d’absorber X points de dégâts. Lorsque ce nombre est atteint, l’ombre se dissipe et le mage doit attendre le prochain crépuscule pour que celle-ci réapparaisse et puisse à nouveau servir d’armure. Le mage peut de nouveau lancer ce sort alors que l’armure est active afin de la régénérer. Il est à noter qu’un personnage sans ombre peut dégager un certain malaise social auprès de ses interlocuteurs.
", + "ruleTags": [ + "magie", + "cauchemar" + ] + } + }, + { + "name": "Aspect cauchemardesque", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage change l’apparence de son visage en lui faisant prendre un aspect contrefait et effrayant.
", + "notes": "", + "tradition": "magie", + "skillKey": "magie", + "polarity": "cauchemar", + "cost": 1, + "costFormula": "", + "variableCost": false, + "preparation": "1 action gratuite", + "duration": "1 h", + "range": "personnelle", + "area": "", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le personnage change l’apparence de son visage en lui faisant prendre un aspect contrefait et effrayant. Cela lui octroie un bonus de +3 pour ses tests de Commandement lorsqu’il souhaite intimider quelqu’un.
", + "ruleTags": [ + "magie", + "cauchemar" + ] + } + }, + { + "name": "Grand peuple", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage augmente la taille d’un être vivant de manière difforme.
", + "notes": "", + "tradition": "magie", + "skillKey": "magie", + "polarity": "cauchemar", + "cost": 1, + "costFormula": "", + "variableCost": false, + "preparation": "1 action libre", + "duration": "1 h", + "range": "toucher", + "area": "un être vivant", + "stacking": "oui", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le personnage augmente la taille d’un être vivant de manière difforme. Il gagne une taille. Éventuellement plusieurs tailles s’il bénéficie plusieurs fois de ce sortilège (au plus jusqu’à doubler sa taille). Le bénéficiaire acquiert immédiatement tous les bonus de sa nouvelle taille puis il les perd à la fin du sortilège.
", + "ruleTags": [ + "magie", + "cauchemar" + ] + } + }, + { + "name": "Lame de noirceur", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage fait apparaître une lame noire comme l’ébène dans sa main.
", + "notes": "", + "tradition": "magie", + "skillKey": "magie", + "polarity": "cauchemar", + "cost": 1, + "costFormula": "", + "variableCost": false, + "preparation": "1 action libre", + "duration": "jusqu’à la fin du prochain combat", + "range": "personnelle", + "area": "-", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le personnage fait apparaître une lame noire comme l’ébène dans sa main. Cette lame peut être donnée à un autre personnage, elle s’adapte alors à son porteur et inflige des dégâts de sa taille. L’arme pousse son porteur à un désir frénétique de combat au mépris de sa sécurité (Primes gratuites et obligatoires Blessure grave et Accélération ; pénalité obligatoire Danger). Cela n’empêche pas le porteur de choisir d’autres primes et pénalités en plus de celles imposées par la lame.
", + "ruleTags": [ + "magie", + "cauchemar" + ] + } + }, + { + "name": "Maître des ombres", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage se fond dans une zone d’ombre.
", + "notes": "", + "tradition": "magie", + "skillKey": "magie", + "polarity": "cauchemar", + "cost": 1, + "costFormula": "", + "variableCost": false, + "preparation": "1 action unique", + "duration": "spécial", + "range": "personnelle", + "area": "-", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le personnage se fond dans une zone d’ombre. Tant qu’il ne bouge pas, il est impossible à repérer – les tests de Sens se soldent par des échecs automatiques. S’il bouge, tout en restant dans l’ombre, les résultats finaux des tests de Discrétion augmentent de 3. Le sort est effectif tant que le mage reste dans la zone d’ombre.
", + "ruleTags": [ + "magie", + "cauchemar" + ] + } + }, + { + "name": "Myriapodie cauchemardesque", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le sort se matérialise sous forme de bras d’arthropodes qui poussent sur le corps du personnage.
", + "notes": "", + "tradition": "magie", + "skillKey": "magie", + "polarity": "cauchemar", + "cost": 1, + "costFormula": "", + "variableCost": false, + "preparation": "1 action unique", + "duration": "1 combat", + "range": "personnelle", + "area": "-", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le sort se matérialise sous forme de bras d’arthropodes qui poussent sur le corps du personnage. Ces membres permettent d’effectuer plusieurs attaques au corps à corps envers des adversaires engagés, accordant gratuitement la prime Attaques multiples au personnage. Les dégâts sont de (taille du personnage) - 1.
", + "ruleTags": [ + "magie", + "cauchemar" + ] + } + }, + { + "name": "Ombre démoniaque", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage choisit une ombre autour de lui : celle-ci prend forme et s’anime pour obéir à son créateur.
", + "notes": "", + "tradition": "magie", + "skillKey": "magie", + "polarity": "cauchemar", + "cost": 1, + "costFormula": "", + "variableCost": false, + "preparation": "1 action unique", + "duration": "1 action", + "range": "vue", + "area": "une ombre", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le personnage choisit une ombre autour de lui : celle-ci prend forme et s’anime pour obéir à son créateur. Elle peut effectuer 1 action avant de se dissiper (éventuellement elle peut attendre sur place jusqu’à ce qu’une condition à la réalisation de cette action indiquée par le personnage se manifeste). Quelle que soit sa taille, elle possède les mêmes compétences physiques que le mage. Si elle frappe, son attaque possède les mêmes caractéristiques – chances de réussir, dégâts, etc. – que celles du personnage.
", + "ruleTags": [ + "magie", + "cauchemar" + ] + } + }, + { + "name": "Papillons d’obsidienne", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage invoque une nuée de petits papillons d’obsidienne aux ailes coupantes.
", + "notes": "", + "tradition": "magie", + "skillKey": "magie", + "polarity": "cauchemar", + "cost": 1, + "costFormula": "", + "variableCost": false, + "preparation": "1 action unique", + "duration": "instantanée", + "range": "personnelle", + "area": "1 m", + "stacking": "-", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le personnage invoque une nuée de petits papillons d’obsidienne aux ailes coupantes. Ils volent en ligne droite sur 1 mètre, depuis le bras tendu du personnage, et infligent 1 point de dégâts à toute créature sur leur chemin, avant de se dissiper dans les airs.
", + "ruleTags": [ + "magie", + "cauchemar" + ] + } + }, + { + "name": "Ténèbres", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage créé une sphère de ténèbres qui obscurcie totalement la vision de la cible.
", + "notes": "", + "tradition": "magie", + "skillKey": "magie", + "polarity": "cauchemar", + "cost": 1, + "costFormula": "X", + "variableCost": true, + "preparation": "1 action libre", + "duration": "X rounds", + "range": "", + "area": "un être vivant", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le personnage créé une sphère de ténèbres qui obscurcie totalement la vision de la cible.
", + "ruleTags": [ + "magie", + "cauchemar" + ] + } + }, + { + "name": "Marche-ombre", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage entre dans une ombre pour ressortir d’une autre ombre visible à l’œil nu au moment du lancement du sort.
", + "notes": "", + "tradition": "magie", + "skillKey": "magie", + "polarity": "cauchemar", + "cost": 1, + "costFormula": "", + "variableCost": false, + "preparation": "1 action unique", + "duration": "instantanée", + "range": "personnelle", + "area": "-", + "stacking": "-", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le personnage entre dans une ombre pour ressortir d’une autre ombre visible à l’œil nu au moment du lancement du sort. Il ne doit pas y avoir d’obstacles physiques entre les deux ombres.
", + "ruleTags": [ + "magie", + "cauchemar" + ] + } + }, + { + "name": "Balafre", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Des plus morbides, ce sortilège nécessite que le farfadet se balafre le visage (1 point de dégâts).
", + "notes": "", + "tradition": "farfadet", + "skillKey": "magie", + "polarity": "cauchemar", + "cost": 1, + "costFormula": "", + "variableCost": false, + "preparation": "1 action unique", + "duration": "1 nuit", + "range": "personnelle", + "area": "-", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Des plus morbides, ce sortilège nécessite que le farfadet se balafre le visage (1 point de dégâts). Cela lui permet de changer d’apparence (y compris une apparence précise s’il a observé au préalable une personne quelques minutes). Le sortilège ne fonctionne que la nuit et son effet disparaît dès les premières lueurs de l’aube. Les vêtements et la corpulence ne changent pas mais le personnage peut ressembler à une personne de sexe opposé.
", + "ruleTags": [ + "farfadet", + "cauchemar" + ] + } + }, + { + "name": "Chimère", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le tatouage du farfadet représente des glyphes d’Obscurine qui permettent de déformer les probabilités.
", + "notes": "", + "tradition": "farfadet", + "skillKey": "magie", + "polarity": "cauchemar", + "cost": 1, + "costFormula": "", + "variableCost": false, + "preparation": "1 action unique", + "duration": "prochain 1 lors d’un test", + "range": "personnelle", + "area": "-", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le tatouage du farfadet représente des glyphes d’Obscurine qui permettent de déformer les probabilités. Lorsque le personnage obtient un 1 lors d’un test, il relance immédiatement le dé et remplace son échec par le nouveau score obtenu. Ce nouveau résultat est appliqué même si c’est à nouveau un 1.
", + "ruleTags": [ + "farfadet", + "cauchemar" + ] + } + }, + { + "name": "Dards osseux", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le farfadet s’automutile – 1 point de dégâts – afin de se badigeonner de son sang.
", + "notes": "", + "tradition": "farfadet", + "skillKey": "magie", + "polarity": "cauchemar", + "cost": 1, + "costFormula": "", + "variableCost": false, + "preparation": "1 action libre", + "duration": "1 combat", + "range": "personnelle", + "area": "-", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le farfadet s’automutile – 1 point de dégâts – afin de se badigeonner de son sang. Sur toutes les surfaces du corps ainsi recouvertes, poussent des os pointus empoisonnés. Tout adversaire au contact du farfadet s’égratigne. Son joueur doit faire un test d’Endurance / 0 à la fin de chaque round sous peine que son personnage subisse 1 point de dégâts à cause du poison qui provoque une fièvre virulente et une douleur atroces. Les effets du poison cessent dès qu’un de ces tests est réussi.
", + "ruleTags": [ + "farfadet", + "cauchemar" + ] + } + }, + { + "name": "Poignarme", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage dessine une lame sur l’avant-bras du bénéficiaire.
", + "notes": "", + "tradition": "farfadet", + "skillKey": "magie", + "polarity": "cauchemar", + "cost": 1, + "costFormula": "X", + "variableCost": true, + "preparation": "1 action unique", + "duration": "X combats", + "range": "toucher", + "area": "un être vivant", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le personnage dessine une lame sur l’avant-bras du bénéficiaire. Dès que celui-ci le souhaite, sa main se transforme en cette lame en une action libre. Elle inflige des dégâts de sa taille +1. Le tatouage s’estompe au fur et à mesure qu’il est utilisé dans les combats : il disparaît après X combats.
", + "ruleTags": [ + "farfadet", + "cauchemar" + ] + } + }, + { + "name": "Scarifications morbides", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Des cicatrices en triangles concentriques renforcent la peau et atténuent la douleur provoquée par les blessures.
", + "notes": "", + "tradition": "farfadet", + "skillKey": "magie", + "polarity": "cauchemar", + "cost": 1, + "costFormula": "", + "variableCost": false, + "preparation": "1 action unique", + "duration": "1 combat", + "range": "personnelle", + "area": "-", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Des cicatrices en triangles concentriques renforcent la peau et atténuent la douleur provoquée par les blessures. Le farfadet ne subit pas le prochain modificateur d’initiative dû aux blessures, puis tout ceux suivant dans le même combat. Tous les autres effets des blessures sont appliqués
", + "ruleTags": [ + "farfadet", + "cauchemar" + ] + } + }, + { + "name": "Bien-être", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage développe une aura de bien-être qui apaise les personnes de son entourage.
", + "notes": "", + "tradition": "onirologie", + "skillKey": "onirologie", + "polarity": "songes", + "cost": 1, + "costFormula": "X", + "variableCost": true, + "preparation": "1 réaction", + "duration": "1 round", + "range": "vue", + "area": "X êtres vivants", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le personnage développe une aura de bien-être qui apaise les personnes de son entourage. L’aura leur octroie un bonus de +3 à leurs tests de Volonté.
", + "ruleTags": [ + "onirologie", + "songes" + ] + } + }, + { + "name": "Bon de sept lieues", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage utilise les résidus de Néphertine qui teintent la réalité pour plier l’espace à sa volonté.
", + "notes": "", + "tradition": "onirologie", + "skillKey": "onirologie", + "polarity": "songes", + "cost": 1, + "costFormula": "", + "variableCost": false, + "preparation": "1 action libre", + "duration": "instantanée", + "range": "vue", + "area": "un être vivant", + "stacking": "-", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le personnage utilise les résidus de Néphertine qui teintent la réalité pour plier l’espace à sa volonté. Il peut ainsi se téléporter, ou téléporter un être consentant, jusqu’à un endroit qu’il voit.
", + "ruleTags": [ + "onirologie", + "songes" + ] + } + }, + { + "name": "Charismagorie", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage est magnifié aux yeux de la cible et bénéficie d’un bonus de +6 à toutes ses interactions sociales avec elle.
", + "notes": "", + "tradition": "onirologie", + "skillKey": "onirologie", + "polarity": "songes", + "cost": 1, + "costFormula": "", + "variableCost": false, + "preparation": "1 action libre", + "duration": "1 h", + "range": "1 m", + "area": "un être vivant", + "stacking": "-", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le personnage est magnifié aux yeux de la cible et bénéficie d’un bonus de +6 à toutes ses interactions sociales avec elle.
", + "ruleTags": [ + "onirologie", + "songes" + ] + } + }, + { + "name": "Corde de la réalité", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage fait apparaître une corde lumineuse qu’il lance jusqu’à sa cible.
", + "notes": "", + "tradition": "onirologie", + "skillKey": "onirologie", + "polarity": "songes", + "cost": 1, + "costFormula": "", + "variableCost": false, + "preparation": "1 action libre", + "duration": "1 réaction", + "range": "vue", + "area": "un être vivant plongé dans le Néphertine", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le personnage fait apparaître une corde lumineuse qu’il lance jusqu’à sa cible. Si celle-ci parvient à s’en emparer – réaction nécessitant un test de Volonté –, elle voit le Néphertine s’estomper et la réalité s’imposer. Elle obtient bonus de +6 au test de Volonté.
", + "ruleTags": [ + "onirologie", + "songes" + ] + } + }, + { + "name": "Hallucination paralytique", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage fait vivre à la cible une hallucination.
", + "notes": "", + "tradition": "onirologie", + "skillKey": "onirologie", + "polarity": "songes", + "cost": 2, + "costFormula": "", + "variableCost": false, + "preparation": "1 action unique", + "duration": "1 round", + "range": "vue", + "area": "un être vivant", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le personnage fait vivre à la cible une hallucination. La cible croit vivre une scène décrite par le lanceur de sort mais en réalité elle passe son round sans agir. Si elle doit effectuer une réaction, le sortilège cesse.
", + "ruleTags": [ + "onirologie", + "songes" + ] + } + }, + { + "name": "Illusion féérique", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage fait apparaître une illusion visuelle immobile réaliste, au plus de la même taille que lui-même.
", + "notes": "", + "tradition": "onirologie", + "skillKey": "onirologie", + "polarity": "songes", + "cost": 1, + "costFormula": "X", + "variableCost": true, + "preparation": "1 action libre", + "duration": "1 h", + "range": "vue", + "area": "X cibles", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le personnage fait apparaître une illusion visuelle immobile réaliste, au plus de la même taille que lui-même. Elle disparaît au terme du sortilège ou si elle est touchée. Le sortilège peut affecter X cibles qui peuvent s’apercevoir de la supercherie en réussissant un test d’Intellect / -3.
", + "ruleTags": [ + "onirologie", + "songes" + ] + } + }, + { + "name": "Incepteur", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage fait plier pendant une courte période la volonté d’une personne qui obéit à une suggestion qu’elle lui fait.
", + "notes": "", + "tradition": "onirologie", + "skillKey": "onirologie", + "polarity": "songes", + "cost": 1, + "costFormula": "X", + "variableCost": true, + "preparation": "1 action libre", + "duration": "1 réaction", + "range": "vue", + "area": "un être vivant", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le personnage fait plier pendant une courte période la volonté d’une personne qui obéit à une suggestion qu’elle lui fait. La victime obéit à une suggestion simple (une phrase avec un seul verbe) pendant la prochaine minute mais elle ne prend aucun risque vital, ni pour elle ni pour autrui. Elle peut résister à cette injonction en réussissant un test de Volonté / -X. veau. Pour utiliser ce sortilège, le personnage doit pouvoir se faire entendre de sa cible et parler sa langue. La victime est consciente de ce qui lui arrive.
", + "ruleTags": [ + "onirologie", + "songes" + ] + } + }, + { + "name": "Mille-et-un masques", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "L’apparence physique du magicien change.
", + "notes": "", + "tradition": "onirologie", + "skillKey": "onirologie", + "polarity": "songes", + "cost": 1, + "costFormula": "", + "variableCost": false, + "preparation": "1 action unique", + "duration": "1 h", + "range": "personnelle", + "area": "-", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "L’apparence physique du magicien change. Il garde le même sexe, la même race et la même morphologie, mais devient méconnaissable.
", + "ruleTags": [ + "onirologie", + "songes" + ] + } + }, + { + "name": "Phare néphertide", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage fait briller les bords de tout Néphertine aux yeux de tout membre du petit Peuple.
", + "notes": "", + "tradition": "onirologie", + "skillKey": "onirologie", + "polarity": "songes", + "cost": 1, + "costFormula": "", + "variableCost": false, + "preparation": "1 action libre", + "duration": "instantané", + "range": "vue", + "area": "Néphertine", + "stacking": "-", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le personnage fait briller les bords de tout Néphertine aux yeux de tout membre du petit Peuple. Cela permet notamment d’éviter ces zones.
", + "ruleTags": [ + "onirologie", + "songes" + ] + } + }, + { + "name": "Souvenirs réifiés", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage peut lire les derniers « souvenirs » liés à un objet.
", + "notes": "", + "tradition": "onirologie", + "skillKey": "onirologie", + "polarity": "songes", + "cost": 1, + "costFormula": "", + "variableCost": false, + "preparation": "1 action unique", + "duration": "1 h", + "range": "toucher", + "area": "1 objet", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le personnage peut lire les derniers « souvenirs » liés à un objet. Le dernier moment important dans lequel a été utilisé l’objet se matérialise autour de lui. Si le personnage tient la main d’une personne pendant ce sortilège, celle-ci voit également le souvenir.
", + "ruleTags": [ + "onirologie", + "songes" + ] + } + }, + { + "name": "Bête noire", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage fait ressortir la part sombre d’une cible, sous forme d’une créature noire informe qui s’extirpe laborieusement de celle-ci.
", + "notes": "", + "tradition": "onirologie", + "skillKey": "onirologie", + "polarity": "cauchemar", + "cost": 3, + "costFormula": "", + "variableCost": false, + "preparation": "1 action unique", + "duration": "1 h", + "range": "vue", + "area": "un être vivant", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le personnage fait ressortir la part sombre d’une cible, sous forme d’une créature noire informe qui s’extirpe laborieusement de celle-ci. La créature attaque au round suivant la créature vivante la plus proche d’elle, à part celle dont elle est issue. La bête possède les mêmes compétences physiques, la même initiative et le même nombre de points de vie que la cible dont elle s’arrache au moment où elle en sort.
", + "ruleTags": [ + "onirologie", + "cauchemar" + ] + } + }, + { + "name": "Brumes scélérates", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le lanceur du sortilège commande à l’humidité ambiante de se condenser afin de créer un brouillard épais sur un lieu qui doit se trouver dans son champ de vision.
", + "notes": "", + "tradition": "onirologie", + "skillKey": "onirologie", + "polarity": "cauchemar", + "cost": 1, + "costFormula": "", + "variableCost": false, + "preparation": "1 action unique", + "duration": "jusqu’à la prochaine aube", + "range": "vue", + "area": "10 m", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le lanceur du sortilège commande à l’humidité ambiante de se condenser afin de créer un brouillard épais sur un lieu qui doit se trouver dans son champ de vision. Un malus de -3 s’applique alors à toute action impliquant la perception visuelle dans la zone.
", + "ruleTags": [ + "onirologie", + "cauchemar" + ] + } + }, + { + "name": "Contamination ténébreuse", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage rend sa victime violemment allergique à la lumière.
", + "notes": "", + "tradition": "onirologie", + "skillKey": "onirologie", + "polarity": "cauchemar", + "cost": 2, + "costFormula": "", + "variableCost": false, + "preparation": "1 action unique", + "duration": "1 h", + "range": "vue", + "area": "un être vivant", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le personnage rend sa victime violemment allergique à la lumière. Chaque round exposé à la lumière inflige 1 point de dégâts à la cible qui a l’impression de brûler. La victime doit protéger toutes les parties de son corps, ou rester à l’ombre, si elle ne veut pas subir ces dommages.
", + "ruleTags": [ + "onirologie", + "cauchemar" + ] + } + }, + { + "name": "Corruption néphertide", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage transforme 1 fil de Songes en fil de Cauchemar.
", + "notes": "", + "tradition": "onirologie", + "skillKey": "onirologie", + "polarity": "cauchemar", + "cost": 1, + "costFormula": "", + "variableCost": false, + "preparation": "1 action unique", + "duration": "instantanée", + "range": "vue", + "area": "1 fil de Songes", + "stacking": "-", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le personnage transforme 1 fil de Songes en fil de Cauchemar.
", + "ruleTags": [ + "onirologie", + "cauchemar" + ] + } + }, + { + "name": "Don de noirceur", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "un test de Volonté / 0 si elle n’est pas consentante.
", + "notes": "", + "tradition": "onirologie", + "skillKey": "onirologie", + "polarity": "cauchemar", + "cost": 1, + "costFormula": "X", + "variableCost": true, + "preparation": "1 action unique", + "duration": "instantanée", + "range": "toucher", + "area": "un être vivant", + "stacking": "-", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "un test de Volonté / 0 si elle n’est pas consentante. Ce lien établit, permet au lanceur de sort de transférer X points de Cauchemar à la cible.
", + "ruleTags": [ + "onirologie", + "cauchemar" + ] + } + }, + { + "name": "Explosion d’effroi", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage prend le contrôle d’une vague de Néphertine pour la rendre particulièrement agressive et la répand autour de lui, plongeant ses victimes dans des visions terrifiantes.
", + "notes": "", + "tradition": "onirologie", + "skillKey": "onirologie", + "polarity": "cauchemar", + "cost": 1, + "costFormula": "X", + "variableCost": true, + "preparation": "1 action libre", + "duration": "instantanée", + "range": "1 cm", + "area": "adversaires dans le Néphertine", + "stacking": "-", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le personnage prend le contrôle d’une vague de Néphertine pour la rendre particulièrement agressive et la répand autour de lui, plongeant ses victimes dans des visions terrifiantes. Elles pensent être attaquées par une multitude de petites créatures cauchemardesques, et subissent 2 point de dégâts si en cas d’échec à un test de Volonté / -X, 1 point en cas de réussite.
", + "ruleTags": [ + "onirologie", + "cauchemar" + ] + } + }, + { + "name": "Illusion cauchemardesque", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage désigne une cible qui a l’impression de voir apparaître et fondre sur elle une de ses grandes frayeurs.
", + "notes": "", + "tradition": "onirologie", + "skillKey": "onirologie", + "polarity": "cauchemar", + "cost": 2, + "costFormula": "", + "variableCost": false, + "preparation": "1 action libre", + "duration": "variable", + "range": "vue", + "area": "un être vivant", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le personnage désigne une cible qui a l’impression de voir apparaître et fondre sur elle une de ses grandes frayeurs. Son cœur s’emballe et elle perd 1 point de vie. Elle prend conscience de l’irréalité de la menace au début de son prochain tour de jeu ou avant si elle doit effectuer une réaction. Néanmoins, encore choquée, tous ses tests subissent un malus de -3 jusqu’à la fin du round.
", + "ruleTags": [ + "onirologie", + "cauchemar" + ] + } + }, + { + "name": "Oubli", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage déclame une incantation hypnotique afin de pénétrer dans l’esprit de sa victime et y détruire un souvenir récent, de quelques minutes à un mois, au choix du mage.
", + "notes": "", + "tradition": "onirologie", + "skillKey": "onirologie", + "polarity": "cauchemar", + "cost": 1, + "costFormula": "X", + "variableCost": true, + "preparation": "1 action unique", + "duration": "instantanée", + "range": "voix", + "area": "un être vivant", + "stacking": "-", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le personnage déclame une incantation hypnotique afin de pénétrer dans l’esprit de sa victime et y détruire un souvenir récent, de quelques minutes à un mois, au choix du mage. La cible peut résister à la destruction du souvenir en réussissant un test de Volonté / -X. Pour utiliser ce sort, le mage doit pouvoir se faire entendre de sa cible et parler sa langue. La victime est conscience de son amnésie.
", + "ruleTags": [ + "onirologie", + "cauchemar" + ] + } + }, + { + "name": "Souvenir traumatique", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage implante une scène horriblement traumatisante dans l’esprit de la victime qui la perçoit comme un souvenir, avec toutes les conséquences psychologiques qui en découlent.
", + "notes": "", + "tradition": "onirologie", + "skillKey": "onirologie", + "polarity": "cauchemar", + "cost": 1, + "costFormula": "X", + "variableCost": true, + "preparation": "1 action unique", + "duration": "1 h", + "range": "vue", + "area": "un être vivant", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le personnage implante une scène horriblement traumatisante dans l’esprit de la victime qui la perçoit comme un souvenir, avec toutes les conséquences psychologiques qui en découlent. Elle est pénalisée d’un malus de -X pour tous ses tests sociaux.
", + "ruleTags": [ + "onirologie", + "cauchemar" + ] + } + }, + { + "name": "Verbe des sourds", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage discute avec une personne endormie et la contraint à révéler un de ses secrets ou connaissances.
", + "notes": "", + "tradition": "onirologie", + "skillKey": "onirologie", + "polarity": "cauchemar", + "cost": 1, + "costFormula": "X", + "variableCost": true, + "preparation": "1 action unique", + "duration": "1 discussion", + "range": "toucher", + "area": "un être vivant endormi", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le personnage discute avec une personne endormie et la contraint à révéler un de ses secrets ou connaissances. Pendant la durée du sortilège, la victime est plongée dans un sommeil tellement profond que seules des secousses ou un son particulièrement puissant peuvent la réveiller. Un test de Volonté / -X permet à la victime de résister à cet interrogatoire.
", + "ruleTags": [ + "onirologie", + "cauchemar" + ] + } + }, + { + "name": "Croquis croquant", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage esquisse un objet ou un être vivant sur une surface et ce qui est dessiné prend vie.
", + "notes": "", + "tradition": "chimerisme", + "skillKey": "chimerisme", + "polarity": "songes", + "cost": 3, + "costFormula": "", + "variableCost": false, + "preparation": "5 actions uniques", + "duration": "1 h", + "range": "toucher", + "area": "variable", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [ + "Peinture", + "Dessin" + ], + "effectsText": "Le personnage esquisse un objet ou un être vivant sur une surface et ce qui est dessiné prend vie. Les statistiques de la créature sont identiques à celles du mage. Si c’est un objet, il a les mêmes caractéristiques qu’un objet normal.
", + "ruleTags": [ + "chimerisme", + "songes" + ] + } + }, + { + "name": "Danse empathique", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage hypnotise X victimes pendant quelque instant, à travers une danse envoûtante.
", + "notes": "", + "tradition": "chimerisme", + "skillKey": "chimerisme", + "polarity": "songes", + "cost": 1, + "costFormula": "X", + "variableCost": true, + "preparation": "1 action unique", + "duration": "10 mn", + "range": "vue", + "area": "", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [ + "Danse" + ], + "effectsText": "Le personnage hypnotise X victimes pendant quelque instant, à travers une danse envoûtante. Cela lui permet de pénétrer dans les esprits et de connaître les émotions, mais pas les pensées.
", + "ruleTags": [ + "chimerisme", + "songes" + ] + } + }, + { + "name": "Inspiration sibylline", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage décrit sous forme de vers une question qu’il se pose.
", + "notes": "", + "tradition": "chimerisme", + "skillKey": "chimerisme", + "polarity": "songes", + "cost": 1, + "costFormula": "", + "variableCost": false, + "preparation": "1 action unique", + "duration": "instantanée", + "range": "personnelle", + "area": "-", + "stacking": "-", + "requiredDomains": [], + "artsDomains": [ + "Poésie" + ], + "effectsText": "Le personnage décrit sous forme de vers une question qu’il se pose. Il s’en inspire pour en tirer quelques phrases ou mots sibyllins – déterminés par le meneur de jeu.
", + "ruleTags": [ + "chimerisme", + "songes" + ] + } + }, + { + "name": "Ode à la guerre", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage chante, raconte ou déclame une ode à la guerre juste avant le combat.
", + "notes": "", + "tradition": "chimerisme", + "skillKey": "chimerisme", + "polarity": "songes", + "cost": 1, + "costFormula": "X", + "variableCost": true, + "preparation": "1 action unique", + "duration": "le prochain combat", + "range": "voix", + "area": "X alliés", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [ + "Chant", + "Conte", + "Poésie" + ], + "effectsText": "Le personnage chante, raconte ou déclame une ode à la guerre juste avant le combat. Il parle des combats d’antan, des destins héroïques, de ceux qui forgèrent le monde avec leurs lames. Son verbe gonfle le cœur de ceux qui partent à la mort. L’assistance se voit capable d’ignorer X points de dégâts avant que les dommages s’appliquent normalement.
", + "ruleTags": [ + "chimerisme", + "songes" + ] + } + }, + { + "name": "Ode à la pierre", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage entame un air de l’ancien temps, faisant référence aux golems de pierre.
", + "notes": "", + "tradition": "chimerisme", + "skillKey": "chimerisme", + "polarity": "songes", + "cost": 1, + "costFormula": "X", + "variableCost": true, + "preparation": "1 action unique", + "duration": "X attaques", + "range": "personnelle", + "area": "-", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [ + "Chant", + "Musique" + ], + "effectsText": "Le personnage entame un air de l’ancien temps, faisant référence aux golems de pierre. Cela donne à sa peau la résistance de la roche lui apportant une protection de 3 contre X attaques.
", + "ruleTags": [ + "chimerisme", + "songes" + ] + } + }, + { + "name": "Parole animale", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Les animaux comprennent ce que dit le personnage.
", + "notes": "", + "tradition": "chimerisme", + "skillKey": "chimerisme", + "polarity": "songes", + "cost": 1, + "costFormula": "", + "variableCost": false, + "preparation": "1 action libre", + "duration": "10 mn", + "range": "vue", + "area": "animaux", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [ + "Musique" + ], + "effectsText": "Les animaux comprennent ce que dit le personnage. Lui-même interprète avec facilité leurs expressions.
", + "ruleTags": [ + "chimerisme", + "songes" + ] + } + }, + { + "name": "Paroles muettes", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage chante ou joue de la musique auprès d’une cible incapable de parler (muette, blessée, malade, mourante, etc.).
", + "notes": "", + "tradition": "chimerisme", + "skillKey": "chimerisme", + "polarity": "songes", + "cost": 1, + "costFormula": "X", + "variableCost": true, + "preparation": "1 action unique", + "duration": "1 conversation", + "range": "1 cm", + "area": "un être pensant incapable de parler", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [ + "Chant", + "Musique" + ], + "effectsText": "Le personnage chante ou joue de la musique auprès d’une cible incapable de parler (muette, blessée, malade, mourante, etc.). Il développe une conversation empathique avec elle. Le personnage peut obtenir des informations spécifiques de son interlocuteur si celui-ci l’accepte : X renseignements. Celles-ci peuvent être plus ou moins claires au gré du meneur de jeu.
", + "ruleTags": [ + "chimerisme", + "songes" + ] + } + }, + { + "name": "Songe d’une nuit des nymphes", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage chante une chanson paillarde, plongeant son auditoire dans un délire sensuel partagé.
", + "notes": "", + "tradition": "chimerisme", + "skillKey": "chimerisme", + "polarity": "songes", + "cost": 1, + "costFormula": "X", + "variableCost": true, + "preparation": "1 action unique", + "duration": "1 h", + "range": "voix", + "area": "X individus", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [ + "Chant" + ], + "effectsText": "Le personnage chante une chanson paillarde, plongeant son auditoire dans un délire sensuel partagé. Les membres de l’assistance peuvent résister avec un test de Volonté / -X.
", + "ruleTags": [ + "chimerisme", + "songes" + ] + } + }, + { + "name": "Voyage", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage peint ou dessine un lieu.
", + "notes": "", + "tradition": "chimerisme", + "skillKey": "chimerisme", + "polarity": "songes", + "cost": 3, + "costFormula": "3X", + "variableCost": true, + "preparation": "10 actions uniques", + "duration": "instantanée", + "range": "toucher", + "area": "", + "stacking": "-", + "requiredDomains": [], + "artsDomains": [ + "Peinture", + "Dessin" + ], + "effectsText": "Le personnage peint ou dessine un lieu. Il y est transporté, ainsi que X personnes présentes à ses côtés. Attention le lieu doit réellement exister et avoir été vu au moins une fois par le personnage.
", + "ruleTags": [ + "chimerisme", + "songes" + ] + } + }, + { + "name": "Voyage onirique", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le chant ou la musique du personnage conduit X membres du Petit Peuple qui l’écoutent et qui sont consentants dans un lieu déjà vu par le personnage ou en Edenia, à Triklir.
", + "notes": "", + "tradition": "chimerisme", + "skillKey": "chimerisme", + "polarity": "songes", + "cost": 1, + "costFormula": "X", + "variableCost": true, + "preparation": "X actions uniques", + "duration": "1 h", + "range": "voix ou son", + "area": "X membres du Petit Peuple", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [ + "Chant", + "Musique" + ], + "effectsText": "Le chant ou la musique du personnage conduit X membres du Petit Peuple qui l’écoutent et qui sont consentants dans un lieu déjà vu par le personnage ou en Edenia, à Triklir. Les corps des voyageurs demeurent sur place, assoupis. Les doubles des voyageurs apparaissent dans l’autre lieu. Ils possèdent les mêmes caractéristiques et possessions que les enveloppes réelles. Tout ce qui arrive à ces doubles est répercuté sur les corps et biens originaux. (Le doux rêveur peut aller avec eux ou rester sur place.)
", + "ruleTags": [ + "chimerisme", + "songes" + ] + } + }, + { + "name": "Chant du parjure", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage entonne une complainte morbide qui empêche la cible d’utiliser des Songes.
", + "notes": "", + "tradition": "chimerisme", + "skillKey": "chimerisme", + "polarity": "cauchemar", + "cost": 2, + "costFormula": "", + "variableCost": false, + "preparation": "1 round", + "duration": "tant que le personnage chante", + "range": "voix", + "area": "un être vivant", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [ + "Chant" + ], + "effectsText": "Le personnage entonne une complainte morbide qui empêche la cible d’utiliser des Songes.
", + "ruleTags": [ + "chimerisme", + "cauchemar" + ] + } + }, + { + "name": "Détournement corrupteur", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage danse à l’envers à la fin du lancement d’un sortilège, ce qui l’annule juste avant qu’il prenne effet.
", + "notes": "", + "tradition": "chimerisme", + "skillKey": "chimerisme", + "polarity": "cauchemar", + "cost": 0, + "costFormula": "identique au sortilège cible", + "variableCost": true, + "preparation": "1 réaction", + "duration": "instantanée", + "range": "vue", + "area": "1 sortilège", + "stacking": "-", + "requiredDomains": [], + "artsDomains": [ + "Danse" + ], + "effectsText": "Le personnage danse à l’envers à la fin du lancement d’un sortilège, ce qui l’annule juste avant qu’il prenne effet. (Il n’est pas nécessaire que le personnage connaisse le sortilège cible.)
", + "ruleTags": [ + "chimerisme", + "cauchemar" + ] + } + }, + { + "name": "Hurlement bestial", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage pousse un cri abominable qui terrorise les animaux.
", + "notes": "", + "tradition": "chimerisme", + "skillKey": "chimerisme", + "polarity": "cauchemar", + "cost": 1, + "costFormula": "X", + "variableCost": true, + "preparation": "1 action libre", + "duration": "instantanée", + "range": "voix", + "area": "X animaux", + "stacking": "-", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Le personnage pousse un cri abominable qui terrorise les animaux. Ceux-ci fuient, ou attaquent s’ils sont acculés.
", + "ruleTags": [ + "chimerisme", + "cauchemar" + ] + } + }, + { + "name": "Lamentation de l’ombre", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage entonne un air de musique ou un chant angoissant qui effraie les personnes qui l’entendent.
", + "notes": "", + "tradition": "chimerisme", + "skillKey": "chimerisme", + "polarity": "cauchemar", + "cost": 1, + "costFormula": "X", + "variableCost": true, + "preparation": "1 action unique", + "duration": "chant ou musique", + "range": "voix ou son", + "area": "X êtres vivants", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [ + "Chant", + "Musique" + ], + "effectsText": "Le personnage entonne un air de musique ou un chant angoissant qui effraie les personnes qui l’entendent. Les cibles subissent un malus de 1 à leurs tests. Si le chant est interrompu, le sortilège cesse immédiatement.
", + "ruleTags": [ + "chimerisme", + "cauchemar" + ] + } + }, + { + "name": "Litanie fatale", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage utilise les accords de son instrument comme autant de coups de taille et d’estoc.
", + "notes": "", + "tradition": "chimerisme", + "skillKey": "chimerisme", + "polarity": "cauchemar", + "cost": 3, + "costFormula": "", + "variableCost": false, + "preparation": "1 action unique", + "duration": "Musique", + "range": "son", + "area": "un être vivant", + "stacking": "-", + "requiredDomains": [], + "artsDomains": [ + "Musique" + ], + "effectsText": "Le personnage utilise les accords de son instrument comme autant de coups de taille et d’estoc. Tous ses adversaires subissent 1 point de dégâts à la fin de chaque round tant qu’ils entendent distinctement la musique.
", + "ruleTags": [ + "chimerisme", + "cauchemar" + ] + } + }, + { + "name": "Mélopée funeste", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Celle-ci cesse ses activités pour suivre le musicien où qu’il aille tant qu’il continue à chanter.
", + "notes": "", + "tradition": "chimerisme", + "skillKey": "chimerisme", + "polarity": "cauchemar", + "cost": 1, + "costFormula": "X", + "variableCost": true, + "preparation": "1 action unique", + "duration": "chant", + "range": "voix", + "area": "X êtres vivants", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [ + "Chant" + ], + "effectsText": "Celle-ci cesse ses activités pour suivre le musicien où qu’il aille tant qu’il continue à chanter. Les victimes peuvent résister à cette obligation en cas de réussite d’un test de Volonté / -X ou d’une situation occasionnant une réaction. Lorsque le sortilège cesse, les victimes ont l’impression de sortir d’un vague cauchemar dont elles sont incapables de se souvenir.
", + "ruleTags": [ + "chimerisme", + "cauchemar" + ] + } + }, + { + "name": "Mort des sens", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage interprète un ancien chant maudit d’Obscurine qui fait perdre un sens à sa victime.
", + "notes": "", + "tradition": "chimerisme", + "skillKey": "chimerisme", + "polarity": "cauchemar", + "cost": 3, + "costFormula": "", + "variableCost": false, + "preparation": "1 action unique", + "duration": "chant", + "range": "voix", + "area": "un être vivant", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [ + "Chant" + ], + "effectsText": "Le personnage interprète un ancien chant maudit d’Obscurine qui fait perdre un sens à sa victime. Le personnage choisit le sens dont il souhaite priver sa cible. Si le chant est interrompu, le sortilège cesse immédiatement.
", + "ruleTags": [ + "chimerisme", + "cauchemar" + ] + } + }, + { + "name": "Portrait de sang", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage prend le temps de peindre ou de dessiner le visage de sa victime.
", + "notes": "", + "tradition": "chimerisme", + "skillKey": "chimerisme", + "polarity": "cauchemar", + "cost": 2, + "costFormula": "", + "variableCost": false, + "preparation": "10 actions uniques", + "duration": "24 h", + "range": "toucher", + "area": "-", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [ + "Peinture", + "Dessin" + ], + "effectsText": "Le personnage prend le temps de peindre ou de dessiner le visage de sa victime. Au moment du lancement du sortilège, toute modification du portrait sera reportée sur la victime, qui ne sera plus reconnue par ses proches. Le sortilège ne modifie que le visage de la cible.
", + "ruleTags": [ + "chimerisme", + "cauchemar" + ] + } + }, + { + "name": "Souvenir mensonger", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage raconte un événement négatif et est capable de faire croire à la victime qu’elle a bien vécu ce souvenir si son joueur rate un test de Volonté / 0.
", + "notes": "", + "tradition": "chimerisme", + "skillKey": "chimerisme", + "polarity": "cauchemar", + "cost": 2, + "costFormula": "", + "variableCost": false, + "preparation": "1 action unique", + "duration": "1 h", + "range": "voix", + "area": "un être vivant", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [ + "Conte" + ], + "effectsText": "Le personnage raconte un événement négatif et est capable de faire croire à la victime qu’elle a bien vécu ce souvenir si son joueur rate un test de Volonté / 0.
", + "ruleTags": [ + "chimerisme", + "cauchemar" + ] + } + }, + { + "name": "Temps antiques", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Le personnage chante une très ancienne chanson qui parle de la fuite du temps.
", + "notes": "", + "tradition": "chimerisme", + "skillKey": "chimerisme", + "polarity": "cauchemar", + "cost": 1, + "costFormula": "X", + "variableCost": true, + "preparation": "1 action unique", + "duration": "instantanée", + "range": "voix ou son", + "area": "X êtres vivants", + "stacking": "-", + "requiredDomains": [], + "artsDomains": [ + "Chant", + "Musique" + ], + "effectsText": "Le personnage chante une très ancienne chanson qui parle de la fuite du temps. Ses accords ralentissent. À chaque note qui résonne, le temps semble ralentir également. Les adversaires du personnage diminuent leur initiative de 4.
", + "ruleTags": [ + "chimerisme", + "cauchemar" + ] + } + }, + { + "name": "Œil de l’esprit", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Pour réaliser ce sortilège, le personnage doit avoir ses deux yeux cousus et un œil tatoué dans sa main.
", + "notes": "", + "tradition": "farfadet", + "skillKey": "chimerisme", + "polarity": "cauchemar", + "cost": 1, + "costFormula": "", + "variableCost": false, + "preparation": "1 action libre", + "duration": "instantanée", + "range": "personnelle", + "area": "un être pensant", + "stacking": "-", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Pour réaliser ce sortilège, le personnage doit avoir ses deux yeux cousus et un œil tatoué dans sa main. Le personnage pointe le tatouage vers un individu. Il capte l’une de ses pensées, au gré du meneur de jeu.
", + "ruleTags": [ + "farfadet", + "soeur-effroi", + "cauchemar" + ] + } + }, + { + "name": "Œil du lointain", + "type": "sortilege", + "img": "icons/svg/daze.svg", + "system": { + "description": "Pour réaliser ce sortilège, le personnage doit avoir ses deux yeux cousus et un œil tatoué dans sa main.
", + "notes": "", + "tradition": "farfadet", + "skillKey": "chimerisme", + "polarity": "cauchemar", + "cost": 1, + "costFormula": "", + "variableCost": false, + "preparation": "1 action libre", + "duration": "10 mn", + "range": "personnelle", + "area": "vue", + "stacking": "non", + "requiredDomains": [], + "artsDomains": [], + "effectsText": "Pour réaliser ce sortilège, le personnage doit avoir ses deux yeux cousus et un œil tatoué dans sa main. Le personnage pointe le tatouage vers un point, y compris lointain. Il le voit comme s’il était à proximité. Il peut déplacer sa main pour examiner ainsi différents points.
", + "ruleTags": [ + "farfadet", + "soeur-effroi", + "cauchemar" + ] + } + } +] diff --git a/packs/armes/000003.log b/packs/armes/000003.log new file mode 100644 index 0000000..582ccc8 Binary files /dev/null and b/packs/armes/000003.log differ diff --git a/packs/armes/CURRENT b/packs/armes/CURRENT new file mode 100644 index 0000000..1a84852 --- /dev/null +++ b/packs/armes/CURRENT @@ -0,0 +1 @@ +MANIFEST-000002 diff --git a/packs/armes/LOCK b/packs/armes/LOCK new file mode 100644 index 0000000..e69de29 diff --git a/packs/armes/LOG b/packs/armes/LOG new file mode 100644 index 0000000..758a6be --- /dev/null +++ b/packs/armes/LOG @@ -0,0 +1 @@ +2026/05/03-20:13:00.428719 7f25c15fe6c0 Delete type=3 #1 diff --git a/packs/armes/MANIFEST-000002 b/packs/armes/MANIFEST-000002 new file mode 100644 index 0000000..bbbc585 Binary files /dev/null and b/packs/armes/MANIFEST-000002 differ diff --git a/packs/armures/000003.log b/packs/armures/000003.log new file mode 100644 index 0000000..b3fad41 Binary files /dev/null and b/packs/armures/000003.log differ diff --git a/packs/armures/CURRENT b/packs/armures/CURRENT new file mode 100644 index 0000000..1a84852 --- /dev/null +++ b/packs/armures/CURRENT @@ -0,0 +1 @@ +MANIFEST-000002 diff --git a/packs/armures/LOCK b/packs/armures/LOCK new file mode 100644 index 0000000..e69de29 diff --git a/packs/armures/LOG b/packs/armures/LOG new file mode 100644 index 0000000..671d2ce --- /dev/null +++ b/packs/armures/LOG @@ -0,0 +1 @@ +2026/05/03-20:13:00.445629 7f2577fff6c0 Delete type=3 #1 diff --git a/packs/armures/MANIFEST-000002 b/packs/armures/MANIFEST-000002 new file mode 100644 index 0000000..bbbc585 Binary files /dev/null and b/packs/armures/MANIFEST-000002 differ diff --git a/packs/competences/000003.log b/packs/competences/000003.log new file mode 100644 index 0000000..efc2271 Binary files /dev/null and b/packs/competences/000003.log differ diff --git a/packs/competences/CURRENT b/packs/competences/CURRENT new file mode 100644 index 0000000..1a84852 --- /dev/null +++ b/packs/competences/CURRENT @@ -0,0 +1 @@ +MANIFEST-000002 diff --git a/packs/competences/LOCK b/packs/competences/LOCK new file mode 100644 index 0000000..e69de29 diff --git a/packs/competences/LOG b/packs/competences/LOG new file mode 100644 index 0000000..9e7981b --- /dev/null +++ b/packs/competences/LOG @@ -0,0 +1 @@ +2026/05/03-20:13:00.492466 7f25c15fe6c0 Delete type=3 #1 diff --git a/packs/competences/MANIFEST-000002 b/packs/competences/MANIFEST-000002 new file mode 100644 index 0000000..bbbc585 Binary files /dev/null and b/packs/competences/MANIFEST-000002 differ diff --git a/packs/equipements/000003.log b/packs/equipements/000003.log new file mode 100644 index 0000000..72a5f14 Binary files /dev/null and b/packs/equipements/000003.log differ diff --git a/packs/equipements/CURRENT b/packs/equipements/CURRENT new file mode 100644 index 0000000..1a84852 --- /dev/null +++ b/packs/equipements/CURRENT @@ -0,0 +1 @@ +MANIFEST-000002 diff --git a/packs/equipements/LOCK b/packs/equipements/LOCK new file mode 100644 index 0000000..e69de29 diff --git a/packs/equipements/LOG b/packs/equipements/LOG new file mode 100644 index 0000000..10ccbe0 --- /dev/null +++ b/packs/equipements/LOG @@ -0,0 +1 @@ +2026/05/03-20:13:00.461618 7f25c1dff6c0 Delete type=3 #1 diff --git a/packs/equipements/MANIFEST-000002 b/packs/equipements/MANIFEST-000002 new file mode 100644 index 0000000..bbbc585 Binary files /dev/null and b/packs/equipements/MANIFEST-000002 differ diff --git a/packs/metiers/000003.log b/packs/metiers/000003.log new file mode 100644 index 0000000..19bd26f Binary files /dev/null and b/packs/metiers/000003.log differ diff --git a/packs/metiers/CURRENT b/packs/metiers/CURRENT new file mode 100644 index 0000000..1a84852 --- /dev/null +++ b/packs/metiers/CURRENT @@ -0,0 +1 @@ +MANIFEST-000002 diff --git a/packs/metiers/LOCK b/packs/metiers/LOCK new file mode 100644 index 0000000..e69de29 diff --git a/packs/metiers/LOG b/packs/metiers/LOG new file mode 100644 index 0000000..5282c63 --- /dev/null +++ b/packs/metiers/LOG @@ -0,0 +1 @@ +2026/05/03-20:13:00.538848 7f25c0dfd6c0 Delete type=3 #1 diff --git a/packs/metiers/MANIFEST-000002 b/packs/metiers/MANIFEST-000002 new file mode 100644 index 0000000..bbbc585 Binary files /dev/null and b/packs/metiers/MANIFEST-000002 differ diff --git a/packs/pouvoirs-compagnie/000003.log b/packs/pouvoirs-compagnie/000003.log new file mode 100644 index 0000000..0b9d2b7 Binary files /dev/null and b/packs/pouvoirs-compagnie/000003.log differ diff --git a/packs/pouvoirs-compagnie/CURRENT b/packs/pouvoirs-compagnie/CURRENT new file mode 100644 index 0000000..1a84852 --- /dev/null +++ b/packs/pouvoirs-compagnie/CURRENT @@ -0,0 +1 @@ +MANIFEST-000002 diff --git a/packs/pouvoirs-compagnie/LOCK b/packs/pouvoirs-compagnie/LOCK new file mode 100644 index 0000000..e69de29 diff --git a/packs/pouvoirs-compagnie/LOG b/packs/pouvoirs-compagnie/LOG new file mode 100644 index 0000000..7375a2d --- /dev/null +++ b/packs/pouvoirs-compagnie/LOG @@ -0,0 +1 @@ +2026/05/03-20:13:00.476671 7f25c0dfd6c0 Delete type=3 #1 diff --git a/packs/pouvoirs-compagnie/MANIFEST-000002 b/packs/pouvoirs-compagnie/MANIFEST-000002 new file mode 100644 index 0000000..bbbc585 Binary files /dev/null and b/packs/pouvoirs-compagnie/MANIFEST-000002 differ diff --git a/packs/races/000003.log b/packs/races/000003.log new file mode 100644 index 0000000..4f2c391 Binary files /dev/null and b/packs/races/000003.log differ diff --git a/packs/races/CURRENT b/packs/races/CURRENT new file mode 100644 index 0000000..1a84852 --- /dev/null +++ b/packs/races/CURRENT @@ -0,0 +1 @@ +MANIFEST-000002 diff --git a/packs/races/LOCK b/packs/races/LOCK new file mode 100644 index 0000000..e69de29 diff --git a/packs/races/LOG b/packs/races/LOG new file mode 100644 index 0000000..0e1e14c --- /dev/null +++ b/packs/races/LOG @@ -0,0 +1 @@ +2026/05/03-20:13:00.509155 7f2577fff6c0 Delete type=3 #1 diff --git a/packs/races/MANIFEST-000002 b/packs/races/MANIFEST-000002 new file mode 100644 index 0000000..bbbc585 Binary files /dev/null and b/packs/races/MANIFEST-000002 differ diff --git a/packs/sortileges/000003.log b/packs/sortileges/000003.log new file mode 100644 index 0000000..6a169ac Binary files /dev/null and b/packs/sortileges/000003.log differ diff --git a/packs/sortileges/CURRENT b/packs/sortileges/CURRENT new file mode 100644 index 0000000..1a84852 --- /dev/null +++ b/packs/sortileges/CURRENT @@ -0,0 +1 @@ +MANIFEST-000002 diff --git a/packs/sortileges/LOCK b/packs/sortileges/LOCK new file mode 100644 index 0000000..e69de29 diff --git a/packs/sortileges/LOG b/packs/sortileges/LOG new file mode 100644 index 0000000..800afdc --- /dev/null +++ b/packs/sortileges/LOG @@ -0,0 +1 @@ +2026/05/03-20:13:00.554628 7f25c15fe6c0 Delete type=3 #1 diff --git a/packs/sortileges/MANIFEST-000002 b/packs/sortileges/MANIFEST-000002 new file mode 100644 index 0000000..bbbc585 Binary files /dev/null and b/packs/sortileges/MANIFEST-000002 differ diff --git a/packs/tribus/000003.log b/packs/tribus/000003.log new file mode 100644 index 0000000..e2ccc48 Binary files /dev/null and b/packs/tribus/000003.log differ diff --git a/packs/tribus/CURRENT b/packs/tribus/CURRENT new file mode 100644 index 0000000..1a84852 --- /dev/null +++ b/packs/tribus/CURRENT @@ -0,0 +1 @@ +MANIFEST-000002 diff --git a/packs/tribus/LOCK b/packs/tribus/LOCK new file mode 100644 index 0000000..e69de29 diff --git a/packs/tribus/LOG b/packs/tribus/LOG new file mode 100644 index 0000000..1ccac27 --- /dev/null +++ b/packs/tribus/LOG @@ -0,0 +1 @@ +2026/05/03-20:13:00.523663 7f25c1dff6c0 Delete type=3 #1 diff --git a/packs/tribus/MANIFEST-000002 b/packs/tribus/MANIFEST-000002 new file mode 100644 index 0000000..bbbc585 Binary files /dev/null and b/packs/tribus/MANIFEST-000002 differ diff --git a/scripts/build-compendiums.mjs b/scripts/build-compendiums.mjs new file mode 100644 index 0000000..aba0ef3 --- /dev/null +++ b/scripts/build-compendiums.mjs @@ -0,0 +1,132 @@ +import fs from "node:fs" +import path from "node:path" +import crypto from "node:crypto" + +import { Level } from "level" + +const rootDir = path.resolve(import.meta.dirname, "..") +const packageJson = JSON.parse(fs.readFileSync(path.join(rootDir, "package.json"), "utf8")) +const systemJson = JSON.parse(fs.readFileSync(path.join(rootDir, "system.json"), "utf8")) + +const PACK_SOURCES = [ + { + sourcePath: path.join(rootDir, "packs-src", "armes.json"), + outputPath: path.join(rootDir, "packs", "armes"), + type: "Item", + }, + { + sourcePath: path.join(rootDir, "packs-src", "armures.json"), + outputPath: path.join(rootDir, "packs", "armures"), + type: "Item", + }, + { + sourcePath: path.join(rootDir, "packs-src", "equipements.json"), + outputPath: path.join(rootDir, "packs", "equipements"), + type: "Item", + }, + { + sourcePath: path.join(rootDir, "packs-src", "pouvoirs-compagnie.json"), + outputPath: path.join(rootDir, "packs", "pouvoirs-compagnie"), + type: "Item", + }, + { + sourcePath: path.join(rootDir, "packs-src", "competences.json"), + outputPath: path.join(rootDir, "packs", "competences"), + type: "Item", + }, + { + sourcePath: path.join(rootDir, "packs-src", "races.json"), + outputPath: path.join(rootDir, "packs", "races"), + type: "Item", + }, + { + sourcePath: path.join(rootDir, "packs-src", "tribus.json"), + outputPath: path.join(rootDir, "packs", "tribus"), + type: "Item", + }, + { + sourcePath: path.join(rootDir, "packs-src", "metiers.json"), + outputPath: path.join(rootDir, "packs", "metiers"), + type: "Item", + }, + { + sourcePath: path.join(rootDir, "packs-src", "sortileges.json"), + outputPath: path.join(rootDir, "packs", "sortileges"), + type: "Item", + }, +] + +const now = Date.now() +const systemId = systemJson.id +const systemVersion = packageJson.version +const coreVersion = String(systemJson.compatibility?.verified ?? systemJson.compatibility?.minimum ?? "") + +function slugId(input) { + const hash = crypto.createHash("sha256").update(input).digest() + const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + let id = "" + for (let index = 0; id.length < 16; index += 1) { + id += alphabet[hash[index % hash.length] % alphabet.length] + } + return id +} + +function toPackDocument(entry, index) { + const docId = slugId(`${entry.type}:${entry.name}`) + return { + name: entry.name, + type: entry.type, + img: entry.img ?? "icons/svg/item-bag.svg", + system: entry.system ?? {}, + effects: Array.isArray(entry.effects) ? entry.effects : [], + flags: entry.flags ?? {}, + _stats: { + systemId, + systemVersion, + coreVersion, + createdTime: now, + modifiedTime: now, + lastModifiedBy: "Copilot", + compendiumSource: null, + duplicateSource: null, + exportSource: null, + }, + _id: docId, + folder: null, + sort: index * 1000, + ownership: { + default: 0, + }, + } +} + +async function buildPack({ sourcePath, outputPath, type }) { + const source = JSON.parse(fs.readFileSync(sourcePath, "utf8")) + if (!Array.isArray(source)) { + throw new Error(`Pack source must be an array: ${sourcePath}`) + } + + fs.rmSync(outputPath, { recursive: true, force: true }) + fs.mkdirSync(outputPath, { recursive: true }) + + const db = new Level(outputPath, { valueEncoding: "utf8" }) + + try { + await db.open() + const batch = db.batch() + source.forEach((entry, index) => { + if (!entry.type) { + throw new Error(`Missing document type in ${sourcePath}: ${entry.name}`) + } + const doc = toPackDocument(entry, index) + batch.put(`!items!${doc._id}`, JSON.stringify(doc)) + }) + await batch.write() + } finally { + await db.close() + } +} + +for (const pack of PACK_SOURCES) { + await buildPack(pack) +} diff --git a/system.json b/system.json index 6887251..8ee81cf 100644 --- a/system.json +++ b/system.json @@ -2,6 +2,9 @@ "id": "fvtt-les-oublies", "title": "Les Oubliés", "description": "Système FoundryVTT AppV2 pour le jeu de role Les Oubliés.", + "manifest": "https://www.uberwald.me/gitea/public/fvtt-les-oublies/raw/branch/main/system.json", + "download": "#{DOWNLOAD}#", + "url": "https://www.uberwald.me/gitea/public/fvtt-les-oublies", "version": "0.1.0", "authors": [ { @@ -61,6 +64,7 @@ "race": { "htmlFields": [ "description", + "appearance", "specialRules", "notes" ] @@ -69,6 +73,7 @@ "htmlFields": [ "description", "specialRules", + "roleplayNotes", "notes" ] }, @@ -76,6 +81,7 @@ "htmlFields": [ "description", "specialRules", + "roleplayNotes", "notes" ] }, @@ -120,5 +126,132 @@ } }, "primaryTokenAttribute": "system.hp.value", + "packs": [ + { + "type": "Item", + "label": "Armes", + "name": "armes", + "path": "packs/armes", + "system": "fvtt-les-oublies", + "flags": {}, + "ownership": { + "PLAYER": "OBSERVER", + "ASSISTANT": "OWNER" + } + }, + { + "type": "Item", + "label": "Armures", + "name": "armures", + "path": "packs/armures", + "system": "fvtt-les-oublies", + "flags": {}, + "ownership": { + "PLAYER": "OBSERVER", + "ASSISTANT": "OWNER" + } + }, + { + "type": "Item", + "label": "Équipement", + "name": "equipements", + "path": "packs/equipements", + "system": "fvtt-les-oublies", + "flags": {}, + "ownership": { + "PLAYER": "OBSERVER", + "ASSISTANT": "OWNER" + } + }, + { + "type": "Item", + "label": "Pouvoirs de compagnie", + "name": "pouvoirs-compagnie", + "path": "packs/pouvoirs-compagnie", + "system": "fvtt-les-oublies", + "flags": {}, + "ownership": { + "PLAYER": "OBSERVER", + "ASSISTANT": "OWNER" + } + }, + { + "type": "Item", + "label": "Compétences", + "name": "competences", + "path": "packs/competences", + "system": "fvtt-les-oublies", + "flags": {}, + "ownership": { + "PLAYER": "OBSERVER", + "ASSISTANT": "OWNER" + } + }, + { + "type": "Item", + "label": "Races", + "name": "races", + "path": "packs/races", + "system": "fvtt-les-oublies", + "flags": {}, + "ownership": { + "PLAYER": "OBSERVER", + "ASSISTANT": "OWNER" + } + }, + { + "type": "Item", + "label": "Tribus", + "name": "tribus", + "path": "packs/tribus", + "system": "fvtt-les-oublies", + "flags": {}, + "ownership": { + "PLAYER": "OBSERVER", + "ASSISTANT": "OWNER" + } + }, + { + "type": "Item", + "label": "Métiers", + "name": "metiers", + "path": "packs/metiers", + "system": "fvtt-les-oublies", + "flags": {}, + "ownership": { + "PLAYER": "OBSERVER", + "ASSISTANT": "OWNER" + } + }, + { + "type": "Item", + "label": "Sortilèges", + "name": "sortileges", + "path": "packs/sortileges", + "system": "fvtt-les-oublies", + "flags": {}, + "ownership": { + "PLAYER": "OBSERVER", + "ASSISTANT": "OWNER" + } + } + ], + "packFolders": [ + { + "name": "Les Oubliés", + "sorting": "a", + "packs": [ + "armes", + "armures", + "equipements", + "pouvoirs-compagnie", + "competences", + "races", + "tribus", + "metiers", + "sortileges" + ] + } + ], "flags": {} } diff --git a/templates/actor-compagnie-sheet-v2.hbs b/templates/actor-compagnie-sheet-v2.hbs new file mode 100644 index 0000000..d774e2e --- /dev/null +++ b/templates/actor-compagnie-sheet-v2.hbs @@ -0,0 +1,114 @@ +Capitaine, pouvoir partagé et liens forgés dans l'Exil
+ +Sélectionnez le capitaine et l'ombre du tourment parmi les personnages du monde.
+ {{#if captain}}Capitaine : {{captain.name}}
{{/if}} + {{#if shadow}}Ombre : {{shadow.name}}
{{/if}} +{{localize "LESOUBLIES.ui.readOnlyCollection"}}
+Sélectionnez le capitaine et l'ombre du tourment parmi les personnages du monde.
+ {{#if captain}}Capitaine : {{captain.name}}
{{/if}} + {{#if shadow}}Ombre : {{shadow.name}}
{{/if}} +{{localize "LESOUBLIES.ui.readOnlyCollection"}}
+Sélectionnez le capitaine et l'ombre du tourment parmi les personnages du monde.
+ {{#if captain}}Capitaine : {{captain.name}}
{{/if}} + {{#if shadow}}Ombre : {{shadow.name}}
{{/if}} +{{localize "LESOUBLIES.ui.readOnlyCollection"}}
+{{localize "LESOUBLIES.labels.membresIds"}}
- {{#if captain}}Capitaine : {{captain.name}}
{{/if}} - {{#if shadow}}Ombre : {{shadow.name}}
{{/if}} -Sélectionnez le capitaine et l'ombre du tourment parmi les personnages du monde.
+ {{#if captain}}Capitaine : {{captain.name}}
{{/if}} + {{#if shadow}}Ombre : {{shadow.name}}
{{/if}} +{{localize "LESOUBLIES.ui.readOnlyCollection"}}
-{{localize "LESOUBLIES.ui.readOnlyCollection"}}
+Créatures du Petit Peuple, du Cauchemar et des frontières
+{{slot.label}}
+ {{#if slot.item}} + {{slot.item.name}} + {{else}} + Glisser ici + {{/if}} +{{localize "LESOUBLIES.labels.compagnie"}} : {{#if derived.compagnie}}{{derived.compagnie.name}}{{else}}—{{/if}}
+Aucune arme équipée.
+ {{/if}} +{{slot.label}}
+ {{#if slot.item}} + {{slot.item.name}} + {{else}} + Glisser ici + {{/if}} +{{localize "LESOUBLIES.labels.compagnie"}} : {{#if derived.compagnie}}{{derived.compagnie.name}}{{else}}—{{/if}}
+ {{#if derived.compagnie}} + + {{/if}} +Aucune arme équipée.
+ {{/if}} +{{slot.label}}
+ {{#if slot.item}} + {{slot.item.name}} + {{else}} + Glisser ici + {{/if}} +{{localize "LESOUBLIES.labels.compagnie"}} : {{#if derived.compagnie}}{{derived.compagnie.name}}{{else}}—{{/if}}
+ {{#if activeCompanyPower}} +{{localize "LESOUBLIES.labels.pouvoirCompagnieActif"}} : {{activeCompanyPower.name}} — {{activeCompanyPower.system.activationCondition}}
+ {{/if}} + {{#if derived.compagnie}} + + {{/if}} +Aucune arme équipée.
+ {{/if}} +{{slot.label}}
+ {{#if slot.item}} + {{slot.item.name}} + {{else}} + Glisser ici + {{/if}} +{{localize "LESOUBLIES.labels.compagnie"}} : {{#if derived.compagnie}}{{derived.compagnie.name}}{{else}}—{{/if}}
+ {{#if activeCompanyPower}} +{{localize "LESOUBLIES.labels.pouvoirCompagnieActif"}} : {{activeCompanyPower.name}} — {{activeCompanyPower.system.activationCondition}}
+ {{/if}} + {{#if derived.compagnie}} + + {{/if}} +Aucune arme équipée.
+ {{/if}} +{{slot.label}}
+ {{#if slot.item}} + {{slot.item.name}} + {{else}} + Glisser ici + {{/if}} +{{localize "LESOUBLIES.labels.compagnie"}} : {{#if derived.compagnie}}{{derived.compagnie.name}}{{else}}—{{/if}}
+ {{#if activeCompanyPower}} +{{localize "LESOUBLIES.labels.pouvoirCompagnieActif"}} : {{activeCompanyPower.name}} — {{activeCompanyPower.system.activationCondition}}
+ {{/if}} + {{#if derived.compagnie}} + + {{/if}} +Aucune arme équipée.
+ {{/if}} +Chronique d'un Oublié
+ +Petit Peuple, Songes, Cauchemar et destin de compagnie
+{{slot.label}}
+ {{#if slot.item}} + {{slot.item.name}} + {{else}} + — + {{/if}} +{{slot.filledHint}}
+ {{else}} +{{slot.emptyHint}}
+ {{/if}} +{{localize "LESOUBLIES.labels.compagnie"}} : {{#if derived.compagnie}}{{derived.compagnie.name}}{{else}}—{{/if}}
+Chronique d'un Oublié
+ +Petit Peuple, Songes, Cauchemar et destin de compagnie
+{{slot.label}}
+ {{#if slot.item}} + {{slot.item.name}} + {{else}} + — + {{/if}} +{{slot.filledHint}}
+ {{else}} +{{slot.emptyHint}}
+ {{/if}} +{{localize "LESOUBLIES.labels.compagnie"}} : {{#if derived.compagnie}}{{derived.compagnie.name}}{{else}}—{{/if}}
+Petit Peuple, Songes, Cauchemar et destin de compagnie
+{{slot.label}}
+ {{#if slot.item}} + {{slot.item.name}} + {{else}} + — + {{/if}} +{{slot.filledHint}}
+ {{else}} +{{slot.emptyHint}}
+ {{/if}} +{{localize "LESOUBLIES.labels.compagnie"}} : {{#if derived.compagnie}}{{derived.compagnie.name}}{{else}}—{{/if}}
+{{slot.label}}
+ {{#if slot.item}} + {{slot.item.name}} + {{else}} + — + {{/if}} +{{slot.filledHint}}
+ {{else}} +{{slot.emptyHint}}
+ {{/if}} +{{localize "LESOUBLIES.labels.compagnie"}} : {{#if derived.compagnie}}{{derived.compagnie.name}}{{else}}—{{/if}}
+{{slot.label}}
+ {{#if slot.item}} + {{slot.item.name}} + {{else}} + Glisser ici + {{/if}} +{{localize "LESOUBLIES.labels.compagnie"}} : {{#if derived.compagnie}}{{derived.compagnie.name}}{{else}}—{{/if}}
+{{slot.label}}
+ {{#if slot.item}} + {{slot.item.name}} + {{else}} + Glisser ici + {{/if}} +{{localize "LESOUBLIES.labels.compagnie"}} : {{#if derived.compagnie}}{{derived.compagnie.name}}{{else}}—{{/if}}
+{{slot.label}}
+ {{#if slot.item}} + {{slot.item.name}} + {{else}} + Glisser ici + {{/if}} +{{localize "LESOUBLIES.labels.compagnie"}} : {{#if derived.compagnie}}{{derived.compagnie.name}}{{else}}—{{/if}}
+Aucune arme équipée.
+ {{/if}} +{{slot.label}}
+ {{#if slot.item}} + {{slot.item.name}} + {{else}} + Glisser ici + {{/if}} +{{localize "LESOUBLIES.labels.compagnie"}} : {{#if derived.compagnie}}{{derived.compagnie.name}}{{else}}—{{/if}}
+Aucune arme équipée.
+ {{/if}} +{{slot.label}}
+ {{#if slot.item}} + {{slot.item.name}} + {{else}} + — + {{/if}} +{{slot.filledHint}}
+ {{else}} +{{slot.emptyHint}}
+ {{/if}}{{localize "LESOUBLIES.labels.compagnie"}} : {{#if derived.compagnie}}{{derived.compagnie.name}}{{else}}—{{/if}}
+{{targetStatus.message}}
+Vous pouvez choisir un adversaire depuis la liste ou conserver la saisie manuelle des valeurs.
{{localize "LESOUBLIES.rolls.testHint"}}
{{/if}}{{#if isMetierMatch}}Le métier de l'acteur couvre ce sortilège.{{else}}Le métier ne couvre pas ce sortilège : le surcoût peut être appliqué ci-dessous.{{/if}}
+{{#if isMetierMatch}}Le métier de l'acteur couvre ce sortilège : le coût de base s'applique.{{else}}Le métier de l'acteur ne couvre pas ce sortilège : le coût est doublé conformément aux règles.{{/if}}
{{localize "LESOUBLIES.rolls.resourceState"}} : Songes {{resources.songesPoints}} / {{resources.songesValue}} · Cauchemar {{resources.cauchemarPoints}} / {{resources.cauchemarValue}}
Difficulté : -3 par fil supplémentaire. Dégâts subis : 1 par fil souhaité. En cas d'échec, plus aucune récolte possible sur ce dormeur cette nuit.
{{localize "LESOUBLIES.rolls.resourceState"}} : Songes {{resources.songesPoints}} / {{resources.songesValue}} · Cauchemar {{resources.cauchemarPoints}} / {{resources.cauchemarValue}}
- +Arsenal
+ + +Race restreinte : {{system.restrictedRace}}
+Race restreinte : {{system.restrictedRace}}
-{{localize "LESOUBLIES.labels.proprietes}} : {{join system.properties}}
{{localize "LESOUBLIES.labels.domaines}} : {{join system.domains}}
-Domaines fixes : {{join system.fixedDomains}}
-Exemples : {{join system.exampleDomains}}
{{localize "LESOUBLIES.labels.motsCles"}} : {{join system.keywords}}
-Domaines de langues : {{join system.languageDomains}}
Tribus principales : {{join system.mainTribes}}
{{localize "LESOUBLIES.labels.motsCles"}} : {{join system.keywords}}
- + @@ -71,26 +67,26 @@{{localize "LESOUBLIES.labels.domaines}} : {{join system.requiredDomains}}
-Arts requis : {{join system.artsDomains}}
-Tags : {{join system.ruleTags}}
+