diff --git a/assets/actions/attaque.svg b/assets/actions/attaque.svg new file mode 100644 index 00000000..b8d67a4e --- /dev/null +++ b/assets/actions/attaque.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/actions/comp.svg b/assets/actions/comp.svg new file mode 100644 index 00000000..3788d0d9 --- /dev/null +++ b/assets/actions/comp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/actions/defense.svg b/assets/actions/defense.svg new file mode 100644 index 00000000..3738f4e1 --- /dev/null +++ b/assets/actions/defense.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/actions/defense1.svg b/assets/actions/defense1.svg new file mode 100644 index 00000000..55e7c89f --- /dev/null +++ b/assets/actions/defense1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/actions/jeu.svg b/assets/actions/jeu.svg new file mode 100644 index 00000000..5e603363 --- /dev/null +++ b/assets/actions/jeu.svg @@ -0,0 +1,66 @@ + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/assets/actions/meditation.svg b/assets/actions/meditation.svg new file mode 100644 index 00000000..04e2e516 --- /dev/null +++ b/assets/actions/meditation.svg @@ -0,0 +1,63 @@ + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/assets/actions/oeuvre.svg b/assets/actions/oeuvre.svg new file mode 100644 index 00000000..167e39cd --- /dev/null +++ b/assets/actions/oeuvre.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/actions/sort.svg b/assets/actions/sort.svg new file mode 100644 index 00000000..8020b758 --- /dev/null +++ b/assets/actions/sort.svg @@ -0,0 +1,65 @@ + + + + + + image/svg+xml + + + + + + + + + diff --git a/assets/actions/surprise-demi.svg b/assets/actions/surprise-demi.svg new file mode 100644 index 00000000..0504dfb0 --- /dev/null +++ b/assets/actions/surprise-demi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/actions/surprise-totale.svg b/assets/actions/surprise-totale.svg new file mode 100644 index 00000000..44a7bdc0 --- /dev/null +++ b/assets/actions/surprise-totale.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/actions/tache.svg b/assets/actions/tache.svg new file mode 100644 index 00000000..17dfc8e8 --- /dev/null +++ b/assets/actions/tache.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/ui/d100.svg b/assets/ui/d100.svg new file mode 100644 index 00000000..fa96ccd2 --- /dev/null +++ b/assets/ui/d100.svg @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/assets/ui/icon_sidebar_cards.svg b/assets/ui/icon_sidebar_cards.svg new file mode 100644 index 00000000..24e481c6 --- /dev/null +++ b/assets/ui/icon_sidebar_cards.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/ui/icon_sidebar_chat.svg b/assets/ui/icon_sidebar_chat.svg index d3fc0290..380d1c37 100644 --- a/assets/ui/icon_sidebar_chat.svg +++ b/assets/ui/icon_sidebar_chat.svg @@ -1,449 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/ui/icon_sidebar_macros.svg b/assets/ui/icon_sidebar_macros.svg new file mode 100644 index 00000000..68ab4fbd --- /dev/null +++ b/assets/ui/icon_sidebar_macros.svg @@ -0,0 +1,54 @@ + + + + + + image/svg+xml + + + + + + + + + diff --git a/assets/ui/players-border.webp b/assets/ui/players-border.webp deleted file mode 100644 index 4f9848a1..00000000 Binary files a/assets/ui/players-border.webp and /dev/null differ diff --git a/changelog.md b/changelog.md index 01272187..63fcc3a7 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,17 @@ # 13.0 + +## 13.0.8 - Le renouveau d'Illysis + +- Fix Foundry V13 + - Les états sont de nouveau affichés en ligne dans la feuille de personnage + - les icones spécifiques Rêve de Dragon de la barre de droite sont de nouveau utilisées + +- Nouvelle fenêtre de jets de dés + - uniquement pour les oeuvres (chant, danse, musique, cuisine,...) + - à activer dans les options avancées + ## 13.0.7 - Sous le signe d'Illysis + - Fix Foundry V13 - les tooltips des ajustements sont correctement visible - correction des affichages des signes d'heures dans la fenêtre d'astrologie diff --git a/css/foundryvtt-reve-de-dragon.css b/css/foundryvtt-reve-de-dragon.css index bdbf15df..25b4833c 100644 --- a/css/foundryvtt-reve-de-dragon.css +++ b/css/foundryvtt-reve-de-dragon.css @@ -50,6 +50,7 @@ --actor-label-color: #464331c4; } /* Global styles & Font */ +.application, .window-app { font-family: CaslonAntique; text-align: justify; @@ -80,14 +81,14 @@ select, /* =================== 2. DEBUGGING HIGHLIGHTERS ============ */ /* =================== 3. some constants ============ */ --fieldset-background: url(/ui/parchment.jpg); - --rdd-color-text-primary: rgba(10, 10, 10, 0.9); - --rdd-input-background: rgba(0, 0, 0, 0.05); - --rdd-color-border-input: rgba(0, 0, 0, 0.2); - --rdd-bg-input: rgba(255, 255, 255, 0.1); - --color-controls: rgba(0, 0, 0, 0.9); + --rdd-color-text-primary: hsla(0, 0%, 4%, 0.9); + --rdd-input-background: hsla(0, 0%, 0%, 0.1); + --rdd-color-border-input: hsla(0, 0%, 0%, 0.2); + --rdd-bg-input: hsla(0, 0%, 100%, 0.1); + --color-controls: hsla(0, 0%, 0%, 0.9); --color-controls-light: hsla(0, 0%, 20%, 0.8); --color-controls-hover: hsla(60, 100%, 75%, 0.7); - --color-control-border-hover: rgba(255, 128, 0, 0.8); + --color-control-border-hover: hsla(30, 100%, 50%, 0.8); --color-gold: rgba(191, 149, 63, 0.8); --gradient-gold: linear-gradient(30deg, rgba(191, 149, 63, 0.3), rgba(252, 246, 186, 0.3), rgba(179, 135, 40, 0.3), rgba(251, 245, 183, 0.3), rgba(170, 119, 28, 0.3)); --gradient-silver: linear-gradient(30deg, rgba(61, 55, 93, 0.3), rgba(178, 179, 196, 0.3), rgba(59, 62, 63, 0.6), rgba(206, 204, 199, 0.3), rgba(61, 46, 49, 0.3)); @@ -97,14 +98,17 @@ select, --gradient-purple-black: linear-gradient(150deg, rgba(0, 0, 0, 0.7), rgba(100, 45, 124, 0.4), rgba(82, 17, 131, 0.3), rgba(100, 45, 124, 0.4), rgba(0, 0, 0, 0.7)); --gradient-silver-light: linear-gradient(30deg, rgba(61, 55, 93, 0.2), rgba(178, 179, 196, 0.1), rgba(59, 62, 63, 0.2), rgba(206, 204, 199, 0.1), rgba(61, 46, 49, 0.2)); --gradient-daylight: conic-gradient(from 0deg, hsla(50, 100%, 80%, 0.7), hsla(30, 30%, 40%, 0.1) 25%, hsla(250, 50%, 40%, 0.1) 25%, hsla(250, 30%, 30%, 0.7) 50%, hsla(250, 50%, 40%, 0.1) 75%, hsla(30, 30%, 40%, 0.1) 75%, hsla(50, 100%, 80%, 0.7)); - --background-custom-button: linear-gradient(to bottom, rgba(33, 55, 74, 0.988) 5%, rgba(21, 40, 51, 0.671) 100%); - --background-custom-button-hover: linear-gradient(to bottom, #800000 5%, #3e0101 100%); + --background-custom-button: linear-gradient(to bottom, hsla(208, 38%, 21%, 0.988) 5%, hsla(202, 42%, 14%, 0.671) 100%); + --background-custom-button-hover: linear-gradient(to bottom, hsl(0, 100%, 25%) 5%, hsl(0, 97%, 12%) 100%); --background-control-selected: linear-gradient(to bottom, hsla(0, 100%, 25%, 0.5) 5%, hsla(0, 100%, 12%, 0.5) 100%); --background-tooltip: hsla(60, 12%, 85%, 0.95); + --background-tooltip-alt: hsla(60, 12%, 74%, 0.95); --color-tooltip: hsla(282, 47%, 33%, 0.9); --color-tooltip-faint: hsla(282, 47%, 66%, 0.5); --background-error: hsla(16, 100%, 50%, 0.8); --color-profile-border: hsla(0, 0%, 80%, 0.05); + --color-background-chat-message: hsla(60, 12%, 85%, 0.9); + --color-background-chat-whisper: repeating-linear-gradient(120deg, hsla(60, 12%, 85%, 0.75), hsla(60, 12%, 85%, 0.75) 1rem, hsla(60, 12%, 85%, 0.8) 1rem, hsla(60, 12%, 85%, 0.75) 1.5rem); } .system-foundryvtt-reve-de-dragon { /* =================== En-têtes de feuilles ============ */ @@ -417,6 +421,222 @@ select, border: 0 none; margin-bottom: 0.2rem; } +.system-foundryvtt-reve-de-dragon .roll-dialog { + font-family: CaslonAntique; + display: grid; + grid-template-areas: "header header header header header header header" "action action action action action action action" "mode separation separation separation separation separation separation" "mode carac carac carac comp comp resume" "mode choix choix choix choix choix modifiers" "mode resolution resolution resolution resolution resolution modifiers" "mode chances chances chances chances chances buttons" "footer footer footer footer footer footer footer"; + grid-template-columns: 2rem 1rem 1fr 1fr 2fr 2fr 3fr; + gap: 0.2rem; +} +.system-foundryvtt-reve-de-dragon .roll-dialog roll-header { + grid-area: header; +} +.system-foundryvtt-reve-de-dragon .roll-dialog roll-line { + grid-area: separation; +} +.system-foundryvtt-reve-de-dragon .roll-dialog roll-action { + grid-area: action; +} +.system-foundryvtt-reve-de-dragon .roll-dialog roll-carac { + grid-area: carac; +} +.system-foundryvtt-reve-de-dragon .roll-dialog roll-comp { + grid-area: comp; +} +.system-foundryvtt-reve-de-dragon .roll-dialog roll-choix { + grid-area: choix; +} +.system-foundryvtt-reve-de-dragon .roll-dialog roll-table { + grid-area: resolution; +} +.system-foundryvtt-reve-de-dragon .roll-dialog roll-conditions { + grid-area: modifiers; +} +.system-foundryvtt-reve-de-dragon .roll-dialog roll-chances { + grid-area: chances; +} +.system-foundryvtt-reve-de-dragon .roll-dialog roll-resume { + grid-area: resume; +} +.system-foundryvtt-reve-de-dragon .roll-dialog roll-buttons { + grid-area: buttons; +} +.system-foundryvtt-reve-de-dragon .roll-dialog roll-mode { + grid-area: mode; + display: flex; + flex-direction: column; +} +.system-foundryvtt-reve-de-dragon .roll-dialog roll-conditions roll-section[name="rollmode"] button[data-checked="true"], +.system-foundryvtt-reve-de-dragon .roll-dialog roll-mode button[data-checked="true"] { + background-color: var(--color-text-selection-bg); + color: var(--color-controls); +} +.system-foundryvtt-reve-de-dragon .roll-dialog roll-conditions roll-section[name="rollmode"] button[data-checked="true"] i, +.system-foundryvtt-reve-de-dragon .roll-dialog roll-mode button[data-checked="true"] i { + filter: invert(0.8); +} +.system-foundryvtt-reve-de-dragon .roll-dialog roll-conditions roll-section[name="rollmode"] button[data-checked="true"] img, +.system-foundryvtt-reve-de-dragon .roll-dialog roll-mode button[data-checked="true"] img { + filter: invert(0.2); +} +.system-foundryvtt-reve-de-dragon .roll-dialog roll-conditions roll-section[name="rollmode"] button, +.system-foundryvtt-reve-de-dragon .roll-dialog roll-mode button { + height: 1.8rem; + width: 1.8rem; + gap: 0.5rem; + padding: 0.2rem; + background-color: var(--button-background-color); + color: var(--color-controls); +} +.system-foundryvtt-reve-de-dragon .roll-dialog roll-conditions roll-section[name="rollmode"] button i, +.system-foundryvtt-reve-de-dragon .roll-dialog roll-mode button i { + filter: invert(0.2); +} +.system-foundryvtt-reve-de-dragon .roll-dialog roll-conditions roll-section[name="rollmode"] button img, +.system-foundryvtt-reve-de-dragon .roll-dialog roll-mode button img { + filter: invert(0.8); +} +.system-foundryvtt-reve-de-dragon .roll-dialog :is(roll-carac, roll-comp) { + display: flex; + flex-direction: row; + align-items: baseline; +} +.system-foundryvtt-reve-de-dragon .roll-dialog roll-section, +.system-foundryvtt-reve-de-dragon .roll-dialog roll-section div { + display: flex; + flex-direction: row; + align-items: anchor-center; + margin: 0 0.2rem; +} +.system-foundryvtt-reve-de-dragon .roll-dialog roll-resume { + display: flex; + flex-direction: row; +} +.system-foundryvtt-reve-de-dragon .roll-dialog roll-resume img.button-effect-img { + filter: invert(0.8); +} +.system-foundryvtt-reve-de-dragon .roll-dialog roll-choix roll-section { + display: grid; + grid-template-areas: "selection selection" "img roll-part"; + grid-template-columns: 3.2rem 1fr; + gap: 0.2rem; + align-items: start; +} +.system-foundryvtt-reve-de-dragon .roll-dialog roll-choix roll-section subline { + grid-area: selection; + display: flex; + flex-direction: row; +} +.system-foundryvtt-reve-de-dragon .roll-dialog roll-choix roll-section roll-part-img { + display: flex; + flex-direction: column; + grid-area: img; +} +.system-foundryvtt-reve-de-dragon .roll-dialog roll-choix roll-section roll-part-img img { + border: 0; + padding: 1px; + max-height: 3rem; + max-width: 3rem; + object-fit: contain; + height: 100%; +} +.system-foundryvtt-reve-de-dragon .roll-dialog roll-choix roll-section roll-part-detail { + grid-area: roll-part; + display: flex; + flex-direction: column; + align-items: normal; +} +.system-foundryvtt-reve-de-dragon .roll-dialog roll-choix roll-section roll-part-detail subline { + display: flex; + flex-direction: row; +} +.system-foundryvtt-reve-de-dragon .roll-dialog roll-choix roll-section roll-part-detail subline div.poesie-extrait { + display: flex; + flex-direction: column; +} +.system-foundryvtt-reve-de-dragon .roll-dialog roll-section selected-numeric-value { + display: flow; + width: 2.5rem; + text-align: right; + margin: 0 0.2rem 0 0.5rem; + padding: 0 0.2rem; +} +.system-foundryvtt-reve-de-dragon .roll-dialog roll-action { + flex-basis: content; + display: flex; + flex-direction: row; + align-items: center; + border-bottom: 0.2rem solid; + font-size: 1.2rem; + font-weight: bold; +} +.system-foundryvtt-reve-de-dragon .roll-dialog roll-action roll-section { + display: flex; + flex-direction: row; + align-items: center; +} +.system-foundryvtt-reve-de-dragon .roll-dialog roll-action roll-section img.action-img { + float: left; +} +.system-foundryvtt-reve-de-dragon .roll-dialog roll-action roll-section img { + max-width: 3rem; + max-height: 3rem; + margin: 0 1rem; + padding: 0; +} +.system-foundryvtt-reve-de-dragon .roll-dialog roll-conditions { + display: flex; + flex-direction: column; +} +.system-foundryvtt-reve-de-dragon .roll-dialog :is(roll-action, roll-carac, roll-comp) roll-section { + flex-basis: content; +} +.system-foundryvtt-reve-de-dragon .roll-dialog :is(roll-choix, roll-conditions, roll-carac, roll-comp) select { + max-height: 1.6rem; + margin: 0 0.2rem; + padding: 0 0.2rem; +} +.system-foundryvtt-reve-de-dragon .roll-dialog :is(roll-choix, roll-conditions, roll-carac, roll-comp) input[type="number"] { + max-height: 1.6rem; + max-width: 2.5rem; + margin: 0 0.2rem; + padding: 0 0.2rem; +} +.system-foundryvtt-reve-de-dragon .roll-dialog :is(roll-choix, roll-conditions, roll-carac, roll-comp) img { + max-width: 1.8rem; + max-height: 1.8rem; + margin: 0 0.2rem; + padding: 0; +} +.system-foundryvtt-reve-de-dragon .roll-dialog roll-carac select[name="select-carac"] { + max-width: 6rem; +} +.system-foundryvtt-reve-de-dragon .roll-dialog roll-comp select[name="select-comp"] { + min-width: 8rem; + max-width: 11rem; + margin-left: 1rem; +} +.system-foundryvtt-reve-de-dragon .roll-dialog roll-conditions roll-section[name="coeur"] select[name="coeur"] { + max-width: 4rem; +} +.system-foundryvtt-reve-de-dragon .roll-dialog roll-conditions roll-section[name="tricher"] img { + /* image de d100 */ + max-width: 2.5rem; + max-height: 2.5rem; + gap: 0; + margin: 0; + padding: 0; + filter: invert(0.8); +} +.system-foundryvtt-reve-de-dragon .roll-dialog roll-buttons { + display: flex; + flex-direction: row-reverse; +} +.system-foundryvtt-reve-de-dragon .roll-dialog roll-table table tr :is(th, td) { + padding: 0.1rem; + width: 1.5rem; + text-align: center; +} .system-foundryvtt-reve-de-dragon .window-header { background: rgba(0, 0, 0, 0.75); } @@ -473,6 +693,17 @@ select, .system-foundryvtt-reve-de-dragon .sheet-header :is(.header-compteurs,.header-etats,.profile-img, .profile-img-token) { padding: 0 0.4rem; } +.system-foundryvtt-reve-de-dragon .sheet-header div.header-etats { + width: calc(40% - 32px - 1rem); + height: 48px; + max-width: fit-content; + flex: initial; + flex-grow: 3; +} +.system-foundryvtt-reve-de-dragon .sheet-header div.header-etats div div { + display: flex; + flex-direction: row; +} .system-foundryvtt-reve-de-dragon .sheet-header :is(.profile-img, .profile-img-token) { -webkit-box-flex: 0; -ms-flex: 0 0 110px; @@ -507,12 +738,6 @@ select, text-align: right; max-width: fit-content; } -.system-foundryvtt-reve-de-dragon .sheet-header div.header-etats { - width: calc(40% - 32px - 1rem); - height: 48px; - max-width: fit-content; - flex: initial; -} .system-foundryvtt-reve-de-dragon .sheet-header .resource-content { width: 2rem; } @@ -946,6 +1171,10 @@ select, .system-foundryvtt-reve-de-dragon .foundryvtt-reve-de-dragon .sheet-body .tab { height: 100%; } +.system-foundryvtt-reve-de-dragon .carac-label { + font-weight: bold; + flex-basis: 40%; +} .system-foundryvtt-reve-de-dragon .rdd.sheet .window-content { overflow: hidden; } @@ -962,23 +1191,15 @@ select, width: 100%; flex-grow: 0; } -.system-foundryvtt-reve-de-dragon .rdd.sheet .window-content .sheet-body .carac-list .caracteristique { - flex-wrap: nowrap; - justify-content: stretch; -} .system-foundryvtt-reve-de-dragon .rdd.sheet .window-content .sheet-body .carac-list .caracteristique.streched { flex-wrap: nowrap; justify-content: stretch; flex-basis: 7.5em; width: max-content; } -.system-foundryvtt-reve-de-dragon .carac-label { - font-weight: bold; - flex-basis: 40%; -} -.system-foundryvtt-reve-de-dragon .rdd.sheet .window-content .sheet-body .carac-list .caracteristique > .utiliser-attribut { - flex-basis: available; - flex-grow: 1; +.system-foundryvtt-reve-de-dragon .rdd.sheet .window-content .sheet-body .carac-list .caracteristique { + flex-wrap: nowrap; + justify-content: stretch; } .system-foundryvtt-reve-de-dragon .rdd.sheet .window-content .sheet-body .carac-list .caracteristique .carac-value { flex-basis: 15%; @@ -1190,25 +1411,21 @@ select, padding: 5px; } .system-foundryvtt-reve-de-dragon .poesie-extrait { + max-height: 8rem; font-size: 0.8rem; font-style: italic; + color: rgba(82, 17, 131, 0.9); + overflow: hidden; +} +.system-foundryvtt-reve-de-dragon .poesie-extrait:hover { + max-height: unset; + overflow: visible; + opacity: 1; } .system-foundryvtt-reve-de-dragon .poesie-reference { font-size: 0.7rem; text-align: right; } -.system-foundryvtt-reve-de-dragon .poesie-overflow { - color: rgba(82, 17, 131, 0.9); - max-height: 1.5rem; - overflow: hidden; - border-left: 1px dotted black; - /* If you want dots under the hoverable text */ -} -.system-foundryvtt-reve-de-dragon .poesie-overflow:hover { - max-height: unset; - overflow: visible; - border-left: 0px; -} .system-foundryvtt-reve-de-dragon .type-compendium { font-size: 0.6rem; } @@ -1775,6 +1992,9 @@ select, background: rgba(0, 0, 0, 0.05); cursor: pointer; } +.system-foundryvtt-reve-de-dragon .chat-message { + background: var(--color-background-chat-message); +} .system-foundryvtt-reve-de-dragon .chat-message h4 { font-size: 0.9rem; } @@ -1785,13 +2005,13 @@ select, font-size: 0.7rem; flex-grow: 3; } -.system-foundryvtt-reve-de-dragon .chat-message.whisper { - background: rgba(220, 220, 210, 0.75); - border: 2px solid #545469; -} .system-foundryvtt-reve-de-dragon .chat-message hr { margin: 0.2rem 0; } +.system-foundryvtt-reve-de-dragon .chat-message.whisper { + background: var(--color-background-chat-whisper); + border: 2px solid #545469; +} .system-foundryvtt-reve-de-dragon .chat-icon { border: 0; padding: 2px 6px 2px 2px; @@ -1813,69 +2033,68 @@ select, margin-right: 0.2rem; vertical-align: bottom; } -.system-foundryvtt-reve-de-dragon #sidebar-tabs { - flex: 0 0 28px; - box-sizing: border-box; - margin: 0 0 3px; - border-bottom: 1px solid rgba(0, 0, 0, 0); - box-shadow: inset 0 0 2rem rgba(0, 0, 0, 0.5); +.system-foundryvtt-reve-de-dragon #sidebar-tabs menu button:is( + [data-tab="chat"], + [data-tab="combat"], + [data-tab="scenes"], + [data-tab="actors"], + [data-tab="items"], + [data-tab="journal"], + [data-tab="cards"], + [data-tab="macros"], + [data-tab="tables"], + [data-tab="playlists"], + [data-tab="compendium"], + [data-tab="settings"] + )::before { + content: none; } -.system-foundryvtt-reve-de-dragon #sidebar-tabs > .item.active { - border: 1px solid #726248; - background: rgba(30, 25, 20, 0.75); - box-shadow: 0 0 6px inset #726248; +.system-foundryvtt-reve-de-dragon #sidebar-tabs menu button { + background-repeat: no-repeat; } -.system-foundryvtt-reve-de-dragon #sidebar #sidebar-tabs i { - width: 23px; - height: 23px; - display: inline-block; - background-position: center; - background-size: cover; - text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.75); +.system-foundryvtt-reve-de-dragon #sidebar-tabs menu button[aria-pressed="false"] { + background-color: hsla(30, 37%, 50%, 0.5); } -.system-foundryvtt-reve-de-dragon #sidebar #sidebar-tabs i:is( - .fa-comments, .fa-fist-raised, .fa-swords, - .fa-users, .fa-user, .fa-map, .fa-suitcase, - .fa-book-open, .fa-th-list, .fa-music, - .fa-atlas,.fa-cogs - ):before { - content: ""; +.system-foundryvtt-reve-de-dragon #sidebar-tabs menu button[aria-pressed="true"] { + border: 1px solid hsl(15, 80%, 30%); + background-color: hsla(30, 100%, 90%, 0.6); + box-shadow: 0 0 3px inset hsl(15, 80%, 30%); } -.system-foundryvtt-reve-de-dragon #sidebar #sidebar-tabs i.fa-comments { - background: url("../assets/ui/icon_sidebar_chat.svg") no-repeat; +.system-foundryvtt-reve-de-dragon #sidebar-tabs menu button[data-tab="chat"] { + background-image: url("../assets/ui/icon_sidebar_chat.svg"); } -.system-foundryvtt-reve-de-dragon #sidebar #sidebar-tabs i.fa-fist-raised { - background: url("../assets/ui/icon_sidebar_fight.svg") no-repeat; +.system-foundryvtt-reve-de-dragon #sidebar-tabs menu button[data-tab="combat"] { + background-image: url("../assets/ui/icon_sidebar_fight.svg"); } -.system-foundryvtt-reve-de-dragon #sidebar #sidebar-tabs i.fa-swords { - background: url("../assets/ui/icon_sidebar_fight.svg") no-repeat; +.system-foundryvtt-reve-de-dragon #sidebar-tabs menu button[data-tab="scenes"] { + background-image: url("../assets/ui/icon_sidebar_scene.svg"); } -.system-foundryvtt-reve-de-dragon #sidebar #sidebar-tabs i.fa-user { - background: url("../assets/ui/icon_sidebar_actor.svg") no-repeat; +.system-foundryvtt-reve-de-dragon #sidebar-tabs menu button[data-tab="actors"] { + background-image: url("../assets/ui/icon_sidebar_actor.svg"); } -.system-foundryvtt-reve-de-dragon #sidebar #sidebar-tabs i.fa-users { - background: url("../assets/ui/icon_sidebar_actor.svg") no-repeat; +.system-foundryvtt-reve-de-dragon #sidebar-tabs menu button[data-tab="items"] { + background-image: url("../assets/ui/icon_sidebar_item.svg"); } -.system-foundryvtt-reve-de-dragon #sidebar #sidebar-tabs i.fa-map { - background: url("../assets/ui/icon_sidebar_scene.svg") no-repeat; +.system-foundryvtt-reve-de-dragon #sidebar-tabs menu button[data-tab="journal"] { + background-image: url("../assets/ui/icon_sidebar_journal.svg"); } -.system-foundryvtt-reve-de-dragon #sidebar #sidebar-tabs i.fa-suitcase { - background: url("../assets/ui/icon_sidebar_item.svg") no-repeat; +.system-foundryvtt-reve-de-dragon #sidebar-tabs menu button[data-tab="cards"] { + background-image: url("../assets/ui/icon_sidebar_cards.svg"); } -.system-foundryvtt-reve-de-dragon #sidebar #sidebar-tabs i.fa-book-open { - background: url("../assets/ui/icon_sidebar_journal.svg") no-repeat; +.system-foundryvtt-reve-de-dragon #sidebar-tabs menu button[data-tab="macros"] { + background-image: url("../assets/ui/icon_sidebar_macros.svg"); } -.system-foundryvtt-reve-de-dragon #sidebar #sidebar-tabs i.fa-th-list { - background: url("../assets/ui/icon_sidebar_rolltable.svg") no-repeat; +.system-foundryvtt-reve-de-dragon #sidebar-tabs menu button[data-tab="tables"] { + background-image: url("../assets/ui/d100.svg"); } -.system-foundryvtt-reve-de-dragon #sidebar #sidebar-tabs i.fa-music { - background: url("../assets/ui/icon_sidebar_music.svg") no-repeat; +.system-foundryvtt-reve-de-dragon #sidebar-tabs menu button[data-tab="playlists"] { + background-image: url("../assets/ui/icon_sidebar_music.svg"); } -.system-foundryvtt-reve-de-dragon #sidebar #sidebar-tabs i.fa-atlas { - background: url("../assets/ui/icon_sidebar_compendium.svg") no-repeat; +.system-foundryvtt-reve-de-dragon #sidebar-tabs menu button[data-tab="compendium"] { + background-image: url("../assets/ui/icon_sidebar_compendium.svg"); } -.system-foundryvtt-reve-de-dragon #sidebar #sidebar-tabs i.fa-cogs { - background: url("../assets/ui/icon_sidebar_settings.svg") no-repeat; +.system-foundryvtt-reve-de-dragon #sidebar-tabs menu button[data-tab="settings"] { + background-image: url("../assets/ui/icon_sidebar_settings.svg"); } .system-foundryvtt-reve-de-dragon #combat #combat-controls { box-shadow: inset 0 0 2rem rgba(0, 0, 0, 0.5); @@ -1950,55 +2169,6 @@ select, border-image-width: 4px; border-image-outset: 0px; } -.system-foundryvtt-reve-de-dragon .window-app.calendar { - display: inline-block; - background: none; - margin: 0; - padding: 0; - box-shadow: none; - pointer-events: none; - max-height: fit-content; -} -.system-foundryvtt-reve-de-dragon .window-app.calendar header.window-header { - min-width: fit-content; - height: 1.4rem; - pointer-events: all; -} -.system-foundryvtt-reve-de-dragon .window-app.calendar .window-content { - margin: 0; - padding: 0; - z-index: 100; - flex-direction: column; - min-width: 250px; - height: fit-content; - background: hsla(0, 0%, 0%, 0); - font-family: "GoudyAcc"; - pointer-events: none; -} -.system-foundryvtt-reve-de-dragon .window-app.calendar .window-content div:is(.calendar-boutons-heure, .horloge-digitale) { - pointer-events: all; -} -.system-foundryvtt-reve-de-dragon .window-app.calendar .window-content div.horloge-analogique { - pointer-events: none; -} -.system-foundryvtt-reve-de-dragon .window-app.calendar .window-content div.horloge-analogique div.horloge-roue { - pointer-events: all; -} -.system-foundryvtt-reve-de-dragon .window-app.calendar div.horloge-roue { - position: relative; - margin-bottom: 7px; - left: 0; - width: 8rem; - height: 8rem; -} -.system-foundryvtt-reve-de-dragon .window-app.calendar div.horloge-roue div.horloge-heure { - width: 1.4rem; - height: 1.4rem; -} -.system-foundryvtt-reve-de-dragon .window-app.calendar div.horloge-roue div.horloge-heure img.horloge-heure-img { - width: 1.4rem; - height: 1.4rem; -} .system-foundryvtt-reve-de-dragon div.horloge-roue div { position: absolute; border: none; @@ -2060,157 +2230,207 @@ select, .system-foundryvtt-reve-de-dragon div.horloge-roue div img { border: none; } -.system-foundryvtt-reve-de-dragon .window-app.calendar div.horloge-heure.heure-01 { - top: calc(50% - 0.7rem + 0 * 38%); - left: calc(50% - 0.7rem + -1 * 38%); +.system-foundryvtt-reve-de-dragon .calendar { + display: inline-block; + background: none; + margin: 0; + padding: 0; + box-shadow: none; + pointer-events: none; + max-height: fit-content; } -.system-foundryvtt-reve-de-dragon .window-app.calendar div.horloge-heure.heure-02 { - top: calc(50% - 0.7rem + -0.5 * 38%); - left: calc(50% - 0.7rem + -0.8660254 * 38%); +.system-foundryvtt-reve-de-dragon .calendar header.window-header { + min-width: fit-content; + height: 1.4rem; + pointer-events: all; } -.system-foundryvtt-reve-de-dragon .window-app.calendar div.horloge-heure.heure-03 { - top: calc(50% - 0.7rem + -0.8660254 * 38%); - left: calc(50% - 0.7rem + -0.5 * 38%); -} -.system-foundryvtt-reve-de-dragon .window-app.calendar div.horloge-heure.heure-04 { - top: calc(50% - 0.7rem + -1 * 38%); - left: calc(50% - 0.7rem + 0 * 38%); -} -.system-foundryvtt-reve-de-dragon .window-app.calendar div.horloge-heure.heure-05 { - top: calc(50% - 0.7rem + -0.8660254 * 38%); - left: calc(50% - 0.7rem + 0.5 * 38%); -} -.system-foundryvtt-reve-de-dragon .window-app.calendar div.horloge-heure.heure-06 { - top: calc(50% - 0.7rem + -0.5 * 38%); - left: calc(50% - 0.7rem + 0.8660254 * 38%); -} -.system-foundryvtt-reve-de-dragon .window-app.calendar div.horloge-heure.heure-07 { - top: calc(50% - 0.7rem + 0 * 38%); - left: calc(50% - 0.7rem + 1 * 38%); -} -.system-foundryvtt-reve-de-dragon .window-app.calendar div.horloge-heure.heure-08 { - top: calc(50% - 0.7rem + 0.5 * 38%); - left: calc(50% - 0.7rem + 0.8660254 * 38%); -} -.system-foundryvtt-reve-de-dragon .window-app.calendar div.horloge-heure.heure-09 { - top: calc(50% - 0.7rem + 0.8660254 * 38%); - left: calc(50% - 0.7rem + 0.5 * 38%); -} -.system-foundryvtt-reve-de-dragon .window-app.calendar div.horloge-heure.heure-10 { - top: calc(50% - 0.7rem + 1 * 38%); - left: calc(50% - 0.7rem + 0 * 38%); -} -.system-foundryvtt-reve-de-dragon .window-app.calendar div.horloge-heure.heure-11 { - top: calc(50% - 0.7rem + 0.8660254 * 38%); - left: calc(50% - 0.7rem + -0.5 * 38%); -} -.system-foundryvtt-reve-de-dragon .window-app.calendar div.horloge-heure.heure-12 { - top: calc(50% - 0.7rem + 0.5 * 38%); - left: calc(50% - 0.7rem + -0.8660254 * 41%); -} -.system-foundryvtt-reve-de-dragon .window-app.calendar-astrologie div.horloge-heure.heure-01 { - top: calc(50% - 1rem + 0 * 41%); - left: calc(50% - 1rem + -1 * 41%); -} -.system-foundryvtt-reve-de-dragon .window-app.calendar-astrologie div.horloge-heure.heure-02 { - top: calc(50% - 1rem + -0.5 * 41%); - left: calc(50% - 1rem + -0.8660254 * 41%); -} -.system-foundryvtt-reve-de-dragon .window-app.calendar-astrologie div.horloge-heure.heure-03 { - top: calc(50% - 1rem + -0.8660254 * 41%); - left: calc(50% - 1rem + -0.5 * 41%); -} -.system-foundryvtt-reve-de-dragon .window-app.calendar-astrologie div.horloge-heure.heure-04 { - top: calc(50% - 1rem + -1 * 41%); - left: calc(50% - 1rem + 0 * 41%); -} -.system-foundryvtt-reve-de-dragon .window-app.calendar-astrologie div.horloge-heure.heure-05 { - top: calc(50% - 1rem + -0.8660254 * 41%); - left: calc(50% - 1rem + 0.5 * 41%); -} -.system-foundryvtt-reve-de-dragon .window-app.calendar-astrologie div.horloge-heure.heure-06 { - top: calc(50% - 1rem + -0.5 * 41%); - left: calc(50% - 1rem + 0.8660254 * 41%); -} -.system-foundryvtt-reve-de-dragon .window-app.calendar-astrologie div.horloge-heure.heure-07 { - top: calc(50% - 1rem + 0 * 41%); - left: calc(50% - 1rem + 1 * 41%); -} -.system-foundryvtt-reve-de-dragon .window-app.calendar-astrologie div.horloge-heure.heure-08 { - top: calc(50% - 1rem + 0.5 * 41%); - left: calc(50% - 1rem + 0.8660254 * 41%); -} -.system-foundryvtt-reve-de-dragon .window-app.calendar-astrologie div.horloge-heure.heure-09 { - top: calc(50% - 1rem + 0.8660254 * 41%); - left: calc(50% - 1rem + 0.5 * 41%); -} -.system-foundryvtt-reve-de-dragon .window-app.calendar-astrologie div.horloge-heure.heure-10 { - top: calc(50% - 1rem + 1 * 41%); - left: calc(50% - 1rem + 0 * 41%); -} -.system-foundryvtt-reve-de-dragon .window-app.calendar-astrologie div.horloge-heure.heure-11 { - top: calc(50% - 1rem + 0.8660254 * 41%); - left: calc(50% - 1rem + -0.5 * 41%); -} -.system-foundryvtt-reve-de-dragon .window-app.calendar-astrologie div.horloge-heure.heure-12 { - top: calc(50% - 1rem + 0.5 * 41%); - left: calc(50% - 1rem + -0.8660254 * 41%); -} -.system-foundryvtt-reve-de-dragon .window-app.calendar-astrologie div.horloge-ajustement.heure-01 { - top: calc(50% - 0.4rem + 0 * 28%); - left: calc(50% - 0.4rem + -1 * 28%); -} -.system-foundryvtt-reve-de-dragon .window-app.calendar-astrologie div.horloge-ajustement.heure-02 { - top: calc(50% - 0.4rem + -0.5 * 28%); - left: calc(50% - 0.4rem + -0.8660254 * 28%); -} -.system-foundryvtt-reve-de-dragon .window-app.calendar-astrologie div.horloge-ajustement.heure-03 { - top: calc(50% - 0.4rem + -0.8660254 * 28%); - left: calc(50% - 0.4rem + -0.5 * 28%); -} -.system-foundryvtt-reve-de-dragon .window-app.calendar-astrologie div.horloge-ajustement.heure-04 { - top: calc(50% - 0.4rem + -1 * 28%); - left: calc(50% - 0.4rem + 0 * 28%); -} -.system-foundryvtt-reve-de-dragon .window-app.calendar-astrologie div.horloge-ajustement.heure-05 { - top: calc(50% - 0.4rem + -0.8660254 * 28%); - left: calc(50% - 0.4rem + 0.5 * 28%); -} -.system-foundryvtt-reve-de-dragon .window-app.calendar-astrologie div.horloge-ajustement.heure-06 { - top: calc(50% - 0.4rem + -0.5 * 28%); - left: calc(50% - 0.4rem + 0.8660254 * 28%); -} -.system-foundryvtt-reve-de-dragon .window-app.calendar-astrologie div.horloge-ajustement.heure-07 { - top: calc(50% - 0.4rem + 0 * 28%); - left: calc(50% - 0.4rem + 1 * 28%); -} -.system-foundryvtt-reve-de-dragon .window-app.calendar-astrologie div.horloge-ajustement.heure-08 { - top: calc(50% - 0.4rem + 0.5 * 28%); - left: calc(50% - 0.4rem + 0.8660254 * 28%); -} -.system-foundryvtt-reve-de-dragon .window-app.calendar-astrologie div.horloge-ajustement.heure-09 { - top: calc(50% - 0.4rem + 0.8660254 * 28%); - left: calc(50% - 0.4rem + 0.5 * 28%); -} -.system-foundryvtt-reve-de-dragon .window-app.calendar-astrologie div.horloge-ajustement.heure-10 { - top: calc(50% - 0.4rem + 1 * 28%); - left: calc(50% - 0.4rem + 0 * 28%); -} -.system-foundryvtt-reve-de-dragon .window-app.calendar-astrologie div.horloge-ajustement.heure-11 { - top: calc(50% - 0.4rem + 0.8660254 * 28%); - left: calc(50% - 0.4rem + -0.5 * 28%); -} -.system-foundryvtt-reve-de-dragon .window-app.calendar-astrologie div.horloge-ajustement.heure-12 { - top: calc(50% - 0.4rem + 0.5 * 28%); - left: calc(50% - 0.4rem + -0.8660254 * 28%); -} -.system-foundryvtt-reve-de-dragon .window-app.calendar header.window-header h4 { +.system-foundryvtt-reve-de-dragon .calendar header.window-header h4 { font-size: 0.8rem; } -.system-foundryvtt-reve-de-dragon .window-app.calendar section.window-content div.calendar-boutons-heure { +.system-foundryvtt-reve-de-dragon .calendar .window-content { + margin: 0; + padding: 0; + z-index: 100; + flex-direction: column; + min-width: 250px; + height: fit-content; + background: hsla(0, 0%, 0%, 0); + font-family: "GoudyAcc"; + pointer-events: none; +} +.system-foundryvtt-reve-de-dragon .calendar .window-content div.calendar-boutons-heure { display: grid; background: hsl(0, 0%, 20%); color: hsla(0, 0%, 80%, 0.8); + pointer-events: all; +} +.system-foundryvtt-reve-de-dragon .calendar .window-content div.horloge-digitale { + pointer-events: all; +} +.system-foundryvtt-reve-de-dragon .calendar .window-content div.horloge-analogique { + pointer-events: none; +} +.system-foundryvtt-reve-de-dragon .calendar .window-content div.horloge-analogique div.horloge-roue { + pointer-events: all; +} +.system-foundryvtt-reve-de-dragon .calendar div.horloge-roue { + position: relative; + margin-bottom: 7px; + left: 0; + width: 8rem; + height: 8rem; +} +.system-foundryvtt-reve-de-dragon .calendar div.horloge-roue div.horloge-heure { + width: 1.4rem; + height: 1.4rem; +} +.system-foundryvtt-reve-de-dragon .calendar div.horloge-roue div.horloge-heure img.horloge-heure-img { + width: 1.4rem; + height: 1.4rem; +} +.system-foundryvtt-reve-de-dragon .calendar div.horloge-heure.heure-01 { + top: calc(50% - 0.7rem + 0 * 38%); + left: calc(50% - 0.7rem + -1 * 38%); +} +.system-foundryvtt-reve-de-dragon .calendar div.horloge-heure.heure-02 { + top: calc(50% - 0.7rem + -0.5 * 38%); + left: calc(50% - 0.7rem + -0.8660254 * 38%); +} +.system-foundryvtt-reve-de-dragon .calendar div.horloge-heure.heure-03 { + top: calc(50% - 0.7rem + -0.8660254 * 38%); + left: calc(50% - 0.7rem + -0.5 * 38%); +} +.system-foundryvtt-reve-de-dragon .calendar div.horloge-heure.heure-04 { + top: calc(50% - 0.7rem + -1 * 38%); + left: calc(50% - 0.7rem + 0 * 38%); +} +.system-foundryvtt-reve-de-dragon .calendar div.horloge-heure.heure-05 { + top: calc(50% - 0.7rem + -0.8660254 * 38%); + left: calc(50% - 0.7rem + 0.5 * 38%); +} +.system-foundryvtt-reve-de-dragon .calendar div.horloge-heure.heure-06 { + top: calc(50% - 0.7rem + -0.5 * 38%); + left: calc(50% - 0.7rem + 0.8660254 * 38%); +} +.system-foundryvtt-reve-de-dragon .calendar div.horloge-heure.heure-07 { + top: calc(50% - 0.7rem + 0 * 38%); + left: calc(50% - 0.7rem + 1 * 38%); +} +.system-foundryvtt-reve-de-dragon .calendar div.horloge-heure.heure-08 { + top: calc(50% - 0.7rem + 0.5 * 38%); + left: calc(50% - 0.7rem + 0.8660254 * 38%); +} +.system-foundryvtt-reve-de-dragon .calendar div.horloge-heure.heure-09 { + top: calc(50% - 0.7rem + 0.8660254 * 38%); + left: calc(50% - 0.7rem + 0.5 * 38%); +} +.system-foundryvtt-reve-de-dragon .calendar div.horloge-heure.heure-10 { + top: calc(50% - 0.7rem + 1 * 38%); + left: calc(50% - 0.7rem + 0 * 38%); +} +.system-foundryvtt-reve-de-dragon .calendar div.horloge-heure.heure-11 { + top: calc(50% - 0.7rem + 0.8660254 * 38%); + left: calc(50% - 0.7rem + -0.5 * 38%); +} +.system-foundryvtt-reve-de-dragon .calendar div.horloge-heure.heure-12 { + top: calc(50% - 0.7rem + 0.5 * 38%); + left: calc(50% - 0.7rem + -0.8660254 * 41%); +} +.system-foundryvtt-reve-de-dragon .calendar-astrologie div.horloge-heure.heure-01 { + top: calc(50% - 1rem + 0 * 41%); + left: calc(50% - 1rem + -1 * 41%); +} +.system-foundryvtt-reve-de-dragon .calendar-astrologie div.horloge-heure.heure-02 { + top: calc(50% - 1rem + -0.5 * 41%); + left: calc(50% - 1rem + -0.8660254 * 41%); +} +.system-foundryvtt-reve-de-dragon .calendar-astrologie div.horloge-heure.heure-03 { + top: calc(50% - 1rem + -0.8660254 * 41%); + left: calc(50% - 1rem + -0.5 * 41%); +} +.system-foundryvtt-reve-de-dragon .calendar-astrologie div.horloge-heure.heure-04 { + top: calc(50% - 1rem + -1 * 41%); + left: calc(50% - 1rem + 0 * 41%); +} +.system-foundryvtt-reve-de-dragon .calendar-astrologie div.horloge-heure.heure-05 { + top: calc(50% - 1rem + -0.8660254 * 41%); + left: calc(50% - 1rem + 0.5 * 41%); +} +.system-foundryvtt-reve-de-dragon .calendar-astrologie div.horloge-heure.heure-06 { + top: calc(50% - 1rem + -0.5 * 41%); + left: calc(50% - 1rem + 0.8660254 * 41%); +} +.system-foundryvtt-reve-de-dragon .calendar-astrologie div.horloge-heure.heure-07 { + top: calc(50% - 1rem + 0 * 41%); + left: calc(50% - 1rem + 1 * 41%); +} +.system-foundryvtt-reve-de-dragon .calendar-astrologie div.horloge-heure.heure-08 { + top: calc(50% - 1rem + 0.5 * 41%); + left: calc(50% - 1rem + 0.8660254 * 41%); +} +.system-foundryvtt-reve-de-dragon .calendar-astrologie div.horloge-heure.heure-09 { + top: calc(50% - 1rem + 0.8660254 * 41%); + left: calc(50% - 1rem + 0.5 * 41%); +} +.system-foundryvtt-reve-de-dragon .calendar-astrologie div.horloge-heure.heure-10 { + top: calc(50% - 1rem + 1 * 41%); + left: calc(50% - 1rem + 0 * 41%); +} +.system-foundryvtt-reve-de-dragon .calendar-astrologie div.horloge-heure.heure-11 { + top: calc(50% - 1rem + 0.8660254 * 41%); + left: calc(50% - 1rem + -0.5 * 41%); +} +.system-foundryvtt-reve-de-dragon .calendar-astrologie div.horloge-heure.heure-12 { + top: calc(50% - 1rem + 0.5 * 41%); + left: calc(50% - 1rem + -0.8660254 * 41%); +} +.system-foundryvtt-reve-de-dragon .calendar-astrologie div.horloge-ajustement.heure-01 { + top: calc(50% - 0.4rem + 0 * 28%); + left: calc(50% - 0.4rem + -1 * 28%); +} +.system-foundryvtt-reve-de-dragon .calendar-astrologie div.horloge-ajustement.heure-02 { + top: calc(50% - 0.4rem + -0.5 * 28%); + left: calc(50% - 0.4rem + -0.8660254 * 28%); +} +.system-foundryvtt-reve-de-dragon .calendar-astrologie div.horloge-ajustement.heure-03 { + top: calc(50% - 0.4rem + -0.8660254 * 28%); + left: calc(50% - 0.4rem + -0.5 * 28%); +} +.system-foundryvtt-reve-de-dragon .calendar-astrologie div.horloge-ajustement.heure-04 { + top: calc(50% - 0.4rem + -1 * 28%); + left: calc(50% - 0.4rem + 0 * 28%); +} +.system-foundryvtt-reve-de-dragon .calendar-astrologie div.horloge-ajustement.heure-05 { + top: calc(50% - 0.4rem + -0.8660254 * 28%); + left: calc(50% - 0.4rem + 0.5 * 28%); +} +.system-foundryvtt-reve-de-dragon .calendar-astrologie div.horloge-ajustement.heure-06 { + top: calc(50% - 0.4rem + -0.5 * 28%); + left: calc(50% - 0.4rem + 0.8660254 * 28%); +} +.system-foundryvtt-reve-de-dragon .calendar-astrologie div.horloge-ajustement.heure-07 { + top: calc(50% - 0.4rem + 0 * 28%); + left: calc(50% - 0.4rem + 1 * 28%); +} +.system-foundryvtt-reve-de-dragon .calendar-astrologie div.horloge-ajustement.heure-08 { + top: calc(50% - 0.4rem + 0.5 * 28%); + left: calc(50% - 0.4rem + 0.8660254 * 28%); +} +.system-foundryvtt-reve-de-dragon .calendar-astrologie div.horloge-ajustement.heure-09 { + top: calc(50% - 0.4rem + 0.8660254 * 28%); + left: calc(50% - 0.4rem + 0.5 * 28%); +} +.system-foundryvtt-reve-de-dragon .calendar-astrologie div.horloge-ajustement.heure-10 { + top: calc(50% - 0.4rem + 1 * 28%); + left: calc(50% - 0.4rem + 0 * 28%); +} +.system-foundryvtt-reve-de-dragon .calendar-astrologie div.horloge-ajustement.heure-11 { + top: calc(50% - 0.4rem + 0.8660254 * 28%); + left: calc(50% - 0.4rem + -0.5 * 28%); +} +.system-foundryvtt-reve-de-dragon .calendar-astrologie div.horloge-ajustement.heure-12 { + top: calc(50% - 0.4rem + 0.5 * 28%); + left: calc(50% - 0.4rem + -0.8660254 * 28%); } .system-foundryvtt-reve-de-dragon .calendar-boutons-heure .calendar-btn:is(.calendar-lyre,.calendar-vaisseau) img { color: hsla(0, 0%, 100%, 0.5); diff --git a/less/colors.less b/less/colors.less index 62f7e94e..6320490a 100644 --- a/less/colors.less +++ b/less/colors.less @@ -12,14 +12,14 @@ /* =================== 3. some constants ============ */ --fieldset-background: url(/ui/parchment.jpg); - --rdd-color-text-primary: rgba(10, 10, 10, 0.9); - --rdd-input-background:rgba(0, 0, 0, 0.05); - --rdd-color-border-input: rgba(0, 0, 0, 0.2); - --rdd-bg-input: rgba(255, 255, 255, 0.1); - --color-controls:rgba(0, 0, 0, 0.9); + --rdd-color-text-primary: hsla(0, 0%, 4%, 0.9); + --rdd-input-background:hsla(0, 0%, 0%, 0.1); + --rdd-color-border-input: hsla(0, 0%, 0%, 0.2); + --rdd-bg-input: hsla(0, 0%, 100%, 0.1); + --color-controls:hsla(0, 0%, 0%, 0.9); --color-controls-light:hsla(0, 0%, 20%, 0.8); --color-controls-hover:hsla(60, 100%, 75%, 0.7); - --color-control-border-hover:rgba(255, 128, 0, 0.8); + --color-control-border-hover:hsla(30, 100%, 50%, 0.8); --color-gold: rgba(191, 149, 63, 0.8); --gradient-gold: linear-gradient(30deg, rgba(191, 149, 63, 0.3), rgba(252, 246, 186, 0.3), rgba(179, 135, 40, 0.3), rgba(251, 245, 183, 0.3), rgba(170, 119, 28, 0.3)); --gradient-silver: linear-gradient(30deg, rgba(61, 55, 93, 0.3), rgba(178, 179, 196, 0.3), rgba(59, 62, 63, 0.6), rgba(206, 204, 199, 0.3), rgba(61, 46, 49, 0.3)); @@ -39,12 +39,20 @@ hsla(50, 100%, 80%, 0.7) ); - --background-custom-button: linear-gradient(to bottom, rgba(33, 55, 74, 0.988) 5%, rgba(21, 40, 51, 0.671) 100%); - --background-custom-button-hover: linear-gradient(to bottom, rgb(128, 0, 0) 5%, rgb(62, 1, 1) 100%); + --background-custom-button: linear-gradient(to bottom, hsla(208, 38%, 21%, 0.988) 5%, hsla(202, 42%, 14%, 0.671) 100%); + --background-custom-button-hover: linear-gradient(to bottom, hsla(0, 100%, 25%, 1) 5%, hsla(0, 97%, 12%, 1) 100%); --background-control-selected: linear-gradient(to bottom, hsla(0, 100%, 25%, 0.5) 5%, hsla(0, 100%, 12%, 0.5) 100%); --background-tooltip: hsla(60, 12%, 85%, 0.95); + --background-tooltip-alt: hsla(60, 12%, 74%, 0.95); --color-tooltip:hsla(282, 47%, 33%, 0.9); --color-tooltip-faint:hsla(282, 47%, 66%, 0.5); --background-error:hsla(16, 100%, 50%, 0.8); --color-profile-border: hsla(0, 0%, 80%, 0.05); + + --color-background-chat-message: hsla(60, 12%, 85%, 0.9); + --color-background-chat-whisper: repeating-linear-gradient(120deg, + hsla(60, 12%, 85%, 0.75), + hsla(60, 12%, 85%, 0.75) 1rem, + hsla(60, 12%, 85%, 0.8) 1rem, + hsla(60, 12%, 85%, 0.75) 1.5rem); } diff --git a/less/fonts.less b/less/fonts.less index 0fc3f995..715c978a 100644 --- a/less/fonts.less +++ b/less/fonts.less @@ -59,6 +59,7 @@ } /* Global styles & Font */ +.application, .window-app { font-family: CaslonAntique; text-align: justify; diff --git a/less/foundryvtt-reve-de-dragon.less b/less/foundryvtt-reve-de-dragon.less index f320e480..479e8a2d 100644 --- a/less/foundryvtt-reve-de-dragon.less +++ b/less/foundryvtt-reve-de-dragon.less @@ -6,11 +6,7 @@ @import "item/monnaie.less"; @import "item/munition.less"; @import "item/tarot.less"; - - // body { - // --input-height: 1.4rem; - // } - + @import "roll-dialog.less"; .window-header{ background: rgba(0,0,0,0.75); } @@ -78,6 +74,17 @@ .sheet-header :is(.header-compteurs,.header-etats,.profile-img, .profile-img-token){ padding: 0 0.4rem; } + .sheet-header div.header-etats { + width: calc(40% - 32px - 1rem); + height: 48px; + max-width: fit-content; + flex: initial; + flex-grow: 3; + div div{ + display: flex; + flex-direction: row; + } + } .sheet-header :is(.profile-img, .profile-img-token) { -webkit-box-flex: 0; @@ -116,13 +123,6 @@ max-width: fit-content; } - .sheet-header div.header-etats { - width: calc(40% - 32px - 1rem); - height: 48px; - max-width: fit-content; - flex: initial; - } - .sheet-header .resource-content { width: 2rem; } @@ -574,51 +574,55 @@ height: 100%; } - .rdd.sheet .window-content {overflow: hidden;} - .rdd.sheet .window-content .sheet-body {overflow-y: scroll;} - .rdd.sheet .window-content .sheet-body .tab {padding-bottom: 30px;} - .rdd.sheet .window-content .sheet-body .competence-list {width: 100%;} - .rdd.sheet .window-content .sheet-body .carac-list { - width: 100%; - flex-grow: 0; - } - - .rdd.sheet .window-content .sheet-body .carac-list .caracteristique { - flex-wrap: nowrap; - justify-content: stretch; - } - .rdd.sheet .window-content .sheet-body .carac-list .caracteristique.streched { - flex-wrap: nowrap; - justify-content: stretch; - flex-basis: 7.5em; - width: max-content; - } - .carac-label { font-weight: bold; flex-basis: 40%; } - .rdd.sheet .window-content .sheet-body .carac-list .caracteristique > .utiliser-attribut { - flex-basis: available; - flex-grow: 1; - } - .rdd.sheet .window-content .sheet-body .carac-list .caracteristique .carac-value { - flex-basis: 15%; - flex-grow: 0; - } - .rdd.sheet .window-content .sheet-body .carac-list .caracteristique .carac-xp { - flex-basis: 13%; - flex-grow: 0; - } - .rdd.sheet .window-content .sheet-body .carac-list .caracteristique .derivee-label { - flex-grow: 1; - } - .rdd.sheet .window-content .sheet-body .carac-list .caracteristique .derivee-value { - flex-grow: 0; - flex-basis: 15%; - margin-right: 0.2rem; - margin-left: 0.2rem; + .rdd.sheet { + .window-content { + overflow: hidden; + .sheet-body { + overflow-y: scroll; + .tab {padding-bottom: 30px;} + .competence-list {width: 100%;} + .carac-list { + width: 100%; + flex-grow: 0; + + .caracteristique.streched { + flex-wrap: nowrap; + justify-content: stretch; + flex-basis: 7.5em; + width: max-content; + } + + .caracteristique { + flex-wrap: nowrap; + justify-content: stretch; + + .carac-value { + flex-basis: 15%; + flex-grow: 0; + } + .carac-xp { + flex-basis: 13%; + flex-grow: 0; + } + .derivee-label { + flex-grow: 1; + } + .derivee-value { + flex-grow: 0; + flex-basis: 15%; + margin-right: 0.2rem; + margin-left: 0.2rem; + } + } + } + } + } } + .flex-grow-1 { flex-grow: 1; } @@ -829,26 +833,25 @@ } .poesie-extrait { + max-height: 8rem; font-size: 0.8rem; font-style: italic; + color: rgba(82, 17, 131, 0.9); + overflow: hidden; } - .poesie-reference{ + + .poesie-extrait:hover { + max-height: unset; + overflow: visible; + opacity: 1; + } + + .poesie-reference { font-size: 0.7rem; text-align: right; } - .poesie-overflow { - color: rgba(82, 17, 131, 0.9); - max-height: 1.5rem; - overflow: hidden; - border-left: 1px dotted black; /* If you want dots under the hoverable text */ - } - .poesie-overflow:hover { - max-height: unset; - overflow: visible; - border-left: 0px - } - .type-compendium{ + .type-compendium { font-size: 0.6rem; } @@ -917,27 +920,26 @@ color: var(--color-text-dark-primary); border-radius: 3px; } - .app-calendar-astrologie div.theme-astral{ - width: 14rem; - margin: 0.4rem; - } - - .app-calendar-astrologie div.horloge-roue { - position: relative; - left: calc(50% - 6.5rem); - width: 13rem; - height: 13rem; - } - - .app-calendar-astrologie div.horloge-roue div.horloge-heure { - position: absolute; - width: 1.8rem; - height: 1.8rem; - } - - .app-calendar-astrologie div.horloge-roue div.horloge-heure img.horloge-heure-img { - width: 2rem; - height: 2rem; + .app-calendar-astrologie{ + div.theme-astral{ + width: 14rem; + margin: 0.4rem; + } + div.horloge-roue { + position: relative; + left: calc(50% - 6.5rem); + width: 13rem; + height: 13rem; + div.horloge-heure { + position: absolute; + width: 1.8rem; + height: 1.8rem; + img.horloge-heure-img { + width: 2rem; + height: 2rem; + } + } + } } .window-app .window-content, .window-app.sheet .window-content .sheet-body{ @@ -1471,27 +1473,29 @@ cursor: pointer; } - - .chat-message h4 { - font-size: 0.9rem; + .chat-message { + background: var(--color-background-chat-message); + + h4 { + font-size: 0.9rem; + } + .message-content { + text-align: justify; + } + header.message-header .heure-rdd { + font-size: 0.7rem; + flex-grow: 3; + } + hr { + margin: 0.2rem 0; + } + } - .chat-message .message-content { - text-align: justify; - } - .chat-message header.message-header .heure-rdd { - font-size: 0.7rem; - flex-grow: 3; - } - .chat-message.whisper { - background: rgba(220,220,210,0.75); + background: var(--color-background-chat-whisper); border: 2px solid #545469; } - .chat-message hr { - margin: 0.2rem 0; - } - .chat-icon { border: 0; padding: 2px 6px 2px 2px; @@ -1515,49 +1519,49 @@ vertical-align: bottom; } - #sidebar-tabs { - flex: 0 0 28px; - box-sizing: border-box; - margin: 0 0 3px; - border-bottom: 1px solid rgba(0,0,0,0); - box-shadow: inset 0 0 2rem rgba(0,0,0,0.5); + #sidebar-tabs menu { + button:is( + [data-tab="chat"], + [data-tab="combat"], + [data-tab="scenes"], + [data-tab="actors"], + [data-tab="items"], + [data-tab="journal"], + [data-tab="cards"], + [data-tab="macros"], + [data-tab="tables"], + [data-tab="playlists"], + [data-tab="compendium"], + [data-tab="settings"] + )::before{ content: none; } + + button { + background-repeat: no-repeat; + } + button[aria-pressed="false"] { + background-color: hsla(30, 37%, 50%, 0.5); + } + button[aria-pressed="true"] { + border: 1px solid hsla(15, 80%, 30%, 1); + background-color: hsla(30, 100%, 90%, 0.6); + box-shadow: 0 0 3px inset hsla(15, 80%, 30%, 1); + } + button[data-tab="chat"] { background-image: url("../assets/ui/icon_sidebar_chat.svg"); } + button[data-tab="combat"] { background-image: url("../assets/ui/icon_sidebar_fight.svg"); } + button[data-tab="scenes"] { background-image: url("../assets/ui/icon_sidebar_scene.svg"); } + button[data-tab="actors"] { background-image: url("../assets/ui/icon_sidebar_actor.svg"); } + button[data-tab="items"] { background-image: url("../assets/ui/icon_sidebar_item.svg"); } + button[data-tab="journal"] { background-image: url("../assets/ui/icon_sidebar_journal.svg"); } + button[data-tab="cards"] { background-image: url("../assets/ui/icon_sidebar_cards.svg"); } + button[data-tab="macros"] { background-image: url("../assets/ui/icon_sidebar_macros.svg"); } + button[data-tab="tables"] { background-image: url("../assets/ui/d100.svg"); } + button[data-tab="playlists"] { background-image: url("../assets/ui/icon_sidebar_music.svg"); } + button[data-tab="compendium"] { background-image: url("../assets/ui/icon_sidebar_compendium.svg"); } + button[data-tab="settings"] { background-image: url("../assets/ui/icon_sidebar_settings.svg"); } } - - #sidebar-tabs > .item.active { - border: 1px solid rgba(114,98,72,1); - background: rgba(30, 25, 20, 0.75); - box-shadow: 0 0 6px inset rgba(114,98,72,1); - } - - #sidebar #sidebar-tabs i{ - width: 23px; - height: 23px; - display: inline-block; - background-position:center; - background-size:cover; - text-shadow: 1px 1px 0 rgba(0,0,0,0.75); - - } - - #sidebar #sidebar-tabs i:is( - .fa-comments, .fa-fist-raised, .fa-swords, - .fa-users, .fa-user, .fa-map, .fa-suitcase, - .fa-book-open, .fa-th-list, .fa-music, - .fa-atlas,.fa-cogs - ):before {content: "";} - #sidebar #sidebar-tabs i.fa-comments {background: url("../assets/ui/icon_sidebar_chat.svg") no-repeat;} - #sidebar #sidebar-tabs i.fa-fist-raised {background: url("../assets/ui/icon_sidebar_fight.svg") no-repeat;} - #sidebar #sidebar-tabs i.fa-swords {background: url("../assets/ui/icon_sidebar_fight.svg") no-repeat;} - #sidebar #sidebar-tabs i.fa-user {background: url("../assets/ui/icon_sidebar_actor.svg") no-repeat;} - #sidebar #sidebar-tabs i.fa-users {background: url("../assets/ui/icon_sidebar_actor.svg") no-repeat;} - #sidebar #sidebar-tabs i.fa-map {background: url("../assets/ui/icon_sidebar_scene.svg") no-repeat;} - #sidebar #sidebar-tabs i.fa-suitcase {background: url("../assets/ui/icon_sidebar_item.svg") no-repeat;} - #sidebar #sidebar-tabs i.fa-book-open {background: url("../assets/ui/icon_sidebar_journal.svg") no-repeat;} - #sidebar #sidebar-tabs i.fa-th-list {background: url("../assets/ui/icon_sidebar_rolltable.svg") no-repeat;} - #sidebar #sidebar-tabs i.fa-music {background: url("../assets/ui/icon_sidebar_music.svg") no-repeat;} - #sidebar #sidebar-tabs i.fa-atlas {background: url("../assets/ui/icon_sidebar_compendium.svg") no-repeat;} - #sidebar #sidebar-tabs i.fa-cogs {background: url("../assets/ui/icon_sidebar_settings.svg") no-repeat;} - + + + // #sidebar #sidebar-tabs i.fa-th-list {background: url("../assets/ui/icon_sidebar_rolltable.svg") no-repeat;} #combat #combat-controls { box-shadow: inset 0 0 2rem rgba(0,0,0,0.5); } @@ -1642,63 +1646,6 @@ border-image-outset: 0px; } - - .window-app.calendar { - display: inline-block; - background: none; - margin: 0; - padding: 0; - box-shadow: none; - pointer-events: none; - max-height: fit-content; - } - - .window-app.calendar header.window-header { - min-width: fit-content; - height: 1.4rem; - pointer-events: all; - } - - .window-app.calendar .window-content { - margin: 0; - padding: 0; - z-index: 100; - flex-direction: column; - min-width: 250px; - height: fit-content; - background: hsla(0, 0%, 0%, 0.0); - font-family: "GoudyAcc"; - pointer-events: none; - } - - .window-app.calendar .window-content div:is(.calendar-boutons-heure, .horloge-digitale) { - pointer-events: all; - } - .window-app.calendar .window-content div.horloge-analogique { - pointer-events: none; - } - - .window-app.calendar .window-content div.horloge-analogique div.horloge-roue { - pointer-events: all; - } - - .window-app.calendar div.horloge-roue { - position: relative; - margin-bottom: 7px; - left: 0; - width: 8rem; - height: 8rem; - } - - .window-app.calendar div.horloge-roue div.horloge-heure { - width: 1.4rem; - height: 1.4rem; - } - .window-app.calendar div.horloge-roue div.horloge-heure img.horloge-heure-img { - width: 1.4rem; - height: 1.4rem; - } - div.horloge-roue div { position: absolute; border: none; @@ -1743,74 +1690,130 @@ div.horloge-roue div img { border: none; } + + + .calendar { + + display: inline-block; + background: none; + margin: 0; + padding: 0; + box-shadow: none; + pointer-events: none; + max-height: fit-content; + + header.window-header { + min-width: fit-content; + height: 1.4rem; + pointer-events: all; + h4 { + font-size: 0.8rem; + } + } + .window-content { + margin: 0; + padding: 0; + z-index: 100; + flex-direction: column; + min-width: 250px; + height: fit-content; + background: hsla(0, 0%, 0%, 0.0); + font-family: "GoudyAcc"; + pointer-events: none; + div.calendar-boutons-heure { + display: grid; + background: hsla(0, 0%, 20%, 1); + color: hsla(0, 0%, 80%, 0.8); + pointer-events: all; + } + div.horloge-digitale { + pointer-events: all; + } + div.horloge-analogique { + pointer-events: none; + } + div.horloge-analogique div.horloge-roue { + pointer-events: all; + } + } + div.horloge-roue { + position: relative; + margin-bottom: 7px; + left: 0; + width: 8rem; + height: 8rem; + div.horloge-heure { + width: 1.4rem; + height: 1.4rem; + img.horloge-heure-img { + width: 1.4rem; + height: 1.4rem; + } + } + } - .window-app.calendar div.horloge-heure.heure-01 { top: calc(50% - 0.7rem + sin(-180deg) *38%); left: calc(50% - 0.7rem + cos(-180deg) *38%); } - .window-app.calendar div.horloge-heure.heure-02 { top: calc(50% - 0.7rem + sin(-150deg) *38%); left: calc(50% - 0.7rem + cos(-150deg) *38%); } - .window-app.calendar div.horloge-heure.heure-03 { top: calc(50% - 0.7rem + sin(-120deg) *38%); left: calc(50% - 0.7rem + cos(-120deg) *38%); } - .window-app.calendar div.horloge-heure.heure-04 { top: calc(50% - 0.7rem + sin(-90deg) *38%); left: calc(50% - 0.7rem + cos(-90deg) *38%); } - .window-app.calendar div.horloge-heure.heure-05 { top: calc(50% - 0.7rem + sin(-60deg) *38%); left: calc(50% - 0.7rem + cos(-60deg) *38%); } - .window-app.calendar div.horloge-heure.heure-06 { top: calc(50% - 0.7rem + sin(-30deg) *38%); left: calc(50% - 0.7rem + cos(-30deg) *38%); } - .window-app.calendar div.horloge-heure.heure-07 { top: calc(50% - 0.7rem + sin(-0deg) *38%); left: calc(50% - 0.7rem + cos(-0deg) *38%); } - .window-app.calendar div.horloge-heure.heure-08 { top: calc(50% - 0.7rem + sin(30deg) *38%); left: calc(50% - 0.7rem + cos(30deg) *38%); } - .window-app.calendar div.horloge-heure.heure-09 { top: calc(50% - 0.7rem + sin(60deg) *38%); left: calc(50% - 0.7rem + cos(60deg) *38%); } - .window-app.calendar div.horloge-heure.heure-10 { top: calc(50% - 0.7rem + sin(90deg) *38%); left: calc(50% - 0.7rem + cos(90deg) *38%); } - .window-app.calendar div.horloge-heure.heure-11 { top: calc(50% - 0.7rem + sin(120deg) *38%); left: calc(50% - 0.7rem + cos(120deg) *38%); } - .window-app.calendar div.horloge-heure.heure-12 { top: calc(50% - 0.7rem + sin(150deg) *38%); left: calc(50% - 0.7rem + cos(150deg) *41%); } - - .window-app.calendar-astrologie div.horloge-heure.heure-01 { top: calc(50% - 1rem + sin(-180deg) *41%); left: calc(50% - 1rem + cos(-180deg) *41%); } - .window-app.calendar-astrologie div.horloge-heure.heure-02 { top: calc(50% - 1rem + sin(-150deg) *41%); left: calc(50% - 1rem + cos(-150deg) *41%); } - .window-app.calendar-astrologie div.horloge-heure.heure-03 { top: calc(50% - 1rem + sin(-120deg) *41%); left: calc(50% - 1rem + cos(-120deg) *41%); } - .window-app.calendar-astrologie div.horloge-heure.heure-04 { top: calc(50% - 1rem + sin(-90deg) *41%); left: calc(50% - 1rem + cos(-90deg) *41%); } - .window-app.calendar-astrologie div.horloge-heure.heure-05 { top: calc(50% - 1rem + sin(-60deg) *41%); left: calc(50% - 1rem + cos(-60deg) *41%); } - .window-app.calendar-astrologie div.horloge-heure.heure-06 { top: calc(50% - 1rem + sin(-30deg) *41%); left: calc(50% - 1rem + cos(-30deg) *41%); } - .window-app.calendar-astrologie div.horloge-heure.heure-07 { top: calc(50% - 1rem + sin(-0deg) *41%); left: calc(50% - 1rem + cos(-0deg) *41%); } - .window-app.calendar-astrologie div.horloge-heure.heure-08 { top: calc(50% - 1rem + sin(30deg) *41%); left: calc(50% - 1rem + cos(30deg) *41%); } - .window-app.calendar-astrologie div.horloge-heure.heure-09 { top: calc(50% - 1rem + sin(60deg) *41%); left: calc(50% - 1rem + cos(60deg) *41%); } - .window-app.calendar-astrologie div.horloge-heure.heure-10 { top: calc(50% - 1rem + sin(90deg) *41%); left: calc(50% - 1rem + cos(90deg) *41%); } - .window-app.calendar-astrologie div.horloge-heure.heure-11 { top: calc(50% - 1rem + sin(120deg) *41%); left: calc(50% - 1rem + cos(120deg) *41%); } - .window-app.calendar-astrologie div.horloge-heure.heure-12 { top: calc(50% - 1rem + sin(150deg) *41%); left: calc(50% - 1rem + cos(150deg) *41%); } - - .window-app.calendar-astrologie div.horloge-ajustement.heure-01 { top: calc(50% - 0.4rem + sin(180deg) * 28%); left: calc(50% - 0.4rem + cos(180deg) * 28%); } - .window-app.calendar-astrologie div.horloge-ajustement.heure-02 { top: calc(50% - 0.4rem + sin(-150deg) * 28%); left: calc(50% - 0.4rem + cos(-150deg) * 28%); } - .window-app.calendar-astrologie div.horloge-ajustement.heure-03 { top: calc(50% - 0.4rem + sin(-120deg) * 28%); left: calc(50% - 0.4rem + cos(-120deg) * 28%); } - .window-app.calendar-astrologie div.horloge-ajustement.heure-04 { top: calc(50% - 0.4rem + sin(-90deg) * 28%); left: calc(50% - 0.4rem + cos(-90deg) * 28%); } - .window-app.calendar-astrologie div.horloge-ajustement.heure-05 { top: calc(50% - 0.4rem + sin(-60deg) * 28%); left: calc(50% - 0.4rem + cos(-60deg) * 28%); } - .window-app.calendar-astrologie div.horloge-ajustement.heure-06 { top: calc(50% - 0.4rem + sin(-30deg) * 28%); left: calc(50% - 0.4rem + cos(-30deg) * 28%); } - .window-app.calendar-astrologie div.horloge-ajustement.heure-07 { top: calc(50% - 0.4rem + sin(0deg) * 28%); left: calc(50% - 0.4rem + cos(0deg) * 28%); } - .window-app.calendar-astrologie div.horloge-ajustement.heure-08 { top: calc(50% - 0.4rem + sin(30deg) * 28%); left: calc(50% - 0.4rem + cos(30deg) * 28%); } - .window-app.calendar-astrologie div.horloge-ajustement.heure-09 { top: calc(50% - 0.4rem + sin(60deg) * 28%); left: calc(50% - 0.4rem + cos(60deg) * 28%); } - .window-app.calendar-astrologie div.horloge-ajustement.heure-10 { top: calc(50% - 0.4rem + sin(90deg) * 28%); left: calc(50% - 0.4rem + cos(90deg) * 28%); } - .window-app.calendar-astrologie div.horloge-ajustement.heure-11 { top: calc(50% - 0.4rem + sin(120deg) * 28%); left: calc(50% - 0.4rem + cos(120deg) * 28%); } - .window-app.calendar-astrologie div.horloge-ajustement.heure-12 { top: calc(50% - 0.4rem + sin(150deg) * 28%); left: calc(50% - 0.4rem + cos(150deg) * 28%); } - - .window-app.calendar header.window-header h4 { - font-size: 0.8rem; + div.horloge-heure.heure-01 { top: calc(50% - 0.7rem + sin(-180deg) *38%); left: calc(50% - 0.7rem + cos(-180deg) *38%); } + div.horloge-heure.heure-02 { top: calc(50% - 0.7rem + sin(-150deg) *38%); left: calc(50% - 0.7rem + cos(-150deg) *38%); } + div.horloge-heure.heure-03 { top: calc(50% - 0.7rem + sin(-120deg) *38%); left: calc(50% - 0.7rem + cos(-120deg) *38%); } + div.horloge-heure.heure-04 { top: calc(50% - 0.7rem + sin(-90deg) *38%); left: calc(50% - 0.7rem + cos(-90deg) *38%); } + div.horloge-heure.heure-05 { top: calc(50% - 0.7rem + sin(-60deg) *38%); left: calc(50% - 0.7rem + cos(-60deg) *38%); } + div.horloge-heure.heure-06 { top: calc(50% - 0.7rem + sin(-30deg) *38%); left: calc(50% - 0.7rem + cos(-30deg) *38%); } + div.horloge-heure.heure-07 { top: calc(50% - 0.7rem + sin(-0deg) *38%); left: calc(50% - 0.7rem + cos(-0deg) *38%); } + div.horloge-heure.heure-08 { top: calc(50% - 0.7rem + sin(30deg) *38%); left: calc(50% - 0.7rem + cos(30deg) *38%); } + div.horloge-heure.heure-09 { top: calc(50% - 0.7rem + sin(60deg) *38%); left: calc(50% - 0.7rem + cos(60deg) *38%); } + div.horloge-heure.heure-10 { top: calc(50% - 0.7rem + sin(90deg) *38%); left: calc(50% - 0.7rem + cos(90deg) *38%); } + div.horloge-heure.heure-11 { top: calc(50% - 0.7rem + sin(120deg) *38%); left: calc(50% - 0.7rem + cos(120deg) *38%); } + div.horloge-heure.heure-12 { top: calc(50% - 0.7rem + sin(150deg) *38%); left: calc(50% - 0.7rem + cos(150deg) *41%); } } - .window-app.calendar section.window-content div.calendar-boutons-heure { - display: grid; - background: hsla(0, 0%, 20%, 1); - color: hsla(0, 0%, 80%, 0.8); + .calendar-astrologie { + div.horloge-heure.heure-01 { top: calc(50% - 1rem + sin(-180deg) *41%); left: calc(50% - 1rem + cos(-180deg) *41%); } + div.horloge-heure.heure-02 { top: calc(50% - 1rem + sin(-150deg) *41%); left: calc(50% - 1rem + cos(-150deg) *41%); } + div.horloge-heure.heure-03 { top: calc(50% - 1rem + sin(-120deg) *41%); left: calc(50% - 1rem + cos(-120deg) *41%); } + div.horloge-heure.heure-04 { top: calc(50% - 1rem + sin(-90deg) *41%); left: calc(50% - 1rem + cos(-90deg) *41%); } + div.horloge-heure.heure-05 { top: calc(50% - 1rem + sin(-60deg) *41%); left: calc(50% - 1rem + cos(-60deg) *41%); } + div.horloge-heure.heure-06 { top: calc(50% - 1rem + sin(-30deg) *41%); left: calc(50% - 1rem + cos(-30deg) *41%); } + div.horloge-heure.heure-07 { top: calc(50% - 1rem + sin(-0deg) *41%); left: calc(50% - 1rem + cos(-0deg) *41%); } + div.horloge-heure.heure-08 { top: calc(50% - 1rem + sin(30deg) *41%); left: calc(50% - 1rem + cos(30deg) *41%); } + div.horloge-heure.heure-09 { top: calc(50% - 1rem + sin(60deg) *41%); left: calc(50% - 1rem + cos(60deg) *41%); } + div.horloge-heure.heure-10 { top: calc(50% - 1rem + sin(90deg) *41%); left: calc(50% - 1rem + cos(90deg) *41%); } + div.horloge-heure.heure-11 { top: calc(50% - 1rem + sin(120deg) *41%); left: calc(50% - 1rem + cos(120deg) *41%); } + div.horloge-heure.heure-12 { top: calc(50% - 1rem + sin(150deg) *41%); left: calc(50% - 1rem + cos(150deg) *41%); } + + div.horloge-ajustement.heure-01 { top: calc(50% - 0.4rem + sin(180deg) * 28%); left: calc(50% - 0.4rem + cos(180deg) * 28%); } + div.horloge-ajustement.heure-02 { top: calc(50% - 0.4rem + sin(-150deg) * 28%); left: calc(50% - 0.4rem + cos(-150deg) * 28%); } + div.horloge-ajustement.heure-03 { top: calc(50% - 0.4rem + sin(-120deg) * 28%); left: calc(50% - 0.4rem + cos(-120deg) * 28%); } + div.horloge-ajustement.heure-04 { top: calc(50% - 0.4rem + sin(-90deg) * 28%); left: calc(50% - 0.4rem + cos(-90deg) * 28%); } + div.horloge-ajustement.heure-05 { top: calc(50% - 0.4rem + sin(-60deg) * 28%); left: calc(50% - 0.4rem + cos(-60deg) * 28%); } + div.horloge-ajustement.heure-06 { top: calc(50% - 0.4rem + sin(-30deg) * 28%); left: calc(50% - 0.4rem + cos(-30deg) * 28%); } + div.horloge-ajustement.heure-07 { top: calc(50% - 0.4rem + sin(0deg) * 28%); left: calc(50% - 0.4rem + cos(0deg) * 28%); } + div.horloge-ajustement.heure-08 { top: calc(50% - 0.4rem + sin(30deg) * 28%); left: calc(50% - 0.4rem + cos(30deg) * 28%); } + div.horloge-ajustement.heure-09 { top: calc(50% - 0.4rem + sin(60deg) * 28%); left: calc(50% - 0.4rem + cos(60deg) * 28%); } + div.horloge-ajustement.heure-10 { top: calc(50% - 0.4rem + sin(90deg) * 28%); left: calc(50% - 0.4rem + cos(90deg) * 28%); } + div.horloge-ajustement.heure-11 { top: calc(50% - 0.4rem + sin(120deg) * 28%); left: calc(50% - 0.4rem + cos(120deg) * 28%); } + div.horloge-ajustement.heure-12 { top: calc(50% - 0.4rem + sin(150deg) * 28%); left: calc(50% - 0.4rem + cos(150deg) * 28%); } + } + + .calendar-boutons-heure { + .calendar-btn:is(.calendar-lyre,.calendar-vaisseau) img { + color: hsla(0, 0%, 100%, 0.5); + border: none; + vertical-align: bottom; + max-width: 1.2em; + max-height: 1.2em; + margin: 1px; + } + i { + border: 1px solid rgba(0, 0, 0, 0); + } + a:hover { + color: var(--color-controls-hover); + border: 1px solid var(--color-control-border-hover); + cursor: pointer; + } } - .calendar-boutons-heure .calendar-btn:is(.calendar-lyre,.calendar-vaisseau) img { - color: hsla(0, 0%, 100%, 0.5); - border: none; - vertical-align: bottom; - max-width: 1.2em; - max-height: 1.2em; - margin: 1px; - } - - .calendar-boutons-heure i { - border: 1px solid rgba(0, 0, 0, 0); - } - .calendar-boutons-heure a:hover { - color: var(--color-controls-hover); - border: 1px solid var(--color-control-border-hover); - cursor: pointer; - } .calendar-1min { grid-column: 1/1; } .calendar-5min { grid-column: 2/2; } @@ -1838,21 +1841,19 @@ background: hsla(0, 0%, 20%, 1); text-align: center; width: 100%; - } - - div.horloge-digitale :is(.calendar-heure-texte,.calendar-minute-texte) { - font-size: 1rem; - pointer-events: all; - margin: 0; - } - - div.horloge-digitale a { - border: 1px solid rgba(0, 0, 0, 0); - } - div.horloge-digitale a:hover { - color: var(--color-controls-hover); - border: 1px solid var(--color-control-border-hover); - cursor: pointer; + :is(.calendar-heure-texte,.calendar-minute-texte) { + font-size: 1rem; + pointer-events: all; + margin: 0; + } + a { + border: 1px solid rgba(0, 0, 0, 0); + } + a:hover { + color: var(--color-controls-hover); + border: 1px solid var(--color-control-border-hover); + cursor: pointer; + } } div.calendar-timestamp-edit select.calendar-signe-heure { diff --git a/less/roll-dialog.less b/less/roll-dialog.less new file mode 100644 index 00000000..891320aa --- /dev/null +++ b/less/roll-dialog.less @@ -0,0 +1,220 @@ +.roll-dialog { + font-family: CaslonAntique; + display: grid; + grid-template-areas: + "header header header header header header header" + "action action action action action action action" + "mode separation separation separation separation separation separation" + "mode carac carac carac comp comp resume" + "mode choix choix choix choix choix modifiers" + "mode resolution resolution resolution resolution resolution modifiers" + "mode chances chances chances chances chances buttons" + "footer footer footer footer footer footer footer"; + grid-template-columns: 2rem 1rem 1fr 1fr 2fr 2fr 3fr; + gap: 0.2rem; + + roll-header { grid-area: header; } + roll-line { grid-area: separation; } + roll-action { grid-area: action; } + roll-carac { grid-area: carac; } + roll-comp { grid-area: comp; } + + roll-choix { grid-area: choix; } + + roll-table { grid-area: resolution; } + roll-conditions { grid-area: modifiers; } + roll-chances { grid-area: chances; } + roll-resume { grid-area: resume; } + roll-buttons { grid-area: buttons; } + + roll-mode { + grid-area: mode; + display: flex; + flex-direction: column; + } + roll-conditions roll-section[name="rollmode"], + roll-mode { + button[data-checked="true"] { + background-color: var(--color-text-selection-bg); + color: var(--color-controls); + i { filter: invert(0.8); } + img { filter: invert(0.2); } + } + button { + height: 1.8rem; + width: 1.8rem; + gap: 0.5rem; + padding: 0.2rem; + background-color: var(--button-background-color); + color: var(--color-controls); + i { filter: invert(0.2); } + img { filter: invert(0.8); } + } + } + + :is(roll-carac, roll-comp) { + display: flex; + flex-direction: row; + align-items: baseline; + } + + roll-section, + roll-section div { + display: flex; + flex-direction: row; + align-items: anchor-center; + margin: 0 0.2rem; + } + + roll-resume { + display: flex; + flex-direction: row; + img.button-effect-img { + filter: invert(0.8); + } + } + + roll-choix roll-section { + display: grid; + grid-template-areas: + "selection selection" + "img roll-part"; + grid-template-columns: 3.2rem 1fr; + gap: 0.2rem; + align-items: start; + + subline { + grid-area: selection; + display: flex; + flex-direction: row; + } + roll-part-img { + display: flex; + flex-direction: column; + grid-area: img; + img{ + border: 0; + padding: 1px; + max-height: 3rem; + max-width: 3rem; + object-fit: contain; + height: 100%; + } + } + roll-part-detail { + grid-area: roll-part; + display: flex; + flex-direction: column; + align-items: normal; + + subline { + display: flex; + flex-direction: row; + div.poesie-extrait{ + display: flex; + flex-direction: column; + } + } + } + } + + roll-section selected-numeric-value { + display: flow; + width: 2.5rem; + text-align: right; + margin: 0 0.2rem 0 0.5rem; + padding: 0 0.2rem; + } + + roll-action { + flex-basis: content; + display: flex; + flex-direction: row; + align-items: center; + border-bottom: 0.2rem solid; + font-size: 1.2rem; + font-weight: bold; + + roll-section { + display: flex; + flex-direction: row; + align-items: center; + + img.action-img { + float: left; + } + img { + max-width: 3rem; + max-height: 3rem; + margin: 0 1rem; + padding: 0; + } + } + } + + roll-conditions { + display: flex; + flex-direction: column; + } + :is(roll-action, roll-carac, roll-comp) roll-section { + flex-basis: content; + } + + :is(roll-choix, roll-conditions, roll-carac, roll-comp) { + select { + max-height: 1.6rem; + margin: 0 0.2rem; + padding: 0 0.2rem; + } + input[type="number"] { + max-height: 1.6rem; + max-width: 2.5rem; + margin: 0 0.2rem; + padding: 0 0.2rem; + } + img { + max-width: 1.8rem; + max-height: 1.8rem; + margin: 0 0.2rem; + padding: 0; + } + } + + roll-carac select[name="select-carac"] { + max-width: 6rem; + + } + roll-comp select[name="select-comp"] { + min-width: 8rem; + max-width: 11rem; + margin-left: 1rem; + } + + roll-conditions roll-section[name="coeur"] select[name="coeur"] { + max-width: 4rem; + } + + roll-conditions roll-section[name="tricher"] img { + /* image de d100 */ + max-width: 2.5rem; + max-height: 2.5rem; + gap: 0; + margin: 0; + padding: 0; + filter: invert(0.8); + } + + roll-buttons { + display: flex; + flex-direction: row-reverse; + } + + roll-table { + table tr :is(th, td) { + padding: 0.1rem; + width: 1.5rem; + text-align: center; + } + } + +} diff --git a/module/actor-sheet.js b/module/actor-sheet.js index f7a43c14..199e250f 100644 --- a/module/actor-sheet.js +++ b/module/actor-sheet.js @@ -1,7 +1,5 @@ import { RdDUtility } from "./rdd-utility.js"; import { HtmlUtility } from "./html-utility.js"; -import { RdDItemArme } from "./item-arme.js"; -import { RdDItemCompetence } from "./item-competence.js"; import { RdDBonus } from "./rdd-bonus.js"; import { Misc } from "./misc.js"; import { RdDCombatManager } from "./rdd-combat.js"; @@ -9,11 +7,12 @@ import { RdDCarac } from "./rdd-carac.js"; import { DialogSplitItem } from "./dialog-split-item.js"; import { ReglesOptionnelles } from "./settings/regles-optionnelles.js"; import { RdDSheetUtility } from "./rdd-sheet-utility.js"; -import { STATUSES } from "./settings/status-effects.js"; import { MAINS_DIRECTRICES } from "./actor.js"; import { RdDBaseActorReveSheet } from "./actor/base-actor-reve-sheet.js"; import { ITEM_TYPES } from "./constants.js"; import { RdDItem } from "./item.js"; +import { RdDItemArme } from "./item/arme.js"; +import { RdDItemCompetence } from "./item-competence.js"; import { RdDItemBlessure } from "./item/blessure.js"; import { RdDEmpoignade } from "./rdd-empoignade.js"; import { RdDBaseActorSangSheet } from "./actor/base-actor-sang-sheet.js"; @@ -34,7 +33,7 @@ export class RdDActorSheet extends RdDBaseActorSangSheet { width: 550, showCompNiveauBase: false, vueArchetype: false, - }, { inplace: false }); + }, { inplace: false }) } /* -------------------------------------------- */ @@ -78,14 +77,10 @@ export class RdDActorSheet extends RdDBaseActorSangSheet { }); // toujours avoir une liste d'armes (pour mettre esquive et corps à corps) - const actor = this.actor; - formData.combat = foundry.utils.duplicate(formData.armes); - RdDItemArme.computeNiveauArmes(formData.combat, formData.competences); - formData.combat.push(RdDItemArme.corpsACorps(actor)); - formData.combat.push(RdDItemArme.empoignade(actor)); + const actor = this.actor formData.esquives = this.actor.getCompetencesEsquive() - formData.combat = RdDCombatManager.listActionsArmes(formData.combat, formData.competences, formData.system.carac); + formData.combat = actor.listActionsAttaque() formData.empoignades = this.actor.getEmpoignades(); this.armesList = formData.combat; @@ -95,7 +90,7 @@ export class RdDActorSheet extends RdDBaseActorSangSheet { formData.difficultesLibres = CONFIG.RDD.difficultesLibres; formData.hautreve = { - isDemiReve: this.actor.getEffect(STATUSES.StatusDemiReve), + isDemiReve: this.actor.isDemiReve(), cacheTMR: this.actor.isTMRCache() } @@ -217,13 +212,17 @@ export class RdDActorSheet extends RdDBaseActorSangSheet { this.html.find('.roll-reve-actuel').click(async event => await this.actor.rollCarac('reve-actuel', { resistance: true })) this.html.find('.action-empoignade').click(async event => await RdDEmpoignade.onAttaqueEmpoignadeFromItem(RdDSheetUtility.getItem(event, this.actor))) - this.html.find('.roll-arme').click(async event => await this.actor.rollArme(foundry.utils.duplicate(this._getEventArmeCombat(event)), 'competence')) + this.html.find('.roll-arme').click(async event => { + const action = this._getActionCombat(event); + await this.actor.rollArme(action.arme, action.main) + + }) // Initiative pour l'arme this.html.find('.roll-init-arme').click(async event => { let combatant = game.combat.combatants.find(c => c.actor.id == this.actor.id) if (combatant) { - RdDCombatManager.rollInitiativeAction(combatant._id, this._getEventArmeCombat(event)); + RdDCombatManager.rollInitiativeAction(combatant._id, this._getActionCombat(event)); } else { ui.notifications.info("Impossible de lancer l'initiative sans être dans un combat."); } @@ -237,11 +236,11 @@ export class RdDActorSheet extends RdDBaseActorSangSheet { this.html.find('.carac-xp-augmenter').click(async event => await this.actor.updateCaracXPAuto(event.currentTarget.name.replace("augmenter.", ""))) this.html.find('.competence-xp-augmenter').click(async event => await this.actor.updateCompetenceXPAuto(RdDSheetUtility.getItemId(event))) - this.html.find('.competence-stress-augmenter').click(async event =>{ + this.html.find('.competence-stress-augmenter').click(async event => { await this.actor.updateCompetenceStress(RdDSheetUtility.getItemId(event)) this.render(true) - } - ) + } + ) if (this.options.vueDetaillee) { // On carac change @@ -347,13 +346,17 @@ export class RdDActorSheet extends RdDBaseActorSangSheet { await this.actor.createItem('tache', 'Nouvelle tache'); } - _getEventArmeCombat(event) { + _getActionCombat(event) { const li = this.html.find(event.currentTarget)?.parents(".item"); let armeName = li.data("arme-name"); let compName = li.data('competence-name'); - const arme = this.armesList.find(a => a.name == armeName && a.system.competence == compName); + const arme = this.armesList.find(a => a.arme.name == armeName && a.comp.name == compName); if (!arme) { - return { name: armeName, system: { competence: compName } }; + return { + name: armeName, + arme: { name: armeName }, + comp: { name: compName } + } } return arme; } diff --git a/module/actor-token.mjs b/module/actor-token.mjs new file mode 100644 index 00000000..f61371c1 --- /dev/null +++ b/module/actor-token.mjs @@ -0,0 +1,40 @@ +/** + * class providing the actor and token, and choosing the name and image from the token if available. + */ +export class ActorToken { + + static fromActorId(actorId, onError = () => undefined) { + actorId = actorId ?? (canvas.tokens.controlled.length > 0 + ? canvas.tokens.controlled[0].actor.id + : undefined) + const actor = actorId ? game.actors.get(actorId) : undefined + if (actor) { + return this.fromActor(actor) + } + return onError() + } + + static fromActor(actor) { + const token = actor.isToken ? actor.token : actor.prototypeToken + return ActorToken.fromToken(token) + } + + static fromTokenId(tokenId, sceneId = undefined) { + const tokensList = sceneId ? game.scenes.get(sceneId).tokens : canvas.tokens.placeables + const token = tokensList.get(tokenId) + return ActorToken.fromToken(token) + } + + static fromToken(token) { + return new ActorToken(token) + } + + constructor(token) { + this.name = token.name ?? token.actor.name + this.img = token.texture.src ?? token.actor.img + this.actor = token.actor + this.id = token.actor?.id + this.token = token + this.tokenId = token?.id + } +} \ No newline at end of file diff --git a/module/actor.js b/module/actor.js index 4735abd1..2d8181fa 100644 --- a/module/actor.js +++ b/module/actor.js @@ -8,12 +8,9 @@ import { RdDResolutionTable } from "./rdd-resolution-table.js"; import { RdDDice } from "./rdd-dice.js"; import { RdDRollTables } from "./rdd-rolltables.js"; import { ChatUtility } from "./chat-utility.js"; -import { RdDItemSort } from "./item-sort.js"; import { Grammar } from "./grammar.js"; -import { RdDItemCompetence } from "./item-competence.js"; import { RdDAlchimie } from "./rdd-alchimie.js"; import { STATUSES } from "./settings/status-effects.js"; -import { RdDItemSigneDraconique } from "./item/signedraconique.js"; import { ReglesOptionnelles } from "./settings/regles-optionnelles.js"; import { EffetsDraconiques } from "./tmr/effets-draconiques.js"; import { Draconique } from "./tmr/draconique.js"; @@ -22,13 +19,11 @@ import { DialogConsommer } from "./dialog-item-consommer.js"; import { DialogFabriquerPotion } from "./dialog-fabriquer-potion.js"; import { RollDataAjustements } from "./rolldata-ajustements.js"; import { RdDPossession } from "./rdd-possession.js"; -import { SHOW_DICE, SYSTEM_RDD, SYSTEM_SOCKET_ID } from "./constants.js"; +import { ACTOR_TYPES, SHOW_DICE, SYSTEM_RDD, SYSTEM_SOCKET_ID } from "./constants.js"; import { RdDConfirm } from "./rdd-confirm.js"; -import { RdDRencontre } from "./item/rencontre.js"; import { DialogRepos } from "./sommeil/dialog-repos.js"; import { RdDBaseActor } from "./actor/base-actor.js"; import { RdDTimestamp } from "./time/rdd-timestamp.js"; -import { RdDItemBlessure } from "./item/blessure.js"; import { AppAstrologie } from "./sommeil/app-astrologie.js"; import { RdDEmpoignade } from "./rdd-empoignade.js"; import { ExperienceLog, XP_TOPIC } from "./actor/experience-log.js"; @@ -36,13 +31,23 @@ import { ITEM_TYPES } from "./constants.js"; import { RdDBaseActorSang } from "./actor/base-actor-sang.js"; import { RdDCoeur } from "./coeur/rdd-coeur.js"; import { DialogChoixXpCarac } from "./dialog-choix-xp-carac.js"; -import { RdDItemArme } from "./item-arme.js"; import { RdDCombatManager } from "./rdd-combat.js"; + +import { RdDItemArme } from "./item/arme.js"; +import { RdDItemBlessure } from "./item/blessure.js"; import { RdDItemTete } from "./item/tete.js"; +import { RdDItemSort } from "./item-sort.js"; +import { RdDItemRace } from "./item/race.js"; +import { RdDItemCompetence } from "./item-competence.js"; +import { RdDItemSigneDraconique } from "./item/signedraconique.js"; +import { RdDRencontre } from "./item/rencontre.js"; import { DialogSelect } from "./dialog-select.js"; import { PAS_DE_DRACONIC, POSSESSION_SANS_DRACONIC } from "./item/base-items.js"; -import { RdDItemRace } from "./item/race.js"; + import { RdDRollResult } from "./rdd-roll-result.js"; +import { RdDInitiative } from "./initiative.mjs"; +import RollDialog from "./roll/roll-dialog.mjs"; +import { OptionsAvancees, ROLL_DIALOG_V2 } from "./settings/options-avancees.js"; export const MAINS_DIRECTRICES = ['Droitier', 'Gaucher', 'Ambidextre'] @@ -105,7 +110,7 @@ export class RdDActor extends RdDBaseActorSang { return Number.isNumeric(this.system.compteurs.chance.value) ? Misc.toInt(this.system.compteurs.chance.value) : this.getChance() } - getMoralTotal() { return this.system.compteurs.moral?.value ?? 0 } + getMoralTotal() { return parseInt(this.system.compteurs.moral?.value ?? 0) } getEnduranceMax() { return Math.max(1, Math.max(this.getTaille() + this.getConstitution(), this.getVieMax() + this.getVolonte())) } @@ -113,8 +118,10 @@ export class RdDActor extends RdDBaseActorSang { getEtatGeneral(options = { ethylisme: false }) { const etatGeneral = this.system.compteurs.etat?.value ?? 0 // Pour les jets d'Ethylisme, on retire le malus d'éthylisme (p.162) - const annuleMalusEthylisme = options.ethylisme ? this.malusEthylisme() : 0 - return etatGeneral - annuleMalusEthylisme + if (options.ethylisme) { + return etatGeneral - this.malusEthylisme() + } + return etatGeneral } /* -------------------------------------------- */ @@ -129,19 +136,13 @@ export class RdDActor extends RdDBaseActorSang { .reduce(Misc.sum(), 0); } - listActionsCombat() { + listActions({ isAttaque = false, isEquipe = false }) { // Recupération des armes - const actions = RdDCombatManager.listActionsArmes( - this.itemTypes[ITEM_TYPES.arme] - .filter(it => RdDItemArme.isAttaque(it)) - .concat(RdDItemArme.corpsACorps(this)) - .concat(RdDItemArme.empoignade(this)) - , - this.itemTypes[ITEM_TYPES.competence], - this.system.carac) + const actions = this.listActionsAttaque() + .filter(it => !isEquipe || it.arme.system.equipe) - if (this.system.attributs.hautrevant.value) { - actions.push({ name: "Draconic", action: 'haut-reve', system: { initOnly: true, competence: "Draconic" } }); + if (!isAttaque && this.system.attributs.hautrevant.value) { + actions.push({ name: "Draconic", action: 'haut-reve', initOnly: true }) } return actions } @@ -166,6 +167,86 @@ export class RdDActor extends RdDBaseActorSang { .find(it => true) } + /* -------------------------------------------- */ + /** Retourne une liste triée d'actions d'armes avec le split arme1 main / arme 2 main / lancer */ + listActionsAttaque() { + let actions = [ + this.$prepareAttaqueArme(RdDItemArme.empoignade(this)), + this.$prepareAttaqueArme(RdDItemArme.corpsACorps(this)), + ] + + const armes = this.itemTypes[ITEM_TYPES.arme] + .filter(it => RdDItemArme.isAttaque(it)) + .sort(Misc.ascending(it => it.name)); + + for (const arme of armes) { + if (arme.system.unemain && arme.system.competence) { + actions.push(this.$prepareAttaqueArme(arme, '(1 main)')) + } + if (arme.system.deuxmains && arme.system.competence) { + actions.push(this.$prepareAttaqueArme(arme, '(2 mains)')) + } + if (arme.system.lancer) { + actions.push(this.$prepareAttaqueArme(arme, '(lancer)')) + } + if (arme.system.tir) { + actions.push(this.$prepareAttaqueArme(arme, '(tir)')) + } + } + return actions; + } + + /* -------------------------------------------- */ + $prepareAttaqueArme(arme, main) { + const comp = this.getCompetence(RdDActor.$getCompetenceAction(arme, main)) + const caracCode = RdDActor.$getCaracAction(comp, main) + const caracValue = this.system.carac[caracCode].value + const dommages = arme.system.dommages.toString() + + // TODO: déplacer sur RdDItemArme + if (arme.system.unemain && arme.system.deuxmains && !dommages.includes("/")) { + ui.notifications.info(`Les dommages de l'arme à 1/2 mains ${arme.name} ne sont pas corrects (ie sous la forme X/Y)`) + } + const tableauDommages = dommages.includes("/") ? dommages.split("/") : [dommages, dommages] + const dmg = main == '(2 mains)' ? tableauDommages[1] : tableauDommages[0] + const niveau = comp?.system.niveau ?? (['(lancer)', '(tir)'].includes(main) ? -8 : -6) + const ajustement = (arme.parent?.getEtatGeneral() ?? 0) + (arme.system.magique) ? arme.system.ecaille_efficacite : 0 + + return { + name: arme.name + (main ? ' ' + main : ''), + action: 'attaque', + initOnly: false, + arme: arme, + comp: comp, + main: main, + carac: { key: caracCode, value: caracValue }, + equipe: arme.system.equipe, + dmg: dmg, + initiative: RdDInitiative.calculInitiative(niveau, caracValue, ajustement) + } + } + + static $getCaracAction(comp, main) { + if (comp?.system.defaut_carac) { + return comp.system.defaut_carac + } + switch (main) { + case '(lancer)': return 'lancer' + case '(tir)': return 'tir' + default: return 'melee' + } + } + + static $getCompetenceAction(arme, main) { + switch (main) { + case '(1 main)': return arme.competence1Mains() + case '(2 mains)': return arme.competence2Mains() + case '(lancer)': return arme.system.lancer + case '(tir)': return arme.system.tir + default: return arme.system.competence + } + } + /* -------------------------------------------- */ async $perteReveEnchantementsChateauDormants() { const toUpdate = this.items.filter(it => [ITEM_TYPES.potion, ITEM_TYPES.gemme].includes(it.type)) @@ -673,7 +754,7 @@ export class RdDActor extends RdDBaseActorSang { if (to > Misc.toInt(this.system.reve.seuil.value)) { updates[`system.reve.seuil.value`] = to; // SFA : Direct and packed changes //this.setPointsDeSeuil(to); - } + } } if (caracName == LIST_CARAC_PERSONNAGE.chance.code) { if (to > Misc.toInt(this.system.compteurs.chance.value)) { @@ -895,8 +976,14 @@ export class RdDActor extends RdDBaseActorSang { } /* -------------------------------------------- */ + ethylisme() { + return this.system.compteurs.ethylisme?.value ?? 1; + } malusEthylisme() { - return Math.min(0, (this.system.compteurs.ethylisme?.value ?? 0)); + return Math.min(0, this.ethylisme()) + } + isAlcoolise() { + return this.ethylisme() < 1 } @@ -1482,7 +1569,7 @@ export class RdDActor extends RdDBaseActorSang { /* -------------------------------------------- */ createCallbackAppelAuMoral() { /* Si l'appel au moral est utilisé, on l'affiche dans le chat et on diminue éventuellement le moral */ return { - action: r => this._appliquerAppelMoral(r) + action: r => this.appliquerAppelMoral(r) }; } @@ -1578,7 +1665,7 @@ export class RdDActor extends RdDBaseActorSang { } /* -------------------------------------------- */ - async _appliquerAppelMoral(rollData) { + async appliquerAppelMoral(rollData) { if (!rollData.use.moral || game.settings.get("core", "rollMode") == 'selfroll') { return } @@ -1591,20 +1678,7 @@ export class RdDActor extends RdDBaseActorSang { /* -------------------------------------------- */ $filterSortList(sortList, coord) { - let tmr = TMRUtility.getTMR(coord); - let filtered = [] - for (let sort of sortList) { - if (sort.system.caseTMR.toLowerCase().includes('variable')) { - filtered.push(sort); - } else if (sort.system.caseTMRspeciale.toLowerCase().includes('variable')) { - filtered.push(sort); - } else if (sort.system.caseTMR.toLowerCase() == tmr.type) { - filtered.push(sort); - } else if (sort.system.caseTMR.toLowerCase().includes('special') && sort.system.caseTMRspeciale.toLowerCase().includes(coord.toLowerCase())) { - filtered.push(sort); - } - } - return filtered; + return sortList.filter(it => RdDItemSort.isSortOnCoord(it, coord)) } /* -------------------------------------------- */ @@ -1630,8 +1704,8 @@ export class RdDActor extends RdDBaseActorSang { ui.notifications.error("Une queue ou un souffle vous empèche de lancer de sort!") return } - // Duplication car les pts de reve sont modifiés dans le sort - let sorts = foundry.utils.duplicate(this.$filterSortList(this.itemTypes['sort'], coord)); + // Duplication car les pts de reve sont modifiés dans le sort! + let sorts = foundry.utils.duplicate(this.itemTypes[ITEM_TYPES.sort].filter(it => RdDItemSort.isSortOnCoord(it, coord))) if (sorts.length == 0) { ui.notifications.info(`Aucun sort disponible en ${TMRUtility.getTMR(coord).label} !`); return; @@ -1919,153 +1993,6 @@ export class RdDActor extends RdDBaseActorSang { } } - /* -------------------------------------------- */ - async _rollArt(artData, selected, oeuvre, callbackAction = async r => await this._resultArt(r)) { - oeuvre.system.niveau = oeuvre.system.niveau ?? 0; - foundry.utils.mergeObject(artData, - { - oeuvre: oeuvre, - art: oeuvre.type, - competence: foundry.utils.duplicate(this.getCompetence(artData.compName ?? oeuvre.system.competence ?? artData.art)), - diffLibre: - oeuvre.system.niveau, - diffConditions: 0, - use: { libre: false, conditions: true, surenc: false }, - selectedCarac: foundry.utils.duplicate(this.system.carac[selected]) - }, - { overwrite: false }); - artData.competence.system.defaut_carac = selected; - if (!artData.forceCarac) { - artData.forceCarac = {}; - artData.forceCarac[selected] = foundry.utils.duplicate(this.system.carac[selected]); - } - - await this.openRollDialog({ - name: `jet-${artData.art}`, - label: `${artData.verbe} ${oeuvre.name}`, - template: `systems/foundryvtt-reve-de-dragon/templates/dialog-roll-${oeuvre.type}.hbs`, - rollData: artData, - callbacks: [{ action: callbackAction }], - }) - } - - /* -------------------------------------------- */ - async _resultArt(artData) { - const niveau = artData.oeuvre.system.niveau ?? 0; - const baseQualite = (artData.rolled.isSuccess ? niveau : artData.competence.system.niveau); - artData.qualiteFinale = Math.min(baseQualite, niveau) + artData.rolled.ptQualite; - - await RdDRollResult.displayRollData(artData, this.name, `chat-resultat-${artData.art}.hbs`); - } - - /* -------------------------------------------- */ - async rollChant(id) { - const artData = { art: 'chant', verbe: 'Chanter' }; - const oeuvre = foundry.utils.duplicate(this.getChant(id)); - await this._rollArt(artData, "ouie", oeuvre); - } - - /* -------------------------------------------- */ - async rollDanse(id) { - const artData = { art: 'danse', verbe: 'Danser', forceCarac: {} }; - const oeuvre = foundry.utils.duplicate(this.findItemLike(id, artData.art)); - if (oeuvre.system.agilite) { - artData.forceCarac['agilite'] = foundry.utils.duplicate(this.system.carac.agilite); - } - if (oeuvre.system.apparence) { - artData.forceCarac['apparence'] = foundry.utils.duplicate(this.system.carac.apparence); - } - const selectedCarac = this._getCaracDanse(oeuvre); - await this._rollArt(artData, selectedCarac, oeuvre); - } - - /* -------------------------------------------- */ - _getCaracDanse(oeuvre) { - if (oeuvre.system.agilite) { return "agilite"; } - else if (oeuvre.system.apparence) { return "apparence"; } - const compData = this.getCompetence(oeuvre.system.competence); - return compData.system.defaut_carac; - } - - /* -------------------------------------------- */ - async rollMusique(id) { - const artData = { art: 'musique', verbe: 'Jouer' }; - const oeuvre = this.findItemLike(id, artData.art); - await this._rollArt(artData, "ouie", oeuvre); - } - - /* -------------------------------------------- */ - async rollRecetteCuisine(id) { - const oeuvre = this.getRecetteCuisine(id); - const artData = { - verbe: 'Cuisiner', - compName: 'cuisine', - proportions: 1, - ajouterEquipement: false - }; - await this._rollArt(artData, 'odoratgout', oeuvre, r => this._resultRecetteCuisine(r)); - } - - /* -------------------------------------------- */ - async _resultRecetteCuisine(cuisine) { - const niveauRecette = cuisine.oeuvre.system.niveau ?? 0; - const baseQualite = (cuisine.rolled.isSuccess ? niveauRecette : cuisine.competence.system.niveau); - cuisine.qualiteFinale = Math.min(baseQualite, niveauRecette) + cuisine.rolled.ptQualite; - cuisine.exotismeFinal = Math.min(Math.min(cuisine.qualiteFinale, cuisine.oeuvre.system.exotisme ?? 0), 0); - cuisine.sust = cuisine.oeuvre.system.sust * Math.min(cuisine.proportions, cuisine.proportionsMax ?? cuisine.proportions) - const platCuisine = { - name: cuisine.oeuvre.name, - type: 'nourritureboisson', - img: 'systems/foundryvtt-reve-de-dragon/icons/objets/provision_cuite.webp', - system: { - "description": cuisine.oeuvre.system.description, - "sust": 1, - "qualite": cuisine.qualiteFinale, - "exotisme": cuisine.exotismeFinal, - "encombrement": 0.1, - "quantite": Math.max(1, Math.floor(cuisine.sust)), - "cout": Math.max(cuisine.qualiteFinale) * 0.01 - } - } - if (cuisine.ajouterEquipement) { - await this.createEmbeddedDocuments('Item', [platCuisine]); - ui.notifications.info(`${platCuisine.system.quantite} rations de ${platCuisine.name} ont été ajoutés à votre équipement`); - } - cuisine.platCuisine = platCuisine; - await RdDRollResult.displayRollData(cuisine, this.name, `chat-resultat-${cuisine.art}.hbs`); - } - - async preparerNourriture(item) { - if (item.getUtilisationCuisine() == 'brut') { - const nourriture = { - name: 'Plat de ' + item.name, - type: 'recettecuisine', - img: item.img, - system: { - sust: item.system.sust, - exotisme: item.system.exotisme, - ingredients: item.name - } - }; - const artData = { - verbe: 'Préparer', - compName: 'cuisine', - proportions: 1, - proportionsMax: Math.min(50, item.system.quantite), - ajouterEquipement: true - }; - await this._rollArt(artData, 'odoratgout', nourriture, async (cuisine) => { - await this._resultRecetteCuisine(cuisine); - const remaining = Math.max(item.system.quantite - cuisine.proportions, 0); - if (remaining > 0) { - await item.update({ 'system.quantite': remaining }) - } - else { - await this.deleteEmbeddedDocuments('Item', [item.id]); - } - }); - } - } - /* -------------------------------------------- */ async rollJeu(id) { const oeuvre = this.getJeu(id); @@ -2081,14 +2008,9 @@ export class RdDActor extends RdDBaseActorSang { listCarac.forEach(c => artData.forceCarac[c] = this.system.carac[c]); artData.competence.system.niveauReel = artData.competence.system.niveau; artData.competence.system.niveau = Math.max(artData.competence.system.niveau, oeuvre.system.base); - await this._rollArt(artData, carac, oeuvre); + await this._rollArtV1(artData, carac, oeuvre); } - async rollOeuvre(id) { - const artData = { art: 'oeuvre', verbe: 'Interpréter' } - const oeuvre = foundry.utils.duplicate(this.findItemLike(id, artData.art)) - await this._rollArt(artData, oeuvre.system.default_carac, oeuvre) - } /* -------------------------------------------- */ async rollMeditation(id) { @@ -2122,7 +2044,7 @@ export class RdDActor extends RdDBaseActorSang { if (meditationRoll.rolled.isSuccess) { await this.createEmbeddedDocuments("Item", [RdDItemSigneDraconique.prepareSigneDraconiqueMeditation(meditationRoll.meditation, meditationRoll.rolled)]); } - if (meditationRoll.rolled.isEPart){ + if (meditationRoll.rolled.isEPart) { await this.updateEmbeddedDocuments('Item', [{ _id: meditationRoll.meditation._id, 'system.malus': meditationRoll.meditation.system.malus - 1 }]); } await this.santeIncDec("fatigue", 2); @@ -2432,7 +2354,7 @@ export class RdDActor extends RdDBaseActorSang { this.tmrApp.forceTMRDisplay() return } - if (mode != 'visu' && this.getEffect(STATUSES.StatusDemiReve)) { + if (mode != 'visu' && this.isDemiReve()) { ui.notifications.warn("Les personnage est déjà dans les Terres Médianes, elles s'affichent en visualisation") mode = "visu"; // bascule le mode en visu automatiquement } @@ -2712,8 +2634,11 @@ export class RdDActor extends RdDBaseActorSang { listeSuivants(filter = suivant => true) { return RdDActor.$buildSubActorLinks( - this.system.subacteurs.suivants.filter(filter), RdDActor.$transformSubActeurSuivant - ) + this.system.subacteurs.suivants, RdDActor.$transformSubActeurSuivant + ).filter(filter) + } + listeAmoureux() { + return this.listeSuivants(it => it.coeur > 0 && it.type == ACTOR_TYPES.personnage) } getSuivant(subActorId) { @@ -3123,5 +3048,204 @@ export class RdDActor extends RdDBaseActorSang { await incarnation.remiseANeuf(); incarnation.sheet.render(true); } + + + /* -------------------------------------------- */ + async _rollArtV2(oeuvreId, callbackAction = async (actor, rd) => await actor._resultArtV2(rd)) { + const oeuvre = this.items.get(oeuvreId) + const rollData = { + title: `Interpretation de ${oeuvre.name} par ${this.name}`, + mode: { + allowed: ["oeuvre"] + }, + selected: { + mode: "oeuvre", + oeuvre: { key: oeuvre.id }, + }, + ids: { + actorId: this.id + } + } + await RollDialog.create(rollData, { + onRoll: (dialog) => { + this._onCloseRollDialog(), + dialog.close() + }, + customChatMessage: true, + callbacks: [callbackAction] + }) + } + + async _resultArtV2(artData) { + const niveau = artData.oeuvre.system.niveau ?? 0; + const baseQualite = (artData.rolled.isSuccess ? niveau : artData.competence.system.niveau); + artData.qualiteFinale = Math.min(baseQualite, niveau) + artData.rolled.ptQualite; + + await RdDRollResult.displayRollData(artData, this.name, `chat-resultat-${artData.art}.hbs`); + } + /* -------------------------------------------- */ + + async rollOeuvre(id) { + if (OptionsAvancees.isUsing(ROLL_DIALOG_V2)) { + return await this._rollArtV2(id) + } + else { + const artData = { art: 'oeuvre', verbe: 'Interpréter' } + const oeuvre = foundry.utils.duplicate(this.findItemLike(id, artData.art)) + await this._rollArtV1(artData, oeuvre.system.default_carac, oeuvre) + } + } + + async rollChant(id) { + if (OptionsAvancees.isUsing(ROLL_DIALOG_V2)) { + await this._rollArtV2(id) + } + else { + const artData = { art: 'chant', verbe: 'Chanter' } + const oeuvre = foundry.utils.duplicate(this.getChant(id)) + await this._rollArtV1(artData, "ouie", oeuvre) + } + } + + async rollDanse(id) { + if (OptionsAvancees.isUsing(ROLL_DIALOG_V2)) { + await this._rollArtV2(id) + } + else { + const artData = { art: 'danse', verbe: 'Danser', forceCarac: {} } + const oeuvre = foundry.utils.duplicate(this.findItemLike(id, artData.art)) + let selectedCarac = this.getCompetence(oeuvre.system.competence)?.system.defaut_carac + if (oeuvre.system.apparence) { + artData.forceCarac['apparence'] = foundry.utils.duplicate(this.system.carac.apparence) + selectedCarac = "apparence" + } + if (oeuvre.system.agilite) { + artData.forceCarac['agilite'] = foundry.utils.duplicate(this.system.carac.agilite) + selectedCarac = "agilite" + } + await this._rollArtV1(artData, selectedCarac, oeuvre) + } + } + + async rollMusique(id) { + if (OptionsAvancees.isUsing(ROLL_DIALOG_V2)) { + await this._rollArtV2(id) + } + else { + const artData = { art: 'musique', verbe: 'Jouer' } + const oeuvre = this.findItemLike(id, artData.art) + await this._rollArtV1(artData, "ouie", oeuvre) + } + } + + /* -------------------------------------------- */ + async _rollArtV1(artData, selected, oeuvre, callbackAction = async r => await this._resultArt(r)) { + oeuvre.system.niveau = oeuvre.system.niveau ?? 0; + foundry.utils.mergeObject(artData, + { + oeuvre: oeuvre, + art: oeuvre.type, + competence: foundry.utils.duplicate(this.getCompetence(artData.compName ?? oeuvre.system.competence ?? artData.art)), + diffLibre: - oeuvre.system.niveau, + diffConditions: 0, + use: { libre: false, conditions: true, surenc: false }, + selectedCarac: foundry.utils.duplicate(this.system.carac[selected]) + }, + { overwrite: false }); + artData.competence.system.defaut_carac = selected; + if (!artData.forceCarac) { + artData.forceCarac = {}; + artData.forceCarac[selected] = foundry.utils.duplicate(this.system.carac[selected]); + } + + await this.openRollDialog({ + name: `jet-${artData.art}`, + label: `${artData.verbe} ${oeuvre.name}`, + template: `systems/foundryvtt-reve-de-dragon/templates/dialog-roll-${oeuvre.type}.hbs`, + rollData: artData, + callbacks: [{ action: callbackAction }], + }) + } + + async _resultArt(artData) { + const niveau = artData.oeuvre.system.niveau ?? 0; + const baseQualite = (artData.rolled.isSuccess ? niveau : artData.competence.system.niveau); + artData.qualiteFinale = Math.min(baseQualite, niveau) + artData.rolled.ptQualite; + + await RdDRollResult.displayRollData(artData, this.name, `chat-resultat-${artData.art}.hbs`); + } + + /* -------------------------------------------- */ + async rollRecetteCuisine(id) { + const oeuvre = this.getRecetteCuisine(id); + const artData = { + verbe: 'Cuisiner', + compName: 'cuisine', + proportions: 1, + ajouterEquipement: false + }; + await this._rollArtV1(artData, 'odoratgout', oeuvre, r => this._resultRecetteCuisine(r)); + } + + /* -------------------------------------------- */ + async _resultRecetteCuisine(cuisine) { + const niveauRecette = cuisine.oeuvre.system.niveau ?? 0; + const baseQualite = (cuisine.rolled.isSuccess ? niveauRecette : cuisine.competence.system.niveau); + cuisine.qualiteFinale = Math.min(baseQualite, niveauRecette) + cuisine.rolled.ptQualite; + cuisine.exotismeFinal = Math.min(Math.min(cuisine.qualiteFinale, cuisine.oeuvre.system.exotisme ?? 0), 0); + cuisine.sust = cuisine.oeuvre.system.sust * Math.min(cuisine.proportions, cuisine.proportionsMax ?? cuisine.proportions) + const platCuisine = { + name: cuisine.oeuvre.name, + type: 'nourritureboisson', + img: 'systems/foundryvtt-reve-de-dragon/icons/objets/provision_cuite.webp', + system: { + "description": cuisine.oeuvre.system.description, + "sust": 1, + "qualite": cuisine.qualiteFinale, + "exotisme": cuisine.exotismeFinal, + "encombrement": 0.1, + "quantite": Math.max(1, Math.floor(cuisine.sust)), + "cout": Math.max(cuisine.qualiteFinale) * 0.01 + } + } + if (cuisine.ajouterEquipement) { + await this.createEmbeddedDocuments('Item', [platCuisine]); + ui.notifications.info(`${platCuisine.system.quantite} rations de ${platCuisine.name} ont été ajoutés à votre équipement`); + } + cuisine.platCuisine = platCuisine; + await RdDRollResult.displayRollData(cuisine, this.name, `chat-resultat-${cuisine.art}.hbs`); + } + + async preparerNourriture(item) { + if (item.getUtilisationCuisine() == 'brut') { + const nourriture = { + name: 'Plat de ' + item.name, + type: 'recettecuisine', + img: item.img, + system: { + sust: item.system.sust, + exotisme: item.system.exotisme, + ingredients: item.name + } + }; + const artData = { + verbe: 'Préparer', + compName: 'cuisine', + proportions: 1, + proportionsMax: Math.min(50, item.system.quantite), + ajouterEquipement: true + }; + await this._rollArtV1(artData, 'odoratgout', nourriture, async (cuisine) => { + await this._resultRecetteCuisine(cuisine); + const remaining = Math.max(item.system.quantite - cuisine.proportions, 0); + if (remaining > 0) { + await item.update({ 'system.quantite': remaining }) + } + else { + await this.deleteEmbeddedDocuments('Item', [item.id]); + } + }); + } + } } diff --git a/module/actor/base-actor-reve.js b/module/actor/base-actor-reve.js index 9e3133b3..b3311ec5 100644 --- a/module/actor/base-actor-reve.js +++ b/module/actor/base-actor-reve.js @@ -8,15 +8,16 @@ import { RdDUtility } from "../rdd-utility.js"; import { ReglesOptionnelles } from "../settings/regles-optionnelles.js"; import { RdDBaseActor } from "./base-actor.js"; import { ITEM_TYPES } from "../constants.js"; -import { RdDItemCompetence } from "../item-competence.js"; -import { RdDItemCompetenceCreature } from "../item-competencecreature.js"; -import { RdDItemArme } from "../item-arme.js"; -import { StatusEffects } from "../settings/status-effects.js"; +import { StatusEffects, STATUSES } from "../settings/status-effects.js"; import { Targets } from "../targets.js"; import { RdDConfirm } from "../rdd-confirm.js"; import { RdDCarac } from "../rdd-carac.js"; import { RdDRollResult } from "../rdd-roll-result.js"; +import { RdDItemCompetence } from "../item-competence.js"; +import { RdDItemCompetenceCreature } from "../item-competencecreature.js"; +import { RdDItemArme } from "../item/arme.js"; + import { ChatUtility } from "../chat-utility.js"; import { DialogValidationEncaissement } from "../dialog-validation-encaissement.js"; import { RdDCombat } from "../rdd-combat.js"; @@ -79,6 +80,7 @@ export class RdDBaseActorReve extends RdDBaseActor { getBonusDegat() { return RdDCarac.getCaracDerivee(this.getEncombrementMax()).plusdom } getMoralTotal() { return 0 } + listeAmoureux() {return []} getProtectionNaturelle() { return Number(this.system.attributs?.protection?.value ?? 0) } getSConst() { return 0 } @@ -108,10 +110,11 @@ export class RdDBaseActorReve extends RdDBaseActor { } return this.system.carac[competence.system.defaut_carac].value; } - listActionsCombat() { + + listActions({ isAttaque = false, isEquipe = false }) { return this.itemTypes[ITEM_TYPES.competencecreature] .filter(it => RdDItemCompetenceCreature.isAttaque(it)) - .map(it => RdDItemCompetenceCreature.armeCreature(it)) + .map(it => RdDItemCompetenceCreature.attaqueCreature(it)) .filter(it => it != undefined); } @@ -164,11 +167,16 @@ export class RdDBaseActorReve extends RdDBaseActor { if (idOrName instanceof Item) { return idOrName.isCompetence() ? idOrName : undefined } - return RdDItemCompetence.findCompetence(this.items, idOrName, options) + return RdDItemCompetence.findCompetence( + this.items.filter(it => [ITEM_TYPES.competence, ITEM_TYPES.competencecreature].includes(it.type)), + idOrName, options) } - getCompetences(name, options = { onMessage: message => { } }) { - return RdDItemCompetence.findCompetences(this.items, name, options) + getCompetences(name = undefined, options = { onMessage: message => { } }) { + if (name == undefined) { + return this.itemTypes[ITEM_TYPES.competence] + } + return RdDItemCompetence.findCompetences(this.itemTypes[ITEM_TYPES.competence], name, options) } getCompetenceCorpsACorps(options = { onMessage: message => { } }) { @@ -218,27 +226,24 @@ export class RdDBaseActorReve extends RdDBaseActor { return this.getEmbeddedCollection("ActiveEffect").filter(filter); } - getEffect(effectId) { - return this.getEmbeddedCollection("ActiveEffect").find(it => it.statuses?.has(effectId)); + getEffectByStatus(statusId) { + return this.getEffects().find(it => it.statuses.has(statusId)); } - async setEffect(effectId, status) { - if (this.isEffectAllowed(effectId)) { - const effect = this.getEffect(effectId); + async setEffect(statusId, status) { + if (this.isEffectAllowed(statusId)) { + const effect = this.getEffectByStatus(statusId); if (!status && effect) { await this.deleteEmbeddedDocuments('ActiveEffect', [effect.id]); } if (status && !effect) { - await this.createEmbeddedDocuments("ActiveEffect", [StatusEffects.prepareActiveEffect(effectId)]); + await this.createEmbeddedDocuments("ActiveEffect", [StatusEffects.prepareActiveEffect(statusId)]); } } } async removeEffect(id) { - const effect = this.getEmbeddedCollection("ActiveEffect").find(it => it.id == id); - if (effect) { - await this.deleteEmbeddedDocuments('ActiveEffect', [id]); - } + this.removeEffects(it => it.id == id) } async removeEffects(filter = e => true) { @@ -249,17 +254,16 @@ export class RdDBaseActorReve extends RdDBaseActor { } /* -------------------------------------------- */ + isDemiReve() { + return this.getEffectByStatus(STATUSES.StatusDemiReve) != undefined + } + getSurprise(isCombat = undefined) { - let niveauSurprise = this.getEffects() - .map(effect => StatusEffects.valeurSurprise(effect, isCombat)) - .reduce(Misc.sum(), 0); - if (niveauSurprise > 1) { - return 'totale'; - } - if (niveauSurprise == 1) { - return 'demi'; - } - return ''; + return StatusEffects.typeSurprise( + this.getEffects() + .map(it => StatusEffects.niveauSurprise(it, isCombat)) + .reduce(Misc.sum(), 0) + ) } /* -------------------------------------------- */ @@ -277,8 +281,20 @@ export class RdDBaseActorReve extends RdDBaseActor { return dialog } - createCallbackExperience() { return { action: r => { } } } - createCallbackAppelAuMoral() { return { action: r => { } } } + + /* -------------------------------------------- */ + createCallbackExperience() { + return { action: r => this.appliquerAjoutExperience(r) } + } + + /* -------------------------------------------- */ + createCallbackAppelAuMoral() { + /* Si l'appel au moral est utilisé, on l'affiche dans le chat et on diminue éventuellement le moral */ + return { action: r => this.appliquerAppelMoral(r) } + } + + async appliquerAjoutExperience(rollData, hideChatMessage = 'show') { } + async appliquerAppelMoral(rollData) { } async _onCloseRollDialog(html) { } @@ -433,9 +449,9 @@ export class RdDBaseActorReve extends RdDBaseActor { * @param {*} categorieArme catégorie d'attaque à utiliser: competence (== melee), lancer, tir; naturelle, possession * @returns */ - rollArme(arme, categorieArme, token) { + rollArme(arme, categorieArme = 'competence', token = undefined) { token = token ?? RdDUtility.getSelectedToken(this) - const compToUse = this.$getCompetenceArme(arme, categorieArme) + const compToUse = RdDItemArme.getCompetenceArme(arme, categorieArme) if (!RdDItemArme.isUtilisable(arme)) { ui.notifications.warn(`Arme inutilisable: ${arme.name} a une résistance de 0 ou moins`) return @@ -469,10 +485,6 @@ export class RdDBaseActorReve extends RdDBaseActor { }) } - $getCompetenceArme(arme, competenceName) { - return RdDItemArme.getCompetenceArme(arme, competenceName) - } - verifierForceMin(item) { } /* -------------------------------------------- */ diff --git a/module/actor/base-actor-sang.js b/module/actor/base-actor-sang.js index 4a021a7f..c80ba39d 100644 --- a/module/actor/base-actor-sang.js +++ b/module/actor/base-actor-sang.js @@ -292,7 +292,7 @@ export class RdDBaseActorSang extends RdDBaseActorReve { } isSonne() { - return this.getEffect(STATUSES.StatusStunned) + return this.getEffectByStatus(STATUSES.StatusStunned) } isEffectAllowed(effectId) { return true } diff --git a/module/actor/base-actor.js b/module/actor/base-actor.js index 3bd2ff97..20c67f70 100644 --- a/module/actor/base-actor.js +++ b/module/actor/base-actor.js @@ -27,6 +27,7 @@ export class RdDBaseActor extends Actor { } return entry && entry.length > 0 ? carac[entry[0]] : undefined; } + static getDefaultValue(actorType, path) { if (path.includes('.')) { path = path.split('.') @@ -743,6 +744,7 @@ export class RdDBaseActor extends Actor { ui.notifications.info(`${this.getAlias()} ne peut pas faire cette action: ${action}`) } + isAlcoolise() { return false } async jetEthylisme() { this.actionImpossible("jet d'éthylisme") } async rollAppelChance() { this.actionImpossible("appel à la chance") } async jetDeMoral() { this.actionImpossible("jet de moral") } @@ -755,18 +757,21 @@ export class RdDBaseActor extends Actor { isActorCombat() { return false } getCaracInit(competence) { return 0 } - listActionsCombat() { return [] } + listAttaques() { + return this.listActions({ isAttaque: true, isEquipe:false }) + } + + listActions({ isAttaque = false, isEquipe=false }) { return [] } + listActionsPossessions() { return this.itemTypes[ITEM_TYPES.possession] .map(p => { return { name: p.name, action: 'possession', - system: { - competence: p.name, - possessionid: p.system.possessionid, - } + possessionid: p.system.possessionid, } }) } + } \ No newline at end of file diff --git a/module/actor/export-scriptarium/actor-encart-sheet.js b/module/actor/export-scriptarium/actor-encart-sheet.js index 9728bed0..a8971a79 100644 --- a/module/actor/export-scriptarium/actor-encart-sheet.js +++ b/module/actor/export-scriptarium/actor-encart-sheet.js @@ -9,6 +9,7 @@ export class RdDActorExportSheet extends RdDActorSheet { static init() { foundry.applications.handlebars.loadTemplates([ "systems/foundryvtt-reve-de-dragon/templates/actor/export-scriptarium/arme.hbs", + "systems/foundryvtt-reve-de-dragon/templates/actor/export-scriptarium/arme-titre.hbs", "systems/foundryvtt-reve-de-dragon/templates/actor/export-scriptarium/blessure.hbs", "systems/foundryvtt-reve-de-dragon/templates/actor/export-scriptarium/blessures.hbs", "systems/foundryvtt-reve-de-dragon/templates/actor/export-scriptarium/carac.hbs", @@ -41,6 +42,7 @@ export class RdDActorExportSheet extends RdDActorSheet { const formData = await super.getData() // Add any structured, precomputed list of data formData.context = Mapping.prepareContext(this.actor) + formData.attaques = this.actor.listActionsAttaque() formData.export = this.getMappingValues(formData.context, this.actor) formData.competences = this.getCompetences(CATEGORIES_COMPETENCES) formData.draconic = this.getCompetences(CATEGORIES_DRACONIC) diff --git a/module/actor/export-scriptarium/mapping.js b/module/actor/export-scriptarium/mapping.js index 0d79ebf1..05b1237d 100644 --- a/module/actor/export-scriptarium/mapping.js +++ b/module/actor/export-scriptarium/mapping.js @@ -1,5 +1,5 @@ import { Grammar } from "../../grammar.js" -import { RdDItemArme } from "../../item-arme.js" +import { RdDItemArme } from "../../item/arme.js" import { RdDItemCompetence } from "../../item-competence.js" import { RdDItemSort } from "../../item-sort.js" import { ITEM_TYPES } from "../../constants.js" @@ -144,7 +144,7 @@ export class Mapping { armes.push(RdDItemArme.corpsACorps(actor)); armes.push(RdDItemArme.empoignade(actor)); return armes.map(arme => [ - arme.system.unemain ? Mapping.prepareArme(actor, arme, 'unemain') : undefined, + arme.system.unemain ? Mapping.prepareArme(actor, arme, '(1 main)') : undefined, arme.system.deuxmains ? Mapping.prepareArme(actor, arme, 'deuxmains') : undefined, !(arme.system.unemain || arme.system.deuxmains) ? Mapping.prepareArme(actor, arme, 'competence') : undefined, arme.system.lancer != "" ? Mapping.prepareArme(actor, arme, 'lancer') : undefined, @@ -256,8 +256,8 @@ export class Mapping { static descriptionSort(sort) { const ptSeuil = Array(sort.system.coutseuil).map(it => '*') const caseTMR = sort.system.caseTMRspeciale.length > 0 ? Mapping.toVar(sort.system.caseTMRspeciale) : Misc.upperFirst(TMRType[sort.system.caseTMR].name) - const coutReve = 'r' + RdDItemSort.addSpaceToNonNumeric(sort.system.ptreve) - const diff = 'R' + RdDItemSort.addSpaceToNonNumeric(sort.system.difficulte) + const coutReve =RdDItemSort.coutReve(sort) + const diff = RdDItemSort.diffReve(sort) return `${sort.name}${ptSeuil} (${caseTMR}) ${diff} ${coutReve}` } static toVar(caseSpeciale) { diff --git a/module/chat-utility.js b/module/chat-utility.js index e19c4a24..83a749c1 100644 --- a/module/chat-utility.js +++ b/module/chat-utility.js @@ -83,8 +83,8 @@ export class ChatUtility { } /* -------------------------------------------- */ - static async createChatWithRollMode(messageData, actor = undefined) { - switch (game.settings.get("core", "rollMode")) { + static async createChatWithRollMode(messageData, actor = undefined, rollMode = game.settings.get("core", "rollMode")) { + switch (rollMode) { case "blindroll": // GM only if (!game.user.isGM) { ChatUtility.blindMessageToGM(messageData) diff --git a/module/grammar.js b/module/grammar.js index b80edf37..2550fd43 100644 --- a/module/grammar.js +++ b/module/grammar.js @@ -22,7 +22,7 @@ export class Grammar { static equalsInsensitive(a, b) { return Grammar.toLowerCaseNoAccent(a) == Grammar.toLowerCaseNoAccent(b) } - + static includesLowerCaseNoAccent(value, content) { return Grammar.toLowerCaseNoAccent(value)?.includes(Grammar.toLowerCaseNoAccent(content)); } diff --git a/module/initiative.mjs b/module/initiative.mjs new file mode 100644 index 00000000..eedf6978 --- /dev/null +++ b/module/initiative.mjs @@ -0,0 +1,7 @@ +export class RdDInitiative { + + static calculInitiative(niveau, caracValue, bonus = 0) { + let base = niveau + Math.floor(caracValue / 2) + bonus; + return "1d6" + (base >= 0 ? "+" : "") + base; + } +} diff --git a/module/item-competence.js b/module/item-competence.js index 708efb52..68d07bdd 100644 --- a/module/item-competence.js +++ b/module/item-competence.js @@ -103,8 +103,8 @@ export class RdDItemCompetence extends Item { } /* -------------------------------------------- */ - static isMalusEncombrementTotal(competence) { - return competence?.name.toLowerCase().match(/(natation|acrobatie)/) || 0; + static isMalusEncombrementTotal(competenceName) { + return competenceName?.toLowerCase().match(/(natation|acrobatie)/) || 0; } /* -------------------------------------------- */ diff --git a/module/item-competencecreature.js b/module/item-competencecreature.js index 53d510f6..6beb4ac3 100644 --- a/module/item-competencecreature.js +++ b/module/item-competencecreature.js @@ -1,7 +1,8 @@ import { ITEM_TYPES } from "./constants.js"; import { Grammar } from "./grammar.js"; -import { RdDCombatManager } from "./rdd-combat.js"; +import { RdDInitiative } from "./initiative.mjs"; +import { RdDItem } from "./item.js"; export const CATEGORIES_COMPETENCES_CREATURES = { "generale": { base: 0, label: "Générale" }, @@ -37,7 +38,7 @@ export class RdDItemCompetenceCreature extends Item { competence: item.name, cac: categorieAttaque == "naturelle" ? "naturelle" : "", niveau: item.system.niveau, - initiative: RdDCombatManager.calculInitiative(item.system.niveau, item.system.carac_value), + initiative: RdDInitiative.calculInitiative(item.system.niveau, item.system.carac_value), equipe: true, resistance: 100, dommagesReels: item.system.dommages, @@ -49,6 +50,41 @@ export class RdDItemCompetenceCreature extends Item { } return undefined; } + static attaqueCreature(comp) { + const categorieAttaque = RdDItemCompetenceCreature.getCategorieAttaque(comp) + if (categorieAttaque != undefined) { + const initative = RdDInitiative.calculInitiative(comp.system.niveau, comp.system.carac_value); + return { + name: comp.name, + action: comp.isCompetencePossession() ? 'possession' : 'attaque', + initOnly: false, + arme: new RdDItem({ + name: comp.name, + type: ITEM_TYPES.arme, + img: comp.img, + system: { + competence: comp.name, + cac: categorieAttaque == "naturelle" ? "naturelle" : "", + niveau: comp.system.niveau, + initiative: initative, + equipe: true, + resistance: 100, + dommagesReels: comp.system.dommages, + penetration: 0, + force: 0, + rapide: true, + } + }), + comp: comp, + // main: '', + carac: { key: comp.name, value: comp.system.carac_value }, + equipe: true, + dmg: comp.system.dommages, + initiative: initative + } + } + return undefined; + } /* -------------------------------------------- */ static isAttaque(item) { diff --git a/module/item-sort.js b/module/item-sort.js index 2ceed69f..ec4f5cdd 100644 --- a/module/item-sort.js +++ b/module/item-sort.js @@ -16,13 +16,10 @@ export const VOIES_DRACONIC = [ /* -------------------------------------------- */ export class RdDItemSort extends Item { static preloadHandlebars() { - Handlebars.registerHelper('itemSort-spaceIfText', val => RdDItemSort.addSpaceToNonNumeric(val)) Handlebars.registerHelper('itemSort-codeDraconic', voie => RdDItemSort.getCode(voie)) Handlebars.registerHelper('itemSort-shortDraconic', voie => RdDItemSort.getShortVoie(voie)) - } - - static addSpaceToNonNumeric(value) { - return Number.isNumeric(value) || ['-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'].includes(String(value).charAt[0]) ? value : ' ' + RdDItemSort.toVar(value) + Handlebars.registerHelper('itemSort-diffReve', sort => RdDItemSort.diffReve(sort)) + Handlebars.registerHelper('itemSort-coutReve', sort => RdDItemSort.coutReve(sort)) } static lancements(sort) { return sort?.system.lancements.length ?? 0 } @@ -43,21 +40,44 @@ export class RdDItemSort extends Item { return value ? value.replace('variable', 'var') : '' } + static isSortOnCoord(sort, coord) { + let tmr = TMRUtility.getTMR(coord) + const caseTMR = sort.system.caseTMR.toLowerCase(); + const caseTMRspeciale = sort.system.caseTMRspeciale.toLowerCase(); + + return caseTMR.includes('variable') + || caseTMRspeciale.includes('variable') + || (caseTMR == tmr.type) + || (caseTMR.includes('special') && caseTMRspeciale.includes(coord.toLowerCase())) + } + + static getCaseTMR(sort) { + const caseTMR = sort.system.caseTMR.toLowerCase(); + const caseTMRspeciale = sort.system.caseTMRspeciale.toLowerCase(); + if (caseTMR.includes('variable') || caseTMRspeciale.includes('variable') || caseTMR.includes('special')) { + return sort.system.caseTMRspeciale + } + return sort.system.caseTMR + } + + + static diffReve(sort) { return RdDItemSort.toVar((sort.system.difficulte.match(/\-?(\d)+/) ? 'R' : 'R ') + sort.system.difficulte) } + static coutReve(sort) { return RdDItemSort.toVar((sort.system.ptreve.match(/(\d)+\+?/) ? 'r' : 'r ') + sort.system.ptreve) } static getDraconicsSort(competencesDraconic, sort) { // se baser sur la voie du sort? switch (Grammar.toLowerCaseNoAccent(sort.name)) { case "lecture d'aura": case "detection d'aura": - return competencesDraconic; + return competencesDraconic case "annulation de magie": - return competencesDraconic.filter(it => !RdDItemCompetence.isThanatos(it)); + return competencesDraconic.filter(it => !RdDItemCompetence.isThanatos(it)) } const voies = sort.system.draconic.split('/') return voies.map(voie => RdDItemCompetence.getVoieDraconic(competencesDraconic, voie)) } static getBestDraconicSort(competencesDraconic, sort) { - return RdDItemSort.getDraconicsSort(competencesDraconic, sort).sort(Misc.descending(it => it.system.niveau)).find(it=>true) + return RdDItemSort.getDraconicsSort(competencesDraconic, sort).sort(Misc.descending(it => it.system.niveau)).find(it => true) } static getOrdreCode(code) { diff --git a/module/item.js b/module/item.js index ec5df5fb..64a01f3f 100644 --- a/module/item.js +++ b/module/item.js @@ -1,7 +1,8 @@ import { ITEM_TYPES } from "./constants.js"; -import { DialogItemVente } from "./achat-vente/dialog-item-vente.js"; + import { Grammar } from "./grammar.js"; import { Misc } from "./misc.js"; +import { DialogItemVente } from "./achat-vente/dialog-item-vente.js"; import { RdDTimestamp } from "./time/rdd-timestamp.js"; import { RdDUtility } from "./rdd-utility.js"; import { SystemCompendiums } from "./settings/system-compendiums.js"; @@ -84,6 +85,7 @@ export const defaultItemImg = { /* -------------------------------------------- */ export class RdDItem extends Item { + static get defaultIcon() { return undefined; } @@ -95,12 +97,12 @@ export class RdDItem extends Item { static isFieldInventaireModifiable(type, field) { switch (field) { case 'quantite': - if ([ITEM_TYPES.conteneur].includes(type)) { + if (ITEM_TYPES.conteneur == type) { return false; } break; case 'cout': - if ([ITEM_TYPES.monnaie].includes(type)) { + if (ITEM_TYPES.monnaie == type) { return game.user.isGM; } break; diff --git a/module/item-arme.js b/module/item/arme.js similarity index 83% rename from module/item-arme.js rename to module/item/arme.js index de6eb939..ed36fbb2 100644 --- a/module/item-arme.js +++ b/module/item/arme.js @@ -1,8 +1,10 @@ -import { Grammar } from "./grammar.js"; -import { RdDItemCompetenceCreature } from "./item-competencecreature.js" -import { ITEM_TYPES } from "./constants.js"; -import { BASE_CORPS_A_CORPS } from "./item/base-items.js"; -import { RdDCombatManager } from "./rdd-combat.js"; +import { ITEM_TYPES } from "../constants.js"; +import { RdDItem } from "../item.js"; + +import { RdDItemCompetenceCreature } from "../item-competencecreature.js" +import { BASE_CORPS_A_CORPS } from "./base-items.js"; +import { Grammar } from "../grammar.js"; +import { RdDInitiative } from "../initiative.mjs"; const nomCategorieParade = { "sans-armes": "Sans arme", @@ -19,13 +21,17 @@ const nomCategorieParade = { } /* -------------------------------------------- */ -export class RdDItemArme extends Item { +export class RdDItemArme extends RdDItem { - static isArme(item) { - return item.type == ITEM_TYPES.arme || RdDItemCompetenceCreature.getCategorieAttaque(item); + static get ITEM_TYPE() { return ITEM_TYPES.arme } + + static get defaultIcon() { + return defaultItemImgArme + //return "systems/foundryvtt-reve-de-dragon/icons/armes_armure/epee_sord.webp"; } /* -------------------------------------------- */ + static getArme(arme) { switch (arme ? arme.type : '') { case ITEM_TYPES.arme: return arme; @@ -42,10 +48,10 @@ export class RdDItemArme extends Item { case ITEM_TYPES.arme: switch (maniement) { case 'competence': return arme.system.competence; - case 'unemain': return RdDItemArme.competence1Mains(arme); - case 'deuxmains': return RdDItemArme.competence2Mains(arme); - case 'tir': return arme.system.tir; - case 'lancer': return arme.system.lancer; + case '(1 main)': return arme.competence1Mains() + case '(2 mains)': return arme.competence2Mains() + case '(tir)': case 'tir': return arme.system.tir + case '(lancer)': case 'lancer': return arme.system.lancer; } } return undefined @@ -186,7 +192,7 @@ export class RdDItemArme extends Item { return Number(arme.system.dommages) } const tableauDegats = arme.system.dommages.split("/"); - return Number(tableauDegats[maniement == 'unemain' ? 0 : 1]) + return Number(tableauDegats[maniement == '(1 main)' ? 0 : 1]) } return Number(arme.system.dommages); } @@ -195,17 +201,17 @@ export class RdDItemArme extends Item { static armeUneOuDeuxMains(arme, aUneMain) { if (arme && !arme.system.cac) { arme = foundry.utils.duplicate(arme); - arme.system.dommagesReels = RdDItemArme.dommagesReels(arme, aUneMain ? 'unemain' : 'deuxmains') + arme.system.dommagesReels = RdDItemArme.dommagesReels(arme, aUneMain ? '(1 main)' : '(2 mains)') } return arme; } - static competence1Mains(arme) { - return arme.system.competence.replace(" 2 mains", " 1 main"); + competence1Mains() { + return this.system.competence.replace(" 2 mains", " 1 main"); } - static competence2Mains(arme) { - return arme.system.competence.replace(" 1 main", " 2 mains"); + competence2Mains() { + return this.system.competence.replace(" 1 main", " 2 mains"); } static isUtilisable(arme) { @@ -218,10 +224,8 @@ export class RdDItemArme extends Item { static isAttaque(arme) { switch (arme.type) { - case ITEM_TYPES.arme: - return arme.system.equipe && (arme.system.resistance > 0 || arme.system.portee_courte > 0) - case ITEM_TYPES.competencecreature: - return arme.system.iscombat && RdDItemCompetenceCreature.isAttaque(item) + case ITEM_TYPES.arme: return arme.system.equipe && (arme.system.resistance > 0 || arme.system.portee_courte > 0) + case ITEM_TYPES.competencecreature: return arme.system.iscombat && RdDItemCompetenceCreature.isAttaque(item) } return false } @@ -229,23 +233,24 @@ export class RdDItemArme extends Item { static isParade(arme) { switch (arme.type) { case ITEM_TYPES.arme: - return arme.system.equipe && arme.system.resistance > 0 && true/* TODO: regarder la categorie d'arme?*/ + return arme.system.resistance > 0 && true/* TODO: regarder la categorie d'arme?*/ case ITEM_TYPES.competencecreature: return arme.system.iscombat && RdDItemCompetenceCreature.isParade(arme) } return false } + static corpsACorps(actor) { let competence = actor?.getCompetenceCorpsACorps() ?? BASE_CORPS_A_CORPS let melee = actor ? actor.system.carac['melee'].value : 0 - return { + return new RdDItemArme({ _id: competence.id, name: 'Corps à corps', type: ITEM_TYPES.arme, img: competence.img, system: { - initiative: RdDCombatManager.calculInitiative(competence.system.niveau, melee), + initiative: RdDInitiative.calculInitiative(competence.system.niveau, melee), equipe: true, rapide: true, force: 0, @@ -259,7 +264,7 @@ export class RdDItemArme extends Item { deuxmains: true, categorie_parade: 'sans-armes' } - } + }) } static mainsNues(actor) { diff --git a/module/item/item-actions.js b/module/item/item-actions.js index 1d2954a9..52aac7b3 100644 --- a/module/item/item-actions.js +++ b/module/item/item-actions.js @@ -3,8 +3,6 @@ import { Misc } from "../misc.js" import { RdDSheetUtility } from "../rdd-sheet-utility.js" import { RdDUtility } from "../rdd-utility.js" - - /** * TODO: * options.editable ? diff --git a/module/item/sheet-base-inventaire.js b/module/item/sheet-base-inventaire.js index 1952c56d..a465d79a 100644 --- a/module/item/sheet-base-inventaire.js +++ b/module/item/sheet-base-inventaire.js @@ -5,7 +5,7 @@ import { RdDRaretes } from "./raretes.js"; const TYPE_ITEMS_NATURELS = ["faune", "herbe", "plante", "ingredient"]; -export class RdDItemInventaireSheet extends RdDItemSheetV1 { +export class RdDInventaireItemSheet extends RdDItemSheetV1 { static get defaultOptions() { return foundry.utils.mergeObject(RdDItemSheetV1.defaultOptions, { diff --git a/module/item/sheet-conteneur.js b/module/item/sheet-conteneur.js index 46966473..4fd3e5e3 100644 --- a/module/item/sheet-conteneur.js +++ b/module/item/sheet-conteneur.js @@ -2,9 +2,9 @@ import { RdDBaseActorSheet } from "../actor/base-actor-sheet.js"; import { ITEM_TYPES } from "../constants.js"; import { RdDSheetUtility } from "../rdd-sheet-utility.js"; import { RdDUtility } from "../rdd-utility.js"; -import { RdDItemInventaireSheet } from "./sheet-base-inventaire.js"; +import { RdDInventaireItemSheet } from "./sheet-base-inventaire.js"; -export class RdDConteneurItemSheet extends RdDItemInventaireSheet { +export class RdDConteneurItemSheet extends RdDInventaireItemSheet { static get ITEM_TYPE() { return ITEM_TYPES.conteneur }; diff --git a/module/item/sheet-faune.js b/module/item/sheet-faune.js index 33eb8225..cf6b2b1e 100644 --- a/module/item/sheet-faune.js +++ b/module/item/sheet-faune.js @@ -1,6 +1,6 @@ -import { RdDItemInventaireSheet } from "./sheet-base-inventaire.js"; +import { RdDInventaireItemSheet } from "./sheet-base-inventaire.js"; -export class RdDFauneItemSheet extends RdDItemInventaireSheet { +export class RdDFauneItemSheet extends RdDInventaireItemSheet { static get ITEM_TYPE() { return "faune" }; diff --git a/module/item/sheet-gemme.js b/module/item/sheet-gemme.js index 297a88bd..3a303121 100644 --- a/module/item/sheet-gemme.js +++ b/module/item/sheet-gemme.js @@ -2,9 +2,9 @@ import { ITEM_TYPES } from "../constants.js"; import { DialogEnchanter } from "../enchantement/dialog-enchanter.js"; import { RdDTimestamp } from "../time/rdd-timestamp.js"; import { RdDItemGemme } from "./gemme.js"; -import { RdDItemInventaireSheet } from "./sheet-base-inventaire.js"; +import { RdDInventaireItemSheet } from "./sheet-base-inventaire.js"; -export class RdDGemmeItemSheet extends RdDItemInventaireSheet { +export class RdDGemmeItemSheet extends RdDInventaireItemSheet { static get ITEM_TYPE() { return ITEM_TYPES.gemme }; diff --git a/module/item/sheet-herbe.js b/module/item/sheet-herbe.js index d4c412bb..9167cbe8 100644 --- a/module/item/sheet-herbe.js +++ b/module/item/sheet-herbe.js @@ -1,6 +1,6 @@ -import { RdDItemInventaireSheet } from "./sheet-base-inventaire.js"; +import { RdDInventaireItemSheet } from "./sheet-base-inventaire.js"; -export class RdDHerbeItemSheet extends RdDItemInventaireSheet { +export class RdDHerbeItemSheet extends RdDInventaireItemSheet { static get ITEM_TYPE() { return "herbe" }; } diff --git a/module/item/sheet-ingredient.js b/module/item/sheet-ingredient.js index 0c39caac..ba542f40 100644 --- a/module/item/sheet-ingredient.js +++ b/module/item/sheet-ingredient.js @@ -1,5 +1,5 @@ -import { RdDItemInventaireSheet } from "./sheet-base-inventaire.js"; +import { RdDInventaireItemSheet } from "./sheet-base-inventaire.js"; -export class RdDIngredientItemSheet extends RdDItemInventaireSheet { +export class RdDIngredientItemSheet extends RdDInventaireItemSheet { static get ITEM_TYPE() { return "ingredient" }; } diff --git a/module/item/sheet-plante.js b/module/item/sheet-plante.js index 2a6f10fa..db981bee 100644 --- a/module/item/sheet-plante.js +++ b/module/item/sheet-plante.js @@ -1,6 +1,6 @@ -import { RdDItemInventaireSheet } from "./sheet-base-inventaire.js"; +import { RdDInventaireItemSheet } from "./sheet-base-inventaire.js"; -export class RdDPlanteItemSheet extends RdDItemInventaireSheet { +export class RdDPlanteItemSheet extends RdDInventaireItemSheet { static get ITEM_TYPE() { return "plante" }; diff --git a/module/item/sheet-potion.js b/module/item/sheet-potion.js index e9f27e75..ad7c03af 100644 --- a/module/item/sheet-potion.js +++ b/module/item/sheet-potion.js @@ -2,10 +2,10 @@ import { ITEM_TYPES } from "../constants.js"; import { DialogEnchanter } from "../enchantement/dialog-enchanter.js"; import { RdDTimestamp } from "../time/rdd-timestamp.js"; import { RdDItemPotion } from "./potion.js"; -import { RdDItemInventaireSheet } from "./sheet-base-inventaire.js"; +import { RdDInventaireItemSheet } from "./sheet-base-inventaire.js"; -export class RdDPotionItemSheet extends RdDItemInventaireSheet { +export class RdDPotionItemSheet extends RdDInventaireItemSheet { static get ITEM_TYPE() { return ITEM_TYPES.potion }; diff --git a/module/migrations.js b/module/migrations.js index 208b667a..c09421b6 100644 --- a/module/migrations.js +++ b/module/migrations.js @@ -8,7 +8,6 @@ import { RdDTimestamp } from "./time/rdd-timestamp.js"; import { RdDRaretes } from "./item/raretes.js"; import { VOIES_DRACONIC } from "./item-sort.js"; import { SystemCompendiums } from "./settings/system-compendiums.js"; -import { Misc } from "./misc.js"; class Migration { get code() { return "sample"; } @@ -248,7 +247,7 @@ class _10_2_10_DesirLancinant_IdeeFixe extends Migration { await this.applyItemsUpdates(items => items .filter(it => ['queue', 'ombre'].includes(it.type)) .map(it => this.migrateQueue(it)) - ); + ) } } diff --git a/module/misc.js b/module/misc.js index 775dd2bc..9b43636d 100644 --- a/module/misc.js +++ b/module/misc.js @@ -38,6 +38,12 @@ export class Misc { return (a, b) => Number(a) + Number(b); } + static and(predicates) { + return value => predicates.map(predicate => predicate(value)) + .reduce((v1, v2) => v1 && v2, true); + } + + static ascending(orderFunction = x => x) { return (a, b) => Misc.sortingBy(orderFunction(a), orderFunction(b)); } @@ -60,6 +66,11 @@ export class Misc { static arrayOrEmpty(items) { return items?.length ? items : []; } + + static findOrFirst(list, predicate) { + return list.find(predicate) ?? list[0] + } + /** * Converts the value to an integer, or to 0 if undefined/null/not representing integer * @param {*} value value to convert to an integer using parseInt @@ -75,13 +86,17 @@ export class Misc { return Math.round(num * power10n) / power10n; } - static getFractionHtml(diviseur) { - if (!diviseur || diviseur <= 1) return undefined; - switch (diviseur || 1) { - case 2: return '½'; - case 4: return '¼'; - default: return '1/' + diviseur; + + static getFractionOneN(divider) { + if (!divider || divider == 1) { + return 1 } + switch (divider) { + case 2: return '½' + case 4: return '¼' + case 8: return '⅛' + } + return `1/${divider}` } static indexLowercase(list) { @@ -216,7 +231,7 @@ export class Misc { static isFirstConnectedGM() { return game.user == Misc.firstConnectedGM(); } - + static hasConnectedGM() { return Misc.firstConnectedGM(); } diff --git a/module/rdd-carac.js b/module/rdd-carac.js index a8c2dcd8..55cb879f 100644 --- a/module/rdd-carac.js +++ b/module/rdd-carac.js @@ -37,21 +37,37 @@ const TABLE_CARACTERISTIQUES_DERIVEES = { 32: { xp: 180, niveau: 11, poids: "1501-2000", poidsMin: 1501, poidsMax: 2000, plusdom: +11, sconst: 10, sust: 17 } }; +export const CARACS = { + TAILLE: 'taille', + APPARENCE: 'apparence', + CONSTITUTION: 'constitution', + FORCE: 'force', + AGILITE: 'agilite', + DEXTERITE: 'dexterite', + VUE: 'vue', + OUIE: 'ouie', + ODORATGOUT: 'odoratgout', + VOLONTE: 'volonte', + INTELLECT: 'intellect', + EMPATHIE: 'empathie', + REVE: 'reve', + CHANCE: 'chance', +} export const LIST_CARAC_PERSONNAGE = { - 'taille': { code: 'taille', label: 'Taille', isCarac: true, path: 'system.carac.taille.value' }, - 'apparence': { code: 'apparence', label: 'Apparence', isCarac: true, path: 'system.carac.apparence.value' }, - 'constitution': { code: 'constitution', label: 'Constitution', isCarac: true, path: 'system.carac.constitution.value' }, - 'force': { code: 'force', label: 'Force', isCarac: true, path: 'system.carac.force.value' }, - 'agilite': { code: 'agilite', label: 'Agilité', isCarac: true, path: 'system.carac.agilite.value' }, - 'dexterite': { code: 'dexterite', label: 'Dextérité', isCarac: true, path: 'system.carac.dexterite.value' }, - 'vue': { code: 'vue', label: 'Vue', isCarac: true, path: 'system.carac.vue.value' }, - 'ouie': { code: 'ouie', label: 'Ouïe', isCarac: true, path: 'system.carac.ouie.value' }, - 'odoratgout': { code: 'odoratgout', label: 'Odorat-Goût', isCarac: true, path: 'system.carac.odoratgout.value' }, - 'volonte': { code: 'volonte', label: 'Volonté', isCarac: true, path: 'system.carac.volonte.value' }, - 'intellect': { code: 'intellect', label: 'Intellect', isCarac: true, path: 'system.carac.intellect.value' }, - 'empathie': { code: 'empathie', label: 'Empathie', isCarac: true, path: 'system.carac.empathie.value' }, - 'reve': { code: 'reve', label: 'Rêve', isCarac: true, path: 'system.carac.reve.value' }, - 'chance': { code: 'chance', label: 'Chance', isCarac: true, path: 'system.carac.chance.value' }, + [CARACS.TAILLE]: { code: CARACS.TAILLE, label: 'Taille', isCarac: true, path: 'system.carac.taille.value' }, + [CARACS.APPARENCE]: { code: CARACS.APPARENCE, label: 'Apparence', isCarac: true, path: 'system.carac.apparence.value' }, + [CARACS.CONSTITUTION]: { code: CARACS.CONSTITUTION, label: 'Constitution', isCarac: true, path: 'system.carac.constitution.value' }, + [CARACS.FORCE]: { code: CARACS.FORCE, label: 'Force', isCarac: true, path: 'system.carac.force.value' }, + [CARACS.AGILITE]: { code: CARACS.AGILITE, label: 'Agilité', isCarac: true, path: 'system.carac.agilite.value' }, + [CARACS.DEXTERITE]: { code: CARACS.DEXTERITE, label: 'Dextérité', isCarac: true, path: 'system.carac.dexterite.value' }, + [CARACS.VUE]: { code: CARACS.VUE, label: 'Vue', isCarac: true, path: 'system.carac.vue.value' }, + [CARACS.OUIE]: { code: CARACS.OUIE, label: 'Ouïe', isCarac: true, path: 'system.carac.ouie.value' }, + [CARACS.ODORATGOUT]: { code: CARACS.ODORATGOUT, label: 'Odorat-Goût', isCarac: true, path: 'system.carac.odoratgout.value' }, + [CARACS.VOLONTE]: { code: CARACS.VOLONTE, label: 'Volonté', isCarac: true, path: 'system.carac.volonte.value' }, + [CARACS.INTELLECT]: { code: CARACS.INTELLECT, label: 'Intellect', isCarac: true, path: 'system.carac.intellect.value' }, + [CARACS.EMPATHIE]: { code: CARACS.EMPATHIE, label: 'Empathie', isCarac: true, path: 'system.carac.empathie.value' }, + [CARACS.REVE]: { code: CARACS.REVE, label: 'Rêve', isCarac: true, path: 'system.carac.reve.value' }, + [CARACS.CHANCE]: { code: CARACS.CHANCE, label: 'Chance', isCarac: true, path: 'system.carac.chance.value' }, 'protection': { code: 'protection', label: 'Protection naturelle', isCarac: false, path: 'system.attributs.protection.value' }, 'beaute': { code: 'beaute', label: 'Beauté', isCarac: false, path: 'system.background.beaute.value' } } @@ -69,7 +85,7 @@ const LIST_CARAC_DERIVEE = { 'reve-actuel': { code: "reve-actuel", label: 'Rêve actuel', path: 'system.reve.reve.value' }, } -const LIST_CARAC_ROLL = Object.values(LIST_CARAC_PERSONNAGE).filter(it => it.isCarac && it.code != 'taille') +export const LIST_CARAC_ROLL = Object.values(LIST_CARAC_PERSONNAGE).filter(it => it.isCarac && it.code != 'taille') .concat(Object.values(LIST_CARAC_AUTRES)) .concat(Object.values(LIST_CARAC_DERIVEE)) @@ -95,27 +111,41 @@ export class RdDCarac { return Object.values(LIST_CARAC_PERSONNAGE).filter(filter) } - static isAgiliteOuDerobee(selectedCarac) { - return selectedCarac?.label.match(/(Agilité|Dérobée)/); + static isAgiliteOuDerobee(caracLabel) { + return RdDCarac.isAgilite(caracLabel) + || RdDCarac.isDerobee(caracLabel) } - static isVolonte(selectedCarac) { - return selectedCarac?.label == 'Volonté'; + static isDerobee(caracLabel) { + return Grammar.equalsInsensitive(caracLabel, LIST_CARAC_PERSONNAGE.agilite.code); } - static isChance(selectedCarac) { - return selectedCarac?.label?.toLowerCase()?.match(/chance( actuelle)?/); + + static isAgilite(caracLabel) { + return Grammar.equalsInsensitive(caracLabel, LIST_CARAC_DERIVEE.derobee.code); } - static isReve(selectedCarac) { - return selectedCarac?.label?.toLowerCase()?.match(/r(e|ê)ve(( |-)actuel)?/); + + static isIntellect(caracLabel) { + return Grammar.toLowerCaseNoAccent(caracLabel) == 'intellect'; + } + + static isVolonte(caracLabel) { + return Grammar.toLowerCaseNoAccent(caracLabel) == 'volonte'; + } + static isChance(caracLabel) { + return Grammar.toLowerCaseNoAccent(caracLabel)?.match(/chance(( |-)?actuelle)?/); + } + static isReve(caracLabel) { + return Grammar.toLowerCaseNoAccent(caracLabel)?.match(/reve(( |-)?actuel)?/); } /** * L’appel à la chance n’est possible que pour recommencer les jets d’actions physiques : * tous les jets de combat, de FORCE, d’AGILITÉ, de DEXTÉRITÉ, de Dérobée, d’APPARENCE, * ainsi que de Perception active et volontaire. + * Le moral ne s'utilise aussi que sur les actions physiques */ - static isActionPhysique(selectedCarac) { - return Grammar.toLowerCaseNoAccent(selectedCarac?.label) + static isActionPhysique(caracLabel) { + return Grammar.toLowerCaseNoAccent(caracLabel) ?.match(/(apparence|force|agilite|dexterite|vue|ouie|gout|odorat|empathie|melee|tir|lancer|derobee)/) != null } diff --git a/module/rdd-combat.js b/module/rdd-combat.js index d10c7cc4..1a55c752 100644 --- a/module/rdd-combat.js +++ b/module/rdd-combat.js @@ -1,9 +1,6 @@ import { ChatUtility } from "./chat-utility.js"; import { ENTITE_BLURETTE, HIDE_DICE, SYSTEM_RDD, SYSTEM_SOCKET_ID } from "./constants.js"; import { Grammar } from "./grammar.js"; -import { RdDItemArme } from "./item-arme.js"; -import { RdDItemCompetence } from "./item-competence.js"; -import { RdDItemCompetenceCreature } from "./item-competencecreature.js"; import { Misc } from "./misc.js"; import { RdDBonus } from "./rdd-bonus.js"; import { RdDResolutionTable } from "./rdd-resolution-table.js"; @@ -14,6 +11,10 @@ import { STATUSES } from "./settings/status-effects.js"; import { Targets } from "./targets.js"; import { RdDEmpoignade } from "./rdd-empoignade.js"; import { RdDRollResult } from "./rdd-roll-result.js"; +import { RdDItemArme } from "./item/arme.js"; +import { RdDItemCompetence } from "./item-competence.js"; +import { RdDItemCompetenceCreature } from "./item-competencecreature.js"; +import { RdDInitiative } from "./initiative.mjs"; /* -------------------------------------------- */ const premierRoundInit = [ @@ -150,7 +151,7 @@ export class RdDCombatManager extends Combat { } static getFirstInitRollFormula(actor) { - const actions = actor.listActionsCombat() + const actions = actor.listActions({ isEquipe: true }) if (actions.length > 0) { const action = actions[0] const init = RdDCombatManager.getInitData(actor, action) @@ -163,87 +164,7 @@ export class RdDCombatManager extends Combat { } static formuleInitiative(rang, carac, niveau, bonusMalus) { - return `${rang} +( (${RdDCombatManager.calculInitiative(niveau, carac, bonusMalus)} )/100)`; - } - - /* -------------------------------------------- */ - static calculInitiative(niveau, caracValue, bonus = 0) { - let base = niveau + Math.floor(caracValue / 2) + bonus; - return "1d6" + (base >= 0 ? "+" : "") + base; - } - - /* -------------------------------------------- */ - /** Retourne une liste triée d'actions d'armes avec le split arme1 main / arme 2 main / lancer */ - static listActionsArmes(armes, competences, carac) { - let actions = []; - for (const arme of armes) { - if (arme.system.equipe) { - const dommages = arme.system.dommages.toString(); - const tableauDommages = dommages.includes("/") ? dommages.split("/") : [dommages, dommages]; - if (arme.system.unemain && arme.system.deuxmains && !dommages.includes("/")) { - ui.notifications.info("Les dommages de l'arme à 1/2 mains " + arme.name + " ne sont pas corrects (ie sous la forme X/Y)"); - } - if (arme.system.unemain && arme.system.competence) { - actions.push(RdDCombatManager.$prepareAttaqueArme({ - arme: arme, - infoMain: "(1 main)", - dommagesReel: Number(tableauDommages[0]), - competence: arme.system.competence, - carac: carac, - competences: competences - })); - } - if (arme.system.deuxmains && arme.system.competence) { - actions.push(RdDCombatManager.$prepareAttaqueArme({ - arme: arme, - infoMain: "(2 mains)", - dommagesReel: Number(tableauDommages[1]), - competence: RdDItemArme.competence2Mains(arme), - carac: carac, - competences: competences - })); - } - if (arme.system.lancer) { - actions.push(RdDCombatManager.$prepareAttaqueArme({ - arme: arme, - infoMain: "(lancer)", - dommagesReel: Number(tableauDommages[0]), - competence: arme.system.lancer, - carac: carac, - competences: competences - })); - } - if (arme.system.tir) { - actions.push(RdDCombatManager.$prepareAttaqueArme({ - arme: arme, - infoMain: "(tir)", - dommagesReel: Number(tableauDommages[0]), - competence: arme.system.tir, - carac: carac, - competences: competences - })); - } - } - } - return actions.sort(Misc.ascending(action => action.name + (action.system.infoMain ?? ''))); - } - - static $prepareAttaqueArme(infoAttaque) { - const comp = infoAttaque.competences.find(it => Grammar.equalsInsensitive(it.name, infoAttaque.competence)) - const arme = infoAttaque.arme; - const attaque = foundry.utils.duplicate(arme) - - const carac = comp?.system.defaut_carac ?? (infoAttaque.infoMain == '(lancer)' ? 'lancer' : infoAttaque.infoMain == '(lancer)' ? 'tir' : 'melee') - const niveau = comp?.system.niveau ?? (infoAttaque.infoMain == '(lancer)' ? -8 : -6) - attaque.action = 'attaque'; - attaque.system.competence = infoAttaque.competence; - attaque.system.dommagesReels = infoAttaque.dommagesReel; - attaque.system.infoMain = infoAttaque.infoMain; - attaque.system.niveau = niveau - - const ajustement = (arme.parent?.getEtatGeneral() ?? 0) + (arme.system.magique) ? arme.system.ecaille_efficacite : 0 - attaque.system.initiative = RdDCombatManager.calculInitiative(niveau, infoAttaque.carac[carac].value, ajustement) - return attaque + return `${rang} +( (${RdDInitiative.calculInitiative(niveau, carac, bonusMalus)} )/100)`; } /* -------------------------------------------- */ @@ -308,7 +229,7 @@ export class RdDCombatManager extends Combat { combatant.initiativeData = { arme: action } // pour reclasser l'init au round 0 const init = RdDCombatManager.getInitData(actor, action) - const ajustement = RdDCombatManager.calculAjustementInit(actor, action) + const ajustement = RdDCombatManager.calculAjustementInit(actor, action.arme) const rollFormula = RdDCombatManager.formuleInitiative(init.offset, init.carac, init.niveau, ajustement); await game.combat.rollInitRdD(combatantId, rollFormula, init); @@ -322,12 +243,12 @@ export class RdDCombatManager extends Combat { if (action.action == 'possession') { return { offset: 10, info: "Possession", carac: actor.getReveActuel(), niveau: 0 } } if (action.action == 'haut-reve') { return { offset: 9, info: "Draconic", carac: actor.getReveActuel(), niveau: 0 } } - const comp = RdDItemCompetence.findCompetence(actor.items, action.system.competence); + const comp = action.comp return { - offset: RdDCombatManager.initOffset(comp?.system.categorie, action), - info: action.name + " / " + action.system.competence, + offset: RdDCombatManager.initOffset(comp?.system.categorie, action.arme), + info: action.name + " / " + comp.name, carac: actor.getCaracInit(comp), - niveau: comp?.system.niveau ?? -8 + niveau: comp?.system.niveau ?? (['(lancer)', '(tir)'].includes(action.main) ? -8 : -6) } } @@ -370,7 +291,7 @@ export class RdDCombatManager extends Combat { const possessions = actor.listActionsPossessions() const actions = possessions.length > 0 ? possessions - : actor.listActionsCombat() + : actor.listActions({ isEquipe: true }) for (let index = 0; index < actions.length; index++) { actions[index].index = index @@ -714,8 +635,9 @@ export class RdDCombat { if (taille <= 20) return { msg: "ogre", diff: 2 }; return { msg: "gigantesque", diff: 4 }; } + _ajustementMouvement(defender) { - if (defender.getSurprise(true)) return { msg: "immobile (surprise)", diff: 0 }; + if (defender.getSurprise(true) != '') return { msg: "immobile (surprise)", diff: 0 }; if (game.combat?.combatants.find(it => it.actorId == defender.id)) return { msg: "en mouvement (combat)", diff: -4 }; return { msg: "à déterminer (0 immobile, -3 actif, -4 en mouvement, -5 en zig-zag)", diff: -3 }; } @@ -779,7 +701,7 @@ export class RdDCombat { // sans armes: à mains nues rollData.arme = RdDItemArme.corpsACorps(this.attacker) rollData.arme.system.niveau = competence.system.niveau - rollData.arme.system.initiative = RdDCombatManager.calculInitiative(competence.system.niveau, this.attacker.system.carac['melee'].value); + rollData.arme.system.initiative = RdDInitiative.calculInitiative(competence.system.niveau, this.attacker.system.carac['melee'].value); } return rollData; } diff --git a/module/rdd-hotbar-drop.js b/module/rdd-hotbar-drop.js index ad80a03d..35e15415 100644 --- a/module/rdd-hotbar-drop.js +++ b/module/rdd-hotbar-drop.js @@ -1,4 +1,4 @@ -import { RdDItemArme } from "./item-arme.js"; +import { RdDItemArme } from "./item/arme.js"; import { RdDItemCompetenceCreature } from "./item-competencecreature.js"; import { ITEM_TYPES } from "./constants.js"; @@ -22,13 +22,12 @@ export class RdDHotbar { static $macroNameSuffix(armeCompetence) { switch (armeCompetence) { - case 'unemain': return ' (1 main)'; - case 'deuxmains': return ' (2 main)'; + case '(1 main)': return ' (1 main)'; + case '(2 mains)': return ' (2 main)'; case 'tir': return ' (tir)'; case 'lancer': return ' (lancer)'; case 'pugilat': return ' (pugilat)'; case 'empoignade': return ' (empoignade)'; - } return '' } @@ -40,10 +39,10 @@ export class RdDHotbar { // Les armes peuvent avoir plusieurs usages if (item.system.competence != '') { if (item.system.unemain) { - await this.createItemMacro(item, slot++, 'unemain') + await this.createItemMacro(item, slot++, '(1 main)') } if (item.system.deuxmains) { - await this.createItemMacro(item, slot++, 'deuxmains') + await this.createItemMacro(item, slot++, '(2 mains)') } } if (item.system.lancer != '') { @@ -121,9 +120,9 @@ export class RdDHotbar { if (item.isCorpsACorps()) { switch (categorieArme) { case 'pugilat': - return actor.rollArme(RdDItemArme.corpsACorps(actor), 'competence'); + return actor.rollArme(RdDItemArme.corpsACorps(actor)); case 'empoignade': - return actor.rollArme(RdDItemArme.empoignade(actor), 'competence'); + return actor.rollArme(RdDItemArme.empoignade(actor)); } } return actor.rollCompetence(item); diff --git a/module/rdd-main.js b/module/rdd-main.js index a9700cdb..e08389af 100644 --- a/module/rdd-main.js +++ b/module/rdd-main.js @@ -1,5 +1,4 @@ -import { SYSTEM_RDD, SYSTEM_SOCKET_ID, RDD_CONFIG } from "./constants.js" -import { Migrations } from './migrations.js' +import { SYSTEM_RDD, SYSTEM_SOCKET_ID, RDD_CONFIG, ITEM_TYPES } from "./constants.js" import { RdDUtility } from "./rdd-utility.js" import { TMRUtility } from "./tmr-utility.js" @@ -11,7 +10,6 @@ import { DialogChronologie } from "./dialog-chronologie.js" import { RdDResolutionTable } from "./rdd-resolution-table.js" import { RdDTokenHud } from "./rdd-token-hud.js" import { RdDCommands } from "./rdd-commands.js" -import { RdDCombatManager, RdDCombat } from "./rdd-combat.js" import { ChatUtility } from "./chat-utility.js" import { StatusEffects } from "./settings/status-effects.js" import { RdDCompendiumOrganiser } from "./rdd-compendium-organiser.js" @@ -39,31 +37,40 @@ import { RdDActorEntiteSheet } from "./actor/entite-sheet.js" import { RdDActorVehiculeSheet } from "./actor/vehicule-sheet.js" import { RdDItem } from "./item.js" + +import * as models from "./models/_module.mjs" +import * as items from "./documents/_module.mjs" +import * as sheets from "./applications/sheets/_module.mjs" + +import { RdDItemArme } from "./item/arme.js" import { RdDItemArmure } from "./item/armure.js" import { RdDItemBlessure } from "./item/blessure.js" -import { RdDItemService } from "./item/service.js" +import { RdDItemGemme } from "./item/gemme.js" import { RdDItemMaladie } from "./item/maladie.js" -import { RdDItemPoison } from "./item/poison.js" -import { RdDItemSigneDraconique } from "./item/signedraconique.js" -import { RdDItemQueue } from "./item/queue.js" import { RdDItemOmbre } from "./item/ombre.js" -import { RdDItemSort } from "./item-sort.js" -import { RdDItemTete } from "./item/tete.js" +import { RdDItemPoison } from "./item/poison.js" +import { RdDItemPotion } from "./item/potion.js" +import { RdDItemQueue } from "./item/queue.js" +import { RdDItemService } from "./item/service.js" import { RdDItemRace } from "./item/race.js" +import { RdDItemSigneDraconique } from "./item/signedraconique.js" +import { RdDItemSort } from "./item-sort.js" import { RdDItemSouffle } from "./item/souffle.js" - +import { RdDItemTete } from "./item/tete.js" import { RdDRencontre } from "./item/rencontre.js" import { RdDItemSheetV1 } from "./item-sheet.js" import { RdDBlessureItemSheet } from "./item/sheet-blessure.js" -import { RdDServiceItemSheet } from "./item/sheet-service.js" -import { RdDRencontreItemSheet } from "./item/sheet-rencontre.js" -import { RdDHerbeItemSheet } from "./item/sheet-herbe.js" -import { RdDPlanteItemSheet } from "./item/sheet-plante.js" -import { RdDIngredientItemSheet } from "./item/sheet-ingredient.js" -import { RdDFauneItemSheet } from "./item/sheet-faune.js" import { RdDConteneurItemSheet } from "./item/sheet-conteneur.js" -import { RdDItemInventaireSheet } from "./item/sheet-base-inventaire.js" +import { RdDGemmeItemSheet } from "./item/sheet-gemme.js" +import { RdDHerbeItemSheet } from "./item/sheet-herbe.js" +import { RdDFauneItemSheet } from "./item/sheet-faune.js" +import { RdDIngredientItemSheet } from "./item/sheet-ingredient.js" +import { RdDInventaireItemSheet } from "./item/sheet-base-inventaire.js" +import { RdDPlanteItemSheet } from "./item/sheet-plante.js" +import { RdDPotionItemSheet } from "./item/sheet-potion.js" +import { RdDRencontreItemSheet } from "./item/sheet-rencontre.js" +import { RdDServiceItemSheet } from "./item/sheet-service.js" import { RdDSigneDraconiqueItemSheet } from "./item/sheet-signedraconique.js" import { AppAstrologie } from "./sommeil/app-astrologie.js" @@ -75,14 +82,10 @@ import { AppPersonnageAleatoire } from "./actor/random/app-personnage-aleatoire. import { RdDActorExportSheet } from "./actor/export-scriptarium/actor-encart-sheet.js" import { RdDStatBlockParser } from "./apps/rdd-import-stats.js" import { RdDJournalSheet } from "./journal/journal-sheet.js" -import { RdDPotionItemSheet } from "./item/sheet-potion.js" -import { RdDItemPotion } from "./item/potion.js" -import { RdDItemGemme } from "./item/gemme.js" -import { RdDGemmeItemSheet } from "./item/sheet-gemme.js" +import { RdDCombatManager, RdDCombat } from "./rdd-combat.js" +import { Migrations } from './migrations.js' -import * as models from "./models/_module.mjs" -import * as items from "./documents/_module.mjs" -import * as sheets from "./applications/sheets/_module.mjs" +import RollDialog from "./roll/roll-dialog.mjs" /** * RdD system @@ -106,7 +109,7 @@ export class SystemReveDeDragon { this.itemClasses = { monnaie: items.RdDItemMonnaie, munition: items.RdDItemMunition, - tarot: items.RdDModelTarot, + arme: RdDItemArme, armure: RdDItemArmure, blessure: RdDItemBlessure, gemme: RdDItemGemme, @@ -120,6 +123,7 @@ export class SystemReveDeDragon { service: RdDItemService, signedraconique: RdDItemSigneDraconique, souffle: RdDItemSouffle, + tarot: items.RdDModelTarot, tete: RdDItemTete, } this.actorClasses = { @@ -206,11 +210,16 @@ export class SystemReveDeDragon { foundry.documents.collections.Actors.registerSheet(SYSTEM_RDD, RdDActorVehiculeSheet, { types: ["vehicule"], makeDefault: true }) foundry.documents.collections.Actors.registerSheet(SYSTEM_RDD, RdDActorEntiteSheet, { types: ["entite"], makeDefault: true }) foundry.documents.collections.Items.unregisterSheet("core", foundry.appv1.sheets.ItemSheet) + RdDActorExportSheet.init() - foundry.documents.collections.Items.registerSheet(SYSTEM_RDD, RdDItemInventaireSheet, { + foundry.documents.collections.Items.registerSheet(SYSTEM_RDD, RdDInventaireItemSheet, { types: [ - "objet", "arme", "armure", "livre", "nourritureboisson", + ITEM_TYPES.objet, + ITEM_TYPES.arme, + ITEM_TYPES.armure, + ITEM_TYPES.livre, + ITEM_TYPES.nourritureboisson, ], makeDefault: true }) @@ -223,12 +232,31 @@ export class SystemReveDeDragon { foundry.documents.collections.Items.registerSheet(SYSTEM_RDD, RdDItemSheetV1, { types: [ - "competence", "competencecreature", - "recettealchimique", "musique", "chant", "danse", "jeu", "race", - "recettecuisine", "oeuvre", "meditation", - "queue", "ombre", "souffle", "tete", "casetmr", "sort", "sortreserve", - "nombreastral", "tache", "maladie", "poison", "possession", - "extraitpoetique", "empoignade" + ITEM_TYPES.competence, + ITEM_TYPES.competencecreature, + ITEM_TYPES.recettealchimique, + ITEM_TYPES.musique, + ITEM_TYPES.chant, + ITEM_TYPES.danse, + ITEM_TYPES.jeu, + ITEM_TYPES.race, + ITEM_TYPES.recettecuisine, + ITEM_TYPES.oeuvre, + ITEM_TYPES.meditation, + ITEM_TYPES.queue, + ITEM_TYPES.ombre, + ITEM_TYPES.souffle, + ITEM_TYPES.tete, + ITEM_TYPES.casetmr, + ITEM_TYPES.sort, + ITEM_TYPES.sortreserve, + ITEM_TYPES.nombreastral, + ITEM_TYPES.tache, + ITEM_TYPES.maladie, + ITEM_TYPES.poison, + ITEM_TYPES.possession, + ITEM_TYPES.extraitpoetique, + ITEM_TYPES.empoignade ], makeDefault: true }) @@ -263,6 +291,7 @@ export class SystemReveDeDragon { RdDPossession.init() TMRRencontres.init() ExportScriptarium.init() + RollDialog.init() } initSettings() { @@ -346,7 +375,7 @@ export class SystemReveDeDragon { StatusEffects.onReady() RdDDice.onReady() - + RollDialog.onReady() RdDStatBlockParser.parseStatBlock() /* -------------------------------------------- */ /* Affiche/Init le calendrier */ @@ -375,6 +404,10 @@ export class SystemReveDeDragon { ` }) } } + + roll(rollData, actors, options){ + RollDialog.create(rollData, actors, options) + } } SystemReveDeDragon.start() diff --git a/module/rdd-resolution-table.js b/module/rdd-resolution-table.js index 14d70ecd..f750c47f 100644 --- a/module/rdd-resolution-table.js +++ b/module/rdd-resolution-table.js @@ -109,7 +109,7 @@ export class RdDResolutionTable { rolled.caracValue = caracValue; rolled.finalLevel = finalLevel; rolled.bonus = rollData.bonus; - rolled.factorHtml = Misc.getFractionHtml(rollData.diviseurSignificative); + rolled.factorHtml = Misc.getFractionOneN(rollData.diviseurSignificative); if (ReglesOptionnelles.isUsing("afficher-colonnes-reussite")) { rolled.niveauNecessaire = this.findNiveauNecessaire(caracValue, rolled.roll); diff --git a/module/rdd-roll-resolution-table.js b/module/rdd-roll-resolution-table.js index f82cb9df..6901c523 100644 --- a/module/rdd-roll-resolution-table.js +++ b/module/rdd-roll-resolution-table.js @@ -123,7 +123,7 @@ export class RdDRollResolutionTable extends Dialog { // Mise à jour valeurs this.html.find("[name='carac']").val(rollData.caracValue); - this.html.find(".roll-param-resolution").text(rollData.selectedCarac.value + " / " + Misc.toSignedString(rollData.finalLevel)); + this.html.find(".roll-part-resolution").text(rollData.selectedCarac.value + " / " + Misc.toSignedString(rollData.finalLevel)); this.html.find("div.placeholder-resolution").empty().append(htmlTable) } diff --git a/module/rdd-roll-result.js b/module/rdd-roll-result.js index 480a641c..3087ff2f 100644 --- a/module/rdd-roll-result.js +++ b/module/rdd-roll-result.js @@ -5,7 +5,8 @@ export class RdDRollResult { static async displayRollData(rollData, actor = undefined, template = 'chat-resultat-general.hbs') { const chatMessage = await ChatUtility.createChatWithRollMode( { content: await RdDRollResult.buildRollDataHtml(rollData, template) }, - actor + actor, + rollData.current?.rollmode?.key ) return chatMessage } diff --git a/module/rdd-roll.js b/module/rdd-roll.js index e4a51b26..58a1fec0 100644 --- a/module/rdd-roll.js +++ b/module/rdd-roll.js @@ -39,7 +39,7 @@ export class RdDRoll extends Dialog { difficultesLibres: CONFIG.RDD.difficultesLibres, etat: actor.getEtatGeneral(), moral: actor.getMoralTotal(), /* La valeur du moral pour les jets de volonté */ - amoureux: actor.listeSuivants(it => it.coeur > 0), + amoureux: actor.listeAmoureux(), carac: foundry.utils.duplicate(actor.system.carac), finalLevel: 0, diffConditions: 0, @@ -54,7 +54,7 @@ export class RdDRoll extends Dialog { surenc: actor.isSurenc(), encTotal: true }, - isMalusEncombrementTotal: RdDItemCompetence.isMalusEncombrementTotal(rollData.competence), + isMalusEncombrementTotal: RdDItemCompetence.isMalusEncombrementTotal(rollData.competence?.name), encTotal: actor.getEncTotal(), ajustementAstrologique: actor.ajustementAstrologique(), surprise: actor.getSurprise(false), @@ -314,15 +314,15 @@ export class RdDRoll extends Dialog { rollData.dmg = rollData.attackerRoll?.dmg ?? RdDBonus.dmg(rollData, this.actor) rollData.caracValue = parseInt(rollData.selectedCarac.value) rollData.dmg.mortalite = rollData.dmg.mortalite ?? 'mortel'; - rollData.use.appelAuMoral = this.actor.isPersonnage() && RdDCarac.isActionPhysique(rollData.selectedCarac); + rollData.use.appelAuMoral = this.actor.isPersonnage() && RdDCarac.isActionPhysique(rollData.selectedCarac?.label); RollDataAjustements.calcul(rollData, this.actor); const resolutionTable = await RdDResolutionTable.buildHTMLTable(RdDResolutionTable.subTable(rollData.caracValue, rollData.finalLevel)) const adjustements = await this.buildAjustements(rollData); - HtmlUtility.showControlWhen(this.html.find(".use-encTotal"), rollData.ajustements.encTotal.visible && RdDCarac.isAgiliteOuDerobee(rollData.selectedCarac)); - HtmlUtility.showControlWhen(this.html.find(".use-surenc"), rollData.ajustements.surenc.visible && RdDCarac.isActionPhysique(rollData.selectedCarac)); + HtmlUtility.showControlWhen(this.html.find(".use-encTotal"), rollData.ajustements.encTotal.visible && RdDCarac.isAgiliteOuDerobee(rollData.selectedCarac?.label)); + HtmlUtility.showControlWhen(this.html.find(".use-surenc"), rollData.ajustements.surenc.visible && RdDCarac.isActionPhysique(rollData.selectedCarac?.label)); HtmlUtility.showControlWhen(this.html.find(".use-astrologique"), rollData.ajustements.astrologique.visible); HtmlUtility.showControlWhen(this.html.find(".utilisation-moral"), rollData.use.appelAuMoral); HtmlUtility.showControlWhen(this.html.find(".divAppelAuMoral"), rollData.use.appelAuMoral); diff --git a/module/rdd-token-hud.js b/module/rdd-token-hud.js index 4b36f2ae..83553c8f 100644 --- a/module/rdd-token-hud.js +++ b/module/rdd-token-hud.js @@ -30,11 +30,11 @@ export class RdDTokenHud { const combatant = game.combat.combatants.find(c => c.tokenId == tokenId) const actor = RdDCombatManager.getActorCombatant(combatant, { warning: false }) if (actor) { - let actions = RdDCombatManager.listActionsActorCombatant(actor) + const actions = RdDCombatManager.listActionsActorCombatant(actor) // initiative await RdDTokenHud.addExtensionHudInit(html, combatant, actions) // combat - await RdDTokenHud.addExtensionHudCombat(html, combatant, token, actions) + await RdDTokenHud.addExtensionHudCombat(html, combatant, token, actions.filter(it => !it.initOnly)) } } @@ -67,18 +67,19 @@ export class RdDTokenHud { } static async addExtensionHudCombat(html, combatant, token, actions) { + const hudData = { combatant, token, actions, commandes: [] }; const controlIconTarget = $(html).find('.control-icon[data-action=target]'); await RdDTokenHud._configureSubMenu(controlIconTarget, 'systems/foundryvtt-reve-de-dragon/templates/hud-actor-attaque.hbs', hudData, (event) => { const actionIndex = event.currentTarget.attributes['data-action-index']?.value; const action = hudData.actions[actionIndex]; - const possession = action.action == 'possession' ? combatant.actor.getPossession(action.system.possessionid) : undefined; + const possession = action.action == 'possession' ? combatant.actor.getPossession(action.possessionid) : undefined; if (possession) { combatant.actor.conjurerPossession(possession); } else { - combatant.actor.rollArme(action, 'competence', token) + combatant.actor.rollArme(action.arme, action.main, token) } }); } diff --git a/module/rdd-utility.js b/module/rdd-utility.js index c0dd17e0..2347bc4e 100644 --- a/module/rdd-utility.js +++ b/module/rdd-utility.js @@ -221,6 +221,7 @@ export class RdDUtility { 'systems/foundryvtt-reve-de-dragon/templates/common/compendium-link.hbs', 'systems/foundryvtt-reve-de-dragon/templates/partial-description-overflow.hbs', 'systems/foundryvtt-reve-de-dragon/templates/partial-description-sort.hbs', + 'systems/foundryvtt-reve-de-dragon/templates/partial-description.hbs', 'systems/foundryvtt-reve-de-dragon/templates/partial-roll-ajustements.hbs', 'systems/foundryvtt-reve-de-dragon/templates/partial-roll-astrologique.hbs', 'systems/foundryvtt-reve-de-dragon/templates/partial-roll-coeur.hbs', @@ -273,7 +274,8 @@ export class RdDUtility { Handlebars.registerHelper('linkCompendium', (pack, id, name) => RdDUtility.linkCompendium(pack, id, name)); Handlebars.registerHelper('regle-optionnelle', (option) => ReglesOptionnelles.isUsing(option)); - Handlebars.registerHelper('plusMoins', diff => (diff > 0 ? '+' : '') + Math.round(diff)) + Handlebars.registerHelper('plusMoins', diff => parseInt(diff) ? (diff > 0 ? '+' : '') + Math.round(diff) : diff) + Handlebars.registerHelper('fractionOneN', n => new Handlebars.SafeString(Misc.getFractionOneN(n))) // Handle v12 removal of this helper Handlebars.registerHelper('select', function (selected, options) { @@ -318,6 +320,7 @@ export class RdDUtility { // gestion des dates et heures Handlebars.registerHelper('timestamp-imgSigneHeure', (heure) => { return new Handlebars.SafeString(RdDTimestamp.imgSigneHeure(heure)) }); Handlebars.registerHelper('timestamp-imgSigne', (heure) => { return new Handlebars.SafeString(RdDTimestamp.imgSigne(heure)) }); + Handlebars.registerHelper('timestamp-label', (heure) => RdDTimestamp.definition(heure)?.label ?? 'inconnue') Handlebars.registerHelper('timestamp-definition', (heure) => RdDTimestamp.definition(heure)) Handlebars.registerHelper('timestamp-extract', timestamp => new RdDTimestamp(timestamp).toCalendrier()); Handlebars.registerHelper('timestamp-formulesDuree', () => RdDTimestamp.formulesDuree()); @@ -813,9 +816,8 @@ export class RdDUtility { static getSelectedToken(actor) { if (canvas.tokens.controlled.length > 0) { - const tokens = canvas.tokens.controlled - .filter(it => it.actor.id == actor.id) - return tokens[0] + return canvas.tokens.controlled + .find(it => it.actor.id == actor.id) } return undefined } diff --git a/module/roll/roll-basic-parts.mjs b/module/roll/roll-basic-parts.mjs new file mode 100644 index 00000000..b395d6da --- /dev/null +++ b/module/roll/roll-basic-parts.mjs @@ -0,0 +1,60 @@ +import { ActorToken } from "../actor-token.mjs" + +export class RollBasicParts { + + restore(rollData) { + rollData.ids.sceneId = rollData.ids.sceneId ?? canvas.scene.id + rollData.active = RollBasicParts.$getActor(rollData) + rollData.opponent = RollBasicParts.$getOpponent(rollData) + if (rollData.mode.opposed == undefined) { + rollData.mode.opposed = rollData.opponent != null + } + } + + initFrom(rollData) { + return { + selected: {}, + mode: { + current: rollData.mode.current + }, + ids: { + sceneId: rollData.ids.sceneId, + actorId: rollData.active.id, + actorTokenId: rollData.active.tokenId, + opponentId: rollData.mode.opposed ? rollData.opponent.id : undefined, + opponentTokenId: rollData.mode.opposed ? rollData.opponent.tokenId : undefined, + } + } + } + + static $getActor(rollData) { + if (rollData.ids.actorTokenId) { + return ActorToken.fromTokenId(rollData.ids.actorTokenId, rollData.ids.sceneId) + } + else { + const actorId = rollData.ids.actorId ?? (canvas.tokens.controlled.length == 1 + /** TODO: jets de plusieurs personnages??? */ + ? canvas.tokens.controlled[0] + : undefined) + return ActorToken.fromActorId(actorId, () => { throw new Error("Pas d'acteur sélectionné") }) + } + } + + static $getOpponent(rollData) { + if (rollData.ids.opponentTokenId) { + return ActorToken.fromTokenId(rollData.ids.opponentTokenId, rollData.ids.sceneId) + } + else if (rollData.ids.opponentId) { + return ActorToken.fromActorId(rollData.ids.opponentId) + } + else { + const targets = Array.from(game.user.targets) + if (targets.length == 1) { + return ActorToken.fromToken(targets[0]) + } + else { + return undefined + } + } + } +} diff --git a/module/roll/roll-constants.mjs b/module/roll/roll-constants.mjs new file mode 100644 index 00000000..1e306096 --- /dev/null +++ b/module/roll/roll-constants.mjs @@ -0,0 +1,28 @@ + +export const ROLL_MODE_ATTAQUE = 'attaque' +export const ROLL_MODE_COMP = 'comp' +export const ROLL_MODE_DEFENSE = 'defense' +export const ROLL_MODE_JEU = 'jeu' +export const ROLL_MODE_MEDITATION = 'meditation' +export const ROLL_MODE_OEUVRE = 'oeuvre' +export const ROLL_MODE_SORT = 'sort' +export const ROLL_MODE_TACHE = 'tache' + +export const DIFF_MODE = { + LIBRE: 'libre', + ATTAQUE: 'attaque', + IMPOSEE: 'imposee', + DEFENSE: 'defense', + DEFAUT: 'defaut', + AUCUN: 'aucun' +} + +export const DIFF_MODES = { + [DIFF_MODE.LIBRE]: { key: DIFF_MODE.LIBRE, label: "Difficulté libre", libre: true, visible: true, max: 0 }, + [DIFF_MODE.ATTAQUE]: { key: DIFF_MODE.ATTAQUE, label: "Difficulté d'attaque", libre: true, visible: true, max: 0 }, + [DIFF_MODE.IMPOSEE]: { key: DIFF_MODE.IMPOSEE, label: "Diffficulté imposée", libre: false, visible: true, max: 0 }, + [DIFF_MODE.DEFENSE]: { key: DIFF_MODE.DEFENSE, label: "Diffficulté défense", libre: false, visible: true, max: 0 }, + [DIFF_MODE.DEFAUT]: { key: DIFF_MODE.DEFAUT, label: "Difficulté", libre: true, visible: true, max: 5 }, + [DIFF_MODE.AUCUN]: { key: DIFF_MODE.AUCUN, label: "", libre: false, visible: false, max: 0 }, +} + diff --git a/module/roll/roll-dialog-adapter.mjs b/module/roll/roll-dialog-adapter.mjs new file mode 100644 index 00000000..6897e836 --- /dev/null +++ b/module/roll/roll-dialog-adapter.mjs @@ -0,0 +1,90 @@ +import { Misc } from "../misc.js"; +import { PART_APPELMORAL } from "./roll-part-appelmoral.mjs"; +import { PART_COMP } from "./roll-part-comp.mjs"; +import { RdDResolutionTable } from "../rdd-resolution-table.js"; +import { ReglesOptionnelles } from "../settings/regles-optionnelles.js"; +import { PART_OEUVRE } from "./roll-part-oeuvre.mjs"; + +/* -------------------------------------------- */ +export class RollDialogAdapter { + + async rollDice(rollData, rollTitle) { + const chances = this.computeChances({ + carac: rollData.current.carac.value, + diff: rollData.current.totaldiff, + bonus: rollData.current.bonus, + sign: rollData.current.sign, + showDice: rollData.options.showDice, + rollMode: rollData.current.rollmode.key + }) + + const rolled = await this.rollChances(rollData, chances) + this.adjustRollDataForV1(rollData, rolled, rollTitle) + + return rolled + } + + computeChances({ carac, diff, bonus, sign, showDice, rollMode }) { + const chances = foundry.utils.duplicate(RdDResolutionTable.computeChances(carac, diff)) + RdDResolutionTable._updateChancesWithBonus(chances, bonus, diff) + RdDResolutionTable._updateChancesFactor(chances, sign) + chances.showDice = showDice + chances.rollMode = rollMode + return chances + } + + async rollChances(rollData, chances) { + const rolled = await RdDResolutionTable.rollChances(chances, rollData.current.sign, rollData.current.resultat) + rolled.caracValue = rollData.current.carac.value + rolled.finalLevel = rollData.current.totaldiff + rolled.bonus = rollData.current.bonus ?? 0 + rolled.factorHtml = Misc.getFractionOneN(rollData.current.sign.diviseur) + return rolled + } + + adjustRollDataForV1(rollData, rolled, rollTitle) { + // temporaire pour être homogène roll v1 + rollData.alias = rollData.active.actor.getAlias() + // pour experience + rollData.finalLevel = rollData.current.totaldiff + if (rollData.use == undefined) { rollData.use = {} } + if (rollData.show == undefined) { rollData.show = {} } + if (rollData.ajustements == undefined) { + rollData.ajustements = {} + } + rollData.selectedCarac = rollData.active.actor.system.carac[rollData.current.carac.key] + + const compKey = rollData.current.comp?.key + if (compKey) { + rollData.competence = rollData.refs[PART_COMP].all.find(it => it.key == compKey)?.comp + rollData.jetResistance = rollData.mode.jetResistance + } + const oeuvreKey = rollData.current.oeuvre?.key + if (oeuvreKey) { + const oeuvreCurrent = rollData.current[PART_OEUVRE]; + rollData.oeuvre = oeuvreCurrent.oeuvre + // rollData.oeuvre = rollData.refs[PART_OEUVRE].oeuvres.find(it => it.key == oeuvreKey)?.oeuvre + rollData.art = oeuvreCurrent.art.type + } + // pour appel moral + rollData.diviseurSignificative = rollData.current.sign + if (rollData.current[PART_APPELMORAL]?.checked) { + rollData.use.moral = true + } + rollData.rolled = rolled + if (ReglesOptionnelles.isUsing("afficher-colonnes-reussite")) { + rolled.niveauNecessaire = this.findNiveauNecessaire(carac, rolled.roll) + rolled.ajustementNecessaire = rolled.niveauNecessaire - diff + } + rollData.ajustements = rollData.ajustements.map(aj => { + return { + used: true, + label: aj.label, + value: aj.diff, + descr: aj.diff == undefined ? aj.label : undefined + } + }) + rollData.show.title = rollTitle + } + +} diff --git a/module/roll/roll-dialog.mjs b/module/roll/roll-dialog.mjs new file mode 100644 index 00000000..f939aa5e --- /dev/null +++ b/module/roll/roll-dialog.mjs @@ -0,0 +1,439 @@ +import { Misc } from "../misc.js"; +import { RollModeComp } from "./roll-mode-comp.mjs"; +import { RollModeTache } from "./roll-mode-tache.mjs"; +import { RollModeAttaque } from "./roll-mode-attaque.mjs"; +import { RollModeDefense } from "./roll-mode-defense.mjs"; +import { RollModeMeditation } from "./roll-mode-meditation.mjs"; +import { RollModeSort } from "./roll-mode-sort.mjs"; +import { RollModeOeuvre } from "./roll-mode-oeuvre.mjs"; +import { RollModeJeu } from "./roll-mode-jeu.mjs"; + +import { RollPartAction } from "./roll-part-action.mjs"; +import { RollPartActor } from "./roll-part-actor.mjs"; +import { RollPartAppelMoral } from "./roll-part-appelmoral.mjs"; +import { RollPartAstrologique } from "./roll-part-astrologique.mjs"; +import { RollPartCarac } from "./roll-part-carac.mjs"; +import { RollPartCoeur } from "./roll-part-coeur.mjs"; +import { PART_COMP, RollPartComp } from "./roll-part-comp.mjs"; +import { RollPartConditions } from "./roll-part-conditions.mjs"; +import { RollPartDiff } from "./roll-part-diff.mjs"; +import { RollPartEncTotal } from "./roll-part-enctotal.mjs"; +import { RollPartEtat } from "./roll-part-etat.mjs"; +import { RollPartEthylisme } from "./roll-part-ethylisme.mjs"; +import { RollPartMalusArmure } from "./roll-part-malusarmure.mjs"; +import { RollPartMeditation } from "./roll-part-meditation.mjs"; +import { RollPartMoral } from "./roll-part-moral.mjs"; +import { RollPartOpponent } from "./roll-part-opponent.mjs"; +import { RollPartSurEnc } from "./roll-part-surenc.mjs"; +import { RollPartTricher } from "./roll-part-tricher.mjs"; +import { RollPartTache } from "./roll-part-tache.mjs"; +import { RollPartOeuvre } from "./roll-part-oeuvre.mjs"; +import { RollPartSort } from "./roll-part-sort.mjs"; +import { RollBasicParts } from "./roll-basic-parts.mjs"; +import { RollPartRollMode } from "./roll-part-rollmode.mjs"; +import { RollPartJeu } from "./roll-part-jeu.mjs"; +import { RollPartSign } from "./roll-part-sign.mjs"; +import { RollPartAttaque } from "./roll-part-attaque.mjs"; +import { RollPartDefense } from "./roll-part-defense.mjs"; +import { RollDialogAdapter } from "./roll-dialog-adapter.mjs"; +import { ROLLDIALOG_SECTION } from "./roll-part.mjs"; +import { ROLL_MODE_COMP } from "./roll-constants.mjs"; + +const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api +const doNothing = (dialog) => { } + +const ROLL_MODE_TABS = [ + new RollModeComp(), + new RollModeTache(), + new RollModeAttaque(), + new RollModeDefense(), + // new RollModeParade?? + // new RollModeEsquive?? + // new RollModeResistance ?? + new RollModeSort(), + new RollModeMeditation(), + new RollModeOeuvre(), + new RollModeJeu(), +] + +const BASIC_PARTS = new RollBasicParts() + +const ROLL_PARTS = [ + new RollPartActor(), + new RollPartAction(), + new RollPartOpponent(), + new RollPartCarac(), + new RollPartComp(), + + new RollPartDiff(), + new RollPartAttaque(), + new RollPartDefense(), + new RollPartMeditation(), + new RollPartSort(), + new RollPartTache(), + new RollPartOeuvre(), + new RollPartJeu(), + + new RollPartSign(), + + new RollPartEtat(), + new RollPartConditions(), + new RollPartEthylisme(), + new RollPartMalusArmure(), + new RollPartEncTotal(), + new RollPartSurEnc(), + new RollPartAppelMoral(), + new RollPartMoral(), + new RollPartCoeur(), + new RollPartAstrologique(), + new RollPartTricher(), + new RollPartRollMode(), +] + +/** + * Extend the base Dialog entity to select roll parameters + * @extends {Dialog} + * # Principes + * - une seule fenêtre de dialogue (classe RollDialog) + * - plusieurs modes de fonctionnement (classe RollMode) + * - gestion uniforme des modificateurs (classe RollPart) + * - un objet rollData contient les informations liées à un jet de dés + * - un rollData doit pouvoir être "réduit" pour fournir les informations significatives + * d'un jet de dés + * - un rollData réduit doit pouvoir être complété pour afficher la même fenêtre + * - un rollData réduit sera utilisé pour piloter l'ouverture de la fenêtre + * + * - TODO: une classe de base RollChatMessage gerera les messages correspondant aux résultats du dés + * - TODO: réfléchir aux messages supplémentaires gérés par RdDCombat ? + * + * ## Modes de fonctionnement - RollMode + * + * Un mode de fonctionnement (RollMode) détermine quelles parties (RollPart) de la + * fenêtre RollDialog sont actives, mais aussi quels sont les effets du jet. + * + * - chaque mode de fonctionnement peut impacter les RollPart utilisés, les données + * attendues et ajoutées au rollData. + * - chaque mode de fonctionnement peut définir le template de ChatMessage correspondant + * - Le mode de fonctionnement détermine aussi quelles sont les effets du jet: + * - quelle ChatMessage afficher dans le tchat? + * - en cas d'attaque/de défense, quelles sont les suites à donner? + * - en cas de lancement de sort, réduire les points de rêve + * - en cas de méditation, créer le signe draconique + * - en cas de tâche, ajuster les points de tâche + * + * + * ## Modificateurs - RollPart + * - Chaque modificateur a: + * - un code (comp, carac, diff, ...) + * - une partie dédiée pour sauvegarder son contexte + * - le contexte d'un RollPart est stocké dans le rollData de la fenêtre RollDialog, + * dans des parties dédiés: + * - `rollData.refs[code]` pour les données de référentiel (liste de compétences, ...) + * - `rollData.current[code]` pour les informations d'état courante (la compétence sélectionnée, ...) + * - `rollData.selected[code]` pour les informations à sauvegarder, et utilisées pour paramétrer l'ouverture + * - Chaque RollPart gère ses données dans cet espace dédié. + * - Chaque RollPart a un sous-template dédié, et indique où il doit s'afficher dans le RollDialog + * - Chaque RollPart peut enregistrer ses propres events handlers pour mettre à jour son contexte (et généralement réafficher le RollDialo) + * - Chaque RollPart fournit les informations contextuelles associées au jet + * - TODO: chaque RollPart peut fournir un sous-template pour le ChatMessage correspondant au résultat du dé. + * + * ## boucle de rétroaction + * Lors de l'affichage, chaque RollPart peut fournir un filtre pour les autres RollParts. + * Ce filtre sert principalement à filtrer les caractéristiques/compétense. + * + * Une fois ce filtrage effectué, chaque RollPart va pouvoir modifier sa partie du contexte + * de la fenêtre, permettant à son template hbs d'avoir les donnéers à afficher. + * + * Enfin, lors de l'affichage (vu que les contrêles sont réaffichés), il peut + * enregistrer les listeners appropriés. + * + * ## Utilisation des informations sélectionnées + * + * Le rollData est la structure de stockage, et sert à préparer le jet de dé. + * Le résultat du jet est stocké dans le noeud `rollData.rolled` (comme pour + * la première version de jets de dés) + * + * + * # TODO + * - intégration pour un jet (oeuvres / tâches / méditation / compétence) + * - RdDRollResult V2 (affichage avec templates basés sur roll-dialog) + * - Extraction de jet résumé (pour appel chance) + * - gestion significative + * - Attaque + * - Défense + * - intégration rdd-combat + * - combat rencontres + * + */ +/* -------------------------------------------- */ +export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2) +{ + + static init() { + } + + static onReady() { + + foundry.applications.handlebars.loadTemplates({ + 'roll-section': 'systems/foundryvtt-reve-de-dragon/templates/roll/roll-section.hbs', + 'roll-mode': 'systems/foundryvtt-reve-de-dragon/templates/roll/roll-mode.hbs', + 'roll-table': 'systems/foundryvtt-reve-de-dragon/templates/roll/roll-table.hbs', + 'roll-ajustements': 'systems/foundryvtt-reve-de-dragon/templates/roll/roll-ajustements.hbs', + 'roll-chances': 'systems/foundryvtt-reve-de-dragon/templates/roll/roll-chances.hbs', + 'roll-button': 'systems/foundryvtt-reve-de-dragon/templates/roll/roll-button.hbs', + }) + + foundry.applications.handlebars.loadTemplates(ROLL_MODE_TABS.map(m => m.template)) + foundry.applications.handlebars.loadTemplates(ROLL_PARTS.map(p => p.template)) + ROLL_PARTS.forEach(p => p.onReady()) + + Handlebars.registerHelper('roll-centered-array', (base, show) => RollDialog.centeredArray(base, show)) + Handlebars.registerHelper('roll-list-item-value', (list, key, path = undefined) => { + const selected = list.find(p => p.key == key) + if (selected && path && path != '') { + return foundry.utils.getProperty(selected, path) + } + return selected + }) + Handlebars.registerHelper('roll-part-context', (rollData, code) => { + const rollPart = ROLL_PARTS.find(it => it.code == code) + if (rollPart == undefined) { + return {} + } + return { + code: code, + name: rollPart.name, + template: rollPart.template, + rollData: rollData, + refs: rollPart.getRefs(rollData), + current: rollPart.getCurrent(rollData) + } + }) + } + + static centeredArray(base, show) { + show = Math.abs(show) + const start = base - show + return [...Array(2 * show + 1).keys()].map(it => start + it) + } + + static async create(rollData, rollOptions = {}) { + const rollDialog = new RollDialog(rollData, rollOptions) + rollDialog.render(true) + } + + static get PARTS() { + return { form: { template: 'systems/foundryvtt-reve-de-dragon/templates/roll/roll-dialog.hbs', } } + } + + static get DEFAULT_OPTIONS() { + const default_options = { + tag: "form", + form: { + handler: RollDialog.handler, + submitOnChange: false, + closeOnSubmit: false + }, + position: { + width: 600, + height: "auto", + }, + } + return default_options + } + + static async handler(event, form, formData) { + // rien pour l'instant + } + + constructor(rollData, rollOptions) { + super() + this.rollData = rollData + // const callbacks = this.rollOptions.callbacks.map(c => + // r => r.activve.actor Promise.all(this.rollOptions.callbacks.map(async callback => await callback(rollData.active.actor, rollData))) + // ) + this.rollOptions = { + callbacks: [ + async (actor, r) => await actor.appliquerAjoutExperience(r), + async (actor, r) => await actor.appliquerAppelMoral(r), + ...(rollOptions.callbacks ?? []) + ], + customChatMessage: rollOptions.customChatMessage, + onRoll: rollOptions.onRoll ?? doNothing + } + this.$loadParts() + } + + /** pre-configure les paramètres des différentes parties de la fenêtre (par exemple, prépare les listes de caractéristiques/compétences */ + $loadParts() { + const rollData = this.rollData; + const loadedMode = rollData.mode?.current + rollData.current = rollData.current ?? {} + rollData.selected = rollData.selected ?? {} + rollData.mode = rollData.mode ?? {} + rollData.mode.retry = rollData.mode.retry ?? false + BASIC_PARTS.restore(rollData) + + rollData.mode.allowed = rollData.mode.retry ? [loadedMode] : rollData.mode.allowed ?? ROLL_MODE_TABS.map(m => m.code) + rollData.mode.current = loadedMode ?? ROLL_MODE_TABS.find(m => m.isAllowed(rollData) && m.visible(rollData))?.code ?? ROLL_MODE_COMP + this.getSelectedMode().setRollDataMode(rollData) + + rollData.refs = this.$prepareRefs(rollData) + rollData.options = rollData.options ?? { showDice: true, rollMode: game.settings.get("core", "rollMode") } + + ROLL_PARTS.forEach(p => p.initialize(rollData)) + ROLL_PARTS.forEach(p => p.restore(rollData)) + ROLL_PARTS.filter(p => p.isValid(rollData)) + .forEach(p => { + p.loadRefs(rollData) + p.prepareContext(rollData) + }) + this.selectMode(); + } + + selectMode() { + this.rollData.mode.label = this.getSelectedMode().title(this.rollData) + this.getSelectedMode().setRollDataMode(this.rollData) + this.getSelectedMode().onSelect(this.rollData); + } + + $prepareRefs(rollData) { + return foundry.utils.mergeObject(rollData.refs ?? {}, Object.fromEntries(ROLL_PARTS.map(p => [p.code, {}]))); + } + + $saveParts() { + const target = BASIC_PARTS.initFrom(this.rollData) + ROLL_PARTS.filter(p => p.isActive(this.rollData)) + .forEach(p => p.store(this.rollData, target)) + return target + } + + getActiveParts() { + return ROLL_PARTS.filter(p => p.isActive(this.rollData)) + } + + get title() { + return this.rollData.title ?? `Jet de dés de ${this.rollData.active.actor.name}` + } + + async _onRender(context, options) { + this.window.title.innerText = this.rollTitle(this.rollData) + const buttonRoll = this.element.querySelector(`button[name="roll-dialog-button"]`) + buttonRoll?.addEventListener( + "click", e => { + e.preventDefault() + this.roll() + } + ) + const buttonsMode = this.element.querySelectorAll(`button[name="roll-mode"]`) + buttonsMode?.forEach(it => it.addEventListener( + "click", e => { + e.preventDefault() + this.rollData.mode.current = e.currentTarget.dataset.mode + this.selectMode() + this.render() + } + )) + + Promise.all( + this.getActiveParts().map(async p => await p._onRender(this, context, options)) + ) + } + + getAjustements() { + return this.getActiveParts() + .map(p => p.getAjustements(this.rollData)) + .reduce((a, b) => a.concat(b)) + .sort((a, b) => a.diff == undefined ? 1 : b.diff == undefined ? -1 : 0) + } + + async buildHTMLTable(carac, diff) { + return await foundry.applications.handlebars.renderTemplate('roll-table', { carac, diff }) + } + + + async _prepareContext() { + const rollData = this.rollData + + const modes = ROLL_MODE_TABS.filter(m => m.isAllowed(rollData) && m.visible(rollData)) + .map(m => m.toModeData(rollData)) + this.setModeTitle() + + const visibleRollParts = this.getActiveParts() + visibleRollParts.forEach(p => p.setExternalFilter(visibleRollParts, rollData)) + + this.setSpecialComp(visibleRollParts); + + visibleRollParts.forEach(p => p.prepareContext(rollData)) + + this.calculAjustements() + + const templates = this.getActiveParts().map(p => p.toTemplateData()) + const context = await super._prepareContext() + return foundry.utils.mergeObject( + { + modes: modes, + templates: templates, + rollData: rollData, + }, context) + } + + setSpecialComp(visibleRollParts) { + const specialComp = visibleRollParts.map(p => p.getSpecialComp(this.rollData)) + .reduce((a, b) => a.concat(b)) + if (specialComp.length > 0) { + const rollPartComp = this.getActiveParts() + .find(it => it.code == PART_COMP); + rollPartComp?.setSpecialComp(this.rollData, specialComp) + } + } + + calculAjustements() { + this.rollData.ajustements = this.getAjustements() + this.rollData.ajustements.forEach(it => it.isDiff = it.diff != undefined) + this.rollData.current.totaldiff = this.rollData.ajustements + .map(adj => adj.diff) + .filter(d => d != undefined) + .reduce(Misc.sum(), 0) + } + + setModeTitle() { + this.rollData.mode.label = this.getSelectedMode()?.title(this.rollData) + } + + getSelectedMode() { + return ROLL_MODE_TABS.find(m => m.code == this.rollData.mode.current) + } + + async roll() { + this.calculAjustements() + const rollData = this.rollData + console.info('Roll parts:', this.$saveParts()) + const rolled = await this.$rollDice(rollData) + rollData.rolled = rolled + Promise.all(this.rollOptions.callbacks.map(async callback => await callback(rollData.active.actor, rollData))) + if (!this.rollOptions.customChatMessage) { + rollData.active.actor.$onRollCompetence(this.rollData) + } + this.rollOptions.onRoll(this) + } + + + async defaultCallback(rollData, rolled) { + await rollData.active.actor.appliquerAjoutExperience(rollData) + await rollData.active.actor.appliquerAppelMoral(rollData) + } + + async $rollDice(rollData) { + const adapter = new RollDialogAdapter(ROLL_PARTS); + return await adapter.rollDice(rollData, this.rollTitle(rollData)); + } + + rollTitle(rollData) { + return ROLL_PARTS + .filter(it => it.section == ROLLDIALOG_SECTION.ACTION) + .filter(it => it.isActive(rollData)) + .map(it => it.title(rollData)) + .reduce(Misc.joining(' ')) + } +} diff --git a/module/roll/roll-mode-attaque.mjs b/module/roll/roll-mode-attaque.mjs new file mode 100644 index 00000000..c40c1743 --- /dev/null +++ b/module/roll/roll-mode-attaque.mjs @@ -0,0 +1,13 @@ +import { DIFF_MODE, ROLL_MODE_ATTAQUE } from "./roll-constants.mjs" +import { RollMode } from "./roll-mode.mjs" + +export class RollModeAttaque extends RollMode { + get code() { return ROLL_MODE_ATTAQUE } + get name() { return `Attaquer` } + + title(rollData) { return `attaque` } + + onSelect(rollData) { + this.setDiffMode(rollData, DIFF_MODE.ATTAQUE) + } +} \ No newline at end of file diff --git a/module/roll/roll-mode-comp.mjs b/module/roll/roll-mode-comp.mjs new file mode 100644 index 00000000..ebc4389a --- /dev/null +++ b/module/roll/roll-mode-comp.mjs @@ -0,0 +1,9 @@ +import { ROLL_MODE_COMP } from "./roll-constants.mjs" +import { RollMode } from "./roll-mode.mjs" + +export class RollModeComp extends RollMode { + get code() { return ROLL_MODE_COMP } + get name() { return `Jet de caractéristique / compétence` } + + title(rollData) { return `fait un jet ${rollData.mode.opposed ? ' contre ' : ''}` } +} \ No newline at end of file diff --git a/module/roll/roll-mode-defense.mjs b/module/roll/roll-mode-defense.mjs new file mode 100644 index 00000000..526c5659 --- /dev/null +++ b/module/roll/roll-mode-defense.mjs @@ -0,0 +1,17 @@ +import { DIFF_MODE, ROLL_MODE_DEFENSE } from "./roll-constants.mjs" +import { RollMode } from "./roll-mode.mjs" + +export class RollModeDefense extends RollMode { + get code() { return ROLL_MODE_DEFENSE } + get name() { return `Se défendre` } + + title(rollData) { return `se défend${rollData.attacker ? ' de' : ''}` } + + getOpponent(rollData) { + return rollData.attacker + } + + onSelect(rollData) { + this.setDiffMode(rollData, DIFF_MODE.DEFENSE) + } +} \ No newline at end of file diff --git a/module/roll/roll-mode-jeu.mjs b/module/roll/roll-mode-jeu.mjs new file mode 100644 index 00000000..a6fea166 --- /dev/null +++ b/module/roll/roll-mode-jeu.mjs @@ -0,0 +1,17 @@ +import { PART_JEU } from "./roll-part-jeu.mjs" +import { RollMode } from "./roll-mode.mjs" +import { ROLL_MODE_JEU } from "./roll-constants.mjs" + +export class RollModeJeu extends RollMode { + get code() { return ROLL_MODE_JEU } + get name() { return `Jouer` } + + visible(rollData) { return rollData.active.actor.isPersonnage() } + title(rollData) { + if (rollData.opponent) { + return `joue contre` + } + return `joue: ${rollData.current[PART_JEU].label}` + } + +} \ No newline at end of file diff --git a/module/roll/roll-mode-meditation.mjs b/module/roll/roll-mode-meditation.mjs new file mode 100644 index 00000000..e225f2db --- /dev/null +++ b/module/roll/roll-mode-meditation.mjs @@ -0,0 +1,19 @@ +import { DIFF_MODE, ROLL_MODE_MEDITATION } from "./roll-constants.mjs" +import { PART_MEDITATION } from "./roll-part-meditation.mjs" +import { RollMode } from "./roll-mode.mjs" + +export class RollModeMeditation extends RollMode { + get code() { return ROLL_MODE_MEDITATION } + get name() { return `Méditation draconique` } + + visible(rollData) { return rollData.active.actor.isHautRevant() } + title(rollData) { + const current = rollData.current[PART_MEDITATION] + const theme = current?.meditation.system.theme + return theme ? 'médite sur ' + theme : 'médite' + } + + onSelect(rollData) { + this.setDiffMode(rollData, DIFF_MODE.AUCUN) + } +} \ No newline at end of file diff --git a/module/roll/roll-mode-oeuvre.mjs b/module/roll/roll-mode-oeuvre.mjs new file mode 100644 index 00000000..03efa62c --- /dev/null +++ b/module/roll/roll-mode-oeuvre.mjs @@ -0,0 +1,19 @@ +import { DIFF_MODE, ROLL_MODE_OEUVRE } from "./roll-constants.mjs" +import { PART_OEUVRE } from "./roll-part-oeuvre.mjs" +import { RollMode } from "./roll-mode.mjs" + +export class RollModeOeuvre extends RollMode { + get code() { return ROLL_MODE_OEUVRE } + get name() { return `Interpréter une oeuvre` } + + visible(rollData) { return rollData.active.actor.isPersonnage() } + title(rollData) { + const current = rollData.current[PART_OEUVRE] + return `${current.art.action} ${current.label}` + } + + onSelect(rollData) { + this.setDiffMode(rollData, DIFF_MODE.AUCUN) + } +} + diff --git a/module/roll/roll-mode-sort.mjs b/module/roll/roll-mode-sort.mjs new file mode 100644 index 00000000..08d2fcae --- /dev/null +++ b/module/roll/roll-mode-sort.mjs @@ -0,0 +1,15 @@ +import { DIFF_MODE, ROLL_MODE_SORT } from "./roll-constants.mjs" +import { RollMode } from "./roll-mode.mjs" +import { PART_SORT } from "./roll-part-sort.mjs" + +export class RollModeSort extends RollMode { + get code() { return ROLL_MODE_SORT } + get name() { return `lancer un sort` } + + visible(rollData) { return rollData.active.actor.isHautRevant() } + title(rollData) { return `lance le sort:` } + + onSelect(rollData) { + this.setDiffMode(rollData, DIFF_MODE.AUCUN) + } +} \ No newline at end of file diff --git a/module/roll/roll-mode-tache.mjs b/module/roll/roll-mode-tache.mjs new file mode 100644 index 00000000..b3606c4f --- /dev/null +++ b/module/roll/roll-mode-tache.mjs @@ -0,0 +1,19 @@ +import { DIFF_MODE, ROLL_MODE_TACHE } from "./roll-constants.mjs" +import { PART_TACHE } from "./roll-part-tache.mjs" +import { RollMode } from "./roll-mode.mjs" + +export class RollModeTache extends RollMode { + get code() { return ROLL_MODE_TACHE } + get name() { return `Travailler à une tâche` } + + visible(rollData) { return rollData.active.actor.isPersonnage() } + title(rollData) { + const current = rollData.current[PART_TACHE] + const tache = current?.tache + return `travaille à sa tâche: ${tache.name ?? ''}` + } + + onSelect(rollData) { + this.setDiffMode(rollData, DIFF_MODE.AUCUN) + } +} \ No newline at end of file diff --git a/module/roll/roll-mode.mjs b/module/roll/roll-mode.mjs new file mode 100644 index 00000000..522e300e --- /dev/null +++ b/module/roll/roll-mode.mjs @@ -0,0 +1,54 @@ +import { DIFF_MODE } from "./roll-constants.mjs" +import { PART_DIFF } from "./roll-part-diff.mjs" + +const DEFAULT_DIFF_MODES = [DIFF_MODE.LIBRE, DIFF_MODE.IMPOSEE, DIFF_MODE.DEFAUT] + +export class RollMode { + + onReady() { } + + get code() { throw new Error(`Pas de code défini pour ${this}`) } + get name() { return this.code } + get icon() { return `systems/foundryvtt-reve-de-dragon/assets/actions/${this.code}.svg` } + + toModeData(rollData) { + return { code: this.code, name: this.name, icon: this.icon, section: 'mode', template: this.template, selected: this.isSelected(rollData) } + } + + isAllowed(rollData) { return rollData.mode.allowed == undefined || rollData.mode.allowed.includes(this.code) } + visible(rollData) { return true } + + title(rollData) { return this.code } + isSelected(rollData) { return rollData.mode.current == this.code } + + setRollDataMode(rollData) { + rollData.mode.opposed = rollData.opponent != undefined + rollData.mode.resistance = false /** TODO */ + } + + onSelect(rollData) { + const mode = [ + rollData.current[PART_DIFF].mode, + this.modeFromOpponents(rollData), + rollData.selected[PART_DIFF].mode].find(m => DEFAULT_DIFF_MODES.includes(m)) + + this.setDiffMode(rollData, mode ?? + DIFF_MODE.DEFAUT) + } + + + modeFromOpponents(rollData) { + if (rollData.mode.opposed) { + if (rollData.mode.resistance) { + return DIFF_MODE.IMPOSEE + } + return DIFF_MODE.LIBRE + } + return undefined + } + + setDiffMode(rollData, mode) { + rollData.current[PART_DIFF].mode = mode + this.setRollDataMode(rollData) + } +} diff --git a/module/roll/roll-part-action.mjs b/module/roll/roll-part-action.mjs new file mode 100644 index 00000000..6b0c568b --- /dev/null +++ b/module/roll/roll-part-action.mjs @@ -0,0 +1,19 @@ +import { ROLLDIALOG_SECTION, RollPart } from "./roll-part.mjs" + +export const PART_ACTION = "action" + +export class RollPartAction extends RollPart { + + get code() { return PART_ACTION } + get section() { return ROLLDIALOG_SECTION.ACTION } + + title(rollData) { + return rollData.mode.label + } + + prepareContext(rollData) { + const current = this.getCurrent(rollData) + current.verb = rollData.mode.label + } + +} diff --git a/module/roll/roll-part-actor.mjs b/module/roll/roll-part-actor.mjs new file mode 100644 index 00000000..56e7faf4 --- /dev/null +++ b/module/roll/roll-part-actor.mjs @@ -0,0 +1,11 @@ +import { ROLLDIALOG_SECTION, RollPart } from "./roll-part.mjs" + +export const PART_ACTOR = "actor" + +export class RollPartActor extends RollPart { + + get code() { return PART_ACTOR } + get section() { return ROLLDIALOG_SECTION.ACTION } + + title(rollData) { return rollData.active.name } +} diff --git a/module/roll/roll-part-appelmoral.mjs b/module/roll/roll-part-appelmoral.mjs new file mode 100644 index 00000000..25a633db --- /dev/null +++ b/module/roll/roll-part-appelmoral.mjs @@ -0,0 +1,42 @@ +import { RdDCarac } from "../rdd-carac.js" +import { RollPartCheckbox } from "./roll-part-checkbox.mjs" + +export const PART_APPELMORAL = "appelmoral" + +export class RollPartAppelMoral extends RollPartCheckbox { + + get code() { return PART_APPELMORAL } + get useCheckboxTemplate() { return false } + get isDefaultChecked() { return false } + + visible(rollData) { + return rollData.active.actor.isPersonnage() && RdDCarac.isActionPhysique(rollData.current.carac.key) + } + + restore(rollData) { + this.getCurrent(rollData).checked = this.getSaved(rollData).checked ?? false + } + + store(rollData, targetData) { + this.setSaved(targetData, { checked: this.getCurrent(rollData).checked }) + } + + loadRefs(rollData) { + const refs = this.getRefs(rollData) + refs.moral = rollData.active.actor.getMoralTotal() + refs.label = refs.moral > 0 ? "Appel au moral" : "Énergie du désespoir" + } + + getCheckboxIcon(rollData) { + const refs = this.getRefs(rollData) + if (refs.moral > 0) { + return '' + } + if (refs.moral < 0) { + return '' + } + return '' + } + getCheckboxLabel(rollData) { return "Appel au moral" } + getCheckboxValue(rollData) { return 1 } +} diff --git a/module/roll/roll-part-astrologique.mjs b/module/roll/roll-part-astrologique.mjs new file mode 100644 index 00000000..8c2519d5 --- /dev/null +++ b/module/roll/roll-part-astrologique.mjs @@ -0,0 +1,33 @@ +import { Grammar } from "../grammar.js" +import { ReglesOptionnelles } from "../settings/regles-optionnelles.js" +import { RollPartCheckbox } from "./roll-part-checkbox.mjs" + +export const PART_ASTROLOGIQUE = "astrologique" + +export class RollPartAstrologique extends RollPartCheckbox { + + get code() { return PART_ASTROLOGIQUE } + get useCheckboxTemplate() { return false } + + visible(rollData) { + return this.$isUsingAstrologie() && ( + this.isJetChance(rollData) + || this.isLancementRituel(rollData) + ) + } + + isLancementRituel(rollData) { + return false + } + + isJetChance(rollData) { + return Grammar.includesLowerCaseNoAccent(rollData.current.carac.key, 'chance') + } + + $isUsingAstrologie() { + return ReglesOptionnelles.isUsing("astrologie") + } + + getCheckboxLabel(rollData) { return "Astrologique" } + getCheckboxValue(rollData) { return rollData.active.actor.ajustementAstrologique() } +} diff --git a/module/roll/roll-part-attaque.mjs b/module/roll/roll-part-attaque.mjs new file mode 100644 index 00000000..db627c7f --- /dev/null +++ b/module/roll/roll-part-attaque.mjs @@ -0,0 +1,66 @@ +import { Grammar } from "../grammar.js" +import { ROLL_MODE_ATTAQUE } from "./roll-constants.mjs" +import { PART_CARAC } from "./roll-part-carac.mjs" +import { PART_COMP } from "./roll-part-comp.mjs" +import { RollPartSelect } from "./roll-part-select.mjs" +import { ROLLDIALOG_SECTION, RollPart } from "./roll-part.mjs" + +export const PART_ATTAQUE = 'attaque' + +export class RollPartAttaque extends RollPartSelect { + + get code() { return PART_ATTAQUE } + get section() { return ROLLDIALOG_SECTION.CHOIX } + + visible(rollData) { return this.isRollMode(rollData, ROLL_MODE_ATTAQUE) } + + loadRefs(rollData) { + const refs = this.getRefs(rollData) + const attaques = rollData.active.actor.listAttaques() + refs.attaques = attaques.map(it => RollPartAttaque.$extractAttaque(it, rollData.active.actor)) + if (refs.attaques.length>0){ + this.$selectAttaque(rollData) + } + } + + choices(refs) { return refs.attaques } + + static $extractAttaque(action, actor) { + return { + key: `${action.action}::${action.arme.id}::${action.comp.id}`, + label: action.name, + action: action, + arme: action.arme, + comp: action.comp, + } + } + + $selectAttaque(rollData, key) { + this.selectByKey(rollData, key, 0) + } + + async _onRender(rollDialog, context, options) { + const selectAttaque = rollDialog.element.querySelector(`roll-section[name="${this.code}"] select[name="select-attaque"]`) + + selectAttaque.addEventListener("change", e => { + const selectOptions = e.currentTarget.options + const index = selectOptions.selectedIndex + this.$selectAttaque(rollDialog.rollData, selectOptions[index]?.value) + rollDialog.setModeTitle() + rollDialog.render() + }) + } + + getExternalPartsFilter(partCode, rollData) { + if (this.visible(rollData)) { + const current = this.getCurrent(rollData) + switch (partCode) { + case PART_CARAC: return p => Grammar.equalsInsensitive(current.action.carac.key, p.key) + case PART_COMP: return p => p.label == current.comp?.name + } + } + return undefined + } + + +} diff --git a/module/roll/roll-part-carac.mjs b/module/roll/roll-part-carac.mjs new file mode 100644 index 00000000..09171d32 --- /dev/null +++ b/module/roll/roll-part-carac.mjs @@ -0,0 +1,66 @@ +import { RollPartSelect } from "./roll-part-select.mjs" +import { ROLLDIALOG_SECTION } from "./roll-part.mjs" + +export const PART_CARAC = "carac" + +export class RollPartCarac extends RollPartSelect { + /** TODO: remplacer selectOption par une sorte de sélecteur plus sympa? */ + + get code() { return PART_CARAC } + get name() { return 'Caractéristiques' } + get section() { return ROLLDIALOG_SECTION.CARAC } + + loadRefs(rollData) { + const refs = this.getRefs(rollData) + refs.all = this.$getActorCaracs(rollData) + refs.caracs = refs.all + this.$selectCarac(rollData) + } + + choices(refs) { return refs.caracs } + + $getActorCaracs(rollData) { + return Object.entries(rollData.active.actor.getCarac()) + .filter(([key, c]) => key != 'taille') + /* TODO: filter by context */ + .map(([key, carac]) => { + return RollPartCarac.$extractCarac(key, carac) + }) + } + + static $extractCarac(key, carac) { + return { + key: key, + label: carac.label, + value: parseInt(carac.value) + } + } + + setFilter(rollData, filter) { + const refs = this.getRefs(rollData) + refs.caracs = refs.all.filter(filter) + } + + prepareContext(rollData) { + this.$selectCarac(rollData) + } + + getAjustements(rollData) { + return [] + } + + async _onRender(rollDialog, context, options) { + const select = rollDialog.element.querySelector(`roll-section[name="${this.code}"] select`) + + select?.addEventListener("change", async e => { + const selectOptions = e.currentTarget.options + const index = selectOptions.selectedIndex + this.$selectCarac(rollDialog.rollData, selectOptions[index]?.value) + rollDialog.render() + }) + } + + $selectCarac(rollData, key) { + this.selectByKey(rollData, key, 10) + } +} diff --git a/module/roll/roll-part-checkbox.mjs b/module/roll/roll-part-checkbox.mjs new file mode 100644 index 00000000..1f8ae1e1 --- /dev/null +++ b/module/roll/roll-part-checkbox.mjs @@ -0,0 +1,61 @@ +import { ROLLDIALOG_SECTION, RollPart } from "./roll-part.mjs" + +export class RollPartCheckbox extends RollPart { + + get section() { return ROLLDIALOG_SECTION.CONDITIONS } + + get useCheckboxTemplate() { return true } + get template() { return this.useCheckboxTemplate ? 'systems/foundryvtt-reve-de-dragon/templates/roll/roll-part-checkbox.hbs' : super.template } + get isDefaultChecked() { return true } + + restore(rollData) { + const checked = this.getSaved(rollData).checked + this.getCurrent(rollData).checked = checked == undefined? this.isDefaultChecked : checked + } + + store(rollData, targetData) { + this.setSaved(targetData, { checked: this.getCurrent(rollData).checked }) + } + + loadRefs(rollData) { + const current = this.getCurrent(rollData) + current.label = this.getCheckboxLabel(rollData) + } + + prepareContext(rollData) { + const current = this.getCurrent(rollData) + if (current.checked == undefined) { + /* TODO: user setting? */ + current.checked = true + } + if (current.value == undefined) { + current.value = this.getCheckboxValue(rollData) + } + current.icon = this.getCheckboxIcon(rollData) + } + + getAjustements(rollData) { + const current = this.getCurrent(rollData) + if (current.checked) { + return [{ label: this.getCheckboxLabelAjustement(rollData), diff: current.value }] + } + return [] + } + + getCheckboxLabelAjustement(rollData) { + return `${this.getCheckboxIcon(rollData)} ${this.getCurrent(rollData).label}` + } + + async _onRender(rollDialog, context, options) { + const checkbox = rollDialog.element.querySelector(`roll-section[name="${this.code}"] input[name="${this.code}"]`) + + checkbox?.addEventListener("change", e => { + this.getCurrent(rollDialog.rollData).checked = e.currentTarget.checked + rollDialog.render() + }) + } + + getCheckboxIcon(rollData) { return '' } + getCheckboxLabel(rollData) { return "LABEL" } + getCheckboxValue(rollData) { return 0 } +} diff --git a/module/roll/roll-part-coeur.mjs b/module/roll/roll-part-coeur.mjs new file mode 100644 index 00000000..4465ae82 --- /dev/null +++ b/module/roll/roll-part-coeur.mjs @@ -0,0 +1,66 @@ +import { RdDCarac } from "../rdd-carac.js" +import { RollPartSelect } from "./roll-part-select.mjs" +import { ROLLDIALOG_SECTION, RollPart } from "./roll-part.mjs" + +const COEUR = "coeur" + +const SANS_AMOUR = { key: '', label: "", value: 0 } + +export class RollPartCoeur extends RollPartSelect { + + get code() { return COEUR } + get section() { return ROLLDIALOG_SECTION.CONDITIONS } + + get useCheckboxTemplate() { return false } + + isValid(rollData) { return rollData.active.actor.isPersonnage() } + visible(rollData) { + return this.getRefs(rollData).amoureux.length > 1 && RdDCarac.isVolonte(rollData.current.carac.key) + } + + loadRefs(rollData) { + const liste = rollData.active.actor.listeAmoureux() + .filter(amour => amour.coeur > 0) + .map(RollPartCoeur.$extractAmoureux) + + this.getRefs(rollData).amoureux = [SANS_AMOUR, ...liste] + this.$selectAmoureux(rollData) + } + + choices(refs) { return refs.amoureux } + + static $extractAmoureux(amour) { + return { + key: amour.id, + label: amour.name, + value: -2 * (amour?.coeur ?? 0), + amour: amour + } + } + + $selectAmoureux(rollData, key) { + this.selectByKey(rollData, key, 0) + } + + getAjustements(rollData) { + const current = this.getCurrent(rollData) + if (current.key != '') { + return [{ + label: "Coeur pour " + current.label, + diff: current.value + }] + } + return [] + } + + async _onRender(rollDialog, context, options) { + const selectAmour = rollDialog.element.querySelector(`roll-section[name="${this.code}"] select[name="${this.code}"]`) + + selectAmour?.addEventListener("change", e => { + const selectOptions = e.currentTarget.options + const index = selectOptions.selectedIndex + this.$selectAmoureux(rollDialog.rollData, selectOptions[index]?.value) + rollDialog.render() + }) + } +} \ No newline at end of file diff --git a/module/roll/roll-part-comp.mjs b/module/roll/roll-part-comp.mjs new file mode 100644 index 00000000..78673637 --- /dev/null +++ b/module/roll/roll-part-comp.mjs @@ -0,0 +1,77 @@ +import { Grammar } from "../grammar.js" +import { Misc } from "../misc.js" +import { RollPartSelect } from "./roll-part-select.mjs" +import { ROLLDIALOG_SECTION } from "./roll-part.mjs" + +export const PART_COMP = "comp" + +const SANS_COMPETENCE = { key: '', label: "Sans compétence", value: 0 } + +export class RollPartComp extends RollPartSelect { + + /** TODO: remplacer selectOption par un sélecteur plus sympa (avec image de compétence, par exemple? */ + + get code() { return PART_COMP } + get name() { return 'Compétences' } + get section() { return ROLLDIALOG_SECTION.COMP } + + loadRefs(rollData) { + const refs = this.getRefs(rollData) + refs.all = this.$getActorComps(rollData) + refs.comps = refs.all + this.$selectComp(rollData) + } + + choices(refs) { return refs.comps } + + $getActorComps(rollData) { + const competences = (rollData.active.actor?.getCompetences() ?? []) + .map(RollPartComp.$extractComp) + .sort(Misc.ascending(it => Grammar.toLowerCaseNoAccentNoSpace(it.label))) + /* TODO: filter competences */ + const listCompetences = [ + SANS_COMPETENCE, + ...competences + ] + return listCompetences + } + + static $extractComp(comp) { + return { + key: comp.name, + label: comp.name, + value: comp.system.niveau, + comp: comp + } + } + + setFilter(rollData, filter) { + const refs = this.getRefs(rollData) + refs.comps = refs.all.filter(filter) + } + + prepareContext(rollData) { + this.$selectComp(rollData) + } + + setSpecialComp(rollData, comps) { + this.getRefs(rollData).comps = comps.map(RollPartComp.$extractComp) + .sort(Misc.ascending(it => Grammar.toLowerCaseNoAccentNoSpace(it.label))) + } + + async _onRender(rollDialog, context, options) { + const select = rollDialog.element.querySelector(`roll-section[name="${this.code}"] select`) + + select?.addEventListener("change", e => { + const selectOptions = e.currentTarget.options + const index = selectOptions.selectedIndex + this.$selectComp(rollDialog.rollData, selectOptions[index]?.value) + rollDialog.render() + }) + } + + $selectComp(rollData, key) { + this.selectByKey(rollData, key, 0) + } + +} diff --git a/module/roll/roll-part-conditions.mjs b/module/roll/roll-part-conditions.mjs new file mode 100644 index 00000000..d686b047 --- /dev/null +++ b/module/roll/roll-part-conditions.mjs @@ -0,0 +1,74 @@ +import { SYSTEM_RDD } from "../constants.js"; +import { ROLLDIALOG_SECTION, RollPart } from "./roll-part.mjs"; + +const CONDITIONS = "conditions" +const DESCR_CONDITIONS = "Conditions" + +export class RollPartConditions extends RollPart { + /** TODO: use alternate to numberInput that supports displaying '+' sign */ + settingMin() { return RollPart.settingKey(this, 'min') } + settingMax() { return RollPart.settingKey(this, 'max') } + + onReady() { + game.settings.register(SYSTEM_RDD, this.settingMin(), + { + name: "Malus maximal de conditions", + type: Number, + config: true, + scope: "world", + range: { min: -20, max: -10, step: 1 }, + default: -16 + } + ) + game.settings.register(SYSTEM_RDD, this.settingMax(), + { + name: "Bonus maximal de conditions", + type: Number, + config: true, + scope: "world", + range: { min: 5, max: 15, step: 1 }, + default: 10 + } + ) + } + + restore(rollData) { + const current = this.getCurrent(rollData) + current.value = this.getSaved(rollData)?.value ?? current.value ?? 0 + } + + + store(rollData, targetData) { + this.setSaved(targetData, { value: this.getCurrent(rollData).value }) + } + + /** @override */ + get code() { return CONDITIONS } + get section() { return ROLLDIALOG_SECTION.CONDITIONS } + + prepareContext(rollData) { + const current = this.getCurrent(rollData) + current.min = game.settings.get(SYSTEM_RDD, this.settingMin()) + current.max = game.settings.get(SYSTEM_RDD, this.settingMax()) + current.value = current.value ?? 0 + } + + getAjustements(rollData) { + const current = this.getCurrent(rollData) + if (current.value != 0) { + return [{ label: DESCR_CONDITIONS, diff: current.value }] + } + return [] + } + + async _onRender(rollDialog, context, options) { + const input = rollDialog.element.querySelector(`roll-section[name="${this.code}"] input[name="${this.code}"]`) + + input?.addEventListener("change", e => { + const current = this.getCurrent(rollDialog.rollData) + current.value = parseInt(e.currentTarget.value) + rollDialog.render() + }) + } + +} \ No newline at end of file diff --git a/module/roll/roll-part-defense.mjs b/module/roll/roll-part-defense.mjs new file mode 100644 index 00000000..b924a815 --- /dev/null +++ b/module/roll/roll-part-defense.mjs @@ -0,0 +1,17 @@ +import { ROLL_MODE_DEFENSE } from "./roll-constants.mjs" +import { ROLLDIALOG_SECTION, RollPart } from "./roll-part.mjs" + +export const PART_DEFENSE = 'defense' + +export class RollPartDefense extends RollPart { + + get code() { return PART_DEFENSE } + get section() { return ROLLDIALOG_SECTION.CHOIX } + visible(rollData) { return this.isRollMode(rollData, ROLL_MODE_DEFENSE) } + + loadRefs(rollData) { + const refs = this.getRefs(rollData) + refs.defenses =[] + } + +} diff --git a/module/roll/roll-part-diff.mjs b/module/roll/roll-part-diff.mjs new file mode 100644 index 00000000..42ca4c54 --- /dev/null +++ b/module/roll/roll-part-diff.mjs @@ -0,0 +1,70 @@ +import { DIFF_MODE, DIFF_MODES, ROLL_MODE_MEDITATION, ROLL_MODE_OEUVRE, ROLL_MODE_SORT, ROLL_MODE_TACHE } from "./roll-constants.mjs"; +import { ROLLDIALOG_SECTION, RollPart } from "./roll-part.mjs"; + +export const PART_DIFF = "diff" + +const EXCLUDED_ROLL_MODES = [ROLL_MODE_TACHE, ROLL_MODE_MEDITATION, ROLL_MODE_SORT, ROLL_MODE_OEUVRE] + +export class RollPartDiff extends RollPart { + + get code() { return PART_DIFF } + get section() { return ROLLDIALOG_SECTION.CHOIX } + + restore(rollData) { + const current = this.getCurrent(rollData) + const saved = this.getSaved(rollData) + current.value = saved?.value ?? current.value ?? 0 + current.mode = saved?.mode ?? current.mode + } + + store(rollData, targetData) { + const current = this.getCurrent(rollData) + this.setSaved(targetData, { + value: current.value, + mode: current.mode + }) + } + + visible(rollData) { + if (EXCLUDED_ROLL_MODES.includes(rollData.mode.current)) { + return false + } + const current = this.getCurrent(rollData) + /* TODO: affiner les cas où afficher ou non. devrait s'afficher pour les jets basiques (même si pas d'opposant sélectionné)*/ + return Object.values(DIFF_MODE).includes(current.mode) + } + + prepareContext(rollData) { + const current = this.getCurrent(rollData) + const diffMode = DIFF_MODES[current.mode] ?? DIFF_MODES[DIFF_MODE.AUCUN] + foundry.utils.mergeObject(current, + { + mode: diffMode.key, + label: diffMode?.label ?? '', + value: current.value ?? 0, + disabled: !diffMode.libre, + min: -10, + max: diffMode.max + }, + { inplace: true } + ) + } + + getAjustements(rollData) { + const current = this.getCurrent(rollData) + return [{ + label: current.label, + diff: current.value + }] + } + + async _onRender(rollDialog, context, options) { + const input = rollDialog.element.querySelector(`roll-section[name="${this.code}"] input[name="${this.code}"]`) + + input?.addEventListener("change", e => { + this.getCurrent(rollDialog.rollData).value = parseInt(e.currentTarget.value) + rollDialog.render() + }) + } + +} \ No newline at end of file diff --git a/module/roll/roll-part-enctotal.mjs b/module/roll/roll-part-enctotal.mjs new file mode 100644 index 00000000..5595b58a --- /dev/null +++ b/module/roll/roll-part-enctotal.mjs @@ -0,0 +1,31 @@ +import { RdDItemCompetence } from "../item-competence.js" +import { RdDCarac } from "../rdd-carac.js" +import { RollPartCheckbox } from "./roll-part-checkbox.mjs" + +const ENCTOTAL = "enctotal" + +export class RollPartEncTotal extends RollPartCheckbox { + + get code() { return ENCTOTAL } + get useCheckboxTemplate() { return false } + + visible(rollData) { + return RdDCarac.isAgiliteOuDerobee(rollData.current.carac.key) + && RdDItemCompetence.isMalusEncombrementTotal(rollData.current.comp?.key) + } + + async _onRender(rollDialog, context, options) { + super._onRender(rollDialog, context, options) + + const inputMalusEnc = rollDialog.element.querySelector(`roll-section[name="${this.code}"] input[name="malusenc"]`) + + inputMalusEnc?.addEventListener("change", e => { + this.getCurrent(rollDialog.rollData).value = parseInt(e.currentTarget.value) + rollDialog.render() + }) + } + + getCheckboxIcon(rollData) { return '' } + getCheckboxLabel(rollData) { return "Enc. total" } + getCheckboxValue(rollData) { return - rollData.active.actor.getEncTotal() } +} diff --git a/module/roll/roll-part-etat.mjs b/module/roll/roll-part-etat.mjs new file mode 100644 index 00000000..4a973b44 --- /dev/null +++ b/module/roll/roll-part-etat.mjs @@ -0,0 +1,30 @@ +import { RdDCarac } from "../rdd-carac.js" +import { RollPartCheckbox } from "./roll-part-checkbox.mjs" + +const ETAT = "etat" + +export class RollPartEtat extends RollPartCheckbox { + + get code() { return ETAT } + + visible(rollData) { + const selectedCarac = rollData.current.carac?.key ?? '' + if (selectedCarac == '') { + return false + } + if (RdDCarac.isChance(selectedCarac)) { + return false + } + if (RdDCarac.isReve(selectedCarac)) { + if ((rollData.current.comp?.key ?? '') == '') { + return false + } + } + return this.getCheckboxValue(rollData) != 0 + } + + getCheckboxLabel(rollData) { return "État général" } + getCheckboxValue(rollData) { + return rollData.active.actor.getEtatGeneral({ ethylisme: true }) + } +} diff --git a/module/roll/roll-part-ethylisme.mjs b/module/roll/roll-part-ethylisme.mjs new file mode 100644 index 00000000..32ab96c4 --- /dev/null +++ b/module/roll/roll-part-ethylisme.mjs @@ -0,0 +1,28 @@ +import { RdDCarac } from "../rdd-carac.js" +import { RdDUtility } from "../rdd-utility.js" +import { RollPartCheckbox } from "./roll-part-checkbox.mjs" + +const ETHYLISME = "ethylisme" + +export class RollPartEthylisme extends RollPartCheckbox { + + get code() { return ETHYLISME } + + isValid(rollData) { return rollData.active.actor.isPersonnage()} + + visible(rollData) { + return rollData.active.actor.isAlcoolise() && !RdDCarac.isChance(rollData.current.carac.key) + } + + getCheckboxIcon(rollData) { + return '' + } + + getCheckboxLabel(rollData) { + return `${RdDUtility.getNomEthylisme(rollData.active.actor.ethylisme())}` + } + + getCheckboxValue(rollData) { + return rollData.active.actor.malusEthylisme() + } +} diff --git a/module/roll/roll-part-jeu.mjs b/module/roll/roll-part-jeu.mjs new file mode 100644 index 00000000..5dad73ff --- /dev/null +++ b/module/roll/roll-part-jeu.mjs @@ -0,0 +1,110 @@ +import { Grammar } from "../grammar.js" +import { ITEM_TYPES } from "../constants.js" +import { CARACS } from "../rdd-carac.js" +import { ROLL_MODE_JEU } from "./roll-constants.mjs" +import { PART_CARAC } from "./roll-part-carac.mjs" +import { PART_COMP } from "./roll-part-comp.mjs" +import { RollPartSelect } from "./roll-part-select.mjs" +import { ROLLDIALOG_SECTION } from "./roll-part.mjs" + +export const PART_JEU = "jeu" + +const COMPETENCE_JEU = 'Jeu' + +export class RollPartJeu extends RollPartSelect { + + get code() { return PART_JEU } + get section() { return ROLLDIALOG_SECTION.CHOIX } + + isValid(rollData) { return rollData.active.actor.isPersonnage() } + visible(rollData) { return this.isRollMode(rollData, ROLL_MODE_JEU) } + + loadRefs(rollData) { + const refs = this.getRefs(rollData) + refs.jeux = rollData.active.actor.itemTypes[ITEM_TYPES.jeu] + .map(it => RollPartJeu.$extractJeu(it, rollData.active.actor)) + if (refs.jeux.length > 0) { + this.$selectJeu(rollData) + } + } + + choices(refs) { return refs.jeux } + + static $extractJeu(jeu, actor) { + const comp = actor.getCompetence(COMPETENCE_JEU) + const caracs = jeu.system.caraccomp.toLowerCase().split(/[.,:\/-]/).map(it => it.trim()) + const base = RollPartJeu.$getJeuBase(jeu, comp, caracs) + return { + key: jeu.id, + label: jeu.name, + caracs: caracs, + jeu: jeu, + value: (base ?? comp).system.niveau, + base: base, + comp: comp + } + } + + static $getJeuBase(jeu, comp, caracs) { + if (jeu.system.base < comp.system.niveau) { + return undefined + } + return { + id: comp.id, + name: `Jeu ${jeu.name}`, + type: comp.type, + img: comp.img, + system: foundry.utils.mergeObject( + { + niveau: jeu.system.base, + base: jeu.system.base, + default_carac: caracs.length > 0 ? caracs[0] : CARACS.CHANCE + }, + comp.system, + { inplace: true, overwrite: false } + ) + } + } + + prepareContext(rollData) { + const current = this.getCurrent(rollData) + if (rollData.mode.current == ROLL_MODE_JEU && current) { + rollData.mode.opposed = true + } + } + + getAjustements(rollData) { return [] } + + $selectJeu(rollData, key) { + this.selectByKey(rollData, key, 0) + } + + async _onRender(rollDialog, context, options) { + const selectjeu = rollDialog.element.querySelector(`roll-section[name="${this.code}"] select[name="select-jeu"]`) + + selectjeu.addEventListener("change", e => { + const selectOptions = e.currentTarget.options + const index = selectOptions.selectedIndex + this.$selectJeu(rollDialog.rollData, selectOptions[index]?.value) + rollDialog.setModeTitle() + rollDialog.render() + }) + } + + getExternalPartsFilter(partCode, rollData) { + if (this.visible(rollData)) { + const current = this.getCurrent(rollData) + switch (partCode) { + case PART_CARAC: return p => current.caracs?.includes(Grammar.toLowerCaseNoAccent(p.key)) + case PART_COMP: return p => p.label == current.comp?.name + } + } + return undefined + } + + getSpecialComp(rollData) { + const current = this.getCurrent(rollData) + return current.base ? [current.base] : [] + } + +} diff --git a/module/roll/roll-part-malusarmure.mjs b/module/roll/roll-part-malusarmure.mjs new file mode 100644 index 00000000..3f150181 --- /dev/null +++ b/module/roll/roll-part-malusarmure.mjs @@ -0,0 +1,16 @@ +import { RdDCarac } from "../rdd-carac.js" +import { RollPartCheckbox } from "./roll-part-checkbox.mjs" + +const MALUSARMURE = "malusarmure" + +export class RollPartMalusArmure extends RollPartCheckbox { + + get code() { return MALUSARMURE } + + visible(rollData) { + return RdDCarac.isAgiliteOuDerobee(rollData.current.carac.key) && this.getCheckboxValue(rollData) != 0 + } + + getCheckboxLabel(rollData) { return "Malus armure" } + getCheckboxValue(rollData) { return rollData.active.actor.getMalusArmure() } +} diff --git a/module/roll/roll-part-meditation.mjs b/module/roll/roll-part-meditation.mjs new file mode 100644 index 00000000..6c765f76 --- /dev/null +++ b/module/roll/roll-part-meditation.mjs @@ -0,0 +1,117 @@ +import { ITEM_TYPES } from "../constants.js" +import { Grammar } from "../grammar.js" +import { RdDCarac } from "../rdd-carac.js" +import { RdDTimestamp } from "../time/rdd-timestamp.js" +import { TMRUtility } from "../tmr-utility.js" +import { ROLL_MODE_MEDITATION } from "./roll-constants.mjs" +import { PART_CARAC } from "./roll-part-carac.mjs" +import { PART_COMP } from "./roll-part-comp.mjs" +import { RollPartSelect } from "./roll-part-select.mjs" +import { ROLLDIALOG_SECTION } from "./roll-part.mjs" + +export const PART_MEDITATION = "meditation" + +export class RollPartMeditation extends RollPartSelect { + + get code() { return PART_MEDITATION } + get section() { return ROLLDIALOG_SECTION.CHOIX } + + isValid(rollData) { return rollData.active.actor.isPersonnage() && rollData.active.actor.isHautRevant() } + visible(rollData) { return this.isRollMode(rollData, ROLL_MODE_MEDITATION) } + + loadRefs(rollData) { + const refs = this.getRefs(rollData) + foundry.utils.mergeObject(refs, + { + meditations: rollData.active.actor.itemTypes[ITEM_TYPES.meditation] + .map(it => RollPartMeditation.$extractMeditation(it, rollData.active.actor)) + } + ) + if (refs.meditations.length > 0) { + this.$selectMeditation(rollData) + } + } + choices(refs) { return refs.meditations } + + static $extractMeditation(meditation, actor) { + return { + key: meditation.id, + label: meditation.name, + meditation: meditation, + comp: actor.getCompetence(meditation.system.competence) + } + } + + prepareContext(rollData) { + this.getCurrent(rollData).value = this.getMalusConditions(rollData) + } + + getMalusConditions(rollData) { + const current = this.getCurrent(rollData) + const conditionsManquantes = [ + current.isComportement, + current.isHeure, + current.isPurification, + current.isVeture + ].filter(it => !it).length + return -2 * conditionsManquantes + } + + getMalusEchecs(rollData) { + return this.getCurrent(rollData).meditation.system.malus + } + + getAjustements(rollData) { + const malusEchecs = { label: "Méditation", diff: this.getMalusEchecs(rollData) } + const malusConditions = { label: "Conditions", diff: this.getMalusConditions(rollData) } + return [malusConditions, ...(malusEchecs.diff == 0 ? [] : [malusEchecs])] + } + + $selectMeditation(rollData, key) { + const previous = this.getCurrent(rollData) + const current = this.selectByKey(rollData, key, 0) + if (current.key != previous.key) { + const heureMonde = RdDTimestamp.getWorldTime().heure + const heureMeditation = RdDTimestamp.findHeure(current.meditation.system.heure)?.heure + current.isHeure = heureMeditation == heureMonde + current.isTMR = Grammar.equalsInsensitive(current.meditation.system.tmr, TMRUtility.getTMRType(rollData.active.actor.system.reve.tmrpos.coord)) + } + } + + async _onRender(rollDialog, context, options) { + + const selectMeditation = rollDialog.element.querySelector(`roll-section[name="${this.code}"] select[name="select-meditation"]`) + selectMeditation.addEventListener("change", e => { + const selectOptions = e.currentTarget.options + const index = selectOptions.selectedIndex + this.$selectMeditation(rollDialog.rollData, selectOptions[index]?.value) + rollDialog.render() + rollDialog.setModeTitle() + }) + + this.setupListenerCondition(rollDialog, 'isComportement') + this.setupListenerCondition(rollDialog, 'isHeure') + this.setupListenerCondition(rollDialog, 'isPurification') + this.setupListenerCondition(rollDialog, 'isVeture') + } + + setupListenerCondition(rollDialog, inputName) { + const checkbox = rollDialog.element.querySelector(`roll-section[name="${this.code}"] input[name="${inputName}"]`) + checkbox.addEventListener("change", e => { + const current = this.getCurrent(rollDialog.rollData) + current[inputName] = e.currentTarget.checked + rollDialog.render() + }) + } + + getExternalPartsFilter(partCode, rollData) { + if (this.visible(rollData)) { + const current = this.getCurrent(rollData) + switch (partCode) { + case PART_CARAC: return p => RdDCarac.isIntellect(p.key) + case PART_COMP: return p => p.label == current.comp?.name + } + } + return undefined + } +} diff --git a/module/roll/roll-part-moral.mjs b/module/roll/roll-part-moral.mjs new file mode 100644 index 00000000..a5914be9 --- /dev/null +++ b/module/roll/roll-part-moral.mjs @@ -0,0 +1,16 @@ +import { RdDCarac } from "../rdd-carac.js" +import { RollPartCheckbox } from "./roll-part-checkbox.mjs" + +const MORAL = "moral" + +export class RollPartMoral extends RollPartCheckbox { + + get code() { return MORAL } + + visible(rollData) { + return RdDCarac.isVolonte(rollData.current.carac.key) && this.getCheckboxValue(rollData) != 0 + } + + getCheckboxLabel(rollData) { return "Moral" } + getCheckboxValue(rollData) { return rollData.active.actor.getMoralTotal() } +} diff --git a/module/roll/roll-part-oeuvre.mjs b/module/roll/roll-part-oeuvre.mjs new file mode 100644 index 00000000..97eaa782 --- /dev/null +++ b/module/roll/roll-part-oeuvre.mjs @@ -0,0 +1,99 @@ +import { ITEM_TYPES } from "../constants.js" +import { Grammar } from "../grammar.js" +import { Misc } from "../misc.js" +import { CARACS } from "../rdd-carac.js" +import { ROLL_MODE_OEUVRE } from "./roll-constants.mjs" +import { PART_CARAC } from "./roll-part-carac.mjs" +import { PART_COMP } from "./roll-part-comp.mjs" +import { RollPartSelect } from "./roll-part-select.mjs" +import { ROLLDIALOG_SECTION } from "./roll-part.mjs" + +export const PART_OEUVRE = "oeuvre" + +const ARTS = [ + { type: ITEM_TYPES.oeuvre, action: "interpréte l'oeuvre", competence: it => it.system.competence, caracs: it => [it.system.default_carac] }, + { type: ITEM_TYPES.chant, action: "chante", competence: it => 'Chant', caracs: it => [CARACS.OUIE] }, + { + type: ITEM_TYPES.danse, action: "danse:", competence: it => 'Danse', caracs: it => { + const caracs = [] + if (it.system.agilite) { caracs.push(CARACS.AGILITE) } + if (it.system.apparence) { caracs.push(CARACS.APPARENCE) } + return caracs + } + }, + { type: ITEM_TYPES.musique, action: "joue le morceau:", competence: it => 'Musique', caracs: it => [CARACS.OUIE] }, + { type: ITEM_TYPES.recettecuisine, action: "cuisine le plat:", competence: it => 'Cuisine', caracs: it => [CARACS.ODORATGOUT] }, +] + +export class RollPartOeuvre extends RollPartSelect { + onReady() { + ARTS.forEach(art => art.label = Misc.typeName('Item', art.type)) + ARTS.map(it => `roll-oeuvre-${it.type}`) + .forEach(art => + foundry.applications.handlebars.loadTemplates({ [art]: `systems/foundryvtt-reve-de-dragon/templates/roll/${art}.hbs` }) + ) + } + + get code() { return PART_OEUVRE } + get section() { return ROLLDIALOG_SECTION.CHOIX } + + isValid(rollData) { return rollData.active.actor.isPersonnage() } + visible(rollData) { return this.isRollMode(rollData, ROLL_MODE_OEUVRE) } + + loadRefs(rollData) { + const refs = this.getRefs(rollData) + refs.oeuvres = rollData.active.actor.items + .filter(it => it.isOeuvre() && RollPartOeuvre.getArt(it)) + .map(it => RollPartOeuvre.$extractOeuvre(it, rollData.active.actor)) + if (refs.oeuvres.length > 0) { + this.$selectOeuvre(rollData) + } + } + + choices(refs) { return refs.oeuvres } + + static $extractOeuvre(oeuvre, actor) { + const art = RollPartOeuvre.getArt(oeuvre) + return { + key: oeuvre.id, + label: oeuvre.name, + art: art, + caracs: art.caracs(oeuvre), + value: -oeuvre.system.niveau, + oeuvre: oeuvre, + comp: actor.getCompetence(art.competence(oeuvre)) + } + } + + static getArt(oeuvre) { + return ARTS.find(it => it.type == oeuvre.type) + } + + $selectOeuvre(rollData, key) { + this.selectByKey(rollData, key, 0) + } + + async _onRender(rollDialog, context, options) { + const selectOeuvre = rollDialog.element.querySelector(`roll-section[name="${this.code}"] select[name="select-oeuvre"]`) + + selectOeuvre.addEventListener("change", e => { + const selectOptions = e.currentTarget.options + const index = selectOptions.selectedIndex + this.$selectOeuvre(rollDialog.rollData, selectOptions[index]?.value) + rollDialog.setModeTitle() + rollDialog.render() + }) + } + + getExternalPartsFilter(partCode, rollData) { + if (this.visible(rollData)) { + const current = this.getCurrent(rollData) + switch (partCode) { + case PART_CARAC: return p => current.caracs?.includes(Grammar.toLowerCaseNoAccent(p.key)) + case PART_COMP: return p => p.label == current.comp?.name + } + } + return undefined + } + +} diff --git a/module/roll/roll-part-opponent.mjs b/module/roll/roll-part-opponent.mjs new file mode 100644 index 00000000..d4dd837f --- /dev/null +++ b/module/roll/roll-part-opponent.mjs @@ -0,0 +1,12 @@ +import { ROLLDIALOG_SECTION, RollPart } from "./roll-part.mjs" + +const OPPONENT = "opponent" + +export class RollPartOpponent extends RollPart { + + get code() { return OPPONENT } + get section() { return ROLLDIALOG_SECTION.ACTION } + + visible(rollData) { return rollData.mode.opposed } + title(rollData) { return rollData.opponent?.name ?? '' } +} diff --git a/module/roll/roll-part-rollmode.mjs b/module/roll/roll-part-rollmode.mjs new file mode 100644 index 00000000..569b1c00 --- /dev/null +++ b/module/roll/roll-part-rollmode.mjs @@ -0,0 +1,32 @@ +import { ROLLDIALOG_SECTION, RollPart } from "./roll-part.mjs" + +const ROLLMODE = "rollmode" + +export class RollPartRollMode extends RollPart { + + get code() { return ROLLMODE } + get section() { return ROLLDIALOG_SECTION.CONDITIONS } + + loadRefs(rollData) { + const refs = this.getRefs(rollData) + refs.rollmodes = Object.entries(CONFIG.Dice.rollModes).map(([k, v]) => { return { key: k, label: v.label, icon: v.icon } }) + } + + restore(rollData) { + this.setCurrent(rollData, { key: this.getSaved(rollData)?.key ?? game.settings.get("core", "rollMode") }) + } + + store(rollData, targetData) { + this.setSaved(targetData, { key: this.getCurrent(rollData).key }) + } + + async _onRender(rollDialog, context, options) { + const rollvisibilityButtons = rollDialog.element.querySelectorAll(`button[name="roll-rollmode"]`) + rollvisibilityButtons?.forEach(it => it.addEventListener( + "click", e => { + e.preventDefault() + this.getCurrent(rollDialog.rollData).key = e.currentTarget.dataset.key + rollDialog.render() + })) + } +} diff --git a/module/roll/roll-part-select.mjs b/module/roll/roll-part-select.mjs new file mode 100644 index 00000000..c8239de3 --- /dev/null +++ b/module/roll/roll-part-select.mjs @@ -0,0 +1,50 @@ +import { Grammar } from "../grammar.js" +import { RollPart } from "./roll-part.mjs" + +export class RollPartSelect extends RollPart { + + restore(rollData) { + this.setCurrent(rollData, { key: this.getSaved(rollData)?.key }) + } + + store(rollData, targetData) { + this.setSaved(targetData, { key: this.getCurrent(rollData).key }) + } + + choices(refs) { return [] } + + getAjustements(rollData) { + const current = this.getCurrent(rollData) + if (current) { + return [{ label: current.label, diff: current.value }] + } + return [] + } + + selectByKey(rollData, key = undefined, defValue = undefined) { + const refs = this.getRefs(rollData) + const choices = this.choices(refs) ??[] + const current = this.getCurrent(rollData) + const newChoice = (choices.length == 0) + ? { key: '', value: defValue } + : this.$getSelectedChoice(choices, key ?? current?.key ?? refs.key ?? '') + this.setCurrent(rollData, newChoice) + return newChoice + } + + $getSelectedChoice(choices, key) { + const potential = choices.filter(it => Grammar.includesLowerCaseNoAccent(it.key, key)) + switch (potential.length) { + case 0: + // ui.notifications.warn(`Aucun choix de ${this.name} pour ${key}`) + return choices[0] + case 1: + return potential[0] + default: + const selected = potential.find(it => Grammar.equalsInsensitive(it.key, key)) + // ui.notifications.info(`Plusieurs choix de ${this.name} pour ${key}, le premier est utilisé`) + return selected ?? potential[0] + } + } + +} diff --git a/module/roll/roll-part-sign.mjs b/module/roll/roll-part-sign.mjs new file mode 100644 index 00000000..588e5bab --- /dev/null +++ b/module/roll/roll-part-sign.mjs @@ -0,0 +1,93 @@ +import { Misc } from "../misc.js" +import { StatusEffects } from "../settings/status-effects.js" +import { ROLL_MODE_ATTAQUE, ROLL_MODE_DEFENSE } from "./roll-constants.mjs" +import { ROLLDIALOG_SECTION, RollPart } from "./roll-part.mjs" + +export const PART_SIGN = "sign" + +export class RollPartSign extends RollPart { + + get code() { return PART_SIGN } + get section() { return ROLLDIALOG_SECTION.AJUSTEMENTS } + + loadRefs(rollData) { + this.setFromState(rollData) + } + + restore(rollData) { + this.setCurrent(rollData, this.getSaved(rollData)) + } + + store(rollData, targetData) { + this.setSaved(targetData, this.getCurrent(rollData)) + } + + // visible(rollData) { + // const current = this.getCurrent(rollData) + // return current.surprise != '' + // } + + isCombat(rollData) { + return [ROLL_MODE_ATTAQUE, ROLL_MODE_DEFENSE].includes(rollData.mode.current) || rollData.mode.isCombat + } + + prepareContext(rollData) { + this.setFromState(rollData) + } + + setFromState(rollData) { + if (rollData.mode.retry) { + return + } + const actor = rollData.active.actor; + const isCombat = this.isCombat(rollData) + const current = this.getCurrent(rollData) + current.surprise = actor.getSurprise(isCombat) + current.reasons = actor.getEffects(it => StatusEffects.niveauSurprise(it) > 0).map(it => it.name) + current.diviseur = 1 + if (isCombat && actor.isDemiReve()) { + current.reasons.push('Demi-rêve en combat') + } + if (current.surprise == 'demi') { + current.diviseur *= 2 + } + + if (this.isAttaqueFinesse(rollData)) { + current.diviseur *= 2 + current.reasons.push('Attaque en finesse') + } + if (this.isForceInsuffisante(rollData)) { + current.diviseur *= 2 + current.reasons.push('Force insuffisante') + } + current.reason = current.reasons.join(', ') + } + + isForceInsuffisante(rollData) { + //this.isCombat(rollData) && ... arme avec force min + return this.isCombat(rollData) && true + } + + isAttaqueFinesse(rollData) { + // this.rollData.selected[PART_DEFENSE] && attaquant avec particulière en finesse + return ROLL_MODE_DEFENSE == rollData.mode.current && true + } + + getAjustements(rollData) { + const current = this.getCurrent(rollData) + if (current.surprise == 'demi') { + return [{ label: 'Significative requise ' + Misc.getFractionOneN(current.diviseur), diff: undefined }] + } + return [] + } + + async _onRender(rollDialog, context, options) { + const input = rollDialog.element.querySelector(`roll-section[name="${this.code}"] input[name="${this.code}"]`) + + input?.addEventListener("change", e => { + this.getCurrent(rollDialog.rollData).value = parseInt(e.currentTarget.value) + rollDialog.render() + }) + } + +} \ No newline at end of file diff --git a/module/roll/roll-part-sort.mjs b/module/roll/roll-part-sort.mjs new file mode 100644 index 00000000..9d014bfa --- /dev/null +++ b/module/roll/roll-part-sort.mjs @@ -0,0 +1,129 @@ +import { ITEM_TYPES } from "../constants.js" +import { ROLL_MODE_SORT } from "./roll-constants.mjs" +import { PART_CARAC } from "./roll-part-carac.mjs" +import { PART_COMP } from "./roll-part-comp.mjs" +import { ROLLDIALOG_SECTION } from "./roll-part.mjs" +import { TMRUtility } from "../tmr-utility.js" +import { RdDItemSort } from "../item-sort.js" +import { RollPartSelect } from "./roll-part-select.mjs" + +export const PART_SORT = "sort" + +export class RollPartSort extends RollPartSelect { + onReady() { + // TODO: utiliser un hook pour écouter les déplacements dans les TMRs? + } + get code() { return PART_SORT } + get section() { return ROLLDIALOG_SECTION.CHOIX } + + isValid(rollData) { return rollData.active.actor.isPersonnage() && rollData.active.actor.isHautRevant() } + visible(rollData) { return this.isRollMode(rollData, ROLL_MODE_SORT) } + + loadRefs(rollData) { + const refs = this.getRefs(rollData) + const coord = rollData.active.actor.system.reve.tmrpos.coord + const draconics = rollData.active.actor.getDraconicList() + const sorts = rollData.active.actor.itemTypes[ITEM_TYPES.sort] + .map(s => RollPartSort.$extractSort(s, coord, draconics)) + + foundry.utils.mergeObject(refs, + { + coord: coord, + tmr: TMRUtility.getTMR(coord), + reve: rollData.active.actor.system.reve.reve.value, + draconics: draconics, + all: sorts, + sorts: sorts.filter(it => RdDItemSort.isSortOnCoord(it.sort, coord)) + }, + { inplace: true } + ) + if (refs.sorts.length > 0) { + this.$selectSort(rollData) + } + } + + choices(refs) { return refs.sorts } + + static $extractSort(sort, coord, draconics) { + const isDiffVariable = RdDItemSort.isDifficulteVariable(sort) + const isReveVariable = RdDItemSort.isCoutVariable(sort) + return { + key: sort.id, + label: sort.name, + value: isDiffVariable ? -7 : parseInt(sort.system.difficulte), + ptreve: isReveVariable ? 1 : sort.system.ptreve, + caseTMR: RdDItemSort.getCaseTMR(sort), + isDiffVariable: isDiffVariable, + isReveVariable: isReveVariable, + isReserve: false, + sort: sort, + draconics: RdDItemSort.getDraconicsSort(draconics, sort).map(it => it.name) + } + } + + getAjustements(rollData) { + const current = this.getCurrent(rollData) + if (current) { + const reserve = current.isReserve ? + [{ label: `Mise en réserve en ${current.coord}` }] : [] + const bonusCase = current.bonusCase ? + [{ label: `Bonus case +${current.bonusCase}%` }] : [] + return [ + { label: current.label, diff: current.value }, + { label: `r${current.ptreve}` }, + ...bonusCase, + ...reserve + ] + } + return [] + } + + $selectSort(rollData, key) { + const previous = this.getCurrent(rollData) + const current = this.selectByKey(rollData, key, -7) + if (current.key != previous.key) { } + current.bonusCase = RdDItemSort.getCaseBonus(current.sort, + rollData.active.actor.system.reve.tmrpos.coord) + } + + async _onRender(rollDialog, context, options) { + const current = this.getCurrent(rollDialog.rollData) + const selectSort = rollDialog.element.querySelector(`roll-section[name="${this.code}"] select[name="select-sort"]`) + const inputDiff = rollDialog.element.querySelector(`roll-section[name="${this.code}"] input[name="diff-var"]`) + const inputPtReve = rollDialog.element.querySelector(`roll-section[name="${this.code}"] input[name="ptreve-var"]`) + const checkboxReserve = rollDialog.element.querySelector(`roll-section[name="${this.code}"] input[name="reserve"]`) + + selectSort.addEventListener("change", e => { + const selectOptions = e.currentTarget.options + const index = selectOptions.selectedIndex + this.$selectSort(rollDialog.rollData, selectOptions[index]?.value) + rollDialog.setModeTitle() + rollDialog.render() + }) + + inputDiff?.addEventListener("change", e => { + current.value = parseInt(e.currentTarget.value) + rollDialog.render() + }) + inputPtReve?.addEventListener("change", e => { + current.ptreve = parseInt(e.currentTarget.value) + rollDialog.render() + }) + checkboxReserve?.addEventListener("change", e => { + current.isReserve = e.currentTarget.checked + rollDialog.render() + }) + } + + getExternalPartsFilter(partCode, rollData) { + if (this.visible(rollData)) { + const current = this.getCurrent(rollData) + switch (partCode) { + case PART_CARAC: return p => p.key == 'reve' + case PART_COMP: return p => current.draconics?.includes(p.label) + } + } + return undefined + } + +} diff --git a/module/roll/roll-part-surenc.mjs b/module/roll/roll-part-surenc.mjs new file mode 100644 index 00000000..89abc579 --- /dev/null +++ b/module/roll/roll-part-surenc.mjs @@ -0,0 +1,16 @@ +import { RdDCarac } from "../rdd-carac.js" +import { RollPartCheckbox } from "./roll-part-checkbox.mjs" + +const SURENC = "surenc" + +export class RollPartSurEnc extends RollPartCheckbox { + + get code() { return SURENC } + + visible(rollData) { + return RdDCarac.isActionPhysique(rollData.current.carac.key) && rollData.active.actor.isSurenc() + } + getCheckboxIcon(rollData) { return '' } + getCheckboxLabel(rollData) { return "Sur-enc." } + getCheckboxValue(rollData) { return rollData.active.actor.computeMalusSurEncombrement() } +} diff --git a/module/roll/roll-part-tache.mjs b/module/roll/roll-part-tache.mjs new file mode 100644 index 00000000..fee4b89e --- /dev/null +++ b/module/roll/roll-part-tache.mjs @@ -0,0 +1,67 @@ +import { ITEM_TYPES } from "../constants.js" +import { Grammar } from "../grammar.js" +import { ROLL_MODE_TACHE } from "./roll-constants.mjs" +import { PART_CARAC } from "./roll-part-carac.mjs" +import { PART_COMP } from "./roll-part-comp.mjs" +import { RollPartSelect } from "./roll-part-select.mjs" +import { ROLLDIALOG_SECTION } from "./roll-part.mjs" + +export const PART_TACHE = "tache" + +export class RollPartTache extends RollPartSelect { + + get code() { return PART_TACHE } + get section() { return ROLLDIALOG_SECTION.CHOIX } + + isValid(rollData) { return rollData.active.actor.isPersonnage() } + visible(rollData) { return this.isRollMode(rollData, ROLL_MODE_TACHE) } + + loadRefs(rollData) { + const refs = this.getRefs(rollData) + refs.taches = rollData.active.actor.itemTypes[ITEM_TYPES.tache] + .filter(tache => tache.system.points_de_tache_courant < tache.system.points_de_tache) + .map(tache => RollPartTache.$extractTache(tache, rollData.active.actor)) + if (refs.taches.length > 0) { + this.$selectTache(rollData) + } + } + choices(refs) { return refs.taches } + + static $extractTache(tache, actor) { + return { + key: tache.id, + label: tache.name, + value: tache.system.difficulte, + tache: tache, + comp: actor.getCompetence(tache.system.competence) + } + } + + $selectTache(rollData, key) { + this.selectByKey(rollData, key, 0) + } + + async _onRender(rollDialog, context, options) { + const selectTache = rollDialog.element.querySelector(`roll-section[name="${this.code}"] select[name="select-tache"]`) + + selectTache.addEventListener("change", e => { + const selectOptions = e.currentTarget.options + const index = selectOptions.selectedIndex + this.$selectTache(rollDialog.rollData, selectOptions[index]?.value) + rollDialog.setModeTitle() + rollDialog.render() + }) + } + + getExternalPartsFilter(partCode, rollData) { + if (this.visible(rollData)) { + const current = this.getCurrent(rollData) + switch (partCode) { + case PART_CARAC: return p => Grammar.equalsInsensitive(p.key, current?.tache.system.carac) + case PART_COMP: return p => p.label == current?.comp.name + } + } + return undefined + } + +} diff --git a/module/roll/roll-part-tricher.mjs b/module/roll/roll-part-tricher.mjs new file mode 100644 index 00000000..e1909be2 --- /dev/null +++ b/module/roll/roll-part-tricher.mjs @@ -0,0 +1,31 @@ +import { ROLLDIALOG_SECTION, RollPart } from "./roll-part.mjs" + +const PART_TRICHER = "tricher" + +export class RollPartTricher extends RollPart { + + get code() { return PART_TRICHER } + get section() { return ROLLDIALOG_SECTION.CONDITIONS } + + visible(rollData) { return game.user.isGM } + + prepareContext(rollData) { + const current = this.getCurrent(rollData) + if (current.resultat == undefined) { + current.resultat = -1 + } + } + + getAjustements(rollData) { + rollData.current.resultat = this.getCurrent(rollData).resultat + return [] + } + + async _onRender(rollDialog, context, options) { + const input = rollDialog.element.querySelector(`roll-section[name="${this.code}"] input[name="${this.code}"]`) + + input?.addEventListener("change", e => { + this.getCurrent(rollDialog.rollData).resultat = parseInt(e.currentTarget.value) + }) + } +} diff --git a/module/roll/roll-part.mjs b/module/roll/roll-part.mjs new file mode 100644 index 00000000..b50a95a5 --- /dev/null +++ b/module/roll/roll-part.mjs @@ -0,0 +1,97 @@ +import { Misc } from "../misc.js" + +export const ROLLDIALOG_SECTION = { + ACTION: 'action', + CARAC: 'carac', + COMP: 'comp', + CHOIX: 'choix', + CONDITIONS: 'conditions', + AJUSTEMENTS: 'ajustements', +} +export class RollPart { + static settingKey(rollPart, key) { return `roll-part-${rollPart.code}.${key}` } + + get code() { throw new Error(`Pas dse code définie pour ${this}`) } + get name() { return this.code } + /** la section de la fenêtre ou le paramêtre apparaît */ + get section() { return undefined } + get priority() { return 0 /* TODO */ } + /** le template handlebars pour affichage */ + get template() { return `systems/foundryvtt-reve-de-dragon/templates/roll/roll-part-${this.code}.hbs` } + + initialize(rollData) { + if (rollData.refs[this.code] == undefined) { + rollData.refs[this.code] = {} + } + if (rollData.current[this.code] == undefined) { + rollData.current[this.code] = {} + } + if (rollData.selected[this.code] == undefined) { + rollData.selected[this.code] = {} + } + } + + /** le conteneur de données du RollPart */ + getRefs(rollData) { + return rollData.refs[this.code] + } + + /** les informations de sélection du paramètre */ + getCurrent(rollData) { + return rollData.current[this.code] + } + setCurrent(rollData, current) { + rollData.current[this.code] = current + } + + /** les informations minimales représentant la sélection dans le rollData permettant de restaurer la fenêtre */ + getSaved(rollData) { + return rollData.selected[this.code] ?? {} + } + setSaved(rollData, saved) { + rollData.selected[this.code] = saved + } + + restore(rollData) { } + store(rollData, targetData) { } + + /** + * le texte à ajouter dans la barre de titre + * @returns une chaîne vide si rien ne doit être affiché + */ + title() { return '' } + isRollMode(rollData, mode) { return rollData.mode.current == mode } + + isActive(rollData) { return this.isValid(rollData) && this.visible(rollData) } + isValid(rollData) { return true } + visible(rollData) { return true } + + onReady() { } + loadRefs(rollData) { } + + prepareContext(rollData) { } + + /** ---- cross roll-part filtering ---- */ + setFilter(rollData, filter) { } + getSpecialComp(rollData) { return [] } + setSpecialComp(comps) { } + + getExternalPartsFilter(partCode, rollData) { return undefined } + setExternalFilter(visibleRollParts, rollData) { + const predicate = Misc.and( + visibleRollParts.map(p => p.getExternalPartsFilter(this.code, rollData)).filter(f => f != undefined) + ) + this.setFilter(rollData, predicate); + } + + toTemplateData() { + return { code: this.code, name: this.name, template: this.template, section: this.section } + } + + getAjustements(rollData) { + return [] + } + + async _onRender(rollDialog, context, options) { } + +} \ No newline at end of file diff --git a/module/rolldata-ajustements.js b/module/rolldata-ajustements.js index aea7b5cb..39e358ad 100644 --- a/module/rolldata-ajustements.js +++ b/module/rolldata-ajustements.js @@ -1,4 +1,4 @@ -import { RdDItemArme } from "./item-arme.js"; +import { RdDItemArme } from "./item/arme.js"; import { RdDItemCompetence } from "./item-competence.js"; import { RdDItemMeditation } from "./item-meditation.js"; import { RdDItemSort } from "./item-sort.js"; @@ -24,11 +24,6 @@ export const referenceAjustements = { getLabel: (rollData, actor) => rollData.competence?.name, getValue: (rollData, actor) => rollData.competence?.system?.niveau, }, - meditation: { - isUsed: (rollData, actor) => rollData.meditation, - getLabel: (rollData, actor) => 'Méditation', - getValue: (rollData, actor) => RdDItemMeditation.calculDifficulte(rollData) - }, diffLibre: { isUsed: (rollData, actor) => rollData.diffLibre != undefined, getLabel: (rollData, actor) => rollData.selectedSort?.name ?? rollData.attackerRoll ?? RdDPossession.isDefensePossession(rollData) ? 'Imposée' : 'Libre', @@ -41,99 +36,59 @@ export const referenceAjustements = { getLabel: (rollData, actor) => 'Conditions', getValue: (rollData, actor) => rollData.diffConditions }, - tactique: { - isUsed: (rollData, actor) => rollData.tactique, - getLabel: (rollData, actor) => RdDBonus.find(rollData.tactique).descr, - getValue: (rollData, actor) => RdDBonus.find(rollData.tactique).attaque, - }, - attaqueDefenseurSurpris: { - isUsed: (rollData, actor) => rollData.surpriseDefenseur, - getLabel: (rollData, actor) => RdDBonus.find(rollData.surpriseDefenseur).descr + (rollData.attackerRoll ? '' : ' défenseur'), - getValue: (rollData, actor) => RdDBonus.find(rollData.surpriseDefenseur).attaque, - }, etat: { isUsed: (rollData, actor) => !RollDataAjustements.isIgnoreEtatGeneral(rollData), getLabel: (rollData, actor) => 'Etat général', getValue: (rollData, actor) => actor.getEtatGeneral({ ethylisme: rollData.forceAlcool != undefined }) }, malusArmure: { - isVisible: (rollData, actor) => RdDCarac.isAgiliteOuDerobee(rollData.selectedCarac), - isUsed: (rollData, actor) => RdDCarac.isAgiliteOuDerobee(rollData.selectedCarac), + isVisible: (rollData, actor) => RdDCarac.isAgiliteOuDerobee(rollData.selectedCarac?.label), + isUsed: (rollData, actor) => RdDCarac.isAgiliteOuDerobee(rollData.selectedCarac?.label), getLabel: (rollData, actor) => 'Malus armure', getValue: (rollData, actor) => actor.getMalusArmure() }, encTotal: { - isVisible: (rollData, actor) => RdDCarac.isAgiliteOuDerobee(rollData.selectedCarac) && RdDItemCompetence.isMalusEncombrementTotal(rollData.competence), - isUsed: (rollData, actor) => !rollData.oeuvre && RdDCarac.isAgiliteOuDerobee(rollData.selectedCarac) && RdDItemCompetence.isMalusEncombrementTotal(rollData.competence) && rollData.use?.encTotal, + isVisible: (rollData, actor) => RdDCarac.isAgiliteOuDerobee(rollData.selectedCarac?.label) && RdDItemCompetence.isMalusEncombrementTotal(rollData.competence?.name), + isUsed: (rollData, actor) => !rollData.oeuvre && RdDCarac.isAgiliteOuDerobee(rollData.selectedCarac?.label) && RdDItemCompetence.isMalusEncombrementTotal(rollData.competence?.name) && rollData.use?.encTotal, getLabel: (rollData, actor) => 'Encombrement total', getValue: (rollData, actor) => -actor.getEncTotal() }, surenc: { - isVisible: (rollData, actor) => RdDCarac.isActionPhysique(rollData.selectedCarac) && actor.isSurenc(), - isUsed: (rollData, actor) => rollData.use.surenc && RdDCarac.isActionPhysique(rollData.selectedCarac), + isVisible: (rollData, actor) => RdDCarac.isActionPhysique(rollData.selectedCarac?.label) && actor.isSurenc(), + isUsed: (rollData, actor) => rollData.use.surenc && RdDCarac.isActionPhysique(rollData.selectedCarac?.label), getLabel: (rollData, actor) => 'Sur-encombrement', getValue: (rollData, actor) => actor.computeMalusSurEncombrement() }, - rituel: { - isUsed: (rollData, actor) => actor.isPersonnage() && ReglesOptionnelles.isUsing("astrologie") && rollData.selectedSort?.system.isrituel, - getLabel: (rollData, actor) => 'Astrologique', - getValue: (rollData, actor) => actor.ajustementAstrologique() - }, astrologique: { - isVisible: (rollData, actor) => actor.isPersonnage() && ReglesOptionnelles.isUsing("astrologie") && RdDCarac.isChance(rollData.selectedCarac), - isUsed: (rollData, actor) => RdDCarac.isChance(rollData.selectedCarac) && rollData.use.astrologique, + isVisible: (rollData, actor) => actor.isPersonnage() && ReglesOptionnelles.isUsing("astrologie") && RdDCarac.isChance(rollData.selectedCarac?.label), + isUsed: (rollData, actor) => RdDCarac.isChance(rollData.selectedCarac?.label) && rollData.use.astrologique, getLabel: (rollData, actor) => 'Astrologique', getValue: (rollData, actor) => actor.ajustementAstrologique() }, moral: { - isVisible: (rollData, actor) => actor.isPersonnage() && RdDCarac.isActionPhysique(rollData.selectedCarac) && rollData.use?.moral, + isVisible: (rollData, actor) => actor.isPersonnage() && RdDCarac.isActionPhysique(rollData.selectedCarac?.label) && rollData.use?.moral, isUsed: (rollData, actor) => rollData.use.moral, getLabel: (rollData, actor) => 'Appel au moral', getValue: (rollData, actor) => 1 }, + moralTotal: { + isUsed: (rollData, actor) => RdDCarac.isVolonte(rollData.selectedCarac?.label), + getLabel: (rollData, actor) => 'Moral', + getValue: (rollData, actor) => actor.getMoralTotal() + }, coeur: { - isVisible: (rollData, actor) => actor.isPersonnage() && RdDCarac.isVolonte(rollData.selectedCarac), + isVisible: (rollData, actor) => actor.isPersonnage() && RdDCarac.isVolonte(rollData.selectedCarac?.label), isUsed: (rollData, actor) => rollData.use.coeur != undefined, getLabel: (rollData, actor) => 'Ajustement de cœur', getValue: (rollData, actor) => -2 * (rollData.use.coeur?.coeur ?? 0) }, - moralTotal: { - isUsed: (rollData, actor) => RdDCarac.isVolonte(rollData.selectedCarac), - getLabel: (rollData, actor) => 'Moral', - getValue: (rollData, actor) => actor.getMoralTotal() - }, - facteurSign: { - isUsed: (rollData, actor) => rollData.diviseurSignificative > 1, - getLabel: (rollData, actor) => Misc.getFractionHtml(rollData.diviseurSignificative), - getDescr: (rollData, actor) => rollData.diviseurSignificative > 1 ? `Facteur significative ×${Misc.getFractionHtml(rollData.diviseurSignificative)}` : '' - }, - isEcaille: { - isVisible: (rollData, actor) => rollData.arme?.system.magique && Number(rollData.arme?.system.ecaille_efficacite) > 0, - isUsed: (rollData, actor) => rollData.arme?.system.magique && Number(rollData.arme?.system.ecaille_efficacite) > 0, - getLabel: (rollData, actor) => "Ecaille d'Efficacité: ", - getValue: (rollData, actor) => rollData.arme?.system.magique ? Math.max(Number(rollData.arme?.system.ecaille_efficacite), 0) : 0, - }, - finesse: { - isUsed: (rollData, actor) => RdDBonus.isDefenseAttaqueFinesse(rollData), - getDescr: (rollData, actor) => 'Attaque particulière en finesse', - }, - armeParade: { - isUsed: (rollData, actor) => RdDItemArme.needParadeSignificative(rollData.attackerRoll?.arme, rollData.arme), - getDescr: (rollData, actor) => rollData.attackerRoll && rollData.arme ? `${RdDItemArme.getNomCategorieParade(rollData.attackerRoll?.arme)} vs ${RdDItemArme.getNomCategorieParade(rollData.arme)}` : '' - }, - surprise: { - isUsed: (rollData, actor) => actor.getSurprise(rollData.passeArme), - getDescr: (rollData, actor) => RdDBonus.find(actor.getSurprise()).descr - }, - bonusCase: { - isUsed: (rollData, actor) => rollData.selectedSort && rollData.tmr.coord, - getDescr: (rollData, actor) => rollData.selectedSort && rollData.tmr.coord ? `Bonus de case: ${RdDItemSort.getCaseBonus(rollData.selectedSort, rollData.tmr.coord)}%` : '' - }, - rencontreTMR: { - isVisible: (rollData, actor) => rollData.tmr && rollData.rencontre?.name, - isUsed: (rollData, actor) => rollData.tmr && rollData.rencontre?.name, - getLabel: (rollData, actor) => rollData.rencontre?.name, - getValue: (rollData, actor) => - (rollData.rencontre?.system.force ?? 0) + + + + meditation: { + isUsed: (rollData, actor) => rollData.meditation, + getLabel: (rollData, actor) => 'Méditation', + getValue: (rollData, actor) => RdDItemMeditation.calculDifficulte(rollData) }, ethylismeAlcool: { isVisible: (rollData, actor) => rollData.nbDoses != undefined, @@ -153,12 +108,61 @@ export const referenceAjustements = { getLabel: (rollData, actor) => "Ethylisme - " + RdDUtility.getNomEthylisme(rollData.ethylisme), getValue: (rollData, actor) => rollData.ethylisme, }, + facteurSign: { + isUsed: (rollData, actor) => rollData.diviseurSignificative > 1, + getLabel: (rollData, actor) => Misc.getFractionOneN(rollData.diviseurSignificative), + getDescr: (rollData, actor) => rollData.diviseurSignificative > 1 ? `Facteur significative ×${Misc.getFractionOneN(rollData.diviseurSignificative)}` : '' + }, + isEcaille: { + isVisible: (rollData, actor) => rollData.arme?.system.magique && Number(rollData.arme?.system.ecaille_efficacite) > 0, + isUsed: (rollData, actor) => rollData.arme?.system.magique && Number(rollData.arme?.system.ecaille_efficacite) > 0, + getLabel: (rollData, actor) => "Ecaille d'Efficacité: ", + getValue: (rollData, actor) => rollData.arme?.system.magique ? Math.max(Number(rollData.arme?.system.ecaille_efficacite), 0) : 0, + }, + tactique: { + isUsed: (rollData, actor) => rollData.tactique, + getLabel: (rollData, actor) => RdDBonus.find(rollData.tactique).descr, + getValue: (rollData, actor) => RdDBonus.find(rollData.tactique).attaque, + }, + finesse: { + isUsed: (rollData, actor) => RdDBonus.isDefenseAttaqueFinesse(rollData), + getDescr: (rollData, actor) => 'Attaque particulière en finesse', + }, + surprise: { + isUsed: (rollData, actor) => actor.getSurprise(rollData.passeArme), + getDescr: (rollData, actor) => RdDBonus.find(actor.getSurprise()).descr + }, + attaqueDefenseurSurpris: { + isUsed: (rollData, actor) => rollData.surpriseDefenseur, + getLabel: (rollData, actor) => RdDBonus.find(rollData.surpriseDefenseur).descr + (rollData.attackerRoll ? '' : ' défenseur'), + getValue: (rollData, actor) => RdDBonus.find(rollData.surpriseDefenseur).attaque, + }, + armeParade: { + isUsed: (rollData, actor) => RdDItemArme.needParadeSignificative(rollData.attackerRoll?.arme, rollData.arme), + getDescr: (rollData, actor) => rollData.attackerRoll && rollData.arme ? `${RdDItemArme.getNomCategorieParade(rollData.attackerRoll?.arme)} vs ${RdDItemArme.getNomCategorieParade(rollData.arme)}` : '' + }, tailleempoignade: { isVisible: (rollData, actor) => rollData.isEmpoignade, isUsed: (rollData, actor) => rollData.isEmpoignade, getLabel: (rollData, actor) => "Malus de taille", getValue: (rollData, actor) => rollData.malusTaille, - } + }, + + rituel: { + isUsed: (rollData, actor) => actor.isPersonnage() && ReglesOptionnelles.isUsing("astrologie") && rollData.selectedSort?.system.isrituel, + getLabel: (rollData, actor) => 'Astrologique', + getValue: (rollData, actor) => actor.ajustementAstrologique() + }, + bonusCase: { + isUsed: (rollData, actor) => rollData.selectedSort && rollData.tmr.coord, + getDescr: (rollData, actor) => rollData.selectedSort && rollData.tmr.coord ? `Bonus de case: ${RdDItemSort.getCaseBonus(rollData.selectedSort, rollData.tmr.coord)}%` : '' + }, + rencontreTMR: { + isVisible: (rollData, actor) => rollData.tmr && rollData.rencontre?.name, + isUsed: (rollData, actor) => rollData.tmr && rollData.rencontre?.name, + getLabel: (rollData, actor) => rollData.rencontre?.name, + getValue: (rollData, actor) => - (rollData.rencontre?.system.force ?? 0) + }, } export class RollDataAjustements { @@ -196,8 +200,8 @@ export class RollDataAjustements { const selectedCarac = rollData.selectedCarac; return !selectedCarac || rollData.ethylisme || - RdDCarac.isChance(selectedCarac) || - (RdDCarac.isReve(selectedCarac) && !rollData.competence); + RdDCarac.isChance(selectedCarac?.label) || + (RdDCarac.isReve(selectedCarac?.label) && !rollData.competence); } } diff --git a/module/settings/options-avancees.js b/module/settings/options-avancees.js index 92352cd0..40676d3c 100644 --- a/module/settings/options-avancees.js +++ b/module/settings/options-avancees.js @@ -2,8 +2,10 @@ import { SYSTEM_RDD } from "../constants.js" import { Misc } from "../misc.js" export const EXPORT_CSV_SCRIPTARIUM = 'export-csv-scriptarium' +export const ROLL_DIALOG_V2 = 'roll-drialog-v2' const OPTIONS_AVANCEES = [ + { group: 'Fenêtres', name: ROLL_DIALOG_V2, descr: "Utiliser les nouvelles fenêtres de jet", default: false }, { group: 'Menus', name: EXPORT_CSV_SCRIPTARIUM, descr: "Proposer le menu d'export csv Scriptarium", default: false }, ] diff --git a/module/settings/status-effects.js b/module/settings/status-effects.js index 76346dae..de2da309 100644 --- a/module/settings/status-effects.js +++ b/module/settings/status-effects.js @@ -1,43 +1,47 @@ import { SYSTEM_RDD } from "../constants.js"; export const STATUSES = { - StatusStunned: 'stun', - StatusBleeding: 'bleeding', - StatusProne: 'prone', StatusGrappling: 'grappling', StatusGrappled: 'grappled', StatusRestrained: 'restrain', + StatusStunned: 'stun', + StatusProne: 'prone', StatusUnconscious: 'unconscious', StatusBlind: 'blind', StatusComma: 'comma', + StatusBleeding: 'bleeding', StatusDead: 'dead', StatusDemiReve: 'demi-reve', } +const demiReveStatusEffect = { rdd: true, id: STATUSES.StatusDemiReve, name: 'EFFECT.StatusDemiReve', img: 'systems/foundryvtt-reve-de-dragon/icons/heures/hd12.svg' }; const rddStatusEffects = [ - { rdd: true, id: STATUSES.StatusStunned, name: 'EFFECT.StatusStunned', img: 'icons/svg/stoned.svg', "duration.rounds": 1 }, - { rdd: true, id: STATUSES.StatusBleeding, name: 'EFFECT.StatusBleeding', img: 'icons/svg/blood.svg' }, - { rdd: true, id: STATUSES.StatusProne, name: 'EFFECT.StatusProne', img: 'icons/svg/falling.svg' }, - { rdd: true, id: STATUSES.StatusGrappling, tint: '#33cc33', name: 'EFFECT.StatusGrappling', img: 'systems/foundryvtt-reve-de-dragon/icons/empoignade.webp' }, - { rdd: true, id: STATUSES.StatusGrappled, tint: '#ff9900', name: 'EFFECT.StatusGrappled', img: 'systems/foundryvtt-reve-de-dragon/icons/empoignade.webp' }, - { rdd: true, id: STATUSES.StatusRestrained, name: 'EFFECT.StatusRestrained', img: 'icons/svg/net.svg' }, - { rdd: true, id: STATUSES.StatusUnconscious, name: 'EFFECT.StatusUnconscious', img: 'icons/svg/unconscious.svg' }, - { rdd: true, id: STATUSES.StatusBlind, name: 'EFFECT.StatusBlind', img: 'icons/svg/blind.svg' }, - { rdd: true, id: STATUSES.StatusComma, name: 'EFFECT.StatusComma', img: 'icons/svg/skull.svg' }, - { rdd: true, id: STATUSES.StatusDead, name: 'EFFECT.StatusDead', img: 'icons/svg/skull.svg' }, - { rdd: true, id: STATUSES.StatusDemiReve, name: 'EFFECT.StatusDemiReve', img: 'systems/foundryvtt-reve-de-dragon/icons/heures/hd12.svg' } + { rdd: true, id: STATUSES.StatusGrappling, tint: '#33cc33', name: 'EFFECT.StatusGrappling', img: 'systems/foundryvtt-reve-de-dragon/icons/empoignade.webp' }, + { rdd: true, id: STATUSES.StatusGrappled, tint: '#ff9900', name: 'EFFECT.StatusGrappled', img: 'systems/foundryvtt-reve-de-dragon/icons/empoignade.webp' }, + + { rdd: true, id: STATUSES.StatusRestrained, name: 'EFFECT.StatusRestrained', img: 'icons/svg/net.svg' }, + { rdd: true, id: STATUSES.StatusStunned, name: 'EFFECT.StatusStunned', img: 'icons/svg/stoned.svg', "duration.rounds": 1 }, + { rdd: true, id: STATUSES.StatusProne, name: 'EFFECT.StatusProne', img: 'icons/svg/falling.svg' }, + + { rdd: true, id: STATUSES.StatusUnconscious, name: 'EFFECT.StatusUnconscious', img: 'icons/svg/unconscious.svg' }, + { rdd: true, id: STATUSES.StatusBlind, name: 'EFFECT.StatusBlind', img: 'icons/svg/blind.svg' }, + { rdd: true, id: STATUSES.StatusComma, name: 'EFFECT.StatusComma', img: 'icons/svg/skull.svg' }, + + { rdd: true, id: STATUSES.StatusBleeding, name: 'EFFECT.StatusBleeding', img: 'icons/svg/blood.svg' }, + { rdd: true, id: STATUSES.StatusDead, name: 'EFFECT.StatusDead', img: 'icons/svg/skull.svg' }, + demiReveStatusEffect ]; -const demiReveStatusEffect = rddStatusEffects.find(it => it.id == STATUSES.StatusDemiReve); const statusDemiSurprise = new Set([STATUSES.StatusStunned, STATUSES.StatusProne, STATUSES.StatusRestrained]) const statusSurpriseTotale = new Set([STATUSES.StatusUnconscious, STATUSES.StatusBlind, STATUSES.StatusComma]) export class StatusEffects extends FormApplication { + + static onReady() { const rddEffectIds = rddStatusEffects.map(it => it.id); rddStatusEffects.forEach(it => { - it.statuses = new Set() - it.statuses.add(it.id) + it.statuses = new Set([it.id]) }) const defaultStatusEffectIds = CONFIG.statusEffects.map(it => it.id); game.settings.register(SYSTEM_RDD, "use-status-effects", { @@ -63,19 +67,31 @@ export class StatusEffects extends FormApplication { console.log('statusEffects', CONFIG.statusEffects); } - static valeurSurprise(effect, isCombat) { + static niveauSurprise(effect, isCombat) { if (statusSurpriseTotale.intersects(effect.statuses)) { return 2 } if (statusDemiSurprise.intersects(effect.statuses)) { return 1 } - if (isCombat && effect.statuses.find(e => e == STATUSES.StatusDemiReve)) { + if (isCombat && StatusEffects.isDemiReve(effect)) { return 1 } return 0 } + static typeSurprise(niveauSurprise) { + switch (niveauSurprise) { + case 0: return '' + case 1: return 'demi' + default: return 'totale' + } + } + + static isDemiReve(effect) { + return effect.statuses.has(STATUSES.StatusDemiReve) + } + static _getUseStatusEffects() { return game.settings.get(SYSTEM_RDD, "use-status-effects")?.split(',') ?? []; } @@ -92,10 +108,10 @@ export class StatusEffects extends FormApplication { } static prepareActiveEffect(effectId) { - let status = rddStatusEffects.find(it => it.id == effectId) + const status = rddStatusEffects.find(it => it.id == effectId) if (status) { status = foundry.utils.duplicate(status) - status.statuses = [effectId] + status.statuses = new Set([effectId]) } return status; } diff --git a/module/tmr-utility.js b/module/tmr-utility.js index b266f296..406932bd 100644 --- a/module/tmr-utility.js +++ b/module/tmr-utility.js @@ -304,7 +304,7 @@ export class TMRUtility { } static typeTmrName(type) { - return Misc.upperFirst(TMRType[Grammar.toLowerCaseNoAccent(type)].name); + return type ? Misc.upperFirst(TMRType[Grammar.toLowerCaseNoAccent(type)].name) : '' } static buildSelectionTypesTMR(typesTMR) { diff --git a/templates/actor/carac-derivee.hbs b/templates/actor/carac-derivee.hbs index b4bede82..5be058c4 100644 --- a/templates/actor/carac-derivee.hbs +++ b/templates/actor/carac-derivee.hbs @@ -67,7 +67,9 @@
  • - + diff --git a/templates/actor/carac-entitee.hbs b/templates/actor/carac-entitee.hbs index 38114340..fb63f4fe 100644 --- a/templates/actor/carac-entitee.hbs +++ b/templates/actor/carac-entitee.hbs @@ -1,6 +1,6 @@
  • Niveau -
  • diff --git a/templates/actor/combat.hbs b/templates/actor/combat.hbs index 7150e974..0174314d 100644 --- a/templates/actor/combat.hbs +++ b/templates/actor/combat.hbs @@ -6,26 +6,31 @@ Initiative -{{#each combat as |arme key|}} +{{#each combat as |action key|}}
  • + data-item-id="{{action._id}}" + data-arme-name="{{action.arme.name}}" + data-competence-name="{{action.comp.name}}" + data-tooltip="{{action.name}}: niveau {{plusMoins action.comp.system.niveau}}"> - {{#if arme.img}} - + {{#if action.arme.img}} + {{/if}} - {{arme.name}} + {{action.name}} - ({{arme.system.competence}}) - {{>"systems/foundryvtt-reve-de-dragon/templates/item/icon-arme-broken.hbs" arme}} + ({{action.comp.name}}) + {{>"systems/foundryvtt-reve-de-dragon/templates/item/icon-arme-broken.hbs" action.arme}} - {{plusMoins arme.system.niveau}} - {{plusMoins arme.system.dommagesReels}} + {{plusMoins action.comp.system.niveau}} + {{plusMoins action.dmg}} - {{arme.system.initiative}} + + + {{action.initiative}} + +
  • {{/each}} {{#each esquives as |esq key|}} diff --git a/templates/actor/export-scriptarium/actor-encart-sheet.hbs b/templates/actor/export-scriptarium/actor-encart-sheet.hbs index 871c4d6a..7fa6487c 100644 --- a/templates/actor/export-scriptarium/actor-encart-sheet.hbs +++ b/templates/actor/export-scriptarium/actor-encart-sheet.hbs @@ -72,12 +72,9 @@
    - {{>"systems/foundryvtt-reve-de-dragon/templates/actor/export-scriptarium/arme.hbs" name='' niveau='Niv' init='Init' dommages='+dom'}} - {{#each context.armes as |arme|}} - {{>"systems/foundryvtt-reve-de-dragon/templates/actor/export-scriptarium/arme.hbs" - name=arme.name niveau=arme.niveau init=arme.init dommages=arme.dommages - arme=arme.arme competence=arme.competence - }} + {{>"systems/foundryvtt-reve-de-dragon/templates/actor/export-scriptarium/arme-titre.hbs"}} + {{#each attaques as |attaque|}} + {{>"systems/foundryvtt-reve-de-dragon/templates/actor/export-scriptarium/arme.hbs" attaque=attaque}} {{/each}} {{>"systems/foundryvtt-reve-de-dragon/templates/actor/export-scriptarium/esquive.hbs" name='Esquive' niveau=context.esquive.value competence=context.esquive.competence}} {{#if (gt export.malue_armure.value 0)}} diff --git a/templates/actor/export-scriptarium/arme-titre.hbs b/templates/actor/export-scriptarium/arme-titre.hbs new file mode 100644 index 00000000..1c27caca --- /dev/null +++ b/templates/actor/export-scriptarium/arme-titre.hbs @@ -0,0 +1,8 @@ +
    +
    +
    +
    Niv.
    +
    Init.
    +
    +dom
    +
    +
    diff --git a/templates/actor/export-scriptarium/arme.hbs b/templates/actor/export-scriptarium/arme.hbs index 264fce9f..572c9367 100644 --- a/templates/actor/export-scriptarium/arme.hbs +++ b/templates/actor/export-scriptarium/arme.hbs @@ -1,24 +1,22 @@
    - {{#if name}} - {{upperFirst name}} + data-item-id="{{attaque.arme._id}}" + data-arme-name="{{attaque.arme.name}}" + data-competence-name="{{attaque.comp.name}}"> + {{#if attaque.name}} + {{upperFirst attaque.name}} {{else}}
    {{/if}}
    -
    {{niveau}}
    - {{#if init}} - {{#if name}} -
    {{init}}
    - {{else}} -
    {{init}}
    - {{/if}} -
    {{dommages}}
    - {{else}} -
    +
    {{plusMoins attaque.comp.system.niveau}}
    +
    {{attaque.initiative}}
    + {{#if (eq arme.system.mortalite 'empoignade')}}
    + {{else if (eq arme.system.mortalite 'non-mortel')}} +
    ({{plusMoins attaque.dmg}})
    + {{else}} +
    {{plusMoins attaque.dmg}}
    {{/if}} +
    diff --git a/templates/actor/export-scriptarium/competences.hbs b/templates/actor/export-scriptarium/competences.hbs index 69273fc2..286a0a04 100644 --- a/templates/actor/export-scriptarium/competences.hbs +++ b/templates/actor/export-scriptarium/competences.hbs @@ -4,6 +4,6 @@ {{#each list as |comp|}} {{comp.name}}{{~#unless (isLastIndex @index ../list)~}},{{/unless~}} {{/each}} - {{numberFormat niveau decimals=0 sign=true}} + {{plusMoins niveau}} {{/with}} {{/each}} diff --git a/templates/actor/export-scriptarium/esquive.hbs b/templates/actor/export-scriptarium/esquive.hbs index d07a4d21..126775e9 100644 --- a/templates/actor/export-scriptarium/esquive.hbs +++ b/templates/actor/export-scriptarium/esquive.hbs @@ -2,7 +2,7 @@
    {{upperFirst name}}
    -
    {{numberFormat competence.system.niveau decimals=0 sign=true}}
    +
    {{plusMoins competence.system.niveau}}
    diff --git a/templates/actor/hr-sorts.hbs b/templates/actor/hr-sorts.hbs index edde88da..d1981eb4 100644 --- a/templates/actor/hr-sorts.hbs +++ b/templates/actor/hr-sorts.hbs @@ -14,7 +14,7 @@ {{#if sort.system.caseTMRspeciale}}{{sort.system.caseTMRspeciale}}{{else}}{{upperFirst sort.system.caseTMR}}{{/if}} - R{{itemSort-spaceIfText sort.system.difficulte}} r{{itemSort-spaceIfText sort.system.ptreve}} + {{itemSort-diffReve sort}} {{itemSort-coutReve sort}} {{>'systems/foundryvtt-reve-de-dragon/templates/actor/item-action-controls.hbs' item=sort options=@root.options}} {{/each}} diff --git a/templates/actor/liens-animaux.hbs b/templates/actor/liens-animaux.hbs index df33c952..89301e80 100644 --- a/templates/actor/liens-animaux.hbs +++ b/templates/actor/liens-animaux.hbs @@ -2,7 +2,7 @@