From 839b2b606ec6c271a5e60d0b5eff96d401699b43 Mon Sep 17 00:00:00 2001 From: LeRatierBretonnier Date: Sat, 2 May 2026 09:16:24 +0200 Subject: [PATCH] Import initial --- css/les-oublies.css | 757 +++++ css/les-oublies.css.map | 1 + gulpfile.js | 26 + lang/fr.json | 191 ++ less/components/sheets.less | 760 +++++ less/les-oublies.less | 1 + modules/applications/sheets/_module.mjs | 10 + modules/applications/sheets/arme-sheet.mjs | 9 + modules/applications/sheets/armure-sheet.mjs | 9 + .../applications/sheets/base-actor-sheet.mjs | 186 ++ .../applications/sheets/base-item-sheet.mjs | 69 + .../applications/sheets/compagnie-sheet.mjs | 33 + .../applications/sheets/competence-sheet.mjs | 9 + .../applications/sheets/creature-sheet.mjs | 29 + .../applications/sheets/equipement-sheet.mjs | 9 + .../applications/sheets/personnage-sheet.mjs | 37 + .../sheets/pouvoir-compagnie-sheet.mjs | 9 + .../sheets/reference-item-sheet.mjs | 17 + .../applications/sheets/sortilege-sheet.mjs | 9 + modules/les-oublies-actor.js | 164 + modules/les-oublies-config.js | 89 + modules/les-oublies-item.js | 9 + modules/les-oublies-main.js | 55 + modules/les-oublies-rolls.js | 1751 +++++++++++ modules/les-oublies-utility.js | 76 + modules/models/arme.mjs | 22 + modules/models/armure.mjs | 17 + modules/models/base-item.mjs | 11 + modules/models/compagnie.mjs | 26 + modules/models/competence.mjs | 18 + modules/models/creature.mjs | 52 + modules/models/equipement.mjs | 18 + modules/models/index.mjs | 13 + modules/models/metier.mjs | 39 + modules/models/personnage.mjs | 66 + modules/models/pouvoir-compagnie.mjs | 18 + modules/models/race.mjs | 30 + modules/models/sortilege.mjs | 25 + modules/models/tribu.mjs | 30 + package-lock.json | 2687 +++++++++++++++++ package.json | 18 + packs-src/armes-sample.json | 10 + packs-src/armures-sample.json | 5 + packs-src/competences.json | 29 + packs-src/equipements-sample.json | 12 + packs-src/metiers.json | 198 ++ packs-src/pouvoirs-compagnie.json | 10 + packs-src/races.json | 128 + packs-src/sortileges-sample.json | 10 + packs-src/tribus.json | 309 ++ system.json | 124 + templates/actor-compagnie-sheet.hbs | 90 + templates/actor-creature-sheet.hbs | 144 + templates/actor-personnage-sheet.hbs | 228 ++ templates/chat-action-roll.hbs | 116 + templates/chat-confrontation-roll.hbs | 152 + templates/chat-dual-roll.hbs | 7 + templates/chat-initiative-roll.hbs | 54 + templates/chat-spell-activation.hbs | 48 + templates/chat-test-roll.hbs | 61 + templates/dialog-damage-resolution.hbs | 49 + templates/dialog-roll-action.hbs | 76 + templates/dialog-roll-attack.hbs | 141 + templates/dialog-roll-choice.hbs | 19 + templates/dialog-roll-confrontation.hbs | 95 + .../dialog-roll-preset-confrontation.hbs | 125 + templates/dialog-roll-test.hbs | 38 + templates/dialog-spell-activation.hbs | 44 + templates/dialog-thread-harvest.hbs | 42 + templates/item-arme-sheet.hbs | 27 + templates/item-armure-sheet.hbs | 24 + templates/item-competence-sheet.hbs | 26 + templates/item-equipement-sheet.hbs | 26 + templates/item-pouvoir-compagnie-sheet.hbs | 27 + templates/item-reference-sheet.hbs | 96 + templates/item-sortilege-sheet.hbs | 30 + 76 files changed, 10025 insertions(+) create mode 100644 css/les-oublies.css create mode 100644 css/les-oublies.css.map create mode 100644 gulpfile.js create mode 100644 lang/fr.json create mode 100644 less/components/sheets.less create mode 100644 less/les-oublies.less create mode 100644 modules/applications/sheets/_module.mjs create mode 100644 modules/applications/sheets/arme-sheet.mjs create mode 100644 modules/applications/sheets/armure-sheet.mjs create mode 100644 modules/applications/sheets/base-actor-sheet.mjs create mode 100644 modules/applications/sheets/base-item-sheet.mjs create mode 100644 modules/applications/sheets/compagnie-sheet.mjs create mode 100644 modules/applications/sheets/competence-sheet.mjs create mode 100644 modules/applications/sheets/creature-sheet.mjs create mode 100644 modules/applications/sheets/equipement-sheet.mjs create mode 100644 modules/applications/sheets/personnage-sheet.mjs create mode 100644 modules/applications/sheets/pouvoir-compagnie-sheet.mjs create mode 100644 modules/applications/sheets/reference-item-sheet.mjs create mode 100644 modules/applications/sheets/sortilege-sheet.mjs create mode 100644 modules/les-oublies-actor.js create mode 100644 modules/les-oublies-config.js create mode 100644 modules/les-oublies-item.js create mode 100644 modules/les-oublies-main.js create mode 100644 modules/les-oublies-rolls.js create mode 100644 modules/les-oublies-utility.js create mode 100644 modules/models/arme.mjs create mode 100644 modules/models/armure.mjs create mode 100644 modules/models/base-item.mjs create mode 100644 modules/models/compagnie.mjs create mode 100644 modules/models/competence.mjs create mode 100644 modules/models/creature.mjs create mode 100644 modules/models/equipement.mjs create mode 100644 modules/models/index.mjs create mode 100644 modules/models/metier.mjs create mode 100644 modules/models/personnage.mjs create mode 100644 modules/models/pouvoir-compagnie.mjs create mode 100644 modules/models/race.mjs create mode 100644 modules/models/sortilege.mjs create mode 100644 modules/models/tribu.mjs create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 packs-src/armes-sample.json create mode 100644 packs-src/armures-sample.json create mode 100644 packs-src/competences.json create mode 100644 packs-src/equipements-sample.json create mode 100644 packs-src/metiers.json create mode 100644 packs-src/pouvoirs-compagnie.json create mode 100644 packs-src/races.json create mode 100644 packs-src/sortileges-sample.json create mode 100644 packs-src/tribus.json create mode 100644 system.json create mode 100644 templates/actor-compagnie-sheet.hbs create mode 100644 templates/actor-creature-sheet.hbs create mode 100644 templates/actor-personnage-sheet.hbs create mode 100644 templates/chat-action-roll.hbs create mode 100644 templates/chat-confrontation-roll.hbs create mode 100644 templates/chat-dual-roll.hbs create mode 100644 templates/chat-initiative-roll.hbs create mode 100644 templates/chat-spell-activation.hbs create mode 100644 templates/chat-test-roll.hbs create mode 100644 templates/dialog-damage-resolution.hbs create mode 100644 templates/dialog-roll-action.hbs create mode 100644 templates/dialog-roll-attack.hbs create mode 100644 templates/dialog-roll-choice.hbs create mode 100644 templates/dialog-roll-confrontation.hbs create mode 100644 templates/dialog-roll-preset-confrontation.hbs create mode 100644 templates/dialog-roll-test.hbs create mode 100644 templates/dialog-spell-activation.hbs create mode 100644 templates/dialog-thread-harvest.hbs create mode 100644 templates/item-arme-sheet.hbs create mode 100644 templates/item-armure-sheet.hbs create mode 100644 templates/item-competence-sheet.hbs create mode 100644 templates/item-equipement-sheet.hbs create mode 100644 templates/item-pouvoir-compagnie-sheet.hbs create mode 100644 templates/item-reference-sheet.hbs create mode 100644 templates/item-sortilege-sheet.hbs diff --git a/css/les-oublies.css b/css/les-oublies.css new file mode 100644 index 0000000..08dcf3a --- /dev/null +++ b/css/les-oublies.css @@ -0,0 +1,757 @@ +@font-face { + font-family: 'Cinzel'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(https://fonts.gstatic.com/s/cinzel/v26/8vIU7ww63mVu7gtR-kwKxNvkNOjw-tbnTYo.ttf) format('truetype'); +} +@font-face { + font-family: 'Cinzel'; + font-style: normal; + font-weight: 600; + font-display: swap; + src: url(https://fonts.gstatic.com/s/cinzel/v26/8vIU7ww63mVu7gtR-kwKxNvkNOjw-gjgTYo.ttf) format('truetype'); +} +@font-face { + font-family: 'Cinzel'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url(https://fonts.gstatic.com/s/cinzel/v26/8vIU7ww63mVu7gtR-kwKxNvkNOjw-jHgTYo.ttf) format('truetype'); +} +@font-face { + font-family: 'Cormorant Garamond'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(https://fonts.gstatic.com/s/cormorantgaramond/v21/co3umX5slCNuHLi8bLeY9MK7whWMhyjypVO7abI26QOD_v86GnM.ttf) format('truetype'); +} +@font-face { + font-family: 'Cormorant Garamond'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url(https://fonts.gstatic.com/s/cormorantgaramond/v21/co3umX5slCNuHLi8bLeY9MK7whWMhyjypVO7abI26QOD_s06GnM.ttf) format('truetype'); +} +@font-face { + font-family: 'Cormorant Garamond'; + font-style: normal; + font-weight: 600; + font-display: swap; + src: url(https://fonts.gstatic.com/s/cormorantgaramond/v21/co3umX5slCNuHLi8bLeY9MK7whWMhyjypVO7abI26QOD_iE9GnM.ttf) format('truetype'); +} +@font-face { + font-family: 'Cormorant Garamond'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url(https://fonts.gstatic.com/s/cormorantgaramond/v21/co3umX5slCNuHLi8bLeY9MK7whWMhyjypVO7abI26QOD_hg9GnM.ttf) format('truetype'); +} +@font-face { + font-family: 'IM Fell English SC'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(https://fonts.gstatic.com/s/imfellenglishsc/v16/a8IENpD3CDX-4zrWfr1VY879qFF05pZLOw.ttf) format('truetype'); +} +.fvtt-les-oublies.sheet, +.fvtt-les-oublies.sheet .window-content { + --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-copper: #8d5c3b; + --lo-blood: #6d2922; + --lo-shadow: rgba(0, 0, 0, 0.42); + --lo-line: rgba(110, 77, 53, 0.4); + --lo-glow: rgba(207, 176, 106, 0.22); +} +.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); +} +.fvtt-les-oublies { + color: var(--lo-ink); + padding: 0.4rem; + position: relative; +} +.fvtt-les-oublies .les-oublies-sheet { + position: relative; +} +.fvtt-les-oublies .les-oublies-sheet::before { + content: ""; + position: absolute; + inset: -0.35rem; + border: 1px solid rgba(207, 176, 106, 0.18); + border-radius: 18px; + pointer-events: none; + box-shadow: 0 0 0 1px rgba(36, 23, 14, 0.5), inset 0 0 40px rgba(0, 0, 0, 0.12); +} +.fvtt-les-oublies .hero-banner { + display: grid; + grid-template-columns: 110px 1fr; + gap: 1.2rem; + align-items: stretch; + margin-bottom: 1.1rem; + padding: 1rem 1.1rem 1.1rem; + border-radius: 18px; + 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); + position: relative; + overflow: hidden; +} +.fvtt-les-oublies .hero-banner::after { + content: ""; + position: absolute; + inset: 0; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.16), transparent), radial-gradient(circle at 85% 15%, rgba(78, 107, 76, 0.16), transparent 18%); + mix-blend-mode: soft-light; + pointer-events: none; +} +.fvtt-les-oublies .profile-img { + width: 110px; + height: 132px; + object-fit: cover; + border: 2px solid rgba(85, 55, 34, 0.7); + border-radius: 14px; + 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; + z-index: 1; +} +.fvtt-les-oublies .hero-copy, +.fvtt-les-oublies .header-fields { + flex: 1; + position: relative; + z-index: 1; +} +.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; + text-transform: uppercase; +} +.fvtt-les-oublies .sheet-title { + margin: 0; + line-height: 1; +} +.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); + letter-spacing: 0.04em; + color: #2b1b14; + background: transparent; + border: none; + box-shadow: none; + padding: 0; + height: auto; +} +.fvtt-les-oublies .sheet-subtitle { + margin: 0.35rem 0 0; + color: var(--lo-ink-soft); + font-size: 1.05rem; + font-style: italic; +} +.fvtt-les-oublies .sheet-grid { + display: grid; + gap: 1rem; + margin-bottom: 1rem; +} +.fvtt-les-oublies .sheet-grid-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} +.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; + 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; +} +.fvtt-les-oublies .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; +} +.fvtt-les-oublies .sheet-card h2, +.fvtt-les-oublies .sheet-card h3 { + font-family: "Cinzel", serif; + letter-spacing: 0.08em; + text-transform: uppercase; + color: #3a251a; +} +.fvtt-les-oublies .sheet-card h2 { + margin: 0 0 0.75rem; + padding-bottom: 0.35rem; + border-bottom: 1px solid rgba(109, 41, 34, 0.18); + font-size: 1rem; +} +.fvtt-les-oublies .sheet-card h3 { + margin: 0 0 0.55rem; + font-size: 0.84rem; +} +.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%); +} +.fvtt-les-oublies .profiles-card { + background: linear-gradient(180deg, rgba(245, 238, 223, 0.95), rgba(232, 223, 201, 0.92)); +} +.fvtt-les-oublies .ledger-card { + background: linear-gradient(180deg, rgba(251, 244, 232, 0.95), rgba(236, 224, 198, 0.92)); +} +.fvtt-les-oublies .notes-card { + background: linear-gradient(180deg, rgba(240, 229, 207, 0.98), rgba(223, 207, 177, 0.95)); +} +.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; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; +} +.fvtt-les-oublies .field-row, +.fvtt-les-oublies .profile-cell { + display: flex; + align-items: center; + gap: 0.65rem; + margin-bottom: 0.65rem; +} +.fvtt-les-oublies .field-row label, +.fvtt-les-oublies .profile-cell label { + min-width: 10rem; + font-family: "Cinzel", serif; + font-size: 0.74rem; + font-weight: 700; + letter-spacing: 0.08em; + text-transform: uppercase; + color: #51392b; +} +.fvtt-les-oublies .field-row span { + font-family: "Cinzel", serif; + font-size: 0.84rem; + color: var(--lo-blood); +} +.fvtt-les-oublies .profile-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0.75rem 1rem; +} +.fvtt-les-oublies .profile-cell { + padding: 0.7rem 0.8rem; + border-radius: 14px; + 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); + justify-content: space-between; + flex-wrap: wrap; +} +.fvtt-les-oublies .group-block + .group-block { + margin-top: 1rem; +} +.fvtt-les-oublies .item-list { + display: flex; + flex-direction: column; + gap: 0.65rem; +} +.fvtt-les-oublies .item-card { + display: flex; + justify-content: space-between; + gap: 1rem; + padding: 0.8rem 0.9rem; + 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; +} +.fvtt-les-oublies .item-card::before { + content: ""; + position: absolute; + left: 0.55rem; + top: 0.6rem; + bottom: 0.6rem; + 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; +} +.fvtt-les-oublies .item-card strong, +.fvtt-les-oublies .reference-list strong { + font-family: "Cinzel", serif; + letter-spacing: 0.04em; + color: #3d281d; +} +.fvtt-les-oublies .reference-list { + margin: 0; + padding-left: 1.2rem; +} +.fvtt-les-oublies .reference-list li + li { + margin-top: 0.35rem; +} +.fvtt-les-oublies .help-text { + color: var(--lo-ink-soft); + font-size: 0.96rem; + font-style: italic; +} +.fvtt-les-oublies .mode-button, +.fvtt-les-oublies 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; + text-transform: uppercase; + padding: 0.5rem 0.9rem; + 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 .les-oublies-roll-dialog { + color: var(--lo-ink); +} +.fvtt-les-oublies .les-oublies-roll-dialog .sheet-card { + margin-bottom: 0; +} +.fvtt-les-oublies .les-oublies-roll-dialog .field-row input, +.fvtt-les-oublies .les-oublies-roll-dialog .field-row select { + flex: 1; +} +.fvtt-les-oublies button:hover, +.fvtt-les-oublies 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); +} +.fvtt-les-oublies input[type="text"], +.fvtt-les-oublies input[type="number"], +.fvtt-les-oublies select, +.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; + color: #2e1f18; + font-family: "Cormorant Garamond", Georgia, serif; + font-size: 1rem; + min-height: 2.1rem; + padding: 0.2rem 0.65rem; + box-shadow: inset 0 1px 3px rgba(72, 46, 30, 0.08); +} +.fvtt-les-oublies input[type="checkbox"] { + accent-color: #6d2922; +} +.fvtt-les-oublies .sheet-card .editor, +.fvtt-les-oublies .sheet-card .editor-content, +.fvtt-les-oublies .sheet-card .editor-container, +.fvtt-les-oublies prose-mirror { + background: rgba(255, 252, 246, 0.62); + border-radius: 12px; +} +.fvtt-les-oublies .prosemirror, +.fvtt-les-oublies prose-mirror { + border: 1px solid rgba(111, 84, 55, 0.18); + padding: 0.6rem 0.7rem; +} +.fvtt-les-oublies a, +.fvtt-les-oublies button[data-action="rollProfile"], +.fvtt-les-oublies button[data-action="rollSkill"] { + text-decoration: none; +} +.fvtt-les-oublies button[data-action="rollProfile"], +.fvtt-les-oublies button[data-action="rollSkill"] { + background: linear-gradient(180deg, #6a2f29, #421914); + color: #f8ead3; +} +.fvtt-les-oublies .compagnie-sheet .hero-banner { + background: linear-gradient(135deg, rgba(245, 235, 210, 0.98), rgba(224, 205, 173, 0.93)), linear-gradient(180deg, rgba(207, 176, 106, 0.18), rgba(58, 37, 26, 0.08)); +} +.fvtt-les-oublies .creature-sheet .hero-banner { + background: linear-gradient(135deg, rgba(236, 226, 206, 0.96), rgba(213, 196, 166, 0.92)), radial-gradient(circle at 90% 15%, rgba(109, 41, 34, 0.14), transparent 28%); +} +@media (max-width: 900px) { + .fvtt-les-oublies .sheet-grid-2, + .fvtt-les-oublies .profile-grid { + grid-template-columns: 1fr; + } + .fvtt-les-oublies .hero-banner { + grid-template-columns: 1fr; + } + .fvtt-les-oublies .profile-img { + width: 100%; + max-width: 110px; + } +} +.chat-message .les-oublies-chat-card, +.chat-popout .les-oublies-chat-card, +#chat-log .les-oublies-chat-card { + --lo-chat-bg-top: rgba(249, 241, 227, 0.98); + --lo-chat-bg-bottom: rgba(226, 209, 178, 0.96); + --lo-chat-line: rgba(114, 80, 55, 0.26); + --lo-chat-ink: #2f1f17; + --lo-chat-soft: #6a5142; + --lo-chat-gold: #cfb06a; + --lo-chat-blood: #6d2922; + 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; + 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; +} +.chat-message .les-oublies-chat-card::before, +.chat-popout .les-oublies-chat-card::before, +#chat-log .les-oublies-chat-card::before { + content: ""; + position: absolute; + inset: 0; + pointer-events: none; + background: linear-gradient(180deg, rgba(255, 255, 255, 0.16), transparent 24%), radial-gradient(circle at 100% 0, rgba(207, 176, 106, 0.14), transparent 28%); +} +.chat-message .les-oublies-chat-card.is-success, +.chat-popout .les-oublies-chat-card.is-success, +#chat-log .les-oublies-chat-card.is-success { + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.56), 0 10px 28px rgba(0, 0, 0, 0.14), 0 0 0 1px rgba(65, 107, 72, 0.18); +} +.chat-message .les-oublies-chat-card.is-failure, +.chat-popout .les-oublies-chat-card.is-failure, +#chat-log .les-oublies-chat-card.is-failure { + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.56), 0 10px 28px rgba(0, 0, 0, 0.14), 0 0 0 1px rgba(109, 41, 34, 0.18); +} +.chat-message .chat-card-banner, +.chat-popout .chat-card-banner, +#chat-log .chat-card-banner { + display: grid; + grid-template-columns: 3rem 1fr auto; + gap: 0.8rem; + align-items: center; + position: relative; + z-index: 1; +} +.chat-message .chat-card-portrait, +.chat-popout .chat-card-portrait, +#chat-log .chat-card-portrait { + width: 3rem; + height: 3rem; + object-fit: cover; + border-radius: 14px; + 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); +} +.chat-message .chat-card-kicker, +.chat-popout .chat-card-kicker, +#chat-log .chat-card-kicker { + margin: 0; + font-family: "Cinzel", serif; + font-size: 0.66rem; + letter-spacing: 0.18em; + 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; + font-family: "Cinzel", serif; + font-size: 1rem; + letter-spacing: 0.06em; + 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; + color: var(--lo-chat-soft); + font-size: 0.95rem; +} +.chat-message .chat-card-badge, +.chat-popout .chat-card-badge, +#chat-log .chat-card-badge { + align-self: start; + padding: 0.38rem 0.65rem; + 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; + text-transform: uppercase; + text-align: center; + max-width: 13rem; +} +.chat-message .chat-card-badge.success, +.chat-popout .chat-card-badge.success, +#chat-log .chat-card-badge.success { + background: rgba(226, 243, 225, 0.74); + color: #29422d; +} +.chat-message .chat-card-badge.failure, +.chat-popout .chat-card-badge.failure, +#chat-log .chat-card-badge.failure { + background: rgba(245, 223, 220, 0.74); + color: #6d2922; +} +.chat-message .chat-card-badge.neutral, +.chat-popout .chat-card-badge.neutral, +#chat-log .chat-card-badge.neutral { + background: rgba(242, 233, 212, 0.78); +} +.chat-message .chat-card-body, +.chat-popout .chat-card-body, +#chat-log .chat-card-body { + position: relative; + z-index: 1; +} +.chat-message .chat-card-body p, +.chat-popout .chat-card-body p, +#chat-log .chat-card-body p { + margin: 0.45rem 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; +} +.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); +} +.chat-message .roll-summary-grid span, +.chat-popout .roll-summary-grid span, +#chat-log .roll-summary-grid span { + display: block; + font-family: "Cinzel", serif; + font-size: 0.66rem; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--lo-chat-blood); +} +.chat-message .roll-summary-grid strong, +.chat-popout .roll-summary-grid strong, +#chat-log .roll-summary-grid strong { + font-size: 1.08rem; + color: var(--lo-chat-ink); +} +.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); +} +.chat-message .dice-strip, +.chat-popout .dice-strip, +#chat-log .dice-strip { + display: flex; + flex-wrap: wrap; + gap: 0.55rem; + margin-top: 0.8rem; +} +.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); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.52); +} +.chat-message .die-chip.selected, +.chat-popout .die-chip.selected, +#chat-log .die-chip.selected { + border-color: rgba(207, 176, 106, 0.86); + background: rgba(255, 247, 228, 0.92); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.52), 0 0 0 1px rgba(207, 176, 106, 0.28); +} +.chat-message .die-chip strong, +.chat-popout .die-chip strong, +#chat-log .die-chip strong, +.chat-message .die-chip em, +.chat-popout .die-chip em, +#chat-log .die-chip em, +.chat-message .die-chip span, +.chat-popout .die-chip span, +#chat-log .die-chip span { + display: block; +} +.chat-message .die-chip span, +.chat-popout .die-chip span, +#chat-log .die-chip span { + color: var(--lo-chat-ink); +} +.chat-message .die-chip em, +.chat-popout .die-chip em, +#chat-log .die-chip em { + color: var(--lo-chat-blood); + font-size: 0.82rem; +} +.chat-message .chat-callouts, +.chat-popout .chat-callouts, +#chat-log .chat-callouts { + display: flex; + flex-wrap: wrap; + gap: 0.6rem; + margin-top: 0.8rem; +} +.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); +} +.chat-message .chat-callout span, +.chat-popout .chat-callout span, +#chat-log .chat-callout span, +.chat-message .chat-callout strong, +.chat-popout .chat-callout strong, +#chat-log .chat-callout strong, +.chat-message .chat-callout em, +.chat-popout .chat-callout em, +#chat-log .chat-callout em { + display: block; +} +.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; + text-transform: uppercase; + color: var(--lo-chat-blood); +} +.chat-message .chat-callout strong, +.chat-popout .chat-callout strong, +#chat-log .chat-callout strong { + color: var(--lo-chat-ink); +} +.chat-message .chat-callout em, +.chat-popout .chat-callout em, +#chat-log .chat-callout em { + color: var(--lo-chat-soft); + font-size: 0.88rem; +} +.chat-message .chat-callout.warning, +.chat-popout .chat-callout.warning, +#chat-log .chat-callout.warning { + background: rgba(245, 223, 220, 0.72); +} +.chat-message .confrontation-body, +.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; +} +.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); +} +.chat-message .chat-side-card.is-success, +.chat-popout .chat-side-card.is-success, +#chat-log .chat-side-card.is-success { + box-shadow: inset 3px 0 0 rgba(65, 107, 72, 0.6); +} +.chat-message .chat-side-card.is-failure, +.chat-popout .chat-side-card.is-failure, +#chat-log .chat-side-card.is-failure { + box-shadow: inset 3px 0 0 rgba(109, 41, 34, 0.6); +} +.chat-message .chat-side-head, +.chat-popout .chat-side-head, +#chat-log .chat-side-head { + display: flex; + justify-content: space-between; + gap: 0.5rem; + align-items: baseline; + margin-bottom: 0.2rem; +} +.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; + text-transform: uppercase; + letter-spacing: 0.06em; + 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-style: italic; +} +@media (max-width: 720px) { + .chat-message .chat-card-banner, + .chat-popout .chat-card-banner, + #chat-log .chat-card-banner { + grid-template-columns: 3rem 1fr; + } + .chat-message .chat-card-badge, + .chat-popout .chat-card-badge, + #chat-log .chat-card-badge { + grid-column: 1 / -1; + justify-self: start; + } + .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 { + grid-template-columns: 1fr 1fr; + } +} +/*# sourceMappingURL=les-oublies.css.map */ +/*# sourceMappingURL=les-oublies.css.map */ diff --git a/css/les-oublies.css.map b/css/les-oublies.css.map new file mode 100644 index 0000000..a015f33 --- /dev/null +++ b/css/les-oublies.css.map @@ -0,0 +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":"AAAA;EACE,aAAa,QAAb;EACA,kBAAA;EACA,gBAAA;EACA,kBAAA;EACA,yFAAyF,OAAO,WAAhG;;AAEF;EACE,aAAa,QAAb;EACA,kBAAA;EACA,gBAAA;EACA,kBAAA;EACA,yFAAyF,OAAO,WAAhG;;AAEF;EACE,aAAa,QAAb;EACA,kBAAA;EACA,gBAAA;EACA,kBAAA;EACA,yFAAyF,OAAO,WAAhG;;AAEF;EACE,aAAa,oBAAb;EACA,kBAAA;EACA,gBAAA;EACA,kBAAA;EACA,oHAAoH,OAAO,WAA3H;;AAEF;EACE,aAAa,oBAAb;EACA,kBAAA;EACA,gBAAA;EACA,kBAAA;EACA,oHAAoH,OAAO,WAA3H;;AAEF;EACE,aAAa,oBAAb;EACA,kBAAA;EACA,gBAAA;EACA,kBAAA;EACA,oHAAoH,OAAO,WAA3H;;AAEF;EACE,aAAa,oBAAb;EACA,kBAAA;EACA,gBAAA;EACA,kBAAA;EACA,oHAAoH,OAAO,WAA3H;;AAEF;EACE,aAAa,oBAAb;EACA,kBAAA;EACA,gBAAA;EACA,kBAAA;EACA,iGAAiG,OAAO,WAAxG;;ACpDF,iBAAiB;AACjB,iBAAiB,MAAO;EACtB,qBAAA;EACA,qBAAA;EACA,sBAAA;EACA,qCAAA;EACA,2CAAA;EACA,0CAAA;EACA,iBAAA;EACA,sBAAA;EACA,kBAAA;EACA,oBAAA;EACA,mBAAA;EACA,gCAAA;EACA,iCAAA;EACA,oCAAA;;AAGF,iBAAiB;EACf,OAAO,aAAP;EACA,aAAa,oCAAb;EACA,YACE,gFACA,gFACA,uEAHF;EAIA,kBAAA;;AAGF,iBAAiB,MAAO;EACtB,YACE,yEACA,8EAFF;EAGA,OAAO,aAAP;;AAGF;EACE,OAAO,aAAP;EACA,eAAA;EACA,kBAAA;;AAHF,iBAKE;EACE,kBAAA;;AANJ,iBASE,mBAAkB;EAChB,SAAS,EAAT;EACA,kBAAA;EACA,eAAA;EACA,2CAAA;EACA,mBAAA;EACA,oBAAA;EACA,+EAAA;;AAhBJ,iBAmBE;EACE,aAAA;EACA,gCAAA;EACA,WAAA;EACA,oBAAA;EACA,qBAAA;EACA,2BAAA;EACA,mBAAA;EACA,YACE,+EACA,2EAFF;EAGA,0CAAA;EACA,wBACc,iGADd;EAIA,kBAAA;EACA,gBAAA;;AApCJ,iBAuCE,aAAY;EACV,SAAS,EAAT;EACA,kBAAA;EACA,QAAA;EACA,YACE,6EACA,4EAFF;EAGA,0BAAA;EACA,oBAAA;;AA/CJ,iBAkDE;EACE,YAAA;EACA,aAAA;EACA,iBAAA;EACA,uCAAA;EACA,mBAAA;EACA,YAAY,yCAAZ;EACA,kFAAA;EAGA,kBAAA;EACA,UAAA;;AA7DJ,iBAgEE;AAhEF,iBAiEE;EACE,OAAA;EACA,kBAAA;EACA,UAAA;;AApEJ,iBAuEE;EACE,mBAAA;EACA,OAAO,eAAP;EACA,aAAa,eAAb;EACA,kBAAA;EACA,sBAAA;EACA,yBAAA;;AA7EJ,iBAgFE;EACE,SAAA;EACA,cAAA;;AAlFJ,iBAqFE,aAAa;AArFf,iBAsFE,eAAe,GAAG;EAChB,aAAa,sBAAsB,eAAnC;EACA,WAAW,0BAAX;EACA,sBAAA;EACA,cAAA;EACA,uBAAA;EACA,YAAA;EACA,gBAAA;EACA,UAAA;EACA,YAAA;;AA/FJ,iBAkGE;EACE,mBAAA;EACA,OAAO,kBAAP;EACA,kBAAA;EACA,kBAAA;;AAtGJ,iBAyGE;EACE,aAAA;EACA,SAAA;EACA,mBAAA;;AA5GJ,iBA+GE;EACE,uBAAuB,UAAU,eAAjC;;AAhHJ,iBAmHE;EACE,YACE,wBAAwB,iBAAiB,wBACzC,+DAFF;EAGA,wCAAA;EACA,mBAAA;EACA,0BAAA;EACA,mBAAA;EACA,8HAAA;EAIA,kBAAA;EACA,gBAAA;;AAhIJ,iBAmIE,YAAW;EACT,SAAS,EAAT;EACA,kBAAA;EACA,QAAA;EACA,YACE,qEACA,4EAFF;EAGA,oBAAA;;AA1IJ,iBA6IE,YAAY;AA7Id,iBA8IE,YAAY;EACV,aAAa,eAAb;EACA,sBAAA;EACA,yBAAA;EACA,cAAA;;AAlJJ,iBAqJE,YAAY;EACV,mBAAA;EACA,uBAAA;EACA,gDAAA;EACA,eAAA;;AAzJJ,iBA4JE,YAAY;EACV,mBAAA;EACA,kBAAA;;AA9JJ,iBAiKE;EACE,YACE,+EACA,mEAFF;;AAlKJ,iBAuKE;EACE,YACE,6EADF;;AAxKJ,iBA4KE;EACE,YACE,6EADF;;AA7KJ,iBAiLE;EACE,YACE,6EADF;;AAlLJ,iBAsLE;AAtLF,iBAuLE;AAvLF,iBAwLE;AAxLF,iBAyLE;EACE,aAAA;EACA,WAAA;EACA,mBAAA;EACA,8BAAA;EACA,eAAA;;AA9LJ,iBAiME;AAjMF,iBAkME;EACE,aAAA;EACA,mBAAA;EACA,YAAA;EACA,sBAAA;;AAtMJ,iBAyME,WAAW;AAzMb,iBA0ME,cAAc;EACZ,gBAAA;EACA,aAAa,eAAb;EACA,kBAAA;EACA,gBAAA;EACA,sBAAA;EACA,yBAAA;EACA,cAAA;;AAjNJ,iBAoNE,WAAW;EACT,aAAa,eAAb;EACA,kBAAA;EACA,OAAO,eAAP;;AAvNJ,iBA0NE;EACE,aAAA;EACA,uBAAuB,UAAU,eAAjC;EACA,iBAAA;;AA7NJ,iBAgOE;EACE,sBAAA;EACA,mBAAA;EACA,YAAY,2EAAZ;EACA,wCAAA;EACA,kDAAA;EACA,8BAAA;EACA,eAAA;;AAvOJ,iBA0OE,aAAa;EACX,gBAAA;;AA3OJ,iBA8OE;EACE,aAAA;EACA,sBAAA;EACA,YAAA;;AAjPJ,iBAoPE;EACE,aAAA;EACA,8BAAA;EACA,SAAA;EACA,sBAAA;EACA,yCAAA;EACA,mBAAA;EACA,YACE,+EACA,+DAFF;EAGA,mFAAA;EAGA,kBAAA;;AAjQJ,iBAoQE,WAAU;EACR,SAAS,EAAT;EACA,kBAAA;EACA,aAAA;EACA,WAAA;EACA,cAAA;EACA,UAAA;EACA,oBAAA;EACA,YAAY,wBAAwB,gBAAgB,gBAApD;EACA,YAAA;;AA7QJ,iBAgRE,WAAW,MAAK;EACd,oBAAA;;AAjRJ,iBAoRE,WAAW;AApRb,iBAqRE,gBAAgB;EACd,aAAa,eAAb;EACA,sBAAA;EACA,cAAA;;AAxRJ,iBA2RE;EACE,SAAA;EACA,oBAAA;;AA7RJ,iBAgSE,gBAAgB,GAAG;EACjB,mBAAA;;AAjSJ,iBAoSE;EACE,OAAO,kBAAP;EACA,kBAAA;EACA,kBAAA;;AAvSJ,iBA0SE;AA1SF,iBA2SE;EACE,eAAA;EACA,wCAAA;EACA,oBAAA;EACA,YACE,yCADF;EAEA,cAAA;EACA,aAAa,eAAb;EACA,kBAAA;EACA,sBAAA;EACA,yBAAA;EACA,sBAAA;EACA,mFAAA;EAGA,gFAAA;;AA1TJ,iBA6TE;EACE,OAAO,aAAP;;AA9TJ,iBA6TE,yBAGE;EACE,gBAAA;;AAjUN,iBA6TE,yBAOE,WAAW;AApUf,iBA6TE,yBAQE,WAAW;EACT,OAAA;;AAtUN,iBA0UE,OAAM;AA1UR,iBA2UE,OAAM;EACJ,WAAW,gBAAX;EACA,uCAAA;EACA,gFAAA;;AA9UJ,iBAmVE,MAAK;AAnVP,iBAoVE,MAAK;AApVP,iBAqVE;AArVF,iBAsVE;EACE,YAAY,6EAAZ;EACA,yCAAA;EACA,mBAAA;EACA,cAAA;EACA,aAAa,oCAAb;EACA,eAAA;EACA,kBAAA;EACA,uBAAA;EACA,kDAAA;;AA/VJ,iBAkWE,MAAK;EACH,qBAAA;;AAnWJ,iBAsWE,YAAY;AAtWd,iBAuWE,YAAY;AAvWd,iBAwWE,YAAY;AAxWd,iBAyWE;EACE,qCAAA;EACA,mBAAA;;AA3WJ,iBA8WE;AA9WF,iBA+WE;EACE,yCAAA;EACA,sBAAA;;AAjXJ,iBAoXE;AApXF,iBAqXE,OAAM;AArXR,iBAsXE,OAAM;EACJ,qBAAA;;AAvXJ,iBA0XE,OAAM;AA1XR,iBA2XE,OAAM;EACJ,YAAY,yCAAZ;EACA,cAAA;;AA7XJ,iBAgYE,iBAAiB;EACf,YACE,+EACA,0EAFF;;AAjYJ,iBAsYE,gBAAgB;EACd,YACE,+EACA,4EAFF;;AAKF,QAA0B;EAA1B,iBACE;EADF,iBAEE;IACE,0BAAA;;EAHJ,iBAME;IACE,0BAAA;;EAPJ,iBAUE;IACE,WAAA;IACA,gBAAA;;;AAKN,aAGE;AAFF,YAEE;AADF,SACE;EACE,2CAAA;EACA,8CAAA;EACA,uCAAA;EACA,sBAAA;EACA,uBAAA;EACA,uBAAA;EACA,wBAAA;EACA,OAAO,kBAAP;EACA,YACE,wBAAwB,uBAAuB,2BAC/C,mEAFF;EAGA,yCAAA;EACA,mBAAA;EACA,oBAAA;EACA,gBAAA;EACA,oFAAA;EAGA,kBAAA;EACA,gBAAA;;AAvBJ,aA0BE,uBAAsB;AAzBxB,YAyBE,uBAAsB;AAxBxB,SAwBE,uBAAsB;EACpB,SAAS,EAAT;EACA,kBAAA;EACA,QAAA;EACA,oBAAA;EACA,YACE,qEACA,6EAFF;;AA/BJ,aAoCE,uBAAsB;AAnCxB,YAmCE,uBAAsB;AAlCxB,SAkCE,uBAAsB;EACpB,uHAAA;;AArCJ,aA2CE,uBAAsB;AA1CxB,YA0CE,uBAAsB;AAzCxB,SAyCE,uBAAsB;EACpB,uHAAA;;AA5CJ,aAkDE;AAjDF,YAiDE;AAhDF,SAgDE;EACE,aAAA;EACA,oCAAA;EACA,WAAA;EACA,mBAAA;EACA,kBAAA;EACA,UAAA;;AAxDJ,aA2DE;AA1DF,YA0DE;AAzDF,SAyDE;EACE,WAAA;EACA,YAAA;EACA,iBAAA;EACA,mBAAA;EACA,uCAAA;EACA,0CAAA;EACA,YAAY,yCAAZ;;AAlEJ,aAqEE;AApEF,YAoEE;AAnEF,SAmEE;EACE,SAAA;EACA,aAAa,eAAb;EACA,kBAAA;EACA,sBAAA;EACA,yBAAA;EACA,OAAO,oBAAP;;AA3EJ,aA8EE,kBAAkB;AA7EpB,YA6EE,kBAAkB;AA5EpB,SA4EE,kBAAkB;EAChB,mBAAA;EACA,aAAa,eAAb;EACA,eAAA;EACA,sBAAA;EACA,yBAAA;EACA,cAAA;;AApFJ,aAuFE;AAtFF,YAsFE;AArFF,SAqFE;EACE,kBAAA;EACA,OAAO,mBAAP;EACA,kBAAA;;AA1FJ,aA6FE;AA5FF,YA4FE;AA3FF,SA2FE;EACE,iBAAA;EACA,wBAAA;EACA,oBAAA;EACA,kBAAkB,mBAAlB;EACA,oCAAA;EACA,cAAA;EACA,aAAa,eAAb;EACA,kBAAA;EACA,sBAAA;EACA,yBAAA;EACA,kBAAA;EACA,gBAAA;;AAzGJ,aA4GE,iBAAgB;AA3GlB,YA2GE,iBAAgB;AA1GlB,SA0GE,iBAAgB;EACd,qCAAA;EACA,cAAA;;AA9GJ,aAiHE,iBAAgB;AAhHlB,YAgHE,iBAAgB;AA/GlB,SA+GE,iBAAgB;EACd,qCAAA;EACA,cAAA;;AAnHJ,aAsHE,iBAAgB;AArHlB,YAqHE,iBAAgB;AApHlB,SAoHE,iBAAgB;EACd,qCAAA;;AAvHJ,aA0HE;AAzHF,YAyHE;AAxHF,SAwHE;EACE,kBAAA;EACA,UAAA;;AA5HJ,aA+HE,gBAAgB;AA9HlB,YA8HE,gBAAgB;AA7HlB,SA6HE,gBAAgB;EACd,mBAAA;;AAhIJ,aAmIE;AAlIF,YAkIE;AAjIF,SAiIE;EACE,aAAA;EACA,uBAAuB,UAAU,eAAjC;EACA,YAAA;EACA,wBAAA;;AAvIJ,aA0IE,mBAAmB;AAzIrB,YAyIE,mBAAmB;AAxIrB,SAwIE,mBAAmB;EACjB,wBAAA;EACA,mBAAA;EACA,qCAAA;EACA,kBAAkB,mBAAlB;;AA9IJ,aAiJE,mBAAmB;AAhJrB,YAgJE,mBAAmB;AA/IrB,SA+IE,mBAAmB;EACjB,cAAA;EACA,aAAa,eAAb;EACA,kBAAA;EACA,sBAAA;EACA,yBAAA;EACA,OAAO,oBAAP;;AAvJJ,aA0JE,mBAAmB;AAzJrB,YAyJE,mBAAmB;AAxJrB,SAwJE,mBAAmB;EACjB,kBAAA;EACA,OAAO,kBAAP;;AA5JJ,aA+JE;AA9JF,YA8JE;AA7JF,SA6JE;EACE,kBAAA;EACA,uBAAA;EACA,mBAAA;EACA,qCAAA;EACA,yCAAA;;AApKJ,aAuKE;AAtKF,YAsKE;AArKF,SAqKE;EACE,aAAA;EACA,eAAA;EACA,YAAA;EACA,kBAAA;;AA3KJ,aA8KE;AA7KF,YA6KE;AA5KF,SA4KE;EACE,iBAAA;EACA,wBAAA;EACA,mBAAA;EACA,oCAAA;EACA,yCAAA;EACA,mDAAA;;AApLJ,aAuLE,UAAS;AAtLX,YAsLE,UAAS;AArLX,SAqLE,UAAS;EACP,uCAAA;EACA,qCAAA;EACA,wFAAA;;AA1LJ,aA+LE,UAAU;AA9LZ,YA8LE,UAAU;AA7LZ,SA6LE,UAAU;AA/LZ,aAgME,UAAU;AA/LZ,YA+LE,UAAU;AA9LZ,SA8LE,UAAU;AAhMZ,aAiME,UAAU;AAhMZ,YAgME,UAAU;AA/LZ,SA+LE,UAAU;EACR,cAAA;;AAlMJ,aAqME,UAAU;AApMZ,YAoME,UAAU;AAnMZ,SAmME,UAAU;EACR,OAAO,kBAAP;;AAtMJ,aAyME,UAAU;AAxMZ,YAwME,UAAU;AAvMZ,SAuME,UAAU;EACR,OAAO,oBAAP;EACA,kBAAA;;AA3MJ,aA8ME;AA7MF,YA6ME;AA5MF,SA4ME;EACE,aAAA;EACA,eAAA;EACA,WAAA;EACA,kBAAA;;AAlNJ,aAqNE;AApNF,YAoNE;AAnNF,SAmNE;EACE,gBAAA;EACA,eAAA;EACA,wBAAA;EACA,mBAAA;EACA,qCAAA;EACA,kBAAkB,mBAAlB;;AA3NJ,aA8NE,cAAc;AA7NhB,YA6NE,cAAc;AA5NhB,SA4NE,cAAc;AA9NhB,aA+NE,cAAc;AA9NhB,YA8NE,cAAc;AA7NhB,SA6NE,cAAc;AA/NhB,aAgOE,cAAc;AA/NhB,YA+NE,cAAc;AA9NhB,SA8NE,cAAc;EACZ,cAAA;;AAjOJ,aAoOE,cAAc;AAnOhB,YAmOE,cAAc;AAlOhB,SAkOE,cAAc;EACZ,aAAa,eAAb;EACA,kBAAA;EACA,sBAAA;EACA,yBAAA;EACA,OAAO,oBAAP;;AAzOJ,aA4OE,cAAc;AA3OhB,YA2OE,cAAc;AA1OhB,SA0OE,cAAc;EACZ,OAAO,kBAAP;;AA7OJ,aAgPE,cAAc;AA/OhB,YA+OE,cAAc;AA9OhB,SA8OE,cAAc;EACZ,OAAO,mBAAP;EACA,kBAAA;;AAlPJ,aAqPE,cAAa;AApPf,YAoPE,cAAa;AAnPf,SAmPE,cAAa;EACX,qCAAA;;AAtPJ,aAyPE;AAxPF,YAwPE;AAvPF,SAuPE;EACE,aAAA;EACA,uBAAuB,UAAU,eAAjC;EACA,YAAA;EACA,kBAAA;;AA7PJ,aAgQE;AA/PF,YA+PE;AA9PF,SA8PE;EACE,wBAAA;EACA,mBAAA;EACA,kBAAkB,mBAAlB;EACA,qCAAA;;AApQJ,aAuQE,gBAAe;AAtQjB,YAsQE,gBAAe;AArQjB,SAqQE,gBAAe;EACb,gDAAA;;AAxQJ,aA2QE,gBAAe;AA1QjB,YA0QE,gBAAe;AAzQjB,SAyQE,gBAAe;EACb,gDAAA;;AA5QJ,aA+QE;AA9QF,YA8QE;AA7QF,SA6QE;EACE,aAAA;EACA,8BAAA;EACA,WAAA;EACA,qBAAA;EACA,qBAAA;;AApRJ,aAuRE,gBAAgB;AAtRlB,YAsRE,gBAAgB;AArRlB,SAqRE,gBAAgB;EACd,SAAA;EACA,aAAa,eAAb;EACA,kBAAA;EACA,yBAAA;EACA,sBAAA;EACA,cAAA;;AA7RJ,aAgSE;AA/RF,YA+RE;AA9RF,SA8RE;EACE,OAAO,mBAAP;EACA,kBAAA;EACA,kBAAA;;AAGF,QAA0B;EAA1B,aACE;EADF,YACE;EADF,SACE;IACE,+BAAA;;EAFJ,aAKE;EALF,YAKE;EALF,SAKE;IACE,mBAAA;IACA,mBAAA;;EAPJ,aAUE;EAVF,YAUE;EAVF,SAUE;EAVF,aAWE;EAXF,YAWE;EAXF,SAWE;IACE,8BAAA","file":"les-oublies.css","sourcesContent":[]} \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..112bf2f --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,26 @@ +import gulp from "gulp" +import less from "gulp-less" +import sourcemaps from "gulp-sourcemaps" + +const paths = { + styles: { + src: "less/**/*.less", + entry: "less/les-oublies.less", + dest: "css/", + }, +} + +export function styles() { + return gulp.src(paths.styles.entry) + .pipe(sourcemaps.init()) + .pipe(less()) + .pipe(sourcemaps.write(".")) + .pipe(gulp.dest(paths.styles.dest)) +} + +export function watch() { + gulp.watch(paths.styles.src, styles) +} + +export const build = gulp.series(styles) +export default gulp.series(build, watch) diff --git a/lang/fr.json b/lang/fr.json new file mode 100644 index 0000000..4b66c86 --- /dev/null +++ b/lang/fr.json @@ -0,0 +1,191 @@ +{ + "TYPES": { + "Actor": { + "personnage": "Personnage", + "compagnie": "Compagnie", + "creature": "Créature" + }, + "Item": { + "race": "Race", + "tribu": "Tribu", + "metier": "Métier", + "competence": "Compétence", + "sortilege": "Sortilège", + "arme": "Arme", + "armure": "Armure", + "equipement": "Équipement", + "pouvoircompagnie": "Pouvoir de compagnie" + } + }, + "LESOUBLIES": { + "ui": { + "editMode": "Mode édition", + "playMode": "Mode lecture", + "newItem": "Nouvel élément", + "newActor": "Nouvel acteur", + "roll": "Jet", + "creation": "Création", + "competences": "Compétences", + "magie": "Magie", + "equipement": "Équipement", + "notes": "Notes", + "membres": "Membres", + "combat": "Combat", + "pouvoir": "Pouvoir", + "liens": "Liens", + "vie": "Points de vie", + "songes": "Songes", + "cauchemar": "Cauchemar", + "profils": "Profils", + "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." + }, + "rolls": { + "roll": "Lancer", + "test": "Test", + "confrontation": "Confrontation", + "initiative": "Initiative", + "attacker": "Acteur", + "defender": "Adversaire", + "label": "Libellé", + "score": "Valeur", + "difficulty": "Difficulté", + "rollMode": "Mode de jet", + "extraDie": "Dé supplémentaire", + "selectedDie": "Dé retenu", + "natural": "Résultat naturel", + "final": "Résultat final", + "threshold": "Seuil", + "margin": "Marge", + "resolution": "Résolution", + "result": "Verdict", + "resourceState": "Réserves", + "debt": "Dette", + "breakdown": "Détail", + "success": "Succès", + "failure": "Échec", + "safeChoice": "Choix prudent", + "riskyChoice": "Choix risqué", + "noDebt": "Aucune dette", + "debtGain": "+1 dette de {type}", + "notEnoughResource": "{actor} n'a pas assez de {resource}.", + "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.", + "testHint": "Le jet lance les dés nécessaires puis vous laisse choisir le dé retenu quand Songes et Cauchemar sont en concurrence.", + "confrontationHint": "Saisissez les deux valeurs à opposer. Chaque camp résout son jet avec ses propres dés et sa propre difficulté.", + "choiceHint": "Choisissez le dé retenu après avoir vu les résultats naturels.", + "confrontationType": "Type de confrontation", + "confrontationTypes": { + "directe": "Directe", + "differee": "Différée" + }, + "rollModes": { + "dual": "2d12 Songes / Cauchemar", + "songes": "2d12 Songes", + "cauchemar": "2d12 Cauchemar", + "single": "1d12" + }, + "extraDice": { + "none": "Aucun", + "songes": "1 dé de Songes", + "cauchemar": "1 dé de Cauchemar" + }, + "dice": { + "songes": "Songes", + "cauchemar": "Cauchemar", + "neutral": "Neutre" + }, + "dialogs": { + "testTitle": "Jet de test", + "initiativeTitle": "Jet d'initiative", + "confrontationTitle": "Jet de confrontation", + "choiceTitle": "Choisir le dé retenu" + }, + "outcomes": { + "attacker-success": "L'acteur passe, l'adversaire rate.", + "defender-success": "L'adversaire l'emporte seul.", + "attacker-advantage": "L'acteur prend l'avantage.", + "defender-advantage": "L'adversaire prend l'avantage.", + "tie": "Égalité : la fiction tranche." + } + }, + "labels": { + "race": "Race", + "tribu": "Tribu", + "metier": "Métier", + "taille": "Taille", + "esperanceVie": "Espérance de vie", + "motsCles": "Mots-clés", + "langue": "Langue", + "territoire": "Territoire", + "philosophie": "Philosophie", + "fierte": "Fierté", + "mytheNature": "Mythe de Dame Nature", + "mytheEdenia": "Mythes d'Edenia", + "reglesSpeciales": "Règles spéciales", + "domaines": "Domaines", + "profil": "Profil", + "base": "Base", + "valeurFinale": "Valeur finale", + "cout": "Coût", + "preparation": "Préparation", + "duree": "Durée", + "portee": "Portée", + "aire": "Aire d'effet", + "cumul": "Cumul", + "proprietes": "Propriétés", + "prix": "Prix", + "quantite": "Quantité", + "equipe": "Équipé", + "categorie": "Catégorie", + "origine": "Origine", + "description": "Description", + "notes": "Notes", + "gmnotes": "Notes MJ", + "compagnie": "Compagnie", + "capitaine": "Capitaine", + "pouvoir": "Pouvoir", + "ombreDuTourment": "Ombre du Tourment", + "identite": "Identité", + "age": "Âge", + "sexe": "Sexe", + "xp": "Expérience", + "ecorces": "Écorces", + "hpMax": "PV max", + "hpAffichage": "PV affichés", + "powerName": "Nom du pouvoir", + "powerEffect": "Effet", + "membresIds": "Identifiants des membres", + "liensNarratifs": "Liens narratifs", + "detteSonges": "Dette de Songes", + "detteCauchemar": "Dette de Cauchemar", + "creditSonges": "Crédits Songes", + "creditCauchemar": "Crédits Cauchemar", + "pointsSonges": "Points de Songes", + "pointsCauchemar": "Points de Cauchemar", + "degats": "Dégâts", + "sortilegesSonges": "Sortilèges de Songes", + "sortilegesCauchemar": "Sortilèges de Cauchemar", + "profilsOptionnels": "Profils (optionnels)", + "reserveSongesCompagnie": "Réserve de Songes", + "activation": "Activation", + "capitaineVisible": "Vue du capitaine requise", + "capitaineTemoin": "Capitaine avec témoin", + "pouvoirCompagnieActif": "Pouvoir de compagnie actif" + }, + "profiles": { + "artiste": "Artiste", + "athlete": "Athlète", + "chasseur": "Chasseur", + "faiseur": "Faiseur", + "forceNature": "Force de la nature", + "guerrier": "Guerrier", + "mystique": "Mystique", + "ombre": "Ombre", + "savant": "Savant" + } + } +} diff --git a/less/components/sheets.less b/less/components/sheets.less new file mode 100644 index 0000000..776b05a --- /dev/null +++ b/less/components/sheets.less @@ -0,0 +1,760 @@ +@import url("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"); + +.fvtt-les-oublies.sheet, +.fvtt-les-oublies.sheet .window-content { + --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-copper: #8d5c3b; + --lo-blood: #6d2922; + --lo-shadow: rgba(0, 0, 0, 0.42); + --lo-line: rgba(110, 77, 53, 0.4); + --lo-glow: rgba(207, 176, 106, 0.22); +} + +.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); +} + +.fvtt-les-oublies { + color: var(--lo-ink); + padding: 0.4rem; + position: relative; + + .les-oublies-sheet { + position: relative; + } + + .les-oublies-sheet::before { + content: ""; + position: absolute; + inset: -0.35rem; + border: 1px solid rgba(207, 176, 106, 0.18); + border-radius: 18px; + pointer-events: none; + box-shadow: 0 0 0 1px rgba(36, 23, 14, 0.5), inset 0 0 40px rgba(0, 0, 0, 0.12); + } + + .hero-banner { + display: grid; + grid-template-columns: 110px 1fr; + gap: 1.2rem; + align-items: stretch; + margin-bottom: 1.1rem; + padding: 1rem 1.1rem 1.1rem; + border-radius: 18px; + 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); + position: relative; + overflow: hidden; + } + + .hero-banner::after { + content: ""; + position: absolute; + inset: 0; + background: + linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.16), transparent), + radial-gradient(circle at 85% 15%, rgba(78, 107, 76, 0.16), transparent 18%); + mix-blend-mode: soft-light; + pointer-events: none; + } + + .profile-img { + width: 110px; + height: 132px; + object-fit: cover; + border: 2px solid rgba(85, 55, 34, 0.7); + border-radius: 14px; + 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; + z-index: 1; + } + + .hero-copy, + .header-fields { + flex: 1; + position: relative; + z-index: 1; + } + + .sheet-kicker { + margin: 0 0 0.18rem; + color: var(--lo-blood); + font-family: "Cinzel", serif; + font-size: 0.78rem; + letter-spacing: 0.24em; + text-transform: uppercase; + } + + .sheet-title { + margin: 0; + line-height: 1; + } + + .sheet-title input, + .header-fields h1 input { + font-family: "IM Fell English SC", "Cinzel", serif; + font-size: clamp(2rem, 2.6vw, 2.8rem); + letter-spacing: 0.04em; + color: #2b1b14; + background: transparent; + border: none; + box-shadow: none; + padding: 0; + height: auto; + } + + .sheet-subtitle { + margin: 0.35rem 0 0; + color: var(--lo-ink-soft); + font-size: 1.05rem; + font-style: italic; + } + + .sheet-grid { + display: grid; + gap: 1rem; + margin-bottom: 1rem; + } + + .sheet-grid-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .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; + 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; + } + + .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; + } + + .sheet-card h2, + .sheet-card h3 { + font-family: "Cinzel", serif; + letter-spacing: 0.08em; + text-transform: uppercase; + color: #3a251a; + } + + .sheet-card h2 { + margin: 0 0 0.75rem; + padding-bottom: 0.35rem; + border-bottom: 1px solid rgba(109, 41, 34, 0.18); + font-size: 1rem; + } + + .sheet-card h3 { + margin: 0 0 0.55rem; + font-size: 0.84rem; + } + + .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%); + } + + .profiles-card { + background: + linear-gradient(180deg, rgba(245, 238, 223, 0.95), rgba(232, 223, 201, 0.92)); + } + + .ledger-card { + background: + linear-gradient(180deg, rgba(251, 244, 232, 0.95), rgba(236, 224, 198, 0.92)); + } + + .notes-card { + background: + linear-gradient(180deg, rgba(240, 229, 207, 0.98), rgba(223, 207, 177, 0.95)); + } + + .sheet-actions, + .embed-buttons, + .item-controls, + .section-title-row { + display: flex; + gap: 0.5rem; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + } + + .field-row, + .profile-cell { + display: flex; + align-items: center; + gap: 0.65rem; + margin-bottom: 0.65rem; + } + + .field-row label, + .profile-cell label { + min-width: 10rem; + font-family: "Cinzel", serif; + font-size: 0.74rem; + font-weight: 700; + letter-spacing: 0.08em; + text-transform: uppercase; + color: #51392b; + } + + .field-row span { + font-family: "Cinzel", serif; + font-size: 0.84rem; + color: var(--lo-blood); + } + + .profile-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0.75rem 1rem; + } + + .profile-cell { + padding: 0.7rem 0.8rem; + border-radius: 14px; + 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); + justify-content: space-between; + flex-wrap: wrap; + } + + .group-block + .group-block { + margin-top: 1rem; + } + + .item-list { + display: flex; + flex-direction: column; + gap: 0.65rem; + } + + .item-card { + display: flex; + justify-content: space-between; + gap: 1rem; + padding: 0.8rem 0.9rem; + 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; + } + + .item-card::before { + content: ""; + position: absolute; + left: 0.55rem; + top: 0.6rem; + bottom: 0.6rem; + width: 3px; + border-radius: 999px; + background: linear-gradient(180deg, var(--lo-gold), var(--lo-blood)); + opacity: 0.8; + } + + .item-card > div:first-child { + padding-left: 0.6rem; + } + + .item-card strong, + .reference-list strong { + font-family: "Cinzel", serif; + letter-spacing: 0.04em; + color: #3d281d; + } + + .reference-list { + margin: 0; + padding-left: 1.2rem; + } + + .reference-list li + li { + margin-top: 0.35rem; + } + + .help-text { + color: var(--lo-ink-soft); + font-size: 0.96rem; + font-style: italic; + } + + .mode-button, + 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; + text-transform: uppercase; + padding: 0.5rem 0.9rem; + 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; + } + + .les-oublies-roll-dialog { + color: var(--lo-ink); + + .sheet-card { + margin-bottom: 0; + } + + .field-row input, + .field-row select { + flex: 1; + } + } + + button:hover, + 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); + } + + input[type="text"], + input[type="number"], + select, + 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: 1rem; + min-height: 2.1rem; + padding: 0.2rem 0.65rem; + box-shadow: inset 0 1px 3px rgba(72, 46, 30, 0.08); + } + + input[type="checkbox"] { + accent-color: #6d2922; + } + + .sheet-card .editor, + .sheet-card .editor-content, + .sheet-card .editor-container, + prose-mirror { + background: rgba(255, 252, 246, 0.62); + border-radius: 12px; + } + + .prosemirror, + prose-mirror { + border: 1px solid rgba(111, 84, 55, 0.18); + padding: 0.6rem 0.7rem; + } + + a, + button[data-action="rollProfile"], + button[data-action="rollSkill"] { + text-decoration: none; + } + + button[data-action="rollProfile"], + button[data-action="rollSkill"] { + background: linear-gradient(180deg, #6a2f29, #421914); + color: #f8ead3; + } + + .compagnie-sheet .hero-banner { + background: + linear-gradient(135deg, rgba(245, 235, 210, 0.98), rgba(224, 205, 173, 0.93)), + linear-gradient(180deg, rgba(207, 176, 106, 0.18), rgba(58, 37, 26, 0.08)); + } + + .creature-sheet .hero-banner { + background: + linear-gradient(135deg, rgba(236, 226, 206, 0.96), rgba(213, 196, 166, 0.92)), + radial-gradient(circle at 90% 15%, rgba(109, 41, 34, 0.14), transparent 28%); + } + + @media (max-width: 900px) { + .sheet-grid-2, + .profile-grid { + grid-template-columns: 1fr; + } + + .hero-banner { + grid-template-columns: 1fr; + } + + .profile-img { + width: 100%; + max-width: 110px; + } + } +} + +.chat-message, +.chat-popout, +#chat-log { + .les-oublies-chat-card { + --lo-chat-bg-top: rgba(249, 241, 227, 0.98); + --lo-chat-bg-bottom: rgba(226, 209, 178, 0.96); + --lo-chat-line: rgba(114, 80, 55, 0.26); + --lo-chat-ink: #2f1f17; + --lo-chat-soft: #6a5142; + --lo-chat-gold: #cfb06a; + --lo-chat-blood: #6d2922; + 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; + 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; + } + + .les-oublies-chat-card::before { + content: ""; + position: absolute; + inset: 0; + pointer-events: none; + background: + linear-gradient(180deg, rgba(255, 255, 255, 0.16), transparent 24%), + radial-gradient(circle at 100% 0, rgba(207, 176, 106, 0.14), transparent 28%); + } + + .les-oublies-chat-card.is-success { + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.56), + 0 10px 28px rgba(0, 0, 0, 0.14), + 0 0 0 1px rgba(65, 107, 72, 0.18); + } + + .les-oublies-chat-card.is-failure { + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.56), + 0 10px 28px rgba(0, 0, 0, 0.14), + 0 0 0 1px rgba(109, 41, 34, 0.18); + } + + .chat-card-banner { + display: grid; + grid-template-columns: 3rem 1fr auto; + gap: 0.8rem; + align-items: center; + position: relative; + z-index: 1; + } + + .chat-card-portrait { + width: 3rem; + height: 3rem; + object-fit: cover; + border-radius: 14px; + 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); + } + + .chat-card-kicker { + margin: 0; + font-family: "Cinzel", serif; + font-size: 0.66rem; + letter-spacing: 0.18em; + text-transform: uppercase; + color: var(--lo-chat-blood); + } + + .chat-card-header h3 { + margin: 0.08rem 0 0; + font-family: "Cinzel", serif; + font-size: 1rem; + letter-spacing: 0.06em; + text-transform: uppercase; + color: #3a251a; + } + + .chat-card-subtitle { + margin: 0.2rem 0 0; + color: var(--lo-chat-soft); + font-size: 0.95rem; + } + + .chat-card-badge { + align-self: start; + padding: 0.38rem 0.65rem; + 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; + text-transform: uppercase; + text-align: center; + max-width: 13rem; + } + + .chat-card-badge.success { + background: rgba(226, 243, 225, 0.74); + color: #29422d; + } + + .chat-card-badge.failure { + background: rgba(245, 223, 220, 0.74); + color: #6d2922; + } + + .chat-card-badge.neutral { + background: rgba(242, 233, 212, 0.78); + } + + .chat-card-body { + position: relative; + z-index: 1; + } + + .chat-card-body p { + margin: 0.45rem 0 0; + } + + .roll-summary-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0.55rem; + margin: 0.85rem 0 0.8rem; + } + + .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); + } + + .roll-summary-grid span { + display: block; + font-family: "Cinzel", serif; + font-size: 0.66rem; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--lo-chat-blood); + } + + .roll-summary-grid strong { + font-size: 1.08rem; + color: var(--lo-chat-ink); + } + + .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); + } + + .dice-strip { + display: flex; + flex-wrap: wrap; + gap: 0.55rem; + margin-top: 0.8rem; + } + + .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); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.52); + } + + .die-chip.selected { + border-color: rgba(207, 176, 106, 0.86); + background: rgba(255, 247, 228, 0.92); + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.52), + 0 0 0 1px rgba(207, 176, 106, 0.28); + } + + .die-chip strong, + .die-chip em, + .die-chip span { + display: block; + } + + .die-chip span { + color: var(--lo-chat-ink); + } + + .die-chip em { + color: var(--lo-chat-blood); + font-size: 0.82rem; + } + + .chat-callouts { + display: flex; + flex-wrap: wrap; + gap: 0.6rem; + margin-top: 0.8rem; + } + + .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); + } + + .chat-callout span, + .chat-callout strong, + .chat-callout em { + display: block; + } + + .chat-callout span { + font-family: "Cinzel", serif; + font-size: 0.66rem; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--lo-chat-blood); + } + + .chat-callout strong { + color: var(--lo-chat-ink); + } + + .chat-callout em { + color: var(--lo-chat-soft); + font-size: 0.88rem; + } + + .chat-callout.warning { + background: rgba(245, 223, 220, 0.72); + } + + .confrontation-body { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 0.85rem; + margin-top: 0.8rem; + } + + .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); + } + + .chat-side-card.is-success { + box-shadow: inset 3px 0 0 rgba(65, 107, 72, 0.6); + } + + .chat-side-card.is-failure { + box-shadow: inset 3px 0 0 rgba(109, 41, 34, 0.6); + } + + .chat-side-head { + display: flex; + justify-content: space-between; + gap: 0.5rem; + align-items: baseline; + margin-bottom: 0.2rem; + } + + .chat-side-head h2 { + margin: 0; + font-family: "Cinzel", serif; + font-size: 0.92rem; + text-transform: uppercase; + letter-spacing: 0.06em; + color: #3a251a; + } + + .chat-side-mode { + color: var(--lo-chat-soft); + font-size: 0.86rem; + font-style: italic; + } + + @media (max-width: 720px) { + .chat-card-banner { + grid-template-columns: 3rem 1fr; + } + + .chat-card-badge { + grid-column: 1 / -1; + justify-self: start; + } + + .roll-summary-grid, + .confrontation-body { + grid-template-columns: 1fr 1fr; + } + } +} diff --git a/less/les-oublies.less b/less/les-oublies.less new file mode 100644 index 0000000..1a6f012 --- /dev/null +++ b/less/les-oublies.less @@ -0,0 +1 @@ +@import "components/sheets.less"; diff --git a/modules/applications/sheets/_module.mjs b/modules/applications/sheets/_module.mjs new file mode 100644 index 0000000..da9d8cb --- /dev/null +++ b/modules/applications/sheets/_module.mjs @@ -0,0 +1,10 @@ +export { default as LesOubliesPersonnageSheet } from "./personnage-sheet.mjs" +export { default as LesOubliesCompagnieSheet } from "./compagnie-sheet.mjs" +export { default as LesOubliesCreatureSheet } from "./creature-sheet.mjs" +export { default as LesOubliesReferenceItemSheet } from "./reference-item-sheet.mjs" +export { default as LesOubliesCompetenceSheet } from "./competence-sheet.mjs" +export { default as LesOubliesSortilegeSheet } from "./sortilege-sheet.mjs" +export { default as LesOubliesArmeSheet } from "./arme-sheet.mjs" +export { default as LesOubliesArmureSheet } from "./armure-sheet.mjs" +export { default as LesOubliesEquipementSheet } from "./equipement-sheet.mjs" +export { default as LesOubliesPouvoirCompagnieSheet } from "./pouvoir-compagnie-sheet.mjs" diff --git a/modules/applications/sheets/arme-sheet.mjs b/modules/applications/sheets/arme-sheet.mjs new file mode 100644 index 0000000..efb7a93 --- /dev/null +++ b/modules/applications/sheets/arme-sheet.mjs @@ -0,0 +1,9 @@ +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", + }, + } +} diff --git a/modules/applications/sheets/armure-sheet.mjs b/modules/applications/sheets/armure-sheet.mjs new file mode 100644 index 0000000..b6b4849 --- /dev/null +++ b/modules/applications/sheets/armure-sheet.mjs @@ -0,0 +1,9 @@ +import LesOubliesItemSheet from "./base-item-sheet.mjs" + +export default class LesOubliesArmureSheet extends LesOubliesItemSheet { + static PARTS = { + sheet: { + template: "systems/fvtt-les-oublies/templates/item-armure-sheet.hbs", + }, + } +} diff --git a/modules/applications/sheets/base-actor-sheet.mjs b/modules/applications/sheets/base-actor-sheet.mjs new file mode 100644 index 0000000..8506454 --- /dev/null +++ b/modules/applications/sheets/base-actor-sheet.mjs @@ -0,0 +1,186 @@ +const { HandlebarsApplicationMixin } = foundry.applications.api + +import { LesOubliesUtility } from "../../les-oublies-utility.js" + +export default class LesOubliesActorSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ActorSheetV2) { + static SHEET_MODES = { EDIT: 0, PLAY: 1 } + + static DEFAULT_OPTIONS = { + classes: ["fvtt-les-oublies", "sheet", "actor"], + position: { + width: 980, + height: 860, + }, + window: { + resizable: true, + }, + form: { + submitOnChange: true, + closeOnSubmit: false, + }, + dragDrop: [{ dragSelector: ".item-card", dropSelector: "form" }], + actions: { + toggleSheet: LesOubliesActorSheet.#onToggleSheet, + editImage: LesOubliesActorSheet.#onEditImage, + createItem: LesOubliesActorSheet.#onCreateItem, + editItem: LesOubliesActorSheet.#onEditItem, + deleteItem: LesOubliesActorSheet.#onDeleteItem, + openRoll: LesOubliesActorSheet.#onOpenRoll, + openConfrontation: LesOubliesActorSheet.#onOpenConfrontation, + openInitiative: LesOubliesActorSheet.#onOpenInitiative, + rollProfile: LesOubliesActorSheet.#onRollProfile, + rollSkill: LesOubliesActorSheet.#onRollSkill, + useWeapon: LesOubliesActorSheet.#onUseWeapon, + resolveWeaponDamage: LesOubliesActorSheet.#onResolveWeaponDamage, + useSpell: LesOubliesActorSheet.#onUseSpell, + openCombatPreset: LesOubliesActorSheet.#onOpenCombatPreset, + openThreadHarvest: LesOubliesActorSheet.#onOpenThreadHarvest, + }, + } + + _sheetMode = this.constructor.SHEET_MODES.EDIT + + get isEditMode() { + return this._sheetMode === this.constructor.SHEET_MODES.EDIT + } + + get isPlayMode() { + return this._sheetMode === this.constructor.SHEET_MODES.PLAY + } + + async _prepareContext() { + return { + actor: this.document, + system: this.document.system, + source: this.document.toObject(), + fields: this.document.schema.fields, + systemFields: this.document.system.schema.fields, + isEditable: this.isEditable, + 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 }), + } + } + + _onRender(context, options) { + super._onRender(context, options) + } + + _canDragStart() { + return this.isEditable + } + + _canDragDrop() { + return this.isEditable + } + + async _onDrop(event) { + const data = TextEditor.getDragEventData(event) + if (data.type !== "Item" || !data.uuid) return + const item = await fromUuid(data.uuid) + if (!item) return + return this._onDropItem(item) + } + + async _onDropItem(item) { + const itemData = item.toObject() + delete itemData._id + return this.document.createEmbeddedDocuments("Item", [itemData], { renderSheet: false }) + } + + static #onToggleSheet() { + const modes = this.constructor.SHEET_MODES + this._sheetMode = this.isEditMode ? modes.PLAY : modes.EDIT + this.render() + } + + static async #onEditImage(event, target) { + const attr = target.dataset.edit + const current = foundry.utils.getProperty(this.document, attr) + const fp = new FilePicker({ + current, + type: "image", + callback: (path) => this.document.update({ [attr]: path }), + top: this.position.top + 40, + left: this.position.left + 10, + }) + return fp.browse() + } + + static async #onCreateItem(event, target) { + const type = target.dataset.type + if (!type) return + const label = game.i18n.localize(`TYPES.Item.${type}`) + return this.document.createEmbeddedDocuments("Item", [{ + name: label, + type, + img: LesOubliesUtility.getDefaultItemImage(type), + }]) + } + + static async #onEditItem(event, target) { + const itemId = target.dataset.itemId + const item = this.document.items.get(itemId) + if (item) item.sheet.render(true) + } + + static async #onDeleteItem(event, target) { + const itemId = target.dataset.itemId + const item = this.document.items.get(itemId) + if (item) await item.delete() + } + + static async #onOpenRoll() { + await this.document.openTestRollDialog() + } + + static async #onOpenConfrontation() { + await this.document.openConfrontationRollDialog() + } + + static async #onOpenInitiative() { + await this.document.openInitiativeRollDialog() + } + + static async #onRollProfile(event, target) { + const profileKey = target.dataset.profileKey + if (!profileKey) return + await this.document.rollProfile(profileKey) + } + + static async #onRollSkill(event, target) { + const itemId = target.dataset.itemId + if (!itemId) return + await this.document.rollCompetence(itemId) + } + + static async #onUseWeapon(event, target) { + const itemId = target.dataset.itemId + if (!itemId) return + await this.document.openAttackRollDialog({ itemId }) + } + + static async #onResolveWeaponDamage(event, target) { + const itemId = target.dataset.itemId + if (!itemId) return + await this.document.openDamageDialog({ itemId }) + } + + static async #onUseSpell(event, target) { + const itemId = target.dataset.itemId + if (!itemId) return + await this.document.openSpellActivationDialog(itemId) + } + + static async #onOpenCombatPreset(event, target) { + const actionKey = target.dataset.preset + if (!actionKey) return + await this.document.openCombatPresetDialog(actionKey) + } + + static async #onOpenThreadHarvest() { + await this.document.openThreadHarvestDialog() + } +} diff --git a/modules/applications/sheets/base-item-sheet.mjs b/modules/applications/sheets/base-item-sheet.mjs new file mode 100644 index 0000000..d056e4c --- /dev/null +++ b/modules/applications/sheets/base-item-sheet.mjs @@ -0,0 +1,69 @@ +const { HandlebarsApplicationMixin } = foundry.applications.api + +export default class LesOubliesItemSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) { + static SHEET_MODES = { EDIT: 0, PLAY: 1 } + + static DEFAULT_OPTIONS = { + classes: ["fvtt-les-oublies", "sheet", "item"], + position: { + width: 760, + height: 720, + }, + window: { + resizable: true, + }, + form: { + submitOnChange: true, + closeOnSubmit: false, + }, + actions: { + toggleSheet: LesOubliesItemSheet.#onToggleSheet, + editImage: LesOubliesItemSheet.#onEditImage, + }, + } + + _sheetMode = this.constructor.SHEET_MODES.EDIT + + get isEditMode() { + return this._sheetMode === this.constructor.SHEET_MODES.EDIT + } + + get isPlayMode() { + return this._sheetMode === this.constructor.SHEET_MODES.PLAY + } + + async _prepareContext() { + return { + item: this.document, + system: this.document.system, + source: this.document.toObject(), + fields: this.document.schema.fields, + systemFields: this.document.system.schema.fields, + isEditable: this.isEditable, + 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 }), + } + } + + static #onToggleSheet() { + const modes = this.constructor.SHEET_MODES + this._sheetMode = this.isEditMode ? modes.PLAY : modes.EDIT + this.render() + } + + static async #onEditImage(event, target) { + const attr = target.dataset.edit + const current = foundry.utils.getProperty(this.document, attr) + const fp = new FilePicker({ + current, + type: "image", + callback: (path) => this.document.update({ [attr]: path }), + top: this.position.top + 40, + left: this.position.left + 10, + }) + return fp.browse() + } +} diff --git a/modules/applications/sheets/compagnie-sheet.mjs b/modules/applications/sheets/compagnie-sheet.mjs new file mode 100644 index 0000000..82d7833 --- /dev/null +++ b/modules/applications/sheets/compagnie-sheet.mjs @@ -0,0 +1,33 @@ +import LesOubliesActorSheet from "./base-actor-sheet.mjs" + +export default class LesOubliesCompagnieSheet extends LesOubliesActorSheet { + static DEFAULT_OPTIONS = { + ...super.DEFAULT_OPTIONS, + classes: [...super.DEFAULT_OPTIONS.classes, "compagnie"], + window: { + ...super.DEFAULT_OPTIONS.window, + title: "TYPES.Actor.compagnie", + }, + } + + static PARTS = { + sheet: { + template: "systems/fvtt-les-oublies/templates/actor-compagnie-sheet.hbs", + }, + } + + async _prepareContext() { + const context = await super._prepareContext() + 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 + context.powers = this.document.getEmbeddedItems("pouvoircompagnie") + context.primaryPower = context.powers[0] ?? null + context.links = (this.document.system.links ?? []).map((link) => ({ + ...link, + sourceLabel: game.actors.get(link.sourceId)?.name ?? link.sourceId, + targetLabel: game.actors.get(link.targetId)?.name ?? link.targetId, + })) + return context + } +} diff --git a/modules/applications/sheets/competence-sheet.mjs b/modules/applications/sheets/competence-sheet.mjs new file mode 100644 index 0000000..a21923f --- /dev/null +++ b/modules/applications/sheets/competence-sheet.mjs @@ -0,0 +1,9 @@ +import LesOubliesItemSheet from "./base-item-sheet.mjs" + +export default class LesOubliesCompetenceSheet extends LesOubliesItemSheet { + static PARTS = { + sheet: { + template: "systems/fvtt-les-oublies/templates/item-competence-sheet.hbs", + }, + } +} diff --git a/modules/applications/sheets/creature-sheet.mjs b/modules/applications/sheets/creature-sheet.mjs new file mode 100644 index 0000000..35f7e1f --- /dev/null +++ b/modules/applications/sheets/creature-sheet.mjs @@ -0,0 +1,29 @@ +import LesOubliesActorSheet from "./base-actor-sheet.mjs" + +export default class LesOubliesCreatureSheet extends LesOubliesActorSheet { + static DEFAULT_OPTIONS = { + ...super.DEFAULT_OPTIONS, + classes: [...super.DEFAULT_OPTIONS.classes, "creature"], + window: { + ...super.DEFAULT_OPTIONS.window, + title: "TYPES.Actor.creature", + }, + } + + static PARTS = { + sheet: { + template: "systems/fvtt-les-oublies/templates/actor-creature-sheet.hbs", + }, + } + + async _prepareContext() { + const context = await super._prepareContext() + context.derived = this.document.getDerivedOverview() + context.skillGroups = this.document.getGroupedCompetences() + context.spells = this.document.getEmbeddedItems("sortilege") + context.weapons = this.document.getEmbeddedItems("arme") + context.armors = this.document.getEmbeddedItems("armure") + context.equipment = this.document.getEmbeddedItems("equipement") + return context + } +} diff --git a/modules/applications/sheets/equipement-sheet.mjs b/modules/applications/sheets/equipement-sheet.mjs new file mode 100644 index 0000000..cc4635b --- /dev/null +++ b/modules/applications/sheets/equipement-sheet.mjs @@ -0,0 +1,9 @@ +import LesOubliesItemSheet from "./base-item-sheet.mjs" + +export default class LesOubliesEquipementSheet extends LesOubliesItemSheet { + static PARTS = { + sheet: { + template: "systems/fvtt-les-oublies/templates/item-equipement-sheet.hbs", + }, + } +} diff --git a/modules/applications/sheets/personnage-sheet.mjs b/modules/applications/sheets/personnage-sheet.mjs new file mode 100644 index 0000000..1074fb6 --- /dev/null +++ b/modules/applications/sheets/personnage-sheet.mjs @@ -0,0 +1,37 @@ +import LesOubliesActorSheet from "./base-actor-sheet.mjs" + +export default class LesOubliesPersonnageSheet extends LesOubliesActorSheet { + static DEFAULT_OPTIONS = { + ...super.DEFAULT_OPTIONS, + classes: [...super.DEFAULT_OPTIONS.classes, "personnage"], + window: { + ...super.DEFAULT_OPTIONS.window, + title: "TYPES.Actor.personnage", + }, + } + + static PARTS = { + sheet: { + template: "systems/fvtt-les-oublies/templates/actor-personnage-sheet.hbs", + }, + } + + async _prepareContext() { + const context = await super._prepareContext() + 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.skillGroups = this.document.getGroupedCompetences() + context.spells = this.document.getEmbeddedItems("sortilege") + context.weapons = this.document.getEmbeddedItems("arme") + 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 + return context + } +} diff --git a/modules/applications/sheets/pouvoir-compagnie-sheet.mjs b/modules/applications/sheets/pouvoir-compagnie-sheet.mjs new file mode 100644 index 0000000..6cd97b8 --- /dev/null +++ b/modules/applications/sheets/pouvoir-compagnie-sheet.mjs @@ -0,0 +1,9 @@ +import LesOubliesItemSheet from "./base-item-sheet.mjs" + +export default class LesOubliesPouvoirCompagnieSheet extends LesOubliesItemSheet { + static PARTS = { + sheet: { + template: "systems/fvtt-les-oublies/templates/item-pouvoir-compagnie-sheet.hbs", + }, + } +} diff --git a/modules/applications/sheets/reference-item-sheet.mjs b/modules/applications/sheets/reference-item-sheet.mjs new file mode 100644 index 0000000..fcb1d8c --- /dev/null +++ b/modules/applications/sheets/reference-item-sheet.mjs @@ -0,0 +1,17 @@ +import LesOubliesItemSheet from "./base-item-sheet.mjs" + +export default class LesOubliesReferenceItemSheet extends LesOubliesItemSheet { + static PARTS = { + sheet: { + template: "systems/fvtt-les-oublies/templates/item-reference-sheet.hbs", + }, + } + + async _prepareContext() { + const context = await super._prepareContext() + context.isRace = this.document.type === "race" + context.isTribu = this.document.type === "tribu" + context.isMetier = this.document.type === "metier" + return context + } +} diff --git a/modules/applications/sheets/sortilege-sheet.mjs b/modules/applications/sheets/sortilege-sheet.mjs new file mode 100644 index 0000000..f270cd8 --- /dev/null +++ b/modules/applications/sheets/sortilege-sheet.mjs @@ -0,0 +1,9 @@ +import LesOubliesItemSheet from "./base-item-sheet.mjs" + +export default class LesOubliesSortilegeSheet extends LesOubliesItemSheet { + static PARTS = { + sheet: { + template: "systems/fvtt-les-oublies/templates/item-sortilege-sheet.hbs", + }, + } +} diff --git a/modules/les-oublies-actor.js b/modules/les-oublies-actor.js new file mode 100644 index 0000000..91ea626 --- /dev/null +++ b/modules/les-oublies-actor.js @@ -0,0 +1,164 @@ +import { LESOUBLIES_CONFIG } from "./les-oublies-config.js" +import { LesOubliesUtility } from "./les-oublies-utility.js" +import { LesOubliesRolls } from "./les-oublies-rolls.js" + +export class LesOubliesActor extends Actor { + prepareDerivedData() { + super.prepareDerivedData() + + if (this.type === "personnage") { + const system = this.system + const sizeValue = Math.clamp(Number(system.size?.value ?? 1), 1, 4) + const hpMax = Math.max(sizeValue * 4 + Number(system.hp?.bonus ?? 0), 0) + system.hp.max = hpMax + system.hp.value = Math.min(Number(system.hp.value ?? hpMax), hpMax) + + const songesValue = Number(system.songes?.value ?? 0) + const cauchemarValue = Number(system.cauchemar?.value ?? 0) + const totals = LesOubliesUtility.computeDreamPointTotals(songesValue, cauchemarValue) + system.songes.max = totals.songesPoints + system.cauchemar.max = totals.cauchemarPoints + system.songes.points = Math.clamp(Number(system.songes.points ?? totals.songesPoints), 0, totals.songesPoints) + system.cauchemar.points = Math.clamp(Number(system.cauchemar.points ?? totals.cauchemarPoints), 0, totals.cauchemarPoints) + return + } + + if (this.type !== "creature") return + + const system = this.system + const hpValue = Math.max(Number(system.hp?.value ?? 0), 0) + const hpMax = Math.max(Number(system.hp?.max ?? hpValue), hpValue, 0) + system.hp.max = hpMax + system.hp.value = Math.min(hpValue, hpMax) + const songesPoints = Math.max(Number(system.songes?.points ?? 0), 0) + const cauchemarPoints = Math.max(Number(system.cauchemar?.points ?? 0), 0) + system.songes.max = Math.max(Number(system.songes?.max ?? songesPoints), songesPoints) + system.cauchemar.max = Math.max(Number(system.cauchemar?.max ?? cauchemarPoints), cauchemarPoints) + system.songes.points = Math.min(songesPoints, system.songes.max) + system.cauchemar.points = Math.min(cauchemarPoints, system.cauchemar.max) + } + + getProfileValue(profileKey) { + return Number(this.system.profils?.[profileKey] ?? 0) + } + + getCreationItem(type) { + return this.items.find((item) => item.type === type) ?? null + } + + getEmbeddedItems(type) { + const items = this.itemTypes?.[type] ?? this.items.filter((item) => item.type === type) + return LesOubliesUtility.sortByName(items) + } + + getCompagnie() { + const compagnieId = this.system.references?.compagnieId + return compagnieId ? game.actors.get(compagnieId) ?? null : null + } + + getCompetenceByKey(skillKey) { + return this.getEmbeddedItems("competence").find((item) => item.system.key === skillKey) ?? null + } + + getSkillScoreByKey(skillKey) { + const competence = this.getCompetenceByKey(skillKey) + return competence ? this.computeSkillValue(competence) : 0 + } + + computeSkillValue(item) { + const base = Number(item.system.base ?? 0) + const profileValue = this.getProfileValue(item.system.profileKey) + if (item.system.closed && base === 0) return 0 + return base + profileValue + } + + getCompetences() { + return this.getEmbeddedItems("competence").map((item) => ({ + item, + finalValue: this.computeSkillValue(item), + profileLabel: LESOUBLIES_CONFIG.profileLabels[item.system.profileKey] ?? item.system.profileKey, + })) + } + + getGroupedCompetences() { + return LESOUBLIES_CONFIG.profiles.map((profile) => ({ + ...profile, + items: this.getCompetences().filter((entry) => entry.item.system.profileKey === profile.id), + })) + } + + getDerivedOverview() { + const hpValue = Number(this.system.hp?.value ?? 0) + const hpMax = Number(this.system.hp?.max ?? 0) + const hpDisplay = this.type === "creature" + ? (this.system.hp?.display || (hpValue === hpMax ? String(hpValue) : `${hpValue}/${hpMax}`)) + : `${hpValue}/${hpMax}` + + return { + sizeLabel: LESOUBLIES_CONFIG.sizes[this.system.size?.value] ?? this.system.size?.value, + hpMax, + hpValue, + hpDisplay, + songesMax: this.system.songes?.max ?? this.system.songes?.points ?? 0, + cauchemarMax: this.system.cauchemar?.max ?? this.system.cauchemar?.points ?? 0, + songesPoints: this.system.songes?.points ?? 0, + cauchemarPoints: this.system.cauchemar?.points ?? 0, + race: this.getCreationItem("race"), + tribu: this.getCreationItem("tribu"), + metier: this.getCreationItem("metier"), + compagnie: this.getCompagnie(), + } + } + + async openTestRollDialog(preset = {}) { + return LesOubliesRolls.openTestDialog(this, preset) + } + + async openConfrontationRollDialog() { + return LesOubliesRolls.openConfrontationDialog(this) + } + + async openInitiativeRollDialog() { + return LesOubliesRolls.openInitiativeDialog(this) + } + + async openAttackRollDialog({ itemId = null, mode = null } = {}) { + return LesOubliesRolls.openAttackDialog(this, { itemId, mode }) + } + + async openDamageDialog({ itemId = null } = {}) { + return LesOubliesRolls.openDamageDialog(this, { itemId }) + } + + async openSpellActivationDialog(itemId) { + return LesOubliesRolls.openSpellDialog(this, itemId) + } + + async openCombatPresetDialog(actionKey) { + return LesOubliesRolls.openCombatPresetDialog(this, actionKey) + } + + async openThreadHarvestDialog() { + return LesOubliesRolls.openThreadHarvestDialog(this) + } + + async rollProfile(profileKey) { + return this.openTestRollDialog({ + label: LESOUBLIES_CONFIG.profileLabels[profileKey] ?? profileKey, + score: this.getProfileValue(profileKey), + difficulty: 0, + rollMode: LesOubliesRolls.getDefaultRollMode(this), + }) + } + + async rollCompetence(itemId) { + const item = this.items.get(itemId) + if (!item) return null + return this.openTestRollDialog({ + label: item.name, + score: this.computeSkillValue(item), + difficulty: 0, + rollMode: LesOubliesRolls.getDefaultRollMode(this), + }) + } +} diff --git a/modules/les-oublies-config.js b/modules/les-oublies-config.js new file mode 100644 index 0000000..b9c3a03 --- /dev/null +++ b/modules/les-oublies-config.js @@ -0,0 +1,89 @@ +export const PROFILE_KEYS = [ + "artiste", + "athlete", + "chasseur", + "faiseur", + "forceNature", + "guerrier", + "mystique", + "ombre", + "savant", +] + +export const PROFILE_LABELS = { + artiste: "Artiste", + athlete: "Athlète", + chasseur: "Chasseur", + faiseur: "Faiseur", + forceNature: "Force de la nature", + guerrier: "Guerrier", + mystique: "Mystique", + ombre: "Ombre", + savant: "Savant", +} + +export const SKILLS = { + arts: { label: "Arts", profileKey: "artiste", closed: false, domainSkill: true }, + empathie: { label: "Empathie", profileKey: "artiste", closed: false, domainSkill: false }, + seduction: { label: "Séduction", profileKey: "artiste", closed: false, domainSkill: false }, + athletisme: { label: "Athlétisme", profileKey: "athlete", closed: false, domainSkill: false }, + rapidite: { label: "Rapidité", profileKey: "athlete", closed: false, domainSkill: false }, + volonte: { label: "Volonté", profileKey: "athlete", closed: false, domainSkill: false }, + sens: { label: "Sens", profileKey: "chasseur", closed: false, domainSkill: false }, + survie: { label: "Survie", profileKey: "chasseur", closed: false, domainSkill: false }, + tir: { label: "Tir", profileKey: "chasseur", closed: false, domainSkill: false }, + artisanat: { label: "Artisanat", profileKey: "faiseur", closed: false, domainSkill: true }, + intellect: { label: "Intellect", profileKey: "faiseur", closed: false, domainSkill: false }, + soins: { label: "Soins", profileKey: "faiseur", closed: false, domainSkill: false }, + commandement: { label: "Commandement", profileKey: "forceNature", closed: false, domainSkill: false }, + endurance: { label: "Endurance", profileKey: "forceNature", closed: false, domainSkill: false }, + force: { label: "Force", profileKey: "forceNature", closed: false, domainSkill: false }, + corpsacorps: { label: "Corps à corps", profileKey: "guerrier", closed: false, domainSkill: false }, + melee: { label: "Mêlée", profileKey: "guerrier", closed: false, domainSkill: false }, + montures: { label: "Montures", profileKey: "guerrier", closed: false, domainSkill: false }, + chimerisme: { label: "Chimérisme", profileKey: "mystique", closed: true, domainSkill: false }, + magie: { label: "Magie", profileKey: "mystique", closed: true, domainSkill: false }, + onirologie: { label: "Onirologie", profileKey: "mystique", closed: true, domainSkill: false }, + discretion: { label: "Discrétion", profileKey: "ombre", closed: false, domainSkill: false }, + esquive: { label: "Esquive", profileKey: "ombre", closed: false, domainSkill: false }, + subterfuge: { label: "Subterfuge", profileKey: "ombre", closed: false, domainSkill: false }, + erudition: { label: "Érudition", profileKey: "savant", closed: true, domainSkill: true }, + langues: { label: "Langues", profileKey: "savant", closed: true, domainSkill: true }, + strategie: { label: "Stratégie", profileKey: "savant", closed: false, domainSkill: false }, +} + +export const SIZE_LABELS = { + 1: "Minuscule", + 2: "Petite", + 3: "Moyenne", + 4: "Grande", +} + +export const ACTOR_IMAGES = { + personnage: "icons/svg/mystery-man.svg", + compagnie: "icons/svg/book.svg", + creature: "icons/svg/eye.svg", +} + +export const ITEM_IMAGES = { + race: "icons/svg/mystery-man.svg", + tribu: "icons/svg/ruins.svg", + metier: "icons/svg/upgrade.svg", + competence: "icons/svg/book.svg", + sortilege: "icons/svg/daze.svg", + arme: "icons/svg/sword.svg", + armure: "icons/svg/shield.svg", + equipement: "icons/svg/chest.svg", + pouvoircompagnie: "icons/svg/aura.svg", +} + +export const LESOUBLIES_CONFIG = { + id: "fvtt-les-oublies", + title: "Les Oubliés", + profiles: PROFILE_KEYS.map((key) => ({ id: key, label: PROFILE_LABELS[key] })), + profileLabels: PROFILE_LABELS, + skills: SKILLS, + sizes: SIZE_LABELS, + actorImages: ACTOR_IMAGES, + itemImages: ITEM_IMAGES, +} diff --git a/modules/les-oublies-item.js b/modules/les-oublies-item.js new file mode 100644 index 0000000..82d6d87 --- /dev/null +++ b/modules/les-oublies-item.js @@ -0,0 +1,9 @@ +export class LesOubliesItem extends Item { + getChatData() { + const data = super.getChatData() + return { + ...data, + description: this.system.description, + } + } +} diff --git a/modules/les-oublies-main.js b/modules/les-oublies-main.js new file mode 100644 index 0000000..819e077 --- /dev/null +++ b/modules/les-oublies-main.js @@ -0,0 +1,55 @@ +import { LESOUBLIES_CONFIG } from "./les-oublies-config.js" +import { LesOubliesUtility } from "./les-oublies-utility.js" +import { LesOubliesActor } from "./les-oublies-actor.js" +import { LesOubliesItem } from "./les-oublies-item.js" +import { LesOubliesRolls } from "./les-oublies-rolls.js" +import * as models from "./models/index.mjs" +import * as sheets from "./applications/sheets/_module.mjs" + +Hooks.once("init", function () { + console.info("Les Oubliés | Initialisation du système") + + CONFIG.Actor.documentClass = LesOubliesActor + CONFIG.Actor.dataModels = { + personnage: models.PersonnageDataModel, + compagnie: models.CompagnieDataModel, + creature: models.CreatureDataModel, + } + + CONFIG.Item.documentClass = LesOubliesItem + CONFIG.Item.dataModels = { + race: models.RaceDataModel, + tribu: models.TribuDataModel, + metier: models.MetierDataModel, + competence: models.CompetenceDataModel, + sortilege: models.SortilegeDataModel, + arme: models.ArmeDataModel, + armure: models.ArmureDataModel, + equipement: models.EquipementDataModel, + pouvoircompagnie: models.PouvoirCompagnieDataModel, + } + + CONFIG.LESOUBLIES = LESOUBLIES_CONFIG + + game.system.lesOublies = { + config: LESOUBLIES_CONFIG, + models, + sheets, + rolls: LesOubliesRolls, + utility: LesOubliesUtility, + } + + foundry.documents.collections.Actors.registerSheet("fvtt-les-oublies", sheets.LesOubliesPersonnageSheet, { types: ["personnage"], makeDefault: true }) + foundry.documents.collections.Actors.registerSheet("fvtt-les-oublies", sheets.LesOubliesCompagnieSheet, { types: ["compagnie"], makeDefault: true }) + foundry.documents.collections.Actors.registerSheet("fvtt-les-oublies", sheets.LesOubliesCreatureSheet, { types: ["creature"], makeDefault: true }) + + foundry.documents.collections.Items.registerSheet("fvtt-les-oublies", sheets.LesOubliesReferenceItemSheet, { types: ["race", "tribu", "metier"], makeDefault: true }) + foundry.documents.collections.Items.registerSheet("fvtt-les-oublies", sheets.LesOubliesCompetenceSheet, { types: ["competence"], makeDefault: true }) + foundry.documents.collections.Items.registerSheet("fvtt-les-oublies", sheets.LesOubliesSortilegeSheet, { types: ["sortilege"], makeDefault: true }) + foundry.documents.collections.Items.registerSheet("fvtt-les-oublies", sheets.LesOubliesArmeSheet, { types: ["arme"], makeDefault: true }) + foundry.documents.collections.Items.registerSheet("fvtt-les-oublies", sheets.LesOubliesArmureSheet, { types: ["armure"], makeDefault: true }) + foundry.documents.collections.Items.registerSheet("fvtt-les-oublies", sheets.LesOubliesEquipementSheet, { types: ["equipement"], makeDefault: true }) + foundry.documents.collections.Items.registerSheet("fvtt-les-oublies", sheets.LesOubliesPouvoirCompagnieSheet, { types: ["pouvoircompagnie"], makeDefault: true }) + + LesOubliesUtility.registerHandlebarsHelpers() +}) diff --git a/modules/les-oublies-rolls.js b/modules/les-oublies-rolls.js new file mode 100644 index 0000000..73a2540 --- /dev/null +++ b/modules/les-oublies-rolls.js @@ -0,0 +1,1751 @@ +const PRIME_DEFINITIONS = [ + { + id: "none", + label: "Aucune prime", + type: "prime", + actionTypes: ["all"], + effects: {}, + }, + { + id: "efficacite", + label: "Efficacité", + type: "prime", + actionTypes: ["all"], + effects: { + finalModifier: 3, + }, + }, + { + id: "debordement", + label: "Débordement", + type: "prime", + actionTypes: ["all"], + effects: { + opponentFinalModifier: -3, + }, + }, + { + id: "acceleration", + label: "Accélération", + type: "prime", + actionTypes: ["all"], + effects: { + initiativeDelta: 4, + }, + }, + { + id: "blessure-grave", + label: "Blessure grave", + type: "prime", + actionTypes: ["meleeAttack", "rangedAttack"], + effects: { + damageMultiplier: 1.5, + }, + }, + { + id: "blessure-precise", + label: "Blessure précise", + type: "prime", + actionTypes: ["meleeAttack", "rangedAttack"], + effects: { + armorDivisor: 2, + }, + }, + { + id: "blessure-non-letale", + label: "Blessure non létale", + type: "prime", + actionTypes: ["meleeAttack", "rangedAttack"], + effects: { + nonLethal: true, + }, + }, + { + id: "attaques-multiples", + label: "Attaques multiples", + type: "prime", + actionTypes: ["meleeAttack"], + effects: { + damageMultiplier: 0.5, + targetsMultiple: true, + }, + }, +] + +const PENALTY_DEFINITIONS = [ + { + id: "none", + label: "Aucune pénalité", + type: "penalty", + actionTypes: ["all"], + effects: {}, + }, + { + id: "difficulte", + label: "Difficulté", + type: "penalty", + actionTypes: ["all"], + effects: { + finalModifier: -3, + }, + }, + { + id: "facilite", + label: "Facilité", + type: "penalty", + actionTypes: ["all"], + effects: { + opponentFinalModifier: 3, + }, + }, + { + id: "ralentissement", + label: "Ralentissement", + type: "penalty", + actionTypes: ["all"], + effects: { + initiativeDelta: -4, + }, + }, + { + id: "danger", + label: "Danger", + type: "penalty", + actionTypes: ["all"], + effects: { + nextReactionModifier: -3, + }, + }, + { + id: "abandon-position", + label: "Abandon de position avantageuse", + type: "penalty", + actionTypes: ["all"], + effects: { + note: "Le personnage perd sa position avantageuse.", + }, + }, + { + id: "risque", + label: "Risque", + type: "penalty", + actionTypes: ["all"], + effects: { + riskIncident: true, + }, + }, + { + id: "blessure-legere", + label: "Blessure légère", + type: "penalty", + actionTypes: ["meleeAttack", "rangedAttack"], + effects: { + damageMultiplier: 0.5, + }, + }, +] + +const ATTACK_DIFFICULTIES = { + melee: [ + { value: 3, label: "Cible inerte (+3)" }, + { value: 3, label: "Attaquant en hauteur (+3)" }, + { value: 3, label: "Cible d'une taille deux fois supérieure ou plus (+3)" }, + { value: -3, label: "Cible en hauteur (-3)" }, + { value: -3, label: "Obscurité (-3)" }, + { value: -3, label: "Cible d'une taille deux fois inférieure ou moins (-3)" }, + ], + ranged: [ + { value: 3, label: "Cible à moins de la portée (+3)" }, + { value: 3, label: "Cible très grande (+3)" }, + { value: 3, label: "Cible immobile (+3)" }, + { value: 0, label: "Cible à découvert (0)" }, + { value: 0, label: "Entre la portée et son double (0)" }, + { value: -1, label: "À genoux derrière un couvert (-1)" }, + { value: -2, label: "À plat ventre derrière un couvert (-2)" }, + { value: -3, label: "Cible très petite (-3)" }, + { value: -3, label: "Cible en mouvement rapide (-3)" }, + { value: -3, label: "Faible lumière (-3)" }, + { value: -3, label: "Couvert léger (-3)" }, + { value: -4, label: "Bouclier humain (-4)" }, + { value: -5, label: "Seule la tête est visible (-5)" }, + { value: -6, label: "Entre le double et le triple de la portée (-6)" }, + ], +} + +const MOVEMENT_DIFFICULTIES = [ + { value: 3, label: "Descendre (+3)" }, + { value: -3, label: "Monter (-3)" }, + { value: -3, label: "Franchir un obstacle (-3)" }, + { value: -3, label: "Ouvrir une porte non verrouillée (-3)" }, + { value: -3, label: "Terrain difficile (-3)" }, + { value: 3, label: "Se jeter à terre (+3)" }, + { value: -3, label: "Se relever (-3)" }, + { value: -6, label: "Ramper (-6)" }, + { value: 3, label: "Courte distance (jusqu'à 10 cm) (+3)" }, + { value: 3, label: "Chemin direct (+3)" }, + { value: -3, label: "Longue distance (20 cm et plus) (-3)" }, + { value: -3, label: "Faire un détour (-3)" }, +] + +const HARVEST_SIDE_EFFECTS = { + 1: "La main du personnage tremble plus ou moins violemment.", + 2: "Le personnage n'arrive à trouver ni repos ni sommeil.", + 3: "Le personnage se replie sur lui-même et parle très peu.", + 4: "Le personnage ne trouve plus de sens à la vie et est moralement brisé.", + 5: "Le personnage subit des troubles de la perception.", + 6: "Le personnage continue de ressentir une peur irraisonnée.", + 7: "Le personnage est tourmenté et se sent en danger.", + 8: "Le personnage se ferme aux autres et n'éprouve plus d'empathie.", + 9: "Le personnage a des brusques accélérations du rythme cardiaque.", + 10: "Le personnage souffre d'un tic.", + 11: "Le personnage n'arrive pas à se concentrer.", + 12: "Le personnage est frappé de brèves amnésies.", +} + +const PRESET_ACTIONS = { + encourager: { + key: "encourager", + title: "Encourager un allié", + mode: "test", + skillKey: "commandement", + difficulty: 0, + hint: "Sur succès, choisissez l'avantage accordé : initiative, prochaine action ou prochaine réaction.", + }, + intimider: { + key: "intimider", + title: "Intimider un adversaire", + mode: "confrontation", + attackerSkillKey: "commandement", + defenderSkillKey: "volonte", + difficulty: 0, + hint: "Sur succès, choisissez le désavantage imposé à l'adversaire.", + }, + evaluer: { + key: "evaluer", + title: "Évaluer un adversaire", + mode: "confrontation", + attackerSkillKey: "strategie", + attackerAlternativeKeys: ["tactique"], + defenderSkillKey: "subterfuge", + difficulty: 0, + hint: "Le paramètre observé sera rappelé dans la carte de chat si l'évaluation aboutit.", + }, + maitriser: { + key: "maitriser", + title: "Maîtriser un adversaire", + mode: "confrontation", + attackerSkillKey: "corpsacorps", + defenderSkillKey: "corpsacorps", + difficulty: 0, + hint: "Sur succès, la carte rappelle les suites possibles : réduire au silence, prendre en otage, conforter sa prise, étouffer, attacher.", + }, + seDeplacer: { + key: "seDeplacer", + title: "Se déplacer", + mode: "test", + skillKey: "athletisme", + difficulty: 0, + hint: "Le premier déplacement court sans opposition peut être automatique ; lancez ce jet quand un vrai test est requis.", + }, +} + +export class LesOubliesRolls { + static async openTestDialog(actor, preset = {}) { + const data = await this.#promptTestOptions(actor, preset) + if (!data || typeof data !== "object") return null + + const result = await this.resolveTest(actor, data) + return this.#createChatMessage(actor, result) + } + + static async openInitiativeDialog(actor) { + const rapidite = actor.getSkillScoreByKey?.("rapidite") ?? 0 + const data = await this.#promptTestOptions(actor, { + label: game.i18n.localize("LESOUBLIES.rolls.initiative"), + score: rapidite, + difficulty: 0, + rollMode: this.getDefaultRollMode(actor), + extraDie: "", + mode: "initiative", + lockLabel: true, + }) + if (!data || typeof data !== "object") return null + + const result = await this.resolveTest(actor, data) + if (!result) return null + return this.#createChatMessage(actor, { + ...result, + mode: "initiative", + initiativeScore: Math.min(Math.max(Math.ceil(result.final / 2), 0), 12), + successLabel: null, + }) + } + + 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) + } + + static async openAttackDialog(actor, { itemId = null, mode = null } = {}) { + const weapon = itemId ? actor.items.get(itemId) ?? null : null + const attackMode = mode ?? this.#getWeaponAttackMode(weapon) + const targetActor = this.#getTargetActor() + const data = await this.#promptAttackOptions(actor, { + weapon, + attackMode, + targetActor, + }) + if (!data) return null + + const modifiers = this.#resolveModifierSelection(data.primeId, data.penaltyId, attackMode === "ranged" ? "rangedAttack" : "meleeAttack") + const reactionOptions = this.#getAttackReactionOptions(data.attackerSkill) + const actionData = { + actionType: attackMode === "ranged" ? "rangedAttack" : "meleeAttack", + title: attackMode === "ranged" ? "Tirer" : "Frapper", + subtitle: weapon ? `${weapon.name} · ${this.#getSkillLabel(data.attackerSkill)}` : this.#getSkillLabel(data.attackerSkill), + hint: attackMode === "ranged" + ? "Réaction possible : Esquive ou Mêlée avec bouclier." + : "Réaction possible : Corps à corps, Esquive ou Mêlée selon l'attaque.", + weaponName: weapon?.name ?? (attackMode === "ranged" ? "Arme à distance" : "Arme"), + modifiers, + targetLabel: data.defenderLabel, + notes: data.notes?.trim() || "", + targetActor, + applyToTarget: Boolean(data.applyToTarget && targetActor), + 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), + modifiers, + }, + extraContext: { + difficultyLabel: data.difficultyLabel, + reactionLabel: reactionOptions.find((option) => option.value === data.defenderSkill)?.label ?? this.#getSkillLabel(data.defenderSkill), + }, + } + + return this.#createConfrontationMessage(actor, { + confrontationType: "directe", + attackerLabel: actor.name, + attackerScore: this.#getSkillScoreWithAlternatives(actor, data.attackerSkill), + attackerDifficulty: Number(data.difficulty ?? 0), + attackerRollMode: data.attackerRollMode, + attackerExtraDie: data.attackerExtraDie, + attackerFinalModifier: modifiers.summary.finalModifier, + defenderLabel: data.defenderLabel, + defenderScore: targetActor + ? this.#getSkillScoreWithAlternatives(targetActor, 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), + }, actionData) + } + + static async openDamageDialog(actor, { itemId = null } = {}) { + const weapon = itemId ? actor.items.get(itemId) ?? null : null + const targetActor = this.#getTargetActor() + const data = await this.#promptDamageOptions(actor, { + weapon, + targetActor, + }) + if (!data) return null + + const modifiers = this.#resolveModifierSelection(data.primeId, data.penaltyId, this.#getWeaponAttackMode(weapon) === "ranged" ? "rangedAttack" : "meleeAttack") + const damage = this.#computeDamageResolution({ + 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.targetLabel || targetActor?.name || game.i18n.localize("LESOUBLIES.rolls.defender")), + targetActor, + applyToTarget: Boolean(data.applyToTarget && targetActor), + modifiers, + }) + + if (damage.applyResult) { + await this.#applyDamageToActor(damage.applyResult.actor, damage.applyResult.damage) + } + + const content = await foundry.applications.handlebars.renderTemplate( + "systems/fvtt-les-oublies/templates/chat-action-roll.hbs", + { + actor, + result: null, + action: { + title: "Résolution de dégâts", + subtitle: weapon ? weapon.name : "Dégâts", + hint: "Les dégâts des attaques standards sont fixes puis modifiés par primes, pénalités et protection.", + modifiers, + notes: data.notes?.trim() || "", + damage, + }, + }, + ) + + return ChatMessage.create({ + speaker: ChatMessage.getSpeaker({ actor }), + content, + }) + } + + static async openSpellDialog(actor, itemId) { + const spell = actor.items.get(itemId) + if (!spell) return null + + 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, + })) + return null + } + if (effectiveCost > 0) { + await actor.update({ + [`system.${resource}.points`]: Math.max(Number(actor.system?.[resource]?.points ?? 0) - effectiveCost, 0), + }) + } + } + + const content = await foundry.applications.handlebars.renderTemplate( + "systems/fvtt-les-oublies/templates/chat-spell-activation.hbs", + { + actor, + spell, + activation: { + targetLabel: data.targetLabel?.trim() || "Sans cible précisée", + 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, + notes: data.notes?.trim() || "", + }, + }, + ) + + return ChatMessage.create({ + speaker: ChatMessage.getSpeaker({ actor }), + content, + }) + } + + static async openCombatPresetDialog(actor, actionKey) { + const preset = PRESET_ACTIONS[actionKey] + if (!preset) return null + + if (preset.mode === "test") { + const data = await this.#promptPresetTestOptions(actor, preset) + if (!data) return null + + const modifiers = this.#resolveModifierSelection(data.primeId, data.penaltyId, data.actionType) + const result = await this.resolveTest(actor, { + label: preset.title, + score: this.#getSkillScoreWithAlternatives(actor, preset.skillKey, preset.alternativeKeys), + difficulty: Number(data.difficulty ?? preset.difficulty ?? 0), + rollMode: data.rollMode, + extraDie: data.extraDie, + mode: "action", + finalModifier: modifiers.summary.finalModifier, + metadata: { + action: { + title: preset.title, + subtitle: this.#getSkillLabel(preset.skillKey), + hint: preset.hint, + modifiers, + notes: data.notes?.trim() || "", + outcome: this.#buildPresetOutcome(actionKey, data), + }, + }, + }) + return this.#createChatMessage(actor, result) + } + + const targetActor = this.#getTargetActor() + const data = await this.#promptPresetConfrontationOptions(actor, preset, targetActor) + if (!data) return null + + const modifiers = this.#resolveModifierSelection(data.primeId, data.penaltyId, actionKey) + const actionData = { + actionType: actionKey, + title: preset.title, + subtitle: `${this.#getSkillLabel(preset.attackerSkillKey)} contre ${this.#getSkillLabel(preset.defenderSkillKey)}`, + hint: preset.hint, + modifiers, + notes: data.notes?.trim() || "", + targetLabel: data.defenderLabel, + targetActor, + applyToTarget: false, + outcome: this.#buildPresetOutcome(actionKey, data), + } + + return this.#createConfrontationMessage(actor, { + confrontationType: "directe", + attackerLabel: actor.name, + attackerScore: this.#getSkillScoreWithAlternatives(actor, preset.attackerSkillKey, preset.attackerAlternativeKeys), + attackerDifficulty: Number(data.attackerDifficulty ?? preset.difficulty ?? 0), + attackerRollMode: data.attackerRollMode, + attackerExtraDie: data.attackerExtraDie, + attackerFinalModifier: modifiers.summary.finalModifier, + defenderLabel: data.defenderLabel, + defenderScore: targetActor + ? this.#getSkillScoreWithAlternatives(targetActor, 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), + }, actionData) + } + + static async openThreadHarvestDialog(actor) { + const data = await this.#promptThreadHarvestOptions(actor) + if (!data) return null + + const threadCount = Math.max(Number(data.threadCount ?? 1), 1) + const damageTaken = threadCount + const difficulty = -3 * (threadCount - 1) + const result = await this.resolveTest(actor, { + label: `Récolte de fils de ${data.threadType === "cauchemar" ? "Cauchemar" : "Songes"}`, + score: this.#getSkillScoreWithAlternatives(actor, "onirologie"), + difficulty, + rollMode: data.rollMode, + extraDie: data.extraDie, + mode: "action", + metadata: { + action: { + title: "Récolte de fils", + subtitle: `${threadCount} fil${threadCount > 1 ? "s" : ""} de ${data.threadType === "cauchemar" ? "Cauchemar" : "Songes"}`, + hint: "Le test subit -3 par fil supplémentaire souhaité et inflige 1 dégât par fil souhaité.", + notes: data.notes?.trim() || "", + }, + }, + }) + if (!result) return null + + await this.#applyDamageToActor(actor, damageTaken) + const durationRoll = await (new Roll("1d12")).evaluate() + const effectRoll = await (new Roll("1d12")).evaluate() + const effectIndex = Number(effectRoll.total ?? 1) + result.metadata.action.harvest = { + threadType: data.threadType, + threadCount, + damageTaken, + lockoutOnFailure: !result.success, + durationHours: Number(durationRoll.total ?? 0), + sideEffectRoll: effectIndex, + sideEffectText: HARVEST_SIDE_EFFECTS[effectIndex], + sleeperLabel: data.sleeperLabel?.trim() || "Dormeur non précisé", + } + + return this.#createChatMessage(actor, result) + } + + static getDefaultRollMode(actor) { + return actor?.type === "creature" ? "single" : "dual" + } + + static getRollModes() { + return [ + { value: "dual", label: game.i18n.localize("LESOUBLIES.rolls.rollModes.dual") }, + { value: "songes", label: game.i18n.localize("LESOUBLIES.rolls.rollModes.songes") }, + { value: "cauchemar", label: game.i18n.localize("LESOUBLIES.rolls.rollModes.cauchemar") }, + { value: "single", label: game.i18n.localize("LESOUBLIES.rolls.rollModes.single") }, + ] + } + + static getExtraDieModes() { + return [ + { value: "", label: game.i18n.localize("LESOUBLIES.rolls.extraDice.none") }, + { value: "songes", label: game.i18n.localize("LESOUBLIES.rolls.extraDice.songes") }, + { value: "cauchemar", label: game.i18n.localize("LESOUBLIES.rolls.extraDice.cauchemar") }, + ] + } + + static getWeaponActionLabel(weapon) { + return this.#getWeaponAttackMode(weapon) === "ranged" ? "Tirer" : "Frapper" + } + + static isRangedWeapon(weapon) { + return this.#getWeaponAttackMode(weapon) === "ranged" + } + + static resolveModifierSelection(primeId = "none", penaltyId = "none", actionType = "all") { + return this.#resolveModifierSelection(primeId, penaltyId, actionType) + } + + static async resolveTest(actor, options) { + const rollContext = this.#getRollContext(actor) + const spentResource = this.#createSpentResource(options.extraDie) + if (spentResource && !this.#canSpendResource(rollContext, spentResource.type)) { + ui.notifications.warn(game.i18n.format("LESOUBLIES.rolls.notEnoughResource", { + resource: spentResource.label, + actor: rollContext.label, + })) + return null + } + + const pool = this.#buildPool(options.rollMode, options.extraDie) + const dice = [] + for (let index = 0; index < pool.length; index += 1) { + const spec = pool[index] + dice.push(await this.#rollExplodingDie({ + ...spec, + index, + })) + } + + const selectedIndex = this.#needsSelection(dice) + ? await this.#promptDieSelection(actor, options.label, dice) + : this.#selectBestDie(dice) + + if (!Number.isInteger(selectedIndex)) return null + + const selectedDie = dice[selectedIndex] + const debt = this.#computeDebt(rollContext, dice, selectedDie) + if (spentResource) await this.#spendResource(rollContext, spentResource) + + const natural = Number(selectedDie.total ?? 0) + const score = Number(options.score ?? 0) + const difficulty = Number(options.difficulty ?? 0) + const threshold = 12 + const finalModifier = Number(options.finalModifier ?? 0) + const baseFinal = natural + score + difficulty + const final = baseFinal + finalModifier + const automaticFailure = selectedDie.firstFace === 1 + const success = !automaticFailure && final >= threshold + + return { + mode: options.mode ?? "test", + label: options.label, + score, + difficulty, + threshold, + margin: final - threshold, + rollMode: options.rollMode, + rollModeLabel: game.i18n.localize(`LESOUBLIES.rolls.rollModes.${options.rollMode}`), + dice, + selectedDie, + selectedSummary: `${selectedDie.typeLabel} ${selectedDie.total}`, + natural, + baseFinal, + finalModifier, + final, + success, + automaticFailure, + successLabel: game.i18n.localize(success ? "LESOUBLIES.rolls.success" : "LESOUBLIES.rolls.failure"), + choiceLabel: game.i18n.localize(debt.amount ? "LESOUBLIES.rolls.riskyChoice" : "LESOUBLIES.rolls.safeChoice"), + debt, + spentResource, + metadata: foundry.utils.deepClone(options.metadata ?? {}), + } + } + + static async #createChatMessage(actor, result) { + if (!result) return null + + const template = result.mode === "initiative" + ? "systems/fvtt-les-oublies/templates/chat-initiative-roll.hbs" + : result.mode === "action" + ? "systems/fvtt-les-oublies/templates/chat-action-roll.hbs" + : "systems/fvtt-les-oublies/templates/chat-test-roll.hbs" + + const content = await foundry.applications.handlebars.renderTemplate(template, { + actor, + result, + action: result.metadata?.action ?? null, + }) + + return ChatMessage.create({ + speaker: ChatMessage.getSpeaker({ actor }), + content, + }) + } + + static async #createConfrontationMessage(actor, data, actionData = null) { + const attacker = await this.resolveTest(actor, { + label: data.attackerLabel, + score: data.attackerScore, + difficulty: data.attackerDifficulty, + rollMode: data.attackerRollMode, + extraDie: data.attackerExtraDie, + mode: "confrontation", + finalModifier: data.attackerFinalModifier, + }) + const defenderContext = data.targetActor ?? this.#createVirtualRollContext({ + label: data.defenderLabel, + songesValue: data.defenderSongesValue, + songesPoints: data.defenderSongesPoints, + cauchemarValue: data.defenderCauchemarValue, + cauchemarPoints: data.defenderCauchemarPoints, + }) + const defender = await this.resolveTest(defenderContext, { + label: data.defenderLabel, + score: data.defenderScore, + difficulty: data.defenderDifficulty, + rollMode: data.defenderRollMode, + extraDie: data.defenderExtraDie, + mode: "confrontation", + finalModifier: data.defenderFinalModifier, + }) + if (!attacker || !defender) return null + + const outcomeKey = this.#getConfrontationOutcome(attacker, defender) + const confrontationAction = foundry.utils.deepClone(actionData ?? {}) + if (confrontationAction.damageRequest) { + const hit = ["attacker-success", "attacker-advantage"].includes(outcomeKey) + const damage = hit + ? this.#computeDamageResolution(confrontationAction.damageRequest) + : null + confrontationAction.damage = damage + if (damage?.applyResult) { + await this.#applyDamageToActor(damage.applyResult.actor, damage.applyResult.damage) + } + } + if (confrontationAction.outcome) { + confrontationAction.outcome.success = ["attacker-success", "attacker-advantage"].includes(outcomeKey) + } + + const content = await foundry.applications.handlebars.renderTemplate( + "systems/fvtt-les-oublies/templates/chat-confrontation-roll.hbs", + { + actor, + confrontationType: game.i18n.localize(`LESOUBLIES.rolls.confrontationTypes.${data.confrontationType}`), + attacker, + defender, + outcomeKey, + outcomeLabel: game.i18n.localize(`LESOUBLIES.rolls.outcomes.${outcomeKey}`), + action: confrontationAction, + }, + ) + + return ChatMessage.create({ + speaker: ChatMessage.getSpeaker({ actor }), + content, + }) + } + + static async #promptTestOptions(actor, preset = {}) { + const content = await foundry.applications.handlebars.renderTemplate( + "systems/fvtt-les-oublies/templates/dialog-roll-test.hbs", + { + actor, + rollModes: this.getRollModes(), + extraDieModes: this.getExtraDieModes(), + resources: this.#getDialogResources(actor), + values: { + label: preset.label ?? "", + score: Number(preset.score ?? 0), + difficulty: Number(preset.difficulty ?? 0), + rollMode: preset.rollMode ?? this.getDefaultRollMode(actor), + extraDie: preset.extraDie ?? "", + }, + isInitiative: preset.mode === "initiative", + lockLabel: Boolean(preset.lockLabel), + }, + ) + + return foundry.applications.api.DialogV2.wait({ + window: { + title: game.i18n.localize( + preset.mode === "initiative" ? "LESOUBLIES.rolls.dialogs.initiativeTitle" : "LESOUBLIES.rolls.dialogs.testTitle", + ), + }, + content, + buttons: [ + { + action: "roll", + label: game.i18n.localize("LESOUBLIES.rolls.roll"), + default: true, + callback: async (_event, _button, dialog) => { + const form = this.#getDialogElement(dialog)?.querySelector("form") + if (!form) return null + const data = this.#formToObject(form) + return { + label: String(data.label || "").trim() || preset.label || game.i18n.localize("LESOUBLIES.ui.roll"), + score: Number(data.score ?? 0), + difficulty: Number(data.difficulty ?? 0), + rollMode: String(data.rollMode || this.getDefaultRollMode(actor)), + extraDie: String(data.extraDie || ""), + mode: preset.mode ?? "test", + } + }, + }, + { + action: "cancel", + label: game.i18n.localize("Cancel"), + }, + ], + }) + } + + static async #promptConfrontationOptions(actor, preset = {}) { + const targetActor = preset.targetActor ?? this.#getTargetActor() + const content = await foundry.applications.handlebars.renderTemplate( + "systems/fvtt-les-oublies/templates/dialog-roll-confrontation.hbs", + { + actor, + rollModes: this.getRollModes(), + extraDieModes: this.getExtraDieModes(), + defaultRollMode: this.getDefaultRollMode(actor), + attackerResources: this.#getDialogResources(actor), + defenderResources: targetActor ? this.#getDialogResources(targetActor) : { + songesValue: 0, + songesPoints: 0, + cauchemarValue: 0, + cauchemarPoints: 0, + }, + values: { + attackerLabel: preset.attackerLabel ?? actor.name, + attackerScore: Number(preset.attackerScore ?? 0), + attackerDifficulty: Number(preset.attackerDifficulty ?? 0), + 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), + defenderDifficulty: Number(preset.defenderDifficulty ?? 0), + defenderRollMode: preset.defenderRollMode ?? this.getDefaultRollMode(targetActor ?? actor), + defenderExtraDie: preset.defenderExtraDie ?? "", + confrontationType: preset.confrontationType ?? "directe", + }, + }, + ) + + return foundry.applications.api.DialogV2.wait({ + window: { + title: game.i18n.localize("LESOUBLIES.rolls.dialogs.confrontationTitle"), + }, + content, + buttons: [ + { + action: "roll", + label: game.i18n.localize("LESOUBLIES.rolls.roll"), + default: true, + callback: async (_event, _button, dialog) => { + const form = this.#getDialogElement(dialog)?.querySelector("form") + if (!form) return null + const data = this.#formToObject(form) + return { + confrontationType: data.confrontationType || "directe", + attackerLabel: String(data.attackerLabel || actor.name || game.i18n.localize("LESOUBLIES.rolls.attacker")).trim(), + attackerScore: Number(data.attackerScore ?? 0), + attackerDifficulty: Number(data.attackerDifficulty ?? 0), + attackerRollMode: String(data.attackerRollMode || this.getDefaultRollMode(actor)), + attackerExtraDie: String(data.attackerExtraDie || ""), + defenderLabel: String(data.defenderLabel || targetActor?.name || game.i18n.localize("LESOUBLIES.rolls.defender")).trim(), + defenderScore: Number(data.defenderScore ?? 0), + defenderDifficulty: Number(data.defenderDifficulty ?? 0), + defenderRollMode: String(data.defenderRollMode || this.getDefaultRollMode(targetActor ?? actor)), + defenderExtraDie: String(data.defenderExtraDie || ""), + 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), + targetActor, + } + }, + }, + { + action: "cancel", + label: game.i18n.localize("Cancel"), + }, + ], + }) + } + + static async #promptAttackOptions(actor, { weapon = null, attackMode = "melee", targetActor = null } = {}) { + 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", + { + actor, + weapon, + attackTitle: attackMode === "ranged" ? "Tirer" : "Frapper", + attackerResources: this.#getDialogResources(actor), + defenderResources: targetActor ? this.#getDialogResources(targetActor) : { + songesValue: 0, + songesPoints: 0, + cauchemarValue: 0, + cauchemarPoints: 0, + }, + targetActor, + rollModes: this.getRollModes(), + extraDieModes: this.getExtraDieModes(), + attackSkills: this.#getAttackSkillOptions(attackMode), + reactionSkills: this.#getAttackReactionOptions(attackMode === "ranged" ? "tir" : "melee"), + difficultyOptions: ATTACK_DIFFICULTIES[attackMode] ?? [], + primeOptions: this.#getModifierOptions("prime", attackMode === "ranged" ? "rangedAttack" : "meleeAttack"), + penaltyOptions: this.#getModifierOptions("penalty", attackMode === "ranged" ? "rangedAttack" : "meleeAttack"), + values: { + attackerSkill: attackMode === "ranged" ? "tir" : "melee", + attackerRollMode: this.getDefaultRollMode(actor), + attackerExtraDie: "", + defenderLabel: targetActor?.name ?? game.i18n.localize("LESOUBLIES.rolls.defender"), + defenderSkill: attackMode === "ranged" ? "esquive" : "esquive", + defenderScore: 0, + defenderDifficulty: 0, + defenderRollMode: this.getDefaultRollMode(targetActor ?? actor), + defenderExtraDie: "", + targetProtection: this.#getActorProtection(targetActor), + difficultyPreset: 0, + customDifficulty: 0, + primeId: "none", + penaltyId: "none", + baseDamage, + baseDamageLabel, + applyToTarget: Boolean(targetActor), + notes: "", + }, + }, + ) + + return foundry.applications.api.DialogV2.wait({ + window: { + title: attackMode === "ranged" ? "Attaque à distance" : "Attaque de mêlée", + }, + content, + buttons: [ + { + action: "roll", + label: game.i18n.localize("LESOUBLIES.rolls.roll"), + default: true, + callback: async (_event, _button, dialog) => { + const form = this.#getDialogElement(dialog)?.querySelector("form") + if (!form) return null + const data = this.#formToObject(form) + 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 || ""), + defenderLabel: String(data.defenderLabel || targetActor?.name || game.i18n.localize("LESOUBLIES.rolls.defender")).trim(), + defenderSkill: String(data.defenderSkill || "esquive"), + defenderScore: Number(data.defenderScore ?? 0), + defenderDifficulty: Number(data.defenderDifficulty ?? 0), + defenderRollMode: String(data.defenderRollMode || this.getDefaultRollMode(targetActor ?? actor)), + defenderExtraDie: String(data.defenderExtraDie || ""), + 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), + difficulty: difficultyPreset + customDifficulty, + difficultyLabel: this.#formatDifficultyBreakdown(difficultyPreset, customDifficulty), + targetProtection: Number(data.targetProtection ?? 0), + primeId: String(data.primeId || "none"), + penaltyId: String(data.penaltyId || "none"), + baseDamage: Number(data.baseDamage ?? baseDamage), + baseDamageLabel: String(data.baseDamageLabel || baseDamageLabel), + applyToTarget: data.applyToTarget === "on", + notes: String(data.notes || ""), + } + }, + }, + { + action: "cancel", + label: game.i18n.localize("Cancel"), + }, + ], + }) + } + + static async #promptDamageOptions(actor, { weapon = null, targetActor = null } = {}) { + 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-damage-resolution.hbs", + { + actor, + weapon, + targetActor, + primeOptions: this.#getModifierOptions("prime", this.#getWeaponAttackMode(weapon) === "ranged" ? "rangedAttack" : "meleeAttack"), + penaltyOptions: this.#getModifierOptions("penalty", this.#getWeaponAttackMode(weapon) === "ranged" ? "rangedAttack" : "meleeAttack"), + values: { + targetLabel: targetActor?.name ?? game.i18n.localize("LESOUBLIES.rolls.defender"), + targetProtection: this.#getActorProtection(targetActor), + baseDamage, + baseDamageLabel, + primeId: "none", + penaltyId: "none", + applyToTarget: Boolean(targetActor), + notes: "", + }, + }, + ) + + return foundry.applications.api.DialogV2.wait({ + window: { + title: "Résolution de dégâts", + }, + content, + buttons: [ + { + action: "resolve", + label: "Résoudre", + default: true, + callback: async (_event, _button, dialog) => { + const form = this.#getDialogElement(dialog)?.querySelector("form") + if (!form) return null + const data = this.#formToObject(form) + return { + targetLabel: String(data.targetLabel || targetActor?.name || game.i18n.localize("LESOUBLIES.rolls.defender")).trim(), + targetProtection: Number(data.targetProtection ?? 0), + baseDamage: Number(data.baseDamage ?? baseDamage), + baseDamageLabel: String(data.baseDamageLabel || baseDamageLabel), + primeId: String(data.primeId || "none"), + penaltyId: String(data.penaltyId || "none"), + applyToTarget: data.applyToTarget === "on", + notes: String(data.notes || ""), + } + }, + }, + { + action: "cancel", + label: game.i18n.localize("Cancel"), + }, + ], + }) + } + + static async #promptSpellOptions(actor, spell) { + 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), + values: { + actualCost: Number(spell.system.cost ?? 0), + paymentMode: "points", + applyMetierSurcharge: true, + targetLabel: "", + notes: "", + }, + }, + ) + + return foundry.applications.api.DialogV2.wait({ + window: { + title: `Activer ${spell.name}`, + }, + content, + buttons: [ + { + action: "activate", + label: "Activer", + default: true, + callback: async (_event, _button, dialog) => { + const form = this.#getDialogElement(dialog)?.querySelector("form") + if (!form) return null + const data = this.#formToObject(form) + 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 || ""), + } + }, + }, + { + action: "cancel", + label: game.i18n.localize("Cancel"), + }, + ], + }) + } + + static async #promptPresetTestOptions(actor, preset) { + const content = await foundry.applications.handlebars.renderTemplate( + "systems/fvtt-les-oublies/templates/dialog-roll-action.hbs", + { + actor, + title: preset.title, + hint: preset.hint, + rollModes: this.getRollModes(), + extraDieModes: this.getExtraDieModes(), + resources: this.#getDialogResources(actor), + difficultyOptions: preset.key === "seDeplacer" ? MOVEMENT_DIFFICULTIES : [], + primeOptions: this.#getModifierOptions("prime", preset.key), + penaltyOptions: this.#getModifierOptions("penalty", preset.key), + values: { + rollMode: this.getDefaultRollMode(actor), + extraDie: "", + difficultyPreset: 0, + customDifficulty: Number(preset.difficulty ?? 0), + primeId: "none", + penaltyId: "none", + targetLabel: "", + outcomeChoice: preset.key === "encourager" ? "initiative" : "", + notes: "", + }, + showMovementOptions: preset.key === "seDeplacer", + showEncourageOptions: preset.key === "encourager", + }, + ) + + return foundry.applications.api.DialogV2.wait({ + window: { + title: preset.title, + }, + content, + buttons: [ + { + action: "roll", + label: game.i18n.localize("LESOUBLIES.rolls.roll"), + default: true, + callback: async (_event, _button, dialog) => { + const form = this.#getDialogElement(dialog)?.querySelector("form") + if (!form) return null + const data = this.#formToObject(form) + return { + actionType: preset.key, + rollMode: String(data.rollMode || this.getDefaultRollMode(actor)), + extraDie: String(data.extraDie || ""), + difficulty: Number(data.difficultyPreset ?? 0) + Number(data.customDifficulty ?? 0), + primeId: String(data.primeId || "none"), + penaltyId: String(data.penaltyId || "none"), + notes: String(data.notes || ""), + targetLabel: String(data.targetLabel || ""), + outcomeChoice: String(data.outcomeChoice || ""), + } + }, + }, + { + action: "cancel", + label: game.i18n.localize("Cancel"), + }, + ], + }) + } + + static async #promptPresetConfrontationOptions(actor, preset, targetActor = null) { + const content = await foundry.applications.handlebars.renderTemplate( + "systems/fvtt-les-oublies/templates/dialog-roll-preset-confrontation.hbs", + { + actor, + title: preset.title, + hint: preset.hint, + targetActor, + rollModes: this.getRollModes(), + extraDieModes: this.getExtraDieModes(), + attackerResources: this.#getDialogResources(actor), + defenderResources: targetActor ? this.#getDialogResources(targetActor) : { + songesValue: 0, + songesPoints: 0, + cauchemarValue: 0, + cauchemarPoints: 0, + }, + primeOptions: this.#getModifierOptions("prime", preset.key), + penaltyOptions: this.#getModifierOptions("penalty", preset.key), + values: { + attackerDifficulty: Number(preset.difficulty ?? 0), + defenderLabel: targetActor?.name ?? game.i18n.localize("LESOUBLIES.rolls.defender"), + defenderDifficulty: 0, + attackerRollMode: this.getDefaultRollMode(actor), + attackerExtraDie: "", + defenderRollMode: this.getDefaultRollMode(targetActor ?? actor), + defenderExtraDie: "", + defenderScore: 0, + primeId: "none", + penaltyId: "none", + outcomeChoice: "", + targetLabel: "", + notes: "", + }, + showIntimidateOptions: preset.key === "intimider", + showEvaluateOptions: preset.key === "evaluer", + showGrappleOptions: preset.key === "maitriser", + }, + ) + + return foundry.applications.api.DialogV2.wait({ + window: { + title: preset.title, + }, + content, + buttons: [ + { + action: "roll", + label: game.i18n.localize("LESOUBLIES.rolls.roll"), + default: true, + callback: async (_event, _button, dialog) => { + const form = this.#getDialogElement(dialog)?.querySelector("form") + if (!form) return null + const data = this.#formToObject(form) + return { + attackerDifficulty: Number(data.attackerDifficulty ?? preset.difficulty ?? 0), + 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)), + attackerExtraDie: String(data.attackerExtraDie || ""), + defenderRollMode: String(data.defenderRollMode || this.getDefaultRollMode(targetActor ?? actor)), + defenderExtraDie: String(data.defenderExtraDie || ""), + defenderScore: Number(data.defenderScore ?? 0), + 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), + primeId: String(data.primeId || "none"), + penaltyId: String(data.penaltyId || "none"), + outcomeChoice: String(data.outcomeChoice || ""), + targetLabel: String(data.targetLabel || ""), + notes: String(data.notes || ""), + } + }, + }, + { + action: "cancel", + label: game.i18n.localize("Cancel"), + }, + ], + }) + } + + static async #promptThreadHarvestOptions(actor) { + const content = await foundry.applications.handlebars.renderTemplate( + "systems/fvtt-les-oublies/templates/dialog-thread-harvest.hbs", + { + actor, + rollModes: this.getRollModes(), + extraDieModes: this.getExtraDieModes(), + resources: this.#getDialogResources(actor), + values: { + threadType: "songes", + threadCount: 1, + rollMode: this.getDefaultRollMode(actor), + extraDie: "", + sleeperLabel: "", + notes: "", + }, + }, + ) + + return foundry.applications.api.DialogV2.wait({ + window: { + title: "Récolte de fils", + }, + content, + buttons: [ + { + action: "roll", + label: game.i18n.localize("LESOUBLIES.rolls.roll"), + default: true, + callback: async (_event, _button, dialog) => { + const form = this.#getDialogElement(dialog)?.querySelector("form") + if (!form) return null + const data = this.#formToObject(form) + return { + threadType: String(data.threadType || "songes"), + threadCount: Number(data.threadCount ?? 1), + rollMode: String(data.rollMode || this.getDefaultRollMode(actor)), + extraDie: String(data.extraDie || ""), + sleeperLabel: String(data.sleeperLabel || ""), + notes: String(data.notes || ""), + } + }, + }, + { + action: "cancel", + label: game.i18n.localize("Cancel"), + }, + ], + }) + } + + static async #promptDieSelection(actor, label, dice) { + const content = await foundry.applications.handlebars.renderTemplate( + "systems/fvtt-les-oublies/templates/dialog-roll-choice.hbs", + { + actor: this.#getRollContext(actor), + label, + dice: dice.map((die) => ({ + ...die, + debtLabel: this.#computeDebt(this.#getRollContext(actor), dice, die).label, + })), + }, + ) + + return foundry.applications.api.DialogV2.wait({ + window: { + title: game.i18n.localize("LESOUBLIES.rolls.dialogs.choiceTitle"), + }, + content, + buttons: dice.map((die) => ({ + action: `select-${die.index}`, + label: `${die.typeLabel} ${die.total}`, + callback: async () => die.index, + })).concat({ + action: "cancel", + label: game.i18n.localize("Cancel"), + }), + }) + } + + static #buildPool(rollMode, extraDie) { + const dice = [] + switch (rollMode) { + case "songes": + dice.push({ type: "songes" }, { type: "songes" }) + break + case "cauchemar": + dice.push({ type: "cauchemar" }, { type: "cauchemar" }) + break + case "single": + dice.push({ type: "neutral" }) + break + case "dual": + default: + dice.push({ type: "songes" }, { type: "cauchemar" }) + break + } + + if (extraDie && rollMode !== "single") { + dice.push({ type: extraDie, source: "extra" }) + } + return dice + } + + static async #rollExplodingDie({ type, index, source = "base" }) { + const faces = [] + let total = 0 + let lastFace = 12 + + while (lastFace === 12) { + const roll = await (new Roll("1d12")).evaluate() + lastFace = Number(roll.total ?? 0) + faces.push(lastFace) + total += lastFace + } + + const typeLabel = game.i18n.localize(`LESOUBLIES.rolls.dice.${type}`) + return { + index, + type, + typeLabel, + source, + sourceLabel: source === "extra" ? game.i18n.localize("LESOUBLIES.rolls.extraDie") : null, + faces, + firstFace: faces[0] ?? 0, + total, + exploded: faces.length > 1, + breakdown: faces.join(" + "), + } + } + + static #needsSelection(dice) { + return new Set(dice.map((die) => die.type)).size > 1 + } + + static #selectBestDie(dice) { + return dice.reduce((bestIndex, die, index, collection) => ( + die.total > collection[bestIndex].total ? index : bestIndex + ), 0) + } + + static #computeDebt(actor, dice, selectedDie) { + if (!actor?.system) { + return { + type: null, + amount: 0, + label: game.i18n.localize("LESOUBLIES.rolls.noDebt"), + } + } + + const rolledPolarities = new Set( + dice + .map((die) => die.type) + .filter((type) => ["songes", "cauchemar"].includes(type)), + ) + + if (rolledPolarities.size < 2 || !["songes", "cauchemar"].includes(selectedDie.type)) { + return { + type: null, + amount: 0, + label: game.i18n.localize("LESOUBLIES.rolls.noDebt"), + } + } + + const songesValue = Number(actor.system.songes?.value ?? 0) + const cauchemarValue = Number(actor.system.cauchemar?.value ?? 0) + if (songesValue === cauchemarValue) { + return { + type: null, + amount: 0, + label: game.i18n.localize("LESOUBLIES.rolls.noDebt"), + } + } + + const dominant = songesValue > cauchemarValue ? "songes" : "cauchemar" + if (selectedDie.type === dominant) { + return { + type: null, + amount: 0, + label: game.i18n.localize("LESOUBLIES.rolls.noDebt"), + } + } + + return { + type: selectedDie.type, + amount: 1, + label: game.i18n.format("LESOUBLIES.rolls.debtGain", { + type: game.i18n.localize(`LESOUBLIES.rolls.dice.${selectedDie.type}`), + }), + } + } + + static #getDialogElement(dialog) { + return dialog.element instanceof HTMLElement ? dialog.element : dialog.element?.[0] ?? null + } + + static #formToObject(form) { + return Object.fromEntries(new FormData(form).entries()) + } + + static #getRollContext(actor) { + if (actor?.system) return actor + return actor ?? this.#createVirtualRollContext() + } + + static #createVirtualRollContext({ + label = game.i18n.localize("LESOUBLIES.rolls.defender"), + songesValue = 0, + songesPoints = 0, + cauchemarValue = 0, + cauchemarPoints = 0, + } = {}) { + return { + name: label, + system: { + songes: { + value: Number(songesValue ?? 0), + points: Number(songesPoints ?? 0), + }, + cauchemar: { + value: Number(cauchemarValue ?? 0), + points: Number(cauchemarPoints ?? 0), + }, + }, + } + } + + static #getDialogResources(actor) { + const context = this.#getRollContext(actor) + return { + songesValue: Number(context.system.songes?.value ?? 0), + songesPoints: Number(context.system.songes?.points ?? 0), + cauchemarValue: Number(context.system.cauchemar?.value ?? 0), + cauchemarPoints: Number(context.system.cauchemar?.points ?? 0), + } + } + + static #createSpentResource(extraDie) { + if (!extraDie) return null + return { + type: extraDie, + amount: 1, + label: game.i18n.localize(`LESOUBLIES.rolls.extraDice.${extraDie}`), + } + } + + static #canSpendResource(actor, resourceType) { + return Number(actor?.system?.[resourceType]?.points ?? 0) >= 1 + } + + static async #spendResource(actor, spentResource) { + if (!actor?.update || !spentResource) return + const path = `system.${spentResource.type}.points` + const current = Number(actor.system?.[spentResource.type]?.points ?? 0) + await actor.update({ [path]: Math.max(current - spentResource.amount, 0) }) + } + + static #getWeaponAttackMode(weapon) { + const category = String(weapon?.system?.category || "").toLowerCase() + if (["distance", "ranged", "tir", "projectile"].some((keyword) => category.includes(keyword))) return "ranged" + return "melee" + } + + static #getAttackSkillOptions(mode) { + if (mode === "ranged") { + return [{ value: "tir", label: this.#getSkillLabel("tir") }] + } + + return [ + { value: "melee", label: this.#getSkillLabel("melee") }, + { value: "corpsacorps", label: this.#getSkillLabel("corpsacorps") }, + ] + } + + static #getAttackReactionOptions(attackerSkill) { + if (attackerSkill === "tir") { + return [ + { value: "esquive", label: "Esquive" }, + { value: "melee", label: "Mêlée (avec bouclier)" }, + ] + } + + return [ + { value: "corpsacorps", label: "Corps à corps" }, + { value: "melee", label: "Mêlée" }, + { value: "esquive", label: "Esquive" }, + ] + } + + static #getModifierOptions(type, actionType) { + const source = type === "prime" ? PRIME_DEFINITIONS : PENALTY_DEFINITIONS + return source + .filter((modifier) => modifier.actionTypes.includes("all") || modifier.actionTypes.includes(actionType)) + .map((modifier) => ({ + value: modifier.id, + label: modifier.label, + })) + } + + static #resolveModifierSelection(primeId = "none", penaltyId = "none", actionType = "all") { + const selectedPrime = PRIME_DEFINITIONS.find((modifier) => modifier.id === primeId && (modifier.actionTypes.includes("all") || modifier.actionTypes.includes(actionType))) + ?? PRIME_DEFINITIONS[0] + const selectedPenalty = PENALTY_DEFINITIONS.find((modifier) => modifier.id === penaltyId && (modifier.actionTypes.includes("all") || modifier.actionTypes.includes(actionType))) + ?? PENALTY_DEFINITIONS[0] + + const modifiers = [selectedPrime, selectedPenalty] + const summary = modifiers.reduce((accumulator, modifier) => { + const effects = modifier.effects ?? {} + accumulator.finalModifier += Number(effects.finalModifier ?? 0) + accumulator.opponentFinalModifier += Number(effects.opponentFinalModifier ?? 0) + accumulator.initiativeDelta += Number(effects.initiativeDelta ?? 0) + accumulator.damageMultiplier *= Number(effects.damageMultiplier ?? 1) + if (effects.armorDivisor) { + accumulator.armorDivisor = accumulator.armorDivisor + ? Math.max(accumulator.armorDivisor, effects.armorDivisor) + : effects.armorDivisor + } + accumulator.nonLethal = accumulator.nonLethal || Boolean(effects.nonLethal) + accumulator.targetsMultiple = accumulator.targetsMultiple || Boolean(effects.targetsMultiple) + accumulator.riskIncident = accumulator.riskIncident || Boolean(effects.riskIncident) + if (effects.note) accumulator.notes.push(effects.note) + if (effects.nextReactionModifier) accumulator.nextReactionModifier += Number(effects.nextReactionModifier) + return accumulator + }, { + finalModifier: 0, + opponentFinalModifier: 0, + initiativeDelta: 0, + damageMultiplier: 1, + armorDivisor: null, + nonLethal: false, + targetsMultiple: false, + riskIncident: false, + nextReactionModifier: 0, + notes: [], + }) + + return { + prime: selectedPrime.id === "none" ? null : selectedPrime, + penalty: selectedPenalty.id === "none" ? null : selectedPenalty, + summary, + labels: [selectedPrime, selectedPenalty] + .filter((modifier) => modifier.id !== "none") + .map((modifier) => modifier.label), + } + } + + static #getSkillLabel(skillKey) { + return CONFIG.LESOUBLIES?.config?.skills?.[skillKey]?.label + ?? CONFIG.LESOUBLIES?.skills?.[skillKey]?.label + ?? skillKey + } + + static #getSkillScoreWithAlternatives(actor, primaryKey, alternativeKeys = []) { + if (!actor?.getSkillScoreByKey) return 0 + const keys = [primaryKey, ...(alternativeKeys ?? [])].filter(Boolean) + for (const key of keys) { + const value = Number(actor.getSkillScoreByKey(key) ?? 0) + if (value > 0) return value + } + return Number(actor.getSkillScoreByKey(primaryKey) ?? 0) + } + + static #getWeaponBaseDamage(actor, weapon) { + const damageText = String(weapon?.system?.damage || "") + const parsed = this.#extractFirstInteger(damageText) + if (parsed !== null) return parsed + + const explicitValue = Number(weapon?.system?.sizeValue ?? 0) + if (explicitValue > 0) return explicitValue + + const actorSize = Number(actor?.system?.size?.value ?? 0) + const sizeModifier = Number(weapon?.system?.sizeModifier ?? 0) + return Math.max(actorSize + sizeModifier, 0) + } + + static #extractFirstInteger(text) { + const match = String(text || "").match(/-?\d+/) + return match ? Number(match[0]) : null + } + + static #getActorProtection(actor) { + if (!actor) return 0 + const armors = actor.itemTypes?.armure ?? actor.items?.filter?.((item) => item.type === "armure") ?? [] + const equippedArmorProtection = armors + .filter((item) => item.system?.equipped) + .reduce((total, item) => total + Number(item.system?.protection ?? 0), 0) + return Math.max(equippedArmorProtection, Number(actor.system?.protection ?? 0), 0) + } + + static #computeDamageResolution({ + actor, + weapon = null, + baseDamage = 0, + baseLabel = "", + targetProtection = 0, + targetLabel = "", + targetActor = null, + applyToTarget = false, + modifiers, + }) { + const summary = modifiers?.summary ?? {} + const adjustedBase = Math.max(Math.ceil(Number(baseDamage ?? 0) * Number(summary.damageMultiplier ?? 1)), 0) + const effectiveProtection = summary.armorDivisor + ? Math.ceil(Number(targetProtection ?? 0) / Number(summary.armorDivisor)) + : Number(targetProtection ?? 0) + const finalDamage = Math.max(adjustedBase - effectiveProtection, 0) + const applyResult = applyToTarget && targetActor ? { actor: targetActor, damage: finalDamage } : null + + return { + weaponName: weapon?.name ?? "Attaque", + baseDamage: Number(baseDamage ?? 0), + baseLabel, + adjustedBase, + targetProtection: Number(targetProtection ?? 0), + effectiveProtection, + finalDamage, + nonLethal: Boolean(summary.nonLethal), + targetsMultiple: Boolean(summary.targetsMultiple), + applyResult, + targetLabel, + } + } + + static async #applyDamageToActor(actor, damage) { + if (!actor?.update || !Number.isFinite(damage) || damage <= 0) return + const currentHp = Number(actor.system?.hp?.value ?? 0) + await actor.update({ + "system.hp.value": Math.max(currentHp - damage, 0), + }) + } + + static #formatDifficultyBreakdown(presetValue, customValue) { + const parts = [] + if (presetValue) parts.push(`${presetValue > 0 ? "+" : ""}${presetValue}`) + if (customValue) parts.push(`${customValue > 0 ? "+" : ""}${customValue}`) + return parts.length ? parts.join(" ") : "0" + } + + static #getConfrontationOutcome(attacker, defender) { + const attackerSuccess = attacker.success + const defenderSuccess = defender.success + + if (attackerSuccess && !defenderSuccess) return "attacker-success" + if (!attackerSuccess && defenderSuccess) return "defender-success" + if (attacker.final > defender.final) return "attacker-advantage" + if (defender.final > attacker.final) return "defender-advantage" + return "tie" + } + + static #buildPresetOutcome(actionKey, data) { + switch (actionKey) { + case "encourager": + return { + label: "Effet choisi", + description: { + initiative: "+4 à l'initiative à la fin du tour de l'allié.", + action: "+3 au résultat final de sa prochaine action.", + reaction: "+3 au résultat final de sa prochaine réaction.", + }[data.outcomeChoice] ?? "Effet à préciser en jeu.", + } + case "intimider": + return { + label: "Effet choisi", + description: { + initiative: "-4 à l'initiative à la fin du tour de l'adversaire.", + action: "-3 au résultat final de sa prochaine action.", + reaction: "-3 au résultat final de sa prochaine réaction.", + }[data.outcomeChoice] ?? "Effet à préciser en jeu.", + } + case "evaluer": + return { + label: "Paramètre observé", + description: data.targetLabel?.trim() || "Paramètre non précisé.", + } + case "maitriser": + return { + label: "Suite envisagée", + description: { + silence: "Réduire au silence (gratuite, automatique).", + otage: "Prendre en otage (gratuite, automatique ; couverture 4).", + conforter: "Conforter sa prise (+3 au résultat initial, une fois).", + etouffer: "Étouffer (libre, automatique ; 2 PV, peut être non létal).", + attacher: "Attacher l'otage (libre, automatique).", + }[data.outcomeChoice] ?? "Aucune suite immédiate déclarée.", + } + case "seDeplacer": + return { + label: "Destination", + description: data.targetLabel?.trim() || "Destination non précisée.", + } + default: + return null + } + } + + static #getTargetActor() { + const target = Array.from(game.user?.targets ?? []).find((token) => token?.actor) + return target?.actor ?? null + } + + static #actorMatchesSpellGrant(actor, spell) { + const métier = actor?.getCreationItem?.("metier") ?? null + const grants = métier?.system?.spellGrants ?? [] + return grants.some((grant) => ( + String(grant.tradition || "").trim().toLowerCase() === String(spell.system.tradition || "").trim().toLowerCase() + && String(grant.polarity || "").trim().toLowerCase() === String(spell.system.polarity || "").trim().toLowerCase() + && String(grant.skillKey || "").trim().toLowerCase() === String(spell.system.skillKey || "").trim().toLowerCase() + )) + } +} diff --git a/modules/les-oublies-utility.js b/modules/les-oublies-utility.js new file mode 100644 index 0000000..7dfb87e --- /dev/null +++ b/modules/les-oublies-utility.js @@ -0,0 +1,76 @@ +import { ACTOR_IMAGES, ITEM_IMAGES, LESOUBLIES_CONFIG, PROFILE_KEYS } from "./les-oublies-config.js" + +export class LesOubliesUtility { + static registerHandlebarsHelpers() { + Handlebars.registerHelper("eq", (left, right) => left === right) + Handlebars.registerHelper("join", (values, separator = ", ") => Array.isArray(values) ? values.filter(Boolean).join(separator) : "") + Handlebars.registerHelper("count", (values) => Array.isArray(values) ? values.length : 0) + Handlebars.registerHelper("concat", (...parts) => parts.slice(0, -1).join("")) + Handlebars.registerHelper("profileLabel", (key) => LESOUBLIES_CONFIG.profileLabels[key] ?? key) + Handlebars.registerHelper("skillLabel", (key) => LESOUBLIES_CONFIG.skills[key]?.label ?? key) + Handlebars.registerHelper("formatPrice", (value) => Number(value) > 0 ? `${value} e` : "—") + Handlebars.registerHelper("formatSkillBonus", (bonus) => { + const keys = [bonus.key, ...(bonus.alternativeKeys ?? [])] + .filter(Boolean) + .map((key) => LESOUBLIES_CONFIG.skills[key]?.label ?? key) + const label = keys.join(" ou ") + const domains = [] + if (Array.isArray(bonus.domainsGranted) && bonus.domainsGranted.length) domains.push(bonus.domainsGranted.join(", ")) + if (Number(bonus.domainsToChoose ?? 0) > 0) { + const prefix = bonus.domainsToChoose > 1 ? `${bonus.domainsToChoose} choix` : "1 choix" + domains.push(bonus.domainsChoiceText ? `${prefix} : ${bonus.domainsChoiceText}` : prefix) + } + return domains.length ? `${label} +${bonus.base} — ${domains.join(" ; ")}` : `${label} +${bonus.base}` + }) + Handlebars.registerHelper("formatEquipmentEntry", (entry) => { + const baseLabel = entry.choiceText || entry.name || "" + const quantity = Number(entry.quantity ?? 0) > 1 ? ` x${entry.quantity}` : "" + const extras = [] + if (entry.details) extras.push(entry.details) + if (Number(entry.ecorces ?? 0) > 0) extras.push(`${entry.ecorces} écorces`) + return extras.length ? `${baseLabel}${quantity} — ${extras.join(", ")}` : `${baseLabel}${quantity}` + }) + Handlebars.registerHelper("formatSpellGrant", (grant) => { + const discipline = LESOUBLIES_CONFIG.skills[grant.skillKey]?.label ?? grant.skillKey ?? grant.tradition + return `${grant.tradition} (${discipline}) / ${grant.polarity} : ${grant.amount}` + }) + } + + static getDefaultActorImage(type) { + return ACTOR_IMAGES[type] ?? "icons/svg/mystery-man.svg" + } + + static getDefaultItemImage(type) { + return ITEM_IMAGES[type] ?? "icons/svg/item-bag.svg" + } + + static createEmptyProfiles() { + return PROFILE_KEYS.reduce((profiles, key) => { + profiles[key] = 0 + return profiles + }, {}) + } + + static computeDreamPointTotals(songesValue, cauchemarValue) { + if (songesValue > cauchemarValue) { + return { + songesPoints: songesValue * 2 - cauchemarValue, + cauchemarPoints: cauchemarValue, + } + } + if (songesValue === cauchemarValue) { + return { + songesPoints: songesValue, + cauchemarPoints: cauchemarValue, + } + } + return { + songesPoints: songesValue, + cauchemarPoints: cauchemarValue * 3 - songesValue * 2, + } + } + + static sortByName(documents = []) { + return [...documents].sort((left, right) => left.name.localeCompare(right.name, "fr")) + } +} diff --git a/modules/models/arme.mjs b/modules/models/arme.mjs new file mode 100644 index 0000000..8aa33cb --- /dev/null +++ b/modules/models/arme.mjs @@ -0,0 +1,22 @@ +import { BaseItemDataModel } from "./base-item.mjs" + +export default class ArmeDataModel extends BaseItemDataModel { + static defineSchema() { + const fields = foundry.data.fields + return { + ...this.defineBaseSchema(), + category: new fields.StringField({ initial: "melee" }), + origin: new fields.StringField({ initial: "petitPeuple" }), + sizeMode: new fields.StringField({ initial: "variable" }), + sizeValue: new fields.NumberField({ initial: 0, integer: true, min: 0 }), + sizeModifier: new fields.NumberField({ initial: 0, integer: true }), + damage: new fields.StringField({ initial: "" }), + range: new fields.StringField({ initial: "" }), + properties: new fields.ArrayField(new fields.StringField(), { initial: [] }), + restrictedRace: new fields.StringField({ initial: "" }), + quantity: new fields.NumberField({ initial: 1, integer: true, min: 0 }), + price: new fields.NumberField({ initial: 0, integer: true, min: 0 }), + equipped: new fields.BooleanField({ initial: false }), + } + } +} diff --git a/modules/models/armure.mjs b/modules/models/armure.mjs new file mode 100644 index 0000000..a328ad3 --- /dev/null +++ b/modules/models/armure.mjs @@ -0,0 +1,17 @@ +import { BaseItemDataModel } from "./base-item.mjs" + +export default class ArmureDataModel extends BaseItemDataModel { + static defineSchema() { + const fields = foundry.data.fields + return { + ...this.defineBaseSchema(), + state: new fields.StringField({ initial: "protege" }), + protection: new fields.NumberField({ initial: 1, integer: true, min: 0 }), + physicalPenalty: new fields.NumberField({ initial: 1, integer: true, min: 0 }), + initiativePenalty: new fields.NumberField({ initial: 1, integer: true, min: 0 }), + quantity: new fields.NumberField({ initial: 1, integer: true, min: 0 }), + price: new fields.NumberField({ initial: 0, integer: true, min: 0 }), + equipped: new fields.BooleanField({ initial: false }), + } + } +} diff --git a/modules/models/base-item.mjs b/modules/models/base-item.mjs new file mode 100644 index 0000000..c5f7916 --- /dev/null +++ b/modules/models/base-item.mjs @@ -0,0 +1,11 @@ +export class BaseItemDataModel extends foundry.abstract.TypeDataModel { + static defineBaseSchema() { + const fields = foundry.data.fields + return { + description: new fields.HTMLField({ initial: "" }), + notes: new fields.HTMLField({ initial: "" }), + source: new fields.StringField({ initial: "Livre de règles Les Oubliés" }), + tags: new fields.ArrayField(new fields.StringField(), { initial: [] }), + } + } +} diff --git a/modules/models/compagnie.mjs b/modules/models/compagnie.mjs new file mode 100644 index 0000000..074ce3a --- /dev/null +++ b/modules/models/compagnie.mjs @@ -0,0 +1,26 @@ +export default class CompagnieDataModel extends foundry.abstract.TypeDataModel { + static defineSchema() { + const fields = foundry.data.fields + return { + description: new fields.HTMLField({ initial: "" }), + notes: new fields.HTMLField({ initial: "" }), + captainId: new fields.StringField({ initial: "" }), + memberIds: new fields.ArrayField(new fields.StringField(), { initial: [] }), + ombreDuTourmentId: new fields.StringField({ initial: "" }), + power: new fields.SchemaField({ + name: new fields.StringField({ initial: "" }), + description: new fields.HTMLField({ initial: "" }), + sharedDreamPoints: new fields.NumberField({ initial: 0, integer: true, min: 0 }), + activationCondition: new fields.StringField({ initial: "À portée de vue du capitaine" }), + captainVisible: new fields.BooleanField({ initial: true }), + captainNeedsWitness: new fields.BooleanField({ initial: true }), + }), + links: new fields.ArrayField(new fields.SchemaField({ + sourceId: new fields.StringField({ initial: "" }), + targetId: new fields.StringField({ initial: "" }), + label: new fields.StringField({ initial: "" }), + details: new fields.StringField({ initial: "" }), + }), { initial: [] }), + } + } +} diff --git a/modules/models/competence.mjs b/modules/models/competence.mjs new file mode 100644 index 0000000..6454d5a --- /dev/null +++ b/modules/models/competence.mjs @@ -0,0 +1,18 @@ +import { BaseItemDataModel } from "./base-item.mjs" + +export default class CompetenceDataModel extends BaseItemDataModel { + static defineSchema() { + const fields = foundry.data.fields + return { + ...this.defineBaseSchema(), + key: new fields.StringField({ initial: "" }), + profileKey: new fields.StringField({ initial: "" }), + base: new fields.NumberField({ initial: 0, integer: true, min: 0 }), + closed: new fields.BooleanField({ initial: false }), + domainSkill: new fields.BooleanField({ initial: false }), + domains: new fields.ArrayField(new fields.StringField(), { initial: [] }), + fixedDomains: new fields.ArrayField(new fields.StringField(), { initial: [] }), + exampleDomains: new fields.ArrayField(new fields.StringField(), { initial: [] }), + } + } +} diff --git a/modules/models/creature.mjs b/modules/models/creature.mjs new file mode 100644 index 0000000..32ec054 --- /dev/null +++ b/modules/models/creature.mjs @@ -0,0 +1,52 @@ +export default class CreatureDataModel extends foundry.abstract.TypeDataModel { + static defineSchema() { + const fields = foundry.data.fields + return { + biodata: new fields.SchemaField({ + categorie: new fields.StringField({ initial: "autre" }), + habitat: new fields.HTMLField({ initial: "" }), + motscles: new fields.StringField({ initial: "" }), + description: new fields.HTMLField({ initial: "" }), + notes: new fields.HTMLField({ initial: "" }), + gmnotes: new fields.HTMLField({ initial: "" }), + }), + size: new fields.SchemaField({ + value: new fields.NumberField({ initial: 2, integer: true, min: 1, max: 8 }), + label: new fields.StringField({ initial: "" }), + }), + profils: new fields.SchemaField({ + artiste: new fields.NumberField({ initial: 0, integer: true }), + athlete: new fields.NumberField({ initial: 0, integer: true }), + chasseur: new fields.NumberField({ initial: 0, integer: true }), + faiseur: new fields.NumberField({ initial: 0, integer: true }), + forceNature: new fields.NumberField({ initial: 0, integer: true }), + guerrier: new fields.NumberField({ initial: 0, integer: true }), + mystique: new fields.NumberField({ initial: 0, integer: true }), + ombre: new fields.NumberField({ initial: 0, integer: true }), + savant: new fields.NumberField({ initial: 0, integer: true }), + }), + songes: new fields.SchemaField({ + value: new fields.NumberField({ initial: 0, integer: true, min: 0 }), + points: new fields.NumberField({ initial: 0, integer: true, min: 0 }), + max: new fields.NumberField({ initial: 0, integer: true, min: 0 }), + }), + cauchemar: new fields.SchemaField({ + value: new fields.NumberField({ initial: 0, integer: true, min: 0 }), + points: new fields.NumberField({ initial: 0, integer: true, min: 0 }), + max: new fields.NumberField({ initial: 0, integer: true, min: 0 }), + }), + hp: new fields.SchemaField({ + value: new fields.NumberField({ initial: 8, integer: true, min: 0 }), + max: new fields.NumberField({ initial: 8, integer: true, min: 0 }), + display: new fields.StringField({ initial: "" }), + }), + protection: new fields.NumberField({ initial: 0, integer: true, min: 0 }), + statblock: new fields.SchemaField({ + damage: new fields.HTMLField({ initial: "" }), + special: new fields.HTMLField({ initial: "" }), + spellSonges: new fields.HTMLField({ initial: "" }), + spellCauchemar: new fields.HTMLField({ initial: "" }), + }), + } + } +} diff --git a/modules/models/equipement.mjs b/modules/models/equipement.mjs new file mode 100644 index 0000000..8ddc4b9 --- /dev/null +++ b/modules/models/equipement.mjs @@ -0,0 +1,18 @@ +import { BaseItemDataModel } from "./base-item.mjs" + +export default class EquipementDataModel extends BaseItemDataModel { + static defineSchema() { + const fields = foundry.data.fields + return { + ...this.defineBaseSchema(), + category: new fields.StringField({ initial: "survie" }), + quantity: new fields.NumberField({ initial: 1, integer: true, min: 0 }), + price: new fields.NumberField({ initial: 0, integer: true, min: 0 }), + bonus: new fields.StringField({ initial: "" }), + usage: new fields.StringField({ initial: "" }), + lifespan: new fields.StringField({ initial: "" }), + equipped: new fields.BooleanField({ initial: false }), + consumable: new fields.BooleanField({ initial: false }), + } + } +} diff --git a/modules/models/index.mjs b/modules/models/index.mjs new file mode 100644 index 0000000..aa1b97a --- /dev/null +++ b/modules/models/index.mjs @@ -0,0 +1,13 @@ +export { BaseItemDataModel } from "./base-item.mjs" +export { default as PersonnageDataModel } from "./personnage.mjs" +export { default as CompagnieDataModel } from "./compagnie.mjs" +export { default as CreatureDataModel } from "./creature.mjs" +export { default as RaceDataModel } from "./race.mjs" +export { default as TribuDataModel } from "./tribu.mjs" +export { default as MetierDataModel } from "./metier.mjs" +export { default as CompetenceDataModel } from "./competence.mjs" +export { default as SortilegeDataModel } from "./sortilege.mjs" +export { default as ArmeDataModel } from "./arme.mjs" +export { default as ArmureDataModel } from "./armure.mjs" +export { default as EquipementDataModel } from "./equipement.mjs" +export { default as PouvoirCompagnieDataModel } from "./pouvoir-compagnie.mjs" diff --git a/modules/models/metier.mjs b/modules/models/metier.mjs new file mode 100644 index 0000000..d20106f --- /dev/null +++ b/modules/models/metier.mjs @@ -0,0 +1,39 @@ +import { BaseItemDataModel } from "./base-item.mjs" + +export default class MetierDataModel extends BaseItemDataModel { + static defineSchema() { + const fields = foundry.data.fields + return { + ...this.defineBaseSchema(), + specialRules: new fields.HTMLField({ initial: "" }), + roleplayNotes: new fields.HTMLField({ initial: "" }), + skillBonuses: new fields.ArrayField(new fields.SchemaField({ + key: new fields.StringField({ initial: "" }), + alternativeKeys: new fields.ArrayField(new fields.StringField(), { initial: [] }), + base: new fields.NumberField({ initial: 0, integer: true, min: 0 }), + domainsGranted: new fields.ArrayField(new fields.StringField(), { initial: [] }), + domainsToChoose: new fields.NumberField({ initial: 0, integer: true, min: 0 }), + domainsChoiceText: new fields.StringField({ initial: "" }), + }), { initial: [] }), + startingEquipment: new fields.ArrayField(new fields.SchemaField({ + name: new fields.StringField({ initial: "" }), + type: new fields.StringField({ initial: "equipement" }), + quantity: new fields.NumberField({ initial: 1, integer: true, min: 0 }), + details: new fields.StringField({ initial: "" }), + choiceText: new fields.StringField({ initial: "" }), + ecorces: new fields.NumberField({ initial: 0, integer: true, min: 0 }), + }), { initial: [] }), + spellGrants: new fields.ArrayField(new fields.SchemaField({ + tradition: new fields.StringField({ initial: "" }), + skillKey: new fields.StringField({ initial: "" }), + polarity: new fields.StringField({ initial: "" }), + amount: new fields.NumberField({ initial: 0, integer: true, min: 0 }), + }), { initial: [] }), + revenues: new fields.SchemaField({ + beginner: new fields.NumberField({ initial: 0, integer: true, min: 0 }), + intermediate: new fields.NumberField({ initial: 0, integer: true, min: 0 }), + expert: new fields.NumberField({ initial: 0, integer: true, min: 0 }), + }), + } + } +} diff --git a/modules/models/personnage.mjs b/modules/models/personnage.mjs new file mode 100644 index 0000000..725dd8f --- /dev/null +++ b/modules/models/personnage.mjs @@ -0,0 +1,66 @@ +export default class PersonnageDataModel extends foundry.abstract.TypeDataModel { + static defineSchema() { + const fields = foundry.data.fields + return { + biodata: new fields.SchemaField({ + age: new fields.NumberField({ initial: 20, integer: true, min: 0 }), + sexe: new fields.StringField({ initial: "" }), + motscles: new fields.StringField({ initial: "" }), + description: new fields.HTMLField({ initial: "" }), + notes: new fields.HTMLField({ initial: "" }), + gmnotes: new fields.HTMLField({ initial: "" }), + }), + references: new fields.SchemaField({ + raceId: new fields.StringField({ initial: "" }), + tribuId: new fields.StringField({ initial: "" }), + metierId: new fields.StringField({ initial: "" }), + compagnieId: new fields.StringField({ initial: "" }), + }), + size: new fields.SchemaField({ + value: new fields.NumberField({ initial: 2, integer: true, min: 1, max: 4 }), + label: new fields.StringField({ initial: "" }), + }), + profils: new fields.SchemaField({ + artiste: new fields.NumberField({ initial: 0, integer: true }), + athlete: new fields.NumberField({ initial: 0, integer: true }), + chasseur: new fields.NumberField({ initial: 0, integer: true }), + faiseur: new fields.NumberField({ initial: 0, integer: true }), + forceNature: new fields.NumberField({ initial: 0, integer: true }), + guerrier: new fields.NumberField({ initial: 0, integer: true }), + mystique: new fields.NumberField({ initial: 0, integer: true }), + ombre: new fields.NumberField({ initial: 0, integer: true }), + savant: new fields.NumberField({ initial: 0, integer: true }), + }), + songes: new fields.SchemaField({ + value: new fields.NumberField({ initial: 1, integer: true, min: 0 }), + points: new fields.NumberField({ initial: 2, integer: true, min: 0 }), + max: new fields.NumberField({ initial: 2, integer: true, min: 0 }), + debt: new fields.NumberField({ initial: 0, integer: true, min: 0 }), + xpCredit: new fields.NumberField({ initial: 0, integer: true, min: 0 }), + }), + cauchemar: new fields.SchemaField({ + value: new fields.NumberField({ initial: 0, integer: true, min: 0 }), + points: new fields.NumberField({ initial: 0, integer: true, min: 0 }), + max: new fields.NumberField({ initial: 0, integer: true, min: 0 }), + debt: new fields.NumberField({ initial: 0, integer: true, min: 0 }), + xpCredit: new fields.NumberField({ initial: 0, integer: true, min: 0 }), + }), + hp: new fields.SchemaField({ + value: new fields.NumberField({ initial: 8, integer: true, min: 0 }), + max: new fields.NumberField({ initial: 8, integer: true, min: 0 }), + bonus: new fields.NumberField({ initial: 0, integer: true }), + }), + experience: new fields.SchemaField({ + value: new fields.NumberField({ initial: 0, integer: true, min: 0 }), + }), + money: new fields.SchemaField({ + ecorces: new fields.NumberField({ initial: 0, integer: true, min: 0 }), + }), + flagsNarratifs: new fields.SchemaField({ + ombreDuTourment: new fields.BooleanField({ initial: false }), + isCaptain: new fields.BooleanField({ initial: false }), + }), + visions: new fields.HTMLField({ initial: "" }), + } + } +} diff --git a/modules/models/pouvoir-compagnie.mjs b/modules/models/pouvoir-compagnie.mjs new file mode 100644 index 0000000..d474fd5 --- /dev/null +++ b/modules/models/pouvoir-compagnie.mjs @@ -0,0 +1,18 @@ +import { BaseItemDataModel } from "./base-item.mjs" + +export default class PouvoirCompagnieDataModel extends BaseItemDataModel { + static defineSchema() { + const fields = foundry.data.fields + return { + ...this.defineBaseSchema(), + scope: new fields.StringField({ initial: "compagnie" }), + effectMode: new fields.StringField({ initial: "" }), + ruleText: new fields.HTMLField({ initial: "" }), + limitedUses: new fields.StringField({ initial: "" }), + resourceImpact: new fields.StringField({ initial: "" }), + activationCondition: new fields.StringField({ initial: "À portée de vue du capitaine" }), + captainVisible: new fields.BooleanField({ initial: true }), + captainNeedsWitness: new fields.BooleanField({ initial: true }), + } + } +} diff --git a/modules/models/race.mjs b/modules/models/race.mjs new file mode 100644 index 0000000..231e30c --- /dev/null +++ b/modules/models/race.mjs @@ -0,0 +1,30 @@ +import { BaseItemDataModel } from "./base-item.mjs" + +export default class RaceDataModel extends BaseItemDataModel { + static defineSchema() { + const fields = foundry.data.fields + return { + ...this.defineBaseSchema(), + size: new fields.NumberField({ initial: 2, integer: true, min: 1, max: 4 }), + lifeExpectancy: new fields.NumberField({ initial: 50, integer: true, min: 0 }), + keywords: new fields.ArrayField(new fields.StringField(), { initial: [] }), + mainTribes: new fields.ArrayField(new fields.StringField(), { initial: [] }), + language: new fields.StringField({ initial: "" }), + languageDomains: new fields.ArrayField(new fields.StringField(), { initial: [] }), + specialRules: new fields.HTMLField({ initial: "" }), + appearance: new fields.HTMLField({ initial: "" }), + roleplayHints: new fields.ArrayField(new fields.StringField(), { initial: [] }), + profiles: new fields.SchemaField({ + artiste: new fields.NumberField({ initial: 0, integer: true }), + athlete: new fields.NumberField({ initial: 0, integer: true }), + chasseur: new fields.NumberField({ initial: 0, integer: true }), + faiseur: new fields.NumberField({ initial: 0, integer: true }), + forceNature: new fields.NumberField({ initial: 0, integer: true }), + guerrier: new fields.NumberField({ initial: 0, integer: true }), + mystique: new fields.NumberField({ initial: 0, integer: true }), + ombre: new fields.NumberField({ initial: 0, integer: true }), + savant: new fields.NumberField({ initial: 0, integer: true }), + }), + } + } +} diff --git a/modules/models/sortilege.mjs b/modules/models/sortilege.mjs new file mode 100644 index 0000000..93559ad --- /dev/null +++ b/modules/models/sortilege.mjs @@ -0,0 +1,25 @@ +import { BaseItemDataModel } from "./base-item.mjs" + +export default class SortilegeDataModel extends BaseItemDataModel { + static defineSchema() { + const fields = foundry.data.fields + return { + ...this.defineBaseSchema(), + tradition: new fields.StringField({ initial: "" }), + skillKey: new fields.StringField({ initial: "" }), + polarity: new fields.StringField({ initial: "songes" }), + cost: new fields.NumberField({ initial: 1, integer: true, min: 0 }), + costFormula: new fields.StringField({ initial: "" }), + variableCost: new fields.BooleanField({ initial: false }), + preparation: new fields.StringField({ initial: "" }), + duration: new fields.StringField({ initial: "" }), + range: new fields.StringField({ initial: "" }), + area: new fields.StringField({ initial: "" }), + stacking: new fields.StringField({ initial: "" }), + requiredDomains: new fields.ArrayField(new fields.StringField(), { initial: [] }), + artsDomains: new fields.ArrayField(new fields.StringField(), { initial: [] }), + effectsText: new fields.HTMLField({ initial: "" }), + ruleTags: new fields.ArrayField(new fields.StringField(), { initial: [] }), + } + } +} diff --git a/modules/models/tribu.mjs b/modules/models/tribu.mjs new file mode 100644 index 0000000..2024385 --- /dev/null +++ b/modules/models/tribu.mjs @@ -0,0 +1,30 @@ +import { BaseItemDataModel } from "./base-item.mjs" + +export default class TribuDataModel extends BaseItemDataModel { + static defineSchema() { + const fields = foundry.data.fields + return { + ...this.defineBaseSchema(), + keywords: new fields.ArrayField(new fields.StringField(), { initial: [] }), + mainRace: new fields.StringField({ initial: "" }), + spokenLanguage: new fields.StringField({ initial: "" }), + philosophy: new fields.StringField({ initial: "" }), + pride: new fields.StringField({ initial: "" }), + mythNature: new fields.StringField({ initial: "" }), + mythEdenia: new fields.StringField({ initial: "" }), + territory: new fields.StringField({ initial: "" }), + specialRules: new fields.HTMLField({ initial: "" }), + roleplayNotes: new fields.HTMLField({ initial: "" }), + restrictedJobs: new fields.ArrayField(new fields.StringField(), { initial: [] }), + allowedJobs: new fields.ArrayField(new fields.StringField(), { initial: [] }), + skillBonuses: new fields.ArrayField(new fields.SchemaField({ + key: new fields.StringField({ initial: "" }), + alternativeKeys: new fields.ArrayField(new fields.StringField(), { initial: [] }), + base: new fields.NumberField({ initial: 0, integer: true, min: 0 }), + domainsGranted: new fields.ArrayField(new fields.StringField(), { initial: [] }), + domainsToChoose: new fields.NumberField({ initial: 0, integer: true, min: 0 }), + domainsChoiceText: new fields.StringField({ initial: "" }), + }), { initial: [] }), + } + } +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..8b281bf --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2687 @@ +{ + "name": "fvtt-les-oublies", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "fvtt-les-oublies", + "version": "0.1.0", + "license": "UNLICENSED", + "devDependencies": { + "gulp": "^5.0.0", + "gulp-less": "^5.0.0", + "gulp-sourcemaps": "^3.0.0" + } + }, + "node_modules/@gulp-sourcemaps/identity-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/identity-map/-/identity-map-2.0.1.tgz", + "integrity": "sha512-Tb+nSISZku+eQ4X1lAkevcQa+jknn/OVUgZ3XCxEKIsLsqYuPoJwJOPQeaOk75X3WPftb29GWY1eqE7GLsXb1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^6.4.1", + "normalize-path": "^3.0.0", + "postcss": "^7.0.16", + "source-map": "^0.6.0", + "through2": "^3.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/@gulp-sourcemaps/identity-map/node_modules/through2": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" + } + }, + "node_modules/@gulp-sourcemaps/map-sources": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/map-sources/-/map-sources-1.0.0.tgz", + "integrity": "sha512-o/EatdaGt8+x2qpb0vFLC/2Gug/xYPRXb6a+ET1wGYKozKN3krDWC/zZFZAtrzxJHuDL12mwdfEFKcKMNvc55A==", + "dev": true, + "license": "MIT", + "dependencies": { + "normalize-path": "^2.0.1", + "through2": "^2.0.3" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/@gulp-sourcemaps/map-sources/node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@gulp-sourcemaps/map-sources/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/@gulp-sourcemaps/map-sources/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@gulp-sourcemaps/map-sources/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/@gulp-sourcemaps/map-sources/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/@gulpjs/messages": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@gulpjs/messages/-/messages-1.1.0.tgz", + "integrity": "sha512-Ys9sazDatyTgZVb4xPlDufLweJ/Os2uHWOv+Caxvy2O85JcnT4M3vc73bi8pdLWlv3fdWQz3pdI9tVwo8rQQSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@gulpjs/to-absolute-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@gulpjs/to-absolute-glob/-/to-absolute-glob-4.0.0.tgz", + "integrity": "sha512-kjotm7XJrJ6v+7knhPaRgaT6q8F8K2jiafwYdNHLzmV0uGLuZY43FK6smNSHUPrhq5kX2slCUy+RGG/xGqmIKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-negated-glob": "^1.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/acorn": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-colors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-wrap": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ansi-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha512-ZyznvL8k/FZeQHr2T6LzcJ/+vBApDnMNZvfVFy3At0knswWd6rJ3/0Hhmpu8oqa6C92npmozs890sX9Dl6q+Qw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", + "integrity": "sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-slice": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", + "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/async-done": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/async-done/-/async-done-2.0.0.tgz", + "integrity": "sha512-j0s3bzYq9yKIVLKGE/tWlCpa3PfFLcrDZLTSVdnnCTGagXuXBJO4SsY9Xdk/fQBirCkH4evW5xOeJXqlAQFdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.4.4", + "once": "^1.4.0", + "stream-exhaust": "^1.0.2" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/async-settle": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-2.0.0.tgz", + "integrity": "sha512-Obu/KE8FurfQRN6ODdHN9LuXqwC+JFIM9NRyZqJJ4ZfLJmIYN9Rg0/kb+wF70VV5+fJusTMQlJ1t5rF7J/ETdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-done": "^2.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true, + "license": "(MIT OR Apache-2.0)", + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/b4a": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", + "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, + "node_modules/bach": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/bach/-/bach-2.0.1.tgz", + "integrity": "sha512-A7bvGMGiTOxGMpNupYl9HQTf0FFDNF4VCmks4PJpFyN1AX2pdKuxuwdvUz2Hu388wcgp+OvGFNsumBfFNkR7eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-done": "^2.0.0", + "async-settle": "^2.0.0", + "now-and-later": "^3.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/bare-events": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", + "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "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" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bl": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "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", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-anything": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", + "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-what": "^4.1.8" + }, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/copy-props": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-4.0.0.tgz", + "integrity": "sha512-bVWtw1wQLzzKiYROtvNlbJgxgBYt2bMJpkCbKmXM3xyijvcjjWXEk5nyrrT3bgJ7ODb19ZohE2T0Y3FgNPyoTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "each-props": "^3.0.0", + "is-plain-object": "^5.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/css": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/css/-/css-3.0.0.tgz", + "integrity": "sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "source-map": "^0.6.1", + "source-map-resolve": "^0.6.0" + } + }, + "node_modules/d": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "dev": true, + "license": "ISC", + "dependencies": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/debug-fabulous": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/debug-fabulous/-/debug-fabulous-1.1.0.tgz", + "integrity": "sha512-GZqvGIgKNlUnHUPQhepnUZFIMoi3dgZKQBzKDeL2g7oJF9SNAji/AAu36dusFUas0O+pae74lNeoIPHqXWDkLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "3.X", + "memoizee": "0.4.X", + "object-assign": "4.X" + } + }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-newline": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", + "integrity": "sha512-CwffZFvlJffUg9zZA0uqrjQayUTC8ob94pnr5sFwaVv3IOmkfUHcWH+jXaQK3askE51Cqe8/9Ql/0uXNwqZ8Zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/each-props": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/each-props/-/each-props-3.0.0.tgz", + "integrity": "sha512-IYf1hpuWrdzse/s/YJOrFmU15lyhSzxelNVAHTEG3DtP4QsLTWZUzcUL3HMXmKQxXpa4EIrBPpwRgj0aehdvAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-object": "^5.0.0", + "object.defaults": "^1.1.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es5-ext": { + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", + "dev": true, + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "dev": true, + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", + "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "d": "^1.0.2", + "ext": "^1.7.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "dev": true, + "license": "ISC", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "dev": true, + "license": "ISC", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, + "node_modules/expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "homedir-polyfill": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "dev": true, + "license": "ISC", + "dependencies": { + "type": "^2.7.2" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-3.0.0.tgz", + "integrity": "sha512-hKKNajm46uNmTlhHSyZkmToAc56uZJwYq7yrciZjqOxnlfQwERDQJmHPUp7m1m9wx8vgOe8IaCKZ5Kv2k1DdCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fastest-levenshtein": "^1.0.7" + } + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/findup-sync": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-5.0.0.tgz", + "integrity": "sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.3", + "micromatch": "^4.0.4", + "resolve-dir": "^1.0.1" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/fined": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fined/-/fined-2.0.0.tgz", + "integrity": "sha512-OFRzsL6ZMHz5s0JrsEr+TpdGNCtrVtnuG3x1yzGNiQHT0yaDnXAj8V/lWcpJVrnoDpcwXcASxAZYbuXda2Y82A==", + "dev": true, + "license": "MIT", + "dependencies": { + "expand-tilde": "^2.0.2", + "is-plain-object": "^5.0.0", + "object.defaults": "^1.1.0", + "object.pick": "^1.3.0", + "parse-filepath": "^1.0.2" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/flagged-respawn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-2.0.0.tgz", + "integrity": "sha512-Gq/a6YCi8zexmGHMuJwahTGzXlAZAOsbCVKduWXC6TlLCjjFRlExMJc4GC2NYPYZ0r/brw9P7CpRgQmlPVeOoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg==", + "dev": true, + "license": "MIT", + "dependencies": { + "for-in": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fs-mkdirp-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-2.0.1.tgz", + "integrity": "sha512-UTOY+59K6IA94tec8Wjqm0FSh5OVudGNB0NL/P6fB3HiE3bYOY3VYBGijsnOHNkQSwC1FKkU77pmq7xp9CskLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.8", + "streamx": "^2.12.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob-stream": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-8.0.3.tgz", + "integrity": "sha512-fqZVj22LtFJkHODT+M4N1RJQ3TjnnQhfE9GwZI8qXscYarnhpip70poMldRnP8ipQ/w0B621kOhfc53/J9bd/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@gulpjs/to-absolute-glob": "^4.0.0", + "anymatch": "^3.1.3", + "fastq": "^1.13.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "is-negated-glob": "^1.0.0", + "normalize-path": "^3.0.0", + "streamx": "^2.12.5" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-stream/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-watcher": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-6.0.0.tgz", + "integrity": "sha512-wGM28Ehmcnk2NqRORXFOTOR064L4imSw3EeOqU5bIwUf62eXGwg89WivH6VMahL8zlQHeodzvHpXplrqzrz3Nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-done": "^2.0.0", + "chokidar": "^3.5.3" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glogg": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-2.2.0.tgz", + "integrity": "sha512-eWv1ds/zAlz+M1ioHsyKJomfY7jbDDPpwSkv14KQj89bycx1nvK5/2Cj/T9g7kzJcX5Bc7Yv22FjfBZS/jl94A==", + "dev": true, + "license": "MIT", + "dependencies": { + "sparkles": "^2.1.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/gulp": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/gulp/-/gulp-5.0.1.tgz", + "integrity": "sha512-PErok3DZSA5WGMd6XXV3IRNO0mlB+wW3OzhFJLEec1jSERg2j1bxJ6e5Fh6N6fn3FH2T9AP4UYNb/pYlADB9sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-watcher": "^6.0.0", + "gulp-cli": "^3.1.0", + "undertaker": "^2.0.0", + "vinyl-fs": "^4.0.2" + }, + "bin": { + "gulp": "bin/gulp.js" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/gulp-cli": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-3.1.0.tgz", + "integrity": "sha512-zZzwlmEsTfXcxRKiCHsdyjZZnFvXWM4v1NqBJSYbuApkvVKivjcmOS2qruAJ+PkEHLFavcDKH40DPc1+t12a9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@gulpjs/messages": "^1.1.0", + "chalk": "^4.1.2", + "copy-props": "^4.0.0", + "gulplog": "^2.2.0", + "interpret": "^3.1.1", + "liftoff": "^5.0.1", + "mute-stdout": "^2.0.0", + "replace-homedir": "^2.0.0", + "semver-greatest-satisfied-range": "^2.0.0", + "string-width": "^4.2.3", + "v8flags": "^4.0.0", + "yargs": "^16.2.0" + }, + "bin": { + "gulp": "bin/gulp.js" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/gulp-less": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/gulp-less/-/gulp-less-5.0.0.tgz", + "integrity": "sha512-W2I3TewO/By6UZsM/wJG3pyK5M6J0NYmJAAhwYXQHR+38S0iDtZasmUgFCH3CQj+pQYw/PAIzxvFvwtEXz1HhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "less": "^3.7.1 || ^4.0.0", + "object-assign": "^4.0.1", + "plugin-error": "^1.0.0", + "replace-ext": "^2.0.0", + "through2": "^4.0.0", + "vinyl-sourcemaps-apply": "^0.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/gulp-sourcemaps": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-3.0.0.tgz", + "integrity": "sha512-RqvUckJkuYqy4VaIH60RMal4ZtG0IbQ6PXMNkNsshEGJ9cldUPRb/YCgboYae+CLAs1HQNb4ADTKCx65HInquQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@gulp-sourcemaps/identity-map": "^2.0.1", + "@gulp-sourcemaps/map-sources": "^1.0.0", + "acorn": "^6.4.1", + "convert-source-map": "^1.0.0", + "css": "^3.0.0", + "debug-fabulous": "^1.0.0", + "detect-newline": "^2.0.0", + "graceful-fs": "^4.0.0", + "source-map": "^0.6.0", + "strip-bom-string": "^1.0.0", + "through2": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gulp-sourcemaps/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/gulp-sourcemaps/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/gulp-sourcemaps/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/gulp-sourcemaps/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/gulplog": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-2.2.0.tgz", + "integrity": "sha512-V2FaKiOhpR3DRXZuYdRLn/qiY0yI5XmqbTKrYbdemJ+xOh2d2MOweI/XFgMzd/9+1twdvMwllnZbWZNJ+BOm4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "glogg": "^2.2.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-passwd": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "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": "BSD-3-Clause" + }, + "node_modules/image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", + "dev": true, + "license": "MIT", + "optional": true, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extendable/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-negated-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", + "integrity": "sha512-czXVVn/QEmgvej1f50BZ648vUI+em0xqMq2Sn+QncCLN4zj1UAxlT+kw/6ggQTOaZPd1HqKQGEqbpQVtJucWug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-relative": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-unc-path": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-unc-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "unc-path-regex": "^0.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-valid-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", + "integrity": "sha512-AhiROmoEFDSsjx8hW+5sGwgKVIORcXnrlAx/R0ZSeaPw70Vw0CqkGBBhHGL58Uox2eXnU1AnvXJl1XlyedO5bA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-what": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", + "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/last-run": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/last-run/-/last-run-2.0.0.tgz", + "integrity": "sha512-j+y6WhTLN4Itnf9j5ZQos1BGPCS8DAwmgMroR3OzfxAsBxam0hMw7J8M3KqZl0pLQJ1jNnwIexg5DYpC/ctwEQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/lead": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/lead/-/lead-4.0.0.tgz", + "integrity": "sha512-DpMa59o5uGUWWjruMp71e6knmwKU3jRBBn1kjuLWN9EeIOxNeSAwvHf03WIl8g/ZMR2oSQC9ej3yeLBwdDc/pg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/less": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/less/-/less-4.6.4.tgz", + "integrity": "sha512-OJmO5+HxZLLw0RLzkqaNHzcgEAQG7C0y3aMbwtCzIUFZsLMNNq/1IdAdHEycQ58CwUO3jPTHmoN+tE5I7FQxNg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "copy-anything": "^3.0.5", + "parse-node-version": "^1.0.1" + }, + "bin": { + "lessc": "bin/lessc" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "needle": "^3.1.0", + "source-map": "~0.6.0" + } + }, + "node_modules/liftoff": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-5.0.1.tgz", + "integrity": "sha512-wwLXMbuxSF8gMvubFcFRp56lkFV69twvbU5vDPbaw+Q+/rF8j0HKjGbIdlSi+LuJm9jf7k9PB+nTxnsLMPcv2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "extend": "^3.0.2", + "findup-sync": "^5.0.0", + "fined": "^2.0.0", + "flagged-respawn": "^2.0.0", + "is-plain-object": "^5.0.0", + "rechoir": "^0.8.0", + "resolve": "^1.20.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/lru-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", + "integrity": "sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es5-ext": "~0.10.2" + } + }, + "node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/memoizee": { + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.17.tgz", + "integrity": "sha512-DGqD7Hjpi/1or4F/aYAspXKNm5Yili0QDAFAY4QYvpqpgiY6+1jOfqpmByzjxbWd/T9mChbCArXAbDAsTm5oXA==", + "dev": true, + "license": "ISC", + "dependencies": { + "d": "^1.0.2", + "es5-ext": "^0.10.64", + "es6-weak-map": "^2.0.3", + "event-emitter": "^0.3.5", + "is-promise": "^2.2.2", + "lru-queue": "^0.1.0", + "next-tick": "^1.1.0", + "timers-ext": "^0.1.7" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mute-stdout": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-2.0.0.tgz", + "integrity": "sha512-32GSKM3Wyc8dg/p39lWPKYu8zci9mJFzV1Np9Of0ZEpe6Fhssn/FbI7ywAMd40uX+p3ZKh3T5EeCFv81qS3HmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/needle": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-3.5.0.tgz", + "integrity": "sha512-jaQyPKKk2YokHrEg+vFDYxXIHTCBgiZwSHOoVx/8V3GIBS8/VN6NdVRmg8q1ERtPkMvmOvebsgga4sAj5hls/w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/now-and-later": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-3.0.0.tgz", + "integrity": "sha512-pGO4pzSdaxhWTGkfSfHx3hVzJVslFPwBp2Myq9MYN/ChfJZF87ochMAXnvz6/58RJSf5ik2q9tXprBBrk2cpcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.defaults": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", + "integrity": "sha512-c/K0mw/F11k4dEUBMW8naXUuBuhxRCfG7W+yFy8EcijU/rSmazOUd1XAEEe6bC0OuXY4HUKjTJv7xbxIMqdxrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-each": "^1.0.1", + "array-slice": "^1.0.0", + "for-own": "^1.0.0", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parse-filepath": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", + "integrity": "sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-absolute": "^1.0.0", + "map-cache": "^0.2.0", + "path-root": "^0.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-root": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", + "integrity": "sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-root-regex": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-root-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", + "integrity": "sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/plugin-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", + "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^1.0.1", + "arr-diff": "^4.0.0", + "arr-union": "^3.1.0", + "extend-shallow": "^3.0.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==", + "dev": true, + "license": "ISC" + }, + "node_modules/replace-ext": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", + "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/replace-homedir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-2.0.0.tgz", + "integrity": "sha512-bgEuQQ/BHW0XkkJtawzrfzHFSN70f/3cNOiHa2QsYxqrjaC30X1k74FJ6xswVBP0sr0SpGIdVFuPwfrYziVeyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-options": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-2.0.0.tgz", + "integrity": "sha512-/FopbmmFOQCfsCx77BRFdKOniglTiHumLgwvd6IDPihy1GKkadZbgQJBcTb2lMzSR1pndzd96b1nZrreZ7+9/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "value-or-function": "^4.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "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" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "dev": true, + "license": "BlueOak-1.0.0", + "optional": true, + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/semver-greatest-satisfied-range": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-2.0.0.tgz", + "integrity": "sha512-lH3f6kMbwyANB7HuOWRMlLCa2itaCrZJ+SAqqkSZrZKO/cAsk2EOyaKHUtNkVLFyFW9pct22SFesFp3Z7zpA0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "sver": "^1.8.3" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-resolve": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz", + "integrity": "sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==", + "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", + "dev": true, + "license": "MIT", + "dependencies": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0" + } + }, + "node_modules/sparkles": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-2.1.0.tgz", + "integrity": "sha512-r7iW1bDw8R/cFifrD3JnQJX0K1jqT0kprL48BiBpLZLJPmAm34zsVBsK5lc7HirZYZqMW65dOXZgbAGt/I6frg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/stream-composer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-composer/-/stream-composer-1.0.2.tgz", + "integrity": "sha512-bnBselmwfX5K10AH6L4c8+S5lgZMWI7ZYrz2rvYjCPB2DIMC4Ig8OpxGpNJSxRZ58oti7y1IcNvjBAz9vW5m4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "streamx": "^2.13.2" + } + }, + "node_modules/stream-exhaust": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", + "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==", + "dev": true, + "license": "MIT" + }, + "node_modules/streamx": { + "version": "2.25.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.25.0.tgz", + "integrity": "sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg==", + "dev": true, + "license": "MIT", + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sver": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/sver/-/sver-1.8.4.tgz", + "integrity": "sha512-71o1zfzyawLfIWBOmw8brleKyvnbn73oVHNCsu51uPMz/HWiKkkXsI31JjHW5zqXEqnPYkIiHd8ZmL7FCimLEA==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "semver": "^6.3.0" + } + }, + "node_modules/sver/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "streamx": "^2.12.5" + } + }, + "node_modules/text-decoder": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", + "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, + "node_modules/through2": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "3" + } + }, + "node_modules/timers-ext": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.8.tgz", + "integrity": "sha512-wFH7+SEAcKfJpfLPkrgMPvvwnEtj8W4IurvEyrKsDleXnKLCDw71w8jltvfLa8Rm4qQxxT4jmDBYbJG/z7qoww==", + "dev": true, + "license": "ISC", + "dependencies": { + "es5-ext": "^0.10.64", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/to-through": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/to-through/-/to-through-3.0.0.tgz", + "integrity": "sha512-y8MN937s/HVhEoBU1SxfHC+wxCHkV1a9gW8eAdTadYh/bGyesZIVcbjI+mSpFbSVwQici/XjBjuUyri1dnXwBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "streamx": "^2.12.5" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/type": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", + "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/unc-path-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "integrity": "sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/undertaker": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-2.0.0.tgz", + "integrity": "sha512-tO/bf30wBbTsJ7go80j0RzA2rcwX6o7XPBpeFcb+jzoeb4pfMM2zUeSDIkY1AWqeZabWxaQZ/h8N9t35QKDLPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bach": "^2.0.1", + "fast-levenshtein": "^3.0.0", + "last-run": "^2.0.0", + "undertaker-registry": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/undertaker-registry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-2.0.0.tgz", + "integrity": "sha512-+hhVICbnp+rlzZMgxXenpvTxpuvA67Bfgtt+O9WOE5jo7w/dyiF1VmoZVIHvP2EkUjsyKyTwYKlLhA+j47m1Ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/v8flags": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-4.0.1.tgz", + "integrity": "sha512-fcRLaS4H/hrZk9hYwbdRM35D0U8IYMfEClhXxCivOojl+yTRAZH3Zy2sSy6qVCiGbV9YAtPssP6jaChqC9vPCg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/value-or-function": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-4.0.0.tgz", + "integrity": "sha512-aeVK81SIuT6aMJfNo9Vte8Dw0/FZINGBV8BfCraGtqVxIeLAEhJyoWs8SmvRVmXfGss2PmmOwZCuBPbZR+IYWg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/vinyl": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.1.tgz", + "integrity": "sha512-0QwqXteBNXgnLCdWdvPQBX6FXRHtIH3VhJPTd5Lwn28tJXc34YqSCWUmkOvtJHBmB3gGoPtrOKk3Ts8/kEZ9aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^2.1.2", + "remove-trailing-separator": "^1.1.0", + "replace-ext": "^2.0.0", + "teex": "^1.0.1" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/vinyl-contents": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vinyl-contents/-/vinyl-contents-2.0.0.tgz", + "integrity": "sha512-cHq6NnGyi2pZ7xwdHSW1v4Jfnho4TEGtxZHw01cmnc8+i7jgR6bRnED/LbrKan/Q7CvVLbnvA5OepnhbpjBZ5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^5.0.0", + "vinyl": "^3.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/vinyl-fs": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-4.0.2.tgz", + "integrity": "sha512-XRFwBLLTl8lRAOYiBqxY279wY46tVxLaRhSwo3GzKEuLz1giffsOquWWboD/haGf5lx+JyTigCFfe7DWHoARIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fs-mkdirp-stream": "^2.0.1", + "glob-stream": "^8.0.3", + "graceful-fs": "^4.2.11", + "iconv-lite": "^0.6.3", + "is-valid-glob": "^1.0.0", + "lead": "^4.0.0", + "normalize-path": "3.0.0", + "resolve-options": "^2.0.0", + "stream-composer": "^1.0.2", + "streamx": "^2.14.0", + "to-through": "^3.0.0", + "value-or-function": "^4.0.0", + "vinyl": "^3.0.1", + "vinyl-sourcemap": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/vinyl-sourcemap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-2.0.0.tgz", + "integrity": "sha512-BAEvWxbBUXvlNoFQVFVHpybBbjW1r03WhohJzJDSfgrrK5xVYIDTan6xN14DlyImShgDRv2gl9qhM6irVMsV0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "convert-source-map": "^2.0.0", + "graceful-fs": "^4.2.10", + "now-and-later": "^3.0.0", + "streamx": "^2.12.5", + "vinyl": "^3.0.0", + "vinyl-contents": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/vinyl-sourcemap/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/vinyl-sourcemaps-apply": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz", + "integrity": "sha512-+oDh3KYZBoZC8hfocrbrxbLUeaYtQK7J5WU5Br9VqWqmCll3tFJqKp97GC9GmMsVIL0qnx2DgEDVxdo5EZ5sSw==", + "dev": true, + "license": "ISC", + "dependencies": { + "source-map": "^0.5.1" + } + }, + "node_modules/vinyl-sourcemaps-apply/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..0b28eef --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "fvtt-les-oublies", + "version": "0.1.0", + "description": "Systeme FoundryVTT AppV2 pour le JDR Les Oublies", + "private": true, + "type": "module", + "scripts": { + "build": "gulp build", + "watch": "gulp watch" + }, + "author": "Copilot", + "license": "UNLICENSED", + "devDependencies": { + "gulp": "^5.0.0", + "gulp-less": "^5.0.0", + "gulp-sourcemaps": "^3.0.0" + } +} diff --git a/packs-src/armes-sample.json b/packs-src/armes-sample.json new file mode 100644 index 0000000..a2e5908 --- /dev/null +++ b/packs-src/armes-sample.json @@ -0,0 +1,10 @@ +[ + { "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/armures-sample.json b/packs-src/armures-sample.json new file mode 100644 index 0000000..ec8b4dd --- /dev/null +++ b/packs-src/armures-sample.json @@ -0,0 +1,5 @@ +[ + { "name": "Protégé", "type": "armure", "img": "icons/svg/shield.svg", "system": { "description": "

État d'équipement léger : bouclier simple, quelques pièces de défense ou protection souple.

", "state": "protégé", "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'équipement intermédiaire combinant plusieurs pièces d'armure.

", "state": "harnaché", "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'équipement lourd et très encombrant, correspondant au niveau maximal du livre de base.

", "state": "bardé", "protection": 3, "physicalPenalty": 3, "initiativePenalty": 3, "quantity": 1, "price": 0, "equipped": false } } +] diff --git a/packs-src/competences.json b/packs-src/competences.json new file mode 100644 index 0000000..c258c0c --- /dev/null +++ b/packs-src/competences.json @@ -0,0 +1,29 @@ +[ + { "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": [] } } +] diff --git a/packs-src/equipements-sample.json b/packs-src/equipements-sample.json new file mode 100644 index 0000000..34c24a4 --- /dev/null +++ b/packs-src/equipements-sample.json @@ -0,0 +1,12 @@ +[ + { "name": "Bougie de géant", "type": "equipement", "img": "icons/svg/chest.svg", "system": { "description": "

Source de lumière simple à planter sur une pique.

", "category": "voyage", "quantity": 1, "price": 180, "bonus": "", "usage": "Éclairage", "lifespan": "", "equipped": false, "consumable": true } }, + { "name": "Dé à coudre en acier", "type": "equipement", "img": "icons/svg/chest.svg", "system": { "description": "

Brasero miniature portable utilisé sans laisser de trace de campement.

", "category": "voyage", "quantity": 1, "price": 15, "bonus": "", "usage": "Campement", "lifespan": "", "equipped": false, "consumable": false } }, + { "name": "Lampe à fée des nuits", "type": "equipement", "img": "icons/svg/chest.svg", "system": { "description": "

Lampe froide alimentée par une ou plusieurs fées des nuits captives.

", "category": "voyage", "quantity": 1, "price": 360, "bonus": "", "usage": "Éclairage", "lifespan": "Quelques mois", "equipped": false, "consumable": true } }, + { "name": "Corde", "type": "equipement", "img": "icons/svg/chest.svg", "system": { "description": "

Segment de corde de 50 à 70 cm prélevé sur les cordages des géants.

", "category": "voyage", "quantity": 1, "price": 3, "bonus": "", "usage": "Escalade", "lifespan": "", "equipped": false, "consumable": false } }, + { "name": "Grappin", "type": "equipement", "img": "icons/svg/chest.svg", "system": { "description": "

Souvent façonné dans un hameçon ou une broche de géant. Peut aussi servir d'arme de corps à corps.

", "category": "voyage", "quantity": 1, "price": 6, "bonus": "", "usage": "Escalade", "lifespan": "", "equipped": false, "consumable": false } }, + { "name": "Nécessaire d'entretien d'armes", "type": "equipement", "img": "icons/svg/chest.svg", "system": { "description": "

Sert à l'affûtage et à la prévention de la corrosion des armes.

", "category": "outil", "quantity": 1, "price": 0, "bonus": "", "usage": "Maintenance", "lifespan": "", "equipped": false, "consumable": false } }, + { "name": "Nécessaire à écriture / dessins", "type": "equipement", "img": "icons/svg/chest.svg", "system": { "description": "

Encre, plume et supports de fortune pour écrire, dessiner ou cartographier.

", "category": "outil", "quantity": 1, "price": 0, "bonus": "", "usage": "Écriture", "lifespan": "", "equipped": false, "consumable": false } }, + { "name": "Piolet", "type": "equipement", "img": "icons/svg/chest.svg", "system": { "description": "

Outil d'ascension accordant un bonus de +3 aux escalades adaptées.

", "category": "voyage", "quantity": 1, "price": 60, "bonus": "+3 escalade", "usage": "Ascension", "lifespan": "", "equipped": false, "consumable": false } }, + { "name": "Rikilin", "type": "equipement", "img": "icons/svg/chest.svg", "system": { "description": "

Chaussures de marche munies de crampons métalliques pour l'escalade.

", "category": "voyage", "quantity": 1, "price": 0, "bonus": "+3 escalade", "usage": "Ascension", "lifespan": "", "equipped": false, "consumable": false } }, + { "name": "Trousse de premiers soins", "type": "equipement", "img": "icons/svg/chest.svg", "system": { "description": "

Bandages, plantes désinfectantes et fioles de soins pour les premiers secours.

", "category": "voyage", "quantity": 1, "price": 0, "bonus": "", "usage": "Soins", "lifespan": "", "equipped": false, "consumable": true } } +] diff --git a/packs-src/metiers.json b/packs-src/metiers.json new file mode 100644 index 0000000..5cecddd --- /dev/null +++ b/packs-src/metiers.json @@ -0,0 +1,198 @@ +[ + { + "name": "Mage des Songes", + "type": "metier", + "img": "icons/svg/upgrade.svg", + "system": { + "description": "

Puissant magicien du Petit Peuple et acteur majeur de sa survie en Terra Incognita.

", + "specialRules": "

Possède 3 sortilèges de magie de Songes et 3 de Cauchemar à la création.

", + "roleplayNotes": "

Le mage des Songes est une figure importante et souvent respectée, dépositaire d'une magie utile à la survie collective.

", + "skillBonuses": [ + { "key": "magie", "alternativeKeys": [], "base": 3, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "intellect", "alternativeKeys": [], "base": 3, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "erudition", "alternativeKeys": [], "base": 1, "domainsGranted": ["Lettres"], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "volonte", "alternativeKeys": [], "base": 1, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" } + ], + "startingEquipment": [ + { "name": "Sphère de verre contenant 1 fil de Songes", "type": "equipement", "quantity": 3, "details": "", "choiceText": "", "ecorces": 0 }, + { "name": "Arme", "type": "arme", "quantity": 1, "details": "", "choiceText": "Arme au choix", "ecorces": 0 }, + { "name": "Bourse", "type": "equipement", "quantity": 1, "details": "", "choiceText": "", "ecorces": 180 } + ], + "spellGrants": [ + { "tradition": "magie", "skillKey": "magie", "polarity": "songes", "amount": 3 }, + { "tradition": "magie", "skillKey": "magie", "polarity": "cauchemar", "amount": 3 } + ], + "revenues": { "beginner": 30, "intermediate": 90, "expert": 450 } + } + }, + { + "name": "Rêvirine", + "type": "metier", + "img": "icons/svg/upgrade.svg", + "system": { + "description": "

Guerrier des Songes chargé de récolter les rêves des géants.

", + "specialRules": "

Connaît 1 sortilège d'Onirologie de Songes et 1 de Cauchemar.

", + "roleplayNotes": "

Les rêvirines sont des spécialistes des dormeurs géants et des filaments de Songe, à la fois magiciens et prédateurs de rêves.

", + "skillBonuses": [ + { "key": "onirologie", "alternativeKeys": [], "base": 3, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "volonte", "alternativeKeys": [], "base": 3, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "magie", "alternativeKeys": [], "base": 1, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "endurance", "alternativeKeys": [], "base": 1, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" } + ], + "startingEquipment": [ + { "name": "Sphère de verre contenant 1 fil de Songes", "type": "equipement", "quantity": 1, "details": "", "choiceText": "", "ecorces": 0 }, + { "name": "Arme", "type": "arme", "quantity": 1, "details": "", "choiceText": "Arme au choix", "ecorces": 0 }, + { "name": "Corde solide et très fine", "type": "equipement", "quantity": 1, "details": "Environ 3 mètres", "choiceText": "", "ecorces": 0 }, + { "name": "Bourse", "type": "equipement", "quantity": 1, "details": "", "choiceText": "", "ecorces": 180 } + ], + "spellGrants": [ + { "tradition": "onirologie", "skillKey": "onirologie", "polarity": "songes", "amount": 1 }, + { "tradition": "onirologie", "skillKey": "onirologie", "polarity": "cauchemar", "amount": 1 } + ], + "revenues": { "beginner": 30, "intermediate": 100, "expert": 300 } + } + }, + { + "name": "Chevalier errant", + "type": "metier", + "img": "icons/svg/upgrade.svg", + "system": { + "description": "

Dernier représentant d'un code d'honneur hérité d'Edenia.

", + "specialRules": "", + "roleplayNotes": "

Les chevaliers errants vivent selon un idéal ancien, souvent moqué mais encore redoutable au combat.

", + "skillBonuses": [ + { "key": "melee", "alternativeKeys": [], "base": 3, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "montures", "alternativeKeys": [], "base": 3, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "commandement", "alternativeKeys": [], "base": 1, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "volonte", "alternativeKeys": [], "base": 1, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" } + ], + "startingEquipment": [ + { "name": "Arme", "type": "arme", "quantity": 2, "details": "", "choiceText": "2 armes au choix", "ecorces": 0 }, + { "name": "Armure", "type": "armure", "quantity": 1, "details": "", "choiceText": "Armure au choix", "ecorces": 0 }, + { "name": "Monture", "type": "equipement", "quantity": 1, "details": "", "choiceText": "Monture au choix", "ecorces": 0 }, + { "name": "Bourse", "type": "equipement", "quantity": 1, "details": "", "choiceText": "", "ecorces": 6 } + ], + "spellGrants": [], + "revenues": { "beginner": 3, "intermediate": 12, "expert": 30 } + } + }, + { + "name": "Mercenaire", + "type": "metier", + "img": "icons/svg/upgrade.svg", + "system": { + "description": "

Combattant de métier, formé à l'obéissance et aux conflits permanents de la Terra.

", + "specialRules": "", + "roleplayNotes": "

Les mercenaires servent dans les grinides et vivent dans une logique de guerre, de hiérarchie et de solde.

", + "skillBonuses": [ + { "key": "melee", "alternativeKeys": [], "base": 3, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "rapidite", "alternativeKeys": [], "base": 3, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "athletisme", "alternativeKeys": [], "base": 1, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "soins", "alternativeKeys": [], "base": 1, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" } + ], + "startingEquipment": [ + { "name": "Arme", "type": "arme", "quantity": 2, "details": "", "choiceText": "2 armes au choix", "ecorces": 0 }, + { "name": "Armure", "type": "armure", "quantity": 1, "details": "", "choiceText": "Armure au choix", "ecorces": 0 }, + { "name": "Bourse", "type": "equipement", "quantity": 1, "details": "", "choiceText": "", "ecorces": 18 } + ], + "spellGrants": [], + "revenues": { "beginner": 9, "intermediate": 30, "expert": 180 } + } + }, + { + "name": "Explorateur-marchand", + "type": "metier", + "img": "icons/svg/upgrade.svg", + "system": { + "description": "

Voyageur, négociant et éclaireur des routes de la Terra Incognita.

", + "specialRules": "", + "roleplayNotes": "

Les explorateurs-marchands parcourent sans cesse les routes dangereuses pour ravitailler le Petit Peuple.

", + "skillBonuses": [ + { "key": "survie", "alternativeKeys": [], "base": 3, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "seduction", "alternativeKeys": [], "base": 3, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "artisanat", "alternativeKeys": ["arts"], "base": 1, "domainsGranted": [], "domainsToChoose": 1, "domainsChoiceText": "domaine au choix dans la compétence retenue" }, + { "key": "montures", "alternativeKeys": [], "base": 1, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" } + ], + "startingEquipment": [ + { "name": "Arme", "type": "arme", "quantity": 1, "details": "", "choiceText": "Arme au choix", "ecorces": 0 }, + { "name": "Bel objet", "type": "equipement", "quantity": 1, "details": "", "choiceText": "Bel objet au choix", "ecorces": 0 }, + { "name": "Bourse", "type": "equipement", "quantity": 1, "details": "", "choiceText": "", "ecorces": 72 } + ], + "spellGrants": [], + "revenues": { "beginner": 18, "intermediate": 60, "expert": 360 } + } + }, + { + "name": "Cartographe", + "type": "metier", + "img": "icons/svg/upgrade.svg", + "system": { + "description": "

Observateur curieux, collectionneur de cartes et d'usages des géants.

", + "specialRules": "", + "roleplayNotes": "

Le cartographe observe les géants, leurs lieux et leurs bibliothèques avec une curiosité méthodique.

", + "skillBonuses": [ + { "key": "erudition", "alternativeKeys": [], "base": 3, "domainsGranted": ["Géants", "Lettres", "Terra Incognita"], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "survie", "alternativeKeys": [], "base": 3, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "artisanat", "alternativeKeys": [], "base": 1, "domainsGranted": ["Cartographie"], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "endurance", "alternativeKeys": [], "base": 1, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" } + ], + "startingEquipment": [ + { "name": "Boîte de cartographe", "type": "equipement", "quantity": 1, "details": "2 plumes, 2 fioles d'encre, 1 fil mesureur et 5 parchemins vierges", "choiceText": "", "ecorces": 0 }, + { "name": "Arme", "type": "arme", "quantity": 1, "details": "", "choiceText": "Arme au choix", "ecorces": 0 }, + { "name": "Objet géant", "type": "equipement", "quantity": 1, "details": "", "choiceText": "Objet géant au choix", "ecorces": 0 }, + { "name": "Bourse", "type": "equipement", "quantity": 1, "details": "", "choiceText": "", "ecorces": 18 } + ], + "spellGrants": [], + "revenues": { "beginner": 9, "intermediate": 30, "expert": 180 } + } + }, + { + "name": "Doux rêveur", + "type": "metier", + "img": "icons/svg/upgrade.svg", + "system": { + "description": "

Conteur, comédien ou ménestrel qui entretient le mythe d'Edenia.

", + "specialRules": "

Connaît 3 sortilèges de Chimérisme de Songes et 3 de Cauchemar.

", + "roleplayNotes": "

Les doux rêveurs font vivre le récit de l'Exil, de Syllistine et des exploits des compagnies dans l'imaginaire du Petit Peuple.

", + "skillBonuses": [ + { "key": "chimerisme", "alternativeKeys": [], "base": 3, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "seduction", "alternativeKeys": [], "base": 3, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "arts", "alternativeKeys": [], "base": 1, "domainsGranted": [], "domainsToChoose": 1, "domainsChoiceText": "domaine d'Arts au choix" }, + { "key": "magie", "alternativeKeys": [], "base": 1, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" } + ], + "startingEquipment": [ + { "name": "Instrument de musique", "type": "equipement", "quantity": 1, "details": "", "choiceText": "", "ecorces": 0 }, + { "name": "Arme", "type": "arme", "quantity": 1, "details": "", "choiceText": "Arme au choix", "ecorces": 0 }, + { "name": "Bourse", "type": "equipement", "quantity": 1, "details": "", "choiceText": "", "ecorces": 12 } + ], + "spellGrants": [ + { "tradition": "chimerisme", "skillKey": "chimerisme", "polarity": "songes", "amount": 3 }, + { "tradition": "chimerisme", "skillKey": "chimerisme", "polarity": "cauchemar", "amount": 3 } + ], + "revenues": { "beginner": 6, "intermediate": 30, "expert": 180 } + } + }, + { + "name": "Trouvetout", + "type": "metier", + "img": "icons/svg/upgrade.svg", + "system": { + "description": "

Récupérateur audacieux des biens des géants.

", + "specialRules": "", + "roleplayNotes": "

Les trouvetouts vivent du risque, de l'intrusion et du pillage utile des demeures géantes.

", + "skillBonuses": [ + { "key": "discretion", "alternativeKeys": [], "base": 3, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "athletisme", "alternativeKeys": [], "base": 3, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "force", "alternativeKeys": [], "base": 1, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "rapidite", "alternativeKeys": [], "base": 1, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" } + ], + "startingEquipment": [ + { "name": "Arme", "type": "arme", "quantity": 1, "details": "", "choiceText": "Arme au choix", "ecorces": 0 }, + { "name": "Objet de survie", "type": "equipement", "quantity": 1, "details": "", "choiceText": "Objet de survie au choix", "ecorces": 0 }, + { "name": "Bourse", "type": "equipement", "quantity": 1, "details": "", "choiceText": "", "ecorces": 60 } + ], + "spellGrants": [], + "revenues": { "beginner": 30, "intermediate": 90, "expert": 180 } + } + } +] diff --git a/packs-src/pouvoirs-compagnie.json b/packs-src/pouvoirs-compagnie.json new file mode 100644 index 0000000..272387e --- /dev/null +++ b/packs-src/pouvoirs-compagnie.json @@ -0,0 +1,10 @@ +[ + { "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 } } +] diff --git a/packs-src/races.json b/packs-src/races.json new file mode 100644 index 0000000..a82a5ae --- /dev/null +++ b/packs-src/races.json @@ -0,0 +1,128 @@ +[ + { + "name": "Belgfolk", + "type": "race", + "img": "icons/svg/mystery-man.svg", + "system": { + "description": "

Race trapue, robuste et ingénieuse du Petit Peuple.

", + "size": 2, + "lifeExpectancy": 60, + "keywords": ["intelligent", "ingénieux", "curieux", "calculateur", "égoïste", "têtu", "bourru", "nostalgique", "costaud"], + "mainTribes": ["Frinios", "Margouts"], + "language": "Belgfolk", + "languageDomains": ["Chimérique", "Jargon des likias", "Belgfolk"], + "specialRules": "", + "appearance": "

Les belgfolks sont massifs, poilus et courts sur patte, avec un long nez, une haute stature pour leur taille et une barbe ou une longue natte soigneusement portée.

", + "roleplayHints": ["Garant d'une époque glorieuse passée", "Inspirations slaves et vikings", "Endurant face aux éléments"], + "profiles": { "artiste": 0, "athlete": 0, "chasseur": 1, "faiseur": 5, "forceNature": 5, "guerrier": 0, "mystique": 0, "ombre": 1, "savant": 3 } + } + }, + { + "name": "Farfadet", + "type": "race", + "img": "icons/svg/mystery-man.svg", + "system": { + "description": "

Race mystique, longévive et liée aux sortilèges.

", + "size": 3, + "lifeExpectancy": 100, + "keywords": ["calme", "silencieux", "paisible", "résigné", "pessimiste", "généreux", "mystique", "solitaire", "enchanteur"], + "mainTribes": ["Siccomores", "Margouts"], + "language": "Farfadet", + "languageDomains": ["Chimérique", "Jargon des likias", "Farfadet"], + "specialRules": "", + "appearance": "

Les farfadets sont voûtés, rabougris, aux cheveux noirs, à la peau abîmée et aux longs ongles. Les femmes se distinguent souvent par leurs bijoux et boucles d'oreilles.

", + "roleplayHints": ["Fier d'une race autrefois influente", "Inspirations d'enchanteurs et de vieilles sorcières médiévales", "Présence inquiétante et ancienne"], + "profiles": { "artiste": 1, "athlete": 0, "chasseur": 0, "faiseur": 1, "forceNature": 3, "guerrier": 0, "mystique": 5, "ombre": 0, "savant": 4 } + } + }, + { + "name": "Gnome", + "type": "race", + "img": "icons/svg/mystery-man.svg", + "system": { + "description": "

Race très petite, acrobatique, bruyante et farceuse.

", + "size": 2, + "lifeExpectancy": 50, + "keywords": ["agile", "acrobate", "chétif", "comédien", "espiègle", "farceur", "bruyant", "bagarreur", "cavalier", "tireur"], + "mainTribes": ["Pataches", "Banshises", "Margouts"], + "language": "Gnome", + "languageDomains": ["Chimérique", "Jargon des likias", "Gnome"], + "specialRules": "", + "appearance": "

Les gnomes ont un visage d'enfant, aucune pilosité et une allure malingre. Leur petite taille contraste avec leur énergie débordante.

", + "roleplayHints": ["Déclenche facilement les bagarres", "Grandes variations culturelles", "Inspirations nomades et tsiganes"], + "profiles": { "artiste": 3, "athlete": 5, "chasseur": 3, "faiseur": 0, "forceNature": 0, "guerrier": 1, "mystique": 0, "ombre": 3, "savant": 0 } + } + }, + { + "name": "Kobold", + "type": "race", + "img": "icons/svg/mystery-man.svg", + "system": { + "description": "

Race gracile et macabre, proche des morts et de la nuit.

", + "size": 3, + "lifeExpectancy": 65, + "keywords": ["calme", "froid", "taciturne", "solitaire", "macabre", "gracieux", "agile", "orgueilleux", "élancé"], + "mainTribes": ["Sixts", "Vivitins", "Margouts"], + "language": "Kobold", + "languageDomains": ["Chimérique", "Jargon des likias", "Kobold"], + "specialRules": "

Les kobolds voient et entendent les esprits des morts qui les entourent.

", + "appearance": "

Les kobolds ont la peau pâle, les cheveux argentés et une beauté glaciale. Certains sont d'un bleu sombre presque noir et sont promis à un grand destin magique.

", + "roleplayHints": ["Chevaliers noirs et noblesse décadente", "Affinité naturelle avec les morts", "Souvent tenus pour suspects"], + "profiles": { "artiste": 0, "athlete": 5, "chasseur": 0, "faiseur": 0, "forceNature": 0, "guerrier": 3, "mystique": 3, "ombre": 1, "savant": 1 } + } + }, + { + "name": "Korrigan", + "type": "race", + "img": "icons/svg/mystery-man.svg", + "system": { + "description": "

Race violente, puissante et exubérante, peu sensible au Songe.

", + "size": 2, + "lifeExpectancy": 45, + "keywords": ["agressif", "violent", "bruyant", "impulsif", "épicurien", "farceur", "tolérant", "force prodigieuse", "guerrier"], + "mainTribes": ["Huvons", "Margouts"], + "language": "Korrigan", + "languageDomains": ["Chimérique", "Jargon des likias", "Korrigan"], + "specialRules": "", + "appearance": "

Très trapus, souvent sombres et extrêmement velus, les korrigans ressemblent à des cubes de muscle taillés pour la bagarre.

", + "roleplayHints": ["Guerrier craint", "Adore la compagnie et les conflits", "Inspirations barbares et celtes"], + "profiles": { "artiste": 0, "athlete": 3, "chasseur": 1, "faiseur": 0, "forceNature": 5, "guerrier": 5, "mystique": 0, "ombre": 1, "savant": 0 } + } + }, + { + "name": "Lutin", + "type": "race", + "img": "icons/svg/mystery-man.svg", + "system": { + "description": "

Race noble, charismatique et très polyvalente.

", + "size": 3, + "lifeExpectancy": 60, + "keywords": ["agile", "sensuel", "élancé", "orgueilleux", "autoritaire", "arrogant", "charismatique", "polyvalent"], + "mainTribes": ["Krograines", "Karius", "Margouts"], + "language": "Lutin", + "languageDomains": ["Chimérique", "Jargon des likias", "Lutin"], + "specialRules": "", + "appearance": "

Les lutins sont minces, élégants, d'une grande beauté et portent de longues chevelures aux reflets d'or ou de rouille. Leur regard froid impressionne les autres races.

", + "roleplayHints": ["Respecté des autres races", "Gardien des mythes d'Edenia", "Inspirations arthuriennes et féodales"], + "profiles": { "artiste": 4, "athlete": 0, "chasseur": 1, "faiseur": 0, "forceNature": 0, "guerrier": 5, "mystique": 3, "ombre": 1, "savant": 0 } + } + }, + { + "name": "Velu nuton", + "type": "race", + "img": "icons/svg/mystery-man.svg", + "system": { + "description": "

Les plus grands et les plus robustes du Petit Peuple.

", + "size": 4, + "lifeExpectancy": 50, + "keywords": ["force de la nature", "brute", "cavalier", "amoureux de la nature", "conteur", "sauvage", "primitif"], + "mainTribes": ["Ventrus", "Margouts"], + "language": "Velu nuton", + "languageDomains": ["Chimérique", "Jargon des likias", "Velu nuton"], + "specialRules": "", + "appearance": "

Les velus nutons mesurent souvent de 10 à 13 cm, voire davantage. Puissants mais peu agiles, ils sont prisés comme gardes du corps.

", + "roleplayHints": ["Individu craint et sous-estimé", "Ogre poétique et nomade", "Inspirations barbares primitives"], + "profiles": { "artiste": 1, "athlete": 1, "chasseur": 3, "faiseur": 0, "forceNature": 5, "guerrier": 3, "mystique": 0, "ombre": 0, "savant": 0 } + } + } +] diff --git a/packs-src/sortileges-sample.json b/packs-src/sortileges-sample.json new file mode 100644 index 0000000..8460df9 --- /dev/null +++ b/packs-src/sortileges-sample.json @@ -0,0 +1,10 @@ +[ + { "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/tribus.json b/packs-src/tribus.json new file mode 100644 index 0000000..5d74422 --- /dev/null +++ b/packs-src/tribus.json @@ -0,0 +1,309 @@ +[ + { + "name": "Krograines", + "type": "tribu", + "img": "icons/svg/ruins.svg", + "system": { + "description": "

Tribu lutine traditionaliste, gardienne de la mémoire d'Edenia.

", + "keywords": ["riche", "traditionaliste", "hautain", "méfiant", "conquérant", "expansionniste", "intolérant"], + "mainRace": "Lutin", + "spokenLanguage": "Vieux lutin", + "philosophy": "Gardiens de la mémoire d'Edenia", + "pride": "Pureté de la race lutine", + "mythNature": "faible", + "mythEdenia": "moyen", + "territory": "Cloître bénédictin et environs", + "specialRules": "", + "roleplayNotes": "

Les Krograines sont naturellement fermés à la discussion et convaincus d'appartenir à une tribu supérieure.

", + "restrictedJobs": [], + "allowedJobs": [], + "skillBonuses": [ + { "key": "commandement", "alternativeKeys": [], "base": 2, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "erudition", "alternativeKeys": [], "base": 2, "domainsGranted": ["Catholicisme", "Histoire"], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "langues", "alternativeKeys": [], "base": 1, "domainsGranted": ["Vieux lutin"], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "melee", "alternativeKeys": [], "base": 1, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "montures", "alternativeKeys": [], "base": 1, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "volonte", "alternativeKeys": [], "base": 2, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" } + ] + } + }, + { + "name": "Karius", + "type": "tribu", + "img": "icons/svg/ruins.svg", + "system": { + "description": "

Tribu repliée sur elle-même, méfiante et excellente chevaucheuse d'oiseaux.

", + "keywords": ["pauvre", "solitaire", "méfiant", "reclus", "prétentieux", "habile chevaucheur d'oiseaux"], + "mainRace": "Lutin", + "spokenLanguage": "Lutin", + "philosophy": "Crainte et suspicion envers l'étranger", + "pride": "Adresse des guerriers en vol et cité du Pic d'Azur", + "mythNature": "faible", + "mythEdenia": "inexistant", + "territory": "Clocher de la vieille église de la Trinité", + "specialRules": "

Chaque Karius est accompagné d'une fée rousse à laquelle il est émotionnellement lié.

", + "roleplayNotes": "

Un Karius jouable est nécessairement un banni, peu expansif et façonné par la peur du monde extérieur.

", + "restrictedJobs": [], + "allowedJobs": [], + "skillBonuses": [ + { "key": "athletisme", "alternativeKeys": [], "base": 2, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "discretion", "alternativeKeys": [], "base": 2, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "esquive", "alternativeKeys": [], "base": 2, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "langues", "alternativeKeys": [], "base": 1, "domainsGranted": ["Lutin"], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "montures", "alternativeKeys": [], "base": 2, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" } + ] + } + }, + { + "name": "Ventrus", + "type": "tribu", + "img": "icons/svg/ruins.svg", + "system": { + "description": "

Nomades sauvages, fidèles à Dame Nature et aux voyages.

", + "keywords": ["pauvre", "violent", "méfiant", "bruyant", "bagarreur", "sauvage", "primitif", "voyageur", "solitaire"], + "mainRace": "Velu nuton", + "spokenLanguage": "Velu nuton", + "philosophy": "Rendre hommage à Dame Nature", + "pride": "Doux rêveurs et puissants guerriers", + "mythNature": "fort", + "mythEdenia": "faible", + "territory": "Nomades", + "specialRules": "", + "roleplayNotes": "

Les Ventrus ont grandi sur les routes et voient la Terra comme un monde rude mais familier.

", + "restrictedJobs": [], + "allowedJobs": [], + "skillBonuses": [ + { "key": "corpsacorps", "alternativeKeys": [], "base": 1, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "endurance", "alternativeKeys": [], "base": 2, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "force", "alternativeKeys": [], "base": 2, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "langues", "alternativeKeys": [], "base": 1, "domainsGranted": ["Velu nuton"], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "montures", "alternativeKeys": [], "base": 1, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "survie", "alternativeKeys": [], "base": 2, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" } + ] + } + }, + { + "name": "Sixts", + "type": "tribu", + "img": "icons/svg/ruins.svg", + "system": { + "description": "

Noblesse kobolde morbide et pilleuse de tombes.

", + "keywords": ["mystérieux", "manipulateur", "déchu", "pilleur de tombes", "sombre", "morbide", "ennemi des Vivitins"], + "mainRace": "Kobold", + "spokenLanguage": "Kobold", + "philosophy": "Restaurer leur grandeur passée", + "pride": "Leur richesse", + "mythNature": "inexistant", + "mythEdenia": "moyen", + "territory": "Cimetières et charniers", + "specialRules": "", + "roleplayNotes": "

Les Sixts sont solitaires, peu loquaces et liés à la noblesse décadente de leur tribu.

", + "restrictedJobs": [], + "allowedJobs": [], + "skillBonuses": [ + { "key": "arts", "alternativeKeys": [], "base": 1, "domainsGranted": ["Conte"], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "chimerisme", "alternativeKeys": [], "base": 2, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "discretion", "alternativeKeys": [], "base": 2, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "erudition", "alternativeKeys": [], "base": 1, "domainsGranted": ["Légendes"], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "langues", "alternativeKeys": [], "base": 1, "domainsGranted": ["Kobold"], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "subterfuge", "alternativeKeys": [], "base": 2, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" } + ] + } + }, + { + "name": "Frinios", + "type": "tribu", + "img": "icons/svg/ruins.svg", + "system": { + "description": "

Belgfolks inventifs, fascinés par les géants et les secrets.

", + "keywords": ["fasciné par les géants", "inventif", "curieux", "bourru", "observateur", "rigoureux", "habile", "tenace", "têtu", "matérialiste"], + "mainRace": "Belgfolk", + "spokenLanguage": "Belgfolk", + "philosophy": "Recherche et découverte de secrets", + "pride": "La Frivolution et les inventions", + "mythNature": "faible", + "mythEdenia": "faible", + "territory": "Chantier naval", + "specialRules": "", + "roleplayNotes": "

Les Frinios sont instruits, avides de connaissances et peu nombreux à quitter leur colonie pour devenir Oubliés.

", + "restrictedJobs": [], + "allowedJobs": [], + "skillBonuses": [ + { "key": "artisanat", "alternativeKeys": [], "base": 2, "domainsGranted": [], "domainsToChoose": 2, "domainsChoiceText": "domaines d'Artisanat au choix" }, + { "key": "erudition", "alternativeKeys": [], "base": 1, "domainsGranted": ["Lettres"], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "intellect", "alternativeKeys": [], "base": 2, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "langues", "alternativeKeys": [], "base": 2, "domainsGranted": ["Belgfolk"], "domainsToChoose": 1, "domainsChoiceText": "autre langue au choix" }, + { "key": "strategie", "alternativeKeys": [], "base": 1, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "survie", "alternativeKeys": [], "base": 1, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" } + ] + } + }, + { + "name": "Pataches", + "type": "tribu", + "img": "icons/svg/ruins.svg", + "system": { + "description": "

Gnomes nomades, chaleureux et dresseurs réputés.

", + "keywords": ["haine des géants", "nomade", "insouciant", "familial", "ouvert", "enjoué", "bonimenteur", "sensible", "dresseur réputé"], + "mainRace": "Gnome", + "spokenLanguage": "Gnome", + "philosophy": "Protéger sa famille et la quête d'Edenia", + "pride": "Leurs caravanes", + "mythNature": "fort", + "mythEdenia": "fort", + "territory": "Nomades", + "specialRules": "", + "roleplayNotes": "

Les Pataches font du rire et de la bonne humeur un rempart contre la dureté de l'Exil.

", + "restrictedJobs": [], + "allowedJobs": [], + "skillBonuses": [ + { "key": "artisanat", "alternativeKeys": [], "base": 1, "domainsGranted": ["Apothicaire"], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "erudition", "alternativeKeys": [], "base": 2, "domainsGranted": ["Faune", "Flore"], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "langues", "alternativeKeys": [], "base": 1, "domainsGranted": ["Gnome"], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "montures", "alternativeKeys": [], "base": 1, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "survie", "alternativeKeys": [], "base": 2, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "tir", "alternativeKeys": [], "base": 2, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" } + ] + } + }, + { + "name": "Banshises", + "type": "tribu", + "img": "icons/svg/ruins.svg", + "system": { + "description": "

Gnomes teigneux et indépendants, très liés à leurs clans.

", + "keywords": ["individualiste", "indépendant", "insoumis", "bagarreur", "teigneux", "voleur", "guerrier dans l'âme"], + "mainRace": "Gnome", + "spokenLanguage": "Gnome", + "philosophy": "Préserver clan et famille", + "pride": "Leur indépendance", + "mythNature": "moyen", + "mythEdenia": "inexistant", + "territory": "Le Labrus", + "specialRules": "", + "roleplayNotes": "

Les Banshises sont élevés dans le conflit et le chapardage ; devenir Oublié est pour eux une rupture profonde.

", + "restrictedJobs": [], + "allowedJobs": [], + "skillBonuses": [ + { "key": "discretion", "alternativeKeys": [], "base": 2, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "esquive", "alternativeKeys": [], "base": 1, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "langues", "alternativeKeys": [], "base": 1, "domainsGranted": ["Gnome"], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "melee", "alternativeKeys": [], "base": 1, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "subterfuge", "alternativeKeys": [], "base": 2, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "survie", "alternativeKeys": [], "base": 2, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" } + ] + } + }, + { + "name": "Vivitins", + "type": "tribu", + "img": "icons/svg/ruins.svg", + "system": { + "description": "

Kobolds navigateurs, fiers guerriers et ennemis des Sixts.

", + "keywords": ["navigateur", "ingénieux", "guerrier", "courageux", "sinistre", "aigri", "hait les Sixts"], + "mainRace": "Kobold", + "spokenLanguage": "Kobold", + "philosophy": "Accroître la puissance des Vivitins", + "pride": "Le nouveau destin de leur tribu", + "mythNature": "inexistant", + "mythEdenia": "inexistant", + "territory": "Marches brumeuses", + "specialRules": "", + "roleplayNotes": "

Les Vivitins veulent prouver leur valeur, portent une forte rancœur envers les Sixts et se vivent comme un peuple renaissant.

", + "restrictedJobs": [], + "allowedJobs": [], + "skillBonuses": [ + { "key": "artisanat", "alternativeKeys": [], "base": 2, "domainsGranted": ["Construction", "Navigation"], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "athletisme", "alternativeKeys": [], "base": 1, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "langues", "alternativeKeys": [], "base": 1, "domainsGranted": ["Kobold"], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "sens", "alternativeKeys": [], "base": 2, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "survie", "alternativeKeys": [], "base": 1, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "volonte", "alternativeKeys": [], "base": 2, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" } + ] + } + }, + { + "name": "Margouts", + "type": "tribu", + "img": "icons/svg/ruins.svg", + "system": { + "description": "

Tribu marchande, multiculturelle et cosmopolite.

", + "keywords": ["navigateur multiculturel", "baratineur", "marchand", "épicurien", "exubérant", "raffiné", "cupide", "riche", "tolérant"], + "mainRace": "Toutes", + "spokenLanguage": "Chimérique", + "philosophy": "Tout se revend avec bénéfice", + "pride": "Leur richesse", + "mythNature": "inexistant", + "mythEdenia": "inexistant", + "territory": "Place du marché", + "specialRules": "", + "roleplayNotes": "

Les Margouts vivent pour l'échange, l'apparat et l'avenir ; ils aiment la Terra bien plus que les vieux mythes.

", + "restrictedJobs": [], + "allowedJobs": [], + "skillBonuses": [ + { "key": "artisanat", "alternativeKeys": [], "base": 1, "domainsGranted": [], "domainsToChoose": 1, "domainsChoiceText": "domaine d'Artisanat au choix" }, + { "key": "empathie", "alternativeKeys": [], "base": 1, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "intellect", "alternativeKeys": [], "base": 2, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "langues", "alternativeKeys": [], "base": 1, "domainsGranted": [], "domainsToChoose": 1, "domainsChoiceText": "langue du Petit Peuple autre que celle de la race" }, + { "key": "seduction", "alternativeKeys": [], "base": 2, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "subterfuge", "alternativeKeys": [], "base": 2, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" } + ] + } + }, + { + "name": "Siccomores", + "type": "tribu", + "img": "icons/svg/ruins.svg", + "system": { + "description": "

Farfadets particulièrement doués en magie et fabricants d'objets enchantés.

", + "keywords": ["ancien conseiller", "doué en magie", "en voie de disparition", "sage", "introverti", "pessimiste", "hait le Cauchemar", "fabricant d'objets enchantés"], + "mainRace": "Farfadet", + "spokenLanguage": "Farfadet", + "philosophy": "Responsables de l'Exil du Petit Peuple", + "pride": "Leur influence", + "mythNature": "faible", + "mythEdenia": "fort", + "territory": "Ghetto juif", + "specialRules": "

Le seul métier possible à la création est mage des Songes.

", + "roleplayNotes": "

Les Siccomores sont austères, peu loquaces et vivent dans la conscience lourde de l'Exil.

", + "restrictedJobs": ["Rêvirine", "Chevalier errant", "Mercenaire", "Explorateur-marchand", "Cartographe", "Doux rêveur", "Trouvetout"], + "allowedJobs": ["Mage des Songes"], + "skillBonuses": [ + { "key": "erudition", "alternativeKeys": [], "base": 2, "domainsGranted": ["Judaïsme", "Lettres"], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "intellect", "alternativeKeys": [], "base": 1, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "langues", "alternativeKeys": [], "base": 1, "domainsGranted": ["Farfadet"], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "magie", "alternativeKeys": [], "base": 2, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "seduction", "alternativeKeys": [], "base": 2, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "volonte", "alternativeKeys": [], "base": 1, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" } + ] + } + }, + { + "name": "Huvons", + "type": "tribu", + "img": "icons/svg/ruins.svg", + "system": { + "description": "

Korrigans violents, maîtres artisans et grands buveurs.

", + "keywords": ["mauvaise réputation", "guerrier", "violent", "maître artisan", "grand buveur", "grande gueule", "dur", "brutal", "vulgaire", "opportuniste"], + "mainRace": "Korrigan", + "spokenLanguage": "Korrigan", + "philosophy": "Être au cœur du combat comme de la vie", + "pride": "Leur vie dans les conflits du Petit Peuple", + "mythNature": "faible", + "mythEdenia": "inexistant", + "territory": "La forge", + "specialRules": "", + "roleplayNotes": "

Les Huvons valorisent la brutalité, les arènes et les compagnies de mercenaires plus que les Oubliés.

", + "restrictedJobs": [], + "allowedJobs": [], + "skillBonuses": [ + { "key": "commandement", "alternativeKeys": [], "base": 1, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "corpsacorps", "alternativeKeys": [], "base": 2, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "endurance", "alternativeKeys": [], "base": 2, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "langues", "alternativeKeys": [], "base": 1, "domainsGranted": ["Korrigan"], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "melee", "alternativeKeys": [], "base": 2, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" }, + { "key": "strategie", "alternativeKeys": [], "base": 1, "domainsGranted": [], "domainsToChoose": 0, "domainsChoiceText": "" } + ] + } + } +] diff --git a/system.json b/system.json new file mode 100644 index 0000000..6887251 --- /dev/null +++ b/system.json @@ -0,0 +1,124 @@ +{ + "id": "fvtt-les-oublies", + "title": "Les Oubliés", + "description": "Système FoundryVTT AppV2 pour le jeu de role Les Oubliés.", + "version": "0.1.0", + "authors": [ + { + "name": "Copilot", + "flags": {} + } + ], + "compatibility": { + "minimum": "13", + "verified": "14" + }, + "esmodules": [ + "modules/les-oublies-main.js" + ], + "styles": [ + "css/les-oublies.css" + ], + "languages": [ + { + "lang": "fr", + "name": "Français", + "path": "lang/fr.json", + "flags": {} + } + ], + "documentTypes": { + "Actor": { + "personnage": { + "htmlFields": [ + "biodata.description", + "biodata.notes", + "biodata.gmnotes", + "visions" + ] + }, + "compagnie": { + "htmlFields": [ + "description", + "notes", + "power.description" + ] + }, + "creature": { + "htmlFields": [ + "biodata.description", + "biodata.habitat", + "biodata.notes", + "biodata.gmnotes", + "statblock.damage", + "statblock.special", + "statblock.spellSonges", + "statblock.spellCauchemar" + ] + } + }, + "Item": { + "race": { + "htmlFields": [ + "description", + "specialRules", + "notes" + ] + }, + "tribu": { + "htmlFields": [ + "description", + "specialRules", + "notes" + ] + }, + "metier": { + "htmlFields": [ + "description", + "specialRules", + "notes" + ] + }, + "competence": { + "htmlFields": [ + "description", + "notes" + ] + }, + "sortilege": { + "htmlFields": [ + "description", + "effectsText", + "notes" + ] + }, + "arme": { + "htmlFields": [ + "description", + "notes" + ] + }, + "armure": { + "htmlFields": [ + "description", + "notes" + ] + }, + "equipement": { + "htmlFields": [ + "description", + "notes" + ] + }, + "pouvoircompagnie": { + "htmlFields": [ + "description", + "ruleText", + "notes" + ] + } + } + }, + "primaryTokenAttribute": "system.hp.value", + "flags": {} +} diff --git a/templates/actor-compagnie-sheet.hbs b/templates/actor-compagnie-sheet.hbs new file mode 100644 index 0000000..7d34a7e --- /dev/null +++ b/templates/actor-compagnie-sheet.hbs @@ -0,0 +1,90 @@ +
+
+ +
+

Blason de la compagnie

+

+

Capitaine, pouvoir partagé et liens forgés dans l'Exil

+ +
+
+ +
+

{{localize "LESOUBLIES.labels.pouvoir"}}

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ {{editor system.power.description target="system.power.description" button=true editable=isEditMode}} +
+

Items de pouvoir

+ +
+
+ {{#each powers as |item|}} +
+
{{item.name}}
{{item.system.activationCondition}}
+
+
+ {{/each}} +
+ {{#if primaryPower}} +
{{primaryPower.name}} — {{primaryPower.system.activationCondition}}
+ {{/if}} +
+ +
+
+

{{localize "LESOUBLIES.ui.membres"}}

+
+ + +
+
+ + +
+

{{localize "LESOUBLIES.labels.membresIds"}}

+ {{#if captain}}

Capitaine : {{captain.name}}

{{/if}} + {{#if shadow}}

Ombre : {{shadow.name}}

{{/if}} +
    + {{#each members as |member|}} +
  • {{member.name}}
  • + {{/each}} +
+
+ +
+

{{localize "LESOUBLIES.labels.liensNarratifs"}}

+

{{localize "LESOUBLIES.ui.readOnlyCollection"}}

+
    + {{#each links as |link|}} +
  • {{link.label}} — {{link.sourceLabel}} -> {{link.targetLabel}} {{link.details}}
  • + {{/each}} +
+
+
+ +
+

{{localize "LESOUBLIES.labels.description"}}

+ {{editor system.description target="system.description" button=true editable=isEditMode}} +

{{localize "LESOUBLIES.labels.notes"}}

+ {{editor system.notes target="system.notes" button=true editable=isEditMode}} +
+
diff --git a/templates/actor-creature-sheet.hbs b/templates/actor-creature-sheet.hbs new file mode 100644 index 0000000..a88047b --- /dev/null +++ b/templates/actor-creature-sheet.hbs @@ -0,0 +1,144 @@ +
+
+ +
+

Bestiaire de la Terra

+

+

Créatures du Petit Peuple, du Cauchemar et des frontières

+
+ + + + + +
+
+
+ +
+
+

{{localize "LESOUBLIES.ui.derivedOverview"}}

+
+
{{derived.sizeLabel}}
+
/ {{derived.hpMax}}
+
{{derived.hpDisplay}}
+
+
+
/ {{system.songes.max}}
+
+
/ {{system.cauchemar.max}}
+
+ +
+

{{localize "LESOUBLIES.labels.profilsOptionnels"}}

+
+ {{#each system.profils as |value key|}} +
+ + +
+ {{/each}} +
+
+
+ +
+
+

{{localize "LESOUBLIES.ui.competences"}}

+ +
+ {{#each skillGroups as |group|}} + {{#if (count group.items)}} +
+

{{group.label}}

+
+ {{#each group.items as |entry|}} +
+
{{entry.item.name}}
{{localize "LESOUBLIES.labels.valeurFinale"}} {{entry.finalValue}}
+
+
+ {{/each}} +
+
+ {{/if}} + {{/each}} +
+ +
+
+

{{localize "LESOUBLIES.labels.degats"}}

+ {{editor system.statblock.damage target="system.statblock.damage" button=true editable=isEditMode}} +

{{localize "LESOUBLIES.labels.reglesSpeciales"}}

+ {{editor system.statblock.special target="system.statblock.special" button=true editable=isEditMode}} +
+ +
+

{{localize "LESOUBLIES.labels.sortilegesSonges"}}

+ {{editor system.statblock.spellSonges target="system.statblock.spellSonges" button=true editable=isEditMode}} +

{{localize "LESOUBLIES.labels.sortilegesCauchemar"}}

+ {{editor system.statblock.spellCauchemar target="system.statblock.spellCauchemar" button=true editable=isEditMode}} +
+
+ +
+
+

{{localize "LESOUBLIES.ui.combat"}}

+
+ + + + + +
+
+
+ +
+
+

{{localize "LESOUBLIES.ui.equipement"}}

+
+ + + + +
+
+
+ {{#each weapons as |item|}} +
+
{{item.name}}
{{item.system.damage}}
+
+
+ {{/each}} + {{#each armors as |item|}} +
+
{{item.name}}
Prot {{item.system.protection}}
+
+
+ {{/each}} + {{#each equipment as |item|}} +
+
{{item.name}}
{{localize "TYPES.Item.equipement"}} - {{item.system.category}}
+
+
+ {{/each}} + {{#each spells as |item|}} +
+
{{item.name}}
{{item.system.tradition}} / {{item.system.polarity}}
+
+
+ {{/each}} +
+
+ +
+

{{localize "LESOUBLIES.labels.description"}}

+ {{editor system.biodata.description target="system.biodata.description" button=true editable=isEditMode}} +

Habitat

+ {{editor system.biodata.habitat target="system.biodata.habitat" button=true editable=isEditMode}} +

{{localize "LESOUBLIES.labels.notes"}}

+ {{editor system.biodata.notes target="system.biodata.notes" button=true editable=isEditMode}} +

{{localize "LESOUBLIES.labels.gmnotes"}}

+ {{editor system.biodata.gmnotes target="system.biodata.gmnotes" button=true editable=isEditMode owner=isGM}} +
+
diff --git a/templates/actor-personnage-sheet.hbs b/templates/actor-personnage-sheet.hbs new file mode 100644 index 0000000..e24e02e --- /dev/null +++ b/templates/actor-personnage-sheet.hbs @@ -0,0 +1,228 @@ +
+
+ +
+

Chronique d'un Oublié

+

+

Petit Peuple, Songes, Cauchemar et destin de compagnie

+
+ + + + + +
+
+
+ +
+
+

{{localize "LESOUBLIES.ui.derivedOverview"}}

+
+ + + {{derived.sizeLabel}} +
+
+ + + / {{derived.hpMax}} +
+
+ + + {{system.songes.points}} / {{system.songes.max}} pts +
+
+ + +
+
+ + +
+
+ + + {{system.cauchemar.points}} / {{system.cauchemar.max}} pts +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+

{{localize "LESOUBLIES.labels.identite"}}

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+

{{localize "LESOUBLIES.ui.creation"}}

+
    +
  • {{localize "LESOUBLIES.labels.race"}} : {{#if creation.race}}{{creation.race.name}}{{else}}—{{/if}}
  • +
  • {{localize "LESOUBLIES.labels.tribu"}} : {{#if creation.tribu}}{{creation.tribu.name}}{{else}}—{{/if}}
  • +
  • {{localize "LESOUBLIES.labels.metier"}} : {{#if creation.metier}}{{creation.metier.name}}{{else}}—{{/if}}
  • +
  • {{localize "LESOUBLIES.labels.compagnie"}} : {{#if derived.compagnie}}{{derived.compagnie.name}}{{else}}—{{/if}}
  • +
+
+ + + +
+
+
+ +
+

{{localize "LESOUBLIES.ui.profils"}}

+
+ {{#each system.profils as |value key|}} +
+ + +
+ {{/each}} +
+
+ +
+
+

{{localize "LESOUBLIES.ui.competences"}}

+ +
+ {{#each skillGroups as |group|}} +
+

{{group.label}}

+
+ {{#each group.items as |entry|}} +
+
+ {{entry.item.name}} +
Base {{entry.item.system.base}} - {{localize "LESOUBLIES.labels.valeurFinale"}} {{entry.finalValue}}
+ {{#if (count entry.item.system.domains)}}
{{localize "LESOUBLIES.labels.domaines}} : {{join entry.item.system.domains}}
{{/if}} +
+
+ + + +
+
+ {{/each}} +
+
+ {{/each}} +
+ +
+
+

{{localize "LESOUBLIES.ui.combat"}}

+
+ + + + + +
+
+
+ +
+
+

{{localize "LESOUBLIES.ui.magie"}}

+ +
+
+ {{#each spells as |item|}} +
+
+ {{item.name}} +
{{item.system.tradition}} / {{item.system.polarity}} / coût {{item.system.cost}}
+
+
+ + + +
+
+ {{/each}} +
+
+ +
+
+

{{localize "LESOUBLIES.ui.equipement"}}

+
+ + + +
+
+
+ {{#each weapons as |item|}} +
+
{{item.name}}
{{localize "TYPES.Item.arme"}} - {{item.system.damage}}
+
+
+ {{/each}} + {{#each armors as |item|}} +
+
{{item.name}}
{{localize "TYPES.Item.armure"}} - Prot {{item.system.protection}}
+
+
+ {{/each}} + {{#each equipment as |item|}} +
+
{{item.name}}
{{localize "TYPES.Item.equipement"}} - {{item.system.category}}
+
+
+ {{/each}} +
+
+ +
+

{{localize "LESOUBLIES.ui.notes"}}

+ + {{editor system.biodata.description target="system.biodata.description" button=true editable=isEditMode}} + + {{editor system.biodata.notes target="system.biodata.notes" button=true editable=isEditMode}} + + {{editor system.biodata.gmnotes target="system.biodata.gmnotes" button=true editable=isEditMode owner=isGM}} + + {{editor system.visions target="system.visions" button=true editable=isEditMode}} + {{#if activeCompanyPower}} + +
{{activeCompanyPower.name}} — {{activeCompanyPower.system.activationCondition}}
+ {{/if}} +
+
diff --git a/templates/chat-action-roll.hbs b/templates/chat-action-roll.hbs new file mode 100644 index 0000000..b2a0cb6 --- /dev/null +++ b/templates/chat-action-roll.hbs @@ -0,0 +1,116 @@ +
+
+
+ {{actor.name}} +
+

{{#if result}}Action{{else}}Résolution{{/if}}

+

{{#if action}}{{action.title}}{{else}}{{result.label}}{{/if}}

+

{{actor.name}}{{#if action.subtitle}} · {{action.subtitle}}{{/if}}

+
+ {{#if result}} +
{{result.successLabel}}
+ {{/if}} +
+
+ +
+ {{#if result}} +
+
{{localize "LESOUBLIES.rolls.score"}}{{result.score}}
+
{{localize "LESOUBLIES.rolls.difficulty"}}{{numberFormat result.difficulty sign=true}}
+
{{localize "LESOUBLIES.rolls.natural"}}{{result.natural}}
+
{{localize "LESOUBLIES.rolls.final"}}{{result.final}}
+
{{localize "LESOUBLIES.rolls.threshold"}}{{result.threshold}}+
+
{{localize "LESOUBLIES.rolls.margin"}}{{numberFormat result.margin sign=true}}
+
+ +

{{localize "LESOUBLIES.rolls.resolution"}} : {{result.natural}} + {{result.score}} {{numberFormat result.difficulty sign=true}}{{#if result.finalModifier}} {{numberFormat result.finalModifier sign=true}}{{/if}} = {{result.final}}

+ +
+ {{#each result.dice as |die|}} +
+ {{die.typeLabel}} + {{die.breakdown}} + {{#if die.exploded}}{{localize "LESOUBLIES.rolls.exploded"}}{{/if}} + {{#if die.sourceLabel}}{{die.sourceLabel}}{{/if}} +
+ {{/each}} +
+ {{/if}} + + {{#if action.hint}} +

Rappel : {{action.hint}}

+ {{/if}} + + {{#if action.modifiers.labels.length}} +
+
+ Modificateurs + {{join action.modifiers.labels}} +
+
+ {{/if}} + + {{#if action.outcome}} + {{#if result}} + {{#if result.success}} +
+
+ {{action.outcome.label}} + {{action.outcome.description}} +
+
+ {{/if}} + {{else}} +
+
+ {{action.outcome.label}} + {{action.outcome.description}} +
+
+ {{/if}} + {{/if}} + + {{#if action.damage}} +
+
+ Dégâts de base + {{action.damage.baseLabel}} +
+
+ Protection + {{action.damage.effectiveProtection}} + {{action.damage.targetLabel}} +
+
+ Dégâts finaux + {{action.damage.finalDamage}} + {{#if action.damage.nonLethal}}Non létal{{/if}} +
+
+ {{/if}} + + {{#if action.harvest}} +
+
+ Récolte + {{action.harvest.threadCount}} fil{{#unless (eq action.harvest.threadCount 1)}}s{{/unless}} de {{action.harvest.threadType}} + {{action.harvest.sleeperLabel}} +
+
+ Dégâts subis + {{action.harvest.damageTaken}} +
+
+ Après le Néphertine + {{action.harvest.durationHours}} h + {{action.harvest.sideEffectText}} +
+
+ {{/if}} + + {{#if action.notes}} +

Notes : {{action.notes}}

+ {{/if}} +
+
diff --git a/templates/chat-confrontation-roll.hbs b/templates/chat-confrontation-roll.hbs new file mode 100644 index 0000000..5b88fbe --- /dev/null +++ b/templates/chat-confrontation-roll.hbs @@ -0,0 +1,152 @@ +
+
+
+ {{actor.name}} +
+

{{localize "LESOUBLIES.rolls.confrontation"}}

+

{{attacker.label}} · {{defender.label}}

+

{{localize "LESOUBLIES.rolls.confrontationType"}} · {{confrontationType}}

+
+
{{outcomeLabel}}
+
+
+ +
+ {{#if action}} +
+
+

{{action.title}}

+ {{#if action.subtitle}}{{action.subtitle}}{{/if}} +
+ {{#if action.hint}}

Rappel : {{action.hint}}

{{/if}} + {{#if action.modifiers.labels.length}} +
+
+ Modificateurs + {{join action.modifiers.labels}} +
+
+ {{/if}} + {{#if action.outcome}} + {{#if action.outcome.success}} +
+
+ {{action.outcome.label}} + {{action.outcome.description}} +
+
+ {{/if}} + {{/if}} + {{#if action.damage}} +
+
+ Dégâts + {{action.damage.finalDamage}} + {{#if action.damage.nonLethal}}Non létal{{/if}} +
+
+ Protection + {{action.damage.effectiveProtection}} + {{action.damage.targetLabel}} +
+
+ {{/if}} + {{#if action.notes}}

Notes : {{action.notes}}

{{/if}} +
+ {{/if}} + +
+
+

{{attacker.label}}

+ {{attacker.rollModeLabel}} +
+
+
{{localize "LESOUBLIES.rolls.score"}}{{attacker.score}}
+
{{localize "LESOUBLIES.rolls.difficulty"}}{{numberFormat attacker.difficulty sign=true}}
+
{{localize "LESOUBLIES.rolls.natural"}}{{attacker.natural}}
+
{{localize "LESOUBLIES.rolls.final"}}{{attacker.final}}
+
+

{{localize "LESOUBLIES.rolls.resolution"}} : {{attacker.natural}} + {{attacker.score}} {{numberFormat attacker.difficulty sign=true}}{{#if attacker.finalModifier}} {{numberFormat attacker.finalModifier sign=true}}{{/if}} = {{attacker.final}}

+
+ {{#each attacker.dice as |die|}} +
+ {{die.typeLabel}} + {{die.breakdown}} + {{#if die.exploded}}{{localize "LESOUBLIES.rolls.exploded"}}{{/if}} + {{#if die.sourceLabel}}{{die.sourceLabel}}{{/if}} +
+ {{/each}} +
+
+
+ {{localize "LESOUBLIES.rolls.result"}} + {{attacker.successLabel}} + {{attacker.selectedSummary}} +
+
+ {{localize "LESOUBLIES.rolls.debt"}} + {{attacker.debt.label}} +
+ {{#if attacker.spentResource}} +
+ {{localize "LESOUBLIES.rolls.extraDie"}} + {{attacker.spentResource.label}} +
+ {{/if}} + {{#if attacker.automaticFailure}} +
+ {{localize "LESOUBLIES.rolls.result"}} + {{localize "LESOUBLIES.rolls.naturalOne"}} +
+ {{/if}} +
+
+ +
+
+

{{defender.label}}

+ {{defender.rollModeLabel}} +
+
+
{{localize "LESOUBLIES.rolls.score"}}{{defender.score}}
+
{{localize "LESOUBLIES.rolls.difficulty"}}{{numberFormat defender.difficulty sign=true}}
+
{{localize "LESOUBLIES.rolls.natural"}}{{defender.natural}}
+
{{localize "LESOUBLIES.rolls.final"}}{{defender.final}}
+
+

{{localize "LESOUBLIES.rolls.resolution"}} : {{defender.natural}} + {{defender.score}} {{numberFormat defender.difficulty sign=true}}{{#if defender.finalModifier}} {{numberFormat defender.finalModifier sign=true}}{{/if}} = {{defender.final}}

+
+ {{#each defender.dice as |die|}} +
+ {{die.typeLabel}} + {{die.breakdown}} + {{#if die.exploded}}{{localize "LESOUBLIES.rolls.exploded"}}{{/if}} + {{#if die.sourceLabel}}{{die.sourceLabel}}{{/if}} +
+ {{/each}} +
+
+
+ {{localize "LESOUBLIES.rolls.result"}} + {{defender.successLabel}} + {{defender.selectedSummary}} +
+
+ {{localize "LESOUBLIES.rolls.debt"}} + {{defender.debt.label}} +
+ {{#if defender.spentResource}} +
+ {{localize "LESOUBLIES.rolls.extraDie"}} + {{defender.spentResource.label}} +
+ {{/if}} + {{#if defender.automaticFailure}} +
+ {{localize "LESOUBLIES.rolls.result"}} + {{localize "LESOUBLIES.rolls.naturalOne"}} +
+ {{/if}} +
+
+
+
diff --git a/templates/chat-dual-roll.hbs b/templates/chat-dual-roll.hbs new file mode 100644 index 0000000..5d5f226 --- /dev/null +++ b/templates/chat-dual-roll.hbs @@ -0,0 +1,7 @@ +
+

{{label}}

+

{{actor.name}} lance les dés de Songes et de Cauchemar.

+

Songes : {{songesDie}} | Cauchemar : {{cauchemarDie}}

+

Cible : {{target}} | Résultat retenu : {{selected}}

+

{{#if success}}Succès{{else}}Échec{{/if}}

+
diff --git a/templates/chat-initiative-roll.hbs b/templates/chat-initiative-roll.hbs new file mode 100644 index 0000000..cf645a0 --- /dev/null +++ b/templates/chat-initiative-roll.hbs @@ -0,0 +1,54 @@ +
+
+
+ {{actor.name}} +
+

{{localize "LESOUBLIES.rolls.test"}}

+

{{localize "LESOUBLIES.rolls.initiative"}}

+

{{actor.name}} · {{result.rollModeLabel}}

+
+
{{result.initiativeScore}}
+
+
+ +
+
+
{{localize "LESOUBLIES.rolls.score"}}{{result.score}}
+
{{localize "LESOUBLIES.rolls.natural"}}{{result.natural}}
+
{{localize "LESOUBLIES.rolls.final"}}{{result.final}}
+
{{localize "LESOUBLIES.rolls.initiative"}}{{result.initiativeScore}}
+
{{localize "LESOUBLIES.rolls.threshold"}}{{result.threshold}}+
+
{{localize "LESOUBLIES.rolls.margin"}}{{numberFormat result.margin sign=true}}
+
+ +

{{localize "LESOUBLIES.rolls.resolution"}} : {{result.natural}} + {{result.score}} = {{result.final}}, puis initiative = {{result.initiativeScore}}

+ +
+ {{#each result.dice as |die|}} +
+ {{die.typeLabel}} + {{die.breakdown}} + {{#if die.exploded}}{{localize "LESOUBLIES.rolls.exploded"}}{{/if}} +
+ {{/each}} +
+ +
+
+ {{localize "LESOUBLIES.rolls.selectedDie"}} + {{result.selectedSummary}} + {{result.choiceLabel}} +
+
+ {{localize "LESOUBLIES.rolls.debt"}} + {{result.debt.label}} +
+ {{#if result.spentResource}} +
+ {{localize "LESOUBLIES.rolls.extraDie"}} + {{result.spentResource.label}} +
+ {{/if}} +
+
+
diff --git a/templates/chat-spell-activation.hbs b/templates/chat-spell-activation.hbs new file mode 100644 index 0000000..d34de0e --- /dev/null +++ b/templates/chat-spell-activation.hbs @@ -0,0 +1,48 @@ +
+
+
+ {{actor.name}} +
+

Sortilège

+

{{spell.name}}

+

{{actor.name}} · {{spell.system.tradition}} / {{spell.system.polarity}}

+
+
Activation
+
+
+ +
+
+
Compétence{{skillLabel spell.system.skillKey}}
+
{{localize "LESOUBLIES.labels.preparation"}}{{spell.system.preparation}}
+
{{localize "LESOUBLIES.labels.duree"}}{{spell.system.duration}}
+
{{localize "LESOUBLIES.labels.portee"}}{{spell.system.range}}
+
{{localize "LESOUBLIES.labels.aire"}}{{spell.system.area}}
+
{{localize "LESOUBLIES.labels.cumul"}}{{spell.system.stacking}}
+
+ +
+
+ Ressource + {{activation.costLabel}} +
+
+ Métier + {{#if activation.metierMatch}}Couvert{{else}}Hors métier{{/if}} + {{#if activation.surcharge}}Surcoût appliqué{{/if}} +
+
+ Cible / scène + {{activation.targetLabel}} +
+
+ + {{#if activation.notes}} +

Notes : {{activation.notes}}

+ {{/if}} + + {{#if spell.system.effectsText}} +

Effets : {{{spell.system.effectsText}}}

+ {{/if}} +
+
diff --git a/templates/chat-test-roll.hbs b/templates/chat-test-roll.hbs new file mode 100644 index 0000000..0855830 --- /dev/null +++ b/templates/chat-test-roll.hbs @@ -0,0 +1,61 @@ +
+
+
+ {{actor.name}} +
+

{{localize "LESOUBLIES.rolls.test"}}

+

{{result.label}}

+

{{actor.name}} · {{result.rollModeLabel}}

+
+
{{result.successLabel}}
+
+
+ +
+
+
{{localize "LESOUBLIES.rolls.score"}}{{result.score}}
+
{{localize "LESOUBLIES.rolls.difficulty"}}{{numberFormat result.difficulty sign=true}}
+
{{localize "LESOUBLIES.rolls.natural"}}{{result.natural}}
+
{{localize "LESOUBLIES.rolls.final"}}{{result.final}}
+
{{localize "LESOUBLIES.rolls.threshold"}}{{result.threshold}}+
+
{{localize "LESOUBLIES.rolls.margin"}}{{numberFormat result.margin sign=true}}
+
+ +

{{localize "LESOUBLIES.rolls.resolution"}} : {{result.natural}} + {{result.score}} {{numberFormat result.difficulty sign=true}} = {{result.final}}

+ +
+ {{#each result.dice as |die|}} +
+ {{die.typeLabel}} + {{die.breakdown}} + {{#if die.exploded}}{{localize "LESOUBLIES.rolls.exploded"}}{{/if}} + {{#if die.sourceLabel}}{{die.sourceLabel}}{{/if}} +
+ {{/each}} +
+ +
+
+ {{localize "LESOUBLIES.rolls.selectedDie"}} + {{result.selectedSummary}} + {{result.choiceLabel}} +
+
+ {{localize "LESOUBLIES.rolls.debt"}} + {{result.debt.label}} +
+ {{#if result.spentResource}} +
+ {{localize "LESOUBLIES.rolls.extraDie"}} + {{result.spentResource.label}} +
+ {{/if}} + {{#if result.automaticFailure}} +
+ {{localize "LESOUBLIES.rolls.result"}} + {{localize "LESOUBLIES.rolls.naturalOne"}} +
+ {{/if}} +
+
+
diff --git a/templates/dialog-damage-resolution.hbs b/templates/dialog-damage-resolution.hbs new file mode 100644 index 0000000..78c23ff --- /dev/null +++ b/templates/dialog-damage-resolution.hbs @@ -0,0 +1,49 @@ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+

Les dégâts standards ne se lancent pas aux dés : ils se résolvent à partir de l'arme, de la protection et des modificateurs choisis.

+
+
diff --git a/templates/dialog-roll-action.hbs b/templates/dialog-roll-action.hbs new file mode 100644 index 0000000..447ee9d --- /dev/null +++ b/templates/dialog-roll-action.hbs @@ -0,0 +1,76 @@ +
+
+

{{title}}

+
+ + +
+
+ + +
+ {{#if showMovementOptions}} +
+ + +
+
+ + +
+ {{/if}} + {{#if showEncourageOptions}} +
+ + +
+
+ + +
+ {{/if}} +
+ + +
+
+ + +
+
+ + +
+
+ + +
+

{{hint}}

+

{{localize "LESOUBLIES.rolls.resourceState"}} : Songes {{resources.songesPoints}} / {{resources.songesValue}} · Cauchemar {{resources.cauchemarPoints}} / {{resources.cauchemarValue}}

+
+
diff --git a/templates/dialog-roll-attack.hbs b/templates/dialog-roll-attack.hbs new file mode 100644 index 0000000..d5e8a00 --- /dev/null +++ b/templates/dialog-roll-attack.hbs @@ -0,0 +1,141 @@ +
+
+
+

{{attackTitle}}

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+

{{localize "LESOUBLIES.rolls.resourceState"}} : Songes {{attackerResources.songesPoints}} / {{attackerResources.songesValue}} · Cauchemar {{attackerResources.cauchemarPoints}} / {{attackerResources.cauchemarValue}}

+
+ +
+

{{localize "LESOUBLIES.rolls.defender"}}

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
diff --git a/templates/dialog-roll-choice.hbs b/templates/dialog-roll-choice.hbs new file mode 100644 index 0000000..4c6240b --- /dev/null +++ b/templates/dialog-roll-choice.hbs @@ -0,0 +1,19 @@ +
+
+

{{label}}

+

{{localize "LESOUBLIES.rolls.choiceHint"}}

+
+ {{#each dice as |die|}} +
+
+ {{die.typeLabel}} +
{{localize "LESOUBLIES.rolls.natural"}} : {{die.total}}
+
{{localize "LESOUBLIES.rolls.breakdown"}} : {{die.breakdown}}
+ {{#if die.sourceLabel}}
{{die.sourceLabel}}
{{/if}} +
{{localize "LESOUBLIES.rolls.debt"}} : {{die.debtLabel}}
+
+
+ {{/each}} +
+
+
diff --git a/templates/dialog-roll-confrontation.hbs b/templates/dialog-roll-confrontation.hbs new file mode 100644 index 0000000..a54cece --- /dev/null +++ b/templates/dialog-roll-confrontation.hbs @@ -0,0 +1,95 @@ +
+
+
+

{{localize "LESOUBLIES.rolls.attacker"}}

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+

{{localize "LESOUBLIES.rolls.resourceState"}} : Songes {{attackerResources.songesPoints}} / {{attackerResources.songesValue}} · Cauchemar {{attackerResources.cauchemarPoints}} / {{attackerResources.cauchemarValue}}

+
+ +
+

{{localize "LESOUBLIES.rolls.defender"}}

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+
+ + +
+

{{localize "LESOUBLIES.rolls.confrontationHint"}}

+
+
diff --git a/templates/dialog-roll-preset-confrontation.hbs b/templates/dialog-roll-preset-confrontation.hbs new file mode 100644 index 0000000..ff445bc --- /dev/null +++ b/templates/dialog-roll-preset-confrontation.hbs @@ -0,0 +1,125 @@ +
+
+
+

{{title}}

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ {{#if showIntimidateOptions}} +
+ + +
+ {{/if}} + {{#if showEvaluateOptions}} +
+ + +
+ {{/if}} + {{#if showGrappleOptions}} +
+ + +
+ {{/if}} +
+ + +
+

{{hint}}

+

{{localize "LESOUBLIES.rolls.resourceState"}} : Songes {{attackerResources.songesPoints}} / {{attackerResources.songesValue}} · Cauchemar {{attackerResources.cauchemarPoints}} / {{attackerResources.cauchemarValue}}

+
+ +
+

{{localize "LESOUBLIES.rolls.defender"}}

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
diff --git a/templates/dialog-roll-test.hbs b/templates/dialog-roll-test.hbs new file mode 100644 index 0000000..8b0d5c3 --- /dev/null +++ b/templates/dialog-roll-test.hbs @@ -0,0 +1,38 @@ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+

{{localize "LESOUBLIES.rolls.resourceState"}} : Songes {{resources.songesPoints}} / {{resources.songesValue}} · Cauchemar {{resources.cauchemarPoints}} / {{resources.cauchemarValue}}

+ {{#if isInitiative}} +

{{localize "LESOUBLIES.rolls.initiativeHint"}}

+ {{else}} +

{{localize "LESOUBLIES.rolls.testHint"}}

+ {{/if}} +
+
diff --git a/templates/dialog-spell-activation.hbs b/templates/dialog-spell-activation.hbs new file mode 100644 index 0000000..c07ba64 --- /dev/null +++ b/templates/dialog-spell-activation.hbs @@ -0,0 +1,44 @@ +
+
+
+

{{spell.name}}

+
+
+
+
+
+
+
+
+

{{#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}}

+
+ +
+

Activation

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+

{{localize "LESOUBLIES.rolls.resourceState"}} : Songes {{resources.songesPoints}} / {{resources.songesValue}} · Cauchemar {{resources.cauchemarPoints}} / {{resources.cauchemarValue}}

+
+
+
diff --git a/templates/dialog-thread-harvest.hbs b/templates/dialog-thread-harvest.hbs new file mode 100644 index 0000000..4ab450b --- /dev/null +++ b/templates/dialog-thread-harvest.hbs @@ -0,0 +1,42 @@ +
+
+

Récolte de fils

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+

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}}

+
+
diff --git a/templates/item-arme-sheet.hbs b/templates/item-arme-sheet.hbs new file mode 100644 index 0000000..32cfb43 --- /dev/null +++ b/templates/item-arme-sheet.hbs @@ -0,0 +1,27 @@ +
+
+ +
+

Arsenal

+

+ +
+
+
+
+
+
+
+
+
+
+

Race restreinte : {{system.restrictedRace}}

+

{{localize "LESOUBLIES.labels.proprietes}} : {{join system.properties}}

+
+
+

{{localize "LESOUBLIES.labels.description"}}

+ {{editor system.description target="system.description" button=true editable=isEditMode}} +

{{localize "LESOUBLIES.labels.notes"}}

+ {{editor system.notes target="system.notes" button=true editable=isEditMode}} +
+
diff --git a/templates/item-armure-sheet.hbs b/templates/item-armure-sheet.hbs new file mode 100644 index 0000000..5e438f2 --- /dev/null +++ b/templates/item-armure-sheet.hbs @@ -0,0 +1,24 @@ +
+
+ +
+

Protection

+

+ +
+
+
+
+
+
+
+
+
+
+
+

{{localize "LESOUBLIES.labels.description"}}

+ {{editor system.description target="system.description" button=true editable=isEditMode}} +

{{localize "LESOUBLIES.labels.notes"}}

+ {{editor system.notes target="system.notes" button=true editable=isEditMode}} +
+
diff --git a/templates/item-competence-sheet.hbs b/templates/item-competence-sheet.hbs new file mode 100644 index 0000000..14e99c5 --- /dev/null +++ b/templates/item-competence-sheet.hbs @@ -0,0 +1,26 @@ +
+
+ +
+

Savoir-faire

+

+ +
+
+
+
+
+
+
+
+

{{localize "LESOUBLIES.labels.domaines}} : {{join system.domains}}

+

Domaines fixes : {{join system.fixedDomains}}

+

Exemples : {{join system.exampleDomains}}

+
+
+

{{localize "LESOUBLIES.labels.description"}}

+ {{editor system.description target="system.description" button=true editable=isEditMode}} +

{{localize "LESOUBLIES.labels.notes"}}

+ {{editor system.notes target="system.notes" button=true editable=isEditMode}} +
+
diff --git a/templates/item-equipement-sheet.hbs b/templates/item-equipement-sheet.hbs new file mode 100644 index 0000000..c160d11 --- /dev/null +++ b/templates/item-equipement-sheet.hbs @@ -0,0 +1,26 @@ +
+
+ +
+

Nécessaire de voyage

+

+ +
+
+
+
+
+
+
+
+
+
+
+
+
+

{{localize "LESOUBLIES.labels.description"}}

+ {{editor system.description target="system.description" button=true editable=isEditMode}} +

{{localize "LESOUBLIES.labels.notes"}}

+ {{editor system.notes target="system.notes" button=true editable=isEditMode}} +
+
diff --git a/templates/item-pouvoir-compagnie-sheet.hbs b/templates/item-pouvoir-compagnie-sheet.hbs new file mode 100644 index 0000000..01779a1 --- /dev/null +++ b/templates/item-pouvoir-compagnie-sheet.hbs @@ -0,0 +1,27 @@ +
+
+ +
+

Souffle collectif

+

+ +
+
+
+
+
+
+
+
+
+
+
+
+

{{localize "LESOUBLIES.labels.description"}}

+ {{editor system.description target="system.description" button=true editable=isEditMode}} +

{{localize "LESOUBLIES.labels.pouvoir"}}

+ {{editor system.ruleText target="system.ruleText" button=true editable=isEditMode}} +

{{localize "LESOUBLIES.labels.notes"}}

+ {{editor system.notes target="system.notes" button=true editable=isEditMode}} +
+
diff --git a/templates/item-reference-sheet.hbs b/templates/item-reference-sheet.hbs new file mode 100644 index 0000000..a0df30f --- /dev/null +++ b/templates/item-reference-sheet.hbs @@ -0,0 +1,96 @@ +
+
+ +
+

Référence de création

+

+

{{localize (concat "TYPES.Item." item.type)}}

+ +
+
+ + {{#if isRace}} +
+
+
+
+

{{localize "LESOUBLIES.labels.motsCles"}} : {{join system.keywords}}

+

Domaines de langues : {{join system.languageDomains}}

+

{{localize "LESOUBLIES.ui.profils"}}

+
+ {{#each system.profiles as |value key|}} +
+ {{/each}} +
+

Tribus principales : {{join system.mainTribes}}

+
+ {{/if}} + + {{#if isTribu}} +
+

{{localize "LESOUBLIES.labels.motsCles"}} : {{join system.keywords}}

+
+
+
+
+
+
+
+

Bonus de compétences

+
    + {{#each system.skillBonuses as |bonus|}} +
  • {{formatSkillBonus bonus}}
  • + {{/each}} +
+
+ {{/if}} + + {{#if isMetier}} +
+

Bonus de compétences

+
    + {{#each system.skillBonuses as |bonus|}} +
  • {{formatSkillBonus bonus}}
  • + {{/each}} +
+

Équipement de départ

+
    + {{#each system.startingEquipment as |entry|}} +
  • {{formatEquipmentEntry entry}}
  • + {{/each}} +
+

Sortilèges octroyés

+
    + {{#each system.spellGrants as |grant|}} +
  • {{formatSpellGrant grant}}
  • + {{/each}} +
+

Revenus : débutant {{formatPrice system.revenues.beginner}}, intermédiaire {{formatPrice system.revenues.intermediate}}, expert {{formatPrice system.revenues.expert}}

+
+ {{/if}} + +
+

{{localize "LESOUBLIES.labels.description"}}

+ {{editor system.description target="system.description" button=true editable=isEditMode}} + {{#if isRace}} +

Apparence et interprétation

+ {{editor system.appearance target="system.appearance" button=true editable=isEditMode}} + {{/if}} +

{{localize "LESOUBLIES.labels.reglesSpeciales"}}

+ {{#if isRace}} + {{editor system.specialRules target="system.specialRules" button=true editable=isEditMode}} + {{/if}} + {{#if isTribu}} + {{editor system.specialRules target="system.specialRules" button=true editable=isEditMode}} +

Interprétation

+ {{editor system.roleplayNotes target="system.roleplayNotes" button=true editable=isEditMode}} + {{/if}} + {{#if isMetier}} + {{editor system.specialRules target="system.specialRules" button=true editable=isEditMode}} +

Interprétation

+ {{editor system.roleplayNotes target="system.roleplayNotes" button=true editable=isEditMode}} + {{/if}} +

{{localize "LESOUBLIES.labels.notes"}}

+ {{editor system.notes target="system.notes" button=true editable=isEditMode}} +
+
diff --git a/templates/item-sortilege-sheet.hbs b/templates/item-sortilege-sheet.hbs new file mode 100644 index 0000000..676ab27 --- /dev/null +++ b/templates/item-sortilege-sheet.hbs @@ -0,0 +1,30 @@ +
+
+ +
+

Art occulte

+

+ +
+
+
+
+
+
+
+
+
+
+
+
+

{{localize "LESOUBLIES.labels.domaines}} : {{join system.requiredDomains}}

+

Arts requis : {{join system.artsDomains}}

+

Tags : {{join system.ruleTags}}

+
+
+

{{localize "LESOUBLIES.labels.description"}}

+ {{editor system.description target="system.description" button=true editable=isEditMode}} +

Effets

+ {{editor system.effectsText target="system.effectsText" button=true editable=isEditMode}} +
+