From 070b10fc9f3d2408ccf69bbd7ad3da8539b39ba9 Mon Sep 17 00:00:00 2001 From: Eunomiac Date: Thu, 16 Nov 2023 05:59:47 -0500 Subject: [PATCH] Began work on Chat output --- .../animations/chat/roll-position-risky.webp | Bin 0 -> 247612 bytes css/style.min.css | 2957 +++++++++++++++-- css/tinymce/content.min.css | 414 ++- gulpfile.js | 132 +- module/BladesActiveEffect.js | 63 +- module/BladesActor.js | 303 +- module/BladesChat.js | 86 + module/BladesDialog.js | 89 +- module/BladesItem.js | 97 +- module/BladesPushAlert.js | 30 +- module/BladesRoll.js | 1025 +++++- module/blades.js | 249 +- module/core/ai.js | 121 +- module/core/constants.js | 182 +- module/core/gsap.js | 767 ++++- module/core/helpers.js | 97 +- module/core/logger.js | 64 +- module/core/settings.js | 47 +- module/core/tags.js | 51 +- module/core/utilities.js | 750 ++++- module/data-import/data-import.js | 107 +- module/documents/BladesActorProxy.js | 18 +- module/documents/BladesItemProxy.js | 17 +- module/documents/actors/BladesCrew.js | 41 +- module/documents/actors/BladesFaction.js | 30 +- module/documents/actors/BladesNPC.js | 36 +- module/documents/actors/BladesPC.js | 74 +- module/documents/actors/blades-crew.js | 84 - module/documents/actors/blades-faction.js | 62 - module/documents/actors/blades-npc.js | 76 - module/documents/actors/blades-pc.js | 306 -- module/documents/blades-actor-proxy.js | 58 - module/documents/blades-item-location.js | 8 - module/documents/blades-item-proxy.js | 58 - module/documents/items/BladesClockKeeper.js | 36 +- module/documents/items/BladesGMTracker.js | 16 +- module/documents/items/BladesLocation.js | 19 +- module/documents/items/BladesScore.js | 24 +- module/documents/items/blades-clock-keeper.js | 188 -- module/documents/items/blades-gm-tracker.js | 28 - .../documents/items/blades-item-location.js | 8 - .../documents/items/blades-location copy.js | 11 - module/documents/items/blades-location.js | 52 - module/documents/items/blades-score.js | 68 - module/sheets/BladesSheet.js | 8 - module/sheets/actor/BladesActorSheet.js | 106 +- module/sheets/actor/BladesCrewSheet.js | 37 +- module/sheets/actor/BladesFactionSheet.js | 24 +- module/sheets/actor/BladesNPCSheet.js | 39 +- module/sheets/actor/BladesPCSheet.js | 54 +- module/sheets/actor/blades-actor-sheet.js | 346 -- module/sheets/actor/blades-crew-sheet.js | 156 - module/sheets/actor/blades-faction-sheet.js | 76 - module/sheets/actor/blades-npc-sheet.js | 94 - module/sheets/actor/blades-pc-sheet.js | 345 -- module/sheets/actor/blades-sheet.js | 399 --- module/sheets/blades-actor-sheet.js | 315 -- module/sheets/blades-clock-keeper-sheet.js | 75 - module/sheets/blades-crew-sheet.js | 177 - module/sheets/blades-faction-sheet.js | 31 - module/sheets/blades-item-sheet.js | 186 -- module/sheets/blades-npc-sheet.js | 41 - module/sheets/blades-roll-collab-sheet.js | 570 ---- module/sheets/blades-sheet.js | 377 --- module/sheets/blades-tracker-sheet.js | 94 - module/sheets/item/BladesClockKeeperSheet.js | 35 +- module/sheets/item/BladesGMTrackerSheet.js | 35 +- module/sheets/item/BladesItemSheet.js | 54 +- module/sheets/item/BladesScoreSheet.js | 65 +- .../sheets/item/blades-clock-keeper-sheet.js | 82 - module/sheets/item/blades-item-sheet.js | 405 --- module/sheets/item/blades-score-sheet.js | 290 -- .../sheets/item/blades-tracker-sheet copy.js | 130 - module/sheets/item/blades-tracker-sheet.js | 129 - .../sheets/roll/blades-roll-collab-sheet.js | 641 ---- scss/chat/_chat.scss | 149 +- scss/components/_comps.scss | 206 +- scss/core/_globals.scss | 2 +- scss/core/_vars.scss | 82 +- scss/dialog/_dialogs.scss | 6 +- scss/sheets/_roll-collab-sheet.scss | 26 +- scss/sheets/_score-sheet.scss | 2 +- scss/style.scss | 1 + templates/chat/roll-result-action-roll.hbs | 65 + ...-roll.hbs => roll-result-fortune-roll.hbs} | 0 ...l.hbs => roll-result-indulgevice-roll.hbs} | 0 .../chat/roll-result-resistance-roll.hbs | 26 + templates/components/consequence.hbs | 53 +- templates/parts/dialog-consequence-block.hbs | 8 + .../roll/partials/roll-collab-action-gm.hbs | 11 +- .../partials/roll-collab-resistance-gm.hbs | 2 +- ts/@types/blades-roll.d.ts | 16 +- ts/BladesChat.ts | 90 + ts/BladesPushAlert.ts | 5 +- ts/BladesRoll.ts | 86 +- ts/blades.ts | 172 +- ts/core/constants.ts | 50 +- ts/core/gsap.ts | 752 ++++- ts/core/logger.ts | 16 +- 99 files changed, 8566 insertions(+), 7625 deletions(-) create mode 100644 assets/animations/chat/roll-position-risky.webp create mode 100644 module/BladesChat.js delete mode 100644 module/documents/actors/blades-crew.js delete mode 100644 module/documents/actors/blades-faction.js delete mode 100644 module/documents/actors/blades-npc.js delete mode 100644 module/documents/actors/blades-pc.js delete mode 100644 module/documents/blades-actor-proxy.js delete mode 100644 module/documents/blades-item-location.js delete mode 100644 module/documents/blades-item-proxy.js delete mode 100644 module/documents/items/blades-clock-keeper.js delete mode 100644 module/documents/items/blades-gm-tracker.js delete mode 100644 module/documents/items/blades-item-location.js delete mode 100644 module/documents/items/blades-location copy.js delete mode 100644 module/documents/items/blades-location.js delete mode 100644 module/documents/items/blades-score.js delete mode 100644 module/sheets/BladesSheet.js delete mode 100644 module/sheets/actor/blades-actor-sheet.js delete mode 100644 module/sheets/actor/blades-crew-sheet.js delete mode 100644 module/sheets/actor/blades-faction-sheet.js delete mode 100644 module/sheets/actor/blades-npc-sheet.js delete mode 100644 module/sheets/actor/blades-pc-sheet.js delete mode 100644 module/sheets/actor/blades-sheet.js delete mode 100644 module/sheets/blades-actor-sheet.js delete mode 100644 module/sheets/blades-clock-keeper-sheet.js delete mode 100644 module/sheets/blades-crew-sheet.js delete mode 100644 module/sheets/blades-faction-sheet.js delete mode 100644 module/sheets/blades-item-sheet.js delete mode 100644 module/sheets/blades-npc-sheet.js delete mode 100644 module/sheets/blades-roll-collab-sheet.js delete mode 100644 module/sheets/blades-sheet.js delete mode 100644 module/sheets/blades-tracker-sheet.js delete mode 100644 module/sheets/item/blades-clock-keeper-sheet.js delete mode 100644 module/sheets/item/blades-item-sheet.js delete mode 100644 module/sheets/item/blades-score-sheet.js delete mode 100644 module/sheets/item/blades-tracker-sheet copy.js delete mode 100644 module/sheets/item/blades-tracker-sheet.js delete mode 100644 module/sheets/roll/blades-roll-collab-sheet.js create mode 100644 templates/chat/roll-result-action-roll.hbs rename templates/chat/{action-roll.hbs => roll-result-fortune-roll.hbs} (100%) rename templates/chat/{resistance-roll.hbs => roll-result-indulgevice-roll.hbs} (100%) create mode 100644 templates/chat/roll-result-resistance-roll.hbs create mode 100644 ts/BladesChat.ts diff --git a/assets/animations/chat/roll-position-risky.webp b/assets/animations/chat/roll-position-risky.webp new file mode 100644 index 0000000000000000000000000000000000000000..d2b417e7f399be313327dea792952f17ffbe861e GIT binary patch literal 247612 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�YaKntFG27P0_jg&!9by@MSNluXG{MuwyhFq*mj< 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 literal 0 HcmV?d00001 diff --git a/css/style.min.css b/css/style.min.css index af7d41b4..3966dd68 100644 --- a/css/style.min.css +++ b/css/style.min.css @@ -743,22 +743,63 @@ template { --blades-grey-dark-nums: 68, 68, 68; --blades-black-nums: 32, 32, 32; --blades-black-dark-nums: 0, 0, 0; - --blades-gold-bright-nums: 255, 231, 92; - --blades-gold-nums: 255, 215, 0; - --blades-gold-dark-nums: 184, 156, 0; - --blades-gold-darkest-nums: 55, 53, 0; - --blades-red-bright-nums: 220, 20, 60; - --blades-red-nums: 204, 0, 0; - --blades-red-dark-nums: 122, 0, 0; + --blades-gold-bright-nums: 206,180, 71; + --blades-gold-nums: 143,118, 11; + --blades-gold-dark-nums: 105, 86, 0; + --blades-gold-darkest-nums: 64, 52, 0; + --blades-red-bright-nums: 255, 0, 0; + --blades-red-nums: 200, 0, 0; + --blades-red-dark-nums: 150, 0, 0; --blades-red-darkest-nums: 50, 0, 0; --blades-green-bright-nums: 20, 220, 60; --blades-green-nums: 0, 204, 0; --blades-green-dark-nums: 0, 122, 0; --blades-green-darkest-nums: 0, 60, 0; - --blades-cyan-bright-nums: 198, 255, 255; - --blades-cyan-nums: 150, 255, 255; - --blades-cyan-dark-nums: 40, 120, 120; - --blades-cyan-darkest-nums: 25, 49, 49; + --blades-blue-bright-nums: 198, 255, 255; + --blades-blue-nums: 150, 255, 255; + --blades-blue-dark-nums: 40, 120, 120; + --blades-blue-darkest-nums: 25, 49, 49; + /* + NEW COLOR PALETTE OVERRIDE + + == GOLD == + http://paletton.com/#uid=11n0u0kNTr2qtG1K2DKRbkEVqcT + + shade 0 = #D7AF00 = rgb(215,175, 0) = rgba(215,175, 0,1) = rgb0(0.843,0.686,0) + shade 1 = #FFD82C = rgb(255,216, 44) = rgba(255,216, 44,1) = rgb0(1,0.847,0.173) + shade 2 = #FFCF00 = rgb(255,207, 0) = rgba(255,207, 0,1) = rgb0(1,0.812,0) + shade 3 = #A58600 = rgb(165,134, 0) = rgba(165,134, 0,1) = rgb0(0.647,0.525,0) + shade 4 = #675300 = rgb(103, 83, 0) = rgba(103, 83, 0,1) = rgb0(0.404,0.325,0)' + + == RED == + http://paletton.com/#uid=1000u0kTixTijNOwGQpTXmEXg9Y + shade 0 = #FF0000 = rgb(255, 0, 0) = rgba(255, 0, 0,1) = rgb0(1,0,0) + shade 1 = #FF6D6D = rgb(255,109,109) = rgba(255,109,109,1) = rgb0(1,0.427,0.427) + shade 2 = #FF0000 = rgb(255, 0, 0) = rgba(255, 0, 0,1) = rgb0(1,0,0) + shade 3 = #B40000 = rgb(180, 0, 0) = rgba(180, 0, 0,1) = rgb0(0.706,0,0) + shade 4 = #4F0000 = rgb( 79, 0, 0) = rgba( 79, 0, 0,1) = rgb0(0.31,0,0) + + == BLUE == + http://paletton.com/#uid=13i0u0kTixTodNREARdTRoAV1g4 + shade 0 = #009F9F = rgb( 0,159,159) = rgba( 0,159,159,1) = rgb0(0,0.624,0.624) + shade 1 = #34D5D5 = rgb( 52,213,213) = rgba( 52,213,213,1) = rgb0(0.204,0.835,0.835) + shade 2 = #00E0E0 = rgb( 0,224,224) = rgba( 0,224,224,1) = rgb0(0,0.878,0.878) + shade 3 = #007676 = rgb( 0,118,118) = rgba( 0,118,118,1) = rgb0(0,0.463,0.463) + shade 4 = #004D4D = rgb( 0, 77, 77) = rgba( 0, 77, 77,1) = rgb0(0,0.302,0.302) + */ + --blades-gold-bright-nums: 255,216, 44; + --blades-gold-nums: 215,175, 0; + --blades-gold-dark-nums: 165,134, 0; + --blades-gold-darkest-nums: 103, 83, 0; + /* --blades-red-bright-nums: 255,109,109; + --blades-red-nums: 255, 0, 0; + --blades-red-dark-nums: 180, 0, 0; + --blades-red-darkest-nums: 79, 0, 0; */ + --blades-blue-bright-nums: 0,224,224; + --blades-blue-nums: 52,213,213; + --blades-blue-dark-nums: 0,118,118; + --blades-blue-darkest-nums: 0, 77, 77; + /* END OVERRIDE */ --blades-white-bright: rgba(var(--blades-white-bright-nums), 1); --blades-white: rgba(var(--blades-white-nums), 1); --blades-grey-bright: rgba(var(--blades-grey-bright-nums), 1); @@ -766,6 +807,7 @@ template { --blades-grey-dark: rgba(var(--blades-grey-dark-nums), 1); --blades-black: rgba(var(--blades-black-nums), 1); --blades-black-dark: rgba(var(--blades-black-dark-nums), 1); + --blades-gold-brightest: rgba(var(--blades-gold-brightest-nums), 1); --blades-gold-bright: rgba(var(--blades-gold-bright-nums), 1); --blades-gold: rgba(var(--blades-gold-nums), 1); --blades-gold-dark: rgba(var(--blades-gold-dark-nums), 1); @@ -778,10 +820,10 @@ template { --blades-green: rgba(var(--blades-green-nums), 1); --blades-green-dark: rgba(var(--blades-green-dark-nums), 1); --blades-green-darkest: rgba(var(--blades-green-darkest-nums), 1); - --blades-cyan-bright: rgba(var(--blades-cyan-bright-nums), 1); - --blades-cyan: rgba(var(--blades-cyan-nums), 1); - --blades-cyan-dark: rgba(var(--blades-cyan-dark-nums), 1); - --blades-cyan-darkest: rgba(var(--blades-cyan-darkest-nums), 1); + --blades-blue-bright: rgba(var(--blades-blue-bright-nums), 1); + --blades-blue: rgba(var(--blades-blue-nums), 1); + --blades-blue-dark: rgba(var(--blades-blue-dark-nums), 1); + --blades-blue-darkest: rgba(var(--blades-blue-darkest-nums), 1); --blades-white-fade: rgba(var(--blades-white-nums), 0.5); --blades-white-fade-strong: rgba(var(--blades-white-nums), 0.25); --blades-white-bright-fade: rgba(var(--blades-white-bright-nums), 0.5); @@ -792,10 +834,10 @@ template { --blades-black-dark-fade-strong: rgba(var(--blades-black-dark-nums), 0.25); --blades-red-dark-fade: rgba(var(--blades-red-dark-nums), 0.5); --blades-green-dark-fade: rgba(var(--blades-green-dark-nums), 0.5); - --blades-cyan-dark-fade: rgba(var(--blades-cyan-dark-nums), 0.5); + --blades-blue-dark-fade: rgba(var(--blades-blue-dark-nums), 0.5); --blades-red-dark-fade-strong: rgba(var(--blades-red-dark-nums), 0.25); --blades-green-dark-fade-strong: rgba(var(--blades-green-dark-nums), 0.25); - --blades-cyan-dark-fade-strong: rgba(var(--blades-cyan-dark-nums), 0.25); + --blades-blue-dark-fade-strong: rgba(var(--blades-blue-dark-nums), 0.25); --color-primary: var(--blades-white-nums); --color-background: var(--blades-black-nums); --color-background-lightest: var(--blades-grey-nums); @@ -1819,13 +1861,9 @@ template { :root .comp.consequence-display-container, :root * .comp.consequence-display-container { --container-height: 40px; - --container-left-shift: 50px; - --csq-icon-dark: var(--blades-red-dark); - --csq-icon-med: var(--blades-red); - --csq-icon-bright: var(--blades-red-bright); - --csq-type-bg: var(--csq-icon-dark); - --csq-type-color: var(--blades-black-dark); + --container-left-shift: 70px; --csq-icon-bg-color: var(--blades-black-dark); + --csq-type-bg: var(--csq-icon-dark); --csq-button-size-mult: 0.33; position: relative; display: block; @@ -1833,34 +1871,128 @@ template { max-height: var(--container-height); min-height: var(--container-height); } +@keyframes anim-glow { + 0% { + box-shadow: 0 0 0px var(--blades-red-bright); + background-color: var(--blades-red-darkest); + } + 10% { + background-color: var(--blades-red-bright); + } + 100% { + box-shadow: 0 0 10px 10px transparent; + background-color: var(--blades-red-darkest); + } +} +@keyframes icon-glow { + 0% { + filter: brightness(1); + } + 10% { + filter: brightness(1); + } + 100% { + filter: brightness(1); + } +} +@keyframes icon-red-pulse { + 0% { + scale: 1; + fill: var(--blades-grey); + } + 10% { + scale: 1.1; + fill: var(--blades-red-bright); + } + 100% { + scale: 1; + fill: var(--blades-grey); + } +} +:root .comp.consequence-display-container .base-consequence, +:root * .comp.consequence-display-container .base-consequence { + --csq-icon-dark: var(--blades-black); + --csq-icon-med: var(--blades-grey); + --csq-icon-bright: var(--blades-white); + --csq-type-color: var(--blades-grey); + --csq-name-color: var(--blades-white); +} +:root .comp.consequence-display-container .accept-consequence, +:root * .comp.consequence-display-container .accept-consequence { + opacity: 0; + --csq-icon-dark: var(--blades-red-dark); + --csq-icon-med: var(--blades-red); + --csq-icon-bright: var(--blades-red-bright); + --csq-type-color: var(--blades-black-dark); + --csq-name-color: var(--blades-red); +} :root .comp.consequence-display-container .resist-consequence, :root * .comp.consequence-display-container .resist-consequence { + opacity: 0; --csq-icon-dark: var(--blades-gold-dark); --csq-icon-med: var(--blades-gold); --csq-icon-bright: var(--blades-gold-bright); --csq-type-color: var(--blades-gold-dark); --csq-name-color: var(--blades-gold-bright); } +:root .comp.consequence-display-container .special-armor-consequence, +:root * .comp.consequence-display-container .special-armor-consequence { + opacity: 0; + --csq-icon-dark: var(--blades-blue-dark); + --csq-icon-med: var(--blades-blue); + --csq-icon-bright: var(--blades-blue-bright); + --csq-type-color: var(--blades-blue-dark); + --csq-name-color: var(--blades-blue-bright); +} +:root .comp.consequence-display-container .consequence-interaction-pad, +:root * .comp.consequence-display-container .consequence-interaction-pad { + display: none; + display: block; + position: absolute; + z-index: 2; + pointer-events: auto; + height: 100%; + top: 0; +} +:root .comp.consequence-display-container .consequence-interaction-pad.accept-consequence-pad, +:root * .comp.consequence-display-container .consequence-interaction-pad.accept-consequence-pad { + --pad-left-shift: calc(var(--container-left-shift) + (var(--container-height))); + left: var(--pad-left-shift); + width: calc(100% - var(--pad-left-shift)); +} +:root .comp.consequence-display-container .consequence-interaction-pad.resist-consequence-pad, :root .comp.consequence-display-container .consequence-interaction-pad.special-armor-consequence-pad, +:root * .comp.consequence-display-container .consequence-interaction-pad.resist-consequence-pad, +:root * .comp.consequence-display-container .consequence-interaction-pad.special-armor-consequence-pad { + left: 0; + width: calc(var(--container-left-shift) - var(--container-height) * 0); +} +:root .comp.consequence-display-container .consequence-interaction-pad.special-armor-consequence-pad, +:root * .comp.consequence-display-container .consequence-interaction-pad.special-armor-consequence-pad { + height: 40%; + z-index: 3; +} :root .comp.consequence-display-container .consequence-icon-container, :root * .comp.consequence-display-container .consequence-icon-container { position: relative; - height: var(--container-height); - width: var(--container-height); - min-width: var(--container-height); - max-width: var(--container-height); - min-height: var(--container-height); - max-height: var(--container-height); + height: calc(var(--container-height) * 0.75); + max-width: calc(var(--container-height) * 0.75); background: transparent; left: var(--container-left-shift); z-index: 1; + pointer-events: auto; + transition: 0.2s; +} +:root .comp.consequence-display-container .consequence-icon-container:hover, +:root * .comp.consequence-display-container .consequence-icon-container:hover { + filter: brightness(2); } :root .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle, :root * .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle { position: absolute; translate: -50% -50%; - transform-origin: 50% 50%; - top: 50%; - left: 50%; + transform-origin: 100% 0%; + top: calc(var(--container-height) * 0.5); + left: calc(var(--container-height) * 0.5); border-radius: 50%; height: var(--container-height); width: var(--container-height); @@ -1897,6 +2029,30 @@ template { :root * .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle svg .fill-linear { fill: var(--csq-icon-med); } +:root .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle svg path, +:root * .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle svg path { + transform-origin: 50% 50%; +} +:root .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence, +:root * .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence { + scale: 0.75; + animation: icon-glow 2s ease infinite; + pointer-events: auto; +} +:root .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence svg path, +:root * .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence svg path { + animation: icon-red-pulse 2s ease infinite; +} +:root .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence:hover, +:root * .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence:hover { + filter: brightness(2) !important; + animation: none; +} +:root .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.resist-consequence, :root .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.special-armor-consequence, +:root * .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.resist-consequence, +:root * .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.special-armor-consequence { + outline-width: 2px; +} :root .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle .consequence-icon, :root * .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle .consequence-icon { height: 100%; @@ -1908,7 +2064,7 @@ template { display: flex; flex-direction: row; flex-wrap: nowrap; - bottom: 0px; + bottom: -10px; } :root .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg, :root * .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg { @@ -1916,10 +2072,16 @@ template { z-index: -1; height: 100%; width: calc(100% + 24px); + transform-origin: 0% 50%; top: 0px; background: var(--csq-icon-bright); display: block; } +:root .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg.consequence-resist-button-bg, :root .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg.consequence-special-armor-button-bg, +:root * .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg.consequence-resist-button-bg, +:root * .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg.consequence-special-armor-button-bg { + transform-origin: 100% 50%; +} :root .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-label, :root * .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-label { position: relative; @@ -1928,6 +2090,9 @@ template { font-size: 10px; line-height: 14px; color: var(--blades-grey); + font-weight: 800; + text-shadow: 0px 0px 1px var(--blades-black-dark); + letter-spacing: 1; text-transform: uppercase; white-space: nowrap; } @@ -1947,7 +2112,7 @@ template { } :root .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-resist-button-container, :root * .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-resist-button-container { - right: calc(100% - 3px); + right: 100%; } :root .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-resist-button-container .consequence-button-bg, :root * .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-resist-button-container .consequence-button-bg { @@ -1956,7 +2121,7 @@ template { } :root .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-accept-button-container, :root * .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-accept-button-container { - left: 100%; + left: 140%; } :root .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-accept-button-container .consequence-button-bg, :root * .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-accept-button-container .consequence-button-bg { @@ -1974,7 +2139,7 @@ template { height: calc(var(--container-height) * 0.33); transform-origin: 0% 50%; left: calc(var(--container-height) + var(--container-left-shift) - 10px); - top: 0px; + top: -2px; padding: 0 5px 0 15px; } :root .comp.consequence-display-container .consequence-type-container .consequence-type-bg, @@ -1984,21 +2149,22 @@ template { left: -20px; height: 100%; width: 170px; + transform-origin: 0% 50%; transform: skewX(-45deg); - background: var(--csq-type-bg); + background: var(--csq-icon-dark); } :root .comp.consequence-display-container .consequence-type-container .consequence-type, :root * .comp.consequence-display-container .consequence-type-container .consequence-type { position: absolute; top: 0; - left: 10px; + transform-origin: 0% 50%; white-space: nowrap; font-family: Oswald, sans-serif; text-transform: uppercase; text-align: right; font-size: 10px; color: var(--csq-type-color); - font-weight: bold; + font-weight: normal; } :root .comp.consequence-display-container .consequence-name-container, :root * .comp.consequence-display-container .consequence-name-container { @@ -2016,9 +2182,10 @@ template { z-index: 1; padding: 0 5px 0 35px; font-size: 14px; - line-height: calc(var(--container-height) * 0.55); + line-height: 17px; font-family: Kirsty, serif; font-variant: small-caps; + transform-origin: 0% 50%; color: var(--csq-icon-bright); font-style: italic; } @@ -2030,7 +2197,7 @@ template { :root * .comp.consequence-display-container .consequence-footer-container { position: absolute; height: calc(var(--container-height) * var(--csq-button-size-mult)); - width: 120px; + width: auto; bottom: 0; top: unset; left: calc(var(--container-height) + var(--container-left-shift) - 20px); @@ -2045,9 +2212,20 @@ template { background: var(--csq-icon-bright); display: block; transform: skewX(45deg); + transform-origin: 0% 50%; +} +:root .comp.consequence-display-container .consequence-footer-container .consequence-footer-bg.resist-consequence, +:root * .comp.consequence-display-container .consequence-footer-container .consequence-footer-bg.resist-consequence { + width: 120px; } -:root .comp.consequence-display-container .consequence-footer-container .consequence-resist-attribute, -:root * .comp.consequence-display-container .consequence-footer-container .consequence-resist-attribute { +:root .comp.consequence-display-container .consequence-footer-container .consequence-footer-bg.special-armor-consequence, +:root * .comp.consequence-display-container .consequence-footer-container .consequence-footer-bg.special-armor-consequence { + width: 250px; +} +:root .comp.consequence-display-container .consequence-footer-container .consequence-footer-message, +:root * .comp.consequence-display-container .consequence-footer-container .consequence-footer-message { + position: absolute; + white-space: nowrap; font-family: Oswald, sans-serif; font-weight: bold; color: var(--blades-black-dark); @@ -2055,6 +2233,7 @@ template { line-height: 14px; padding-left: 25px; justify-content: flex-start; + transform-origin: 0% 50%; gap: 5px; } :root .comp.consequence-display-container .consequence-footer-container .dotline, @@ -3407,13 +3586,9 @@ template { } :root body.vtt.game.system-eunos-blades #clocks-overlay .comp.consequence-display-container { --container-height: 40px; - --container-left-shift: 50px; - --csq-icon-dark: var(--blades-red-dark); - --csq-icon-med: var(--blades-red); - --csq-icon-bright: var(--blades-red-bright); - --csq-type-bg: var(--csq-icon-dark); - --csq-type-color: var(--blades-black-dark); + --container-left-shift: 70px; --csq-icon-bg-color: var(--blades-black-dark); + --csq-type-bg: var(--csq-icon-dark); --csq-button-size-mult: 0.33; position: relative; display: block; @@ -3421,31 +3596,116 @@ template { max-height: var(--container-height); min-height: var(--container-height); } +@keyframes anim-glow { + 0% { + box-shadow: 0 0 0px var(--blades-red-bright); + background-color: var(--blades-red-darkest); + } + 10% { + background-color: var(--blades-red-bright); + } + 100% { + box-shadow: 0 0 10px 10px transparent; + background-color: var(--blades-red-darkest); + } +} +@keyframes icon-glow { + 0% { + filter: brightness(1); + } + 10% { + filter: brightness(1); + } + 100% { + filter: brightness(1); + } +} +@keyframes icon-red-pulse { + 0% { + scale: 1; + fill: var(--blades-grey); + } + 10% { + scale: 1.1; + fill: var(--blades-red-bright); + } + 100% { + scale: 1; + fill: var(--blades-grey); + } +} +:root body.vtt.game.system-eunos-blades #clocks-overlay .comp.consequence-display-container .base-consequence { + --csq-icon-dark: var(--blades-black); + --csq-icon-med: var(--blades-grey); + --csq-icon-bright: var(--blades-white); + --csq-type-color: var(--blades-grey); + --csq-name-color: var(--blades-white); +} +:root body.vtt.game.system-eunos-blades #clocks-overlay .comp.consequence-display-container .accept-consequence { + opacity: 0; + --csq-icon-dark: var(--blades-red-dark); + --csq-icon-med: var(--blades-red); + --csq-icon-bright: var(--blades-red-bright); + --csq-type-color: var(--blades-black-dark); + --csq-name-color: var(--blades-red); +} :root body.vtt.game.system-eunos-blades #clocks-overlay .comp.consequence-display-container .resist-consequence { + opacity: 0; --csq-icon-dark: var(--blades-gold-dark); --csq-icon-med: var(--blades-gold); --csq-icon-bright: var(--blades-gold-bright); --csq-type-color: var(--blades-gold-dark); --csq-name-color: var(--blades-gold-bright); } +:root body.vtt.game.system-eunos-blades #clocks-overlay .comp.consequence-display-container .special-armor-consequence { + opacity: 0; + --csq-icon-dark: var(--blades-blue-dark); + --csq-icon-med: var(--blades-blue); + --csq-icon-bright: var(--blades-blue-bright); + --csq-type-color: var(--blades-blue-dark); + --csq-name-color: var(--blades-blue-bright); +} +:root body.vtt.game.system-eunos-blades #clocks-overlay .comp.consequence-display-container .consequence-interaction-pad { + display: none; + display: block; + position: absolute; + z-index: 2; + pointer-events: auto; + height: 100%; + top: 0; +} +:root body.vtt.game.system-eunos-blades #clocks-overlay .comp.consequence-display-container .consequence-interaction-pad.accept-consequence-pad { + --pad-left-shift: calc(var(--container-left-shift) + (var(--container-height))); + left: var(--pad-left-shift); + width: calc(100% - var(--pad-left-shift)); +} +:root body.vtt.game.system-eunos-blades #clocks-overlay .comp.consequence-display-container .consequence-interaction-pad.resist-consequence-pad, :root body.vtt.game.system-eunos-blades #clocks-overlay .comp.consequence-display-container .consequence-interaction-pad.special-armor-consequence-pad { + left: 0; + width: calc(var(--container-left-shift) - var(--container-height) * 0); +} +:root body.vtt.game.system-eunos-blades #clocks-overlay .comp.consequence-display-container .consequence-interaction-pad.special-armor-consequence-pad { + height: 40%; + z-index: 3; +} :root body.vtt.game.system-eunos-blades #clocks-overlay .comp.consequence-display-container .consequence-icon-container { position: relative; - height: var(--container-height); - width: var(--container-height); - min-width: var(--container-height); - max-width: var(--container-height); - min-height: var(--container-height); - max-height: var(--container-height); + height: calc(var(--container-height) * 0.75); + max-width: calc(var(--container-height) * 0.75); background: transparent; left: var(--container-left-shift); z-index: 1; + pointer-events: auto; + transition: 0.2s; +} +:root body.vtt.game.system-eunos-blades #clocks-overlay .comp.consequence-display-container .consequence-icon-container:hover { + filter: brightness(2); } :root body.vtt.game.system-eunos-blades #clocks-overlay .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle { position: absolute; translate: -50% -50%; - transform-origin: 50% 50%; - top: 50%; - left: 50%; + transform-origin: 100% 0%; + top: calc(var(--container-height) * 0.5); + left: calc(var(--container-height) * 0.5); border-radius: 50%; height: var(--container-height); width: var(--container-height); @@ -3476,6 +3736,24 @@ template { :root body.vtt.game.system-eunos-blades #clocks-overlay .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle svg .fill-linear { fill: var(--csq-icon-med); } +:root body.vtt.game.system-eunos-blades #clocks-overlay .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle svg path { + transform-origin: 50% 50%; +} +:root body.vtt.game.system-eunos-blades #clocks-overlay .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence { + scale: 0.75; + animation: icon-glow 2s ease infinite; + pointer-events: auto; +} +:root body.vtt.game.system-eunos-blades #clocks-overlay .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence svg path { + animation: icon-red-pulse 2s ease infinite; +} +:root body.vtt.game.system-eunos-blades #clocks-overlay .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence:hover { + filter: brightness(2) !important; + animation: none; +} +:root body.vtt.game.system-eunos-blades #clocks-overlay .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.resist-consequence, :root body.vtt.game.system-eunos-blades #clocks-overlay .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.special-armor-consequence { + outline-width: 2px; +} :root body.vtt.game.system-eunos-blades #clocks-overlay .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle .consequence-icon { height: 100%; } @@ -3485,17 +3763,21 @@ template { display: flex; flex-direction: row; flex-wrap: nowrap; - bottom: 0px; + bottom: -10px; } :root body.vtt.game.system-eunos-blades #clocks-overlay .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg { position: absolute; z-index: -1; height: 100%; width: calc(100% + 24px); + transform-origin: 0% 50%; top: 0px; background: var(--csq-icon-bright); display: block; } +:root body.vtt.game.system-eunos-blades #clocks-overlay .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg.consequence-resist-button-bg, :root body.vtt.game.system-eunos-blades #clocks-overlay .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg.consequence-special-armor-button-bg { + transform-origin: 100% 50%; +} :root body.vtt.game.system-eunos-blades #clocks-overlay .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-label { position: relative; z-index: 1; @@ -3503,6 +3785,9 @@ template { font-size: 10px; line-height: 14px; color: var(--blades-grey); + font-weight: 800; + text-shadow: 0px 0px 1px var(--blades-black-dark); + letter-spacing: 1; text-transform: uppercase; white-space: nowrap; } @@ -3519,14 +3804,14 @@ template { margin: 0; } :root body.vtt.game.system-eunos-blades #clocks-overlay .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-resist-button-container { - right: calc(100% - 3px); + right: 100%; } :root body.vtt.game.system-eunos-blades #clocks-overlay .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-resist-button-container .consequence-button-bg { left: -7px; transform: skewX(45deg); } :root body.vtt.game.system-eunos-blades #clocks-overlay .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-accept-button-container { - left: 100%; + left: 140%; } :root body.vtt.game.system-eunos-blades #clocks-overlay .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-accept-button-container .consequence-button-bg { right: -7px; @@ -3541,7 +3826,7 @@ template { height: calc(var(--container-height) * 0.33); transform-origin: 0% 50%; left: calc(var(--container-height) + var(--container-left-shift) - 10px); - top: 0px; + top: -2px; padding: 0 5px 0 15px; } :root body.vtt.game.system-eunos-blades #clocks-overlay .comp.consequence-display-container .consequence-type-container .consequence-type-bg { @@ -3550,20 +3835,21 @@ template { left: -20px; height: 100%; width: 170px; + transform-origin: 0% 50%; transform: skewX(-45deg); - background: var(--csq-type-bg); + background: var(--csq-icon-dark); } :root body.vtt.game.system-eunos-blades #clocks-overlay .comp.consequence-display-container .consequence-type-container .consequence-type { position: absolute; top: 0; - left: 10px; + transform-origin: 0% 50%; white-space: nowrap; font-family: Oswald, sans-serif; text-transform: uppercase; text-align: right; font-size: 10px; color: var(--csq-type-color); - font-weight: bold; + font-weight: normal; } :root body.vtt.game.system-eunos-blades #clocks-overlay .comp.consequence-display-container .consequence-name-container { position: absolute; @@ -3579,9 +3865,10 @@ template { z-index: 1; padding: 0 5px 0 35px; font-size: 14px; - line-height: calc(var(--container-height) * 0.55); + line-height: 17px; font-family: Kirsty, serif; font-variant: small-caps; + transform-origin: 0% 50%; color: var(--csq-icon-bright); font-style: italic; } @@ -3591,7 +3878,7 @@ template { :root body.vtt.game.system-eunos-blades #clocks-overlay .comp.consequence-display-container .consequence-footer-container { position: absolute; height: calc(var(--container-height) * var(--csq-button-size-mult)); - width: 120px; + width: auto; bottom: 0; top: unset; left: calc(var(--container-height) + var(--container-left-shift) - 20px); @@ -3605,8 +3892,17 @@ template { background: var(--csq-icon-bright); display: block; transform: skewX(45deg); + transform-origin: 0% 50%; +} +:root body.vtt.game.system-eunos-blades #clocks-overlay .comp.consequence-display-container .consequence-footer-container .consequence-footer-bg.resist-consequence { + width: 120px; } -:root body.vtt.game.system-eunos-blades #clocks-overlay .comp.consequence-display-container .consequence-footer-container .consequence-resist-attribute { +:root body.vtt.game.system-eunos-blades #clocks-overlay .comp.consequence-display-container .consequence-footer-container .consequence-footer-bg.special-armor-consequence { + width: 250px; +} +:root body.vtt.game.system-eunos-blades #clocks-overlay .comp.consequence-display-container .consequence-footer-container .consequence-footer-message { + position: absolute; + white-space: nowrap; font-family: Oswald, sans-serif; font-weight: bold; color: var(--blades-black-dark); @@ -3614,6 +3910,7 @@ template { line-height: 14px; padding-left: 25px; justify-content: flex-start; + transform-origin: 0% 50%; gap: 5px; } :root body.vtt.game.system-eunos-blades #clocks-overlay .comp.consequence-display-container .consequence-footer-container .dotline { @@ -6491,13 +6788,9 @@ template { :root body.vtt.game.system-eunos-blades #hotbar .comp.consequence-display-container, :root body.vtt.game.system-eunos-blades #players .comp.consequence-display-container { --container-height: 40px; - --container-left-shift: 50px; - --csq-icon-dark: var(--blades-red-dark); - --csq-icon-med: var(--blades-red); - --csq-icon-bright: var(--blades-red-bright); - --csq-type-bg: var(--csq-icon-dark); - --csq-type-color: var(--blades-black-dark); + --container-left-shift: 70px; --csq-icon-bg-color: var(--blades-black-dark); + --csq-type-bg: var(--csq-icon-dark); --csq-button-size-mult: 0.33; position: relative; display: block; @@ -6505,32 +6798,153 @@ template { max-height: var(--container-height); min-height: var(--container-height); } +@keyframes anim-glow { + 0% { + box-shadow: 0 0 0px var(--blades-red-bright); + background-color: var(--blades-red-darkest); + } + 10% { + background-color: var(--blades-red-bright); + } + 100% { + box-shadow: 0 0 10px 10px transparent; + background-color: var(--blades-red-darkest); + } +} +@keyframes icon-glow { + 0% { + filter: brightness(1); + } + 10% { + filter: brightness(1); + } + 100% { + filter: brightness(1); + } +} +@keyframes icon-red-pulse { + 0% { + scale: 1; + fill: var(--blades-grey); + } + 10% { + scale: 1.1; + fill: var(--blades-red-bright); + } + 100% { + scale: 1; + fill: var(--blades-grey); + } +} +:root body.vtt.game.system-eunos-blades #interface .comp.consequence-display-container .base-consequence, +:root body.vtt.game.system-eunos-blades #controls .comp.consequence-display-container .base-consequence, +:root body.vtt.game.system-eunos-blades #navigation .comp.consequence-display-container .base-consequence, +:root body.vtt.game.system-eunos-blades #hotbar .comp.consequence-display-container .base-consequence, +:root body.vtt.game.system-eunos-blades #players .comp.consequence-display-container .base-consequence { + --csq-icon-dark: var(--blades-black); + --csq-icon-med: var(--blades-grey); + --csq-icon-bright: var(--blades-white); + --csq-type-color: var(--blades-grey); + --csq-name-color: var(--blades-white); +} +:root body.vtt.game.system-eunos-blades #interface .comp.consequence-display-container .accept-consequence, +:root body.vtt.game.system-eunos-blades #controls .comp.consequence-display-container .accept-consequence, +:root body.vtt.game.system-eunos-blades #navigation .comp.consequence-display-container .accept-consequence, +:root body.vtt.game.system-eunos-blades #hotbar .comp.consequence-display-container .accept-consequence, +:root body.vtt.game.system-eunos-blades #players .comp.consequence-display-container .accept-consequence { + opacity: 0; + --csq-icon-dark: var(--blades-red-dark); + --csq-icon-med: var(--blades-red); + --csq-icon-bright: var(--blades-red-bright); + --csq-type-color: var(--blades-black-dark); + --csq-name-color: var(--blades-red); +} :root body.vtt.game.system-eunos-blades #interface .comp.consequence-display-container .resist-consequence, :root body.vtt.game.system-eunos-blades #controls .comp.consequence-display-container .resist-consequence, :root body.vtt.game.system-eunos-blades #navigation .comp.consequence-display-container .resist-consequence, :root body.vtt.game.system-eunos-blades #hotbar .comp.consequence-display-container .resist-consequence, :root body.vtt.game.system-eunos-blades #players .comp.consequence-display-container .resist-consequence { + opacity: 0; --csq-icon-dark: var(--blades-gold-dark); --csq-icon-med: var(--blades-gold); --csq-icon-bright: var(--blades-gold-bright); --csq-type-color: var(--blades-gold-dark); --csq-name-color: var(--blades-gold-bright); } +:root body.vtt.game.system-eunos-blades #interface .comp.consequence-display-container .special-armor-consequence, +:root body.vtt.game.system-eunos-blades #controls .comp.consequence-display-container .special-armor-consequence, +:root body.vtt.game.system-eunos-blades #navigation .comp.consequence-display-container .special-armor-consequence, +:root body.vtt.game.system-eunos-blades #hotbar .comp.consequence-display-container .special-armor-consequence, +:root body.vtt.game.system-eunos-blades #players .comp.consequence-display-container .special-armor-consequence { + opacity: 0; + --csq-icon-dark: var(--blades-blue-dark); + --csq-icon-med: var(--blades-blue); + --csq-icon-bright: var(--blades-blue-bright); + --csq-type-color: var(--blades-blue-dark); + --csq-name-color: var(--blades-blue-bright); +} +:root body.vtt.game.system-eunos-blades #interface .comp.consequence-display-container .consequence-interaction-pad, +:root body.vtt.game.system-eunos-blades #controls .comp.consequence-display-container .consequence-interaction-pad, +:root body.vtt.game.system-eunos-blades #navigation .comp.consequence-display-container .consequence-interaction-pad, +:root body.vtt.game.system-eunos-blades #hotbar .comp.consequence-display-container .consequence-interaction-pad, +:root body.vtt.game.system-eunos-blades #players .comp.consequence-display-container .consequence-interaction-pad { + display: none; + display: block; + position: absolute; + z-index: 2; + pointer-events: auto; + height: 100%; + top: 0; +} +:root body.vtt.game.system-eunos-blades #interface .comp.consequence-display-container .consequence-interaction-pad.accept-consequence-pad, +:root body.vtt.game.system-eunos-blades #controls .comp.consequence-display-container .consequence-interaction-pad.accept-consequence-pad, +:root body.vtt.game.system-eunos-blades #navigation .comp.consequence-display-container .consequence-interaction-pad.accept-consequence-pad, +:root body.vtt.game.system-eunos-blades #hotbar .comp.consequence-display-container .consequence-interaction-pad.accept-consequence-pad, +:root body.vtt.game.system-eunos-blades #players .comp.consequence-display-container .consequence-interaction-pad.accept-consequence-pad { + --pad-left-shift: calc(var(--container-left-shift) + (var(--container-height))); + left: var(--pad-left-shift); + width: calc(100% - var(--pad-left-shift)); +} +:root body.vtt.game.system-eunos-blades #interface .comp.consequence-display-container .consequence-interaction-pad.resist-consequence-pad, :root body.vtt.game.system-eunos-blades #interface .comp.consequence-display-container .consequence-interaction-pad.special-armor-consequence-pad, +:root body.vtt.game.system-eunos-blades #controls .comp.consequence-display-container .consequence-interaction-pad.resist-consequence-pad, +:root body.vtt.game.system-eunos-blades #controls .comp.consequence-display-container .consequence-interaction-pad.special-armor-consequence-pad, +:root body.vtt.game.system-eunos-blades #navigation .comp.consequence-display-container .consequence-interaction-pad.resist-consequence-pad, +:root body.vtt.game.system-eunos-blades #navigation .comp.consequence-display-container .consequence-interaction-pad.special-armor-consequence-pad, +:root body.vtt.game.system-eunos-blades #hotbar .comp.consequence-display-container .consequence-interaction-pad.resist-consequence-pad, +:root body.vtt.game.system-eunos-blades #hotbar .comp.consequence-display-container .consequence-interaction-pad.special-armor-consequence-pad, +:root body.vtt.game.system-eunos-blades #players .comp.consequence-display-container .consequence-interaction-pad.resist-consequence-pad, +:root body.vtt.game.system-eunos-blades #players .comp.consequence-display-container .consequence-interaction-pad.special-armor-consequence-pad { + left: 0; + width: calc(var(--container-left-shift) - var(--container-height) * 0); +} +:root body.vtt.game.system-eunos-blades #interface .comp.consequence-display-container .consequence-interaction-pad.special-armor-consequence-pad, +:root body.vtt.game.system-eunos-blades #controls .comp.consequence-display-container .consequence-interaction-pad.special-armor-consequence-pad, +:root body.vtt.game.system-eunos-blades #navigation .comp.consequence-display-container .consequence-interaction-pad.special-armor-consequence-pad, +:root body.vtt.game.system-eunos-blades #hotbar .comp.consequence-display-container .consequence-interaction-pad.special-armor-consequence-pad, +:root body.vtt.game.system-eunos-blades #players .comp.consequence-display-container .consequence-interaction-pad.special-armor-consequence-pad { + height: 40%; + z-index: 3; +} :root body.vtt.game.system-eunos-blades #interface .comp.consequence-display-container .consequence-icon-container, :root body.vtt.game.system-eunos-blades #controls .comp.consequence-display-container .consequence-icon-container, :root body.vtt.game.system-eunos-blades #navigation .comp.consequence-display-container .consequence-icon-container, :root body.vtt.game.system-eunos-blades #hotbar .comp.consequence-display-container .consequence-icon-container, :root body.vtt.game.system-eunos-blades #players .comp.consequence-display-container .consequence-icon-container { position: relative; - height: var(--container-height); - width: var(--container-height); - min-width: var(--container-height); - max-width: var(--container-height); - min-height: var(--container-height); - max-height: var(--container-height); + height: calc(var(--container-height) * 0.75); + max-width: calc(var(--container-height) * 0.75); background: transparent; left: var(--container-left-shift); z-index: 1; + pointer-events: auto; + transition: 0.2s; +} +:root body.vtt.game.system-eunos-blades #interface .comp.consequence-display-container .consequence-icon-container:hover, +:root body.vtt.game.system-eunos-blades #controls .comp.consequence-display-container .consequence-icon-container:hover, +:root body.vtt.game.system-eunos-blades #navigation .comp.consequence-display-container .consequence-icon-container:hover, +:root body.vtt.game.system-eunos-blades #hotbar .comp.consequence-display-container .consequence-icon-container:hover, +:root body.vtt.game.system-eunos-blades #players .comp.consequence-display-container .consequence-icon-container:hover { + filter: brightness(2); } :root body.vtt.game.system-eunos-blades #interface .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle, :root body.vtt.game.system-eunos-blades #controls .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle, @@ -6539,9 +6953,9 @@ template { :root body.vtt.game.system-eunos-blades #players .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle { position: absolute; translate: -50% -50%; - transform-origin: 50% 50%; - top: 50%; - left: 50%; + transform-origin: 100% 0%; + top: calc(var(--container-height) * 0.5); + left: calc(var(--container-height) * 0.5); border-radius: 50%; height: var(--container-height); width: var(--container-height); @@ -6596,9 +7010,51 @@ template { :root body.vtt.game.system-eunos-blades #players .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle svg .fill-linear { fill: var(--csq-icon-med); } -:root body.vtt.game.system-eunos-blades #interface .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle .consequence-icon, -:root body.vtt.game.system-eunos-blades #controls .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle .consequence-icon, -:root body.vtt.game.system-eunos-blades #navigation .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle .consequence-icon, +:root body.vtt.game.system-eunos-blades #interface .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle svg path, +:root body.vtt.game.system-eunos-blades #controls .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle svg path, +:root body.vtt.game.system-eunos-blades #navigation .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle svg path, +:root body.vtt.game.system-eunos-blades #hotbar .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle svg path, +:root body.vtt.game.system-eunos-blades #players .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle svg path { + transform-origin: 50% 50%; +} +:root body.vtt.game.system-eunos-blades #interface .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence, +:root body.vtt.game.system-eunos-blades #controls .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence, +:root body.vtt.game.system-eunos-blades #navigation .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence, +:root body.vtt.game.system-eunos-blades #hotbar .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence, +:root body.vtt.game.system-eunos-blades #players .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence { + scale: 0.75; + animation: icon-glow 2s ease infinite; + pointer-events: auto; +} +:root body.vtt.game.system-eunos-blades #interface .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence svg path, +:root body.vtt.game.system-eunos-blades #controls .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence svg path, +:root body.vtt.game.system-eunos-blades #navigation .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence svg path, +:root body.vtt.game.system-eunos-blades #hotbar .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence svg path, +:root body.vtt.game.system-eunos-blades #players .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence svg path { + animation: icon-red-pulse 2s ease infinite; +} +:root body.vtt.game.system-eunos-blades #interface .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence:hover, +:root body.vtt.game.system-eunos-blades #controls .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence:hover, +:root body.vtt.game.system-eunos-blades #navigation .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence:hover, +:root body.vtt.game.system-eunos-blades #hotbar .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence:hover, +:root body.vtt.game.system-eunos-blades #players .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence:hover { + filter: brightness(2) !important; + animation: none; +} +:root body.vtt.game.system-eunos-blades #interface .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.resist-consequence, :root body.vtt.game.system-eunos-blades #interface .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.special-armor-consequence, +:root body.vtt.game.system-eunos-blades #controls .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.resist-consequence, +:root body.vtt.game.system-eunos-blades #controls .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.special-armor-consequence, +:root body.vtt.game.system-eunos-blades #navigation .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.resist-consequence, +:root body.vtt.game.system-eunos-blades #navigation .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.special-armor-consequence, +:root body.vtt.game.system-eunos-blades #hotbar .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.resist-consequence, +:root body.vtt.game.system-eunos-blades #hotbar .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.special-armor-consequence, +:root body.vtt.game.system-eunos-blades #players .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.resist-consequence, +:root body.vtt.game.system-eunos-blades #players .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.special-armor-consequence { + outline-width: 2px; +} +:root body.vtt.game.system-eunos-blades #interface .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle .consequence-icon, +:root body.vtt.game.system-eunos-blades #controls .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle .consequence-icon, +:root body.vtt.game.system-eunos-blades #navigation .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle .consequence-icon, :root body.vtt.game.system-eunos-blades #hotbar .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle .consequence-icon, :root body.vtt.game.system-eunos-blades #players .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle .consequence-icon { height: 100%; @@ -6613,7 +7069,7 @@ template { display: flex; flex-direction: row; flex-wrap: nowrap; - bottom: 0px; + bottom: -10px; } :root body.vtt.game.system-eunos-blades #interface .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg, :root body.vtt.game.system-eunos-blades #controls .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg, @@ -6624,10 +7080,22 @@ template { z-index: -1; height: 100%; width: calc(100% + 24px); + transform-origin: 0% 50%; top: 0px; background: var(--csq-icon-bright); display: block; } +:root body.vtt.game.system-eunos-blades #interface .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg.consequence-resist-button-bg, :root body.vtt.game.system-eunos-blades #interface .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg.consequence-special-armor-button-bg, +:root body.vtt.game.system-eunos-blades #controls .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg.consequence-resist-button-bg, +:root body.vtt.game.system-eunos-blades #controls .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg.consequence-special-armor-button-bg, +:root body.vtt.game.system-eunos-blades #navigation .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg.consequence-resist-button-bg, +:root body.vtt.game.system-eunos-blades #navigation .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg.consequence-special-armor-button-bg, +:root body.vtt.game.system-eunos-blades #hotbar .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg.consequence-resist-button-bg, +:root body.vtt.game.system-eunos-blades #hotbar .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg.consequence-special-armor-button-bg, +:root body.vtt.game.system-eunos-blades #players .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg.consequence-resist-button-bg, +:root body.vtt.game.system-eunos-blades #players .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg.consequence-special-armor-button-bg { + transform-origin: 100% 50%; +} :root body.vtt.game.system-eunos-blades #interface .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-label, :root body.vtt.game.system-eunos-blades #controls .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-label, :root body.vtt.game.system-eunos-blades #navigation .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-label, @@ -6639,6 +7107,9 @@ template { font-size: 10px; line-height: 14px; color: var(--blades-grey); + font-weight: 800; + text-shadow: 0px 0px 1px var(--blades-black-dark); + letter-spacing: 1; text-transform: uppercase; white-space: nowrap; } @@ -6667,7 +7138,7 @@ template { :root body.vtt.game.system-eunos-blades #navigation .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-resist-button-container, :root body.vtt.game.system-eunos-blades #hotbar .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-resist-button-container, :root body.vtt.game.system-eunos-blades #players .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-resist-button-container { - right: calc(100% - 3px); + right: 100%; } :root body.vtt.game.system-eunos-blades #interface .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-resist-button-container .consequence-button-bg, :root body.vtt.game.system-eunos-blades #controls .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-resist-button-container .consequence-button-bg, @@ -6682,7 +7153,7 @@ template { :root body.vtt.game.system-eunos-blades #navigation .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-accept-button-container, :root body.vtt.game.system-eunos-blades #hotbar .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-accept-button-container, :root body.vtt.game.system-eunos-blades #players .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-accept-button-container { - left: 100%; + left: 140%; } :root body.vtt.game.system-eunos-blades #interface .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-accept-button-container .consequence-button-bg, :root body.vtt.game.system-eunos-blades #controls .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-accept-button-container .consequence-button-bg, @@ -6709,7 +7180,7 @@ template { height: calc(var(--container-height) * 0.33); transform-origin: 0% 50%; left: calc(var(--container-height) + var(--container-left-shift) - 10px); - top: 0px; + top: -2px; padding: 0 5px 0 15px; } :root body.vtt.game.system-eunos-blades #interface .comp.consequence-display-container .consequence-type-container .consequence-type-bg, @@ -6722,8 +7193,9 @@ template { left: -20px; height: 100%; width: 170px; + transform-origin: 0% 50%; transform: skewX(-45deg); - background: var(--csq-type-bg); + background: var(--csq-icon-dark); } :root body.vtt.game.system-eunos-blades #interface .comp.consequence-display-container .consequence-type-container .consequence-type, :root body.vtt.game.system-eunos-blades #controls .comp.consequence-display-container .consequence-type-container .consequence-type, @@ -6732,14 +7204,14 @@ template { :root body.vtt.game.system-eunos-blades #players .comp.consequence-display-container .consequence-type-container .consequence-type { position: absolute; top: 0; - left: 10px; + transform-origin: 0% 50%; white-space: nowrap; font-family: Oswald, sans-serif; text-transform: uppercase; text-align: right; font-size: 10px; color: var(--csq-type-color); - font-weight: bold; + font-weight: normal; } :root body.vtt.game.system-eunos-blades #interface .comp.consequence-display-container .consequence-name-container, :root body.vtt.game.system-eunos-blades #controls .comp.consequence-display-container .consequence-name-container, @@ -6763,9 +7235,10 @@ template { z-index: 1; padding: 0 5px 0 35px; font-size: 14px; - line-height: calc(var(--container-height) * 0.55); + line-height: 17px; font-family: Kirsty, serif; font-variant: small-caps; + transform-origin: 0% 50%; color: var(--csq-icon-bright); font-style: italic; } @@ -6783,7 +7256,7 @@ template { :root body.vtt.game.system-eunos-blades #players .comp.consequence-display-container .consequence-footer-container { position: absolute; height: calc(var(--container-height) * var(--csq-button-size-mult)); - width: 120px; + width: auto; bottom: 0; top: unset; left: calc(var(--container-height) + var(--container-left-shift) - 20px); @@ -6801,12 +7274,29 @@ template { background: var(--csq-icon-bright); display: block; transform: skewX(45deg); + transform-origin: 0% 50%; +} +:root body.vtt.game.system-eunos-blades #interface .comp.consequence-display-container .consequence-footer-container .consequence-footer-bg.resist-consequence, +:root body.vtt.game.system-eunos-blades #controls .comp.consequence-display-container .consequence-footer-container .consequence-footer-bg.resist-consequence, +:root body.vtt.game.system-eunos-blades #navigation .comp.consequence-display-container .consequence-footer-container .consequence-footer-bg.resist-consequence, +:root body.vtt.game.system-eunos-blades #hotbar .comp.consequence-display-container .consequence-footer-container .consequence-footer-bg.resist-consequence, +:root body.vtt.game.system-eunos-blades #players .comp.consequence-display-container .consequence-footer-container .consequence-footer-bg.resist-consequence { + width: 120px; } -:root body.vtt.game.system-eunos-blades #interface .comp.consequence-display-container .consequence-footer-container .consequence-resist-attribute, -:root body.vtt.game.system-eunos-blades #controls .comp.consequence-display-container .consequence-footer-container .consequence-resist-attribute, -:root body.vtt.game.system-eunos-blades #navigation .comp.consequence-display-container .consequence-footer-container .consequence-resist-attribute, -:root body.vtt.game.system-eunos-blades #hotbar .comp.consequence-display-container .consequence-footer-container .consequence-resist-attribute, -:root body.vtt.game.system-eunos-blades #players .comp.consequence-display-container .consequence-footer-container .consequence-resist-attribute { +:root body.vtt.game.system-eunos-blades #interface .comp.consequence-display-container .consequence-footer-container .consequence-footer-bg.special-armor-consequence, +:root body.vtt.game.system-eunos-blades #controls .comp.consequence-display-container .consequence-footer-container .consequence-footer-bg.special-armor-consequence, +:root body.vtt.game.system-eunos-blades #navigation .comp.consequence-display-container .consequence-footer-container .consequence-footer-bg.special-armor-consequence, +:root body.vtt.game.system-eunos-blades #hotbar .comp.consequence-display-container .consequence-footer-container .consequence-footer-bg.special-armor-consequence, +:root body.vtt.game.system-eunos-blades #players .comp.consequence-display-container .consequence-footer-container .consequence-footer-bg.special-armor-consequence { + width: 250px; +} +:root body.vtt.game.system-eunos-blades #interface .comp.consequence-display-container .consequence-footer-container .consequence-footer-message, +:root body.vtt.game.system-eunos-blades #controls .comp.consequence-display-container .consequence-footer-container .consequence-footer-message, +:root body.vtt.game.system-eunos-blades #navigation .comp.consequence-display-container .consequence-footer-container .consequence-footer-message, +:root body.vtt.game.system-eunos-blades #hotbar .comp.consequence-display-container .consequence-footer-container .consequence-footer-message, +:root body.vtt.game.system-eunos-blades #players .comp.consequence-display-container .consequence-footer-container .consequence-footer-message { + position: absolute; + white-space: nowrap; font-family: Oswald, sans-serif; font-weight: bold; color: var(--blades-black-dark); @@ -6814,6 +7304,7 @@ template { line-height: 14px; padding-left: 25px; justify-content: flex-start; + transform-origin: 0% 50%; gap: 5px; } :root body.vtt.game.system-eunos-blades #interface .comp.consequence-display-container .consequence-footer-container .dotline, @@ -9229,13 +9720,9 @@ template { :root body.vtt.game.system-eunos-blades #hotbar #chat .comp.consequence-display-container, :root body.vtt.game.system-eunos-blades #players #chat .comp.consequence-display-container { --container-height: 40px; - --container-left-shift: 50px; - --csq-icon-dark: var(--blades-red-dark); - --csq-icon-med: var(--blades-red); - --csq-icon-bright: var(--blades-red-bright); - --csq-type-bg: var(--csq-icon-dark); - --csq-type-color: var(--blades-black-dark); + --container-left-shift: 70px; --csq-icon-bg-color: var(--blades-black-dark); + --csq-type-bg: var(--csq-icon-dark); --csq-button-size-mult: 0.33; position: relative; display: block; @@ -9243,32 +9730,153 @@ template { max-height: var(--container-height); min-height: var(--container-height); } +@keyframes anim-glow { + 0% { + box-shadow: 0 0 0px var(--blades-red-bright); + background-color: var(--blades-red-darkest); + } + 10% { + background-color: var(--blades-red-bright); + } + 100% { + box-shadow: 0 0 10px 10px transparent; + background-color: var(--blades-red-darkest); + } +} +@keyframes icon-glow { + 0% { + filter: brightness(1); + } + 10% { + filter: brightness(1); + } + 100% { + filter: brightness(1); + } +} +@keyframes icon-red-pulse { + 0% { + scale: 1; + fill: var(--blades-grey); + } + 10% { + scale: 1.1; + fill: var(--blades-red-bright); + } + 100% { + scale: 1; + fill: var(--blades-grey); + } +} +:root body.vtt.game.system-eunos-blades #interface #chat .comp.consequence-display-container .base-consequence, +:root body.vtt.game.system-eunos-blades #controls #chat .comp.consequence-display-container .base-consequence, +:root body.vtt.game.system-eunos-blades #navigation #chat .comp.consequence-display-container .base-consequence, +:root body.vtt.game.system-eunos-blades #hotbar #chat .comp.consequence-display-container .base-consequence, +:root body.vtt.game.system-eunos-blades #players #chat .comp.consequence-display-container .base-consequence { + --csq-icon-dark: var(--blades-black); + --csq-icon-med: var(--blades-grey); + --csq-icon-bright: var(--blades-white); + --csq-type-color: var(--blades-grey); + --csq-name-color: var(--blades-white); +} +:root body.vtt.game.system-eunos-blades #interface #chat .comp.consequence-display-container .accept-consequence, +:root body.vtt.game.system-eunos-blades #controls #chat .comp.consequence-display-container .accept-consequence, +:root body.vtt.game.system-eunos-blades #navigation #chat .comp.consequence-display-container .accept-consequence, +:root body.vtt.game.system-eunos-blades #hotbar #chat .comp.consequence-display-container .accept-consequence, +:root body.vtt.game.system-eunos-blades #players #chat .comp.consequence-display-container .accept-consequence { + opacity: 0; + --csq-icon-dark: var(--blades-red-dark); + --csq-icon-med: var(--blades-red); + --csq-icon-bright: var(--blades-red-bright); + --csq-type-color: var(--blades-black-dark); + --csq-name-color: var(--blades-red); +} :root body.vtt.game.system-eunos-blades #interface #chat .comp.consequence-display-container .resist-consequence, :root body.vtt.game.system-eunos-blades #controls #chat .comp.consequence-display-container .resist-consequence, :root body.vtt.game.system-eunos-blades #navigation #chat .comp.consequence-display-container .resist-consequence, :root body.vtt.game.system-eunos-blades #hotbar #chat .comp.consequence-display-container .resist-consequence, :root body.vtt.game.system-eunos-blades #players #chat .comp.consequence-display-container .resist-consequence { + opacity: 0; --csq-icon-dark: var(--blades-gold-dark); --csq-icon-med: var(--blades-gold); --csq-icon-bright: var(--blades-gold-bright); --csq-type-color: var(--blades-gold-dark); --csq-name-color: var(--blades-gold-bright); } +:root body.vtt.game.system-eunos-blades #interface #chat .comp.consequence-display-container .special-armor-consequence, +:root body.vtt.game.system-eunos-blades #controls #chat .comp.consequence-display-container .special-armor-consequence, +:root body.vtt.game.system-eunos-blades #navigation #chat .comp.consequence-display-container .special-armor-consequence, +:root body.vtt.game.system-eunos-blades #hotbar #chat .comp.consequence-display-container .special-armor-consequence, +:root body.vtt.game.system-eunos-blades #players #chat .comp.consequence-display-container .special-armor-consequence { + opacity: 0; + --csq-icon-dark: var(--blades-blue-dark); + --csq-icon-med: var(--blades-blue); + --csq-icon-bright: var(--blades-blue-bright); + --csq-type-color: var(--blades-blue-dark); + --csq-name-color: var(--blades-blue-bright); +} +:root body.vtt.game.system-eunos-blades #interface #chat .comp.consequence-display-container .consequence-interaction-pad, +:root body.vtt.game.system-eunos-blades #controls #chat .comp.consequence-display-container .consequence-interaction-pad, +:root body.vtt.game.system-eunos-blades #navigation #chat .comp.consequence-display-container .consequence-interaction-pad, +:root body.vtt.game.system-eunos-blades #hotbar #chat .comp.consequence-display-container .consequence-interaction-pad, +:root body.vtt.game.system-eunos-blades #players #chat .comp.consequence-display-container .consequence-interaction-pad { + display: none; + display: block; + position: absolute; + z-index: 2; + pointer-events: auto; + height: 100%; + top: 0; +} +:root body.vtt.game.system-eunos-blades #interface #chat .comp.consequence-display-container .consequence-interaction-pad.accept-consequence-pad, +:root body.vtt.game.system-eunos-blades #controls #chat .comp.consequence-display-container .consequence-interaction-pad.accept-consequence-pad, +:root body.vtt.game.system-eunos-blades #navigation #chat .comp.consequence-display-container .consequence-interaction-pad.accept-consequence-pad, +:root body.vtt.game.system-eunos-blades #hotbar #chat .comp.consequence-display-container .consequence-interaction-pad.accept-consequence-pad, +:root body.vtt.game.system-eunos-blades #players #chat .comp.consequence-display-container .consequence-interaction-pad.accept-consequence-pad { + --pad-left-shift: calc(var(--container-left-shift) + (var(--container-height))); + left: var(--pad-left-shift); + width: calc(100% - var(--pad-left-shift)); +} +:root body.vtt.game.system-eunos-blades #interface #chat .comp.consequence-display-container .consequence-interaction-pad.resist-consequence-pad, :root body.vtt.game.system-eunos-blades #interface #chat .comp.consequence-display-container .consequence-interaction-pad.special-armor-consequence-pad, +:root body.vtt.game.system-eunos-blades #controls #chat .comp.consequence-display-container .consequence-interaction-pad.resist-consequence-pad, +:root body.vtt.game.system-eunos-blades #controls #chat .comp.consequence-display-container .consequence-interaction-pad.special-armor-consequence-pad, +:root body.vtt.game.system-eunos-blades #navigation #chat .comp.consequence-display-container .consequence-interaction-pad.resist-consequence-pad, +:root body.vtt.game.system-eunos-blades #navigation #chat .comp.consequence-display-container .consequence-interaction-pad.special-armor-consequence-pad, +:root body.vtt.game.system-eunos-blades #hotbar #chat .comp.consequence-display-container .consequence-interaction-pad.resist-consequence-pad, +:root body.vtt.game.system-eunos-blades #hotbar #chat .comp.consequence-display-container .consequence-interaction-pad.special-armor-consequence-pad, +:root body.vtt.game.system-eunos-blades #players #chat .comp.consequence-display-container .consequence-interaction-pad.resist-consequence-pad, +:root body.vtt.game.system-eunos-blades #players #chat .comp.consequence-display-container .consequence-interaction-pad.special-armor-consequence-pad { + left: 0; + width: calc(var(--container-left-shift) - var(--container-height) * 0); +} +:root body.vtt.game.system-eunos-blades #interface #chat .comp.consequence-display-container .consequence-interaction-pad.special-armor-consequence-pad, +:root body.vtt.game.system-eunos-blades #controls #chat .comp.consequence-display-container .consequence-interaction-pad.special-armor-consequence-pad, +:root body.vtt.game.system-eunos-blades #navigation #chat .comp.consequence-display-container .consequence-interaction-pad.special-armor-consequence-pad, +:root body.vtt.game.system-eunos-blades #hotbar #chat .comp.consequence-display-container .consequence-interaction-pad.special-armor-consequence-pad, +:root body.vtt.game.system-eunos-blades #players #chat .comp.consequence-display-container .consequence-interaction-pad.special-armor-consequence-pad { + height: 40%; + z-index: 3; +} :root body.vtt.game.system-eunos-blades #interface #chat .comp.consequence-display-container .consequence-icon-container, :root body.vtt.game.system-eunos-blades #controls #chat .comp.consequence-display-container .consequence-icon-container, :root body.vtt.game.system-eunos-blades #navigation #chat .comp.consequence-display-container .consequence-icon-container, :root body.vtt.game.system-eunos-blades #hotbar #chat .comp.consequence-display-container .consequence-icon-container, :root body.vtt.game.system-eunos-blades #players #chat .comp.consequence-display-container .consequence-icon-container { position: relative; - height: var(--container-height); - width: var(--container-height); - min-width: var(--container-height); - max-width: var(--container-height); - min-height: var(--container-height); - max-height: var(--container-height); + height: calc(var(--container-height) * 0.75); + max-width: calc(var(--container-height) * 0.75); background: transparent; left: var(--container-left-shift); z-index: 1; + pointer-events: auto; + transition: 0.2s; +} +:root body.vtt.game.system-eunos-blades #interface #chat .comp.consequence-display-container .consequence-icon-container:hover, +:root body.vtt.game.system-eunos-blades #controls #chat .comp.consequence-display-container .consequence-icon-container:hover, +:root body.vtt.game.system-eunos-blades #navigation #chat .comp.consequence-display-container .consequence-icon-container:hover, +:root body.vtt.game.system-eunos-blades #hotbar #chat .comp.consequence-display-container .consequence-icon-container:hover, +:root body.vtt.game.system-eunos-blades #players #chat .comp.consequence-display-container .consequence-icon-container:hover { + filter: brightness(2); } :root body.vtt.game.system-eunos-blades #interface #chat .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle, :root body.vtt.game.system-eunos-blades #controls #chat .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle, @@ -9277,9 +9885,9 @@ template { :root body.vtt.game.system-eunos-blades #players #chat .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle { position: absolute; translate: -50% -50%; - transform-origin: 50% 50%; - top: 50%; - left: 50%; + transform-origin: 100% 0%; + top: calc(var(--container-height) * 0.5); + left: calc(var(--container-height) * 0.5); border-radius: 50%; height: var(--container-height); width: var(--container-height); @@ -9334,6 +9942,48 @@ template { :root body.vtt.game.system-eunos-blades #players #chat .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle svg .fill-linear { fill: var(--csq-icon-med); } +:root body.vtt.game.system-eunos-blades #interface #chat .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle svg path, +:root body.vtt.game.system-eunos-blades #controls #chat .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle svg path, +:root body.vtt.game.system-eunos-blades #navigation #chat .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle svg path, +:root body.vtt.game.system-eunos-blades #hotbar #chat .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle svg path, +:root body.vtt.game.system-eunos-blades #players #chat .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle svg path { + transform-origin: 50% 50%; +} +:root body.vtt.game.system-eunos-blades #interface #chat .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence, +:root body.vtt.game.system-eunos-blades #controls #chat .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence, +:root body.vtt.game.system-eunos-blades #navigation #chat .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence, +:root body.vtt.game.system-eunos-blades #hotbar #chat .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence, +:root body.vtt.game.system-eunos-blades #players #chat .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence { + scale: 0.75; + animation: icon-glow 2s ease infinite; + pointer-events: auto; +} +:root body.vtt.game.system-eunos-blades #interface #chat .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence svg path, +:root body.vtt.game.system-eunos-blades #controls #chat .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence svg path, +:root body.vtt.game.system-eunos-blades #navigation #chat .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence svg path, +:root body.vtt.game.system-eunos-blades #hotbar #chat .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence svg path, +:root body.vtt.game.system-eunos-blades #players #chat .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence svg path { + animation: icon-red-pulse 2s ease infinite; +} +:root body.vtt.game.system-eunos-blades #interface #chat .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence:hover, +:root body.vtt.game.system-eunos-blades #controls #chat .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence:hover, +:root body.vtt.game.system-eunos-blades #navigation #chat .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence:hover, +:root body.vtt.game.system-eunos-blades #hotbar #chat .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence:hover, +:root body.vtt.game.system-eunos-blades #players #chat .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence:hover { + filter: brightness(2) !important; + animation: none; +} +:root body.vtt.game.system-eunos-blades #interface #chat .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.resist-consequence, :root body.vtt.game.system-eunos-blades #interface #chat .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.special-armor-consequence, +:root body.vtt.game.system-eunos-blades #controls #chat .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.resist-consequence, +:root body.vtt.game.system-eunos-blades #controls #chat .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.special-armor-consequence, +:root body.vtt.game.system-eunos-blades #navigation #chat .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.resist-consequence, +:root body.vtt.game.system-eunos-blades #navigation #chat .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.special-armor-consequence, +:root body.vtt.game.system-eunos-blades #hotbar #chat .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.resist-consequence, +:root body.vtt.game.system-eunos-blades #hotbar #chat .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.special-armor-consequence, +:root body.vtt.game.system-eunos-blades #players #chat .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.resist-consequence, +:root body.vtt.game.system-eunos-blades #players #chat .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.special-armor-consequence { + outline-width: 2px; +} :root body.vtt.game.system-eunos-blades #interface #chat .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle .consequence-icon, :root body.vtt.game.system-eunos-blades #controls #chat .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle .consequence-icon, :root body.vtt.game.system-eunos-blades #navigation #chat .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle .consequence-icon, @@ -9351,7 +10001,7 @@ template { display: flex; flex-direction: row; flex-wrap: nowrap; - bottom: 0px; + bottom: -10px; } :root body.vtt.game.system-eunos-blades #interface #chat .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg, :root body.vtt.game.system-eunos-blades #controls #chat .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg, @@ -9362,10 +10012,22 @@ template { z-index: -1; height: 100%; width: calc(100% + 24px); + transform-origin: 0% 50%; top: 0px; background: var(--csq-icon-bright); display: block; } +:root body.vtt.game.system-eunos-blades #interface #chat .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg.consequence-resist-button-bg, :root body.vtt.game.system-eunos-blades #interface #chat .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg.consequence-special-armor-button-bg, +:root body.vtt.game.system-eunos-blades #controls #chat .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg.consequence-resist-button-bg, +:root body.vtt.game.system-eunos-blades #controls #chat .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg.consequence-special-armor-button-bg, +:root body.vtt.game.system-eunos-blades #navigation #chat .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg.consequence-resist-button-bg, +:root body.vtt.game.system-eunos-blades #navigation #chat .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg.consequence-special-armor-button-bg, +:root body.vtt.game.system-eunos-blades #hotbar #chat .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg.consequence-resist-button-bg, +:root body.vtt.game.system-eunos-blades #hotbar #chat .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg.consequence-special-armor-button-bg, +:root body.vtt.game.system-eunos-blades #players #chat .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg.consequence-resist-button-bg, +:root body.vtt.game.system-eunos-blades #players #chat .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg.consequence-special-armor-button-bg { + transform-origin: 100% 50%; +} :root body.vtt.game.system-eunos-blades #interface #chat .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-label, :root body.vtt.game.system-eunos-blades #controls #chat .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-label, :root body.vtt.game.system-eunos-blades #navigation #chat .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-label, @@ -9377,6 +10039,9 @@ template { font-size: 10px; line-height: 14px; color: var(--blades-grey); + font-weight: 800; + text-shadow: 0px 0px 1px var(--blades-black-dark); + letter-spacing: 1; text-transform: uppercase; white-space: nowrap; } @@ -9405,7 +10070,7 @@ template { :root body.vtt.game.system-eunos-blades #navigation #chat .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-resist-button-container, :root body.vtt.game.system-eunos-blades #hotbar #chat .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-resist-button-container, :root body.vtt.game.system-eunos-blades #players #chat .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-resist-button-container { - right: calc(100% - 3px); + right: 100%; } :root body.vtt.game.system-eunos-blades #interface #chat .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-resist-button-container .consequence-button-bg, :root body.vtt.game.system-eunos-blades #controls #chat .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-resist-button-container .consequence-button-bg, @@ -9420,7 +10085,7 @@ template { :root body.vtt.game.system-eunos-blades #navigation #chat .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-accept-button-container, :root body.vtt.game.system-eunos-blades #hotbar #chat .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-accept-button-container, :root body.vtt.game.system-eunos-blades #players #chat .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-accept-button-container { - left: 100%; + left: 140%; } :root body.vtt.game.system-eunos-blades #interface #chat .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-accept-button-container .consequence-button-bg, :root body.vtt.game.system-eunos-blades #controls #chat .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-accept-button-container .consequence-button-bg, @@ -9447,7 +10112,7 @@ template { height: calc(var(--container-height) * 0.33); transform-origin: 0% 50%; left: calc(var(--container-height) + var(--container-left-shift) - 10px); - top: 0px; + top: -2px; padding: 0 5px 0 15px; } :root body.vtt.game.system-eunos-blades #interface #chat .comp.consequence-display-container .consequence-type-container .consequence-type-bg, @@ -9460,8 +10125,9 @@ template { left: -20px; height: 100%; width: 170px; + transform-origin: 0% 50%; transform: skewX(-45deg); - background: var(--csq-type-bg); + background: var(--csq-icon-dark); } :root body.vtt.game.system-eunos-blades #interface #chat .comp.consequence-display-container .consequence-type-container .consequence-type, :root body.vtt.game.system-eunos-blades #controls #chat .comp.consequence-display-container .consequence-type-container .consequence-type, @@ -9470,14 +10136,14 @@ template { :root body.vtt.game.system-eunos-blades #players #chat .comp.consequence-display-container .consequence-type-container .consequence-type { position: absolute; top: 0; - left: 10px; + transform-origin: 0% 50%; white-space: nowrap; font-family: Oswald, sans-serif; text-transform: uppercase; text-align: right; font-size: 10px; color: var(--csq-type-color); - font-weight: bold; + font-weight: normal; } :root body.vtt.game.system-eunos-blades #interface #chat .comp.consequence-display-container .consequence-name-container, :root body.vtt.game.system-eunos-blades #controls #chat .comp.consequence-display-container .consequence-name-container, @@ -9501,9 +10167,10 @@ template { z-index: 1; padding: 0 5px 0 35px; font-size: 14px; - line-height: calc(var(--container-height) * 0.55); + line-height: 17px; font-family: Kirsty, serif; font-variant: small-caps; + transform-origin: 0% 50%; color: var(--csq-icon-bright); font-style: italic; } @@ -9521,7 +10188,7 @@ template { :root body.vtt.game.system-eunos-blades #players #chat .comp.consequence-display-container .consequence-footer-container { position: absolute; height: calc(var(--container-height) * var(--csq-button-size-mult)); - width: 120px; + width: auto; bottom: 0; top: unset; left: calc(var(--container-height) + var(--container-left-shift) - 20px); @@ -9539,12 +10206,29 @@ template { background: var(--csq-icon-bright); display: block; transform: skewX(45deg); + transform-origin: 0% 50%; +} +:root body.vtt.game.system-eunos-blades #interface #chat .comp.consequence-display-container .consequence-footer-container .consequence-footer-bg.resist-consequence, +:root body.vtt.game.system-eunos-blades #controls #chat .comp.consequence-display-container .consequence-footer-container .consequence-footer-bg.resist-consequence, +:root body.vtt.game.system-eunos-blades #navigation #chat .comp.consequence-display-container .consequence-footer-container .consequence-footer-bg.resist-consequence, +:root body.vtt.game.system-eunos-blades #hotbar #chat .comp.consequence-display-container .consequence-footer-container .consequence-footer-bg.resist-consequence, +:root body.vtt.game.system-eunos-blades #players #chat .comp.consequence-display-container .consequence-footer-container .consequence-footer-bg.resist-consequence { + width: 120px; } -:root body.vtt.game.system-eunos-blades #interface #chat .comp.consequence-display-container .consequence-footer-container .consequence-resist-attribute, -:root body.vtt.game.system-eunos-blades #controls #chat .comp.consequence-display-container .consequence-footer-container .consequence-resist-attribute, -:root body.vtt.game.system-eunos-blades #navigation #chat .comp.consequence-display-container .consequence-footer-container .consequence-resist-attribute, -:root body.vtt.game.system-eunos-blades #hotbar #chat .comp.consequence-display-container .consequence-footer-container .consequence-resist-attribute, -:root body.vtt.game.system-eunos-blades #players #chat .comp.consequence-display-container .consequence-footer-container .consequence-resist-attribute { +:root body.vtt.game.system-eunos-blades #interface #chat .comp.consequence-display-container .consequence-footer-container .consequence-footer-bg.special-armor-consequence, +:root body.vtt.game.system-eunos-blades #controls #chat .comp.consequence-display-container .consequence-footer-container .consequence-footer-bg.special-armor-consequence, +:root body.vtt.game.system-eunos-blades #navigation #chat .comp.consequence-display-container .consequence-footer-container .consequence-footer-bg.special-armor-consequence, +:root body.vtt.game.system-eunos-blades #hotbar #chat .comp.consequence-display-container .consequence-footer-container .consequence-footer-bg.special-armor-consequence, +:root body.vtt.game.system-eunos-blades #players #chat .comp.consequence-display-container .consequence-footer-container .consequence-footer-bg.special-armor-consequence { + width: 250px; +} +:root body.vtt.game.system-eunos-blades #interface #chat .comp.consequence-display-container .consequence-footer-container .consequence-footer-message, +:root body.vtt.game.system-eunos-blades #controls #chat .comp.consequence-display-container .consequence-footer-container .consequence-footer-message, +:root body.vtt.game.system-eunos-blades #navigation #chat .comp.consequence-display-container .consequence-footer-container .consequence-footer-message, +:root body.vtt.game.system-eunos-blades #hotbar #chat .comp.consequence-display-container .consequence-footer-container .consequence-footer-message, +:root body.vtt.game.system-eunos-blades #players #chat .comp.consequence-display-container .consequence-footer-container .consequence-footer-message { + position: absolute; + white-space: nowrap; font-family: Oswald, sans-serif; font-weight: bold; color: var(--blades-black-dark); @@ -9552,6 +10236,7 @@ template { line-height: 14px; padding-left: 25px; justify-content: flex-start; + transform-origin: 0% 50%; gap: 5px; } :root body.vtt.game.system-eunos-blades #interface #chat .comp.consequence-display-container .consequence-footer-container .dotline, @@ -10157,143 +10842,1709 @@ template { bottom: 100%; } } -:root body.vtt.game.system-eunos-blades #interface #chat, -:root body.vtt.game.system-eunos-blades #interface #chat *, -:root body.vtt.game.system-eunos-blades #controls #chat, -:root body.vtt.game.system-eunos-blades #controls #chat *, -:root body.vtt.game.system-eunos-blades #navigation #chat, -:root body.vtt.game.system-eunos-blades #navigation #chat *, -:root body.vtt.game.system-eunos-blades #hotbar #chat, -:root body.vtt.game.system-eunos-blades #hotbar #chat *, -:root body.vtt.game.system-eunos-blades #players #chat, -:root body.vtt.game.system-eunos-blades #players #chat * { - --font-primary: "Minion Pro"; - --font-heading: "Kirsty"; - --font-weight-heading: normal; - --text-shadow-heading: none; - --line-height-heading: 1.2; +:root body.vtt.game.system-eunos-blades #interface #chat blockquote, +:root body.vtt.game.system-eunos-blades #controls #chat blockquote, +:root body.vtt.game.system-eunos-blades #navigation #chat blockquote, +:root body.vtt.game.system-eunos-blades #hotbar #chat blockquote, +:root body.vtt.game.system-eunos-blades #players #chat blockquote { + border-left: 2px solid var(--blades-grey-bright); + margin-left: 1.5rem; + padding-left: 1rem; } -:root body.vtt.game.system-eunos-blades #interface #chat .chat-message, -:root body.vtt.game.system-eunos-blades #controls #chat .chat-message, -:root body.vtt.game.system-eunos-blades #navigation #chat .chat-message, -:root body.vtt.game.system-eunos-blades #hotbar #chat .chat-message, -:root body.vtt.game.system-eunos-blades #players #chat .chat-message { - background: var(--blades-black); +:root body.vtt.game.system-eunos-blades #interface #chat table, +:root body.vtt.game.system-eunos-blades #interface #chat table tr, +:root body.vtt.game.system-eunos-blades #interface #chat table tr th, +:root body.vtt.game.system-eunos-blades #interface #chat table tr td, +:root body.vtt.game.system-eunos-blades #interface #chat table tbody, +:root body.vtt.game.system-eunos-blades #interface #chat table tbody tr, +:root body.vtt.game.system-eunos-blades #interface #chat table tbody td, +:root body.vtt.game.system-eunos-blades #interface #chat table thead, +:root body.vtt.game.system-eunos-blades #interface #chat table thead tr, +:root body.vtt.game.system-eunos-blades #interface #chat table thead tr th, +:root body.vtt.game.system-eunos-blades #interface #chat table thead tr td, +:root body.vtt.game.system-eunos-blades #controls #chat table, +:root body.vtt.game.system-eunos-blades #controls #chat table tr, +:root body.vtt.game.system-eunos-blades #controls #chat table tr th, +:root body.vtt.game.system-eunos-blades #controls #chat table tr td, +:root body.vtt.game.system-eunos-blades #controls #chat table tbody, +:root body.vtt.game.system-eunos-blades #controls #chat table tbody tr, +:root body.vtt.game.system-eunos-blades #controls #chat table tbody td, +:root body.vtt.game.system-eunos-blades #controls #chat table thead, +:root body.vtt.game.system-eunos-blades #controls #chat table thead tr, +:root body.vtt.game.system-eunos-blades #controls #chat table thead tr th, +:root body.vtt.game.system-eunos-blades #controls #chat table thead tr td, +:root body.vtt.game.system-eunos-blades #navigation #chat table, +:root body.vtt.game.system-eunos-blades #navigation #chat table tr, +:root body.vtt.game.system-eunos-blades #navigation #chat table tr th, +:root body.vtt.game.system-eunos-blades #navigation #chat table tr td, +:root body.vtt.game.system-eunos-blades #navigation #chat table tbody, +:root body.vtt.game.system-eunos-blades #navigation #chat table tbody tr, +:root body.vtt.game.system-eunos-blades #navigation #chat table tbody td, +:root body.vtt.game.system-eunos-blades #navigation #chat table thead, +:root body.vtt.game.system-eunos-blades #navigation #chat table thead tr, +:root body.vtt.game.system-eunos-blades #navigation #chat table thead tr th, +:root body.vtt.game.system-eunos-blades #navigation #chat table thead tr td, +:root body.vtt.game.system-eunos-blades #hotbar #chat table, +:root body.vtt.game.system-eunos-blades #hotbar #chat table tr, +:root body.vtt.game.system-eunos-blades #hotbar #chat table tr th, +:root body.vtt.game.system-eunos-blades #hotbar #chat table tr td, +:root body.vtt.game.system-eunos-blades #hotbar #chat table tbody, +:root body.vtt.game.system-eunos-blades #hotbar #chat table tbody tr, +:root body.vtt.game.system-eunos-blades #hotbar #chat table tbody td, +:root body.vtt.game.system-eunos-blades #hotbar #chat table thead, +:root body.vtt.game.system-eunos-blades #hotbar #chat table thead tr, +:root body.vtt.game.system-eunos-blades #hotbar #chat table thead tr th, +:root body.vtt.game.system-eunos-blades #hotbar #chat table thead tr td, +:root body.vtt.game.system-eunos-blades #players #chat table, +:root body.vtt.game.system-eunos-blades #players #chat table tr, +:root body.vtt.game.system-eunos-blades #players #chat table tr th, +:root body.vtt.game.system-eunos-blades #players #chat table tr td, +:root body.vtt.game.system-eunos-blades #players #chat table tbody, +:root body.vtt.game.system-eunos-blades #players #chat table tbody tr, +:root body.vtt.game.system-eunos-blades #players #chat table tbody td, +:root body.vtt.game.system-eunos-blades #players #chat table thead, +:root body.vtt.game.system-eunos-blades #players #chat table thead tr, +:root body.vtt.game.system-eunos-blades #players #chat table thead tr th, +:root body.vtt.game.system-eunos-blades #players #chat table thead tr td { + margin: 0; + padding: 0; + background: none; + border: none; } -:root body.vtt.game.system-eunos-blades #interface #chat .chat-message .message-content .dice-roll-strip, -:root body.vtt.game.system-eunos-blades #controls #chat .chat-message .message-content .dice-roll-strip, -:root body.vtt.game.system-eunos-blades #navigation #chat .chat-message .message-content .dice-roll-strip, -:root body.vtt.game.system-eunos-blades #hotbar #chat .chat-message .message-content .dice-roll-strip, -:root body.vtt.game.system-eunos-blades #players #chat .chat-message .message-content .dice-roll-strip { - display: flex; - flex-direction: row; - flex-wrap: nowrap; - height: 30px; - width: 100%; - align-items: stretch; - justify-content: space-evenly; +:root body.vtt.game.system-eunos-blades #interface #chat table, +:root body.vtt.game.system-eunos-blades #controls #chat table, +:root body.vtt.game.system-eunos-blades #navigation #chat table, +:root body.vtt.game.system-eunos-blades #hotbar #chat table, +:root body.vtt.game.system-eunos-blades #players #chat table { + border-collapse: collapse; } -:root body.vtt.game.system-eunos-blades #interface #chat .chat-message .message-content .dice-roll-strip .blades-die, -:root body.vtt.game.system-eunos-blades #controls #chat .chat-message .message-content .dice-roll-strip .blades-die, -:root body.vtt.game.system-eunos-blades #navigation #chat .chat-message .message-content .dice-roll-strip .blades-die, -:root body.vtt.game.system-eunos-blades #hotbar #chat .chat-message .message-content .dice-roll-strip .blades-die, -:root body.vtt.game.system-eunos-blades #players #chat .chat-message .message-content .dice-roll-strip .blades-die { - display: block; - height: 30px; +:root body.vtt.game.system-eunos-blades #interface #chat table thead tr th, +:root body.vtt.game.system-eunos-blades #controls #chat table thead tr th, +:root body.vtt.game.system-eunos-blades #navigation #chat table thead tr th, +:root body.vtt.game.system-eunos-blades #hotbar #chat table thead tr th, +:root body.vtt.game.system-eunos-blades #players #chat table thead tr th { + background-color: var(--blades-white); + text-shadow: none; + color: var(--blades-black); + font-family: var(--font-emphasis); + font-weight: normal; + font-size: 20px; + font-variant: small-caps; + padding: 2px; } -:root body.vtt.game.system-eunos-blades #interface #chat .chat-message .message-content .dice-roll-strip .blades-die img, -:root body.vtt.game.system-eunos-blades #controls #chat .chat-message .message-content .dice-roll-strip .blades-die img, -:root body.vtt.game.system-eunos-blades #navigation #chat .chat-message .message-content .dice-roll-strip .blades-die img, -:root body.vtt.game.system-eunos-blades #hotbar #chat .chat-message .message-content .dice-roll-strip .blades-die img, -:root body.vtt.game.system-eunos-blades #players #chat .chat-message .message-content .dice-roll-strip .blades-die img { - height: 30px; - width: 30px; +:root body.vtt.game.system-eunos-blades #interface #chat figure, +:root body.vtt.game.system-eunos-blades #controls #chat figure, +:root body.vtt.game.system-eunos-blades #navigation #chat figure, +:root body.vtt.game.system-eunos-blades #hotbar #chat figure, +:root body.vtt.game.system-eunos-blades #players #chat figure { + display: table; + margin: 1rem auto; +} +:root body.vtt.game.system-eunos-blades #interface #chat figure figcaption, +:root body.vtt.game.system-eunos-blades #controls #chat figure figcaption, +:root body.vtt.game.system-eunos-blades #navigation #chat figure figcaption, +:root body.vtt.game.system-eunos-blades #hotbar #chat figure figcaption, +:root body.vtt.game.system-eunos-blades #players #chat figure figcaption { + font-family: var(--font-primary-small); + color: var(--blades-grey-bright); display: block; + margin-top: 0.25rem; + text-align: center; } -:root body.vtt.game.system-eunos-blades #interface #chat .chat-message .message-content .dice-roll-strip .blades-die.blades-die-critical, -:root body.vtt.game.system-eunos-blades #controls #chat .chat-message .message-content .dice-roll-strip .blades-die.blades-die-critical, -:root body.vtt.game.system-eunos-blades #navigation #chat .chat-message .message-content .dice-roll-strip .blades-die.blades-die-critical, -:root body.vtt.game.system-eunos-blades #hotbar #chat .chat-message .message-content .dice-roll-strip .blades-die.blades-die-critical, -:root body.vtt.game.system-eunos-blades #players #chat .chat-message .message-content .dice-roll-strip .blades-die.blades-die-critical { - outline: 2px solid var(--blades-gold-bright); +:root body.vtt.game.system-eunos-blades #interface #chat hr, +:root body.vtt.game.system-eunos-blades #controls #chat hr, +:root body.vtt.game.system-eunos-blades #navigation #chat hr, +:root body.vtt.game.system-eunos-blades #hotbar #chat hr, +:root body.vtt.game.system-eunos-blades #players #chat hr { + border-color: var(--blades-grey-bright); + border-style: solid; + border-width: 1px 0 0 0; } -:root body.vtt.game.system-eunos-blades #interface #chat .chat-message .message-content .dice-roll-strip .blades-die.blades-die-success, -:root body.vtt.game.system-eunos-blades #controls #chat .chat-message .message-content .dice-roll-strip .blades-die.blades-die-success, -:root body.vtt.game.system-eunos-blades #navigation #chat .chat-message .message-content .dice-roll-strip .blades-die.blades-die-success, -:root body.vtt.game.system-eunos-blades #hotbar #chat .chat-message .message-content .dice-roll-strip .blades-die.blades-die-success, -:root body.vtt.game.system-eunos-blades #players #chat .chat-message .message-content .dice-roll-strip .blades-die.blades-die-success { - outline: 2px solid var(--blades-white-bright); +:root body.vtt.game.system-eunos-blades #interface #chat code, +:root body.vtt.game.system-eunos-blades #controls #chat code, +:root body.vtt.game.system-eunos-blades #navigation #chat code, +:root body.vtt.game.system-eunos-blades #hotbar #chat code, +:root body.vtt.game.system-eunos-blades #players #chat code { + padding: 0.1rem 0.2rem; } -:root body.vtt.game.system-eunos-blades #interface #chat .chat-message .message-content .dice-roll-strip .blades-die.blades-die-ghost img, -:root body.vtt.game.system-eunos-blades #controls #chat .chat-message .message-content .dice-roll-strip .blades-die.blades-die-ghost img, -:root body.vtt.game.system-eunos-blades #navigation #chat .chat-message .message-content .dice-roll-strip .blades-die.blades-die-ghost img, -:root body.vtt.game.system-eunos-blades #hotbar #chat .chat-message .message-content .dice-roll-strip .blades-die.blades-die-ghost img, -:root body.vtt.game.system-eunos-blades #players #chat .chat-message .message-content .dice-roll-strip .blades-die.blades-die-ghost img { - opacity: 0.5; +:root body.vtt.game.system-eunos-blades #interface #chat .text-secret, +:root body.vtt.game.system-eunos-blades #controls #chat .text-secret, +:root body.vtt.game.system-eunos-blades #navigation #chat .text-secret, +:root body.vtt.game.system-eunos-blades #hotbar #chat .text-secret, +:root body.vtt.game.system-eunos-blades #players #chat .text-secret { + display: var(--secret-text-display, "none"); + background-color: var(--blades-white); + color: var(--blades-black); + font-weight: bold; + font-style: italic; + border-radius: 3px; + box-shadow: 0 0 3px var(--blades-white-bright), 0 0 3px var(--blades-white-bright), 0 0 3px var(--blades-white-bright); + padding: 0 0.3125rem; + margin: 0 0.3125rem; } -:root body.vtt.game.system-eunos-blades #interface #chat .chat-message .message-content .dice-roll-strip .blades-die.blades-die-resistance, -:root body.vtt.game.system-eunos-blades #controls #chat .chat-message .message-content .dice-roll-strip .blades-die.blades-die-resistance, -:root body.vtt.game.system-eunos-blades #navigation #chat .chat-message .message-content .dice-roll-strip .blades-die.blades-die-resistance, -:root body.vtt.game.system-eunos-blades #hotbar #chat .chat-message .message-content .dice-roll-strip .blades-die.blades-die-resistance, -:root body.vtt.game.system-eunos-blades #players #chat .chat-message .message-content .dice-roll-strip .blades-die.blades-die-resistance { - outline: 2px solid var(--blades-cyan-bright); +:root body.vtt.game.system-eunos-blades #interface #chat .text-secret:first-child:last-child, +:root body.vtt.game.system-eunos-blades #controls #chat .text-secret:first-child:last-child, +:root body.vtt.game.system-eunos-blades #navigation #chat .text-secret:first-child:last-child, +:root body.vtt.game.system-eunos-blades #hotbar #chat .text-secret:first-child:last-child, +:root body.vtt.game.system-eunos-blades #players #chat .text-secret:first-child:last-child { + display: block; } -:root body.vtt.game.system-eunos-blades #interface #chat .chat-message .message-content .chat-label, :root body.vtt.game.system-eunos-blades #interface #chat .chat-message .message-content .chat-trait-label, -:root body.vtt.game.system-eunos-blades #controls #chat .chat-message .message-content .chat-label, -:root body.vtt.game.system-eunos-blades #controls #chat .chat-message .message-content .chat-trait-label, -:root body.vtt.game.system-eunos-blades #navigation #chat .chat-message .message-content .chat-label, -:root body.vtt.game.system-eunos-blades #navigation #chat .chat-message .message-content .chat-trait-label, -:root body.vtt.game.system-eunos-blades #hotbar #chat .chat-message .message-content .chat-label, -:root body.vtt.game.system-eunos-blades #hotbar #chat .chat-message .message-content .chat-trait-label, -:root body.vtt.game.system-eunos-blades #players #chat .chat-message .message-content .chat-label, -:root body.vtt.game.system-eunos-blades #players #chat .chat-message .message-content .chat-trait-label { - background-color: var(--blades-grey-bright); - font-family: var(--font-emphasis); - color: var(--blades-white); - font-size: 21px; - text-align: center; - padding: 0px 5px; - height: 30px; - text-transform: capitalize; +:root body.vtt.game.system-eunos-blades #interface #chat label:not([class]), +:root body.vtt.game.system-eunos-blades #controls #chat label:not([class]), +:root body.vtt.game.system-eunos-blades #navigation #chat label:not([class]), +:root body.vtt.game.system-eunos-blades #hotbar #chat label:not([class]), +:root body.vtt.game.system-eunos-blades #players #chat label:not([class]) { + color: var(--blades-white-bright); + font-weight: bold; } -:root body.vtt.game.system-eunos-blades #interface #chat .chat-message .message-content .chat-label-small, -:root body.vtt.game.system-eunos-blades #controls #chat .chat-message .message-content .chat-label-small, -:root body.vtt.game.system-eunos-blades #navigation #chat .chat-message .message-content .chat-label-small, -:root body.vtt.game.system-eunos-blades #hotbar #chat .chat-message .message-content .chat-label-small, -:root body.vtt.game.system-eunos-blades #players #chat .chat-message .message-content .chat-label-small { - background-color: var(--blades-grey); +:root body.vtt.game.system-eunos-blades #interface #chat .filled-label, +:root body.vtt.game.system-eunos-blades #controls #chat .filled-label, +:root body.vtt.game.system-eunos-blades #navigation #chat .filled-label, +:root body.vtt.game.system-eunos-blades #hotbar #chat .filled-label, +:root body.vtt.game.system-eunos-blades #players #chat .filled-label { + font-size: 1.25rem; + line-height: 1; + flex-grow: 0; + padding: 0.3125rem; + background: var(--blades-white); color: var(--blades-black); - font-size: small; - text-align: center; - padding: 3px 5px; - height: 20px; -} -:root body.vtt.game.system-eunos-blades #interface #chat .chat-message .message-content .label-stripe-chat, -:root body.vtt.game.system-eunos-blades #controls #chat .chat-message .message-content .label-stripe-chat, -:root body.vtt.game.system-eunos-blades #navigation #chat .chat-message .message-content .label-stripe-chat, -:root body.vtt.game.system-eunos-blades #hotbar #chat .chat-message .message-content .label-stripe-chat, -:root body.vtt.game.system-eunos-blades #players #chat .chat-message .message-content .label-stripe-chat { + font-family: var(--font-emphasis); text-transform: uppercase; - background-color: var(--blades-black); - color: var(--blades-white); position: relative; - padding-top: 3px; - display: flex; - font-weight: bold; - margin: 0; + display: block; + width: min-content; + white-space: nowrap; } -:root body.vtt.game.system-eunos-blades #interface #chat .chat-message .message-content .label-stripe-chat-small, -:root body.vtt.game.system-eunos-blades #controls #chat .chat-message .message-content .label-stripe-chat-small, -:root body.vtt.game.system-eunos-blades #navigation #chat .chat-message .message-content .label-stripe-chat-small, -:root body.vtt.game.system-eunos-blades #hotbar #chat .chat-message .message-content .label-stripe-chat-small, -:root body.vtt.game.system-eunos-blades #players #chat .chat-message .message-content .label-stripe-chat-small { - text-transform: capitalize; - background-color: var(--blades-grey); - color: var(--blades-black); - margin-bottom: 10px; - position: relative; - padding-top: 3px; - display: flex; +:root body.vtt.game.system-eunos-blades #interface #chat .filled-label.narrow-label, +:root body.vtt.game.system-eunos-blades #controls #chat .filled-label.narrow-label, +:root body.vtt.game.system-eunos-blades #navigation #chat .filled-label.narrow-label, +:root body.vtt.game.system-eunos-blades #hotbar #chat .filled-label.narrow-label, +:root body.vtt.game.system-eunos-blades #players #chat .filled-label.narrow-label { + transform-origin: 0 50%; + scale: 0.8 1; + min-width: 125%; + margin-left: 25%; +} +:root body.vtt.game.system-eunos-blades #interface #chat .number-circle, +:root body.vtt.game.system-eunos-blades #controls #chat .number-circle, +:root body.vtt.game.system-eunos-blades #navigation #chat .number-circle, +:root body.vtt.game.system-eunos-blades #hotbar #chat .number-circle, +:root body.vtt.game.system-eunos-blades #players #chat .number-circle { + --nc-size: var(--number-circle-size, 1.25rem); + --nc-border-width: calc(var(--nc-size) * (1/10)); + --nc-font-size: calc(var(--nc-size) * (7/10)); + --nc-border-radius: calc(var(--nc-size) * (5/10)); + --nc-line-height: calc(var(--nc-size) * (8/10)); + --nc-drop-shadow: calc(var(--nc-size) * (0.5/10)); + height: var(--nc-size); + width: var(--nc-size); + font-weight: bold; + font-family: "Minion Pro Caption Cond", serif; + font-size: var(--nc-font-size); + text-align: center; + border-radius: var(--nc-border-radius); + background: var(--number-circle-bg, var(--blades-grey-dark)); + color: var(--number-circle-color, var(--blades-white)); + line-height: var(--nc-line-height); + border: var(--nc-border-width) outset var(--number-circle-color, var(--blades-white)); + margin: -0.1875rem 0.25rem 0 0; + flex-shrink: 0; +} +:root body.vtt.game.system-eunos-blades #interface #chat .gold-bright, +:root body.vtt.game.system-eunos-blades #controls #chat .gold-bright, +:root body.vtt.game.system-eunos-blades #navigation #chat .gold-bright, +:root body.vtt.game.system-eunos-blades #hotbar #chat .gold-bright, +:root body.vtt.game.system-eunos-blades #players #chat .gold-bright { + color: var(--blades-gold-bright) !important; +} +:root body.vtt.game.system-eunos-blades #interface #chat .red-bright, +:root body.vtt.game.system-eunos-blades #controls #chat .red-bright, +:root body.vtt.game.system-eunos-blades #navigation #chat .red-bright, +:root body.vtt.game.system-eunos-blades #hotbar #chat .red-bright, +:root body.vtt.game.system-eunos-blades #players #chat .red-bright { + color: var(--blades-red-bright) !important; +} +:root body.vtt.game.system-eunos-blades #interface #chat .red-dark, +:root body.vtt.game.system-eunos-blades #controls #chat .red-dark, +:root body.vtt.game.system-eunos-blades #navigation #chat .red-dark, +:root body.vtt.game.system-eunos-blades #hotbar #chat .red-dark, +:root body.vtt.game.system-eunos-blades #players #chat .red-dark { + color: var(--blades-red-dark) !important; +} +:root body.vtt.game.system-eunos-blades #interface #chat .grey, +:root body.vtt.game.system-eunos-blades #controls #chat .grey, +:root body.vtt.game.system-eunos-blades #navigation #chat .grey, +:root body.vtt.game.system-eunos-blades #hotbar #chat .grey, +:root body.vtt.game.system-eunos-blades #players #chat .grey { + color: var(--blades-grey) !important; +} +:root body.vtt.game.system-eunos-blades #interface #chat .white, +:root body.vtt.game.system-eunos-blades #controls #chat .white, +:root body.vtt.game.system-eunos-blades #navigation #chat .white, +:root body.vtt.game.system-eunos-blades #hotbar #chat .white, +:root body.vtt.game.system-eunos-blades #players #chat .white { + color: var(--blades-white) !important; +} +:root body.vtt.game.system-eunos-blades #interface #chat .white-bright, +:root body.vtt.game.system-eunos-blades #controls #chat .white-bright, +:root body.vtt.game.system-eunos-blades #navigation #chat .white-bright, +:root body.vtt.game.system-eunos-blades #hotbar #chat .white-bright, +:root body.vtt.game.system-eunos-blades #players #chat .white-bright { + color: var(--blades-white-bright) !important; +} +:root body.vtt.game.system-eunos-blades #interface #chat .cyan-bright, +:root body.vtt.game.system-eunos-blades #controls #chat .cyan-bright, +:root body.vtt.game.system-eunos-blades #navigation #chat .cyan-bright, +:root body.vtt.game.system-eunos-blades #hotbar #chat .cyan-bright, +:root body.vtt.game.system-eunos-blades #players #chat .cyan-bright { + color: var(--blades-blue-bright) !important; +} +:root body.vtt.game.system-eunos-blades #interface #chat .uppercase, +:root body.vtt.game.system-eunos-blades #controls #chat .uppercase, +:root body.vtt.game.system-eunos-blades #navigation #chat .uppercase, +:root body.vtt.game.system-eunos-blades #hotbar #chat .uppercase, +:root body.vtt.game.system-eunos-blades #players #chat .uppercase { + text-transform: uppercase !important; +} +:root body.vtt.game.system-eunos-blades #interface #chat .inline-code, +:root body.vtt.game.system-eunos-blades #controls #chat .inline-code, +:root body.vtt.game.system-eunos-blades #navigation #chat .inline-code, +:root body.vtt.game.system-eunos-blades #hotbar #chat .inline-code, +:root body.vtt.game.system-eunos-blades #players #chat .inline-code { + font-family: var(--font-mono) !important; + font-size: smaller; + text-transform: uppercase; +} +:root body.vtt.game.system-eunos-blades #interface #chat .shadowed, +:root body.vtt.game.system-eunos-blades #controls #chat .shadowed, +:root body.vtt.game.system-eunos-blades #navigation #chat .shadowed, +:root body.vtt.game.system-eunos-blades #hotbar #chat .shadowed, +:root body.vtt.game.system-eunos-blades #players #chat .shadowed { + box-shadow: none; + background: transparent; + text-shadow: 0 0 4px var(--blades-black-dark), 0 0 4px var(--blades-black-dark), 0 0 4px var(--blades-black-dark), 0 0 4px var(--blades-black-dark); +} +:root body.vtt.game.system-eunos-blades #interface #chat .hidden, +:root body.vtt.game.system-eunos-blades #controls #chat .hidden, +:root body.vtt.game.system-eunos-blades #navigation #chat .hidden, +:root body.vtt.game.system-eunos-blades #hotbar #chat .hidden, +:root body.vtt.game.system-eunos-blades #players #chat .hidden { + display: none !important; +} +:root body.vtt.game.system-eunos-blades #interface #chat .start-invisible, +:root body.vtt.game.system-eunos-blades #controls #chat .start-invisible, +:root body.vtt.game.system-eunos-blades #navigation #chat .start-invisible, +:root body.vtt.game.system-eunos-blades #hotbar #chat .start-invisible, +:root body.vtt.game.system-eunos-blades #players #chat .start-invisible { + opacity: 0; +} +:root body.vtt.game.system-eunos-blades #interface #chat .text-checkbox, +:root body.vtt.game.system-eunos-blades #controls #chat .text-checkbox, +:root body.vtt.game.system-eunos-blades #navigation #chat .text-checkbox, +:root body.vtt.game.system-eunos-blades #hotbar #chat .text-checkbox, +:root body.vtt.game.system-eunos-blades #players #chat .text-checkbox { + position: relative; + display: inline-block; +} +:root body.vtt.game.system-eunos-blades #interface #chat .text-checkbox input[type=checkbox], +:root body.vtt.game.system-eunos-blades #controls #chat .text-checkbox input[type=checkbox], +:root body.vtt.game.system-eunos-blades #navigation #chat .text-checkbox input[type=checkbox], +:root body.vtt.game.system-eunos-blades #hotbar #chat .text-checkbox input[type=checkbox], +:root body.vtt.game.system-eunos-blades #players #chat .text-checkbox input[type=checkbox] { + opacity: 0; + height: 100%; + width: 100%; + margin-right: -100%; + display: inline-block; +} +:root body.vtt.game.system-eunos-blades #interface #chat .text-checkbox span, +:root body.vtt.game.system-eunos-blades #controls #chat .text-checkbox span, +:root body.vtt.game.system-eunos-blades #navigation #chat .text-checkbox span, +:root body.vtt.game.system-eunos-blades #hotbar #chat .text-checkbox span, +:root body.vtt.game.system-eunos-blades #players #chat .text-checkbox span { + display: inline-block; +} +:root body.vtt.game.system-eunos-blades #interface #chat .text-checkbox input[type=checkbox]:checked + span, +:root body.vtt.game.system-eunos-blades #controls #chat .text-checkbox input[type=checkbox]:checked + span, +:root body.vtt.game.system-eunos-blades #navigation #chat .text-checkbox input[type=checkbox]:checked + span, +:root body.vtt.game.system-eunos-blades #hotbar #chat .text-checkbox input[type=checkbox]:checked + span, +:root body.vtt.game.system-eunos-blades #players #chat .text-checkbox input[type=checkbox]:checked + span { + color: var(--active-text-color, var(--blades-gold-bright)); +} +:root body.vtt.game.system-eunos-blades #interface #chat .no-img img, +:root body.vtt.game.system-eunos-blades #controls #chat .no-img img, +:root body.vtt.game.system-eunos-blades #navigation #chat .no-img img, +:root body.vtt.game.system-eunos-blades #hotbar #chat .no-img img, +:root body.vtt.game.system-eunos-blades #players #chat .no-img img { + display: none; +} +:root body.vtt.game.system-eunos-blades #interface #chat .flex-horizontal, +:root body.vtt.game.system-eunos-blades #controls #chat .flex-horizontal, +:root body.vtt.game.system-eunos-blades #navigation #chat .flex-horizontal, +:root body.vtt.game.system-eunos-blades #hotbar #chat .flex-horizontal, +:root body.vtt.game.system-eunos-blades #players #chat .flex-horizontal { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: space-between; + align-items: center; +} +:root body.vtt.game.system-eunos-blades #interface #chat .flex-horizontal.flex-wrap, +:root body.vtt.game.system-eunos-blades #controls #chat .flex-horizontal.flex-wrap, +:root body.vtt.game.system-eunos-blades #navigation #chat .flex-horizontal.flex-wrap, +:root body.vtt.game.system-eunos-blades #hotbar #chat .flex-horizontal.flex-wrap, +:root body.vtt.game.system-eunos-blades #players #chat .flex-horizontal.flex-wrap { + flex-wrap: wrap; + align-content: flex-start; +} +:root body.vtt.game.system-eunos-blades #interface #chat .flex-horizontal.full-width, +:root body.vtt.game.system-eunos-blades #controls #chat .flex-horizontal.full-width, +:root body.vtt.game.system-eunos-blades #navigation #chat .flex-horizontal.full-width, +:root body.vtt.game.system-eunos-blades #hotbar #chat .flex-horizontal.full-width, +:root body.vtt.game.system-eunos-blades #players #chat .flex-horizontal.full-width { + width: 100%; + justify-content: space-evenly; +} +:root body.vtt.game.system-eunos-blades #interface #chat .flex-vertical, +:root body.vtt.game.system-eunos-blades #controls #chat .flex-vertical, +:root body.vtt.game.system-eunos-blades #navigation #chat .flex-vertical, +:root body.vtt.game.system-eunos-blades #hotbar #chat .flex-vertical, +:root body.vtt.game.system-eunos-blades #players #chat .flex-vertical { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} +:root body.vtt.game.system-eunos-blades #interface #chat .full-width, +:root body.vtt.game.system-eunos-blades #controls #chat .full-width, +:root body.vtt.game.system-eunos-blades #navigation #chat .full-width, +:root body.vtt.game.system-eunos-blades #hotbar #chat .full-width, +:root body.vtt.game.system-eunos-blades #players #chat .full-width { + width: 100%; +} +:root body.vtt.game.system-eunos-blades #interface #chat .tooltip-trigger, +:root body.vtt.game.system-eunos-blades #controls #chat .tooltip-trigger, +:root body.vtt.game.system-eunos-blades #navigation #chat .tooltip-trigger, +:root body.vtt.game.system-eunos-blades #hotbar #chat .tooltip-trigger, +:root body.vtt.game.system-eunos-blades #players #chat .tooltip-trigger { + pointer-events: auto !important; +} +:root body.vtt.game.system-eunos-blades #interface #chat .tooltip, +:root body.vtt.game.system-eunos-blades #controls #chat .tooltip, +:root body.vtt.game.system-eunos-blades #navigation #chat .tooltip, +:root body.vtt.game.system-eunos-blades #hotbar #chat .tooltip, +:root body.vtt.game.system-eunos-blades #players #chat .tooltip { + opacity: 0; + display: flex; + flex-direction: column; + justify-content: center; + align-items: stretch; + color: var(--blades-white); + background: var(--blades-black-dark); + width: auto; + padding: 0 0.3125rem; + border: 0.0625rem solid var(--blades-grey); + border-radius: 0.1875rem; + text-align: center; + position: absolute; + top: unset; + left: -200px; + bottom: 50px; + z-index: 12; + pointer-events: none; +} +:root body.vtt.game.system-eunos-blades #interface #chat .tooltip > h1, +:root body.vtt.game.system-eunos-blades #controls #chat .tooltip > h1, +:root body.vtt.game.system-eunos-blades #navigation #chat .tooltip > h1, +:root body.vtt.game.system-eunos-blades #hotbar #chat .tooltip > h1, +:root body.vtt.game.system-eunos-blades #players #chat .tooltip > h1 { + text-align: center; + background: transparent; + font-family: var(--font-emphasis); + text-shadow: 0px 0px 4px var(--blades-black-dark), 0px 0px 4px var(--blades-black-dark), 0px 0px 4px var(--blades-black-dark), 0px 0px 4px var(--blades-black-dark); + width: 90%; + margin: 0 auto; + border-bottom: 2px solid var(--blades-white); + white-space: nowrap; +} +:root body.vtt.game.system-eunos-blades #interface #chat .tooltip > h1:last-of-type, +:root body.vtt.game.system-eunos-blades #controls #chat .tooltip > h1:last-of-type, +:root body.vtt.game.system-eunos-blades #navigation #chat .tooltip > h1:last-of-type, +:root body.vtt.game.system-eunos-blades #hotbar #chat .tooltip > h1:last-of-type, +:root body.vtt.game.system-eunos-blades #players #chat .tooltip > h1:last-of-type { + margin-bottom: 5px; +} +:root body.vtt.game.system-eunos-blades #interface #chat .tooltip > p, +:root body.vtt.game.system-eunos-blades #controls #chat .tooltip > p, +:root body.vtt.game.system-eunos-blades #navigation #chat .tooltip > p, +:root body.vtt.game.system-eunos-blades #hotbar #chat .tooltip > p, +:root body.vtt.game.system-eunos-blades #players #chat .tooltip > p { + margin: 5px 0; + text-wrap: balance; +} +:root body.vtt.game.system-eunos-blades #interface #chat .tooltip > ul, :root body.vtt.game.system-eunos-blades #interface #chat .tooltip ol, +:root body.vtt.game.system-eunos-blades #controls #chat .tooltip > ul, +:root body.vtt.game.system-eunos-blades #controls #chat .tooltip ol, +:root body.vtt.game.system-eunos-blades #navigation #chat .tooltip > ul, +:root body.vtt.game.system-eunos-blades #navigation #chat .tooltip ol, +:root body.vtt.game.system-eunos-blades #hotbar #chat .tooltip > ul, +:root body.vtt.game.system-eunos-blades #hotbar #chat .tooltip ol, +:root body.vtt.game.system-eunos-blades #players #chat .tooltip > ul, +:root body.vtt.game.system-eunos-blades #players #chat .tooltip ol { + margin: 5px 0; + text-align: left; + text-wrap: normal; +} +:root body.vtt.game.system-eunos-blades #interface #chat .tooltip > ul li, :root body.vtt.game.system-eunos-blades #interface #chat .tooltip ol li, +:root body.vtt.game.system-eunos-blades #controls #chat .tooltip > ul li, +:root body.vtt.game.system-eunos-blades #controls #chat .tooltip ol li, +:root body.vtt.game.system-eunos-blades #navigation #chat .tooltip > ul li, +:root body.vtt.game.system-eunos-blades #navigation #chat .tooltip ol li, +:root body.vtt.game.system-eunos-blades #hotbar #chat .tooltip > ul li, +:root body.vtt.game.system-eunos-blades #hotbar #chat .tooltip ol li, +:root body.vtt.game.system-eunos-blades #players #chat .tooltip > ul li, +:root body.vtt.game.system-eunos-blades #players #chat .tooltip ol li { + text-wrap: normal; +} +:root body.vtt.game.system-eunos-blades #interface #chat .tooltip h2, +:root body.vtt.game.system-eunos-blades #controls #chat .tooltip h2, +:root body.vtt.game.system-eunos-blades #navigation #chat .tooltip h2, +:root body.vtt.game.system-eunos-blades #hotbar #chat .tooltip h2, +:root body.vtt.game.system-eunos-blades #players #chat .tooltip h2 { + color: var(--blades-white-bright); +} +:root body.vtt.game.system-eunos-blades #interface #chat .tooltip.tooltip-left, +:root body.vtt.game.system-eunos-blades #controls #chat .tooltip.tooltip-left, +:root body.vtt.game.system-eunos-blades #navigation #chat .tooltip.tooltip-left, +:root body.vtt.game.system-eunos-blades #hotbar #chat .tooltip.tooltip-left, +:root body.vtt.game.system-eunos-blades #players #chat .tooltip.tooltip-left { + left: 200px; + max-width: unset; +} +:root body.vtt.game.system-eunos-blades #interface #chat .tooltip.tooltip-portrait, +:root body.vtt.game.system-eunos-blades #controls #chat .tooltip.tooltip-portrait, +:root body.vtt.game.system-eunos-blades #navigation #chat .tooltip.tooltip-portrait, +:root body.vtt.game.system-eunos-blades #hotbar #chat .tooltip.tooltip-portrait, +:root body.vtt.game.system-eunos-blades #players #chat .tooltip.tooltip-portrait { + bottom: -45px; + left: -200px; +} +:root body.vtt.game.system-eunos-blades #interface #chat .tooltip.tooltip-playbook, +:root body.vtt.game.system-eunos-blades #controls #chat .tooltip.tooltip-playbook, +:root body.vtt.game.system-eunos-blades #navigation #chat .tooltip.tooltip-playbook, +:root body.vtt.game.system-eunos-blades #hotbar #chat .tooltip.tooltip-playbook, +:root body.vtt.game.system-eunos-blades #players #chat .tooltip.tooltip-playbook { + bottom: unset; + top: 0px; + left: -220px; +} +:root body.vtt.game.system-eunos-blades #interface #chat .tooltip.tooltip-trauma, +:root body.vtt.game.system-eunos-blades #controls #chat .tooltip.tooltip-trauma, +:root body.vtt.game.system-eunos-blades #navigation #chat .tooltip.tooltip-trauma, +:root body.vtt.game.system-eunos-blades #hotbar #chat .tooltip.tooltip-trauma, +:root body.vtt.game.system-eunos-blades #players #chat .tooltip.tooltip-trauma { + left: -300px; +} +:root body.vtt.game.system-eunos-blades #interface #chat .tooltip.tooltip-dialog-selection, +:root body.vtt.game.system-eunos-blades #controls #chat .tooltip.tooltip-dialog-selection, +:root body.vtt.game.system-eunos-blades #navigation #chat .tooltip.tooltip-dialog-selection, +:root body.vtt.game.system-eunos-blades #hotbar #chat .tooltip.tooltip-dialog-selection, +:root body.vtt.game.system-eunos-blades #players #chat .tooltip.tooltip-dialog-selection { + left: -300px; + bottom: 75px; +} +:root body.vtt.game.system-eunos-blades #interface #chat .tooltip.tooltip-attribute, +:root body.vtt.game.system-eunos-blades #controls #chat .tooltip.tooltip-attribute, +:root body.vtt.game.system-eunos-blades #navigation #chat .tooltip.tooltip-attribute, +:root body.vtt.game.system-eunos-blades #hotbar #chat .tooltip.tooltip-attribute, +:root body.vtt.game.system-eunos-blades #players #chat .tooltip.tooltip-attribute { + left: -110px; + bottom: 10px; + translate: 0% 50% !important; +} +:root body.vtt.game.system-eunos-blades #interface #chat .tooltip.tooltip-attribute > p, +:root body.vtt.game.system-eunos-blades #controls #chat .tooltip.tooltip-attribute > p, +:root body.vtt.game.system-eunos-blades #navigation #chat .tooltip.tooltip-attribute > p, +:root body.vtt.game.system-eunos-blades #hotbar #chat .tooltip.tooltip-attribute > p, +:root body.vtt.game.system-eunos-blades #players #chat .tooltip.tooltip-attribute > p { + font-size: 16px; +} +:root body.vtt.game.system-eunos-blades #interface #chat .tooltip.tooltip-action, +:root body.vtt.game.system-eunos-blades #controls #chat .tooltip.tooltip-action, +:root body.vtt.game.system-eunos-blades #navigation #chat .tooltip.tooltip-action, +:root body.vtt.game.system-eunos-blades #hotbar #chat .tooltip.tooltip-action, +:root body.vtt.game.system-eunos-blades #players #chat .tooltip.tooltip-action { + left: unset; + right: -100px; + bottom: 0px; + max-width: 525px; + translate: 0% 50% !important; +} +:root body.vtt.game.system-eunos-blades #interface #chat .tooltip.tooltip-action > p, +:root body.vtt.game.system-eunos-blades #controls #chat .tooltip.tooltip-action > p, +:root body.vtt.game.system-eunos-blades #navigation #chat .tooltip.tooltip-action > p, +:root body.vtt.game.system-eunos-blades #hotbar #chat .tooltip.tooltip-action > p, +:root body.vtt.game.system-eunos-blades #players #chat .tooltip.tooltip-action > p { + font-size: 16px; +} +:root body.vtt.game.system-eunos-blades #interface #chat .tooltip.tooltip-roll-mod, +:root body.vtt.game.system-eunos-blades #controls #chat .tooltip.tooltip-roll-mod, +:root body.vtt.game.system-eunos-blades #navigation #chat .tooltip.tooltip-roll-mod, +:root body.vtt.game.system-eunos-blades #hotbar #chat .tooltip.tooltip-roll-mod, +:root body.vtt.game.system-eunos-blades #players #chat .tooltip.tooltip-roll-mod { + left: calc(50% + 350px); + bottom: 20px; + max-width: 350px; + width: 350px; + min-width: 350px; + translate: -50% 0% !important; + text-align: center; + hyphens: none; +} +:root body.vtt.game.system-eunos-blades #interface #chat .tooltip.tooltip-roll-mod > p:not(:last-of-type), +:root body.vtt.game.system-eunos-blades #controls #chat .tooltip.tooltip-roll-mod > p:not(:last-of-type), +:root body.vtt.game.system-eunos-blades #navigation #chat .tooltip.tooltip-roll-mod > p:not(:last-of-type), +:root body.vtt.game.system-eunos-blades #hotbar #chat .tooltip.tooltip-roll-mod > p:not(:last-of-type), +:root body.vtt.game.system-eunos-blades #players #chat .tooltip.tooltip-roll-mod > p:not(:last-of-type) { + margin-bottom: 10px; +} +:root body.vtt.game.system-eunos-blades #interface #chat .tooltip.tooltip-pos-effect-trade, +:root body.vtt.game.system-eunos-blades #controls #chat .tooltip.tooltip-pos-effect-trade, +:root body.vtt.game.system-eunos-blades #navigation #chat .tooltip.tooltip-pos-effect-trade, +:root body.vtt.game.system-eunos-blades #hotbar #chat .tooltip.tooltip-pos-effect-trade, +:root body.vtt.game.system-eunos-blades #players #chat .tooltip.tooltip-pos-effect-trade { + width: 330px; + max-width: 330px; + min-width: 330px; + bottom: 5px; + left: 0px; + font-family: var(--font-default); + font-size: 0.875rem; + text-transform: none; + text-align: center; +} +:root body.vtt.game.system-eunos-blades #interface #chat .tooltip.tooltip-roll-trait-pc, +:root body.vtt.game.system-eunos-blades #controls #chat .tooltip.tooltip-roll-trait-pc, +:root body.vtt.game.system-eunos-blades #navigation #chat .tooltip.tooltip-roll-trait-pc, +:root body.vtt.game.system-eunos-blades #hotbar #chat .tooltip.tooltip-roll-trait-pc, +:root body.vtt.game.system-eunos-blades #players #chat .tooltip.tooltip-roll-trait-pc { + bottom: 100px; +} +:root body.vtt.game.system-eunos-blades #interface #chat .tooltip.tooltip-roll-trait-pc table tbody tr:nth-child(4n), +:root body.vtt.game.system-eunos-blades #controls #chat .tooltip.tooltip-roll-trait-pc table tbody tr:nth-child(4n), +:root body.vtt.game.system-eunos-blades #navigation #chat .tooltip.tooltip-roll-trait-pc table tbody tr:nth-child(4n), +:root body.vtt.game.system-eunos-blades #hotbar #chat .tooltip.tooltip-roll-trait-pc table tbody tr:nth-child(4n), +:root body.vtt.game.system-eunos-blades #players #chat .tooltip.tooltip-roll-trait-pc table tbody tr:nth-child(4n) { + border-bottom: 2px solid var(--blades-white); +} +:root body.vtt.game.system-eunos-blades #interface #chat .tooltip.tooltip-roll-trait-pc table tbody tr td:nth-child(1), +:root body.vtt.game.system-eunos-blades #controls #chat .tooltip.tooltip-roll-trait-pc table tbody tr td:nth-child(1), +:root body.vtt.game.system-eunos-blades #navigation #chat .tooltip.tooltip-roll-trait-pc table tbody tr td:nth-child(1), +:root body.vtt.game.system-eunos-blades #hotbar #chat .tooltip.tooltip-roll-trait-pc table tbody tr td:nth-child(1), +:root body.vtt.game.system-eunos-blades #players #chat .tooltip.tooltip-roll-trait-pc table tbody tr td:nth-child(1) { + text-align: right; +} +:root body.vtt.game.system-eunos-blades #interface #chat .tooltip.tooltip-roll-trait-pc table tbody tr td:nth-child(2), +:root body.vtt.game.system-eunos-blades #controls #chat .tooltip.tooltip-roll-trait-pc table tbody tr td:nth-child(2), +:root body.vtt.game.system-eunos-blades #navigation #chat .tooltip.tooltip-roll-trait-pc table tbody tr td:nth-child(2), +:root body.vtt.game.system-eunos-blades #hotbar #chat .tooltip.tooltip-roll-trait-pc table tbody tr td:nth-child(2), +:root body.vtt.game.system-eunos-blades #players #chat .tooltip.tooltip-roll-trait-pc table tbody tr td:nth-child(2) { + text-align: left; +} +:root body.vtt.game.system-eunos-blades #interface #chat .tooltip.tooltip-roll-trait-pc table tbody tr td:nth-child(3), +:root body.vtt.game.system-eunos-blades #controls #chat .tooltip.tooltip-roll-trait-pc table tbody tr td:nth-child(3), +:root body.vtt.game.system-eunos-blades #navigation #chat .tooltip.tooltip-roll-trait-pc table tbody tr td:nth-child(3), +:root body.vtt.game.system-eunos-blades #hotbar #chat .tooltip.tooltip-roll-trait-pc table tbody tr td:nth-child(3), +:root body.vtt.game.system-eunos-blades #players #chat .tooltip.tooltip-roll-trait-pc table tbody tr td:nth-child(3) { + text-align: left; +} +:root body.vtt.game.system-eunos-blades #interface #chat .tooltip-scaling-elem, +:root body.vtt.game.system-eunos-blades #controls #chat .tooltip-scaling-elem, +:root body.vtt.game.system-eunos-blades #navigation #chat .tooltip-scaling-elem, +:root body.vtt.game.system-eunos-blades #hotbar #chat .tooltip-scaling-elem, +:root body.vtt.game.system-eunos-blades #players #chat .tooltip-scaling-elem { + display: inline-block; +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel { + display: flex; + flex-direction: column; + height: min-content; +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel > *, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel > *, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel > *, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel > *, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel > * { + flex-grow: 1; + flex-shrink: 1; +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary { + width: 100%; + overflow: visible; + padding: 5px; + position: relative; + z-index: 4; + pointer-events: auto; + display: grid; + grid-template-areas: "header header header notes" "actions assets harm notes"; + grid-template-rows: 20px 1fr; + grid-template-columns: 16% 16% 16% 1fr; +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary:hover, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary:hover, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary:hover, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary:hover, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary:hover { + z-index: 5; +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary .pc-summary-img, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary .pc-summary-img, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary .pc-summary-img, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary .pc-summary-img, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary .pc-summary-img { + position: absolute; + top: 20px; + width: 16%; + pointer-events: none; + z-index: -1; + opacity: 0.5; +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary section.pc-summary-header, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary section.pc-summary-header, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary section.pc-summary-header, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary section.pc-summary-header, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary section.pc-summary-header { + grid-area: header; + display: grid; + grid-template-areas: "name name heritage background-img vice-img playbook-img"; + grid-template-rows: 20px; + grid-template-columns: 1fr 1fr 40px 20px 20px 20px; + position: relative; + width: 100%; + grid-gap: 5px; + height: min-content; +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary section.pc-summary-header > img, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary section.pc-summary-header > img, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary section.pc-summary-header > img, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary section.pc-summary-header > img, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary section.pc-summary-header > img { + filter: drop-shadow(1px 1px 0px var(--blades-black-dark)); +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary section.pc-summary-header > img.pc-summary-background-img, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary section.pc-summary-header > img.pc-summary-background-img, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary section.pc-summary-header > img.pc-summary-background-img, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary section.pc-summary-header > img.pc-summary-background-img, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary section.pc-summary-header > img.pc-summary-background-img { + grid-area: background-img; +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary section.pc-summary-header > img.pc-summary-vice-img, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary section.pc-summary-header > img.pc-summary-vice-img, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary section.pc-summary-header > img.pc-summary-vice-img, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary section.pc-summary-header > img.pc-summary-vice-img, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary section.pc-summary-header > img.pc-summary-vice-img { + grid-area: vice-img; +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary section.pc-summary-header > img.pc-summary-playbook-img, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary section.pc-summary-header > img.pc-summary-playbook-img, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary section.pc-summary-header > img.pc-summary-playbook-img, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary section.pc-summary-header > img.pc-summary-playbook-img, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary section.pc-summary-header > img.pc-summary-playbook-img { + grid-area: playbook-img; +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary section.pc-summary-header .pc-summary-name, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary section.pc-summary-header .pc-summary-name, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary section.pc-summary-header .pc-summary-name, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary section.pc-summary-header .pc-summary-name, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary section.pc-summary-header .pc-summary-name { + grid-area: name; + font-family: var(--font-emphasis); + font-size: 1.25rem; + line-height: 1.25rem; + color: var(--blades-white-bright); + font-variant: small-caps; +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary section.pc-summary-header .pc-summary-heritage, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary section.pc-summary-header .pc-summary-heritage, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary section.pc-summary-header .pc-summary-heritage, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary section.pc-summary-header .pc-summary-heritage, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary section.pc-summary-header .pc-summary-heritage { + grid-area: heritage; + font-size: 10px; + font-family: Oswald, sans-serif; + text-transform: uppercase; + line-height: 20px; + text-align: right; + color: var(--blades-white-bright); + padding-right: 5px; +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary section.pc-summary-actions, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary section.pc-summary-actions, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary section.pc-summary-actions, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary section.pc-summary-actions, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary section.pc-summary-actions { + grid-area: actions; + display: flex; + flex-direction: row; + flex-wrap: wrap; + align-items: flex-start; + align-content: flex-start; +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary section.pc-summary-actions .pc-summary-attribute, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary section.pc-summary-actions .pc-summary-attribute, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary section.pc-summary-actions .pc-summary-attribute, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary section.pc-summary-actions .pc-summary-attribute, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary section.pc-summary-actions .pc-summary-attribute { + text-align: center; + flex-basis: 100%; + flex-grow: 1; + flex-shrink: 0; + line-height: 0.875rem; + height: 0.875rem; +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary section.pc-summary-actions .pc-summary-attribute:not(:first-child), +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary section.pc-summary-actions .pc-summary-attribute:not(:first-child), +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary section.pc-summary-actions .pc-summary-attribute:not(:first-child), +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary section.pc-summary-actions .pc-summary-attribute:not(:first-child), +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary section.pc-summary-actions .pc-summary-attribute:not(:first-child) { + margin-top: 5px; +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary section.pc-summary-actions .pc-summary-attribute label, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary section.pc-summary-actions .pc-summary-attribute label, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary section.pc-summary-actions .pc-summary-attribute label, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary section.pc-summary-actions .pc-summary-attribute label, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary section.pc-summary-actions .pc-summary-attribute label { + font-family: Oswald, sans-serif; + font-size: 0.75rem; + line-height: 0.75rem; + color: var(--blades-white); +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary section.pc-summary-actions .pc-summary-attribute span, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary section.pc-summary-actions .pc-summary-attribute span, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary section.pc-summary-actions .pc-summary-attribute span, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary section.pc-summary-actions .pc-summary-attribute span, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary section.pc-summary-actions .pc-summary-attribute span { + font-family: Oswald, sans-serif; + font-size: 0.75rem; + line-height: 0.75rem; + color: var(--blades-white-bright); +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary section.pc-summary-actions .pc-summary-action, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary section.pc-summary-actions .pc-summary-action, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary section.pc-summary-actions .pc-summary-action, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary section.pc-summary-actions .pc-summary-action, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary section.pc-summary-actions .pc-summary-action { + flex-basis: 50%; + flex-grow: 0; + flex-shrink: 0; + line-height: 0.75rem; + height: 0.75rem; + display: flex; + flex-direction: row; +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary section.pc-summary-actions .pc-summary-action label, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary section.pc-summary-actions .pc-summary-action label, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary section.pc-summary-actions .pc-summary-action label, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary section.pc-summary-actions .pc-summary-action label, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary section.pc-summary-actions .pc-summary-action label { + font-family: Oswald, sans-serif; + font-size: 0.625rem; + line-height: 0.625rem; + color: var(--blades-grey-bright); + flex-grow: 1; + text-align: right; +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary section.pc-summary-actions .pc-summary-action span, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary section.pc-summary-actions .pc-summary-action span, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary section.pc-summary-actions .pc-summary-action span, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary section.pc-summary-actions .pc-summary-action span, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary section.pc-summary-actions .pc-summary-action span { + font-family: Oswald, sans-serif; + font-size: 0.625rem; + line-height: 0.625rem; + color: var(--blades-white-bright); + width: 20px; + text-align: center; +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary section.pc-summary-assets, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary section.pc-summary-assets, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary section.pc-summary-assets, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary section.pc-summary-assets, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary section.pc-summary-assets { + grid-area: assets; + padding-right: 3px; + position: relative; + z-index: 5; +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary section.pc-summary-assets:hover, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary section.pc-summary-assets:hover, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary section.pc-summary-assets:hover, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary section.pc-summary-assets:hover, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary section.pc-summary-assets:hover { + z-index: 10; +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container:not(:first-child), +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container:not(:first-child), +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container:not(:first-child), +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container:not(:first-child), +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container:not(:first-child) { + margin-top: 5px; + border-top: 1px dotted var(--blades-white-bright); +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container .pc-summary-gear-loadout-container, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container .pc-summary-gear-loadout-container, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container .pc-summary-gear-loadout-container, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container .pc-summary-gear-loadout-container, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container .pc-summary-gear-loadout-container { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: stretch; + gap: 3px; + height: 14px; +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container .pc-summary-gear-loadout-container .pc-summary-gear-loadout, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container .pc-summary-gear-loadout-container .pc-summary-gear-loadout, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container .pc-summary-gear-loadout-container .pc-summary-gear-loadout, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container .pc-summary-gear-loadout-container .pc-summary-gear-loadout, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container .pc-summary-gear-loadout-container .pc-summary-gear-loadout { + flex-grow: 1; + font-family: "Fjalla One", sans-serif; + font-size: 10px; + line-height: 12px; + text-transform: uppercase; + white-space: nowrap; + overflow: hidden; + text-align: left; + color: var(--blades-gold-bright); +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container .pc-summary-gear-loadout-container i, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container .pc-summary-gear-loadout-container i, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container .pc-summary-gear-loadout-container i, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container .pc-summary-gear-loadout-container i, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container .pc-summary-gear-loadout-container i { + font-size: 10px; + line-height: 12px; +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container .pc-summary-asset, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container .pc-summary-asset, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container .pc-summary-asset, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container .pc-summary-asset, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container .pc-summary-asset { + display: flex; + flex-direction: row; +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container .pc-summary-asset .pc-summary-asset-img, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container .pc-summary-asset .pc-summary-asset-img, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container .pc-summary-asset .pc-summary-asset-img, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container .pc-summary-asset .pc-summary-asset-img, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container .pc-summary-asset .pc-summary-asset-img { + height: 12px; + flex-grow: 0; + flex-shrink: 0; + flex-basis: 12px; +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container .pc-summary-asset .pc-summary-asset-name, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container .pc-summary-asset .pc-summary-asset-name, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container .pc-summary-asset .pc-summary-asset-name, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container .pc-summary-asset .pc-summary-asset-name, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container .pc-summary-asset .pc-summary-asset-name { + flex-grow: 1; + font-family: "Fjalla One", sans-serif; + font-size: 10px; + line-height: 12px; + white-space: nowrap; + overflow: hidden; + text-transform: uppercase; + color: var(--blades-white-bright); +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container .pc-summary-asset .pc-summary-asset-name:hover, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container .pc-summary-asset .pc-summary-asset-name:hover, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container .pc-summary-asset .pc-summary-asset-name:hover, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container .pc-summary-asset .pc-summary-asset-name:hover, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container .pc-summary-asset .pc-summary-asset-name:hover { + overflow: visible; +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container .tooltip.pc-summary-asset-tooltip, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container .tooltip.pc-summary-asset-tooltip, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container .tooltip.pc-summary-asset-tooltip, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container .tooltip.pc-summary-asset-tooltip, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary section.pc-summary-assets .pc-summary-assets-container .tooltip.pc-summary-asset-tooltip { + font-size: 10px; + line-height: 10px; +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses { + grid-area: harm; +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-stress-container, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-stress-container, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-stress-container, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-stress-container, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-stress-container { + width: 100%; + display: flex; + justify-content: flex-end; + height: 14px; + overflow: hidden; + flex-wrap: nowrap; + flex-direction: row; +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-stress-container .pc-summary-stress-box, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-stress-container .pc-summary-stress-box, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-stress-container .pc-summary-stress-box, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-stress-container .pc-summary-stress-box, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-stress-container .pc-summary-stress-box { + display: block; + height: 100%; + width: 5px; + margin: 0px 1px; + border: 1px solid var(--blades-red-dark); + border-radius: 3px; +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-stress-container .pc-summary-stress-box.stress-box-full, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-stress-container .pc-summary-stress-box.stress-box-full, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-stress-container .pc-summary-stress-box.stress-box-full, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-stress-container .pc-summary-stress-box.stress-box-full, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-stress-container .pc-summary-stress-box.stress-box-full { + background: var(--blades-red-bright); + border-color: var(--blades-red); +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-trauma-container, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-trauma-container, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-trauma-container, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-trauma-container, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-trauma-container { + margin-top: 5px; + border-bottom: 1px dotted var(--blades-red); +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-trauma-container .pc-summary-trauma, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-trauma-container .pc-summary-trauma, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-trauma-container .pc-summary-trauma, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-trauma-container .pc-summary-trauma, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-trauma-container .pc-summary-trauma { + color: var(--blades-red-bright); + font-weight: bold; + font-family: Kirsty, serif; + font-size: 12px; + text-transform: uppercase; + text-align: center; + width: 100%; + display: block; +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container { + margin-top: 5px; + display: flex; + justify-content: flex-start; + align-items: stretch; + flex-direction: column; +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container .pc-summary-harm, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container .pc-summary-harm, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container .pc-summary-harm, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container .pc-summary-harm, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container .pc-summary-harm { + font-size: 10px; + font-family: "Fjalla One", sans-serif; + overflow: hidden; + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: stretch; + flex-grow: 1; + padding: 0 5px; + line-height: 15px; + pointer-events: auto; +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container .pc-summary-harm:hover, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container .pc-summary-harm:hover, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container .pc-summary-harm:hover, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container .pc-summary-harm:hover, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container .pc-summary-harm:hover { + overflow: visible; +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container .pc-summary-harm > label, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container .pc-summary-harm > label, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container .pc-summary-harm > label, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container .pc-summary-harm > label, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container .pc-summary-harm > label { + flex-grow: 1; + white-space: nowrap; + padding-left: 4px; +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container .pc-summary-harm > i, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container .pc-summary-harm > i, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container .pc-summary-harm > i, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container .pc-summary-harm > i, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container .pc-summary-harm > i { + line-height: 15px; +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container .pc-summary-harm.pc-harm-heavy, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container .pc-summary-harm.pc-harm-heavy, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container .pc-summary-harm.pc-harm-heavy, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container .pc-summary-harm.pc-harm-heavy, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container .pc-summary-harm.pc-harm-heavy { + color: var(--blades-white-bright); + background: var(--blades-red-dark); + font-weight: bold; +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container .pc-summary-harm.pc-harm-medium, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container .pc-summary-harm.pc-harm-medium, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container .pc-summary-harm.pc-harm-medium, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container .pc-summary-harm.pc-harm-medium, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container .pc-summary-harm.pc-harm-medium { + color: var(--blades-red-bright); + background: var(--blades-red-dark-fade); + font-weight: bold; +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container .pc-summary-harm.pc-harm-light, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container .pc-summary-harm.pc-harm-light, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container .pc-summary-harm.pc-harm-light, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container .pc-summary-harm.pc-harm-light, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container .pc-summary-harm.pc-harm-light { + color: var(--blades-red-bright); + background: var(--blades-red-dark-fade-strong); +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container .pc-summary-harm.harm-inactive, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container .pc-summary-harm.harm-inactive, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container .pc-summary-harm.harm-inactive, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container .pc-summary-harm.harm-inactive, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary section.pc-summary-maluses .pc-summary-harm-container .pc-summary-harm.harm-inactive { + background: transparent; + color: var(--blades-grey); +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary section.pc-summary-notes, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary section.pc-summary-notes, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary section.pc-summary-notes, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary section.pc-summary-notes, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary section.pc-summary-notes { + grid-area: notes; +} +:root body.vtt.game.system-eunos-blades #interface #chat .player-character-summary-panel .pc-summary section.pc-summary-notes .pc-summary-notes-body, +:root body.vtt.game.system-eunos-blades #controls #chat .player-character-summary-panel .pc-summary section.pc-summary-notes .pc-summary-notes-body, +:root body.vtt.game.system-eunos-blades #navigation #chat .player-character-summary-panel .pc-summary section.pc-summary-notes .pc-summary-notes-body, +:root body.vtt.game.system-eunos-blades #hotbar #chat .player-character-summary-panel .pc-summary section.pc-summary-notes .pc-summary-notes-body, +:root body.vtt.game.system-eunos-blades #players #chat .player-character-summary-panel .pc-summary section.pc-summary-notes .pc-summary-notes-body { + height: 100%; + resize: none; +} +:root body.vtt.game.system-eunos-blades #interface #chat .selectable-image-panel, +:root body.vtt.game.system-eunos-blades #controls #chat .selectable-image-panel, +:root body.vtt.game.system-eunos-blades #navigation #chat .selectable-image-panel, +:root body.vtt.game.system-eunos-blades #hotbar #chat .selectable-image-panel, +:root body.vtt.game.system-eunos-blades #players #chat .selectable-image-panel { + display: flex; + flex-wrap: wrap; + align-items: flex-start; + align-content: flex-start; + justify-content: flex-start; + border-left: 4px double var(--blades-white); + border-right: 4px double var(--blades-white); + gap: 5px; + height: min-content; + position: relative; + min-height: 100px; +} +:root body.vtt.game.system-eunos-blades #interface #chat .selectable-image-panel .selectable-image, +:root body.vtt.game.system-eunos-blades #controls #chat .selectable-image-panel .selectable-image, +:root body.vtt.game.system-eunos-blades #navigation #chat .selectable-image-panel .selectable-image, +:root body.vtt.game.system-eunos-blades #hotbar #chat .selectable-image-panel .selectable-image, +:root body.vtt.game.system-eunos-blades #players #chat .selectable-image-panel .selectable-image { + pointer-events: auto !important; + max-height: 100px; + max-width: 100px; + filter: brightness(1.5); +} +:root body.vtt.game.system-eunos-blades #interface #chat .selectable-image-panel .selectable-image.image-selected, +:root body.vtt.game.system-eunos-blades #controls #chat .selectable-image-panel .selectable-image.image-selected, +:root body.vtt.game.system-eunos-blades #navigation #chat .selectable-image-panel .selectable-image.image-selected, +:root body.vtt.game.system-eunos-blades #hotbar #chat .selectable-image-panel .selectable-image.image-selected, +:root body.vtt.game.system-eunos-blades #players #chat .selectable-image-panel .selectable-image.image-selected { + outline: 2px solid var(--blades-green-bright); + position: relative; + z-index: 2; +} +:root body.vtt.game.system-eunos-blades #interface #chat .selectable-image-panel .add-image-control, +:root body.vtt.game.system-eunos-blades #controls #chat .selectable-image-panel .add-image-control, +:root body.vtt.game.system-eunos-blades #navigation #chat .selectable-image-panel .add-image-control, +:root body.vtt.game.system-eunos-blades #hotbar #chat .selectable-image-panel .add-image-control, +:root body.vtt.game.system-eunos-blades #players #chat .selectable-image-panel .add-image-control { + position: absolute; + z-index: 3; + top: 10px; + right: 10px; + pointer-events: auto !important; + height: 20px; + width: 20px; + rotate: 45deg; + opacity: 0.5; + transition: 0.25s; +} +:root body.vtt.game.system-eunos-blades #interface #chat .selectable-image-panel .add-image-control:hover, +:root body.vtt.game.system-eunos-blades #controls #chat .selectable-image-panel .add-image-control:hover, +:root body.vtt.game.system-eunos-blades #navigation #chat .selectable-image-panel .add-image-control:hover, +:root body.vtt.game.system-eunos-blades #hotbar #chat .selectable-image-panel .add-image-control:hover, +:root body.vtt.game.system-eunos-blades #players #chat .selectable-image-panel .add-image-control:hover { + opacity: 1; +} +:root body.vtt.game.system-eunos-blades #interface #chat .opposition-creation-panel, +:root body.vtt.game.system-eunos-blades #controls #chat .opposition-creation-panel, +:root body.vtt.game.system-eunos-blades #navigation #chat .opposition-creation-panel, +:root body.vtt.game.system-eunos-blades #hotbar #chat .opposition-creation-panel, +:root body.vtt.game.system-eunos-blades #players #chat .opposition-creation-panel { + display: flex; + flex-direction: row; + flex-wrap: wrap; + height: min-content; + flex-grow: 0; + gap: 5px; +} +:root body.vtt.game.system-eunos-blades #interface #chat .opposition-creation-panel .roll-opposition-container, +:root body.vtt.game.system-eunos-blades #controls #chat .opposition-creation-panel .roll-opposition-container, +:root body.vtt.game.system-eunos-blades #navigation #chat .opposition-creation-panel .roll-opposition-container, +:root body.vtt.game.system-eunos-blades #hotbar #chat .opposition-creation-panel .roll-opposition-container, +:root body.vtt.game.system-eunos-blades #players #chat .opposition-creation-panel .roll-opposition-container { + flex-basis: 45%; + max-width: 50%; + flex-grow: 1; + flex-shrink: 1; + display: flex; + flex-direction: column; + align-items: stretch; + justify-content: flex-start; +} +:root body.vtt.game.system-eunos-blades #interface #chat .opposition-creation-panel .roll-opposition-container.selected-opposition, +:root body.vtt.game.system-eunos-blades #controls #chat .opposition-creation-panel .roll-opposition-container.selected-opposition, +:root body.vtt.game.system-eunos-blades #navigation #chat .opposition-creation-panel .roll-opposition-container.selected-opposition, +:root body.vtt.game.system-eunos-blades #hotbar #chat .opposition-creation-panel .roll-opposition-container.selected-opposition, +:root body.vtt.game.system-eunos-blades #players #chat .opposition-creation-panel .roll-opposition-container.selected-opposition { + box-shadow: inset 0 0 10px var(--blades-green-bright), inset 0 0 10px var(--blades-green-bright), inset 0 0 10px var(--blades-green-bright); +} +:root body.vtt.game.system-eunos-blades #interface #chat .opposition-creation-panel .roll-opposition-container.opposition-blank, +:root body.vtt.game.system-eunos-blades #controls #chat .opposition-creation-panel .roll-opposition-container.opposition-blank, +:root body.vtt.game.system-eunos-blades #navigation #chat .opposition-creation-panel .roll-opposition-container.opposition-blank, +:root body.vtt.game.system-eunos-blades #hotbar #chat .opposition-creation-panel .roll-opposition-container.opposition-blank, +:root body.vtt.game.system-eunos-blades #players #chat .opposition-creation-panel .roll-opposition-container.opposition-blank { + opacity: 0.5; + filter: blur(2px); +} +:root body.vtt.game.system-eunos-blades #interface #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-top-container, +:root body.vtt.game.system-eunos-blades #controls #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-top-container, +:root body.vtt.game.system-eunos-blades #navigation #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-top-container, +:root body.vtt.game.system-eunos-blades #hotbar #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-top-container, +:root body.vtt.game.system-eunos-blades #players #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-top-container { + flex-grow: 1; + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: stretch; + align-items: flex-start; +} +:root body.vtt.game.system-eunos-blades #interface #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-top-container .roll-opposition-img, +:root body.vtt.game.system-eunos-blades #controls #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-top-container .roll-opposition-img, +:root body.vtt.game.system-eunos-blades #navigation #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-top-container .roll-opposition-img, +:root body.vtt.game.system-eunos-blades #hotbar #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-top-container .roll-opposition-img, +:root body.vtt.game.system-eunos-blades #players #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-top-container .roll-opposition-img { + max-height: 58px; + max-width: 58px; + flex-grow: 0; + pointer-events: auto !important; +} +:root body.vtt.game.system-eunos-blades #interface #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-top-container .roll-opposition-text-container, +:root body.vtt.game.system-eunos-blades #controls #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-top-container .roll-opposition-text-container, +:root body.vtt.game.system-eunos-blades #navigation #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-top-container .roll-opposition-text-container, +:root body.vtt.game.system-eunos-blades #hotbar #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-top-container .roll-opposition-text-container, +:root body.vtt.game.system-eunos-blades #players #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-top-container .roll-opposition-text-container { + display: flex; + flex-grow: 1; + flex-direction: column; + align-items: stretch; + justify-content: space-between; +} +:root body.vtt.game.system-eunos-blades #interface #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-factors-container, +:root body.vtt.game.system-eunos-blades #interface #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-factor-label-container, +:root body.vtt.game.system-eunos-blades #controls #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-factors-container, +:root body.vtt.game.system-eunos-blades #controls #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-factor-label-container, +:root body.vtt.game.system-eunos-blades #navigation #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-factors-container, +:root body.vtt.game.system-eunos-blades #navigation #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-factor-label-container, +:root body.vtt.game.system-eunos-blades #hotbar #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-factors-container, +:root body.vtt.game.system-eunos-blades #hotbar #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-factor-label-container, +:root body.vtt.game.system-eunos-blades #players #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-factors-container, +:root body.vtt.game.system-eunos-blades #players #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-factor-label-container { + flex-grow: 1; + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: space-evenly; + align-items: center; + gap: 5px; +} +:root body.vtt.game.system-eunos-blades #interface #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-factors-container .factor-label, +:root body.vtt.game.system-eunos-blades #interface #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-factor-label-container .factor-label, +:root body.vtt.game.system-eunos-blades #controls #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-factors-container .factor-label, +:root body.vtt.game.system-eunos-blades #controls #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-factor-label-container .factor-label, +:root body.vtt.game.system-eunos-blades #navigation #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-factors-container .factor-label, +:root body.vtt.game.system-eunos-blades #navigation #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-factor-label-container .factor-label, +:root body.vtt.game.system-eunos-blades #hotbar #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-factors-container .factor-label, +:root body.vtt.game.system-eunos-blades #hotbar #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-factor-label-container .factor-label, +:root body.vtt.game.system-eunos-blades #players #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-factors-container .factor-label, +:root body.vtt.game.system-eunos-blades #players #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-factor-label-container .factor-label { + flex-grow: 1; + text-align: center; + font-family: "Fjalla One", sans-serif; + font-size: 8px; + color: var(--blades-white-bright); + max-width: 50px; +} +:root body.vtt.game.system-eunos-blades #interface #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-factors-container .factor-selector, +:root body.vtt.game.system-eunos-blades #interface #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-factor-label-container .factor-selector, +:root body.vtt.game.system-eunos-blades #controls #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-factors-container .factor-selector, +:root body.vtt.game.system-eunos-blades #controls #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-factor-label-container .factor-selector, +:root body.vtt.game.system-eunos-blades #navigation #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-factors-container .factor-selector, +:root body.vtt.game.system-eunos-blades #navigation #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-factor-label-container .factor-selector, +:root body.vtt.game.system-eunos-blades #hotbar #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-factors-container .factor-selector, +:root body.vtt.game.system-eunos-blades #hotbar #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-factor-label-container .factor-selector, +:root body.vtt.game.system-eunos-blades #players #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-factors-container .factor-selector, +:root body.vtt.game.system-eunos-blades #players #chat .opposition-creation-panel .roll-opposition-container .roll-opposition-factor-label-container .factor-selector { + flex-grow: 0; + flex-basis: 25%; + flex-shrink: 1; + pointer-events: auto !important; + font-family: var(--font-emphasis); + color: var(--blades-white-bright); + text-align: center; + font-size: 14px; + height: 18px; + max-width: 50px; +} +:root body.vtt.game.system-eunos-blades #interface #chat .opposition-creation-panel .roll-opposition-container input.shadowed, +:root body.vtt.game.system-eunos-blades #controls #chat .opposition-creation-panel .roll-opposition-container input.shadowed, +:root body.vtt.game.system-eunos-blades #navigation #chat .opposition-creation-panel .roll-opposition-container input.shadowed, +:root body.vtt.game.system-eunos-blades #hotbar #chat .opposition-creation-panel .roll-opposition-container input.shadowed, +:root body.vtt.game.system-eunos-blades #players #chat .opposition-creation-panel .roll-opposition-container input.shadowed { + pointer-events: auto !important; + font-family: "Fjalla One", sans-serif; + background: var(--blades-black-dark-fade-strong); + border-radius: 3px; + box-shadow: inset 0px 0px 3px var(--blades-black-dark), inset 0px 0px 3px var(--blades-black-dark), inset 0px 0px 3px var(--blades-black-dark), inset 0px 0px 3px var(--blades-black-dark); + font-size: 0.875rem; + color: var(--blades-white-bright); + height: 1.125rem; + transform-origin: 0% 50%; + flex-shrink: 1; + scale: 0.75 1; + width: 133.3333333333%; +} +:root body.vtt.game.system-eunos-blades #interface #chat .accordian-label, +:root body.vtt.game.system-eunos-blades #controls #chat .accordian-label, +:root body.vtt.game.system-eunos-blades #navigation #chat .accordian-label, +:root body.vtt.game.system-eunos-blades #hotbar #chat .accordian-label, +:root body.vtt.game.system-eunos-blades #players #chat .accordian-label { + position: relative; + filter: sepia(0.75); +} +:root body.vtt.game.system-eunos-blades #interface #chat .accordian-label .randomizer-trigger, +:root body.vtt.game.system-eunos-blades #controls #chat .accordian-label .randomizer-trigger, +:root body.vtt.game.system-eunos-blades #navigation #chat .accordian-label .randomizer-trigger, +:root body.vtt.game.system-eunos-blades #hotbar #chat .accordian-label .randomizer-trigger, +:root body.vtt.game.system-eunos-blades #players #chat .accordian-label .randomizer-trigger { + position: absolute; + text-indent: 0px; + left: 5px; + translate: 0 -50%; + top: 50%; + scale: 1; + color: var(--blades-black); + text-shadow: none; + transform-origin: 50% 50%; + transition: 0.25s; +} +:root body.vtt.game.system-eunos-blades #interface #chat .accordian-label .randomizer-trigger:hover, +:root body.vtt.game.system-eunos-blades #controls #chat .accordian-label .randomizer-trigger:hover, +:root body.vtt.game.system-eunos-blades #navigation #chat .accordian-label .randomizer-trigger:hover, +:root body.vtt.game.system-eunos-blades #hotbar #chat .accordian-label .randomizer-trigger:hover, +:root body.vtt.game.system-eunos-blades #players #chat .accordian-label .randomizer-trigger:hover { + scale: 1.2; + color: var(--blades-gold-bright); + text-shadow: 0px 0px 4px var(--blades-black-dark), 0px 0px 4px var(--blades-black-dark), 0px 0px 4px var(--blades-black-dark), 0px 0px 4px var(--blades-black-dark); +} +:root body.vtt.game.system-eunos-blades #interface #chat .accordian-label.accordian-label-small, +:root body.vtt.game.system-eunos-blades #controls #chat .accordian-label.accordian-label-small, +:root body.vtt.game.system-eunos-blades #navigation #chat .accordian-label.accordian-label-small, +:root body.vtt.game.system-eunos-blades #hotbar #chat .accordian-label.accordian-label-small, +:root body.vtt.game.system-eunos-blades #players #chat .accordian-label.accordian-label-small { + height: calc(0.75 * var(--header-height)); + line-height: calc(0.8 * var(--header-height)); + width: calc(100% - 10px); + margin-left: 10px; + filter: none; +} +:root body.vtt.game.system-eunos-blades #interface #chat .accordian-label.accordian-label-small .randomizer-trigger, +:root body.vtt.game.system-eunos-blades #controls #chat .accordian-label.accordian-label-small .randomizer-trigger, +:root body.vtt.game.system-eunos-blades #navigation #chat .accordian-label.accordian-label-small .randomizer-trigger, +:root body.vtt.game.system-eunos-blades #hotbar #chat .accordian-label.accordian-label-small .randomizer-trigger, +:root body.vtt.game.system-eunos-blades #players #chat .accordian-label.accordian-label-small .randomizer-trigger { + top: 60%; +} +:root body.vtt.game.system-eunos-blades #interface #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item, +:root body.vtt.game.system-eunos-blades #controls #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item, +:root body.vtt.game.system-eunos-blades #navigation #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item, +:root body.vtt.game.system-eunos-blades #hotbar #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item, +:root body.vtt.game.system-eunos-blades #players #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item { + position: relative; +} +:root body.vtt.game.system-eunos-blades #interface #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item:not(:last-child), +:root body.vtt.game.system-eunos-blades #controls #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item:not(:last-child), +:root body.vtt.game.system-eunos-blades #navigation #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item:not(:last-child), +:root body.vtt.game.system-eunos-blades #hotbar #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item:not(:last-child), +:root body.vtt.game.system-eunos-blades #players #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item:not(:last-child) { + border-bottom: 1px dotted var(--blades-white); +} +:root body.vtt.game.system-eunos-blades #interface #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item:not(.randomizer-item-locked) .randomizer-input, +:root body.vtt.game.system-eunos-blades #controls #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item:not(.randomizer-item-locked) .randomizer-input, +:root body.vtt.game.system-eunos-blades #navigation #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item:not(.randomizer-item-locked) .randomizer-input, +:root body.vtt.game.system-eunos-blades #hotbar #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item:not(.randomizer-item-locked) .randomizer-input, +:root body.vtt.game.system-eunos-blades #players #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item:not(.randomizer-item-locked) .randomizer-input { + background: transparent; + box-shadow: none; +} +:root body.vtt.game.system-eunos-blades #interface #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item *:not(i), +:root body.vtt.game.system-eunos-blades #controls #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item *:not(i), +:root body.vtt.game.system-eunos-blades #navigation #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item *:not(i), +:root body.vtt.game.system-eunos-blades #hotbar #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item *:not(i), +:root body.vtt.game.system-eunos-blades #players #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item *:not(i) { + font-family: "Minion Pro Cond", serif; +} +:root body.vtt.game.system-eunos-blades #interface #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item textarea, +:root body.vtt.game.system-eunos-blades #controls #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item textarea, +:root body.vtt.game.system-eunos-blades #navigation #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item textarea, +:root body.vtt.game.system-eunos-blades #hotbar #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item textarea, +:root body.vtt.game.system-eunos-blades #players #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item textarea { + resize: none; + min-height: 2.5rem; + padding: 3px; + text-indent: 0; + font-size: 10px; + overflow: hidden; +} +:root body.vtt.game.system-eunos-blades #interface #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item ul, +:root body.vtt.game.system-eunos-blades #controls #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item ul, +:root body.vtt.game.system-eunos-blades #navigation #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item ul, +:root body.vtt.game.system-eunos-blades #hotbar #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item ul, +:root body.vtt.game.system-eunos-blades #players #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item ul { + font-size: 10px; + margin: 0 0 0 10px; + padding: 0; + list-style: none; +} +:root body.vtt.game.system-eunos-blades #interface #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item ul li::before, +:root body.vtt.game.system-eunos-blades #controls #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item ul li::before, +:root body.vtt.game.system-eunos-blades #navigation #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item ul li::before, +:root body.vtt.game.system-eunos-blades #hotbar #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item ul li::before, +:root body.vtt.game.system-eunos-blades #players #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item ul li::before { + content: "● "; + margin-left: -9px; +} +:root body.vtt.game.system-eunos-blades #interface #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item .randomizer-input-title, +:root body.vtt.game.system-eunos-blades #controls #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item .randomizer-input-title, +:root body.vtt.game.system-eunos-blades #navigation #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item .randomizer-input-title, +:root body.vtt.game.system-eunos-blades #hotbar #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item .randomizer-input-title, +:root body.vtt.game.system-eunos-blades #players #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item .randomizer-input-title { + max-width: calc(100% - 25px); + margin-left: 25px; +} +:root body.vtt.game.system-eunos-blades #interface #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item .toggle-icon, +:root body.vtt.game.system-eunos-blades #controls #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item .toggle-icon, +:root body.vtt.game.system-eunos-blades #navigation #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item .toggle-icon, +:root body.vtt.game.system-eunos-blades #hotbar #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item .toggle-icon, +:root body.vtt.game.system-eunos-blades #players #chat .randomizer-panel .randomizer-container .randomizer-list .randomizer-item .toggle-icon { + position: absolute; + cursor: pointer; + left: 0; + bottom: unset; + right: unset; + top: 0; + pointer-events: auto; + z-index: 20; +} +:root body.vtt.game.system-eunos-blades #interface #chat, +:root body.vtt.game.system-eunos-blades #interface #chat *, +:root body.vtt.game.system-eunos-blades #controls #chat, +:root body.vtt.game.system-eunos-blades #controls #chat *, +:root body.vtt.game.system-eunos-blades #navigation #chat, +:root body.vtt.game.system-eunos-blades #navigation #chat *, +:root body.vtt.game.system-eunos-blades #hotbar #chat, +:root body.vtt.game.system-eunos-blades #hotbar #chat *, +:root body.vtt.game.system-eunos-blades #players #chat, +:root body.vtt.game.system-eunos-blades #players #chat * { + --font-primary: "Minion Pro"; + --font-heading: "Kirsty"; + --font-weight-heading: normal; + --text-shadow-heading: none; + --line-height-heading: 1.2; +} +:root body.vtt.game.system-eunos-blades #interface #chat .chat-message, +:root body.vtt.game.system-eunos-blades #controls #chat .chat-message, +:root body.vtt.game.system-eunos-blades #navigation #chat .chat-message, +:root body.vtt.game.system-eunos-blades #hotbar #chat .chat-message, +:root body.vtt.game.system-eunos-blades #players #chat .chat-message { + background: var(--blades-black); +} +:root body.vtt.game.system-eunos-blades #interface #chat .chat-message .message-header, +:root body.vtt.game.system-eunos-blades #controls #chat .chat-message .message-header, +:root body.vtt.game.system-eunos-blades #navigation #chat .chat-message .message-header, +:root body.vtt.game.system-eunos-blades #hotbar #chat .chat-message .message-header, +:root body.vtt.game.system-eunos-blades #players #chat .chat-message .message-header { + position: relative; + z-index: 1; +} +:root body.vtt.game.system-eunos-blades #interface #chat .chat-message .message-header .message-sender, +:root body.vtt.game.system-eunos-blades #controls #chat .chat-message .message-header .message-sender, +:root body.vtt.game.system-eunos-blades #navigation #chat .chat-message .message-header .message-sender, +:root body.vtt.game.system-eunos-blades #hotbar #chat .chat-message .message-header .message-sender, +:root body.vtt.game.system-eunos-blades #players #chat .chat-message .message-header .message-sender { + color: var(--blades-grey); +} +:root body.vtt.game.system-eunos-blades #interface #chat .chat-message .message-content .chat-message-bg, +:root body.vtt.game.system-eunos-blades #controls #chat .chat-message .message-content .chat-message-bg, +:root body.vtt.game.system-eunos-blades #navigation #chat .chat-message .message-content .chat-message-bg, +:root body.vtt.game.system-eunos-blades #hotbar #chat .chat-message .message-content .chat-message-bg, +:root body.vtt.game.system-eunos-blades #players #chat .chat-message .message-content .chat-message-bg { + position: absolute; + top: 0; + left: 0; + height: 100%; + width: 100%; + z-index: 0; +} +:root body.vtt.game.system-eunos-blades #interface #chat .chat-message .message-content .chat-message-bg.roll-position-risky, +:root body.vtt.game.system-eunos-blades #controls #chat .chat-message .message-content .chat-message-bg.roll-position-risky, +:root body.vtt.game.system-eunos-blades #navigation #chat .chat-message .message-content .chat-message-bg.roll-position-risky, +:root body.vtt.game.system-eunos-blades #hotbar #chat .chat-message .message-content .chat-message-bg.roll-position-risky, +:root body.vtt.game.system-eunos-blades #players #chat .chat-message .message-content .chat-message-bg.roll-position-risky { + background-image: url("../assets/animations/chat/roll-position-risky.webp"); + background-size: cover; +} +:root body.vtt.game.system-eunos-blades #interface #chat .chat-message .message-content .blades-roll, +:root body.vtt.game.system-eunos-blades #controls #chat .chat-message .message-content .blades-roll, +:root body.vtt.game.system-eunos-blades #navigation #chat .chat-message .message-content .blades-roll, +:root body.vtt.game.system-eunos-blades #hotbar #chat .chat-message .message-content .blades-roll, +:root body.vtt.game.system-eunos-blades #players #chat .chat-message .message-content .blades-roll { + position: relative; + z-index: 2; +} +:root body.vtt.game.system-eunos-blades #interface #chat .chat-message .message-content .blades-roll .chat-header, +:root body.vtt.game.system-eunos-blades #controls #chat .chat-message .message-content .blades-roll .chat-header, +:root body.vtt.game.system-eunos-blades #navigation #chat .chat-message .message-content .blades-roll .chat-header, +:root body.vtt.game.system-eunos-blades #hotbar #chat .chat-message .message-content .blades-roll .chat-header, +:root body.vtt.game.system-eunos-blades #players #chat .chat-message .message-content .blades-roll .chat-header { + margin: 0; + padding: 0; + background: transparent; + box-shadow: none; + color: var(--blades-grey); +} +:root body.vtt.game.system-eunos-blades #interface #chat .chat-message .message-content .blades-roll h1.chat-header, +:root body.vtt.game.system-eunos-blades #controls #chat .chat-message .message-content .blades-roll h1.chat-header, +:root body.vtt.game.system-eunos-blades #navigation #chat .chat-message .message-content .blades-roll h1.chat-header, +:root body.vtt.game.system-eunos-blades #hotbar #chat .chat-message .message-content .blades-roll h1.chat-header, +:root body.vtt.game.system-eunos-blades #players #chat .chat-message .message-content .blades-roll h1.chat-header { + margin: 0; + padding: 0; + text-align: center; + background: transparent; + box-shadow: none; + font-size: 32px; + color: var(--blades-gold); +} +:root body.vtt.game.system-eunos-blades #interface #chat .chat-message .message-content .blades-roll .roll-states, +:root body.vtt.game.system-eunos-blades #controls #chat .chat-message .message-content .blades-roll .roll-states, +:root body.vtt.game.system-eunos-blades #navigation #chat .chat-message .message-content .blades-roll .roll-states, +:root body.vtt.game.system-eunos-blades #hotbar #chat .chat-message .message-content .blades-roll .roll-states, +:root body.vtt.game.system-eunos-blades #players #chat .chat-message .message-content .blades-roll .roll-states { + justify-content: space-between; + padding: 0 20px; +} +:root body.vtt.game.system-eunos-blades #interface #chat .chat-message .message-content .blades-roll .roll-states .roll-state-container, +:root body.vtt.game.system-eunos-blades #controls #chat .chat-message .message-content .blades-roll .roll-states .roll-state-container, +:root body.vtt.game.system-eunos-blades #navigation #chat .chat-message .message-content .blades-roll .roll-states .roll-state-container, +:root body.vtt.game.system-eunos-blades #hotbar #chat .chat-message .message-content .blades-roll .roll-states .roll-state-container, +:root body.vtt.game.system-eunos-blades #players #chat .chat-message .message-content .blades-roll .roll-states .roll-state-container { + flex-basis: 30%; + flex-grow: 0; + flex-shrink: 0; +} +:root body.vtt.game.system-eunos-blades #interface #chat .chat-message .message-content .blades-roll .roll-states h4.roll-state-label, +:root body.vtt.game.system-eunos-blades #controls #chat .chat-message .message-content .blades-roll .roll-states h4.roll-state-label, +:root body.vtt.game.system-eunos-blades #navigation #chat .chat-message .message-content .blades-roll .roll-states h4.roll-state-label, +:root body.vtt.game.system-eunos-blades #hotbar #chat .chat-message .message-content .blades-roll .roll-states h4.roll-state-label, +:root body.vtt.game.system-eunos-blades #players #chat .chat-message .message-content .blades-roll .roll-states h4.roll-state-label { + font-family: var(--font-primary); + font-size: 12px; + display: block; + width: 100%; + text-align: center; + white-space: nowrap; + color: var(--blades-grey); + margin-top: -4px; +} +:root body.vtt.game.system-eunos-blades #interface #chat .chat-message .message-content .blades-roll .roll-states h3.roll-state, +:root body.vtt.game.system-eunos-blades #controls #chat .chat-message .message-content .blades-roll .roll-states h3.roll-state, +:root body.vtt.game.system-eunos-blades #navigation #chat .chat-message .message-content .blades-roll .roll-states h3.roll-state, +:root body.vtt.game.system-eunos-blades #hotbar #chat .chat-message .message-content .blades-roll .roll-states h3.roll-state, +:root body.vtt.game.system-eunos-blades #players #chat .chat-message .message-content .blades-roll .roll-states h3.roll-state { + white-space: nowrap; +} +:root body.vtt.game.system-eunos-blades #interface #chat .chat-message .message-content .blades-roll h2.chat-header.roll-position, +:root body.vtt.game.system-eunos-blades #controls #chat .chat-message .message-content .blades-roll h2.chat-header.roll-position, +:root body.vtt.game.system-eunos-blades #navigation #chat .chat-message .message-content .blades-roll h2.chat-header.roll-position, +:root body.vtt.game.system-eunos-blades #hotbar #chat .chat-message .message-content .blades-roll h2.chat-header.roll-position, +:root body.vtt.game.system-eunos-blades #players #chat .chat-message .message-content .blades-roll h2.chat-header.roll-position { + text-align: right; + font-family: var(--font-primary); +} +:root body.vtt.game.system-eunos-blades #interface #chat .chat-message .message-content .blades-roll h2.chat-header.roll-position .position-text, +:root body.vtt.game.system-eunos-blades #controls #chat .chat-message .message-content .blades-roll h2.chat-header.roll-position .position-text, +:root body.vtt.game.system-eunos-blades #navigation #chat .chat-message .message-content .blades-roll h2.chat-header.roll-position .position-text, +:root body.vtt.game.system-eunos-blades #hotbar #chat .chat-message .message-content .blades-roll h2.chat-header.roll-position .position-text, +:root body.vtt.game.system-eunos-blades #players #chat .chat-message .message-content .blades-roll h2.chat-header.roll-position .position-text { + margin: 0 15px; + transform-origin: 50% 50%; + scale: 1.5; + display: inline-block; + font-family: var(--font-emphasis); +} +:root body.vtt.game.system-eunos-blades #interface #chat .chat-message .message-content .blades-roll .dice-roll-strip, +:root body.vtt.game.system-eunos-blades #controls #chat .chat-message .message-content .blades-roll .dice-roll-strip, +:root body.vtt.game.system-eunos-blades #navigation #chat .chat-message .message-content .blades-roll .dice-roll-strip, +:root body.vtt.game.system-eunos-blades #hotbar #chat .chat-message .message-content .blades-roll .dice-roll-strip, +:root body.vtt.game.system-eunos-blades #players #chat .chat-message .message-content .blades-roll .dice-roll-strip { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + height: 30px; + width: 100%; + align-items: stretch; + justify-content: center; + gap: 10px; + margin: 10px 0; +} +:root body.vtt.game.system-eunos-blades #interface #chat .chat-message .message-content .blades-roll .dice-roll-strip .blades-die, +:root body.vtt.game.system-eunos-blades #controls #chat .chat-message .message-content .blades-roll .dice-roll-strip .blades-die, +:root body.vtt.game.system-eunos-blades #navigation #chat .chat-message .message-content .blades-roll .dice-roll-strip .blades-die, +:root body.vtt.game.system-eunos-blades #hotbar #chat .chat-message .message-content .blades-roll .dice-roll-strip .blades-die, +:root body.vtt.game.system-eunos-blades #players #chat .chat-message .message-content .blades-roll .dice-roll-strip .blades-die { + display: block; + height: 30px; +} +:root body.vtt.game.system-eunos-blades #interface #chat .chat-message .message-content .blades-roll .dice-roll-strip .blades-die img, +:root body.vtt.game.system-eunos-blades #controls #chat .chat-message .message-content .blades-roll .dice-roll-strip .blades-die img, +:root body.vtt.game.system-eunos-blades #navigation #chat .chat-message .message-content .blades-roll .dice-roll-strip .blades-die img, +:root body.vtt.game.system-eunos-blades #hotbar #chat .chat-message .message-content .blades-roll .dice-roll-strip .blades-die img, +:root body.vtt.game.system-eunos-blades #players #chat .chat-message .message-content .blades-roll .dice-roll-strip .blades-die img { + height: 30px; + width: 30px; + display: block; +} +:root body.vtt.game.system-eunos-blades #interface #chat .chat-message .message-content .blades-roll .dice-roll-strip .blades-die.blades-die-critical, +:root body.vtt.game.system-eunos-blades #controls #chat .chat-message .message-content .blades-roll .dice-roll-strip .blades-die.blades-die-critical, +:root body.vtt.game.system-eunos-blades #navigation #chat .chat-message .message-content .blades-roll .dice-roll-strip .blades-die.blades-die-critical, +:root body.vtt.game.system-eunos-blades #hotbar #chat .chat-message .message-content .blades-roll .dice-roll-strip .blades-die.blades-die-critical, +:root body.vtt.game.system-eunos-blades #players #chat .chat-message .message-content .blades-roll .dice-roll-strip .blades-die.blades-die-critical { + outline: 2px solid var(--blades-gold-bright); +} +:root body.vtt.game.system-eunos-blades #interface #chat .chat-message .message-content .blades-roll .dice-roll-strip .blades-die.blades-die-success, +:root body.vtt.game.system-eunos-blades #controls #chat .chat-message .message-content .blades-roll .dice-roll-strip .blades-die.blades-die-success, +:root body.vtt.game.system-eunos-blades #navigation #chat .chat-message .message-content .blades-roll .dice-roll-strip .blades-die.blades-die-success, +:root body.vtt.game.system-eunos-blades #hotbar #chat .chat-message .message-content .blades-roll .dice-roll-strip .blades-die.blades-die-success, +:root body.vtt.game.system-eunos-blades #players #chat .chat-message .message-content .blades-roll .dice-roll-strip .blades-die.blades-die-success { + outline: 2px solid var(--blades-white-bright); +} +:root body.vtt.game.system-eunos-blades #interface #chat .chat-message .message-content .blades-roll .dice-roll-strip .blades-die.blades-die-ghost img, +:root body.vtt.game.system-eunos-blades #controls #chat .chat-message .message-content .blades-roll .dice-roll-strip .blades-die.blades-die-ghost img, +:root body.vtt.game.system-eunos-blades #navigation #chat .chat-message .message-content .blades-roll .dice-roll-strip .blades-die.blades-die-ghost img, +:root body.vtt.game.system-eunos-blades #hotbar #chat .chat-message .message-content .blades-roll .dice-roll-strip .blades-die.blades-die-ghost img, +:root body.vtt.game.system-eunos-blades #players #chat .chat-message .message-content .blades-roll .dice-roll-strip .blades-die.blades-die-ghost img { + opacity: 0.5; +} +:root body.vtt.game.system-eunos-blades #interface #chat .chat-message .message-content .blades-roll .dice-roll-strip .blades-die.blades-die-resistance, +:root body.vtt.game.system-eunos-blades #controls #chat .chat-message .message-content .blades-roll .dice-roll-strip .blades-die.blades-die-resistance, +:root body.vtt.game.system-eunos-blades #navigation #chat .chat-message .message-content .blades-roll .dice-roll-strip .blades-die.blades-die-resistance, +:root body.vtt.game.system-eunos-blades #hotbar #chat .chat-message .message-content .blades-roll .dice-roll-strip .blades-die.blades-die-resistance, +:root body.vtt.game.system-eunos-blades #players #chat .chat-message .message-content .blades-roll .dice-roll-strip .blades-die.blades-die-resistance { + outline: 2px solid var(--blades-blue-bright); +} +:root body.vtt.game.system-eunos-blades #interface #chat .chat-message .message-content .chat-label, :root body.vtt.game.system-eunos-blades #interface #chat .chat-message .message-content .chat-trait-label, +:root body.vtt.game.system-eunos-blades #controls #chat .chat-message .message-content .chat-label, +:root body.vtt.game.system-eunos-blades #controls #chat .chat-message .message-content .chat-trait-label, +:root body.vtt.game.system-eunos-blades #navigation #chat .chat-message .message-content .chat-label, +:root body.vtt.game.system-eunos-blades #navigation #chat .chat-message .message-content .chat-trait-label, +:root body.vtt.game.system-eunos-blades #hotbar #chat .chat-message .message-content .chat-label, +:root body.vtt.game.system-eunos-blades #hotbar #chat .chat-message .message-content .chat-trait-label, +:root body.vtt.game.system-eunos-blades #players #chat .chat-message .message-content .chat-label, +:root body.vtt.game.system-eunos-blades #players #chat .chat-message .message-content .chat-trait-label { + background-color: var(--blades-grey-bright); + font-family: var(--font-emphasis); + color: var(--blades-white); + font-size: 21px; + text-align: center; + padding: 0px 5px; + height: 30px; + text-transform: capitalize; +} +:root body.vtt.game.system-eunos-blades #interface #chat .chat-message .message-content .chat-label-small, +:root body.vtt.game.system-eunos-blades #controls #chat .chat-message .message-content .chat-label-small, +:root body.vtt.game.system-eunos-blades #navigation #chat .chat-message .message-content .chat-label-small, +:root body.vtt.game.system-eunos-blades #hotbar #chat .chat-message .message-content .chat-label-small, +:root body.vtt.game.system-eunos-blades #players #chat .chat-message .message-content .chat-label-small { + background-color: var(--blades-grey); + color: var(--blades-black); + font-size: small; + text-align: center; + padding: 3px 5px; + height: 20px; +} +:root body.vtt.game.system-eunos-blades #interface #chat .chat-message .message-content .label-stripe-chat, +:root body.vtt.game.system-eunos-blades #controls #chat .chat-message .message-content .label-stripe-chat, +:root body.vtt.game.system-eunos-blades #navigation #chat .chat-message .message-content .label-stripe-chat, +:root body.vtt.game.system-eunos-blades #hotbar #chat .chat-message .message-content .label-stripe-chat, +:root body.vtt.game.system-eunos-blades #players #chat .chat-message .message-content .label-stripe-chat { + text-transform: uppercase; + background-color: var(--blades-black); + color: var(--blades-white); + position: relative; + padding-top: 3px; + display: flex; + font-weight: bold; + margin: 0; +} +:root body.vtt.game.system-eunos-blades #interface #chat .chat-message .message-content .label-stripe-chat-small, +:root body.vtt.game.system-eunos-blades #controls #chat .chat-message .message-content .label-stripe-chat-small, +:root body.vtt.game.system-eunos-blades #navigation #chat .chat-message .message-content .label-stripe-chat-small, +:root body.vtt.game.system-eunos-blades #hotbar #chat .chat-message .message-content .label-stripe-chat-small, +:root body.vtt.game.system-eunos-blades #players #chat .chat-message .message-content .label-stripe-chat-small { + text-transform: capitalize; + background-color: var(--blades-grey); + color: var(--blades-black); + margin-bottom: 10px; + position: relative; + padding-top: 3px; + display: flex; font-weight: bold; } :root body.vtt.game.system-eunos-blades #interface #chat .chat-message .message-content .blades-die-tooltip .die, @@ -11260,13 +13511,9 @@ template { } :root body.vtt.game.system-eunos-blades .app.window-app .comp.consequence-display-container { --container-height: 40px; - --container-left-shift: 50px; - --csq-icon-dark: var(--blades-red-dark); - --csq-icon-med: var(--blades-red); - --csq-icon-bright: var(--blades-red-bright); - --csq-type-bg: var(--csq-icon-dark); - --csq-type-color: var(--blades-black-dark); + --container-left-shift: 70px; --csq-icon-bg-color: var(--blades-black-dark); + --csq-type-bg: var(--csq-icon-dark); --csq-button-size-mult: 0.33; position: relative; display: block; @@ -11274,31 +13521,116 @@ template { max-height: var(--container-height); min-height: var(--container-height); } +@keyframes anim-glow { + 0% { + box-shadow: 0 0 0px var(--blades-red-bright); + background-color: var(--blades-red-darkest); + } + 10% { + background-color: var(--blades-red-bright); + } + 100% { + box-shadow: 0 0 10px 10px transparent; + background-color: var(--blades-red-darkest); + } +} +@keyframes icon-glow { + 0% { + filter: brightness(1); + } + 10% { + filter: brightness(1); + } + 100% { + filter: brightness(1); + } +} +@keyframes icon-red-pulse { + 0% { + scale: 1; + fill: var(--blades-grey); + } + 10% { + scale: 1.1; + fill: var(--blades-red-bright); + } + 100% { + scale: 1; + fill: var(--blades-grey); + } +} +:root body.vtt.game.system-eunos-blades .app.window-app .comp.consequence-display-container .base-consequence { + --csq-icon-dark: var(--blades-black); + --csq-icon-med: var(--blades-grey); + --csq-icon-bright: var(--blades-white); + --csq-type-color: var(--blades-grey); + --csq-name-color: var(--blades-white); +} +:root body.vtt.game.system-eunos-blades .app.window-app .comp.consequence-display-container .accept-consequence { + opacity: 0; + --csq-icon-dark: var(--blades-red-dark); + --csq-icon-med: var(--blades-red); + --csq-icon-bright: var(--blades-red-bright); + --csq-type-color: var(--blades-black-dark); + --csq-name-color: var(--blades-red); +} :root body.vtt.game.system-eunos-blades .app.window-app .comp.consequence-display-container .resist-consequence { + opacity: 0; --csq-icon-dark: var(--blades-gold-dark); --csq-icon-med: var(--blades-gold); --csq-icon-bright: var(--blades-gold-bright); --csq-type-color: var(--blades-gold-dark); --csq-name-color: var(--blades-gold-bright); } +:root body.vtt.game.system-eunos-blades .app.window-app .comp.consequence-display-container .special-armor-consequence { + opacity: 0; + --csq-icon-dark: var(--blades-blue-dark); + --csq-icon-med: var(--blades-blue); + --csq-icon-bright: var(--blades-blue-bright); + --csq-type-color: var(--blades-blue-dark); + --csq-name-color: var(--blades-blue-bright); +} +:root body.vtt.game.system-eunos-blades .app.window-app .comp.consequence-display-container .consequence-interaction-pad { + display: none; + display: block; + position: absolute; + z-index: 2; + pointer-events: auto; + height: 100%; + top: 0; +} +:root body.vtt.game.system-eunos-blades .app.window-app .comp.consequence-display-container .consequence-interaction-pad.accept-consequence-pad { + --pad-left-shift: calc(var(--container-left-shift) + (var(--container-height))); + left: var(--pad-left-shift); + width: calc(100% - var(--pad-left-shift)); +} +:root body.vtt.game.system-eunos-blades .app.window-app .comp.consequence-display-container .consequence-interaction-pad.resist-consequence-pad, :root body.vtt.game.system-eunos-blades .app.window-app .comp.consequence-display-container .consequence-interaction-pad.special-armor-consequence-pad { + left: 0; + width: calc(var(--container-left-shift) - var(--container-height) * 0); +} +:root body.vtt.game.system-eunos-blades .app.window-app .comp.consequence-display-container .consequence-interaction-pad.special-armor-consequence-pad { + height: 40%; + z-index: 3; +} :root body.vtt.game.system-eunos-blades .app.window-app .comp.consequence-display-container .consequence-icon-container { position: relative; - height: var(--container-height); - width: var(--container-height); - min-width: var(--container-height); - max-width: var(--container-height); - min-height: var(--container-height); - max-height: var(--container-height); + height: calc(var(--container-height) * 0.75); + max-width: calc(var(--container-height) * 0.75); background: transparent; left: var(--container-left-shift); z-index: 1; + pointer-events: auto; + transition: 0.2s; +} +:root body.vtt.game.system-eunos-blades .app.window-app .comp.consequence-display-container .consequence-icon-container:hover { + filter: brightness(2); } :root body.vtt.game.system-eunos-blades .app.window-app .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle { position: absolute; translate: -50% -50%; - transform-origin: 50% 50%; - top: 50%; - left: 50%; + transform-origin: 100% 0%; + top: calc(var(--container-height) * 0.5); + left: calc(var(--container-height) * 0.5); border-radius: 50%; height: var(--container-height); width: var(--container-height); @@ -11329,6 +13661,24 @@ template { :root body.vtt.game.system-eunos-blades .app.window-app .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle svg .fill-linear { fill: var(--csq-icon-med); } +:root body.vtt.game.system-eunos-blades .app.window-app .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle svg path { + transform-origin: 50% 50%; +} +:root body.vtt.game.system-eunos-blades .app.window-app .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence { + scale: 0.75; + animation: icon-glow 2s ease infinite; + pointer-events: auto; +} +:root body.vtt.game.system-eunos-blades .app.window-app .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence svg path { + animation: icon-red-pulse 2s ease infinite; +} +:root body.vtt.game.system-eunos-blades .app.window-app .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence:hover { + filter: brightness(2) !important; + animation: none; +} +:root body.vtt.game.system-eunos-blades .app.window-app .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.resist-consequence, :root body.vtt.game.system-eunos-blades .app.window-app .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.special-armor-consequence { + outline-width: 2px; +} :root body.vtt.game.system-eunos-blades .app.window-app .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle .consequence-icon { height: 100%; } @@ -11338,17 +13688,21 @@ template { display: flex; flex-direction: row; flex-wrap: nowrap; - bottom: 0px; + bottom: -10px; } :root body.vtt.game.system-eunos-blades .app.window-app .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg { position: absolute; z-index: -1; height: 100%; width: calc(100% + 24px); + transform-origin: 0% 50%; top: 0px; background: var(--csq-icon-bright); display: block; } +:root body.vtt.game.system-eunos-blades .app.window-app .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg.consequence-resist-button-bg, :root body.vtt.game.system-eunos-blades .app.window-app .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg.consequence-special-armor-button-bg { + transform-origin: 100% 50%; +} :root body.vtt.game.system-eunos-blades .app.window-app .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-label { position: relative; z-index: 1; @@ -11356,6 +13710,9 @@ template { font-size: 10px; line-height: 14px; color: var(--blades-grey); + font-weight: 800; + text-shadow: 0px 0px 1px var(--blades-black-dark); + letter-spacing: 1; text-transform: uppercase; white-space: nowrap; } @@ -11372,14 +13729,14 @@ template { margin: 0; } :root body.vtt.game.system-eunos-blades .app.window-app .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-resist-button-container { - right: calc(100% - 3px); + right: 100%; } :root body.vtt.game.system-eunos-blades .app.window-app .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-resist-button-container .consequence-button-bg { left: -7px; transform: skewX(45deg); } :root body.vtt.game.system-eunos-blades .app.window-app .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-accept-button-container { - left: 100%; + left: 140%; } :root body.vtt.game.system-eunos-blades .app.window-app .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-accept-button-container .consequence-button-bg { right: -7px; @@ -11394,7 +13751,7 @@ template { height: calc(var(--container-height) * 0.33); transform-origin: 0% 50%; left: calc(var(--container-height) + var(--container-left-shift) - 10px); - top: 0px; + top: -2px; padding: 0 5px 0 15px; } :root body.vtt.game.system-eunos-blades .app.window-app .comp.consequence-display-container .consequence-type-container .consequence-type-bg { @@ -11403,20 +13760,21 @@ template { left: -20px; height: 100%; width: 170px; + transform-origin: 0% 50%; transform: skewX(-45deg); - background: var(--csq-type-bg); + background: var(--csq-icon-dark); } :root body.vtt.game.system-eunos-blades .app.window-app .comp.consequence-display-container .consequence-type-container .consequence-type { position: absolute; top: 0; - left: 10px; + transform-origin: 0% 50%; white-space: nowrap; font-family: Oswald, sans-serif; text-transform: uppercase; text-align: right; font-size: 10px; color: var(--csq-type-color); - font-weight: bold; + font-weight: normal; } :root body.vtt.game.system-eunos-blades .app.window-app .comp.consequence-display-container .consequence-name-container { position: absolute; @@ -11432,9 +13790,10 @@ template { z-index: 1; padding: 0 5px 0 35px; font-size: 14px; - line-height: calc(var(--container-height) * 0.55); + line-height: 17px; font-family: Kirsty, serif; font-variant: small-caps; + transform-origin: 0% 50%; color: var(--csq-icon-bright); font-style: italic; } @@ -11444,7 +13803,7 @@ template { :root body.vtt.game.system-eunos-blades .app.window-app .comp.consequence-display-container .consequence-footer-container { position: absolute; height: calc(var(--container-height) * var(--csq-button-size-mult)); - width: 120px; + width: auto; bottom: 0; top: unset; left: calc(var(--container-height) + var(--container-left-shift) - 20px); @@ -11458,8 +13817,17 @@ template { background: var(--csq-icon-bright); display: block; transform: skewX(45deg); + transform-origin: 0% 50%; +} +:root body.vtt.game.system-eunos-blades .app.window-app .comp.consequence-display-container .consequence-footer-container .consequence-footer-bg.resist-consequence { + width: 120px; } -:root body.vtt.game.system-eunos-blades .app.window-app .comp.consequence-display-container .consequence-footer-container .consequence-resist-attribute { +:root body.vtt.game.system-eunos-blades .app.window-app .comp.consequence-display-container .consequence-footer-container .consequence-footer-bg.special-armor-consequence { + width: 250px; +} +:root body.vtt.game.system-eunos-blades .app.window-app .comp.consequence-display-container .consequence-footer-container .consequence-footer-message { + position: absolute; + white-space: nowrap; font-family: Oswald, sans-serif; font-weight: bold; color: var(--blades-black-dark); @@ -11467,6 +13835,7 @@ template { line-height: 14px; padding-left: 25px; justify-content: flex-start; + transform-origin: 0% 50%; gap: 5px; } :root body.vtt.game.system-eunos-blades .app.window-app .comp.consequence-display-container .consequence-footer-container .dotline { @@ -11959,7 +14328,7 @@ template { color: var(--blades-white-bright) !important; } :root body.vtt.game.system-eunos-blades .app.window-app .cyan-bright { - color: var(--blades-cyan-bright) !important; + color: var(--blades-blue-bright) !important; } :root body.vtt.game.system-eunos-blades .app.window-app .uppercase { text-transform: uppercase !important; @@ -13880,7 +16249,7 @@ template { color: var(--blades-white-bright) !important; } :root body.vtt.game.system-eunos-blades .app.window-app.sheet .window-content form .cyan-bright { - color: var(--blades-cyan-bright) !important; + color: var(--blades-blue-bright) !important; } :root body.vtt.game.system-eunos-blades .app.window-app.sheet .window-content form .uppercase { text-transform: uppercase !important; @@ -16919,7 +19288,7 @@ template { opacity: 1; } :root body.vtt.game.system-eunos-blades .app.window-app.sheet.dialog .window-content .dialog-content .nav-group .tab[data-tab] .comp.fine-quality .comp-body .comp-title { - color: var(--blades-cyan); + color: var(--blades-blue); } :root body.vtt.game.system-eunos-blades .app.window-app.sheet.dialog .window-content .dialog-content .nav-group .tab[data-tab] .comp.unaffordable { order: 2; @@ -16990,7 +19359,7 @@ template { background: var(--section-bg-color); } :root body.vtt.game.system-eunos-blades .app.window-app.sheet.dialog .window-content .consequence-section.consequence-section-controlled { - --section-bg-color: var(--blades-cyan-dark-fade) ; + --section-bg-color: var(--blades-blue-dark-fade) ; } :root body.vtt.game.system-eunos-blades .app.window-app.sheet.dialog .window-content .consequence-section.consequence-section-risky { --section-bg-color: transparent ; @@ -17011,7 +19380,7 @@ template { box-shadow: none; } :root body.vtt.game.system-eunos-blades .app.window-app.sheet.dialog .window-content .consequence-section h1.consequence-header-controlled { - --h1-color: var(--blades-cyan) ; + --h1-color: var(--blades-blue) ; } :root body.vtt.game.system-eunos-blades .app.window-app.sheet.dialog .window-content .consequence-section h1.consequence-header-risky { --h1-color: var(--blades-grey-bright) ; @@ -17552,9 +19921,9 @@ template { --final-block-border-color: var(--blades-red); } :root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .roll-sheet-float-block.final-block.position-final-block.position-controlled { - --final-block-text-color: var(--blades-cyan-bright); - --final-block-background-color: var(--blades-cyan-dark-fade); - --final-block-border-color: var(--blades-cyan); + --final-block-text-color: var(--blades-blue-bright); + --final-block-background-color: var(--blades-blue-dark-fade); + --final-block-border-color: var(--blades-blue); } :root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .roll-sheet-float-block.final-block.effect-final-block.effect-zero { --final-block-text-color: var(--blades-red-dark); @@ -17567,9 +19936,9 @@ template { --final-block-border-color: var(--blades-red); } :root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .roll-sheet-float-block.final-block.effect-final-block.effect-great { - --final-block-text-color: var(--blades-cyan-bright); - --final-block-background-color: var(--blades-cyan-dark-fade); - --final-block-border-color: var(--blades-cyan); + --final-block-text-color: var(--blades-blue-bright); + --final-block-background-color: var(--blades-blue-dark-fade); + --final-block-border-color: var(--blades-blue); } :root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .roll-sheet-float-block.final-block.effect-final-block.effect-extreme { --final-block-text-color: var(--blades-white-bright); @@ -17690,8 +20059,8 @@ template { :root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab.gm-roll-collab .window-content form .sheet-root .roll-sheet-block .roll-sheet-sub-block .position-controls-container .roll-mod-gm-control.gm-control-effect-great, :root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab.gm-roll-collab .window-content form .sheet-root .roll-sheet-block .roll-sheet-sub-block .position-controls-container .roll-mod-gm-control.gm-control-position-controlled, :root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab.gm-roll-collab .window-content form .sheet-root .roll-sheet-block .roll-sheet-sub-block .effect-controls-container .roll-mod-gm-control.gm-control-effect-great, :root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab.gm-roll-collab .window-content form .sheet-root .roll-sheet-block .roll-sheet-sub-block .effect-controls-container .roll-mod-gm-control.gm-control-position-controlled { - --gm-control-border: var(--blades-cyan-bright); - --gm-control-background: var(--blades-cyan-dark); + --gm-control-border: var(--blades-blue-bright); + --gm-control-background: var(--blades-blue-dark); } :root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab.gm-roll-collab .window-content form .sheet-root .roll-sheet-block .roll-sheet-sub-block .position-controls-container .roll-mod-gm-control.gm-control-effect-extreme, :root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab.gm-roll-collab .window-content form .sheet-root .roll-sheet-block .roll-sheet-sub-block .effect-controls-container .roll-mod-gm-control.gm-control-effect-extreme { @@ -17880,10 +20249,10 @@ template { --roll-type-header-shadow-color: var(--blades-black-dark); } :root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form .sheet-root .sheet-topper.roll-type.roll-type-fortune { - --roll-type-header-color: var(--blades-cyan-bright); - --roll-type-header-bg-color: var(--blades-cyan-dark); - --roll-type-header-underline-color: var(--blades-cyan-bright); - --roll-type-header-shadow-color: var(--blades-cyan-dark); + --roll-type-header-color: var(--blades-blue-bright); + --roll-type-header-bg-color: var(--blades-blue-dark); + --roll-type-header-underline-color: var(--blades-blue-bright); + --roll-type-header-shadow-color: var(--blades-blue-dark); } :root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form .sheet-root .sheet-topper.roll-type .roll-type-header { font-family: var(--font-emphasis); @@ -18585,7 +20954,7 @@ template { color: var(--blades-red-bright) !important; } :root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form .sheet-root section.sheet-footer .roll-sheet-float-block.roll-stress-block .cyan-bright, :root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form .sheet-root section.sheet-footer .roll-sheet-float-block.roll-stress-block .cyan-bright * { - color: var(--blades-cyan-bright) !important; + color: var(--blades-blue-bright) !important; } :root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab .window-content form .sheet-root section.sheet-footer .roll-sheet-float-block.roll-stress-block .tooltip.tooltip-roll-stress { font-family: var(--font-default); diff --git a/css/tinymce/content.min.css b/css/tinymce/content.min.css index f213d60f..847f80bc 100644 --- a/css/tinymce/content.min.css +++ b/css/tinymce/content.min.css @@ -109,22 +109,63 @@ html, :root { --blades-grey-dark-nums: 68, 68, 68; --blades-black-nums: 32, 32, 32; --blades-black-dark-nums: 0, 0, 0; - --blades-gold-bright-nums: 255, 231, 92; - --blades-gold-nums: 255, 215, 0; - --blades-gold-dark-nums: 184, 156, 0; - --blades-gold-darkest-nums: 55, 53, 0; - --blades-red-bright-nums: 220, 20, 60; - --blades-red-nums: 204, 0, 0; - --blades-red-dark-nums: 122, 0, 0; + --blades-gold-bright-nums: 206,180, 71; + --blades-gold-nums: 143,118, 11; + --blades-gold-dark-nums: 105, 86, 0; + --blades-gold-darkest-nums: 64, 52, 0; + --blades-red-bright-nums: 255, 0, 0; + --blades-red-nums: 200, 0, 0; + --blades-red-dark-nums: 150, 0, 0; --blades-red-darkest-nums: 50, 0, 0; --blades-green-bright-nums: 20, 220, 60; --blades-green-nums: 0, 204, 0; --blades-green-dark-nums: 0, 122, 0; --blades-green-darkest-nums: 0, 60, 0; - --blades-cyan-bright-nums: 198, 255, 255; - --blades-cyan-nums: 150, 255, 255; - --blades-cyan-dark-nums: 40, 120, 120; - --blades-cyan-darkest-nums: 25, 49, 49; + --blades-blue-bright-nums: 198, 255, 255; + --blades-blue-nums: 150, 255, 255; + --blades-blue-dark-nums: 40, 120, 120; + --blades-blue-darkest-nums: 25, 49, 49; + /* + NEW COLOR PALETTE OVERRIDE + + == GOLD == + http://paletton.com/#uid=11n0u0kNTr2qtG1K2DKRbkEVqcT + + shade 0 = #D7AF00 = rgb(215,175, 0) = rgba(215,175, 0,1) = rgb0(0.843,0.686,0) + shade 1 = #FFD82C = rgb(255,216, 44) = rgba(255,216, 44,1) = rgb0(1,0.847,0.173) + shade 2 = #FFCF00 = rgb(255,207, 0) = rgba(255,207, 0,1) = rgb0(1,0.812,0) + shade 3 = #A58600 = rgb(165,134, 0) = rgba(165,134, 0,1) = rgb0(0.647,0.525,0) + shade 4 = #675300 = rgb(103, 83, 0) = rgba(103, 83, 0,1) = rgb0(0.404,0.325,0)' + + == RED == + http://paletton.com/#uid=1000u0kTixTijNOwGQpTXmEXg9Y + shade 0 = #FF0000 = rgb(255, 0, 0) = rgba(255, 0, 0,1) = rgb0(1,0,0) + shade 1 = #FF6D6D = rgb(255,109,109) = rgba(255,109,109,1) = rgb0(1,0.427,0.427) + shade 2 = #FF0000 = rgb(255, 0, 0) = rgba(255, 0, 0,1) = rgb0(1,0,0) + shade 3 = #B40000 = rgb(180, 0, 0) = rgba(180, 0, 0,1) = rgb0(0.706,0,0) + shade 4 = #4F0000 = rgb( 79, 0, 0) = rgba( 79, 0, 0,1) = rgb0(0.31,0,0) + + == BLUE == + http://paletton.com/#uid=13i0u0kTixTodNREARdTRoAV1g4 + shade 0 = #009F9F = rgb( 0,159,159) = rgba( 0,159,159,1) = rgb0(0,0.624,0.624) + shade 1 = #34D5D5 = rgb( 52,213,213) = rgba( 52,213,213,1) = rgb0(0.204,0.835,0.835) + shade 2 = #00E0E0 = rgb( 0,224,224) = rgba( 0,224,224,1) = rgb0(0,0.878,0.878) + shade 3 = #007676 = rgb( 0,118,118) = rgba( 0,118,118,1) = rgb0(0,0.463,0.463) + shade 4 = #004D4D = rgb( 0, 77, 77) = rgba( 0, 77, 77,1) = rgb0(0,0.302,0.302) + */ + --blades-gold-bright-nums: 255,216, 44; + --blades-gold-nums: 215,175, 0; + --blades-gold-dark-nums: 165,134, 0; + --blades-gold-darkest-nums: 103, 83, 0; + /* --blades-red-bright-nums: 255,109,109; + --blades-red-nums: 255, 0, 0; + --blades-red-dark-nums: 180, 0, 0; + --blades-red-darkest-nums: 79, 0, 0; */ + --blades-blue-bright-nums: 0,224,224; + --blades-blue-nums: 52,213,213; + --blades-blue-dark-nums: 0,118,118; + --blades-blue-darkest-nums: 0, 77, 77; + /* END OVERRIDE */ --blades-white-bright: rgba(var(--blades-white-bright-nums), 1); --blades-white: rgba(var(--blades-white-nums), 1); --blades-grey-bright: rgba(var(--blades-grey-bright-nums), 1); @@ -132,6 +173,7 @@ html, :root { --blades-grey-dark: rgba(var(--blades-grey-dark-nums), 1); --blades-black: rgba(var(--blades-black-nums), 1); --blades-black-dark: rgba(var(--blades-black-dark-nums), 1); + --blades-gold-brightest: rgba(var(--blades-gold-brightest-nums), 1); --blades-gold-bright: rgba(var(--blades-gold-bright-nums), 1); --blades-gold: rgba(var(--blades-gold-nums), 1); --blades-gold-dark: rgba(var(--blades-gold-dark-nums), 1); @@ -144,10 +186,10 @@ html, :root { --blades-green: rgba(var(--blades-green-nums), 1); --blades-green-dark: rgba(var(--blades-green-dark-nums), 1); --blades-green-darkest: rgba(var(--blades-green-darkest-nums), 1); - --blades-cyan-bright: rgba(var(--blades-cyan-bright-nums), 1); - --blades-cyan: rgba(var(--blades-cyan-nums), 1); - --blades-cyan-dark: rgba(var(--blades-cyan-dark-nums), 1); - --blades-cyan-darkest: rgba(var(--blades-cyan-darkest-nums), 1); + --blades-blue-bright: rgba(var(--blades-blue-bright-nums), 1); + --blades-blue: rgba(var(--blades-blue-nums), 1); + --blades-blue-dark: rgba(var(--blades-blue-dark-nums), 1); + --blades-blue-darkest: rgba(var(--blades-blue-darkest-nums), 1); --blades-white-fade: rgba(var(--blades-white-nums), 0.5); --blades-white-fade-strong: rgba(var(--blades-white-nums), 0.25); --blades-white-bright-fade: rgba(var(--blades-white-bright-nums), 0.5); @@ -158,10 +200,10 @@ html, :root { --blades-black-dark-fade-strong: rgba(var(--blades-black-dark-nums), 0.25); --blades-red-dark-fade: rgba(var(--blades-red-dark-nums), 0.5); --blades-green-dark-fade: rgba(var(--blades-green-dark-nums), 0.5); - --blades-cyan-dark-fade: rgba(var(--blades-cyan-dark-nums), 0.5); + --blades-blue-dark-fade: rgba(var(--blades-blue-dark-nums), 0.5); --blades-red-dark-fade-strong: rgba(var(--blades-red-dark-nums), 0.25); --blades-green-dark-fade-strong: rgba(var(--blades-green-dark-nums), 0.25); - --blades-cyan-dark-fade-strong: rgba(var(--blades-cyan-dark-nums), 0.25); + --blades-blue-dark-fade-strong: rgba(var(--blades-blue-dark-nums), 0.25); --color-primary: var(--blades-white-nums); --color-background: var(--blades-black-nums); --color-background-lightest: var(--blades-grey-nums); @@ -1022,13 +1064,9 @@ html .comp.controls-container .controls-panel.active .controls-list li:hover > a } html .comp.consequence-display-container, :root .comp.consequence-display-container { --container-height: 40px; - --container-left-shift: 50px; - --csq-icon-dark: var(--blades-red-dark); - --csq-icon-med: var(--blades-red); - --csq-icon-bright: var(--blades-red-bright); - --csq-type-bg: var(--csq-icon-dark); - --csq-type-color: var(--blades-black-dark); + --container-left-shift: 70px; --csq-icon-bg-color: var(--blades-black-dark); + --csq-type-bg: var(--csq-icon-dark); --csq-button-size-mult: 0.33; position: relative; display: block; @@ -1036,31 +1074,116 @@ html .comp.consequence-display-container, :root .comp.consequence-display-contai max-height: var(--container-height); min-height: var(--container-height); } +@keyframes anim-glow { + 0% { + box-shadow: 0 0 0px var(--blades-red-bright); + background-color: var(--blades-red-darkest); + } + 10% { + background-color: var(--blades-red-bright); + } + 100% { + box-shadow: 0 0 10px 10px transparent; + background-color: var(--blades-red-darkest); + } +} +@keyframes icon-glow { + 0% { + filter: brightness(1); + } + 10% { + filter: brightness(1); + } + 100% { + filter: brightness(1); + } +} +@keyframes icon-red-pulse { + 0% { + scale: 1; + fill: var(--blades-grey); + } + 10% { + scale: 1.1; + fill: var(--blades-red-bright); + } + 100% { + scale: 1; + fill: var(--blades-grey); + } +} +html .comp.consequence-display-container .base-consequence, :root .comp.consequence-display-container .base-consequence { + --csq-icon-dark: var(--blades-black); + --csq-icon-med: var(--blades-grey); + --csq-icon-bright: var(--blades-white); + --csq-type-color: var(--blades-grey); + --csq-name-color: var(--blades-white); +} +html .comp.consequence-display-container .accept-consequence, :root .comp.consequence-display-container .accept-consequence { + opacity: 0; + --csq-icon-dark: var(--blades-red-dark); + --csq-icon-med: var(--blades-red); + --csq-icon-bright: var(--blades-red-bright); + --csq-type-color: var(--blades-black-dark); + --csq-name-color: var(--blades-red); +} html .comp.consequence-display-container .resist-consequence, :root .comp.consequence-display-container .resist-consequence { + opacity: 0; --csq-icon-dark: var(--blades-gold-dark); --csq-icon-med: var(--blades-gold); --csq-icon-bright: var(--blades-gold-bright); --csq-type-color: var(--blades-gold-dark); --csq-name-color: var(--blades-gold-bright); } +html .comp.consequence-display-container .special-armor-consequence, :root .comp.consequence-display-container .special-armor-consequence { + opacity: 0; + --csq-icon-dark: var(--blades-blue-dark); + --csq-icon-med: var(--blades-blue); + --csq-icon-bright: var(--blades-blue-bright); + --csq-type-color: var(--blades-blue-dark); + --csq-name-color: var(--blades-blue-bright); +} +html .comp.consequence-display-container .consequence-interaction-pad, :root .comp.consequence-display-container .consequence-interaction-pad { + display: none; + display: block; + position: absolute; + z-index: 2; + pointer-events: auto; + height: 100%; + top: 0; +} +html .comp.consequence-display-container .consequence-interaction-pad.accept-consequence-pad, :root .comp.consequence-display-container .consequence-interaction-pad.accept-consequence-pad { + --pad-left-shift: calc(var(--container-left-shift) + (var(--container-height))); + left: var(--pad-left-shift); + width: calc(100% - var(--pad-left-shift)); +} +html .comp.consequence-display-container .consequence-interaction-pad.resist-consequence-pad, html .comp.consequence-display-container .consequence-interaction-pad.special-armor-consequence-pad, :root .comp.consequence-display-container .consequence-interaction-pad.resist-consequence-pad, :root .comp.consequence-display-container .consequence-interaction-pad.special-armor-consequence-pad { + left: 0; + width: calc(var(--container-left-shift) - var(--container-height) * 0); +} +html .comp.consequence-display-container .consequence-interaction-pad.special-armor-consequence-pad, :root .comp.consequence-display-container .consequence-interaction-pad.special-armor-consequence-pad { + height: 40%; + z-index: 3; +} html .comp.consequence-display-container .consequence-icon-container, :root .comp.consequence-display-container .consequence-icon-container { position: relative; - height: var(--container-height); - width: var(--container-height); - min-width: var(--container-height); - max-width: var(--container-height); - min-height: var(--container-height); - max-height: var(--container-height); + height: calc(var(--container-height) * 0.75); + max-width: calc(var(--container-height) * 0.75); background: transparent; left: var(--container-left-shift); z-index: 1; + pointer-events: auto; + transition: 0.2s; +} +html .comp.consequence-display-container .consequence-icon-container:hover, :root .comp.consequence-display-container .consequence-icon-container:hover { + filter: brightness(2); } html .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle, :root .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle { position: absolute; translate: -50% -50%; - transform-origin: 50% 50%; - top: 50%; - left: 50%; + transform-origin: 100% 0%; + top: calc(var(--container-height) * 0.5); + left: calc(var(--container-height) * 0.5); border-radius: 50%; height: var(--container-height); width: var(--container-height); @@ -1091,6 +1214,24 @@ html .comp.consequence-display-container .consequence-icon-container .consequenc html .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle svg .fill-linear, :root .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle svg .fill-linear { fill: var(--csq-icon-med); } +html .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle svg path, :root .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle svg path { + transform-origin: 50% 50%; +} +html .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence, :root .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence { + scale: 0.75; + animation: icon-glow 2s ease infinite; + pointer-events: auto; +} +html .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence svg path, :root .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence svg path { + animation: icon-red-pulse 2s ease infinite; +} +html .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence:hover, :root .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence:hover { + filter: brightness(2) !important; + animation: none; +} +html .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.resist-consequence, html .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.special-armor-consequence, :root .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.resist-consequence, :root .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.special-armor-consequence { + outline-width: 2px; +} html .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle .consequence-icon, :root .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle .consequence-icon { height: 100%; } @@ -1100,17 +1241,21 @@ html .comp.consequence-display-container .consequence-icon-container .consequenc display: flex; flex-direction: row; flex-wrap: nowrap; - bottom: 0px; + bottom: -10px; } html .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg, :root .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg { position: absolute; z-index: -1; height: 100%; width: calc(100% + 24px); + transform-origin: 0% 50%; top: 0px; background: var(--csq-icon-bright); display: block; } +html .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg.consequence-resist-button-bg, html .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg.consequence-special-armor-button-bg, :root .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg.consequence-resist-button-bg, :root .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg.consequence-special-armor-button-bg { + transform-origin: 100% 50%; +} html .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-label, :root .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-label { position: relative; z-index: 1; @@ -1118,6 +1263,9 @@ html .comp.consequence-display-container .consequence-icon-container .consequenc font-size: 10px; line-height: 14px; color: var(--blades-grey); + font-weight: 800; + text-shadow: 0px 0px 1px var(--blades-black-dark); + letter-spacing: 1; text-transform: uppercase; white-space: nowrap; } @@ -1134,14 +1282,14 @@ html .comp.consequence-display-container .consequence-icon-container .consequenc margin: 0; } html .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-resist-button-container, :root .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-resist-button-container { - right: calc(100% - 3px); + right: 100%; } html .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-resist-button-container .consequence-button-bg, :root .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-resist-button-container .consequence-button-bg { left: -7px; transform: skewX(45deg); } html .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-accept-button-container, :root .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-accept-button-container { - left: 100%; + left: 140%; } html .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-accept-button-container .consequence-button-bg, :root .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-accept-button-container .consequence-button-bg { right: -7px; @@ -1156,7 +1304,7 @@ html .comp.consequence-display-container .consequence-type-container, :root .com height: calc(var(--container-height) * 0.33); transform-origin: 0% 50%; left: calc(var(--container-height) + var(--container-left-shift) - 10px); - top: 0px; + top: -2px; padding: 0 5px 0 15px; } html .comp.consequence-display-container .consequence-type-container .consequence-type-bg, :root .comp.consequence-display-container .consequence-type-container .consequence-type-bg { @@ -1165,20 +1313,21 @@ html .comp.consequence-display-container .consequence-type-container .consequenc left: -20px; height: 100%; width: 170px; + transform-origin: 0% 50%; transform: skewX(-45deg); - background: var(--csq-type-bg); + background: var(--csq-icon-dark); } html .comp.consequence-display-container .consequence-type-container .consequence-type, :root .comp.consequence-display-container .consequence-type-container .consequence-type { position: absolute; top: 0; - left: 10px; + transform-origin: 0% 50%; white-space: nowrap; font-family: Oswald, sans-serif; text-transform: uppercase; text-align: right; font-size: 10px; color: var(--csq-type-color); - font-weight: bold; + font-weight: normal; } html .comp.consequence-display-container .consequence-name-container, :root .comp.consequence-display-container .consequence-name-container { position: absolute; @@ -1194,9 +1343,10 @@ html .comp.consequence-display-container .consequence-name-container .consequenc z-index: 1; padding: 0 5px 0 35px; font-size: 14px; - line-height: calc(var(--container-height) * 0.55); + line-height: 17px; font-family: Kirsty, serif; font-variant: small-caps; + transform-origin: 0% 50%; color: var(--csq-icon-bright); font-style: italic; } @@ -1206,7 +1356,7 @@ html .comp.consequence-display-container .consequence-name-container .consequenc html .comp.consequence-display-container .consequence-footer-container, :root .comp.consequence-display-container .consequence-footer-container { position: absolute; height: calc(var(--container-height) * var(--csq-button-size-mult)); - width: 120px; + width: auto; bottom: 0; top: unset; left: calc(var(--container-height) + var(--container-left-shift) - 20px); @@ -1220,8 +1370,17 @@ html .comp.consequence-display-container .consequence-footer-container .conseque background: var(--csq-icon-bright); display: block; transform: skewX(45deg); + transform-origin: 0% 50%; +} +html .comp.consequence-display-container .consequence-footer-container .consequence-footer-bg.resist-consequence, :root .comp.consequence-display-container .consequence-footer-container .consequence-footer-bg.resist-consequence { + width: 120px; } -html .comp.consequence-display-container .consequence-footer-container .consequence-resist-attribute, :root .comp.consequence-display-container .consequence-footer-container .consequence-resist-attribute { +html .comp.consequence-display-container .consequence-footer-container .consequence-footer-bg.special-armor-consequence, :root .comp.consequence-display-container .consequence-footer-container .consequence-footer-bg.special-armor-consequence { + width: 250px; +} +html .comp.consequence-display-container .consequence-footer-container .consequence-footer-message, :root .comp.consequence-display-container .consequence-footer-container .consequence-footer-message { + position: absolute; + white-space: nowrap; font-family: Oswald, sans-serif; font-weight: bold; color: var(--blades-black-dark); @@ -1229,6 +1388,7 @@ html .comp.consequence-display-container .consequence-footer-container .conseque line-height: 14px; padding-left: 25px; justify-content: flex-start; + transform-origin: 0% 50%; gap: 5px; } html .comp.consequence-display-container .consequence-footer-container .dotline, :root .comp.consequence-display-container .consequence-footer-container .dotline { @@ -2521,13 +2681,9 @@ html .comp.controls-container .controls-panel.active .controls-list li:hover > a } html .comp.consequence-display-container, :root .comp.consequence-display-container { --container-height: 40px; - --container-left-shift: 50px; - --csq-icon-dark: var(--blades-red-dark); - --csq-icon-med: var(--blades-red); - --csq-icon-bright: var(--blades-red-bright); - --csq-type-bg: var(--csq-icon-dark); - --csq-type-color: var(--blades-black-dark); + --container-left-shift: 70px; --csq-icon-bg-color: var(--blades-black-dark); + --csq-type-bg: var(--csq-icon-dark); --csq-button-size-mult: 0.33; position: relative; display: block; @@ -2535,31 +2691,116 @@ html .comp.consequence-display-container, :root .comp.consequence-display-contai max-height: var(--container-height); min-height: var(--container-height); } +@keyframes anim-glow { + 0% { + box-shadow: 0 0 0px var(--blades-red-bright); + background-color: var(--blades-red-darkest); + } + 10% { + background-color: var(--blades-red-bright); + } + 100% { + box-shadow: 0 0 10px 10px transparent; + background-color: var(--blades-red-darkest); + } +} +@keyframes icon-glow { + 0% { + filter: brightness(1); + } + 10% { + filter: brightness(1); + } + 100% { + filter: brightness(1); + } +} +@keyframes icon-red-pulse { + 0% { + scale: 1; + fill: var(--blades-grey); + } + 10% { + scale: 1.1; + fill: var(--blades-red-bright); + } + 100% { + scale: 1; + fill: var(--blades-grey); + } +} +html .comp.consequence-display-container .base-consequence, :root .comp.consequence-display-container .base-consequence { + --csq-icon-dark: var(--blades-black); + --csq-icon-med: var(--blades-grey); + --csq-icon-bright: var(--blades-white); + --csq-type-color: var(--blades-grey); + --csq-name-color: var(--blades-white); +} +html .comp.consequence-display-container .accept-consequence, :root .comp.consequence-display-container .accept-consequence { + opacity: 0; + --csq-icon-dark: var(--blades-red-dark); + --csq-icon-med: var(--blades-red); + --csq-icon-bright: var(--blades-red-bright); + --csq-type-color: var(--blades-black-dark); + --csq-name-color: var(--blades-red); +} html .comp.consequence-display-container .resist-consequence, :root .comp.consequence-display-container .resist-consequence { + opacity: 0; --csq-icon-dark: var(--blades-gold-dark); --csq-icon-med: var(--blades-gold); --csq-icon-bright: var(--blades-gold-bright); --csq-type-color: var(--blades-gold-dark); --csq-name-color: var(--blades-gold-bright); } +html .comp.consequence-display-container .special-armor-consequence, :root .comp.consequence-display-container .special-armor-consequence { + opacity: 0; + --csq-icon-dark: var(--blades-blue-dark); + --csq-icon-med: var(--blades-blue); + --csq-icon-bright: var(--blades-blue-bright); + --csq-type-color: var(--blades-blue-dark); + --csq-name-color: var(--blades-blue-bright); +} +html .comp.consequence-display-container .consequence-interaction-pad, :root .comp.consequence-display-container .consequence-interaction-pad { + display: none; + display: block; + position: absolute; + z-index: 2; + pointer-events: auto; + height: 100%; + top: 0; +} +html .comp.consequence-display-container .consequence-interaction-pad.accept-consequence-pad, :root .comp.consequence-display-container .consequence-interaction-pad.accept-consequence-pad { + --pad-left-shift: calc(var(--container-left-shift) + (var(--container-height))); + left: var(--pad-left-shift); + width: calc(100% - var(--pad-left-shift)); +} +html .comp.consequence-display-container .consequence-interaction-pad.resist-consequence-pad, html .comp.consequence-display-container .consequence-interaction-pad.special-armor-consequence-pad, :root .comp.consequence-display-container .consequence-interaction-pad.resist-consequence-pad, :root .comp.consequence-display-container .consequence-interaction-pad.special-armor-consequence-pad { + left: 0; + width: calc(var(--container-left-shift) - var(--container-height) * 0); +} +html .comp.consequence-display-container .consequence-interaction-pad.special-armor-consequence-pad, :root .comp.consequence-display-container .consequence-interaction-pad.special-armor-consequence-pad { + height: 40%; + z-index: 3; +} html .comp.consequence-display-container .consequence-icon-container, :root .comp.consequence-display-container .consequence-icon-container { position: relative; - height: var(--container-height); - width: var(--container-height); - min-width: var(--container-height); - max-width: var(--container-height); - min-height: var(--container-height); - max-height: var(--container-height); + height: calc(var(--container-height) * 0.75); + max-width: calc(var(--container-height) * 0.75); background: transparent; left: var(--container-left-shift); z-index: 1; + pointer-events: auto; + transition: 0.2s; +} +html .comp.consequence-display-container .consequence-icon-container:hover, :root .comp.consequence-display-container .consequence-icon-container:hover { + filter: brightness(2); } html .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle, :root .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle { position: absolute; translate: -50% -50%; - transform-origin: 50% 50%; - top: 50%; - left: 50%; + transform-origin: 100% 0%; + top: calc(var(--container-height) * 0.5); + left: calc(var(--container-height) * 0.5); border-radius: 50%; height: var(--container-height); width: var(--container-height); @@ -2590,6 +2831,24 @@ html .comp.consequence-display-container .consequence-icon-container .consequenc html .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle svg .fill-linear, :root .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle svg .fill-linear { fill: var(--csq-icon-med); } +html .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle svg path, :root .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle svg path { + transform-origin: 50% 50%; +} +html .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence, :root .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence { + scale: 0.75; + animation: icon-glow 2s ease infinite; + pointer-events: auto; +} +html .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence svg path, :root .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence svg path { + animation: icon-red-pulse 2s ease infinite; +} +html .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence:hover, :root .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.base-consequence:hover { + filter: brightness(2) !important; + animation: none; +} +html .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.resist-consequence, html .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.special-armor-consequence, :root .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.resist-consequence, :root .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle.special-armor-consequence { + outline-width: 2px; +} html .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle .consequence-icon, :root .comp.consequence-display-container .consequence-icon-container .consequence-icon-circle .consequence-icon { height: 100%; } @@ -2599,17 +2858,21 @@ html .comp.consequence-display-container .consequence-icon-container .consequenc display: flex; flex-direction: row; flex-wrap: nowrap; - bottom: 0px; + bottom: -10px; } html .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg, :root .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg { position: absolute; z-index: -1; height: 100%; width: calc(100% + 24px); + transform-origin: 0% 50%; top: 0px; background: var(--csq-icon-bright); display: block; } +html .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg.consequence-resist-button-bg, html .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg.consequence-special-armor-button-bg, :root .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg.consequence-resist-button-bg, :root .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-bg.consequence-special-armor-button-bg { + transform-origin: 100% 50%; +} html .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-label, :root .comp.consequence-display-container .consequence-icon-container .consequence-button-container .consequence-button-label { position: relative; z-index: 1; @@ -2617,6 +2880,9 @@ html .comp.consequence-display-container .consequence-icon-container .consequenc font-size: 10px; line-height: 14px; color: var(--blades-grey); + font-weight: 800; + text-shadow: 0px 0px 1px var(--blades-black-dark); + letter-spacing: 1; text-transform: uppercase; white-space: nowrap; } @@ -2633,14 +2899,14 @@ html .comp.consequence-display-container .consequence-icon-container .consequenc margin: 0; } html .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-resist-button-container, :root .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-resist-button-container { - right: calc(100% - 3px); + right: 100%; } html .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-resist-button-container .consequence-button-bg, :root .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-resist-button-container .consequence-button-bg { left: -7px; transform: skewX(45deg); } html .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-accept-button-container, :root .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-accept-button-container { - left: 100%; + left: 140%; } html .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-accept-button-container .consequence-button-bg, :root .comp.consequence-display-container .consequence-icon-container .consequence-button-container.consequence-accept-button-container .consequence-button-bg { right: -7px; @@ -2655,7 +2921,7 @@ html .comp.consequence-display-container .consequence-type-container, :root .com height: calc(var(--container-height) * 0.33); transform-origin: 0% 50%; left: calc(var(--container-height) + var(--container-left-shift) - 10px); - top: 0px; + top: -2px; padding: 0 5px 0 15px; } html .comp.consequence-display-container .consequence-type-container .consequence-type-bg, :root .comp.consequence-display-container .consequence-type-container .consequence-type-bg { @@ -2664,20 +2930,21 @@ html .comp.consequence-display-container .consequence-type-container .consequenc left: -20px; height: 100%; width: 170px; + transform-origin: 0% 50%; transform: skewX(-45deg); - background: var(--csq-type-bg); + background: var(--csq-icon-dark); } html .comp.consequence-display-container .consequence-type-container .consequence-type, :root .comp.consequence-display-container .consequence-type-container .consequence-type { position: absolute; top: 0; - left: 10px; + transform-origin: 0% 50%; white-space: nowrap; font-family: Oswald, sans-serif; text-transform: uppercase; text-align: right; font-size: 10px; color: var(--csq-type-color); - font-weight: bold; + font-weight: normal; } html .comp.consequence-display-container .consequence-name-container, :root .comp.consequence-display-container .consequence-name-container { position: absolute; @@ -2693,9 +2960,10 @@ html .comp.consequence-display-container .consequence-name-container .consequenc z-index: 1; padding: 0 5px 0 35px; font-size: 14px; - line-height: calc(var(--container-height) * 0.55); + line-height: 17px; font-family: Kirsty, serif; font-variant: small-caps; + transform-origin: 0% 50%; color: var(--csq-icon-bright); font-style: italic; } @@ -2705,7 +2973,7 @@ html .comp.consequence-display-container .consequence-name-container .consequenc html .comp.consequence-display-container .consequence-footer-container, :root .comp.consequence-display-container .consequence-footer-container { position: absolute; height: calc(var(--container-height) * var(--csq-button-size-mult)); - width: 120px; + width: auto; bottom: 0; top: unset; left: calc(var(--container-height) + var(--container-left-shift) - 20px); @@ -2719,8 +2987,17 @@ html .comp.consequence-display-container .consequence-footer-container .conseque background: var(--csq-icon-bright); display: block; transform: skewX(45deg); + transform-origin: 0% 50%; +} +html .comp.consequence-display-container .consequence-footer-container .consequence-footer-bg.resist-consequence, :root .comp.consequence-display-container .consequence-footer-container .consequence-footer-bg.resist-consequence { + width: 120px; +} +html .comp.consequence-display-container .consequence-footer-container .consequence-footer-bg.special-armor-consequence, :root .comp.consequence-display-container .consequence-footer-container .consequence-footer-bg.special-armor-consequence { + width: 250px; } -html .comp.consequence-display-container .consequence-footer-container .consequence-resist-attribute, :root .comp.consequence-display-container .consequence-footer-container .consequence-resist-attribute { +html .comp.consequence-display-container .consequence-footer-container .consequence-footer-message, :root .comp.consequence-display-container .consequence-footer-container .consequence-footer-message { + position: absolute; + white-space: nowrap; font-family: Oswald, sans-serif; font-weight: bold; color: var(--blades-black-dark); @@ -2728,6 +3005,7 @@ html .comp.consequence-display-container .consequence-footer-container .conseque line-height: 14px; padding-left: 25px; justify-content: flex-start; + transform-origin: 0% 50%; gap: 5px; } html .comp.consequence-display-container .consequence-footer-container .dotline, :root .comp.consequence-display-container .consequence-footer-container .dotline { @@ -3231,7 +3509,7 @@ html .white-bright, :root .white-bright { color: var(--blades-white-bright) !important; } html .cyan-bright, :root .cyan-bright { - color: var(--blades-cyan-bright) !important; + color: var(--blades-blue-bright) !important; } html .uppercase, :root .uppercase { text-transform: uppercase !important; diff --git a/gulpfile.js b/gulpfile.js index 9cc7e426..492374cb 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,3 +1,4 @@ +/* eslint-disable func-names */ /* eslint-disable @typescript-eslint/no-var-requires, */ // #region ▮▮▮▮▮▮▮ IMPORTS ▮▮▮▮▮▮▮ ~ const argv = require("yargs").argv; @@ -80,15 +81,15 @@ const ANSICOLORS = { invert: "\u001b[7m" }; const STREAMSTYLES = { - gulp: ["grey", "█", "(gulp)"], - initWhiteSpace: ["bred", "█", "(staging)"], - tsInit: ["blue", "░", " TS "], - jsFull: ["bmagenta", "█", " JS "], - jsMin: ["magenta", "░", " js "], - cssFull: ["byellow", "█", " SCSS "], - cssMin: ["yellow", "░", " scss "], - hbs: ["bblue", "█", " HBS "], - toDest: ["bgreen", "█", " ASSETS "] + gulp: ["grey", "-", "(gulp)"], + initWhiteSpace: ["bred", "=", "(staging)"], + tsInit: ["blue", "T", " TS "], + jsFull: ["bmagenta", "J", " JS "], + jsMin: ["magenta", "J", " js "], + cssFull: ["byellow", "C", " SCSS "], + cssMin: ["yellow", "C", " scss "], + hbs: ["bblue", "<", " HBS "], + toDest: ["bgreen", "$", " ASSETS "] }; const ansi = (str, {fg, bg, style} = {}) => { fg = ANSICOLORS[fg ?? "white"]; @@ -99,7 +100,7 @@ const ansi = (str, {fg, bg, style} = {}) => { const toBright = (color) => (`b${color}` in ANSICOLORS ? `b${color}` : color); const toDim = (color) => (color.slice(1) in ANSICOLORS ? color.slice(1) : color); const logParts = { - tag: (tag = "gulp", color = "white", padChar = "█") => ansi(`▌${centerString(tag, 10, padChar)}▐`, {fg: color}), + tag: (tag = "gulp", color = "white", padChar = " ") => ansi(` ${centerString(tag, 10, padChar)} `, {fg: color}), error: (tag, message) => [ansi(`[ERROR: ${tag}]`, {fg: "white", bg: "red", style: "bold"}), ansi(message, {fg: "red"})].join(" "), finish: function(title, source, destination) { title ??= "gulp"; @@ -226,7 +227,22 @@ const BANNERTEMPLATE = { let BUILDFILES = {}; -if (ISCOMPILINGCODE) { +if (ISRAPIDGULPING) { + BUILDFILES = { + ts: { + "./module/": ["ts/**/*.*"] + }, + css: { + "./css/": ["scss/**/*.scss"] + }, + hbs: { + "./templates/": ["DISABLE"] + }, + quickAssets: { + "./css/": ["scss/**/*.css"] + } + }; +} else if (ISCOMPILINGCODE) { BUILDFILES = { ts: { "./module_staging_1/": ["ts/**/*.*"] @@ -301,12 +317,14 @@ const REGEXPPATTERNS = { init: new Map([ [/^\s+$/gm, "/*~ @@DOUBLE-BLANK@@ ~*/"] // Replace double-blank lines with token for later retrieval ]), - ts: new Map([[/from "gsap\/all"/gu, 'from "/scripts/greensock/esm/all.js"']]), + ts: new Map([ + [/from "gsap\/all"/gu, 'from "/scripts/greensock/esm/all.js"'], + [/from ['"](.+?)(\.ts|\.js)?['"]/g, "from \"$1.js\""] // Fix imports to include .js suffix + ]), js: new Map([ ISDEPLOYING || ISBUILDINGDIST ? [/(\r\n)?\s*?(\/\*DEVCODE\*\/.*?\/\*!DEVCODE\*\/)\s*?(\r\n)?/gms, ""] : [/`/, "`"], // Strip developer code - [/from ['"](.+?)(\.ts|\.js)?['"]/g, "from \"$1.js\""], // Fix imports to include .js suffix [ (/\/\*~\s*\*{4}▌.*?▐\*{4}\s*\*\//s, padHeaderLines) ], // Pad header lines to same length @@ -376,7 +394,7 @@ const PIPES = { // #endregion ___ PIPES ___ const PLUMBING = { - analyzeJS: async function(done) { + analyzeJS: async function analyzeJS(done) { if (!ISANALYZING) { return done(); } try { const analysisData = analyzeProject("./"); @@ -421,16 +439,19 @@ const PLUMBING = { } return done(); }, - initDest: async function(done, destGlobs = ["./dist/", "./module/", "./module_staging_1", "./module_staging_2", "./css/"]) { + initDest: async function initDest(done, destGlobs = ["./dist/", "./module/", "./module_staging_1", "./module_staging_2", "./css/"]) { destGlobs.forEach((d) => { try { fs.rmSync(d); } catch{ } }); return done(); }, - watch: function() { + watch: function watchFunc() { + console.log("\n\n==== BUILDFILES ====\n\n"); + console.log(JSON.stringify(BUILDFILES, null, 2)); + console.log("\n\n--------------------\n\n"); for (const [type, globs] of Object.entries(BUILDFILES)) { Object.values(globs ?? {}).forEach((glob) => watch(glob, BUILDFUNCS[type])); } }, - tsInit: (source, destination) => function() { + tsInit: (source, destination) => function tsInit() { const tsStream = src(source, {allowEmpty: true}) // .pipe(sourcemaps.init()) .pipe(PIPES.openPipe("tsInit")()) @@ -448,18 +469,18 @@ const PLUMBING = { } return tsStream .pipe(PIPES.replacer("ts")()) - .pipe(PIPES.replacer("js")()) + // .pipe(PIPES.replacer("js")()) // .pipe(sourcemaps.write(".")) .pipe(PIPES.closePipe("tsInit", source, destination)); }, - jsFull: (source, destination) => function() { + jsFull: (source, destination) => function jsFull() { return src(source, {allowEmpty: true}) .pipe(PIPES.openPipe("jsFull")()) .pipe(header(BANNERS.js.full, {package: packageJSON})) .pipe(PIPES.replacer("js")()) .pipe(PIPES.closePipe("jsFull", source, destination)); }, - jsMin: (source, destination) => function() { + jsMin: (source, destination) => function jsMin() { return src(source, {allowEmpty: true}) .pipe(PIPES.openPipe("jsMin")()) .pipe(header(BANNERS.js.min, {package: packageJSON})) @@ -468,7 +489,7 @@ const PLUMBING = { .pipe(PIPES.terser()()) .pipe(PIPES.closePipe("jsMin", source, destination)); }, - cssFull: (source, destination) => function() { + cssFull: (source, destination) => function cssFull() { if (ISRAPIDGULPING) { return src(source, {allowEmpty: true}) .pipe(PIPES.openPipe("cssFull")()) @@ -485,7 +506,7 @@ const PLUMBING = { .pipe(PIPES.closePipe("cssFull", source, destination)); } }, - cssMin: (source, destination) => function() { + cssMin: (source, destination) => function cssMin() { return src(source, {allowEmpty: true}) .pipe(PIPES.openPipe("cssMin")()) .pipe(sasser({outputStyle: "compressed"})) @@ -497,12 +518,12 @@ const PLUMBING = { .pipe(renamer({suffix: ".min"})) .pipe(PIPES.closePipe("cssMin", source, destination)); }, - hbs: (source, destination) => function() { + hbs: (source, destination) => function hbs() { return src(source, {allowEmpty: true}) .pipe(PIPES.openPipe("hbs")()) .pipe(PIPES.closePipe("hbs", source, destination)); }, - toDest: (source, destination) => function() { + toDest: (source, destination) => function toDest() { return src(source, {allowEmpty: true}) .pipe(PIPES.openPipe("toDest")()) .pipe(PIPES.closePipe("toDest", source, destination)); @@ -536,34 +557,36 @@ if (ISCOMPILINGCODE) { })(BUILDFILES.ts)); // ); - const jsBuildFuncs = [ - parallel(...((buildFiles) => { - const funcs = []; - for (const [destGlob, sourceGlobs] of Object.entries(buildFiles)) { - sourceGlobs.forEach((sourceGlob) => { - funcs.push(PLUMBING.jsFull(sourceGlob, destGlob)); - }); - } - return funcs; - })(BUILDFILES.js)) - ]; + if (!ISRAPIDGULPING) { + const jsBuildFuncs = [ + parallel(...((buildFiles) => { + const funcs = []; + for (const [destGlob, sourceGlobs] of Object.entries(buildFiles)) { + sourceGlobs.forEach((sourceGlob) => { + funcs.push(PLUMBING.jsFull(sourceGlob, destGlob)); + }); + } + return funcs; + })(BUILDFILES.js)) + ]; + + if (ISMINIFYINGJS) { + jsBuildFuncs.push(parallel(...((buildFiles) => { + const funcs = []; + for (const [destGlob, sourceGlobs] of Object.entries(buildFiles)) { + sourceGlobs.forEach((sourceGlob) => { + funcs.push(PLUMBING.jsMin(sourceGlob, destGlob)); + }); + } + return funcs; + })(BUILDFILES.js_2))); + } - if (ISMINIFYINGJS) { - jsBuildFuncs.push(parallel(...((buildFiles) => { - const funcs = []; - for (const [destGlob, sourceGlobs] of Object.entries(buildFiles)) { - sourceGlobs.forEach((sourceGlob) => { - funcs.push(PLUMBING.jsMin(sourceGlob, destGlob)); - }); - } - return funcs; - })(BUILDFILES.js_2))); + BUILDFUNCS.js = series( + ...jsBuildFuncs, + PLUMBING.analyzeJS + ); } - - BUILDFUNCS.js = series( - ...jsBuildFuncs, - PLUMBING.analyzeJS - ); } // #endregion ▄▄▄▄▄ JS ▄▄▄▄▄ @@ -617,8 +640,12 @@ if (ISCOMPILINGCODE) { return funcs; }; - BUILDFUNCS.quickAssets = parallel(...assetPipe(BUILDFILES.quickAssets)); - BUILDFUNCS.slowAssets = parallel(...assetPipe(BUILDFILES.slowAssets)); + if (BUILDFILES.quickAssets) { + BUILDFUNCS.quickAssets = parallel(...assetPipe(BUILDFILES.quickAssets)); + } + if (BUILDFILES.slowAssets) { + BUILDFUNCS.slowAssets = parallel(...assetPipe(BUILDFILES.slowAssets)); + } } // #endregion ▄▄▄▄▄ ASSETS ▄▄▄▄▄ @@ -634,7 +661,6 @@ const parallelFuncs = [ ].filter((pFunc) => pFunc !== undefined); // Const parallelFuncs = parallelBuildFuncs((pFunc) => pFunc !== undefined); - exports.default = series( PLUMBING.initDest, parallel(...parallelFuncs), diff --git a/module/BladesActiveEffect.js b/module/BladesActiveEffect.js index c81d85e1..7f307d33 100644 --- a/module/BladesActiveEffect.js +++ b/module/BladesActiveEffect.js @@ -1,14 +1,9 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - import BladesActor from "./BladesActor.js"; import U from "./core/utilities.js"; import { Tag, BladesPhase, BladesActorType } from "./core/constants.js"; +/*~ @@DOUBLE-BLANK@@ ~*/ const FUNCQUEUE = {}; +// {type: "ability", name: "rX:/^(?!Ghost)/"} const CUSTOMFUNCS = { addItem: async (actor, funcData, _, isReversing = false) => { eLog.checkLog("activeEffects", "addItem", { actor, funcData, isReversing }); @@ -47,6 +42,7 @@ const CUSTOMFUNCS = { APPLYTOMEMBERS: async () => new Promise(() => undefined), APPLYTOCOHORTS: async () => new Promise(() => undefined), remItem: async (actor, funcData, _, isReversing = false) => { + /*~ @@DOUBLE-BLANK@@ ~*/ function testString(targetString, testDef) { if (testDef.startsWith("rX")) { const pat = new RegExp(testDef.replace(/^rX:\/(.*?)\//, "$1")); @@ -54,6 +50,7 @@ const CUSTOMFUNCS = { } return targetString === testDef; } + /*~ @@DOUBLE-BLANK@@ ~*/ if (funcData.startsWith("{")) { if (isReversing) { console.error("Cannot reverse a 'remItem' custom effect that was defined with a JSON object."); @@ -95,6 +92,7 @@ const CUSTOMFUNCS = { return undefined; } }; +/*~ @@DOUBLE-BLANK@@ ~*/ var EffectMode; (function (EffectMode) { EffectMode[EffectMode["Custom"] = 0] = "Custom"; @@ -104,20 +102,29 @@ var EffectMode; EffectMode[EffectMode["Upgrade"] = 4] = "Upgrade"; EffectMode[EffectMode["Override"] = 5] = "Override"; })(EffectMode || (EffectMode = {})); +/*~ @@DOUBLE-BLANK@@ ~*/ class BladesActiveEffect extends ActiveEffect { static Initialize() { CONFIG.ActiveEffect.documentClass = BladesActiveEffect; + /*~ @@DOUBLE-BLANK@@ ~*/ Hooks.on("preCreateActiveEffect", async (effect) => { + /*~ @@DOUBLE-BLANK@@ ~*/ eLog.checkLog3("effect", "PRECREATE ActiveEffect", { effect, parent: effect.parent?.name }); + /*~ @@DOUBLE-BLANK@@ ~*/ if (!(effect.parent instanceof BladesActor)) { return; } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Does this effect have an "APPLYTOMEMBERS" or "APPLYTOCOHORTS" CUSTOM effect? if (effect.changes.some((change) => change.key === "APPLYTOMEMBERS")) { + /*~ @@DOUBLE-BLANK@@ ~*/ if (BladesActor.IsType(effect.parent, BladesActorType.pc) && BladesActor.IsType(effect.parent.crew, BladesActorType.crew)) { const otherMembers = effect.parent.crew.members.filter((member) => member.id !== effect.parent?.id); if (otherMembers.length > 0) { + // If PC & APPLYTOMEMBERS --> Create effect on members MINUS the 'APPLYTOMEMBERS' key, leave PC's effect unchanged. effect.changes = effect.changes.filter((change) => change.key !== "APPLYTOMEMBERS"); await Promise.all(otherMembers.map(async (member) => member.createEmbeddedDocuments("ActiveEffect", [effect.toJSON()]))); + // Set flag with effect's data on member, so future members can have effect applied to them. await effect.parent.setFlag("eunos-blades", `memberEffects.${effect.id}`, { appliedTo: otherMembers.map((member) => member.id), effect: effect.toJSON() @@ -130,28 +137,37 @@ class BladesActiveEffect extends ActiveEffect { return; } if (effect.parent.members.length > 0) { + // If Crew & APPLYTOMEMBERS --> Create effect on members MINUS the 'APPLYTOMEMBERS' key await Promise.all(effect.parent.members.map(async (member) => member.createEmbeddedDocuments("ActiveEffect", [effect.toJSON()]))); } + // Set flag with effect's data on crew, so future members can have effect applied to them. await effect.parent.setFlag("eunos-blades", `memberEffects.${effect.id}`, { appliedTo: effect.parent.members.map((member) => member.id), effect }); + // Update effect on crew-parent to only include 'APPLYTOMEMBERS' change await effect.updateSource({ changes: [changeKey] }); } } else if (effect.changes.some((change) => change.key === "APPLYTOCOHORTS") && (BladesActor.IsType(effect.parent, BladesActorType.pc) || BladesActor.IsType(effect.parent, BladesActorType.crew))) { if (effect.parent.cohorts.length > 0) { + // If APPLYTOCOHORTS --> Create effect on cohorts await Promise.all(effect.parent.cohorts.map(async (cohort) => cohort.createEmbeddedDocuments("ActiveEffect", [effect.toJSON()]))); } + // Set flag with effect's data on parent, so future cohorts can have effect applied to them. await effect.parent.setFlag("eunos-blades", `cohortEffects.${effect.id}`, { appliedTo: effect.parent.cohorts.map((cohort) => cohort.id), effect }); + // Update effect on parent to only include 'APPLYTOCOHORTS' change await effect.updateSource({ changes: effect.changes.filter((change) => change.key === "APPLYTOCOHORTS") }); } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Partition effect.changes into permanent and non-permanent changes: const [permChanges, changes] = U.partition(effect.changes, (change) => change.key.startsWith("perm")); await effect.updateSource({ changes }); + /*~ @@DOUBLE-BLANK@@ ~*/ for (const permChange of permChanges) { const { key, value } = permChange; const permFuncName = key.replace(/^perm/, ""); @@ -170,10 +186,13 @@ class BladesActiveEffect extends ActiveEffect { } } }); + /*~ @@DOUBLE-BLANK@@ ~*/ Hooks.on("applyActiveEffect", (actor, changeData) => { + /*~ @@DOUBLE-BLANK@@ ~*/ if (!(actor instanceof BladesActor)) { return; } + /*~ @@DOUBLE-BLANK@@ ~*/ if (changeData.key in CUSTOMFUNCS) { const funcData = { funcName: changeData.key, @@ -184,6 +203,7 @@ class BladesActiveEffect extends ActiveEffect { BladesActiveEffect.ThrottleCustomFunc(actor, funcData); } }); + /*~ @@DOUBLE-BLANK@@ ~*/ Hooks.on("updateActiveEffect", (effect, { disabled }) => { if (!(effect.parent instanceof BladesActor)) { return; @@ -199,41 +219,51 @@ class BladesActiveEffect extends ActiveEffect { BladesActiveEffect.ThrottleCustomFunc(effect.parent, funcData); }); }); + /*~ @@DOUBLE-BLANK@@ ~*/ Hooks.on("deleteActiveEffect", async (effect) => { if (!(effect.parent instanceof BladesActor)) { return; } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Does this effect have an "APPLYTOMEMBERS" or "APPLYTOCOHORTS" CUSTOM effect? if (effect.changes.some((change) => change.key === "APPLYTOMEMBERS")) { if (BladesActor.IsType(effect.parent, BladesActorType.pc) && BladesActor.IsType(effect.parent.crew, BladesActorType.crew)) { const otherMembers = effect.parent.crew.members.filter((member) => member.id !== effect.parent?.id); if (otherMembers.length > 0) { + // If PC & APPLYTOMEMBERS --> Delete effect on all other members. await Promise.all(otherMembers .map(async (member) => Promise.all(member.effects .filter((e) => e.name === effect.name) .map(async (e) => e.delete())))); } + // Clear flag from parent await effect.parent.unsetFlag("eunos-blades", `memberEffects.${effect.id}`); } else if (BladesActor.IsType(effect.parent, BladesActorType.crew)) { if (effect.parent.members.length > 0) { + // If CREW & APPLYTOMEMBERS --> Delete effect on all other members. await Promise.all(effect.parent.members .map(async (member) => Promise.all(member.effects .filter((e) => e.name === effect.name) .map(async (e) => e.delete())))); } + // Clear flag from parent await effect.parent.unsetFlag("eunos-blades", `memberEffects.${effect.id}`); } } else if (effect.changes.some((change) => change.key === "APPLYTOCOHORTS") && (BladesActor.IsType(effect.parent, BladesActorType.pc, BladesActorType.crew))) { if (effect.parent.cohorts.length > 0) { + // If APPLYTOCOHORTS --> Delete effect on cohorts. await Promise.all(effect.parent.cohorts .map(async (cohort) => Promise.all(cohort.effects .filter((e) => e.name === effect.name) .map(async (e) => e.delete())))); } + // Clear flag from parent await effect.parent.unsetFlag("eunos-blades", `cohortEffects.${effect.id}`); } + /*~ @@DOUBLE-BLANK@@ ~*/ const customEffects = effect.changes.filter((changes) => changes.mode === 0); customEffects.forEach(({ key, value }) => { const funcData = { @@ -246,17 +276,21 @@ class BladesActiveEffect extends ActiveEffect { }); }); } + /*~ @@DOUBLE-BLANK@@ ~*/ static async AddActiveEffect(doc, name, eChanges, icon = "systems/eunos-blades/assets/icons/effect-icons/default.png") { const changes = [eChanges].flat(); await doc.createEmbeddedDocuments("ActiveEffect", [{ name, icon, changes }]); } + /*~ @@DOUBLE-BLANK@@ ~*/ static ThrottleCustomFunc(actor, data) { const { funcName, funcData, isReversing, effect } = data; if (!actor.id) { return; } eLog.display(`Throttling Func: ${funcName}(${funcData}, ${isReversing})`); + // Is there a currently-running function for this actor? if (actor.id && actor.id in FUNCQUEUE) { + // Is this a duplicate of a function already queued? const matchingQueue = FUNCQUEUE[actor.id].queue.find((fData) => JSON.stringify(fData) === JSON.stringify(data)); eLog.checkLog("activeEffects", "... Checking Queue", { data, FUNCQUEUE: FUNCQUEUE[actor.id], matchingQueue }); if (matchingQueue) { @@ -266,12 +300,14 @@ class BladesActiveEffect extends ActiveEffect { FUNCQUEUE[actor.id].queue.push(data); return; } + // If not, create FUNCQUEUE entry and run first function. eLog.display("... Creating New FUNCQUEUE, RUNNING:"); FUNCQUEUE[actor.id] = { curFunc: BladesActiveEffect.RunCustomFunc(actor, CUSTOMFUNCS[funcName](actor, funcData, effect, isReversing)), queue: [] }; } + /*~ @@DOUBLE-BLANK@@ ~*/ static async RunCustomFunc(actor, funcPromise) { if (!actor.id) { return; @@ -295,7 +331,12 @@ class BladesActiveEffect extends ActiveEffect { delete FUNCQUEUE[actor.id]; } } - static onManageActiveEffect(event, owner) { + /** + * Manage Active Effect instances through the Actor Sheet via effect control buttons. + * @param {MouseEvent} event The left-click event on the effect control + * @param {Actor|Item} owner The owning entity which manages this effect + */ + static onManageActiveEffect(event, owner) { event.preventDefault(); const a = event.currentTarget; if (a.dataset.action === "create") { @@ -324,6 +365,7 @@ class BladesActiveEffect extends ActiveEffect { default: return null; } } + /*~ @@DOUBLE-BLANK@@ ~*/ async _preCreate(data, options, user) { eLog.checkLog3("effect", "ActiveEffect._preCreate()", { data, options, user }); await super._preCreate(data, options, user); @@ -332,7 +374,9 @@ class BladesActiveEffect extends ActiveEffect { eLog.checkLog3("effect", "ActiveEffect._onDelete()", { options, userID }); super._onDelete(options, userID); } + /*~ @@DOUBLE-BLANK@@ ~*/ get isSuppressed() { + // Get source item from "origin.js" field -- of form 'Actor..Item.' if (!/Actor.*Item/.test(this.origin)) { return super.isSuppressed; } @@ -342,4 +386,5 @@ class BladesActiveEffect extends ActiveEffect { return super.isSuppressed || item?.hasTag(Tag.System.Archived); } } -export default BladesActiveEffect; \ No newline at end of file +/*~ @@DOUBLE-BLANK@@ ~*/ +export default BladesActiveEffect; diff --git a/module/BladesActor.js b/module/BladesActor.js index eec973c4..76e8668b 100644 --- a/module/BladesActor.js +++ b/module/BladesActor.js @@ -1,21 +1,23 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - +// #region Imports ~ import U from "./core/utilities.js"; import C, { BladesActorType, Tag, Playbook, BladesItemType, ActionTrait, PrereqType, AdvancementPoint, Randomizers, Factor } from "./core/constants.js"; import { BladesItem } from "./documents/BladesItemProxy.js"; +/*~ @@DOUBLE-BLANK@@ ~*/ import { BladesRollMod } from "./BladesRoll.js"; import BladesPushAlert from "./BladesPushAlert.js"; import { SelectionCategory } from "./BladesDialog.js"; +// #endregion +/*~ @@DOUBLE-BLANK@@ ~*/ +// Blades Theme Song: "Bangkok" from The Gray Man soundtrack: https://www.youtube.com/watch?v=cjjImvMqYlo&list=OLAK5uy_k9cZDd1Fbpd25jfDtte5A6HyauD2-cwgk&index=2 +// Also check out Discord thread: https://discord.com/channels/325094888133885952/1152316839163068527 +/*~ @@DOUBLE-BLANK@@ ~*/ +// eslint-disable-next-line no-shadow var BladesActorUniqueTags; (function (BladesActorUniqueTags) { BladesActorUniqueTags["CharacterCrew"] = "CharacterCrew"; BladesActorUniqueTags["VicePurveyor"] = "VicePurveyor"; })(BladesActorUniqueTags || (BladesActorUniqueTags = {})); +// eslint-disable-next-line no-shadow var BladesItemUniqueTypes; (function (BladesItemUniqueTypes) { BladesItemUniqueTypes["background"] = "background"; @@ -27,15 +29,22 @@ var BladesItemUniqueTypes; BladesItemUniqueTypes["preferred_op"] = "preferred_op"; })(BladesItemUniqueTypes || (BladesItemUniqueTypes = {})); class BladesActor extends Actor { - + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region Static Overrides: Create ~ static async create(data, options = {}) { data.token = data.token || {}; data.system = data.system ?? {}; + /*~ @@DOUBLE-BLANK@@ ~*/ + // ~ Create world_name data.system.world_name = data.system.world_name ?? data.name.replace(/[^A-Za-z_0-9 ]/g, "").trim().replace(/ /g, "_"); + /*~ @@DOUBLE-BLANK@@ ~*/ return super.create(data, options); } - + // #endregion + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region BladesDocument Implementation ~ static get All() { return game.actors; } + /*~ @@DOUBLE-BLANK@@ ~*/ static Get(actorRef) { if (actorRef instanceof BladesActor) { return actorRef; @@ -46,18 +55,23 @@ class BladesActor extends Actor { return BladesActor.All.find((a) => a.system.world_name === actorRef) || BladesActor.All.find((a) => a.name === actorRef); } + /*~ @@DOUBLE-BLANK@@ ~*/ static GetTypeWithTags(docType, ...tags) { return BladesActor.All.filter((actor) => actor.type === docType) .filter((actor) => actor.hasTag(...tags)); } + /*~ @@DOUBLE-BLANK@@ ~*/ static IsType(doc, ...types) { const typeSet = new Set(types); return doc instanceof BladesActor && typeSet.has(doc.type); } + /*~ @@DOUBLE-BLANK@@ ~*/ get tags() { return this.system.tags ?? []; } + /*~ @@DOUBLE-BLANK@@ ~*/ hasTag(...tags) { return tags.every((tag) => this.tags.includes(tag)); } + /*~ @@DOUBLE-BLANK@@ ~*/ async addTag(...tags) { const curTags = this.tags; tags.forEach((tag) => { @@ -69,18 +83,22 @@ class BladesActor extends Actor { eLog.checkLog2("actor", "BladesActor.addTag(...tags)", { tags, curTags }); await this.update({ "system.tags": curTags }); } + /*~ @@DOUBLE-BLANK@@ ~*/ async remTag(...tags) { const curTags = this.tags.filter((tag) => !tags.includes(tag)); eLog.checkLog2("actor", "BladesActor.remTag(...tags)", { tags, curTags }); await this.update({ "system.tags": curTags }); } + /*~ @@DOUBLE-BLANK@@ ~*/ get tooltip() { const tooltipText = [this.system.concept, this.system.subtitle] .filter(Boolean) .join("

"); return tooltipText ? (new Handlebars.SafeString(tooltipText)).toString() : undefined; } + /*~ @@DOUBLE-BLANK@@ ~*/ get dialogCSSClasses() { return ""; } + /*~ @@DOUBLE-BLANK@@ ~*/ getFactorTotal(factor) { switch (factor) { case Factor.tier: { @@ -105,26 +123,41 @@ class BladesActor extends Actor { default: return 0; } } + // #endregion + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region SubActorControl Implementation ~ + /*~ @@DOUBLE-BLANK@@ ~*/ get subActors() { return Object.keys(this.system.subactors) .map((id) => this.getSubActor(id)) .filter((subActor) => Boolean(subActor)); } + /*~ @@DOUBLE-BLANK@@ ~*/ get activeSubActors() { return this.subActors.filter((subActor) => !subActor.hasTag(Tag.System.Archived)); } + /*~ @@DOUBLE-BLANK@@ ~*/ get archivedSubActors() { return this.subActors.filter((subActor) => subActor.hasTag(Tag.System.Archived)); } + /*~ @@DOUBLE-BLANK@@ ~*/ checkActorPrereqs(actor) { - + /*~ @@DOUBLE-BLANK@@ ~*/ + /* Implement any prerequisite checks for embedding actors */ + /*~ @@DOUBLE-BLANK@@ ~*/ return Boolean(actor); } + /*~ @@DOUBLE-BLANK@@ ~*/ processEmbeddedActorMatches(globalActors) { + /*~ @@DOUBLE-BLANK@@ ~*/ return globalActors + // Step 1: Filter out globals that fail prereqs. .filter(this.checkActorPrereqs) + // Step 2: Filter out actors that are already active subActors .filter((gActor) => !this.activeSubActors.some((aActor) => aActor.id === gActor.id)) + // Step 3: Merge subactor data onto matching global actors .map((gActor) => this.getSubActor(gActor) || gActor) + // Step 4: Sort by name .sort((a, b) => { if (a.name === b.name) { return 0; @@ -144,9 +177,13 @@ class BladesActor extends Actor { return 0; }); } + /*~ @@DOUBLE-BLANK@@ ~*/ getDialogActors(category) { - + /*~ @@DOUBLE-BLANK@@ ~*/ + /* **** NEED TO FILTER OUT ACTORS PLAYER DOESN'T HAVE PERMISSION TO SEE **** */ + /*~ @@DOUBLE-BLANK@@ ~*/ const dialogData = {}; + /*~ @@DOUBLE-BLANK@@ ~*/ switch (category) { case SelectionCategory.Contact: case SelectionCategory.Rival: @@ -173,19 +210,26 @@ class BladesActor extends Actor { default: return false; } } + /*~ @@DOUBLE-BLANK@@ ~*/ async addSubActor(actorRef, tags) { + /*~ @@DOUBLE-BLANK@@ ~*/ let focusSubActor; + // Does an embedded subActor of this Actor already exist on the character? if (this.hasSubActorOf(actorRef)) { const subActor = this.getSubActor(actorRef); if (!subActor) { return; } + // Is it an archived Item? if (subActor.hasTag(Tag.System.Archived)) { + // Unarchive it await subActor.remTag(Tag.System.Archived); } + // Make it the focus item. focusSubActor = subActor; } else { + // Is it not embedded at all? Create new entry in system.subactors from global actor const actor = BladesActor.Get(actorRef); if (!actor) { return; @@ -197,14 +241,19 @@ class BladesActor extends Actor { ...tags ]); } + // Await the update, then make the retrieved subactor the focus await this.update({ [`system.subactors.${actor.id}`]: subActorData }); focusSubActor = this.getSubActor(actor.id); } + /*~ @@DOUBLE-BLANK@@ ~*/ if (!focusSubActor) { return; } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Does this Actor contain any tags limiting it to one per actor? const uniqueTags = focusSubActor.tags.filter((tag) => tag in BladesActorUniqueTags); if (uniqueTags.length > 0) { + // ... then archive all other versions. uniqueTags.forEach((uTag) => this.activeSubActors .filter((subActor) => Boolean(focusSubActor?.id && subActor.id !== focusSubActor.id @@ -212,6 +261,7 @@ class BladesActor extends Actor { .map((subActor) => this.remSubActor(subActor.id))); } } + /*~ @@DOUBLE-BLANK@@ ~*/ getSubActor(actorRef) { const actor = BladesActor.Get(actorRef); if (!actor?.id) { @@ -225,6 +275,7 @@ class BladesActor extends Actor { actor.parentActor = this; return actor; } + /*~ @@DOUBLE-BLANK@@ ~*/ hasSubActorOf(actorRef) { const actor = BladesActor.Get(actorRef); if (!actor) { @@ -232,6 +283,7 @@ class BladesActor extends Actor { } return actor?.id ? actor.id in this.system.subactors : false; } + /*~ @@DOUBLE-BLANK@@ ~*/ async updateSubActor(actorRef, upData) { const updateData = U.objExpand(upData); if (!updateData.system) { @@ -241,15 +293,23 @@ class BladesActor extends Actor { if (!actor) { return undefined; } + /*~ @@DOUBLE-BLANK@@ ~*/ + // DiffObject new update data against actor data. const diffUpdateSystem = U.objDiff(actor.system, updateData.system); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Merge new update data onto current subactor data. const mergedSubActorSystem = U.objMerge(this.system.subactors[actor.id] ?? {}, diffUpdateSystem, { isReplacingArrays: true, isConcatenatingArrays: false }); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Confirm this update changes data: if (JSON.stringify(this.system.subactors[actor.id]) === JSON.stringify(mergedSubActorSystem)) { return undefined; } + // Update actor with new subactor data. return this.update({ [`system.subactors.${actor.id}`]: null }, undefined, true) .then(() => this.update({ [`system.subactors.${actor.id}`]: mergedSubActorSystem }, undefined, true)) .then(() => actor.sheet?.render()); } + /*~ @@DOUBLE-BLANK@@ ~*/ async remSubActor(actorRef) { const subActor = this.getSubActor(actorRef); if (!subActor) { @@ -257,6 +317,7 @@ class BladesActor extends Actor { } await this.update({ "system.subactors": mergeObject(this.system.subactors, { [`-=${subActor.id}`]: null }) }, undefined, true); } + /*~ @@DOUBLE-BLANK@@ ~*/ async clearSubActors(isReRendering = true) { this.subActors.forEach((subActor) => { if (subActor.parentActor?.id === this.id) { @@ -265,6 +326,7 @@ class BladesActor extends Actor { }); await this.sheet?.render(); } + /*~ @@DOUBLE-BLANK@@ ~*/ async clearParentActor(isReRendering = true) { const { parentActor } = this; if (!parentActor) { @@ -273,15 +335,21 @@ class BladesActor extends Actor { this.parentActor = undefined; this.system = this._source.system; this.ownership = this._source.ownership; + /*~ @@DOUBLE-BLANK@@ ~*/ this.prepareData(); if (isReRendering) { await this.sheet?.render(); } } - + // #endregion + // #region SubItemControl Implementation ~ + /*~ @@DOUBLE-BLANK@@ ~*/ get subItems() { return Array.from(this.items); } + /*~ @@DOUBLE-BLANK@@ ~*/ get activeSubItems() { return this.items.filter((item) => !item.hasTag(Tag.System.Archived)); } + /*~ @@DOUBLE-BLANK@@ ~*/ get archivedSubItems() { return this.items.filter((item) => item.hasTag(Tag.System.Archived)); } + /*~ @@DOUBLE-BLANK@@ ~*/ _checkItemPrereqs(item) { if (!item.system.prereqs) { return true; @@ -295,6 +363,7 @@ class BladesActor extends Actor { } return true; } + /*~ @@DOUBLE-BLANK@@ ~*/ _processPrereqArray(pReqArray, pType, hitRecord) { while (pReqArray.length) { const pString = pReqArray.pop(); @@ -305,6 +374,7 @@ class BladesActor extends Actor { } return true; } + /*~ @@DOUBLE-BLANK@@ ~*/ _processPrereqType(pType, pString, hitRecord) { switch (pType) { case PrereqType.HasActiveItem: { @@ -319,6 +389,7 @@ class BladesActor extends Actor { default: return true; } } + /*~ @@DOUBLE-BLANK@@ ~*/ _processActiveItemPrereq(pString, hitRecord, pType) { const thisItem = this.activeSubItems .filter((i) => !hitRecord[pType]?.includes(i.id)) @@ -331,6 +402,7 @@ class BladesActor extends Actor { return false; } } + /*~ @@DOUBLE-BLANK@@ ~*/ _processActiveItemsByTagPrereq(pString, hitRecord, pType) { const thisItem = this.activeSubItems .filter((i) => !hitRecord[pType]?.includes(i.id)) @@ -343,6 +415,7 @@ class BladesActor extends Actor { return false; } } + /*~ @@DOUBLE-BLANK@@ ~*/ _processAdvancedPlaybookPrereq() { if (!BladesActor.IsType(this, BladesActorType.pc)) { return false; @@ -352,12 +425,20 @@ class BladesActor extends Actor { } return true; } + /*~ @@DOUBLE-BLANK@@ ~*/ _processEmbeddedItemMatches(globalItems) { + /*~ @@DOUBLE-BLANK@@ ~*/ return globalItems + /*~ @@DOUBLE-BLANK@@ ~*/ + // Step 1: Filter out globals that fail prereqs. .filter((item) => this._checkItemPrereqs(item)) + /*~ @@DOUBLE-BLANK@@ ~*/ + // Step 2: Filter out already-active items based on max_per_score (unless MultiplesOk) .filter((gItem) => gItem.hasTag(Tag.System.MultiplesOK) || (gItem.system.max_per_score ?? 1) > this.activeSubItems.filter((sItem) => sItem.system.world_name === gItem.system.world_name).length) + /*~ @@DOUBLE-BLANK@@ ~*/ + // Step 3: Replace with matching Archived, Embedded subItems .map((gItem) => { const matchingSubItems = this.archivedSubItems.filter((sItem) => sItem.system.world_name === gItem.system.world_name); if (matchingSubItems.length > 0) { @@ -368,6 +449,8 @@ class BladesActor extends Actor { } }) .flat() + /*~ @@DOUBLE-BLANK@@ ~*/ + // Step 4: Apply CSS classes .map((sItem) => { sItem.dialogCSSClasses = ""; const cssClasses = []; @@ -402,11 +485,15 @@ class BladesActor extends Actor { cssClasses.push("expensive"); } } + /*~ @@DOUBLE-BLANK@@ ~*/ if (cssClasses.length > 0) { sItem.dialogCSSClasses = cssClasses.join(" "); } + /*~ @@DOUBLE-BLANK@@ ~*/ return sItem; }) + /*~ @@DOUBLE-BLANK@@ ~*/ + // Step 5: Sort by featured, then by fine, then by world_name, then embedded first sorted by name .sort((a, b) => { if (a.hasTag(Tag.System.Featured) && !b.hasTag(Tag.System.Featured)) { return -1; @@ -450,6 +537,7 @@ class BladesActor extends Actor { return 0; }); } + /*~ @@DOUBLE-BLANK@@ ~*/ getDialogItems(category) { const dialogData = {}; const isPC = BladesActor.IsType(this, BladesActorType.pc); @@ -459,6 +547,7 @@ class BladesActor extends Actor { return false; } const { playbookName } = this; + /*~ @@DOUBLE-BLANK@@ ~*/ if (category === SelectionCategory.Heritage && isPC) { dialogData.Main = this._processEmbeddedItemMatches(BladesItem.GetTypeWithTags(BladesItemType.heritage)); } @@ -494,15 +583,19 @@ class BladesActor extends Actor { ...BladesItem.GetTypeWithTags(BladesItemType.gear, Tag.Gear.General) ]) .filter((item) => self.remainingLoad >= item.system.load); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Two tabs, one for playbook and the other for general items dialogData[playbookName] = gearItems.filter((item) => item.hasTag(playbookName)); dialogData.General = gearItems .filter((item) => item.hasTag(Tag.Gear.General)) + // Remove featured class from General items .map((item) => { if (item.dialogCSSClasses) { item.dialogCSSClasses = item.dialogCSSClasses.replace(/featured-item\s?/g, ""); } return item; }) + // Re-sort by world_name .sort((a, b) => { if (a.system.world_name > b.system.world_name) { return 1; @@ -521,12 +614,14 @@ class BladesActor extends Actor { dialogData[playbookName] = this._processEmbeddedItemMatches(BladesItem.GetTypeWithTags(BladesItemType.ability, playbookName)); dialogData.Veteran = this._processEmbeddedItemMatches(BladesItem.GetTypeWithTags(BladesItemType.ability)) .filter((item) => !item.hasTag(playbookName)) + // Remove featured class from Veteran items .map((item) => { if (item.dialogCSSClasses) { item.dialogCSSClasses = item.dialogCSSClasses.replace(/featured-item\s?/g, ""); } return item; }) + // Re-sort by world_name .sort((a, b) => { if (a.system.world_name > b.system.world_name) { return 1; @@ -545,8 +640,10 @@ class BladesActor extends Actor { dialogData[playbookName] = this._processEmbeddedItemMatches(BladesItem.GetTypeWithTags(BladesItemType.crew_upgrade, playbookName)); dialogData.General = this._processEmbeddedItemMatches(BladesItem.GetTypeWithTags(BladesItemType.crew_upgrade, Tag.Gear.General)); } + /*~ @@DOUBLE-BLANK@@ ~*/ return dialogData; } + /*~ @@DOUBLE-BLANK@@ ~*/ getSubItem(itemRef, activeOnly = false) { const activeCheck = (i) => !activeOnly || !i.hasTag(Tag.System.Archived); if (typeof itemRef === "string" && this.items.get(itemRef)) { @@ -567,6 +664,7 @@ class BladesActor extends Actor { ?? this.items.find((item) => item.system.world_name === globalItem.system.world_name && activeCheck(item)); } } + /*~ @@DOUBLE-BLANK@@ ~*/ hasSubItemOf(itemRef) { const item = BladesItem.Get(itemRef); if (!item) { @@ -574,6 +672,7 @@ class BladesActor extends Actor { } return Boolean(this.items.find((i) => i.system.world_name === item.system.world_name)); } + /*~ @@DOUBLE-BLANK@@ ~*/ hasActiveSubItemOf(itemRef) { const item = BladesItem.Get(itemRef); if (!item) { @@ -582,34 +681,57 @@ class BladesActor extends Actor { return Boolean(this.items.find((i) => !i.hasTag(Tag.System.Archived) && i.system.world_name === item.system.world_name)); } + /*~ @@DOUBLE-BLANK@@ ~*/ async addSubItem(itemRef) { - function isBladesItemUniqueTypes(type) { + /*~ @@DOUBLE-BLANK@@ ~*/ + /** + * Determines whether a submitted BladesItemType is a type that should appear only once + * on any given Actor. + * @param {BladesItemType} type + * @returns {boolean} True if the type is a BladesItemUniqueTypes + **/ + function isBladesItemUniqueTypes(type) { return Object.values(BladesItemUniqueTypes).includes(type); } + /*~ @@DOUBLE-BLANK@@ ~*/ eLog.checkLog3("subitems", "[addSubItem] itemRef", itemRef); + /*~ @@DOUBLE-BLANK@@ ~*/ let focusItem; + /*~ @@DOUBLE-BLANK@@ ~*/ + // Does an embedded copy of this item already exist on the character? const embeddedItem = this.getSubItem(itemRef); + /*~ @@DOUBLE-BLANK@@ ~*/ if (embeddedItem) { + /*~ @@DOUBLE-BLANK@@ ~*/ + // Is it an archived Item? if (embeddedItem.hasTag(Tag.System.Archived)) { + // Unarchive it & make it the focus item. await embeddedItem.remTag(Tag.System.Archived); focusItem = embeddedItem; eLog.checkLog3("subitems", `[addSubItem] IS ARCHIVED EMBEDDED > Removing 'Archived' Tag, '${focusItem.id}':`, focusItem); } - else { + else { // Otherwise... + // Duplicate the item, and make the newly-created item the focus. focusItem = await BladesItem.create([embeddedItem], { parent: this }); eLog.checkLog3("subitems", `[addSubItem] IS ACTIVE EMBEDDED > Duplicating, focusItem '${focusItem.id}':`, focusItem); } } else { + // Is it not embedded at all? Embed from global instance. const globalItem = BladesItem.Get(itemRef); + /*~ @@DOUBLE-BLANK@@ ~*/ eLog.checkLog3("subitems", `[addSubItem] IS NOT EMBEDDED > Fetching Global, globalItem '${globalItem?.id}':`, globalItem); + /*~ @@DOUBLE-BLANK@@ ~*/ if (!globalItem) { return; } focusItem = await BladesItem.create([globalItem], { parent: this }); focusItem = this.items.getName(globalItem.name); } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Is this item type limited to one per actor? if (focusItem && isBladesItemUniqueTypes(focusItem.type)) { + // ... then archive all other versions. await Promise.all(this.activeSubItems .filter((subItem) => subItem.type === focusItem?.type && subItem.system.world_name !== focusItem?.system.world_name @@ -617,6 +739,7 @@ class BladesActor extends Actor { .map(this.remSubItem.bind(this))); } } + /*~ @@DOUBLE-BLANK@@ ~*/ async remSubItem(itemRef) { const subItem = this.getSubItem(itemRef); if (!subItem) { @@ -632,6 +755,7 @@ class BladesActor extends Actor { } await subItem.addTag(Tag.System.Archived); } + /*~ @@DOUBLE-BLANK@@ ~*/ async purgeSubItem(itemRef) { const subItem = this.getSubItem(itemRef); if (!subItem || subItem.hasTag(Tag.System.Archived)) { @@ -639,13 +763,40 @@ class BladesActor extends Actor { } await subItem.delete(); } - + /*~ @@DOUBLE-BLANK@@ ~*/ + // #endregion + // #region Advancement Implementation ~ + // get totalAbilityPoints(): number { + // if (!BladesActor.IsType(this, BladesActorType.pc, BladesActorType.crew)) { return 0 } + // if (!this.playbook) { return 0 } + // switch (this.type) { + // case BladesActorType.pc: return this.system.advancement.ability ?? 0; + // case BladesActorType.crew: return Math.floor(0.5 * (this.system.advancement.general ?? 0)) + // + (this.system.advancement.ability ?? 0); + // default: return 0; + // } + // } + // get spentAbilityPoints(): number { + // if (!BladesActor.IsType(this, BladesActorType.pc, BladesActorType.crew)) { return 0 } + // if (!this.playbook) { return 0 } + // return this.abilities.reduce((total, ability) => total + (ability.system.price ?? 1), 0); + // } + // get getAvailableAdvancements("Ability")(): number { + // if (!BladesActor.IsType(this, BladesActorType.pc, BladesActorType.crew)) { return 0 } + // if (!this.playbook) { return 0 } + // return this.totalAbilityPoints - this.spentAbilityPoints; + // } + /*~ @@DOUBLE-BLANK@@ ~*/ + /* Need simple getters for total ability & upgrade points that check for PRICES of items + (upgrade.system.price ?? 1) */ + /*~ @@DOUBLE-BLANK@@ ~*/ async grantAdvancementPoints(allowedTypes, amount = 1) { const aPtKey = Array.isArray(allowedTypes) ? [...allowedTypes].sort((a, b) => a.localeCompare(b)).join("_") : allowedTypes; await this.update({ [`system.advancement_points.${aPtKey}`]: (this.system.advancement_points?.[aPtKey] ?? 0) + amount }); } + /*~ @@DOUBLE-BLANK@@ ~*/ async removeAdvancementPoints(allowedTypes, amount = 1) { const aPtKey = Array.isArray(allowedTypes) ? [...allowedTypes].sort((a, b) => a.localeCompare(b)).join("_") @@ -658,6 +809,7 @@ class BladesActor extends Actor { await this.update({ [`system.advancement_points.${aPtKey}`]: newCount }); } } + /*~ @@DOUBLE-BLANK@@ ~*/ getAvailableAdvancements(trait) { if (!BladesActor.IsType(this, BladesActorType.pc, BladesActorType.crew)) { return 0; @@ -670,10 +822,12 @@ class BladesActor extends Actor { const spentCohort = this.cohorts.length; return Math.max(0, pointsCohort - spentCohort); } + /*~ @@DOUBLE-BLANK@@ ~*/ const pointsAbility = this.system.advancement_points?.[AdvancementPoint.Ability] ?? 0; const pointsCohortType = this.system.advancement_points?.[AdvancementPoint.CohortType] ?? 0; const pointsUpgrade = this.system.advancement_points?.[AdvancementPoint.Upgrade] ?? 0; const pointsUpgradeOrAbility = this.system.advancement_points?.[AdvancementPoint.UpgradeOrAbility] ?? 0; + /*~ @@DOUBLE-BLANK@@ ~*/ const spentAbility = U.sum(this.items .filter((item) => BladesItem.IsType(item, BladesItemType.ability, BladesItemType.crew_ability)) .map((abil) => abil.system.price ?? 1)); @@ -682,13 +836,16 @@ class BladesActor extends Actor { const spentUpgrade = U.sum(this.items .filter((item) => BladesItem.IsType(item, BladesItemType.crew_upgrade)) .map((upgrade) => upgrade.system.price ?? 1)); + /*~ @@DOUBLE-BLANK@@ ~*/ const excessUpgrade = Math.max(0, spentUpgrade - pointsUpgrade); const excessCohortType = Math.max(0, spentCohortType - pointsCohortType); const excessAbility = Math.max(0, spentAbility - pointsAbility); + /*~ @@DOUBLE-BLANK@@ ~*/ const remainingAbility = Math.max(0, pointsAbility - spentAbility); const remainingCohortType = Math.max(0, pointsCohortType - spentCohortType); const remainingUpgrade = Math.max(0, pointsUpgrade - spentUpgrade); const remainingUpgradeOrAbility = Math.max(0, pointsUpgradeOrAbility - excessUpgrade - (2 * excessAbility) - (2 * excessCohortType)); + /*~ @@DOUBLE-BLANK@@ ~*/ if (trait === "Ability") { return remainingAbility + Math.floor(0.5 * remainingUpgradeOrAbility); } @@ -698,16 +855,26 @@ class BladesActor extends Actor { if (trait === "CohortType") { return remainingCohortType + remainingUpgradeOrAbility; } + /*~ @@DOUBLE-BLANK@@ ~*/ return 0; } + /*~ @@DOUBLE-BLANK@@ ~*/ get availableAbilityPoints() { return this.getAvailableAdvancements("Ability"); } + /*~ @@DOUBLE-BLANK@@ ~*/ get availableUpgradePoints() { return this.getAvailableAdvancements("Upgrade"); } + /*~ @@DOUBLE-BLANK@@ ~*/ get availableCohortPoints() { return this.getAvailableAdvancements("Cohort"); } + /*~ @@DOUBLE-BLANK@@ ~*/ get availableCohortTypePoints() { return this.getAvailableAdvancements("CohortType"); } + /*~ @@DOUBLE-BLANK@@ ~*/ get canPurchaseAbility() { return this.availableAbilityPoints > 0; } + /*~ @@DOUBLE-BLANK@@ ~*/ get canPurchaseUpgrade() { return this.availableUpgradePoints > 0; } + /*~ @@DOUBLE-BLANK@@ ~*/ get canPurchaseCohort() { return this.availableCohortPoints > 0; } + /*~ @@DOUBLE-BLANK@@ ~*/ get canPurchaseCohortType() { return this.availableCohortTypePoints > 0; } + /*~ @@DOUBLE-BLANK@@ ~*/ async advancePlaybook() { if (!BladesActor.IsType(this, BladesActorType.pc, BladesActorType.crew) || !this.playbook) { return; @@ -728,17 +895,27 @@ class BladesActor extends Actor { this.grantAdvancementPoints(AdvancementPoint.UpgradeOrAbility, 2); } } + /*~ @@DOUBLE-BLANK@@ ~*/ async advanceAttribute(attribute) { await this.update({ [`system.experience.${attribute}.value`]: 0 }); const actions = C.Action[attribute].map((action) => `${U.tCase(action)}`); BladesPushAlert.Get().pushToAll("GM", `${this.name} Advances their ${U.uCase(attribute)}!`, `${this.name}, add a dot to one of ${U.oxfordize(actions, true, "or")}.`); } + /*~ @@DOUBLE-BLANK@@ ~*/ + // #endregion + // #region BladesSubActor Implementation ~ + /*~ @@DOUBLE-BLANK@@ ~*/ parentActor; + /*~ @@DOUBLE-BLANK@@ ~*/ get isSubActor() { return this.parentActor !== undefined; } - + /*~ @@DOUBLE-BLANK@@ ~*/ + // #endregion + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region BladesRoll Implementation get rollModsData() { return BladesRollMod.ParseDocRollMods(this); } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollFactors() { const factorData = { [Factor.tier]: { @@ -764,13 +941,24 @@ class BladesActor extends Actor { highFavorsPC: true } }; + /*~ @@DOUBLE-BLANK@@ ~*/ return factorData; } + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region BladesRoll.PrimaryDoc Implementation + /*~ @@DOUBLE-BLANK@@ ~*/ get rollPrimaryID() { return this.id; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollPrimaryDoc() { return this; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollPrimaryName() { return this.name; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollPrimaryType() { return this.type; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollPrimaryImg() { return this.img; } + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region BladesCrew Implementation ~ + /*~ @@DOUBLE-BLANK@@ ~*/ get members() { if (!BladesActor.IsType(this, BladesActorType.crew)) { return []; @@ -778,6 +966,7 @@ class BladesActor extends Actor { const self = this; return BladesActor.GetTypeWithTags(BladesActorType.pc).filter((actor) => actor.isMember(self)); } + /*~ @@DOUBLE-BLANK@@ ~*/ get contacts() { if (!BladesActor.IsType(this, BladesActorType.crew) || !this.playbook) { return []; @@ -785,12 +974,14 @@ class BladesActor extends Actor { const self = this; return this.activeSubActors.filter((actor) => actor.hasTag(self.playbookName)); } + /*~ @@DOUBLE-BLANK@@ ~*/ get claims() { if (!BladesActor.IsType(this, BladesActorType.crew) || !this.playbook) { return {}; } return this.playbook.system.turfs; } + /*~ @@DOUBLE-BLANK@@ ~*/ get turfCount() { if (!BladesActor.IsType(this, BladesActorType.crew) || !this.playbook) { return 0; @@ -798,6 +989,7 @@ class BladesActor extends Actor { return Object.values(this.playbook.system.turfs) .filter((claim) => claim.isTurf && claim.value).length; } + /*~ @@DOUBLE-BLANK@@ ~*/ get upgrades() { if (!BladesActor.IsType(this, BladesActorType.crew) || !this.playbook) { return []; @@ -805,14 +997,19 @@ class BladesActor extends Actor { return this.activeSubItems .filter((item) => item.type === BladesItemType.crew_upgrade); } + /*~ @@DOUBLE-BLANK@@ ~*/ get cohorts() { return this.activeSubItems .filter((item) => [BladesItemType.cohort_gang, BladesItemType.cohort_expert].includes(item.type)); } + /*~ @@DOUBLE-BLANK@@ ~*/ getTaggedItemBonuses(tags) { - return tags.length; + // Check ACTIVE EFFECTS supplied by upgrade/ability against submitted tags? + return tags.length; // Placeholder to avoid linter error } - + // #endregion + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region PREPARING DERIVED DATA prepareDerivedData() { if (BladesActor.IsType(this, BladesActorType.pc)) { this._preparePCData(this.system); @@ -821,10 +1018,13 @@ class BladesActor extends Actor { this._prepareCrewData(this.system); } } + /*~ @@DOUBLE-BLANK@@ ~*/ _preparePCData(system) { if (!BladesActor.IsType(this, BladesActorType.pc)) { return; } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Extract experience clues from playbook item, if any if (this.playbook) { system.experience.clues = [ ...system.experience.clues, @@ -832,6 +1032,7 @@ class BladesActor extends Actor { .filter((clue) => Boolean(clue.trim())) ]; } + // Extract gather information questions from playbook item, if any if (this.playbook) { system.gather_info = [ ...system.gather_info, @@ -840,10 +1041,13 @@ class BladesActor extends Actor { ]; } } + /*~ @@DOUBLE-BLANK@@ ~*/ _prepareCrewData(system) { if (!BladesActor.IsType(this, BladesActorType.crew)) { return; } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Extract experience clues and turfs from playbook item, if any if (this.playbook) { system.experience.clues = [ ...system.experience.clues, @@ -853,7 +1057,11 @@ class BladesActor extends Actor { system.turfs = this.playbook.system.turfs; } } - + /*~ @@DOUBLE-BLANK@@ ~*/ + // #endregion + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region OVERRIDES: _onCreateDescendantDocuments, update ~ + // @ts-expect-error New method not defined in @league VTT types. async _onCreateDescendantDocuments(parent, collection, docs, data, options, userId) { await Promise.all(docs.map(async (doc) => { if (BladesItem.IsType(doc, BladesItemType.playbook, BladesItemType.crew_playbook)) { @@ -862,8 +1070,12 @@ class BladesActor extends Actor { .map((aItem) => this.remSubItem(aItem))); } })); + /*~ @@DOUBLE-BLANK@@ ~*/ + // @ts-expect-error New method not defined in @league VTT types. await super._onCreateDescendantDocuments(parent, collection, docs, data, options, userId); + /*~ @@DOUBLE-BLANK@@ ~*/ eLog.checkLog("actorTrigger", "_onCreateDescendantDocuments", { parent, collection, docs, data, options, userId }); + /*~ @@DOUBLE-BLANK@@ ~*/ docs.forEach((doc) => { if (BladesItem.IsType(doc, BladesItemType.vice) && BladesActor.IsType(this, BladesActorType.pc)) { this.activeSubActors @@ -872,6 +1084,7 @@ class BladesActor extends Actor { } }); } + /*~ @@DOUBLE-BLANK@@ ~*/ async update(updateData, context, isSkippingSubActorCheck = false) { if (!updateData) { return super.update(updateData); @@ -880,14 +1093,17 @@ class BladesActor extends Actor { if (!this.playbook) { return undefined; } + /*~ @@DOUBLE-BLANK@@ ~*/ eLog.checkLog("actorTrigger", "Updating Crew", { updateData }); const playbookUpdateData = Object.fromEntries(Object.entries(flattenObject(updateData)) .filter(([key, _]) => key.startsWith("system.turfs."))); updateData = Object.fromEntries(Object.entries(flattenObject(updateData)) .filter(([key, _]) => !key.startsWith("system.turfs."))); eLog.checkLog("actorTrigger", "Updating Crew", { crewUpdateData: updateData, playbookUpdateData }); + /*~ @@DOUBLE-BLANK@@ ~*/ const diffPlaybookData = diffObject(flattenObject(this.playbook), playbookUpdateData); delete diffPlaybookData._id; + /*~ @@DOUBLE-BLANK@@ ~*/ if (!U.isEmpty(diffPlaybookData)) { await this.playbook.update(playbookUpdateData, context) .then(() => this.sheet?.render(false)); @@ -897,18 +1113,36 @@ class BladesActor extends Actor { || BladesActor.IsType(this, BladesActorType.faction)) && this.parentActor && !isSkippingSubActorCheck) { + // This is an embedded Actor: Update it as a subActor of parentActor. return this.parentActor.updateSubActor(this.id, updateData) .then(() => this); } + /*~ @@DOUBLE-BLANK@@ ~*/ return super.update(updateData, context); } - - - createListOfDiceMods(rs, re, s) { + /*~ @@DOUBLE-BLANK@@ ~*/ + // #endregion + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region Rolling Dice ~ + /*~ @@DOUBLE-BLANK@@ ~*/ + /** + * Creates modifiers for dice roll. + * + * @param {int} rs + * Min die modifier + * @param {int} re + * Max die modifier + * @param {int} s + * Selected die + */ + createListOfDiceMods(rs, re, s) { + /*~ @@DOUBLE-BLANK@@ ~*/ let text = ""; + /*~ @@DOUBLE-BLANK@@ ~*/ if (s === "") { s = 0; } + /*~ @@DOUBLE-BLANK@@ ~*/ for (let i = rs; i <= re; i++) { let plus = ""; if (i >= 0) { @@ -918,19 +1152,32 @@ class BladesActor extends Actor { if (i === s) { text += " selected"; } + /*~ @@DOUBLE-BLANK@@ ~*/ text += `>${plus}${i}d`; } + /*~ @@DOUBLE-BLANK@@ ~*/ return text; } - + /*~ @@DOUBLE-BLANK@@ ~*/ + // #endregion Rolling Dice + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region NPC Randomizers ~ updateRandomizers() { if (!BladesActor.IsType(this, BladesActorType.npc)) { return; } const titleChance = 0.05; const suffixChance = 0.01; + /*~ @@DOUBLE-BLANK@@ ~*/ const { persona, secret, random } = this.system; - function sampleArray(arr, ...curVals) { + /*~ @@DOUBLE-BLANK@@ ~*/ + /** + * Returns a random element from an array, optionally excluding all values + * passed as subsequent parameters. + * @param {string[]} arr The array to randomly select from. + * @param {...string} curVals The values to exclude from the sample. + */ + function sampleArray(arr, ...curVals) { arr = arr.filter((elem) => !curVals.includes(elem)); if (!arr.length) { return ""; @@ -969,6 +1216,7 @@ class BladesActor extends Actor { ...(gen.charAt(0).toLowerCase() !== "f" ? Randomizers.NPC.style.male : []) ], persona.style.value) }; + /*~ @@DOUBLE-BLANK@@ ~*/ const gender = persona.gender.isLocked ? persona.gender.value : randomGen.gender(); const updateKeys = [ ...Object.keys(persona).filter((key) => !persona[key]?.isLocked), @@ -976,8 +1224,10 @@ class BladesActor extends Actor { ...Object.keys(secret).filter((key) => !secret[key]?.isLocked) .map((secretKey) => `secret-${secretKey}`) ]; + /*~ @@DOUBLE-BLANK@@ ~*/ eLog.checkLog("Update Keys", { updateKeys }); const updateData = {}; + /*~ @@DOUBLE-BLANK@@ ~*/ updateKeys.forEach((key) => { switch (key) { case "name": @@ -1061,7 +1311,10 @@ class BladesActor extends Actor { } } }); + /*~ @@DOUBLE-BLANK@@ ~*/ this.update(updateData); } } -export default BladesActor; \ No newline at end of file +/*~ @@DOUBLE-BLANK@@ ~*/ +export default BladesActor; +/*~ @@DOUBLE-BLANK@@ ~*/ diff --git a/module/BladesChat.js b/module/BladesChat.js new file mode 100644 index 00000000..cf8d0910 --- /dev/null +++ b/module/BladesChat.js @@ -0,0 +1,86 @@ +// #region IMPORTS ~ +/* import U from "./core/utilities.js"; +import C, { + BladesActorType, BladesItemType, RollPermissions, RollType, RollSubType, + RollModStatus, RollModSection, ActionTrait, DowntimeAction, AttributeTrait, + Position, Effect, Factor, RollResult, RollPhase, ConsequenceType, Tag +} from "./core/constants.js"; +import {BladesActor, BladesPC, BladesCrew} from "./documents/BladesActorProxy.js"; +import {BladesItem, BladesGMTracker} from "./documents/BladesItemProxy.js"; +import {ApplyTooltipListeners, ApplyConsequenceListeners} from "./core/gsap.js"; +import BladesDialog from "./BladesDialog.js"; */ +/*~ @@DOUBLE-BLANK@@ ~*/ +import U from "./core/utilities.js"; +import C, { BladesItemType } from "./core/constants.js"; +import { BladesPC } from "./documents/BladesActorProxy.js"; +import { BladesItem } from "./documents/BladesItemProxy.js"; +import { ApplyTooltipListeners, ApplyConsequenceListeners } from "./core/gsap.js"; +// #endregion +/*~ @@DOUBLE-BLANK@@ ~*/ +class BladesChat extends ChatMessage { + /*~ @@DOUBLE-BLANK@@ ~*/ + static Initialize() { + Hooks.on("renderChatMessage", (_msg, html) => { + ApplyTooltipListeners(html); + ApplyConsequenceListeners(html); + }); + return loadTemplates([ + "systems/eunos-blades/templates/chat/roll-result-action-roll.hbs", + "systems/eunos-blades/templates/chat/roll-result-resistance-roll.hbs", + "systems/eunos-blades/templates/chat/roll-result-fortune-roll.hbs", + "systems/eunos-blades/templates/chat/roll-result-indulgevice-roll.hbs" + ]); + } + /*~ @@DOUBLE-BLANK@@ ~*/ + static GetRollSpeaker(rollInst) { + /*~ @@DOUBLE-BLANK@@ ~*/ + // Get initial speaker data + const speaker = BladesChat.getSpeaker(); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Compare against rollInst.rollPrimary and modify accordingly. + const { rollPrimaryID, rollPrimaryName, rollPrimaryType, rollPrimaryDoc } = rollInst.rollPrimary; + /*~ @@DOUBLE-BLANK@@ ~*/ + speaker.alias = rollPrimaryName; + /*~ @@DOUBLE-BLANK@@ ~*/ + if (BladesItem.IsType(BladesItemType.cohort_gang, BladesItemType.cohort_expert)) { + speaker.actor = rollPrimaryDoc?.parent?.id ?? speaker.actor; + if (rollPrimaryDoc?.parent instanceof BladesPC) { + speaker.alias = `${speaker.alias} (${rollPrimaryDoc.parent.name})`; + } + } + else if (BladesItem.IsType(BladesItemType.gm_tracker, BladesItemType.score)) { + speaker.actor = null; + speaker.alias = "The Gamemaster"; + } + else if (rollPrimaryID) { + speaker.actor = rollPrimaryID; + } + /*~ @@DOUBLE-BLANK@@ ~*/ + speaker.alias = `${speaker.alias} Rolls ...`; + /*~ @@DOUBLE-BLANK@@ ~*/ + return speaker; + } + /*~ @@DOUBLE-BLANK@@ ~*/ + static async ConstructRollOutput(rollInst) { + const speaker = BladesChat.GetRollSpeaker(rollInst); + /*~ @@DOUBLE-BLANK@@ ~*/ + const template = `systems/eunos-blades/templates/chat/roll-result-${U.lCase(rollInst.rollType)}-roll.hbs`; + /*~ @@DOUBLE-BLANK@@ ~*/ + const templateData = rollInst; + if (rollInst.rollResult) { + templateData.rollResultDescription = C.RollResultDescriptions[rollInst.finalPosition][rollInst.rollResult]; + } + /*~ @@DOUBLE-BLANK@@ ~*/ + const renderedHTML = await renderTemplate(template, rollInst); + /*~ @@DOUBLE-BLANK@@ ~*/ + const messageData = { + speaker, + content: renderedHTML + }; + /*~ @@DOUBLE-BLANK@@ ~*/ + BladesChat.create(messageData, {}); + } +} +/*~ @@DOUBLE-BLANK@@ ~*/ +export default BladesChat; +/*~ @@DOUBLE-BLANK@@ ~*/ diff --git a/module/BladesDialog.js b/module/BladesDialog.js index a1bb60bf..65da64f5 100644 --- a/module/BladesDialog.js +++ b/module/BladesDialog.js @@ -1,16 +1,10 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - import { ApplyTooltipListeners } from "./core/gsap.js"; import U from "./core/utilities.js"; import { BladesActor, BladesPC } from "./documents/BladesActorProxy.js"; import BladesRoll from "./BladesRoll.js"; import C, { RollResult, AttributeTrait, Position } from "./core/constants.js"; import BladesAI, { AGENTS } from "./core/ai.js"; +/*~ @@DOUBLE-BLANK@@ ~*/ const baseCsqData = { [RollResult.partial]: { 0: { name: "", type: "", attribute: "" }, @@ -23,6 +17,8 @@ const baseCsqData = { 2: { name: "", type: "", attribute: "" } } }; +/*~ @@DOUBLE-BLANK@@ ~*/ +// eslint-disable-next-line no-shadow export var SelectionCategory; (function (SelectionCategory) { SelectionCategory["Heritage"] = "Heritage"; @@ -47,12 +43,16 @@ export var SelectionCategory; SelectionCategory["Member"] = "Member"; SelectionCategory["Contact"] = "Contact"; })(SelectionCategory || (SelectionCategory = {})); +/*~ @@DOUBLE-BLANK@@ ~*/ +// eslint-disable-next-line no-shadow export var BladesDialogType; (function (BladesDialogType) { BladesDialogType["Selection"] = "Selection"; BladesDialogType["Consequence"] = "Consequence"; })(BladesDialogType || (BladesDialogType = {})); +/*~ @@DOUBLE-BLANK@@ ~*/ class BladesDialog extends Dialog { + /*~ @@DOUBLE-BLANK@@ ~*/ static get defaultOptions() { return foundry.utils.mergeObject(super.defaultOptions, { classes: ["eunos-blades", "sheet", "dialog"], @@ -61,6 +61,7 @@ class BladesDialog extends Dialog { tabs: [{ navSelector: ".nav-tabs", contentSelector: ".tab-content", initial: "front" }] }); } + /*~ @@DOUBLE-BLANK@@ ~*/ static Initialize() { return loadTemplates([ "systems/eunos-blades/templates/dialog-selection.hbs", @@ -68,7 +69,9 @@ class BladesDialog extends Dialog { "systems/eunos-blades/templates/parts/dialog-consequence-block.hbs" ]); } + /*~ @@DOUBLE-BLANK@@ ~*/ static async DisplaySelectionDialog(parent, title, docType, tabs, tags) { + /*~ @@DOUBLE-BLANK@@ ~*/ const app = new BladesDialog({ parent, title, @@ -88,9 +91,12 @@ class BladesDialog extends Dialog { }, default: "cancel" }); + /*~ @@DOUBLE-BLANK@@ ~*/ return app.hasItems ? app.render(true, { width: app.width }) : undefined; } + /*~ @@DOUBLE-BLANK@@ ~*/ static async DisplayRollConsequenceDialog(rollInst) { + /*~ @@DOUBLE-BLANK@@ ~*/ const app = new BladesDialog({ parent: rollInst, title: "Consequences", @@ -114,44 +120,58 @@ class BladesDialog extends Dialog { }, default: "apply" }, { classes: ["eunos-blades", "sheet", "dialog", "consequence-dialog"] }); + /*~ @@DOUBLE-BLANK@@ ~*/ return app._render(true, { width: app.width }).then(() => eLog.checkLog3("dialog", "Dialog Instance", { this: app })); } + /*~ @@DOUBLE-BLANK@@ ~*/ get template() { if (this.dialogType === BladesDialogType.Selection) { return "systems/eunos-blades/templates/dialog-selection.hbs"; } return "systems/eunos-blades/templates/dialog-consequence.hbs"; } + /*~ @@DOUBLE-BLANK@@ ~*/ get hasItems() { return Object.values(this.tabs ?? []).some((tabItems) => tabItems.length > 0); } + /*~ @@DOUBLE-BLANK@@ ~*/ parent; + /*~ @@DOUBLE-BLANK@@ ~*/ tabs; + /*~ @@DOUBLE-BLANK@@ ~*/ dialogType; + /*~ @@DOUBLE-BLANK@@ ~*/ tags = []; + /*~ @@DOUBLE-BLANK@@ ~*/ width; + /*~ @@DOUBLE-BLANK@@ ~*/ docType; + /*~ @@DOUBLE-BLANK@@ ~*/ csqData = { [Position.controlled]: { ...baseCsqData }, [Position.risky]: { ...baseCsqData }, [Position.desperate]: { ...baseCsqData } }; + /*~ @@DOUBLE-BLANK@@ ~*/ constructor(data, options) { super(data, options); + /*~ @@DOUBLE-BLANK@@ ~*/ this.dialogType = data.dialogType ?? BladesDialogType.Selection; this.parent = data.parent; this.width = 500; + /*~ @@DOUBLE-BLANK@@ ~*/ switch (this.dialogType) { case BladesDialogType.Selection: - this.constructSelectionData(data ); + this.constructSelectionData(data /* , options */); return; case BladesDialogType.Consequence: - this.constructConsequenceData(data ); + this.constructConsequenceData(data /* , options */); return; default: throw new Error(`Unrecognized type for BladesDialog constructor: '${this.dialogType}'`); } } - constructSelectionData(data ) { + /*~ @@DOUBLE-BLANK@@ ~*/ + constructSelectionData(data /* , options?: Partial */) { const validTabs = []; if (!data.tabs) { return; @@ -164,37 +184,46 @@ class BladesDialog extends Dialog { validTabs.push(tabName); } } + /*~ @@DOUBLE-BLANK@@ ~*/ if (validTabs.length === 1 && !("Main" in data.tabs)) { data.tabs.Main = [...data.tabs[validTabs[0]]]; delete data.tabs[validTabs[0]]; } + /*~ @@DOUBLE-BLANK@@ ~*/ this.docType = data.docType; this.tabs = data.tabs; this.tags = data.tags ?? []; this.width = 150 * Math.ceil(Math.sqrt(Object.values(data.tabs)[0].length)); } - constructConsequenceData(data ) { + /*~ @@DOUBLE-BLANK@@ ~*/ + constructConsequenceData(data /* , options?: Partial */) { eLog.checkLog3("dialog", "constructConsequenceData", { incoming: data }); if (this.parent instanceof BladesRoll) { + /*~ @@DOUBLE-BLANK@@ ~*/ this.csqData = U.objMerge(this.csqData, this.parent.getFlagVal("consequenceData") ?? {}); this._consequenceAI = new BladesAI(AGENTS.ConsequenceAdjuster); } } + /*~ @@DOUBLE-BLANK@@ ~*/ getData() { const data = super.getData(); + /*~ @@DOUBLE-BLANK@@ ~*/ switch (this.dialogType) { case BladesDialogType.Selection: return this.prepareSelectionData(data); case BladesDialogType.Consequence: return this.prepareConsequenceData(data); default: return null; } } + /*~ @@DOUBLE-BLANK@@ ~*/ prepareSelectionData(data) { data.title = this.title; data.tabs = this.tabs; data.docType = this.docType; data.tags = this.tags; + /*~ @@DOUBLE-BLANK@@ ~*/ return data; } + /*~ @@DOUBLE-BLANK@@ ~*/ prepareConsequenceData(data) { eLog.checkLog3("dialog", "prepareConsequenceData this.csqData", this.csqData); eLog.checkLog3("dialog", "prepareConsequenceData", { incoming: data }); @@ -210,6 +239,7 @@ class BladesDialog extends Dialog { eLog.checkLog3("dialog", "prepareConsequenceData", { outgoing: data }); return data; } + /*~ @@DOUBLE-BLANK@@ ~*/ get consequenceTypeOptions() { if (this.parent instanceof BladesRoll) { const returnData = {}; @@ -225,6 +255,7 @@ class BladesDialog extends Dialog { } return {}; } + /*~ @@DOUBLE-BLANK@@ ~*/ updateConsequenceDialog(html, isRendering = true) { if (!(this.parent instanceof BladesRoll)) { return; @@ -233,6 +264,7 @@ class BladesDialog extends Dialog { if (!(rollPrimaryDoc instanceof BladesPC)) { return; } + /*~ @@DOUBLE-BLANK@@ ~*/ [Position.controlled, Position.risky, Position.desperate].forEach((rollPos) => { const posCsqData = {}; [RollResult.partial, RollResult.fail].forEach((rollResult) => { @@ -283,17 +315,23 @@ class BladesDialog extends Dialog { this.render(); } } + /*~ @@DOUBLE-BLANK@@ ~*/ async writeToRollInstance(html) { if (this.parent instanceof BladesRoll) { this.updateConsequenceDialog(html, false); await this.parent.setFlagVal("consequenceData", this.csqData); } } + /*~ @@DOUBLE-BLANK@@ ~*/ _consequenceAI; + /*~ @@DOUBLE-BLANK@@ ~*/ async queryAI(event) { + // If the AI generator has not been initialized, do so. if (!this._consequenceAI) { this._consequenceAI = new BladesAI(AGENTS.ConsequenceAdjuster); } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Get the name of the consequence. const dataAction = event.currentTarget.dataset.action; if (dataAction && dataAction.startsWith("ai-query")) { const [rollPosition, rollResult, csqIndex] = dataAction.split(/-/).slice(2); @@ -306,11 +344,13 @@ class BladesDialog extends Dialog { } } } + /*~ @@DOUBLE-BLANK@@ ~*/ async setFlagVal(target, value) { if (this.parent instanceof BladesRoll) { return this.parent.setFlagVal(target, value, false); } } + /*~ @@DOUBLE-BLANK@@ ~*/ async addResistanceOptions(rollPosition, rollResult, cIndex, rOptions) { const cData = this.csqData[rollPosition][rollResult][cIndex]; if (!cData) { @@ -333,21 +373,30 @@ class BladesDialog extends Dialog { eLog.checkLog3("dialog", "addResistanceOptions() this.csqData", this.csqData); this.render(); } + /*~ @@DOUBLE-BLANK@@ ~*/ async selectResistOption(event) { eLog.checkLog3("dialog", "Clicked Resistance Option", event); const dataAction = event.currentTarget.dataset.action; if (dataAction && dataAction.startsWith("gm-select-toggle")) { const [rollPosition, rollResult, csqIndex, resIndex] = dataAction.split(/-/).slice(3); eLog.checkLog3("dialog", "... Action Passed", { rollResult, csqIndex, resIndex }); + // Get consequence data const cData = this.csqData[rollPosition][rollResult][csqIndex]; cData.resistOptions ??= {}; + /*~ @@DOUBLE-BLANK@@ ~*/ + // Toggle clicked resistance option cData.resistOptions[resIndex].isSelected = !cData.resistOptions[resIndex].isSelected; + /*~ @@DOUBLE-BLANK@@ ~*/ + // If resistance option is now selected... if (cData.resistOptions[resIndex].isSelected) { + // ... deselect other options Object.keys(cData.resistOptions) .filter((key) => key !== resIndex) .forEach((key) => { cData.resistOptions[key].isSelected = false; }); + /*~ @@DOUBLE-BLANK@@ ~*/ + // ... and set 'resistedTo' to this consequence. cData.resistedTo = cData.resistOptions[resIndex]; if (cData.resistedTo.type) { cData.resistedTo.icon = C.ConsequenceIcons[cData.resistedTo.type]; @@ -355,15 +404,22 @@ class BladesDialog extends Dialog { } } else { + // Otherwise, set 'resistedTo' to false. cData.resistedTo = false; } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Assign new cData instance. this.csqData[rollPosition][rollResult][csqIndex] = cData; this.render(); } } + /*~ @@DOUBLE-BLANK@@ ~*/ activateListeners(html) { super.activateListeners(html); + /*~ @@DOUBLE-BLANK@@ ~*/ + // ~ Tooltips ApplyTooltipListeners(html); + /*~ @@DOUBLE-BLANK@@ ~*/ switch (this.dialogType) { case BladesDialogType.Selection: this.activateSelectionListeners(html); @@ -373,8 +429,11 @@ class BladesDialog extends Dialog { break; } } + /*~ @@DOUBLE-BLANK@@ ~*/ activateSelectionListeners(html) { const self = this; + /*~ @@DOUBLE-BLANK@@ ~*/ + // ~ Changing Width on Tab Change Depending on Number of Items html.find(".nav-tabs .tab-selector").on("click", (event) => { const tabIndex = U.pInt($(event.currentTarget).data("tab")); const numItems = Object.values(self.tabs ?? [])[tabIndex].length; @@ -382,6 +441,8 @@ class BladesDialog extends Dialog { eLog.checkLog3("nav", "Nav Tab Size Recalculation", { tabIndex, numItems, width }); this.render(false, { width }); }); + /*~ @@DOUBLE-BLANK@@ ~*/ + // ~ Item Control html.find("[data-item-id]").on("click", function () { if ($(this).parent().hasClass("locked")) { return; @@ -399,7 +460,9 @@ class BladesDialog extends Dialog { } self.close(); }); + /*~ @@DOUBLE-BLANK@@ ~*/ } + /*~ @@DOUBLE-BLANK@@ ~*/ activateConsequenceListeners(html) { html.find("input").on({ blur: () => this.updateConsequenceDialog(html) }); html.find("select").on({ change: () => this.updateConsequenceDialog(html) }); @@ -407,4 +470,6 @@ class BladesDialog extends Dialog { html.find('[data-action^="gm-select-toggle"]').on({ click: (event) => this.selectResistOption(event) }); } } -export default BladesDialog; \ No newline at end of file +/*~ @@DOUBLE-BLANK@@ ~*/ +export default BladesDialog; +/*~ @@DOUBLE-BLANK@@ ~*/ diff --git a/module/BladesItem.js b/module/BladesItem.js index 7ff70c87..9e8f79dd 100644 --- a/module/BladesItem.js +++ b/module/BladesItem.js @@ -1,27 +1,29 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - import C, { BladesActorType, BladesItemType, Tag, Factor } from "./core/constants.js"; import U from "./core/utilities.js"; import { BladesActor } from "./documents/BladesActorProxy.js"; import { BladesRollMod } from "./BladesRoll.js"; +/*~ @@DOUBLE-BLANK@@ ~*/ class BladesItem extends Item { - + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region Static Overrides: Create ~ static async create(data, options = {}) { if (Array.isArray(data)) { data = data[0]; } data.system = data.system ?? {}; + /*~ @@DOUBLE-BLANK@@ ~*/ eLog.checkLog2("item", "BladesItem.create(data,options)", { data, options }); + /*~ @@DOUBLE-BLANK@@ ~*/ + // ~ Create world_name data.system.world_name = data.system.world_name ?? data.name.replace(/[^A-Za-z_0-9 ]/g, "").trim().replace(/ /g, "_"); + /*~ @@DOUBLE-BLANK@@ ~*/ return super.create(data, options); } - + // #endregion + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region BladesDocument Implementation static get All() { return game.items; } + /*~ @@DOUBLE-BLANK@@ ~*/ static Get(itemRef) { if (itemRef instanceof BladesItem) { return itemRef; @@ -32,6 +34,7 @@ class BladesItem extends Item { return BladesItem.All.find((a) => a.system.world_name === itemRef) || BladesItem.All.find((a) => a.name === itemRef); } + /*~ @@DOUBLE-BLANK@@ ~*/ static GetTypeWithTags(docType, ...tags) { if (Array.isArray(docType)) { return docType @@ -41,14 +44,18 @@ class BladesItem extends Item { return BladesItem.All.filter((item) => item.type === docType) .filter((item) => item.hasTag(...tags)); } + /*~ @@DOUBLE-BLANK@@ ~*/ static IsType(doc, ...types) { const typeSet = new Set(types); return doc instanceof BladesItem && typeSet.has(doc.type); } + /*~ @@DOUBLE-BLANK@@ ~*/ get tags() { return this.system.tags ?? []; } + /*~ @@DOUBLE-BLANK@@ ~*/ hasTag(...tags) { return tags.every((tag) => this.tags.includes(tag)); } + /*~ @@DOUBLE-BLANK@@ ~*/ async addTag(...tags) { const curTags = this.tags; tags.forEach((tag) => { @@ -59,10 +66,12 @@ class BladesItem extends Item { }); await this.update({ "system.tags": curTags }); } + /*~ @@DOUBLE-BLANK@@ ~*/ async remTag(...tags) { const curTags = this.tags.filter((tag) => !tags.includes(tag)); await this.update({ "system.tags": curTags }); } + /*~ @@DOUBLE-BLANK@@ ~*/ get tooltip() { const tooltipText = [ this.system.concept, @@ -74,7 +83,9 @@ class BladesItem extends Item { } return undefined; } + /*~ @@DOUBLE-BLANK@@ ~*/ dialogCSSClasses = ""; + /*~ @@DOUBLE-BLANK@@ ~*/ getFactorTotal(factor) { switch (factor) { case Factor.tier: { @@ -128,17 +139,23 @@ class BladesItem extends Item { default: return 0; } } - + // #endregion + // #region BladesItemDocument Implementation + /*~ @@DOUBLE-BLANK@@ ~*/ async archive() { await this.addTag(Tag.System.Archived); return this; } + /*~ @@DOUBLE-BLANK@@ ~*/ async unarchive() { await this.remTag(Tag.System.Archived); return this; } - - + /*~ @@DOUBLE-BLANK@@ ~*/ + // #endregion + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region BladesRoll Implementation + /*~ @@DOUBLE-BLANK@@ ~*/ get rollFactors() { const factorsMap = { [BladesItemType.cohort_gang]: [Factor.quality, Factor.scale], @@ -151,7 +168,9 @@ class BladesItem extends Item { if (!factorsMap[this.type]) { return {}; } + /*~ @@DOUBLE-BLANK@@ ~*/ const factors = factorsMap[this.type]; + /*~ @@DOUBLE-BLANK@@ ~*/ const factorData = {}; (factors ?? []).forEach((factor, i) => { const factorTotal = this.getFactorTotal(factor); @@ -168,34 +187,63 @@ class BladesItem extends Item { cssClasses: `factor-gold${i === 0 ? " factor-main" : ""}` }; }); + /*~ @@DOUBLE-BLANK@@ ~*/ return factorData; } - + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region BladesRoll.PrimaryDoc Implementation get rollPrimaryID() { return this.id; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollPrimaryDoc() { return this; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollPrimaryName() { return this.name; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollPrimaryType() { return this.type; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollPrimaryImg() { return this.img; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollModsData() { - + // Const rollModData = BladesRollMod.ParseDocRollMods(this); + // Add roll mods from COHORT harm + /*~ @@DOUBLE-BLANK@@ ~*/ return BladesRollMod.ParseDocRollMods(this); } - + /*~ @@DOUBLE-BLANK@@ ~*/ + // #endregion + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region BladesRoll.OppositionDoc Implementation get rollOppID() { return this.id; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollOppDoc() { return this; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollOppImg() { return this.img; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollOppName() { return this.name; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollOppSubName() { return ""; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollOppType() { return this.type; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollOppModsData() { return []; } - + // #endregion + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region BladesRoll.ParticipantDoc Implementation get rollParticipantID() { return this.id; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollParticipantDoc() { return this; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollParticipantIcon() { return this.img; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollParticipantName() { return this.name; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollParticipantType() { return this.type; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollParticipantModsData() { return []; } - + // #endregion + /*~ @@DOUBLE-BLANK@@ ~*/ + // #endregion + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region PREPARING DERIVED DATA prepareDerivedData() { super.prepareDerivedData(); if (BladesItem.IsType(this, BladesItemType.cohort_gang, BladesItemType.cohort_expert)) { @@ -211,11 +259,14 @@ class BladesItem extends Item { this._preparePlaybookData(this.system); } } + /*~ @@DOUBLE-BLANK@@ ~*/ _prepareCohortData(system) { if (!BladesItem.IsType(this, BladesItemType.cohort_gang, BladesItemType.cohort_expert)) { return; } + /*~ @@DOUBLE-BLANK@@ ~*/ system.tier.name = "Quality"; + /*~ @@DOUBLE-BLANK@@ ~*/ const subtypes = U.unique(Object.values(system.subtypes) .map((subtype) => subtype.trim()) .filter((subtype) => /[A-Za-z]/.test(subtype))); @@ -227,6 +278,7 @@ class BladesItem extends Item { ] .map((subtype) => subtype.trim()) .filter((subtype) => /[A-Za-z]/.test(subtype) && subtypes.includes(subtype))); + /*~ @@DOUBLE-BLANK@@ ~*/ system.subtypes = Object.fromEntries(subtypes.map((subtype, i) => [`${i + 1}`, subtype])); system.elite_subtypes = Object.fromEntries(eliteSubtypes.map((subtype, i) => [`${i + 1}`, subtype])); system.edges = Object.fromEntries(Object.values(system.edges ?? []) @@ -235,7 +287,9 @@ class BladesItem extends Item { system.flaws = Object.fromEntries(Object.values(system.flaws ?? []) .filter((flaw) => /[A-Za-z]/.test(flaw)) .map((flaw, i) => [`${i + 1}`, flaw.trim()])); + /*~ @@DOUBLE-BLANK@@ ~*/ system.quality = this.getFactorTotal(Factor.quality); + /*~ @@DOUBLE-BLANK@@ ~*/ if (BladesItem.IsType(this, BladesItemType.cohort_gang)) { if ([...subtypes, ...eliteSubtypes].includes(Tag.GangType.Vehicle)) { system.scale = this.getFactorTotal(Factor.scale); @@ -256,6 +310,7 @@ class BladesItem extends Item { system.scaleExample = [...subtypes, ...eliteSubtypes].includes("Pet") ? "(1 animal)" : "(1 person)"; system.subtitle = "An Expert"; } + /*~ @@DOUBLE-BLANK@@ ~*/ if (subtypes.length + eliteSubtypes.length > 0) { if ([...subtypes, ...eliteSubtypes].includes(Tag.GangType.Vehicle)) { system.subtitle = C.VehicleDescriptors[Math.min(6, this.getFactorTotal(Factor.tier))]; @@ -268,12 +323,14 @@ class BladesItem extends Item { } } } + /*~ @@DOUBLE-BLANK@@ ~*/ _prepareGearData(system) { if (!BladesItem.IsType(this, BladesItemType.gear)) { return; } system.tier.name = "Quality"; } + /*~ @@DOUBLE-BLANK@@ ~*/ _preparePlaybookData(system) { if (!BladesItem.IsType(this, BladesItemType.playbook, BladesItemType.crew_playbook)) { return; @@ -282,12 +339,16 @@ class BladesItem extends Item { [...Object.values(system.experience_clues).filter((clue) => /[A-Za-z]/.test(clue)), " "].forEach((clue, i) => { expClueData[(i + 1).toString()] = clue; }); system.experience_clues = expClueData; eLog.checkLog3("experienceClues", { expClueData }); + /*~ @@DOUBLE-BLANK@@ ~*/ if (BladesItem.IsType(this, BladesItemType.playbook)) { const gatherInfoData = {}; [...Object.values(system.gather_info_questions).filter((question) => /[A-Za-z]/.test(question)), " "].forEach((question, i) => { gatherInfoData[(i + 1).toString()] = question; }); system.gather_info_questions = gatherInfoData; eLog.checkLog3("gatherInfoQuestions", { gatherInfoData }); + /*~ @@DOUBLE-BLANK@@ ~*/ } } } -export default BladesItem; \ No newline at end of file +/*~ @@DOUBLE-BLANK@@ ~*/ +export default BladesItem; +/*~ @@DOUBLE-BLANK@@ ~*/ diff --git a/module/BladesPushAlert.js b/module/BladesPushAlert.js index cc04f809..ff46a5ea 100644 --- a/module/BladesPushAlert.js +++ b/module/BladesPushAlert.js @@ -1,19 +1,17 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - import U from "./core/utilities.js"; +import C from "./core/constants.js"; +/*~ @@DOUBLE-BLANK@@ ~*/ export default class BladesPushAlert { + /*~ @@DOUBLE-BLANK@@ ~*/ static Get() { if (!game.eunoblades.PushController) { throw new Error("Attempt to Get BladesPushAlert before 'ready' hook."); } return game.eunoblades.PushController; } + /*~ @@DOUBLE-BLANK@@ ~*/ static isInitialized = false; + /*~ @@DOUBLE-BLANK@@ ~*/ static Initialize() { game.eunoblades ??= {}; Hooks.once("ready", async () => { @@ -26,6 +24,7 @@ export default class BladesPushAlert { }); Hooks.on("canvasReady", async () => { game.eunoblades.PushController?.initOverlay(); }); } + /*~ @@DOUBLE-BLANK@@ ~*/ static InitSockets() { if (game.eunoblades.PushController) { socketlib.system.register("pushNotice", game.eunoblades.PushController.push); @@ -33,13 +32,18 @@ export default class BladesPushAlert { } return false; } + /*~ @@DOUBLE-BLANK@@ ~*/ initOverlay() { $("#sidebar").append($("
")); BladesPushAlert.isInitialized = true; } + /*~ @@DOUBLE-BLANK@@ ~*/ get elem$() { return $("#blades-push-notifications"); } + /*~ @@DOUBLE-BLANK@@ ~*/ get elem() { return this.elem$[0]; } + /*~ @@DOUBLE-BLANK@@ ~*/ activeNotifications = {}; + /*~ @@DOUBLE-BLANK@@ ~*/ push(blockClass, charName, titleText, bodyText) { const pushController = BladesPushAlert.Get(); const pushID = randomID(); @@ -57,6 +61,7 @@ export default class BladesPushAlert { } pushLines.push(""); const pushElem$ = $(pushLines.join("\n")); + /*~ @@DOUBLE-BLANK@@ ~*/ pushController.elem$.append(pushElem$); pushElem$.on("click", () => pushController.removePush(pushElem$[0])); U.gsap.from(pushElem$[0], { @@ -66,19 +71,22 @@ export default class BladesPushAlert { ease: "power2" }); U.gsap.from(pushElem$[0], { - background: "rgb(255, 231, 92)", - borderColor: "rgb(255, 255, 255)", + background: C.Colors.bGOLD, + borderColor: C.Colors.WHITE, duration: 10, ease: "power2" }); } + /*~ @@DOUBLE-BLANK@@ ~*/ removePush(pushElem) { U.gsap.effects.slideUp(pushElem) .then(() => $(pushElem).remove()); } + /*~ @@DOUBLE-BLANK@@ ~*/ pushToAll(...args) { socketlib.system.executeForEveryone("pushNotice", "", ...args); } + /*~ @@DOUBLE-BLANK@@ ~*/ pushToSome(...args) { const users = (args.pop() ?? []) .filter((user) => Boolean(user?.id)); @@ -88,7 +96,9 @@ export default class BladesPushAlert { const pushArgs = args.slice(0, 3); socketlib.system.executeForUsers("pushNotice", users.map((user) => user.id), "", ...pushArgs); } + /*~ @@DOUBLE-BLANK@@ ~*/ pushToGM(...args) { socketlib.system.executeForAllGMs("pushNotice", "to-gm-notice", ...args); } -} \ No newline at end of file +} +/*~ @@DOUBLE-BLANK@@ ~*/ diff --git a/module/BladesRoll.js b/module/BladesRoll.js index bee097e3..e74d33aa 100644 --- a/module/BladesRoll.js +++ b/module/BladesRoll.js @@ -1,32 +1,66 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - +// #region IMPORTS ~ import U from "./core/utilities.js"; import C, { BladesActorType, BladesItemType, RollPermissions, RollType, RollSubType, RollModStatus, RollModSection, ActionTrait, DowntimeAction, AttributeTrait, Position, Effect, Factor, RollResult, RollPhase, ConsequenceType, Tag } from "./core/constants.js"; import { BladesActor, BladesPC, BladesCrew } from "./documents/BladesActorProxy.js"; import { BladesItem, BladesGMTracker } from "./documents/BladesItemProxy.js"; import { ApplyTooltipListeners, ApplyConsequenceListeners } from "./core/gsap.js"; import BladesDialog from "./BladesDialog.js"; - +import BladesChat from "./BladesChat.js"; +// #endregion +// #region Types & Type Checking ~ +/*~ @@DOUBLE-BLANK@@ ~*/ +/** + * Checks if the given string is a RollType. + * @param {unknown} str The string to check. + * @returns {boolean} True if the string is a RollType, false otherwise. + */ function isRollType(str) { return typeof str === "string" && str in RollType; } +/*~ @@DOUBLE-BLANK@@ ~*/ +/** + * Checks if the given trait is an ActionTrait. + * @param {unknown} trait The trait to check. + * @returns {boolean} True if the trait is an ActionTrait, false otherwise. + */ function isAction(trait) { return Boolean(trait && typeof trait === "string" && U.lCase(trait) in ActionTrait); } +/*~ @@DOUBLE-BLANK@@ ~*/ +/** + * Checks if the given trait is an AttributeTrait. + * @param {unknown} trait The trait to check. + * @returns {boolean} True if the trait is an AttributeTrait, false otherwise. + */ function isAttribute(trait) { return Boolean(trait && typeof trait === "string" && U.lCase(trait) in AttributeTrait); } +/*~ @@DOUBLE-BLANK@@ ~*/ +/** + * Checks if the given trait is a Factor. + * @param {unknown} trait The trait to check. + * @returns {boolean} True if the trait is a Factor, false otherwise. + */ function isFactor(trait) { return Boolean(trait && typeof trait === "string" && U.lCase(trait) in Factor); } +/*~ @@DOUBLE-BLANK@@ ~*/ +/** + * Checks if the given string is a RollModStatus. + * @param {unknown} str The string to check. + * @returns {boolean} True if the string is a RollModStatus, false otherwise. + */ function isModStatus(str) { return typeof str === "string" && str in RollModStatus; } +/*~ @@DOUBLE-BLANK@@ ~*/ +/** + * Checks if the given value is valid consequence data for a Resistance Roll. + * @param {unknown} val The value to check. + * @param {boolean} [isCheckingResistedTo=false] If the check is being recursively + * applied to the 'resistedTo' value. + * @returns {boolean} True if the val is valid BladesRoll.ResistanceRollConsequenceData, false otherwise. + */ function isValidConsequenceData(val, isCheckingResistedTo = false) { if (!U.isList(val)) { return false; @@ -43,17 +77,25 @@ function isValidConsequenceData(val, isCheckingResistedTo = false) { if (typeof val.typeDisplay !== "string") { return false; } + /*~ @@DOUBLE-BLANK@@ ~*/ if (isCheckingResistedTo) { return true; } + /*~ @@DOUBLE-BLANK@@ ~*/ if (typeof val.attribute !== "string" || !(val.attribute in AttributeTrait)) { return false; } if (!isValidConsequenceData(val.resistedTo, true)) { return false; } + /*~ @@DOUBLE-BLANK@@ ~*/ return true; } +/*~ @@DOUBLE-BLANK@@ ~*/ +/** + * Checks if the given section can contain BladesRollParticipant documents. + * @param {RollModSection} section + */ function isParticipantSection(section) { return [ RollModSection.roll, @@ -61,6 +103,11 @@ function isParticipantSection(section) { RollModSection.effect ].includes(section); } +/*~ @@DOUBLE-BLANK@@ ~*/ +/** + * Checks if the given subSection can hold BladesRollParticipant documents. + * @param {string} subSection + */ function isParticipantSubSection(subSection) { if (subSection.startsWith("Group_")) { return true; @@ -70,15 +117,27 @@ function isParticipantSubSection(subSection) { } return false; } +// #endregion +// #region Utility Functions ~ +/** + * Prunes the configuration file of flag-incompatible direct document references. + * @param {BladesRoll.Config} cfg The configuration object to be pruned. + * @returns {BladesRoll.ConfigFlags} - The pruned configuration object. + */ function pruneConfig(cfg) { return expandObject(U.objFilter(flattenObject(cfg), (v) => !(v instanceof BladesActor) && !(v instanceof BladesItem))); } +// #endregion +/*~ @@DOUBLE-BLANK@@ ~*/ class BladesRollMod { + /*~ @@DOUBLE-BLANK@@ ~*/ static ParseDocRollMods(doc) { + /*~ @@DOUBLE-BLANK@@ ~*/ const { roll_mods } = doc.system; if (!roll_mods || roll_mods.length === 0) { return []; } + /*~ @@DOUBLE-BLANK@@ ~*/ return roll_mods .filter((elem) => typeof elem === "string") .map((modString) => { @@ -95,6 +154,7 @@ class BladesRollMod { } const posNegString = (U.pullElement(pStrings, (v) => typeof v === "string" && /^p/i.test(v)) || "posNeg:positive"); const posNegVal = posNegString.replace(/^.*:/, ""); + /*~ @@DOUBLE-BLANK@@ ~*/ const rollModData = { id: `${nameVal}-${posNegVal}-${catVal}`, name: nameVal, @@ -105,6 +165,7 @@ class BladesRollMod { posNeg: posNegVal, tooltip: "" }; + /*~ @@DOUBLE-BLANK@@ ~*/ pStrings.forEach((pString) => { const [keyString, valString] = pString.split(/:/); let val = /\|/.test(valString) ? valString.split(/\|/) : valString; @@ -151,9 +212,11 @@ class BladesRollMod { else { throw new Error(`Bad Roll Mod Key: ${keyString}`); } + /*~ @@DOUBLE-BLANK@@ ~*/ if (key === "base_status" && val === "Conditional") { val = RollModStatus.Hidden; } + /*~ @@DOUBLE-BLANK@@ ~*/ let valProcessed; if (["value"].includes(key)) { valProcessed = U.pInt(val); @@ -164,11 +227,14 @@ class BladesRollMod { else { valProcessed = val.replace(/%COLON%/g, ":"); } + /*~ @@DOUBLE-BLANK@@ ~*/ Object.assign(rollModData, { [key]: valProcessed }); }); + /*~ @@DOUBLE-BLANK@@ ~*/ return rollModData; }); } + /*~ @@DOUBLE-BLANK@@ ~*/ get status() { if (this.userStatus && [RollModStatus.ForcedOn, RollModStatus.ForcedOff, RollModStatus.Hidden].includes(this.userStatus)) { @@ -179,17 +245,25 @@ class BladesRollMod { } return this.heldStatus ?? this.userStatus ?? this.baseStatus; } + /*~ @@DOUBLE-BLANK@@ ~*/ get isActive() { return [RollModStatus.ToggledOn, RollModStatus.ForcedOn].includes(this.status); } + /*~ @@DOUBLE-BLANK@@ ~*/ get isVisible() { return this.status !== RollModStatus.Hidden; } + /*~ @@DOUBLE-BLANK@@ ~*/ _heldStatus; + /*~ @@DOUBLE-BLANK@@ ~*/ get heldStatus() { return this._heldStatus; } + /*~ @@DOUBLE-BLANK@@ ~*/ set heldStatus(val) { this._heldStatus = val; } + /*~ @@DOUBLE-BLANK@@ ~*/ get flagParams() { return [C.SYSTEM_ID, `rollCollab.rollModsData.${this.id}`]; } + /*~ @@DOUBLE-BLANK@@ ~*/ getUserStatusFlag() { return this.rollInstance.document.getFlag(...this.flagParams); } + /*~ @@DOUBLE-BLANK@@ ~*/ async setUserStatusFlag(val) { if (val === this.userStatus) { return; @@ -212,13 +286,17 @@ class BladesRollMod { } await socketlib.system.executeForEveryone("renderRollCollab", this.rollInstance.rollID); } + /*~ @@DOUBLE-BLANK@@ ~*/ get userStatus() { return this.getUserStatusFlag(); } + /*~ @@DOUBLE-BLANK@@ ~*/ set userStatus(val) { this.setUserStatusFlag(val); } + /*~ @@DOUBLE-BLANK@@ ~*/ get sourceName() { return this._sourceName; } + /*~ @@DOUBLE-BLANK@@ ~*/ get isConditional() { return [ ...this.conditionalRollTraits, @@ -229,6 +307,7 @@ class BladesRollMod { ...this.participantRollTypes ].length > 0; } + /*~ @@DOUBLE-BLANK@@ ~*/ get isInInactiveBlock() { if (game.user.isGM) { return [RollModStatus.Hidden, RollModStatus.ForcedOff, RollModStatus.ToggledOff].includes(this.status) @@ -237,11 +316,14 @@ class BladesRollMod { return [RollModStatus.ForcedOff, RollModStatus.ToggledOff].includes(this.status) && (this.isConditional || [BladesItemType.ability].includes(this.modType)); } + /*~ @@DOUBLE-BLANK@@ ~*/ get isPush() { return Boolean(U.lCase(this.name) === "push" || this.effectKeys.find((eKey) => eKey === "Is-Push")); } + /*~ @@DOUBLE-BLANK@@ ~*/ get isBasicPush() { return U.lCase(this.name) === "push"; } + /*~ @@DOUBLE-BLANK@@ ~*/ get stressCost() { const costKeys = this.effectKeys.filter((key) => key.startsWith("Cost-Stress")); if (costKeys.length === 0) { @@ -255,6 +337,7 @@ class BladesRollMod { }); return stressCost; } + /*~ @@DOUBLE-BLANK@@ ~*/ isValidForRollType() { switch (this.rollInstance.rollType) { case RollType.Action: { @@ -272,39 +355,68 @@ class BladesRollMod { default: return false; } } - setConditionalStatus() { + /*~ @@DOUBLE-BLANK@@ ~*/ + /** + * Sets the conditional status of the roll instance. + * @returns {boolean} - Returns false if the status is ForcedOn or ToggledOff, true if the status is Hidden. + */ + setConditionalStatus() { + // If the roll instance is not conditional, return false if (!this.isConditional) { return false; } + /*~ @@DOUBLE-BLANK@@ ~*/ + // If any auto-Traits/Types apply, set status to ForcedOn and return false const autoTypesOrTraitsApply = this.autoRollTypes.includes(this.rollInstance.rollType) || (!this.rollInstance.rollTrait || this.autoRollTraits.includes(this.rollInstance.rollTrait)); if (autoTypesOrTraitsApply) { this.heldStatus = RollModStatus.ForcedOn; return false; } + /*~ @@DOUBLE-BLANK@@ ~*/ + // If any conditionalTypes apply and any conditionalTraits apply, set status to ToggledOff and return false const conditionalTypesOrTraitsApply = this.checkTypesOrTraits(this.conditionalRollTypes, this.conditionalRollTraits); if (conditionalTypesOrTraitsApply) { this.heldStatus = RollModStatus.ToggledOff; return false; } - + /*~ @@DOUBLE-BLANK@@ ~*/ + // If this is a participant roll + // AND any participantTypes apply + // AND any participantTraits apply, + // ... set status to ToggledOff and return false const participantTypesOrTraitsApply = this.rollInstance.isParticipantRoll && this.checkTypesOrTraits(this.participantRollTypes, this.participantRollTraits); if (participantTypesOrTraitsApply) { this.heldStatus = RollModStatus.ToggledOff; return false; } + /*~ @@DOUBLE-BLANK@@ ~*/ + // If none of the above conditions apply, set status to Hidden and return true this.heldStatus = RollModStatus.Hidden; return true; } - checkTypesOrTraits(types, traits) { + /*~ @@DOUBLE-BLANK@@ ~*/ + /** + * Checks if any types or traits apply to the roll instance. + * @param {AnyRollType[]} types The types to check. + * @param {RollTrait[]} traits The traits to check. + * @returns {boolean} - Returns true if any types or traits apply, false otherwise. + */ + checkTypesOrTraits(types, traits) { const typesApply = Boolean((!this.rollInstance.isParticipantRoll && types.length === 0) || types.includes(this.rollInstance.rollType)); const traitsApply = Boolean((!this.rollInstance.isParticipantRoll && traits.length === 0) || (this.rollInstance.rollTrait && traits.includes(this.rollInstance.rollTrait))); return typesApply && traitsApply; } - processKey(key) { + /*~ @@DOUBLE-BLANK@@ ~*/ + /** + * Helper function to process each key + * @param {string} key The key to process + * @returns {boolean} - Whether the processing was successful + */ + processKey(key) { const [thisKey, thisParam] = key.split(/-/) ?? []; const positions = [Position.controlled, Position.risky, Position.desperate]; if (positions.includes(U.lCase(thisParam)) && this.rollInstance.finalPosition === U.lCase(thisParam)) { @@ -319,27 +431,34 @@ class BladesRollMod { } return false; } + /*~ @@DOUBLE-BLANK@@ ~*/ setAutoStatus() { + // Check for AutoRevealOn and AutoEnableOn const holdKeys = this.effectKeys.filter((key) => key.startsWith("Auto")); if (holdKeys.length === 0) { return false; } + /*~ @@DOUBLE-BLANK@@ ~*/ for (const key of holdKeys) { if (this.processKey(key)) { return false; } } + /*~ @@DOUBLE-BLANK@@ ~*/ this.heldStatus = RollModStatus.Hidden; return true; } + /*~ @@DOUBLE-BLANK@@ ~*/ setRelevancyStatus() { const holdKeys = this.effectKeys.filter((key) => /^Negate|^Increase/.test(key)); if (holdKeys.length === 0) { return false; } + /*~ @@DOUBLE-BLANK@@ ~*/ const relevantKeys = holdKeys .filter((key) => { const [thisKey, thisParam] = key.split(/-/) ?? []; + /*~ @@DOUBLE-BLANK@@ ~*/ const negateOperations = { PushCost: () => this.rollInstance.isPushed(), PushCost0: () => this.rollInstance.isPushed(), @@ -370,6 +489,7 @@ class BladesRollMod { && (this.rollInstance.rollFactors.source[Factor.tier]?.value ?? 0) < (this.rollInstance.rollFactors.opposition[Factor.tier]?.value ?? 0) }; + /*~ @@DOUBLE-BLANK@@ ~*/ if (thisKey === "Negate") { if (Object.hasOwn(negateOperations, thisParam)) { return negateOperations[thisParam](); @@ -392,11 +512,13 @@ class BladesRollMod { } return false; } + /*~ @@DOUBLE-BLANK@@ ~*/ setPayableStatus() { const holdKeys = this.effectKeys.filter((key) => key.startsWith("Cost")); if (holdKeys.length === 0) { return false; } + /*~ @@DOUBLE-BLANK@@ ~*/ const payableKeys = holdKeys .filter((key) => { const [thisParam] = (key.split(/-/) ?? []).slice(1); @@ -419,22 +541,28 @@ class BladesRollMod { default: throw new Error(`Unrecognize Payable Key: ${traitStr}`); } }); + /*~ @@DOUBLE-BLANK@@ ~*/ if (payableKeys.length === 0) { this.heldStatus = RollModStatus.ForcedOff; return true; } return false; } + /*~ @@DOUBLE-BLANK@@ ~*/ applyRollModEffectKeys() { if (!this.isActive) { return; } + /*~ @@DOUBLE-BLANK@@ ~*/ const holdKeys = this.effectKeys.filter((key) => /^Negate|^Increase/.test(key)); if (holdKeys.length === 0) { return; } + /*~ @@DOUBLE-BLANK@@ ~*/ holdKeys.forEach((key) => { + // Console.log({key, split: key.split(/-/)}) const [thisKey, thisParam] = key.split(/-/) ?? []; + /*~ @@DOUBLE-BLANK@@ ~*/ const negateOperations = { PushCost: () => { const costlyPushMod = this.rollInstance.getActiveRollMods() @@ -443,8 +571,37 @@ class BladesRollMod { U.pullElement(costlyPushMod.effectKeys, (k) => k.startsWith("Cost-Stress")); } }, + // PushCost0: negateOperations.PushCost, Consequence: () => { - }, + /*~ @@DOUBLE-BLANK@@ ~*/ + /* Should cancel roll entirely? */ + }, + // HarmLevel: () => { + // const harmLevels = [ + // [ConsequenceType.InsightHarm1, ConsequenceType.ProwessHarm1, ConsequenceType.ResolveHarm1], + // [ConsequenceType.InsightHarm2, ConsequenceType.ProwessHarm2, ConsequenceType.ResolveHarm2], + // [ConsequenceType.InsightHarm3, ConsequenceType.ProwessHarm3, ConsequenceType.ResolveHarm3], + // [ConsequenceType.InsightHarm4, ConsequenceType.ProwessHarm4, ConsequenceType.ResolveHarm4] + // ]; + // let harmConsequence: BladesRoll.ResistanceRollConsequenceData|undefined = undefined; + // while (!harmConsequence && harmLevels.length > 0) { + // harmConsequence = Object.values(this.rollInstance.rollConsequences) + // .find(({type}) => (harmLevels.pop() ?? []).includes(type as ConsequenceType)); + // } + // if (harmConsequence) { + // harmConsequence.resistedTo = { + // name: [ + // ConsequenceType.InsightHarm1, + // ConsequenceType.ProwessHarm1, + // ConsequenceType.ResolveHarm1 + // ].includes(harmConsequence.type as ConsequenceType) + // ? "Fully Negated" + // : (Object.values(harmConsequence.resistOptions ?? [])[0]?.name ?? harmConsequence.name), + // type: C.ResistedConsequenceTypes[harmConsequence.type as KeyOf], + // isSelected: true + // }; + // } + // }, QualityPenalty: () => { this.rollInstance.negateFactorPenalty(Factor.quality); }, @@ -455,6 +612,7 @@ class BladesRollMod { this.rollInstance.negateFactorPenalty(Factor.tier); } }; + /*~ @@DOUBLE-BLANK@@ ~*/ if (thisKey === "Negate") { if (Object.hasOwn(negateOperations, thisParam)) { return negateOperations[thisParam](); @@ -472,6 +630,7 @@ class BladesRollMod { } }); } + /*~ @@DOUBLE-BLANK@@ ~*/ get selectOptions() { if (this.modType !== "teamwork") { return null; @@ -484,12 +643,14 @@ class BladesRollMod { } return null; } + /*~ @@DOUBLE-BLANK@@ ~*/ get selectedParticipant() { if (this.modType !== "teamwork") { return null; } return this.rollInstance.getRollParticipant(this.section, this.name); } + /*~ @@DOUBLE-BLANK@@ ~*/ get tooltip() { let parsedTooltip = this._tooltip.replace(/%COLON%/g, ":"); if (parsedTooltip.includes("%DOC_NAME%")) { @@ -504,6 +665,7 @@ class BladesRollMod { } return parsedTooltip; } + /*~ @@DOUBLE-BLANK@@ ~*/ get sideString() { if (this._sideString) { return this._sideString; @@ -513,9 +675,11 @@ class BladesRollMod { } return undefined; } + /*~ @@DOUBLE-BLANK@@ ~*/ get allFlagData() { return this.rollInstance.document.getFlag("eunos-blades", "rollCollab"); } + /*~ @@DOUBLE-BLANK@@ ~*/ get data() { return { id: this.id, @@ -535,6 +699,7 @@ class BladesRollMod { section: this.section }; } + /*~ @@DOUBLE-BLANK@@ ~*/ get costs() { if (!this.isActive) { return undefined; @@ -543,9 +708,11 @@ class BladesRollMod { if (holdKeys.length === 0) { return undefined; } + /*~ @@DOUBLE-BLANK@@ ~*/ return holdKeys.map((key) => { const [thisParam] = (key.split(/-/) ?? []).slice(1); const [traitStr, valStr] = (/([A-Za-z]+)(\d*)/.exec(thisParam) ?? []).slice(1); + /*~ @@DOUBLE-BLANK@@ ~*/ let label = this.name; if (this.isBasicPush) { if (this.posNeg === "negative") { @@ -556,6 +723,7 @@ class BladesRollMod { label = `${this.name} (${effect})`; } } + /*~ @@DOUBLE-BLANK@@ ~*/ return { id: this.id, label, @@ -564,24 +732,43 @@ class BladesRollMod { }; }); } + /*~ @@DOUBLE-BLANK@@ ~*/ id; + /*~ @@DOUBLE-BLANK@@ ~*/ name; + /*~ @@DOUBLE-BLANK@@ ~*/ _sourceName; + /*~ @@DOUBLE-BLANK@@ ~*/ baseStatus; + /*~ @@DOUBLE-BLANK@@ ~*/ value; + /*~ @@DOUBLE-BLANK@@ ~*/ effectKeys; + /*~ @@DOUBLE-BLANK@@ ~*/ _sideString; + /*~ @@DOUBLE-BLANK@@ ~*/ _tooltip; + /*~ @@DOUBLE-BLANK@@ ~*/ posNeg; + /*~ @@DOUBLE-BLANK@@ ~*/ modType; + /*~ @@DOUBLE-BLANK@@ ~*/ conditionalRollTypes; + /*~ @@DOUBLE-BLANK@@ ~*/ autoRollTypes; + /*~ @@DOUBLE-BLANK@@ ~*/ participantRollTypes; + /*~ @@DOUBLE-BLANK@@ ~*/ conditionalRollTraits; + /*~ @@DOUBLE-BLANK@@ ~*/ autoRollTraits; + /*~ @@DOUBLE-BLANK@@ ~*/ participantRollTraits; + /*~ @@DOUBLE-BLANK@@ ~*/ section; + /*~ @@DOUBLE-BLANK@@ ~*/ rollInstance; + /*~ @@DOUBLE-BLANK@@ ~*/ constructor(modData, rollInstance) { this.rollInstance = rollInstance; this.id = modData.id; @@ -603,8 +790,10 @@ class BladesRollMod { this.section = modData.section; } } +/*~ @@DOUBLE-BLANK@@ ~*/ class BladesRollPrimary { - + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region Static Methods ~ static IsValidData(data) { if (BladesRollPrimary.IsDoc(data)) { return true; @@ -618,13 +807,19 @@ class BladesRollPrimary { && (!data.rollPrimaryID || typeof data.rollPrimaryID === "string") && (!data.rollPrimaryDoc || BladesRollPrimary.IsDoc(data.rollPrimaryDoc)); } + /*~ @@DOUBLE-BLANK@@ ~*/ static IsDoc(doc) { return BladesActor.IsType(doc, BladesActorType.pc, BladesActorType.crew) || BladesItem.IsType(doc, BladesItemType.cohort_expert, BladesItemType.cohort_gang, BladesItemType.gm_tracker); } + // #endregion + /*~ @@DOUBLE-BLANK@@ ~*/ rollInstance; + /*~ @@DOUBLE-BLANK@@ ~*/ rollPrimaryID; + /*~ @@DOUBLE-BLANK@@ ~*/ _rollPrimaryDoc; + /*~ @@DOUBLE-BLANK@@ ~*/ get rollPrimaryDoc() { if (!this._rollPrimaryDoc) { let doc; @@ -642,25 +837,36 @@ class BladesRollPrimary { } return this._rollPrimaryDoc; } + /*~ @@DOUBLE-BLANK@@ ~*/ rollPrimaryName; + /*~ @@DOUBLE-BLANK@@ ~*/ rollPrimaryType; + /*~ @@DOUBLE-BLANK@@ ~*/ rollPrimaryImg; + /*~ @@DOUBLE-BLANK@@ ~*/ _rollModsData; + /*~ @@DOUBLE-BLANK@@ ~*/ get rollModsData() { return this.rollPrimaryDoc?.rollModsData ?? this._rollModsData ?? []; } + /*~ @@DOUBLE-BLANK@@ ~*/ rollFactors; - + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region Constructor ~ constructor(rollInstance, { rollPrimaryID, rollPrimaryName, rollPrimaryType, rollPrimaryImg, rollModsData, rollFactors }) { + // Identify ID, Doc, Name, SubName, Type & Image, to best of ability this.rollInstance = rollInstance; + /*~ @@DOUBLE-BLANK@@ ~*/ this.rollPrimaryID = rollPrimaryID ?? this.rollInstance.rollPrimary.rollPrimaryID ?? this.rollInstance.rollPrimary.rollPrimaryDoc?.rollPrimaryID; + /*~ @@DOUBLE-BLANK@@ ~*/ rollPrimaryName ??= this.rollInstance.rollPrimary.rollPrimaryName; rollPrimaryType ??= this.rollInstance.rollPrimary.rollPrimaryType; rollPrimaryImg ??= this.rollInstance.rollPrimary.rollPrimaryImg; rollModsData ??= this.rollInstance.rollPrimary.rollModsData; rollFactors ??= this.rollInstance.rollPrimary.rollFactors; + /*~ @@DOUBLE-BLANK@@ ~*/ if (BladesRollPrimary.IsDoc(this.rollPrimaryDoc)) { this.rollPrimaryName = rollPrimaryName ?? this.rollPrimaryDoc.rollPrimaryName; this.rollPrimaryType = this.rollPrimaryDoc.rollPrimaryType; @@ -684,6 +890,7 @@ class BladesRollPrimary { if (!rollFactors) { throw new Error("Must include a rollFactors when constructing a BladesRollPrimary object."); } + /*~ @@DOUBLE-BLANK@@ ~*/ this.rollPrimaryName = rollPrimaryName; this.rollPrimaryType = rollPrimaryType; this.rollPrimaryImg = rollPrimaryImg; @@ -692,8 +899,10 @@ class BladesRollPrimary { } } } +/*~ @@DOUBLE-BLANK@@ ~*/ class BladesRollOpposition { - + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region Static Methods ~ static IsValidData(data) { if (BladesRollOpposition.IsDoc(data)) { return true; @@ -707,6 +916,7 @@ class BladesRollOpposition { && U.isList(data.rollFactors) && (!data.rollOppID || typeof data.rollOppID === "string"); } + /*~ @@DOUBLE-BLANK@@ ~*/ static GetDoc(docRef) { let doc = docRef; if (typeof docRef === "string") { @@ -720,6 +930,7 @@ class BladesRollOpposition { } return false; } + /*~ @@DOUBLE-BLANK@@ ~*/ static IsDoc(doc) { return BladesActor.IsType(doc, BladesActorType.npc, BladesActorType.faction) || BladesItem.IsType(doc, ...[ @@ -731,9 +942,14 @@ class BladesRollOpposition { BladesItemType.ritual ]); } + // #endregion + /*~ @@DOUBLE-BLANK@@ ~*/ rollInstance; + /*~ @@DOUBLE-BLANK@@ ~*/ _rollOppID; + /*~ @@DOUBLE-BLANK@@ ~*/ get rollOppID() { return this._rollOppID; } + /*~ @@DOUBLE-BLANK@@ ~*/ set rollOppID(val) { if (val) { const doc = BladesRollOpposition.GetDoc(val); @@ -743,18 +959,31 @@ class BladesRollOpposition { } this._rollOppID = val; } + /*~ @@DOUBLE-BLANK@@ ~*/ rollOppDoc; + /*~ @@DOUBLE-BLANK@@ ~*/ rollOppName; + /*~ @@DOUBLE-BLANK@@ ~*/ rollOppSubName; + /*~ @@DOUBLE-BLANK@@ ~*/ rollOppType; + /*~ @@DOUBLE-BLANK@@ ~*/ rollOppImg; + /*~ @@DOUBLE-BLANK@@ ~*/ rollOppModsData; + /*~ @@DOUBLE-BLANK@@ ~*/ rollFactors; - + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region Constructor ~ constructor(rollInstance, { rollOppID, rollOppDoc, rollOppName, rollOppSubName, rollOppType, rollOppImg, rollOppModsData, rollFactors } = {}) { + /*~ @@DOUBLE-BLANK@@ ~*/ this.rollInstance = rollInstance; + /*~ @@DOUBLE-BLANK@@ ~*/ + // Attempt to fetch an associated BladesActor or BladesItem document const doc = BladesRollOpposition.GetDoc(rollOppDoc ?? rollOppID ?? rollOppName); + /*~ @@DOUBLE-BLANK@@ ~*/ if (doc) { + // Derive settings from valid Actor/Item document, unless explicitly set in constructor. rollOppID = doc.rollOppID; rollOppDoc = doc; rollOppName ??= doc.rollOppName; @@ -770,6 +999,8 @@ class BladesRollOpposition { ...rollFactors ?? {} }; } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Confirm required settings if (!rollOppName) { throw new Error("Must include a rollOppName when constructing a BladesRollOpposition object."); } @@ -782,6 +1013,8 @@ class BladesRollOpposition { if (!rollFactors) { throw new Error("Must include a rollFactors when constructing a BladesRollOpposition object."); } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Initialize properties this.rollOppID = rollOppID; this.rollOppName = rollOppName; this.rollOppSubName = rollOppSubName; @@ -790,9 +1023,12 @@ class BladesRollOpposition { this.rollOppModsData = rollOppModsData ?? []; this.rollFactors = rollFactors; } + // #endregion + /*~ @@DOUBLE-BLANK@@ ~*/ get flagParams() { return [C.SYSTEM_ID, "rollCollab.rollOppData"]; } + /*~ @@DOUBLE-BLANK@@ ~*/ get flagData() { return { rollOppID: this.rollOppID, @@ -800,14 +1036,17 @@ class BladesRollOpposition { rollOppSubName: this.rollOppSubName, rollOppType: this.rollOppType, rollOppImg: this.rollOppImg, + /*~ @@DOUBLE-BLANK@@ ~*/ rollOppModsData: this.rollOppModsData, rollFactors: this.rollFactors }; } + /*~ @@DOUBLE-BLANK@@ ~*/ async updateRollFlags() { await this.rollInstance.document.setFlag(...this.flagParams, this.flagData); socketlib.system.executeForEveryone("renderRollCollab", this.rollInstance.rollID); } + /*~ @@DOUBLE-BLANK@@ ~*/ refresh() { const rollOppFlags = this.rollInstance.flagData.rollOppData; if (rollOppFlags) { @@ -816,14 +1055,17 @@ class BladesRollOpposition { this.rollOppSubName = rollOppFlags.rollOppSubName; this.rollOppType = rollOppFlags.rollOppType; this.rollOppImg = rollOppFlags.rollOppImg; + /*~ @@DOUBLE-BLANK@@ ~*/ this.rollOppModsData = rollOppFlags.rollOppModsData; this.rollFactors = rollOppFlags.rollFactors; } return this; } } +/*~ @@DOUBLE-BLANK@@ ~*/ class BladesRollParticipant { - + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region Static Methods ~ static IsValidData(data) { if (BladesRollParticipant.IsDoc(data)) { return true; @@ -837,6 +1079,7 @@ class BladesRollParticipant { && (!data.rollParticipantID || typeof data.rollParticipantID === "string") && (!data.rollParticipantDoc || BladesRollParticipant.IsDoc(data.rollParticipantDoc)); } + /*~ @@DOUBLE-BLANK@@ ~*/ static GetDoc(docRef) { let doc = docRef; if (typeof docRef === "string") { @@ -850,13 +1093,19 @@ class BladesRollParticipant { } return false; } + /*~ @@DOUBLE-BLANK@@ ~*/ static IsDoc(doc) { return BladesActor.IsType(doc, BladesActorType.pc, BladesActorType.crew, BladesActorType.npc) || BladesItem.IsType(doc, BladesItemType.cohort_expert, BladesItemType.cohort_gang, BladesItemType.gm_tracker); } + // #endregion + /*~ @@DOUBLE-BLANK@@ ~*/ rollInstance; + /*~ @@DOUBLE-BLANK@@ ~*/ _rollParticipantID; + /*~ @@DOUBLE-BLANK@@ ~*/ get rollParticipantID() { return this._rollParticipantID; } + /*~ @@DOUBLE-BLANK@@ ~*/ set rollParticipantID(val) { if (val) { const doc = BladesRollParticipant.GetDoc(val); @@ -866,17 +1115,28 @@ class BladesRollParticipant { } this._rollParticipantID = val; } + /*~ @@DOUBLE-BLANK@@ ~*/ rollParticipantDoc; + /*~ @@DOUBLE-BLANK@@ ~*/ rollParticipantName; + /*~ @@DOUBLE-BLANK@@ ~*/ rollParticipantType; + /*~ @@DOUBLE-BLANK@@ ~*/ rollParticipantIcon; + /*~ @@DOUBLE-BLANK@@ ~*/ rollParticipantSection; + /*~ @@DOUBLE-BLANK@@ ~*/ rollParticipantSubSection; - rollParticipantModsData; + /*~ @@DOUBLE-BLANK@@ ~*/ + rollParticipantModsData; // As applied to MAIN roll when this participant involved + /*~ @@DOUBLE-BLANK@@ ~*/ rollFactors; - + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region Constructor ~ constructor(rollInstance, { rollParticipantSection, rollParticipantSubSection, rollParticipantID, rollParticipantDoc, rollParticipantName, rollParticipantType, rollParticipantIcon, rollParticipantModsData, rollFactors }) { + /*~ @@DOUBLE-BLANK@@ ~*/ this.rollInstance = rollInstance; + /*~ @@DOUBLE-BLANK@@ ~*/ if (!rollParticipantSection) { throw new Error("Must include a rollParticipantSection when constructing a BladesRollParticipant object."); } @@ -885,8 +1145,12 @@ class BladesRollParticipant { } this.rollParticipantSection = rollParticipantSection; this.rollParticipantSubSection = rollParticipantSubSection; + /*~ @@DOUBLE-BLANK@@ ~*/ + // Attempt to fetch an associated BladesActor or BladesItem document const doc = BladesRollParticipant.GetDoc(rollParticipantDoc ?? rollParticipantID ?? rollParticipantName); + /*~ @@DOUBLE-BLANK@@ ~*/ if (doc) { + // Derive settings from valid Actor/Item document, unless explicitly set in constructor. rollParticipantID = doc.rollParticipantID; rollParticipantDoc = doc; rollParticipantName ??= doc.rollParticipantName; @@ -901,6 +1165,8 @@ class BladesRollParticipant { ...rollFactors ?? {} }; } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Confirm required settings if (!rollParticipantName) { throw new Error("Must include a rollParticipantName when constructing a BladesRollParticipant object."); } @@ -910,6 +1176,8 @@ class BladesRollParticipant { if (!rollFactors) { throw new Error("Must include a rollFactors when constructing a BladesRollParticipant object."); } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Initialize properties this.rollParticipantID = rollParticipantID; this.rollParticipantName = rollParticipantName; this.rollParticipantType = rollParticipantType; @@ -917,36 +1185,45 @@ class BladesRollParticipant { this.rollParticipantModsData = rollParticipantModsData ?? []; this.rollFactors = rollFactors; } + // #endregion + /*~ @@DOUBLE-BLANK@@ ~*/ get flagParams() { return [C.SYSTEM_ID, `rollCollab.rollParticipantData.${this.rollParticipantSection}.${this.rollParticipantSubSection}`]; } + /*~ @@DOUBLE-BLANK@@ ~*/ get flagData() { return { rollParticipantSection: this.rollParticipantSection, rollParticipantSubSection: this.rollParticipantSubSection, + /*~ @@DOUBLE-BLANK@@ ~*/ rollParticipantID: this.rollParticipantID, rollParticipantName: this.rollParticipantName, rollParticipantType: this.rollParticipantType, rollParticipantIcon: this.rollParticipantIcon, + /*~ @@DOUBLE-BLANK@@ ~*/ rollParticipantModsData: this.rollParticipantModsData, rollFactors: this.rollFactors }; } + /*~ @@DOUBLE-BLANK@@ ~*/ async updateRollFlags() { await this.rollInstance.document.setFlag(...this.flagParams, this.flagData); socketlib.system.executeForEveryone("renderRollCollab", this.rollInstance.rollID); } + /*~ @@DOUBLE-BLANK@@ ~*/ refresh() { const rollParticipantFlagData = this.rollInstance.flagData.rollParticipantData?.[this.rollParticipantSection]; if (rollParticipantFlagData) { const rollParticipantFlags = rollParticipantFlagData[this.rollParticipantSubSection]; if (rollParticipantFlags) { this.rollParticipantID = rollParticipantFlags.rollParticipantID; + /*~ @@DOUBLE-BLANK@@ ~*/ this.rollParticipantName = rollParticipantFlags.rollParticipantName; this.rollParticipantType = rollParticipantFlags.rollParticipantType; this.rollParticipantIcon = rollParticipantFlags.rollParticipantIcon; this.rollParticipantSection = rollParticipantFlags.rollParticipantSection; this.rollParticipantSubSection = rollParticipantFlags.rollParticipantSubSection; + /*~ @@DOUBLE-BLANK@@ ~*/ this.rollParticipantModsData = rollParticipantFlags.rollParticipantModsData; this.rollFactors = rollParticipantFlags.rollFactors; } @@ -954,8 +1231,10 @@ class BladesRollParticipant { return this; } } +/*~ @@DOUBLE-BLANK@@ ~*/ class BladesRoll extends DocumentSheet { - + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region STATIC METHODS: INITIALIZATION & DEFAULTS ~ static get defaultOptions() { return foundry.utils.mergeObject(super.defaultOptions, { classes: ["eunos-blades", "sheet", "roll-collab", game.user.isGM ? "gm-roll-collab" : ""], @@ -965,8 +1244,10 @@ class BladesRoll extends DocumentSheet { dragDrop: [ { dragSelector: null, dropSelector: "[data-action='gm-drop-opposition'" } ] + // Height: 500 }); } + /*~ @@DOUBLE-BLANK@@ ~*/ static Initialize() { return loadTemplates([ "systems/eunos-blades/templates/roll/roll-collab.hbs", @@ -986,11 +1267,13 @@ class BladesRoll extends DocumentSheet { "systems/eunos-blades/templates/roll/partials/roll-collab-incarceration-gm.hbs" ]); } + /*~ @@DOUBLE-BLANK@@ ~*/ static InitSockets() { socketlib.system.register("constructRollCollab", BladesRoll.ConstructRollCollab); socketlib.system.register("renderRollCollab", BladesRoll.RenderRollCollab); socketlib.system.register("closeRollCollab", BladesRoll.CloseRollCollab); } + /*~ @@DOUBLE-BLANK@@ ~*/ static get DefaultRollMods() { return [ { @@ -1078,6 +1361,7 @@ class BladesRoll extends DocumentSheet { } ]; } + /*~ @@DOUBLE-BLANK@@ ~*/ static get DefaultFlagData() { return { rollModsData: {}, @@ -1161,9 +1445,13 @@ class BladesRoll extends DocumentSheet { } }; } - + // #endregion + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region STATIC METHODS: New Roll Creation ~ static Current = {}; + /*~ @@DOUBLE-BLANK@@ ~*/ static _Active; + /*~ @@DOUBLE-BLANK@@ ~*/ static get Active() { if (BladesRoll._Active) { return BladesRoll._Active; @@ -1173,43 +1461,59 @@ class BladesRoll extends DocumentSheet { } return undefined; } + /*~ @@DOUBLE-BLANK@@ ~*/ static set Active(val) { BladesRoll._Active = val; } + /*~ @@DOUBLE-BLANK@@ ~*/ static async ConstructRollCollab({ userID, rollID, rollPermission }) { const rollInst = new BladesRoll(userID, rollID, rollPermission); eLog.checkLog3("rollCollab", "ConstructRollCollab()", { params: { userID, rollID, rollPermission }, rollInst }); await rollInst._render(true); } + /*~ @@DOUBLE-BLANK@@ ~*/ static RenderRollCollab(rollID) { BladesRoll.Current[rollID]?.prepareRollParticipantData(); BladesRoll.Current[rollID]?.render(); } + /*~ @@DOUBLE-BLANK@@ ~*/ static async CloseRollCollab(rollID) { eLog.checkLog3("rollCollab", "CloseRollCollab()", { rollID }); await BladesRoll.Current[rollID]?.close({ rollID }); delete BladesRoll.Current[rollID]; } + /*~ @@DOUBLE-BLANK@@ ~*/ static GetUserPermissions(config) { - + /*~ @@DOUBLE-BLANK@@ ~*/ + // === ONE === GET USER IDS + /*~ @@DOUBLE-BLANK@@ ~*/ + // Get user ID of GM const GMUserID = game.users.find((user) => user.isGM)?.id; if (!GMUserID) { throw new Error("[BladesRoll.GetUserPermissions()] No GM found!"); } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Get user IDs of players const playerUserIDs = game.users .filter((user) => BladesPC.IsType(user.character) && !user.isGM && typeof user.id === "string") .map((user) => user.id); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Prepare user ID permissions object const userIDs = { [RollPermissions.GM]: [GMUserID], [RollPermissions.Primary]: [], [RollPermissions.Participant]: [], [RollPermissions.Observer]: [] }; - + /*~ @@DOUBLE-BLANK@@ ~*/ + // === TWO === DETERMINE PRIMARY USER(S) + /*~ @@DOUBLE-BLANK@@ ~*/ + // Check RollPrimaryDoc to determine how to assign primary users const { rollPrimaryDoc } = config.rollPrimaryData; if (BladesPC.IsType(rollPrimaryDoc) && U.pullElement(playerUserIDs, rollPrimaryDoc.primaryUser?.id)) { userIDs[RollPermissions.Primary].push(rollPrimaryDoc.primaryUser?.id); + /*~ @@DOUBLE-BLANK@@ ~*/ } else if (BladesCrew.IsType(rollPrimaryDoc)) { userIDs[RollPermissions.Primary].push(...playerUserIDs); @@ -1225,14 +1529,25 @@ class BladesRoll extends DocumentSheet { else if (BladesGMTracker.IsType(rollPrimaryDoc)) { userIDs[RollPermissions.Primary].push(GMUserID); } - + /*~ @@DOUBLE-BLANK@@ ~*/ + // === THREE === DETERMINE ROLL PARTICIPANT USER(S) + /*~ @@DOUBLE-BLANK@@ ~*/ + // Check config.rollParticipantData to determine if roll starts with any participants if (config.rollParticipantData) { userIDs[RollPermissions.Participant].push(...getParticipantDocUserIDs(config.rollParticipantData, playerUserIDs)); } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Finally, add remaining players as observers. userIDs[RollPermissions.Observer] = playerUserIDs .filter((uID) => !userIDs[RollPermissions.Participant].includes(uID)); + /*~ @@DOUBLE-BLANK@@ ~*/ return userIDs; - function getParticipantDocs(participantData) { + /*~ @@DOUBLE-BLANK@@ ~*/ + /** + * Generates BladesRollParticipant documents from the provided schema data. + * @param {BladesRoll.RollParticipantData} participantData + */ + function getParticipantDocs(participantData) { return Object.values(flattenObject(participantData)) .map((pData) => { if (BladesRollParticipant.IsDoc(pData)) { @@ -1252,7 +1567,13 @@ class BladesRoll extends DocumentSheet { return null; }); } - function getParticipantDocUserIDs(participantData, unassignedIDs) { + /*~ @@DOUBLE-BLANK@@ ~*/ + /** + * Returns the user ids of potential BladesRollParticipants defined in the provided data schema. + * @param {BladesRoll.RollParticipantData} participantData + * @param {string[]} unassignedIDs + */ + function getParticipantDocUserIDs(participantData, unassignedIDs) { return getParticipantDocs(participantData) .map((pDoc) => { if (BladesPC.IsType(pDoc) && typeof pDoc.primaryUser?.id === "string") { @@ -1268,11 +1589,18 @@ class BladesRoll extends DocumentSheet { .filter((pUser) => pUser !== null && !userIDs[RollPermissions.Primary].includes(pUser)); } } + /*~ @@DOUBLE-BLANK@@ ~*/ static async PrepareActionRoll(rollID, config) { + /*~ @@DOUBLE-BLANK@@ ~*/ + // Validate the rollTrait if (!(U.isInt(config.rollTrait) || U.lCase(config.rollTrait) in { ...ActionTrait, ...Factor })) { throw new Error(`[PrepareActionRoll()] Bad RollTrait for Action Roll: ${config.rollTrait}`); } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Retrieve the roll users const userIDs = BladesRoll.GetUserPermissions(config); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Prepare roll user flag data const userFlagData = {}; Object.entries(userIDs) .forEach(([rollPermission, idsArray]) => { @@ -1280,24 +1608,38 @@ class BladesRoll extends DocumentSheet { userFlagData[id] = rollPermission; } }); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Prepare the flag data. const flagUpdateData = { ...BladesRoll.DefaultFlagData, ...pruneConfig(config), userPermissions: userFlagData, rollID }; + /*~ @@DOUBLE-BLANK@@ ~*/ + // Return flagData and roll users return { flagUpdateData, userIDs }; + /*~ @@DOUBLE-BLANK@@ ~*/ } + /*~ @@DOUBLE-BLANK@@ ~*/ static async PrepareResistanceRoll(rollID, config) { + /*~ @@DOUBLE-BLANK@@ ~*/ + // Validate consequenceData if (!config.resistanceData || !isValidConsequenceData(config.resistanceData?.consequence)) { eLog.error("rollCollab", "[PrepareResistanceRoll] Bad Roll Consequence Data.", config); throw new Error("[PrepareResistanceRoll()] Bad Consequence Data for Resistance Roll"); } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Set rollTrait config.rollTrait = config.resistanceData.consequence.attribute; + /*~ @@DOUBLE-BLANK@@ ~*/ + // Retrieve the roll users const userIDs = BladesRoll.GetUserPermissions(config); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Prepare roll user flag data const userFlagData = {}; Object.entries(userIDs) .forEach(([rollPermission, idsArray]) => { @@ -1305,22 +1647,33 @@ class BladesRoll extends DocumentSheet { userFlagData[id] = rollPermission; } }); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Prepare the flag data. const flagUpdateData = { ...BladesRoll.DefaultFlagData, ...pruneConfig(config), userPermissions: userFlagData, rollID }; + /*~ @@DOUBLE-BLANK@@ ~*/ + // Return flagData and roll users return { flagUpdateData, userIDs }; } + /*~ @@DOUBLE-BLANK@@ ~*/ static async PrepareFortuneRoll(rollID, config) { + /*~ @@DOUBLE-BLANK@@ ~*/ + // Validate the rollTrait if (!(U.isInt(config.rollTrait) || U.lCase(config.rollTrait) in { ...ActionTrait, ...AttributeTrait, ...Factor })) { throw new Error(`[PrepareFortuneRoll()] Bad RollTrait for Fortune Roll: ${config.rollTrait}`); } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Retrieve the roll users const userIDs = BladesRoll.GetUserPermissions(config); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Prepare roll user flag data const userFlagData = {}; Object.entries(userIDs) .forEach(([rollPermission, idsArray]) => { @@ -1328,18 +1681,26 @@ class BladesRoll extends DocumentSheet { userFlagData[id] = rollPermission; } }); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Prepare the flag data. const flagUpdateData = { ...BladesRoll.DefaultFlagData, ...pruneConfig(config), userPermissions: userFlagData, rollID }; + /*~ @@DOUBLE-BLANK@@ ~*/ + // Return flagData and roll users return { flagUpdateData, userIDs }; + /*~ @@DOUBLE-BLANK@@ ~*/ } + /*~ @@DOUBLE-BLANK@@ ~*/ static async PrepareIndulgeViceRoll(rollID, config) { + /*~ @@DOUBLE-BLANK@@ ~*/ + // If primary doc is given, derive rollTrait from that (i.e. lowest Attribute) const { rollPrimaryDoc } = config.rollPrimaryData; if (BladesPC.IsType(rollPrimaryDoc)) { const minAttrVal = Math.min(...Object.values(rollPrimaryDoc.attributes)); @@ -1348,8 +1709,14 @@ class BladesRoll extends DocumentSheet { if (!(U.isInt(config.rollTrait) || U.lCase(config.rollTrait) in AttributeTrait)) { throw new Error(`[PrepareIndulgeViceRoll()] Bad RollTrait for Indulge Vice Roll: ${config.rollTrait}`); } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Set other known config values config.rollDowntimeAction = DowntimeAction.IndulgeVice; + /*~ @@DOUBLE-BLANK@@ ~*/ + // Retrieve the roll users const userIDs = BladesRoll.GetUserPermissions(config); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Prepare roll user flag data const userFlagData = {}; Object.entries(userIDs) .forEach(([rollPermission, idsArray]) => { @@ -1357,25 +1724,43 @@ class BladesRoll extends DocumentSheet { userFlagData[id] = rollPermission; } }); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Prepare the flag data. const flagUpdateData = { ...BladesRoll.DefaultFlagData, ...pruneConfig(config), userPermissions: userFlagData, rollID }; + /*~ @@DOUBLE-BLANK@@ ~*/ + // Return flagData and roll users return { flagUpdateData, userIDs }; } - static async NewRoll(config) { + /*~ @@DOUBLE-BLANK@@ ~*/ + /** + * This static method accepts a partial version of the config options required + * to build a BladesRoll instance, sets the requisite flags on the storage + * document, then sends out a socket call to the relevant users to construct + * and display the roll instance. + * + * @param {BladesRoll.ConstructorConfig} config The configuration object for the new roll. + */ + static async NewRoll(config) { + // If no rollType is provided, throw an error. if (!isRollType(config.rollType)) { throw new Error("[BladesRoll.NewRoll()] You must provide a valid rollType in the config object."); } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Get User document to serve as flag storage. const rollUser = game.users.get(config.rollUserID ?? game.user.id ?? ""); if (!rollUser?.id) { throw new Error("[BladesRoll.NewRoll()] You must provide a valid rollUserID in the config object."); } + /*~ @@DOUBLE-BLANK@@ ~*/ + // If roll flag data is already on user, throw an error. const flagData = rollUser.getFlag("eunos-blades", "rollCollab"); if (flagData) { const { rollID } = flagData; @@ -1385,9 +1770,12 @@ class BladesRoll extends DocumentSheet { await rollUser.setFlag("eunos-blades", `rollCollabArchive.${rollID}`, flagData); await rollUser.unsetFlag("eunos-blades", "rollCollab"); } + /*~ @@DOUBLE-BLANK@@ ~*/ + // If no rollPrimaryData is provided, attempt to derive it from the rest of the config object let { rollPrimaryData } = config; if (!BladesRollPrimary.IsValidData(rollPrimaryData)) { let rollPrimarySourceData; + // If the user is a player with a BladesPC character, assume that is rollPrimary. if (BladesPC.IsType(rollUser.character)) { rollPrimarySourceData = rollUser.character; rollPrimaryData = { @@ -1403,9 +1791,14 @@ class BladesRoll extends DocumentSheet { if (!BladesRollPrimary.IsValidData(rollPrimaryData)) { throw new Error("[BladesRoll.NewRoll()] A valid source of PrimaryDocData must be provided to construct a roll."); } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Create a random ID for storing the roll instance const rID = randomID(); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Derive user flag data depending on given roll type and subtype let userIDs; let flagUpdateData; + /*~ @@DOUBLE-BLANK@@ ~*/ switch (config.rollType) { case RollType.Action: { ({ userIDs, flagUpdateData } = await BladesRoll.PrepareActionRoll(rID, { @@ -1440,19 +1833,32 @@ class BladesRoll extends DocumentSheet { break; } } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Log the roll data eLog.checkLog3("bladesRoll", "BladesRoll.NewRoll()", { userIDs, flagUpdateData, rollPrimaryData: flagUpdateData.rollPrimaryData }); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Store the roll data on the storage document await rollUser.setFlag(C.SYSTEM_ID, "rollCollab", flagUpdateData); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Send out socket calls to all users to see the roll. socketlib.system.executeForUsers("constructRollCollab", userIDs[RollPermissions.GM], { userID: rollUser.id, rollID: rID, rollPermission: RollPermissions.GM }); socketlib.system.executeForUsers("constructRollCollab", userIDs[RollPermissions.Primary], { userID: rollUser.id, rollID: rID, rollPermission: RollPermissions.Primary }); socketlib.system.executeForUsers("constructRollCollab", userIDs[RollPermissions.Observer], { userID: rollUser.id, rollID: rID, rollPermission: RollPermissions.Observer }); socketlib.system.executeForUsers("constructRollCollab", userIDs[RollPermissions.Participant], { userID: rollUser.id, rollID: rID, rollPermission: RollPermissions.Participant }); } - + // #endregion + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region Constructor ~ rollID; + /*~ @@DOUBLE-BLANK@@ ~*/ rollPermission; + /*~ @@DOUBLE-BLANK@@ ~*/ _rollPrimary; + /*~ @@DOUBLE-BLANK@@ ~*/ _rollOpposition; + /*~ @@DOUBLE-BLANK@@ ~*/ _rollParticipants; + /*~ @@DOUBLE-BLANK@@ ~*/ constructor(userID, rollID, rollPermission) { const rollUser = game.users.get(userID); if (!rollUser) { @@ -1481,13 +1887,19 @@ class BladesRoll extends DocumentSheet { } BladesRoll.Current[this.rollID] = this; } + // #endregion + /*~ @@DOUBLE-BLANK@@ ~*/ async setRollSubType(subType) { await this.setFlagVal("rollSubType", subType); } + /*~ @@DOUBLE-BLANK@@ ~*/ async addRollParticipant(participantRef, rollSection, rollSubSection) { + /*~ @@DOUBLE-BLANK@@ ~*/ if (!rollSubSection) { - rollSubSection = "Assist"; + /* Insert logic to determine from rollSection and number of existing Group_X members */ + rollSubSection = "Assist"; } + /*~ @@DOUBLE-BLANK@@ ~*/ const participantData = typeof participantRef === "string" ? game.actors.get(participantRef) ?? game.actors.getName(participantRef) @@ -1502,24 +1914,31 @@ class BladesRoll extends DocumentSheet { rollParticipantSubSection: rollSubSection, rollParticipantID: participantData.rollParticipantID }); + /*~ @@DOUBLE-BLANK@@ ~*/ await rollParticipant.updateRollFlags(); socketlib.system.executeForEveryone("renderRollCollab", this.rollID); } + /*~ @@DOUBLE-BLANK@@ ~*/ async removeRollParticipant(rollSection, rollSubSection) { await this.clearFlagVal(`rollParticipantData.${rollSection}.${rollSubSection}`); } + /*~ @@DOUBLE-BLANK@@ ~*/ async updateUserPermission(_user, _permission) { - } - + /* Force-render roll with new permissions */ + } + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region Basic User Flag Getters/Setters ~ get flagData() { if (!this.document.getFlag(C.SYSTEM_ID, "rollCollab")) { throw new Error("[get flags()] No RollCollab Flags Found on User Document"); } return this.document.getFlag(C.SYSTEM_ID, "rollCollab"); } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollPrimary() { return this._rollPrimary; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollPrimaryDoc() { if (BladesRollPrimary.IsDoc(this.rollPrimary.rollPrimaryDoc)) { return this.rollPrimary.rollPrimaryDoc; @@ -1529,12 +1948,14 @@ class BladesRoll extends DocumentSheet { } return undefined; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollOpposition() { if (!this._rollOpposition && BladesRollOpposition.IsValidData(this.flagData.rollOppData)) { this._rollOpposition = new BladesRollOpposition(this, this.flagData.rollOppData); } return this._rollOpposition?.refresh(); } + /*~ @@DOUBLE-BLANK@@ ~*/ set rollOpposition(val) { if (val === undefined) { this._rollOpposition = undefined; @@ -1544,12 +1965,21 @@ class BladesRoll extends DocumentSheet { val.updateRollFlags(); } } - prepareRollParticipantData() { + /*~ @@DOUBLE-BLANK@@ ~*/ + /** + * This method prepares the roll participant data. + * It iterates over the roll sections (roll, position, effect) and for each section, + * it creates a new BladesRollParticipant instance for each participant in that section. + * The created instances are stored in the rollParticipants object. + */ + prepareRollParticipantData() { const participantFlagData = this.flagData.rollParticipantData; if (!participantFlagData) { return; } + /*~ @@DOUBLE-BLANK@@ ~*/ const rollParticipants = {}; + /*~ @@DOUBLE-BLANK@@ ~*/ [ RollModSection.roll, RollModSection.position, @@ -1571,11 +2001,14 @@ class BladesRoll extends DocumentSheet { rollParticipants[rollSection] = sectionParticipants; } }); + /*~ @@DOUBLE-BLANK@@ ~*/ this._rollParticipants = rollParticipants; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollParticipants() { return this._rollParticipants; } + /*~ @@DOUBLE-BLANK@@ ~*/ getRollParticipant(section, subSection) { if (isParticipantSection(section) && isParticipantSubSection(subSection)) { const sectionData = this.rollParticipants?.[section]; @@ -1585,6 +2018,7 @@ class BladesRoll extends DocumentSheet { } return null; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollParticipantSelectOptions() { const nonPrimaryPCs = BladesPC.All .filter((actor) => actor.hasTag(Tag.PC.ActivePC) && actor.id !== this.rollPrimary.rollPrimaryID) @@ -1595,13 +2029,21 @@ class BladesRoll extends DocumentSheet { Group: nonPrimaryPCs }; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollType() { return this.flagData.rollType; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollSubType() { return this.flagData.rollSubType; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollDowntimeAction() { return this.flagData.rollDowntimeAction; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollTrait() { return this.flagData.rollTrait; } + /*~ @@DOUBLE-BLANK@@ ~*/ _rollTraitValOverride; + /*~ @@DOUBLE-BLANK@@ ~*/ get rollTraitValOverride() { return this._rollTraitValOverride; } + /*~ @@DOUBLE-BLANK@@ ~*/ set rollTraitValOverride(val) { this._rollTraitValOverride = val; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollTraitData() { if (BladesActor.IsType(this.rollPrimaryDoc, BladesActorType.pc)) { if (isAction(this.rollTrait)) { @@ -1639,6 +2081,7 @@ class BladesRoll extends DocumentSheet { } throw new Error(`[get rollTraitData] Invalid rollTrait: '${this.rollTrait}'`); } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollTraitOptions() { if (BladesActor.IsType(this.rollPrimaryDoc, BladesActorType.pc)) { if (isAction(this.rollTrait)) { @@ -1668,39 +2111,48 @@ class BladesRoll extends DocumentSheet { } throw new Error(`[get rollTraitOptions] Invalid rollTrait: '${this.rollTrait}'`); } + /*~ @@DOUBLE-BLANK@@ ~*/ get posEffectTrade() { return this.flagData?.rollPosEffectTrade ?? false; } + /*~ @@DOUBLE-BLANK@@ ~*/ getFlagVal(flagKey) { if (flagKey) { return this.document.getFlag(C.SYSTEM_ID, `rollCollab.${flagKey}`); } return this.document.getFlag(C.SYSTEM_ID, "rollCollab"); } + /*~ @@DOUBLE-BLANK@@ ~*/ async setFlagVal(flagKey, flagVal, isRerendering = true) { await this.document.setFlag(C.SYSTEM_ID, `rollCollab.${flagKey}`, flagVal); if (isRerendering) { socketlib.system.executeForEveryone("renderRollCollab", this.rollID); } } + /*~ @@DOUBLE-BLANK@@ ~*/ async clearFlagVal(flagKey, isRerendering = true) { await this.document.unsetFlag(C.SYSTEM_ID, `rollCollab.${flagKey}`); if (isRerendering) { socketlib.system.executeForEveryone("renderRollCollab", this.rollID); } } + /*~ @@DOUBLE-BLANK@@ ~*/ get initialPosition() { return this.getFlagVal("rollPositionInitial") ?? Position.risky; } + /*~ @@DOUBLE-BLANK@@ ~*/ set initialPosition(val) { this.setFlagVal("rollPositionInitial", val ?? Position.risky); } + /*~ @@DOUBLE-BLANK@@ ~*/ get initialEffect() { return this.getFlagVal("rollEffectInitial") ?? Effect.standard; } + /*~ @@DOUBLE-BLANK@@ ~*/ set initialEffect(val) { this.setFlagVal("rollEffectInitial", val); } + /*~ @@DOUBLE-BLANK@@ ~*/ get isApplyingConsequences() { if (this.rollType !== RollType.Action) { return false; @@ -1713,12 +2165,19 @@ class BladesRoll extends DocumentSheet { } return true; } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Get rollConsequence() --> For resistance rolls. get rollConsequence() { return this.getFlagVal("resistanceData.consequence"); } + /*~ @@DOUBLE-BLANK@@ ~*/ async applyConsequencesFromDialog(_html) { - } - + /* Convert values of dialog input fields to flag data */ + } + /*~ @@DOUBLE-BLANK@@ ~*/ + // #endregion + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region GETTERS: DERIVED DATA ~ get finalPosition() { return Object.values(Position)[U.clampNum(Object.values(Position) .indexOf(this.initialPosition) @@ -1726,6 +2185,7 @@ class BladesRoll extends DocumentSheet { + (this.posEffectTrade === "position" ? 1 : 0) + (this.posEffectTrade === "effect" ? -1 : 0), [0, 2])]; } + /*~ @@DOUBLE-BLANK@@ ~*/ get finalEffect() { return Object.values(Effect)[U.clampNum(Object.values(Effect) .indexOf(this.initialEffect) @@ -1733,29 +2193,36 @@ class BladesRoll extends DocumentSheet { + (this.posEffectTrade === "effect" ? 1 : 0) + (this.posEffectTrade === "position" ? -1 : 0), [0, 4])]; } + /*~ @@DOUBLE-BLANK@@ ~*/ get finalResult() { return this.getModsDelta(RollModSection.result) + (this.flagData?.GMBoosts.Result ?? 0) + (this.tempGMBoosts.Result ?? 0); } + /*~ @@DOUBLE-BLANK@@ ~*/ get finalDicePool() { return Math.max(0, this.rollTraitData.value + this.getModsDelta(RollModSection.roll) + (this.flagData.GMBoosts.Dice ?? 0) + (this.tempGMBoosts.Dice ?? 0)); } + /*~ @@DOUBLE-BLANK@@ ~*/ get isRollingZero() { return Math.max(0, this.rollTraitData.value + this.getModsDelta(RollModSection.roll) + (this.flagData.GMBoosts.Dice ?? 0) + (this.tempGMBoosts.Dice ?? 0)) <= 0; } + /*~ @@DOUBLE-BLANK@@ ~*/ _roll; + /*~ @@DOUBLE-BLANK@@ ~*/ get roll() { this._roll ??= new Roll(`${this.isRollingZero ? 2 : this.finalDicePool}d6`, {}); return this._roll; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollFactors() { + /*~ @@DOUBLE-BLANK@@ ~*/ const defaultFactors = { [Factor.tier]: { name: "Tier", @@ -1806,10 +2273,13 @@ class BladesRoll extends DocumentSheet { cssClasses: "factor-gold" } }; + /*~ @@DOUBLE-BLANK@@ ~*/ const mergedSourceFactors = U.objMerge(U.objMerge(defaultFactors, this.rollPrimary.rollFactors, { isMutatingOk: false }), this.flagData.rollFactorToggles.source, { isMutatingOk: false }); + /*~ @@DOUBLE-BLANK@@ ~*/ const mergedOppFactors = this.rollOpposition ? U.objMerge(U.objMerge(defaultFactors, this.rollOpposition.rollFactors, { isMutatingOk: false }), this.flagData.rollFactorToggles.opposition, { isMutatingOk: false }) : {}; + /*~ @@DOUBLE-BLANK@@ ~*/ return { source: Object.fromEntries(Object.entries(mergedSourceFactors) .map(([factor, factorData]) => { @@ -1837,22 +2307,39 @@ class BladesRoll extends DocumentSheet { })) }; } + // #endregion + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region ROLL MODS: Getters & Update Method ~ + /*~ @@DOUBLE-BLANK@@ ~*/ initRollMods(modsData) { + // Reset override values previously enabled by rollmods this.rollTraitValOverride = undefined; this.rollFactorPenaltiesNegated = {}; this.tempGMBoosts = {}; + /*~ @@DOUBLE-BLANK@@ ~*/ this.rollMods = modsData.map((modData) => new BladesRollMod(modData, this)); + /*~ @@DOUBLE-BLANK@@ ~*/ const initReport = {}; - this.rollMods = this.rollMods.filter((rollMod) => rollMod.isValidForRollType()); - this.rollMods + /*~ @@DOUBLE-BLANK@@ ~*/ + /* *** PASS ZERO: ROLLTYPE VALIDATION PASS *** */ + this.rollMods = this.rollMods.filter((rollMod) => rollMod.isValidForRollType()); + /*~ @@DOUBLE-BLANK@@ ~*/ + /* *** PASS ONE: DISABLE PASS *** */ + this.rollMods + // ... Conditional Status Pass .filter((rollMod) => !rollMod.setConditionalStatus()) + // ... AutoReveal/AutoEnable Pass .filter((rollMod) => !rollMod.setAutoStatus()) + // ... Payable Pass .forEach((rollMod) => { rollMod.setPayableStatus(); }); - const parseForceOnKeys = (mod) => { + /*~ @@DOUBLE-BLANK@@ ~*/ + /* *** PASS TWO: FORCE-ON PASS *** */ + const parseForceOnKeys = (mod) => { const holdKeys = mod.effectKeys.filter((key) => key.startsWith("ForceOn")); if (holdKeys.length === 0) { return; } + /*~ @@DOUBLE-BLANK@@ ~*/ while (holdKeys.length) { const thisTarget = holdKeys.pop()?.split(/-/)?.pop(); if (thisTarget === "BestAction") { @@ -1889,14 +2376,21 @@ class BladesRoll extends DocumentSheet { } }; this.getActiveRollMods().forEach((rollMod) => parseForceOnKeys(rollMod)); - + /*~ @@DOUBLE-BLANK@@ ~*/ + /* *** PASS THREE: PUSH-CHECK PASS *** */ + /*~ @@DOUBLE-BLANK@@ ~*/ + // IF ROLL FORCED ... if (this.isForcePushed()) { + // ... Force Off _ALL_ visible, inactive "Is-Push" mods. this.getInactivePushMods() .filter((mod) => !mod.isBasicPush) .forEach((mod) => { mod.heldStatus = RollModStatus.ForcedOff; }); } + /*~ @@DOUBLE-BLANK@@ ~*/ + // ... BY CATEGORY ... [RollModSection.roll, RollModSection.effect].forEach((cat) => { if (this.isPushed(cat)) { + // ... if pushed by positive mod, Force Off any visible Bargain if (cat === RollModSection.roll && this.isPushed(cat, "positive")) { const bargainMod = this.getRollModByID("Bargain-positive-roll"); if (bargainMod?.isVisible) { @@ -1905,23 +2399,31 @@ class BladesRoll extends DocumentSheet { } } else { + // Otherwise, hide all Is-Push mods this.getInactivePushMods(cat) .filter((mod) => !mod.isBasicPush) .forEach((mod) => { mod.heldStatus = RollModStatus.Hidden; }); } }); - + /*~ @@DOUBLE-BLANK@@ ~*/ + /* *** PASS FOUR: Relevancy Pass *** */ + /*~ @@DOUBLE-BLANK@@ ~*/ this.getVisibleRollMods() .forEach((mod) => { mod.setRelevancyStatus(); }); - + /*~ @@DOUBLE-BLANK@@ ~*/ + /* *** PASS FIVE: Overpayment Pass *** */ + /*~ @@DOUBLE-BLANK@@ ~*/ + // ... If 'Cost-SpecialArmor' active, ForceOff other visible Cost-SpecialArmor mods const activeArmorCostMod = this.getActiveRollMods().find((mod) => mod.effectKeys.includes("Cost-SpecialArmor")); if (activeArmorCostMod) { this.getVisibleRollMods() .filter((mod) => !mod.isActive && mod.effectKeys.includes("Cost-SpecialArmor")) .forEach((mod) => { mod.heldStatus = RollModStatus.ForcedOff; }); } + /*~ @@DOUBLE-BLANK@@ ~*/ eLog.checkLog2("rollMods", "*** initRollMods() PASS ***", initReport); } + /*~ @@DOUBLE-BLANK@@ ~*/ isTraitRelevant(trait) { if (trait in Factor) { const { source, opposition } = this.rollFactors; @@ -1929,18 +2431,26 @@ class BladesRoll extends DocumentSheet { } return false; } + /*~ @@DOUBLE-BLANK@@ ~*/ get isParticipantRoll() { return (this.rollType === RollType.Fortune && !game.user.isGM) || (this.rollSubType === RollSubType.GroupParticipant); } + /*~ @@DOUBLE-BLANK@@ ~*/ rollFactorPenaltiesNegated = {}; + /*~ @@DOUBLE-BLANK@@ ~*/ negateFactorPenalty(factor) { this.rollFactorPenaltiesNegated[factor] = true; } + /*~ @@DOUBLE-BLANK@@ ~*/ tempGMBoosts = {}; + /*~ @@DOUBLE-BLANK@@ ~*/ isPushed(cat, posNeg) { return this.getActiveBasicPushMods(cat, posNeg).length > 0; } + /*~ @@DOUBLE-BLANK@@ ~*/ hasOpenPush(cat, posNeg) { return this.isPushed(cat) && this.getOpenPushMods(cat, posNeg).length > 0; } + /*~ @@DOUBLE-BLANK@@ ~*/ isForcePushed(cat, posNeg) { return this.isPushed(cat) && this.getForcedPushMods(cat, posNeg).length > 0; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollCosts() { if (!this.isPushed) { return 0; @@ -1955,11 +2465,13 @@ class BladesRoll extends DocumentSheet { + ((effectPush?.isActive && effectPush?.stressCost) || 0) - (negatePushCostMods.length * 2); } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollCostData() { return this.getActiveRollMods() .map((rollMod) => rollMod.costs ?? []) .flat(); } + /*~ @@DOUBLE-BLANK@@ ~*/ getRollModByName(name, cat, posNeg) { const modMatches = this.rollMods.filter((rollMod) => { if (U.lCase(rollMod.name) !== U.lCase(name)) { @@ -1981,99 +2493,149 @@ class BladesRoll extends DocumentSheet { } return modMatches[0]; } + /*~ @@DOUBLE-BLANK@@ ~*/ getRollModByID(id) { return this.rollMods.find((rollMod) => rollMod.id === id); } + /*~ @@DOUBLE-BLANK@@ ~*/ getRollMods(cat, posNeg) { return this.rollMods.filter((rollMod) => (!cat || rollMod.section === cat) && (!posNeg || rollMod.posNeg === posNeg)); } + /*~ @@DOUBLE-BLANK@@ ~*/ getVisibleRollMods(cat, posNeg) { return this.getRollMods(cat, posNeg).filter((rollMod) => rollMod.isVisible); } + /*~ @@DOUBLE-BLANK@@ ~*/ getActiveRollMods(cat, posNeg) { return this.getRollMods(cat, posNeg).filter((rollMod) => rollMod.isActive); } + /*~ @@DOUBLE-BLANK@@ ~*/ getVisibleInactiveRollMods(cat, posNeg) { return this.getVisibleRollMods(cat, posNeg).filter((rollMod) => !rollMod.isActive); } + /*~ @@DOUBLE-BLANK@@ ~*/ getPushMods(cat, posNeg) { return this.getRollMods(cat, posNeg).filter((rollMod) => rollMod.isPush); } + /*~ @@DOUBLE-BLANK@@ ~*/ getVisiblePushMods(cat, posNeg) { return this.getPushMods(cat, posNeg).filter((rollMod) => rollMod.isVisible); } + /*~ @@DOUBLE-BLANK@@ ~*/ getActivePushMods(cat, posNeg) { return this.getVisiblePushMods(cat, posNeg).filter((rollMod) => rollMod.isActive); } + /*~ @@DOUBLE-BLANK@@ ~*/ getActiveBasicPushMods(cat, posNeg) { return this.getActivePushMods(cat, posNeg).filter((rollMod) => rollMod.isBasicPush); } + /*~ @@DOUBLE-BLANK@@ ~*/ getInactivePushMods(cat, posNeg) { return this.getVisiblePushMods(cat, posNeg).filter((rollMod) => !rollMod.isActive); } + /*~ @@DOUBLE-BLANK@@ ~*/ getInactiveBasicPushMods(cat, posNeg) { return this.getInactivePushMods(cat, posNeg).filter((rollMod) => rollMod.isBasicPush); } + /*~ @@DOUBLE-BLANK@@ ~*/ getForcedPushMods(cat, posNeg) { return this.getActivePushMods(cat, posNeg) .filter((rollMod) => rollMod.isBasicPush && rollMod.status === RollModStatus.ForcedOn); } + /*~ @@DOUBLE-BLANK@@ ~*/ getOpenPushMods(cat, posNeg) { return this.getActivePushMods(cat, posNeg) .filter((rollMod) => rollMod.isBasicPush && rollMod.status === RollModStatus.ToggledOn); } + /*~ @@DOUBLE-BLANK@@ ~*/ getModsDelta = (cat) => { return U.sum([ ...this.getActiveRollMods(cat, "positive").map((mod) => mod.value), ...this.getActiveRollMods(cat, "negative").map((mod) => -mod.value) ]); }; + /*~ @@DOUBLE-BLANK@@ ~*/ _rollMods; - compareMods(modA, modB) { + /*~ @@DOUBLE-BLANK@@ ~*/ + /** + * Compare function for sorting roll mods. + * @param {BladesRollMod} modA First mod to compare. + * @param {BladesRollMod} modB Second mod to compare. + * @returns {number} - Comparison result. + */ + compareMods(modA, modB) { + // Define the order of mod names for sorting const modOrder = ["Bargain", "Assist", "Setup"]; + /*~ @@DOUBLE-BLANK@@ ~*/ + // Check for basic push if (modA.isBasicPush) { return -1; } if (modB.isBasicPush) { return 1; } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Check for active Bargain if (modA.name === "Bargain" && modA.isActive) { return -1; } if (modB.name === "Bargain" && modB.isActive) { return 1; } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Check for push if (modA.isPush) { return -1; } if (modB.isPush) { return 1; } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Check for mod name order const modAIndex = modOrder.indexOf(modA.name); const modBIndex = modOrder.indexOf(modB.name); if (modAIndex !== -1 && modBIndex !== -1) { return modAIndex - modBIndex; } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Default to alphabetical order return modA.name.localeCompare(modB.name); } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollMods() { if (!this._rollMods) { throw new Error("[get rollMods] No roll mods found!"); } return [...this._rollMods].sort((modA, modB) => this.compareMods(modA, modB)); } + /*~ @@DOUBLE-BLANK@@ ~*/ set rollMods(val) { this._rollMods = val; } - - - async getData() { + /*~ @@DOUBLE-BLANK@@ ~*/ + // #endregion + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region *** GETDATA *** ~ + /*~ @@DOUBLE-BLANK@@ ~*/ + /** + * Retrieve the data for rendering the base RollCollab sheet. + * @returns {Promise} The data which can be used to render the HTML of the sheet. + */ + async getData() { const context = super.getData(); + /*~ @@DOUBLE-BLANK@@ ~*/ this.initRollMods(this.getRollModsData()); this.rollMods.forEach((rollMod) => rollMod.applyRollModEffectKeys()); + /*~ @@DOUBLE-BLANK@@ ~*/ const sheetData = this.getSheetData(this.getIsGM(), this.getRollCosts()); + /*~ @@DOUBLE-BLANK@@ ~*/ return { ...context, ...sheetData }; } - getRollModsData() { + /*~ @@DOUBLE-BLANK@@ ~*/ + /** + * Gets the roll modifications data. + * @returns {BladesRoll.RollModData[]} The roll modifications data. + */ + getRollModsData() { const defaultMods = [ ...BladesRoll.DefaultRollMods, ...this.rollPrimary.rollModsData @@ -2086,25 +2648,60 @@ class BladesRoll extends DocumentSheet { } return defaultMods; } - getIsGM() { + /*~ @@DOUBLE-BLANK@@ ~*/ + /** + * Determines if the user is a game master. + * @returns {boolean} Whether the user is a GM. + */ + getIsGM() { return game.eunoblades.Tracker?.system.is_spoofing_player ? false : game.user.isGM; } - getRollCosts() { + /*~ @@DOUBLE-BLANK@@ ~*/ + /** + * Gets the roll costs. + * @returns {BladesRoll.CostData[]} The roll costs. + */ + getRollCosts() { return this.getActiveRollMods() .map((rollMod) => rollMod.costs) .flat() .filter((costData) => costData !== undefined); } - getStressCosts(rollCosts) { + /*~ @@DOUBLE-BLANK@@ ~*/ + /** + * Filters the roll costs to retrieve stress costs. + * @param {BladesRoll.CostData[]} rollCosts The roll costs. + * @returns {BladesRoll.CostData[]} The stress costs. + */ + getStressCosts(rollCosts) { return rollCosts.filter((costData) => costData.costType === "Stress"); } - getTotalStressCost(stressCosts) { + /*~ @@DOUBLE-BLANK@@ ~*/ + /** + * Calculates the total stress cost. + * @param {BladesRoll.CostData[]} stressCosts The stress costs. + * @returns {number} The total stress cost. + */ + getTotalStressCost(stressCosts) { return U.sum(stressCosts.map((costData) => costData.costAmount)); } - getSpecArmorCost(rollCosts) { + /*~ @@DOUBLE-BLANK@@ ~*/ + /** + * Searches for any special armor roll costs. + * @param {BladesRoll.CostData[]} rollCosts The roll costs. + * @returns {BladesRoll.CostData[]} The stress costs. + */ + getSpecArmorCost(rollCosts) { return rollCosts.find((costData) => costData.costType === "SpecialArmor"); } - getSheetData(isGM, rollCosts) { + /*~ @@DOUBLE-BLANK@@ ~*/ + /** + * Constructs the sheet data. + * @param {boolean} isGM If the user is a GM. + * @param {BladesRoll.CostData[]} rollCosts The roll costs. + * @returns {BladesRoll.SheetData} The constructed sheet data. + */ + getSheetData(isGM, rollCosts) { const { flagData: rData, rollPrimary, rollTraitData, rollTraitOptions, finalDicePool, finalPosition, finalEffect, finalResult, rollMods, rollFactors } = this; if (!rollPrimary) { throw new Error("A primary roll source is required for BladesRoll."); @@ -2115,32 +2712,42 @@ class BladesRoll extends DocumentSheet { editable: this.options.editable, isGM, system: this.rollPrimaryDoc?.system, + /*~ @@DOUBLE-BLANK@@ ~*/ rollMods, rollPrimary, rollTraitData, rollTraitOptions, + /*~ @@DOUBLE-BLANK@@ ~*/ diceTotal: finalDicePool, + /*~ @@DOUBLE-BLANK@@ ~*/ rollOpposition: this.rollOpposition, rollParticipants: this.rollParticipants, rollParticipantOptions: this.rollParticipantSelectOptions, rollEffects: Object.values(Effect), rollTraitValOverride: this.rollTraitValOverride, rollFactorPenaltiesNegated: this.rollFactorPenaltiesNegated, + /*~ @@DOUBLE-BLANK@@ ~*/ posRollMods: Object.fromEntries(Object.values(RollModSection) .map((cat) => [cat, this.getRollMods(cat, "positive")])), negRollMods: Object.fromEntries(Object.values(RollModSection) .map((cat) => [cat, this.getRollMods(cat, "negative")])), hasInactiveConditionals: this.calculateHasInactiveConditionalsData(), + /*~ @@DOUBLE-BLANK@@ ~*/ rollFactors, ...this.calculateOddsHTML(finalDicePool, finalResult), costData: this.getCostsHTML(rollCosts) }; + /*~ @@DOUBLE-BLANK@@ ~*/ const rollPositionData = this.calculatePositionData(finalPosition); const rollEffectData = this.calculateEffectData(isGM, finalEffect); const rollResultData = this.calculateResultData(isGM, finalResult); + /*~ @@DOUBLE-BLANK@@ ~*/ const GMBoostsData = this.calculateGMBoostsData(rData); + /*~ @@DOUBLE-BLANK@@ ~*/ const positionEffectTradeData = this.calculatePositionEffectTradeData(); + /*~ @@DOUBLE-BLANK@@ ~*/ const userPermission = U.objFindKey(baseData.userPermissions, (v) => v.includes(game.user.id ?? "")); + /*~ @@DOUBLE-BLANK@@ ~*/ return { ...baseData, ...(this.rollPrimary.rollPrimaryDoc ? { rollPrimary: this.rollPrimary.rollPrimaryDoc } : {}), @@ -2152,12 +2759,14 @@ class BladesRoll extends DocumentSheet { userPermission }; } + /*~ @@DOUBLE-BLANK@@ ~*/ calculatePositionData(finalPosition) { return { rollPositions: Object.values(Position), rollPositionFinal: finalPosition }; } + /*~ @@DOUBLE-BLANK@@ ~*/ calculateEffectData(isGM, finalEffect) { return { rollEffects: Object.values(Effect), @@ -2166,6 +2775,7 @@ class BladesRoll extends DocumentSheet { || (isGM && this.getRollMods(RollModSection.after).length > 0) }; } + /*~ @@DOUBLE-BLANK@@ ~*/ calculateResultData(isGM, finalResult) { return { rollResultFinal: finalResult, @@ -2174,6 +2784,7 @@ class BladesRoll extends DocumentSheet { || (isGM && this.getRollMods(RollModSection.result).length > 0) }; } + /*~ @@DOUBLE-BLANK@@ ~*/ calculateGMBoostsData(flagData) { return { GMBoosts: { @@ -2192,13 +2803,21 @@ class BladesRoll extends DocumentSheet { } }; } + /*~ @@DOUBLE-BLANK@@ ~*/ calculateOddsHTML(diceTotal, finalResult) { if (this.rollType === RollType.Resistance) { return this.calculateOddsHTML_Resistance(diceTotal); } return this.calculateOddsHTML_Standard(diceTotal, finalResult); } - calculateOddsHTML_Standard(diceTotal, finalResult) { + /*~ @@DOUBLE-BLANK@@ ~*/ + /** + * Calculate odds starting & ending HTML based on given dice total. + * @param {number} diceTotal Total number of dice. + * @param {number} finalResult + * @returns {{oddsHTMLStart: string, oddsHTMLStop: string}} Opening & Closing HTML for odds bar display + */ + calculateOddsHTML_Standard(diceTotal, finalResult) { const oddsColors = { crit: "var(--blades-gold)", success: "var(--blades-white-bright)", @@ -2206,6 +2825,7 @@ class BladesRoll extends DocumentSheet { fail: "var(--blades-black-dark)" }; const odds = { ...C.DiceOddsStandard[diceTotal] }; + /*~ @@DOUBLE-BLANK@@ ~*/ if (finalResult < 0) { for (let i = finalResult; i < 0; i++) { oddsColors.crit = oddsColors.success; @@ -2220,13 +2840,16 @@ class BladesRoll extends DocumentSheet { oddsColors.success = oddsColors.crit; } } + /*~ @@DOUBLE-BLANK@@ ~*/ const resultElements = []; + /*~ @@DOUBLE-BLANK@@ ~*/ Object.entries(odds).reverse().forEach(([result, chance]) => { if (chance === 0) { return; } resultElements.push(`
 
`); }); + /*~ @@DOUBLE-BLANK@@ ~*/ return { oddsHTMLStart: [ "
", @@ -2235,7 +2858,22 @@ class BladesRoll extends DocumentSheet { oddsHTMLStop: "
" }; } - calculateOddsHTML_Resistance(diceTotal) { + /*~ @@DOUBLE-BLANK@@ ~*/ + /** + * Calculate odds starting & ending HTML based on given dice total. + * @param {number} diceTotal Total number of dice. + * @returns {{oddsHTMLStart: string, oddsHTMLStop: string}} Opening & Closing HTML for odds bar display + */ + calculateOddsHTML_Resistance(diceTotal) { + // Const oddsColors = [ + // "var(--blades-gold)", // -1 + // "var(--blades-white)", // 0 + // "var(--blades-red-bright)", // 1 + // "var(--blades-red-dark)", // 2 + // "var(--blades-red-bright)", // 3 + // "var(--blades-red-dark)", // 4 + // "var(--blades-red-bright)" // 5 + // ].reverse(); const oddsColors = [ "var(--blades-gold)", "var(--blades-white)", @@ -2243,7 +2881,7 @@ class BladesRoll extends DocumentSheet { "var(--blades-red)", "var(--blades-red)", "var(--blades-red)", - "var(--blades-red)" + "var(--blades-red)" // 5 ].reverse(); const oddsFilters = [ "none", @@ -2255,7 +2893,9 @@ class BladesRoll extends DocumentSheet { "none" ].reverse(); const odds = [...C.DiceOddsResistance[diceTotal]].reverse(); + /*~ @@DOUBLE-BLANK@@ ~*/ const resultElements = []; + /*~ @@DOUBLE-BLANK@@ ~*/ for (let index = 0; index < odds.length; index++) { const chance = odds[index]; if (chance > 0) { @@ -2266,6 +2906,7 @@ class BladesRoll extends DocumentSheet { ]); } } + /*~ @@DOUBLE-BLANK@@ ~*/ return { oddsHTMLStart: [ "
", @@ -2274,22 +2915,34 @@ class BladesRoll extends DocumentSheet { oddsHTMLStop: "
" }; } - calculatePositionEffectTradeData() { + /*~ @@DOUBLE-BLANK@@ ~*/ + /** + * Calculate data for position and effect trade. + * @returns {{canTradePosition: boolean, canTradeEffect: boolean}} + */ + calculatePositionEffectTradeData() { const canTradePosition = this.posEffectTrade === "position" || (this.posEffectTrade === false && this.finalPosition !== Position.desperate && this.finalEffect !== Effect.extreme); const canTradeEffect = this.posEffectTrade === "effect" || (this.posEffectTrade === false && this.finalPosition !== Position.controlled && this.finalEffect !== Effect.zero); + /*~ @@DOUBLE-BLANK@@ ~*/ return { canTradePosition, canTradeEffect }; } - calculateHasInactiveConditionalsData() { + /*~ @@DOUBLE-BLANK@@ ~*/ + /** + * Calculate data on whether there are any inactive conditionals. + * @returns {Record} - Data on inactive conditionals. + */ + calculateHasInactiveConditionalsData() { const hasInactive = {}; for (const section of Object.values(RollModSection)) { hasInactive[section] = this.getRollMods(section).filter((mod) => mod.isInInactiveBlock).length > 0; } return hasInactive; } + /*~ @@DOUBLE-BLANK@@ ~*/ getCostsHTML(rollCosts) { switch (this.rollType) { case RollType.Action: { @@ -2307,7 +2960,14 @@ class BladesRoll extends DocumentSheet { default: return false; } } - parseActionRollCostsHTML(stressCosts, specArmorCost) { + /*~ @@DOUBLE-BLANK@@ ~*/ + /** + * Calculate data for costs of Action Roll. + * @param {BladesRoll.CostData[]} stressCosts Array of stress costs. + * @param {BladesRoll.CostData} [specArmorCost] Specific armor cost. + * @returns {{footerLabel: string, tooltip: string} | undefined} - Costs data or undefined. + */ + parseActionRollCostsHTML(stressCosts, specArmorCost) { if (specArmorCost || stressCosts.length > 0) { const totalStressCost = this.getTotalStressCost(stressCosts); return { @@ -2328,6 +2988,7 @@ class BladesRoll extends DocumentSheet { } return undefined; } + /*~ @@DOUBLE-BLANK@@ ~*/ parseResistanceRollCostsHTML(stressCosts, specArmorCost) { const footerLabelStrings = [ "( Resisting Costs" @@ -2348,14 +3009,21 @@ class BladesRoll extends DocumentSheet { ].filter((line) => Boolean(line)).join("") }; } + /*~ @@DOUBLE-BLANK@@ ~*/ parseFortuneRollCostsHTML() { return { footerLabel: "Fortune Cost Label", tooltip: "Fortune Cost Tooltip" }; } + /*~ @@DOUBLE-BLANK@@ ~*/ parseIndulgeViceRollCostsHTML() { return { footerLabel: "Indulge Vice Cost Label", tooltip: "Indulge Vice Cost Tooltip" }; } - + // #endregion + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region *** EVALUATING & DISPLAYING ROLL TO CHAT *** ~ + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region DICE ~ _dieVals; + /*~ @@DOUBLE-BLANK@@ ~*/ get dieVals() { this._dieVals ??= this.roll.terms[0].results .map((result) => result.result) @@ -2363,6 +3031,7 @@ class BladesRoll extends DocumentSheet { .reverse(); return this._dieVals; } + /*~ @@DOUBLE-BLANK@@ ~*/ getDieClass(val, i) { eLog.checkLog3("rollCollab", `getDieClass(${val}, ${i})`, { inst: this }); if (val === 6 && i <= 1 && this.rollResult === RollResult.critical) { @@ -2379,10 +3048,12 @@ class BladesRoll extends DocumentSheet { "blades-die-critical" ][val]; } + /*~ @@DOUBLE-BLANK@@ ~*/ get dieValsHTML() { eLog.checkLog3("rollCollab", "[get dieValsHTML()]", { roll: this, dieVals: this.dieVals }); const dieVals = [...this.dieVals]; const ghostNum = this.isRollingZero ? dieVals.shift() : null; + /*~ @@DOUBLE-BLANK@@ ~*/ if (this.rollType === RollType.Resistance) { return [ ...dieVals.map((val, i) => ``), @@ -2400,76 +3071,50 @@ class BladesRoll extends DocumentSheet { .join(""); } } + // #endregion + /*~ @@DOUBLE-BLANK@@ ~*/ get rollResult() { + /*~ @@DOUBLE-BLANK@@ ~*/ if ([RollPhase.Collaboration, RollPhase.AwaitingRoll].includes(this.rollPhase)) { return false; } + /*~ @@DOUBLE-BLANK@@ ~*/ + // If rollingZero, remove highest die. const dieVals = this.isRollingZero ? [[...this.dieVals].pop()] : this.dieVals; + /*~ @@DOUBLE-BLANK@@ ~*/ + // Is this a critical success? if (dieVals.filter((val) => val === 6).length >= 2) { return RollResult.critical; } + /*~ @@DOUBLE-BLANK@@ ~*/ + // A full success? if (dieVals.find((val) => val === 6)) { return RollResult.success; } + /*~ @@DOUBLE-BLANK@@ ~*/ + // A partial? if (dieVals.find((val) => val && val >= 4)) { return RollResult.partial; } + /*~ @@DOUBLE-BLANK@@ ~*/ return RollResult.fail; + /*~ @@DOUBLE-BLANK@@ ~*/ } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollPhase() { return this.getFlagVal("rollPhase") ?? RollPhase.Collaboration; } + /*~ @@DOUBLE-BLANK@@ ~*/ set rollPhase(phase) { this.setFlagVal("rollPhase", phase); } + /*~ @@DOUBLE-BLANK@@ ~*/ async outputRollToChat() { - const speaker = ChatMessage.getSpeaker(); - let renderedHTML = ""; - this.rollPhase = RollPhase.AwaitingChatInput; - switch (this.rollType) { - case RollType.Action: { - renderedHTML = - await renderTemplate("systems/eunos-blades/templates/chat/action-roll.hbs", { - sourceName: this.rollPrimary.rollPrimaryName, - oppName: this.rollOpposition?.rollOppName, - type: U.lCase(this.rollType), - subType: U.lCase(this.rollSubType), - downtimeAction: U.lCase(this.rollDowntimeAction), - position: this.finalPosition, - effect: this.finalEffect, - result: this.rollResult, - trait_label: typeof this.rollTrait === "number" ? `${this.rollTrait} Dice` : U.tCase(this.rollTrait), - dieVals: this.dieValsHTML - }); - break; - } - case RollType.Resistance: { - renderedHTML = await renderTemplate("systems/eunos-blades/templates/chat/resistance-roll.hbs", { - dieVals: this.dieValsHTML, - result: this.rollResult, - trait_label: typeof this.rollTrait === "number" ? `${this.rollTrait} Dice` : U.tCase(this.rollTrait), - stress: this.resistanceStressCost - }); - break; - } - case RollType.Fortune: { - break; - } - case RollType.IndulgeVice: { - break; - } - default: throw new Error(`Unrecognized RollType: ${this.rollType}`); - } - const messageData = { - speaker, - content: renderedHTML, - type: CONST.CHAT_MESSAGE_TYPES.ROLL, - roll: this.roll - }; - CONFIG.ChatMessage.documentClass.create(messageData, {}); + BladesChat.ConstructRollOutput(this); } + /*~ @@DOUBLE-BLANK@@ ~*/ async resolveRoll() { await this.roll.evaluate({ async: true }); if (this.isApplyingConsequences) { @@ -2479,6 +3124,10 @@ class BladesRoll extends DocumentSheet { await this.outputRollToChat(); this.close(); } + // #endregion + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region LISTENER FUNCTIONS ~ + /*~ @@DOUBLE-BLANK@@ ~*/ _toggleRollModClick(event) { event.preventDefault(); const elem$ = $(event.currentTarget); @@ -2487,6 +3136,7 @@ class BladesRoll extends DocumentSheet { if (!rollMod) { throw new Error(`Unable to find roll mod with id '${id}'`); } + /*~ @@DOUBLE-BLANK@@ ~*/ switch (rollMod.status) { case RollModStatus.Hidden: rollMod.userStatus = RollModStatus.ForcedOff; @@ -2508,7 +3158,12 @@ class BladesRoll extends DocumentSheet { default: throw new Error(`Unrecognized RollModStatus: ${rollMod.status}`); } } - _gmControlSet(event) { + /*~ @@DOUBLE-BLANK@@ ~*/ + /** + * Handles setting of rollMod status via GM pop-out controls + * @param {ClickEvent} event JQuery click event sent to listener. + */ + _gmControlSet(event) { event.preventDefault(); if (!game.user.isGM) { return; @@ -2520,11 +3175,17 @@ class BladesRoll extends DocumentSheet { return; } const rollMod = this.getRollModByID(id); + /*~ @@DOUBLE-BLANK@@ ~*/ if (rollMod) { rollMod.userStatus = status === "Reset" ? undefined : status; } } - async _gmControlSetTargetToValue(event) { + /*~ @@DOUBLE-BLANK@@ ~*/ + /** + * Handles setting values via GM number line (e.g. roll factor boosts/modifications). + * @param {ClickEvent} event JQuery click event sent to listener. + */ + async _gmControlSetTargetToValue(event) { event.preventDefault(); if (!game.user.isGM) { return; @@ -2532,18 +3193,55 @@ class BladesRoll extends DocumentSheet { const elem$ = $(event.currentTarget); const target = elem$.data("target").replace(/flags\.eunos-blades\./, ""); const value = elem$.data("value"); + /*~ @@DOUBLE-BLANK@@ ~*/ await this.document.setFlag(C.SYSTEM_ID, target, value).then(() => socketlib.system.executeForEveryone("renderRollCollab", this.rollID)); } - async _gmControlResetTarget(event) { + /*~ @@DOUBLE-BLANK@@ ~*/ + async _gmControlCycleTarget(event) { + event.preventDefault(); + if (!game.user.isGM) { + return; + } + const elem$ = $(event.currentTarget); + const flagTarget = elem$.data("flagTarget"); + const curVal = elem$.data("curVal"); + const cycleVals = elem$.data("vals")?.split(/\|/); + if (!cycleVals) { + throw new Error(`Unable to parse cycle values from data-vals = ${elem$.data("vals")}`); + } + const curValIndex = cycleVals.indexOf(curVal); + if (curValIndex === -1) { + throw new Error(`Unable to find current value '${curVal}' in cycle values '${elem$.data("vals")}'`); + } + let newValIndex = curValIndex + 1; + if (newValIndex >= cycleVals.length) { + newValIndex = 0; + } + const newVal = cycleVals[newValIndex]; + eLog.checkLog3("gmControlCycleTarget", "gmControlCycleTarget", { flagTarget, curVal, cycleVals, curValIndex, newValIndex, newVal }); + await this.setFlagVal(flagTarget, newVal); + } + /*~ @@DOUBLE-BLANK@@ ~*/ + /** + * Handles resetting value associated with GM number line on a right-click. + * @param {ClickEvent} event JQuery context menu event sent to listener. + */ + async _gmControlResetTarget(event) { event.preventDefault(); if (!game.user.isGM) { return; } const elem$ = $(event.currentTarget); const target = elem$.data("target").replace(/flags\.eunos-blades\./, ""); + /*~ @@DOUBLE-BLANK@@ ~*/ await this.document.unsetFlag(C.SYSTEM_ID, target).then(() => socketlib.system.executeForEveryone("renderRollCollab", this.rollID)); } - _gmControlSetPosition(event) { + /*~ @@DOUBLE-BLANK@@ ~*/ + /** + * Handles setting of baseline rollPosition via GM button line + * @param {ClickEvent} event JQuery click event sent to listener. + */ + _gmControlSetPosition(event) { event.preventDefault(); if (!game.user.isGM) { return; @@ -2552,7 +3250,12 @@ class BladesRoll extends DocumentSheet { const position = elem$.data("status"); this.initialPosition = position; } - _gmControlSetEffect(event) { + /*~ @@DOUBLE-BLANK@@ ~*/ + /** + * Handles setting of baseline rollPosition via GM button line + * @param {ClickEvent} event JQuery click event sent to listener. + */ + _gmControlSetEffect(event) { event.preventDefault(); if (!game.user.isGM) { return; @@ -2561,7 +3264,12 @@ class BladesRoll extends DocumentSheet { const effect = elem$.data("status"); this.initialEffect = effect; } - async _gmControlToggleFactor(event) { + /*~ @@DOUBLE-BLANK@@ ~*/ + /** + * Handles setting of Factor toggles: isActive, isPrimary, highFavorsPC, isDominant + * @param {ClickEvent} event JQuery click event sent to listener. + */ + async _gmControlToggleFactor(event) { event.preventDefault(); if (!game.user.isGM) { return; @@ -2569,20 +3277,31 @@ class BladesRoll extends DocumentSheet { const elem$ = $(event.currentTarget); const target = elem$.data("target"); const value = !elem$.data("value"); + /*~ @@DOUBLE-BLANK@@ ~*/ eLog.checkLog3("toggleFactor", "_gmControlToggleFactor", { event, target, value }); + /*~ @@DOUBLE-BLANK@@ ~*/ const factorToggleData = this.document.getFlag(C.SYSTEM_ID, "rollCollab.rollFactorToggles"); + /*~ @@DOUBLE-BLANK@@ ~*/ const [thisSource, thisFactor, thisToggle] = target.split(/\./).slice(-3); + /*~ @@DOUBLE-BLANK@@ ~*/ + // If thisToggle is unrecognized, just toggle whatever value target points at if (!["isActive", "isPrimary", "isDominant", "highFavorsPC"].includes(thisToggle)) { await this.document.setFlag(C.SYSTEM_ID, `rollCollab.${target}`, value) .then(() => socketlib.system.executeForEveryone("renderRollCollab", this.rollID)); } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Otherwise, first toggle targeted factor to new value factorToggleData[thisSource][thisFactor] = { ...factorToggleData[thisSource][thisFactor] ?? { display: "" }, [thisToggle]: value }; + /*~ @@DOUBLE-BLANK@@ ~*/ + // Then perform specific logic depending on toggle targeted: switch (thisToggle) { case "isDominant": case "isPrimary": { + // Only one factor per sourceType can be declared Primary or Dominant: + // If one is being activated, must toggle off the others. if (value === true) { Object.values(Factor) .filter((factor) => factor !== thisFactor) @@ -2598,6 +3317,7 @@ class BladesRoll extends DocumentSheet { break; } case "isActive": { + // 'isActive' should be synchronized when 1) value is true, and 2) the other value is false if (value === true) { const otherSource = thisSource === "source" ? "opposition" : "source"; factorToggleData[otherSource][thisFactor] = { @@ -2609,9 +3329,11 @@ class BladesRoll extends DocumentSheet { } default: break; } + /*~ @@DOUBLE-BLANK@@ ~*/ await this.document.setFlag(C.SYSTEM_ID, "rollCollab.rollFactorToggles", factorToggleData) .then(() => socketlib.system.executeForEveryone("renderRollCollab", this.rollID)); } + /*~ @@DOUBLE-BLANK@@ ~*/ async _onSelectChange(event) { event.preventDefault(); const elem = event.currentTarget; @@ -2625,11 +3347,27 @@ class BladesRoll extends DocumentSheet { socketlib.system.executeForEveryone("renderRollCollab", this.rollID); } } + /*~ @@DOUBLE-BLANK@@ ~*/ async _onTextInputBlur(event) { await U.EventHandlers.onTextInputBlur(this, event); socketlib.system.executeForEveryone("renderRollCollab", this.rollID); } - + /*~ @@DOUBLE-BLANK@@ ~*/ + // Async _gmControlSelect(event: SelectChangeEvent) { + // event.preventDefault(); + // const elem$ = $(event.currentTarget); + // const section = elem$.data("rollSection"); + // const subSection = elem$.data("rollSubSection"); + // const selectedOption = elem$.val(); + /*~ @@DOUBLE-BLANK@@ ~*/ + // if (typeof selectedOption !== "string") { return; } + // if (selectedOption === "false") { + // await this.document.unsetFlag(C.SYSTEM_ID, `rollCollab.rollParticipantData.${section}.${subSection}`); + // } + // await this.addRollParticipant(selectedOption, section, subSection); + /*~ @@DOUBLE-BLANK@@ ~*/ + // } + /*~ @@DOUBLE-BLANK@@ ~*/ get resistanceStressCost() { const dieVals = this.dieVals; if (this.rollResult === RollResult.critical) { @@ -2640,14 +3378,19 @@ class BladesRoll extends DocumentSheet { } return 6 - (dieVals.shift() ?? 0); } - + /*~ @@DOUBLE-BLANK@@ ~*/ + // #endregion + // #region ACTIVATE LISTENERS ~ activateListeners(html) { super.activateListeners(html); ApplyTooltipListeners(html); ApplyConsequenceListeners(html); + /*~ @@DOUBLE-BLANK@@ ~*/ + // User-Toggleable Roll Mods html.find(".roll-mod[data-action='toggle']").on({ click: this._toggleRollModClick.bind(this) }); + /*~ @@DOUBLE-BLANK@@ ~*/ html.find("[data-action='tradePosition']").on({ click: (event) => { const curVal = `${$(event.currentTarget).data("value")}`; @@ -2670,19 +3413,27 @@ class BladesRoll extends DocumentSheet { } } }); + /*~ @@DOUBLE-BLANK@@ ~*/ html.find("[data-action='roll']").on({ click: () => this.resolveRoll() }); + /*~ @@DOUBLE-BLANK@@ ~*/ html .find("select[data-action='player-select']") .on({ change: this._onSelectChange.bind(this) }); + /*~ @@DOUBLE-BLANK@@ ~*/ if (!game.user.isGM) { return; } + /*~ @@DOUBLE-BLANK@@ ~*/ + // GM Controls html.on({ - focusin: () => { BladesRoll.Active = this; } + focusin: () => { BladesRoll.Active = this; } // Set reference to top-most, focused roll. }); - html.find(".controls-toggle").on({ + /** + * Handles setting of rollMod status via GM pop-out controls + */ + html.find(".controls-toggle").on({ click: (event) => { event.preventDefault(); $(event.currentTarget).parents(".controls-panel").toggleClass("active"); @@ -2691,33 +3442,60 @@ class BladesRoll extends DocumentSheet { html.find("[data-action=\"gm-set\"]").on({ click: this._gmControlSet.bind(this) }); - html.find("[data-action=\"gm-set-position\"]").on({ + /** + * Handles setting of baseline rollPosition via GM button line + */ + html.find("[data-action=\"gm-set-position\"]").on({ click: this._gmControlSetPosition.bind(this) }); - html.find("[data-action=\"gm-set-effect\"]").on({ + /** + * Handles setting of baseline rollEffect via GM button line + */ + html.find("[data-action=\"gm-set-effect\"]").on({ click: this._gmControlSetEffect.bind(this) }); - html.find("[data-action=\"gm-set-target\"]").on({ + /** + * Handles setting values via GM number line (e.g. roll factor boosts/modifications). + * Handles resetting value associated with GM number line on a right-click. + */ + html.find("[data-action=\"gm-set-target\"]").on({ click: this._gmControlSetTargetToValue.bind(this), contextmenu: this._gmControlResetTarget.bind(this) }); - html.find("[data-action=\"gm-toggle-factor\"]").on({ + /** + * Handles setting values via GM number line (e.g. roll factor boosts/modifications). + * Handles resetting value associated with GM number line on a right-click. + */ + html.find("[data-action=\"gm-cycle-target\"]").on({ + click: this._gmControlCycleTarget.bind(this) + }); + /** + * Handles setting of Factor toggles: isActive, isPrimary, highFavorsPC, isDominant + */ + html.find("[data-action=\"gm-toggle-factor\"]").on({ click: this._gmControlToggleFactor.bind(this) }); + /*~ @@DOUBLE-BLANK@@ ~*/ html .find("select[data-action='gm-select']") .on({ change: this._onSelectChange.bind(this) }); + /*~ @@DOUBLE-BLANK@@ ~*/ html .find("[data-action=\"gm-edit-consequences\"]") .on({ click: () => BladesDialog.DisplayRollConsequenceDialog(this) }); + /*~ @@DOUBLE-BLANK@@ ~*/ html .find("[data-action='gm-text-input']") .on({ blur: this._onTextInputBlur.bind(this) }); + /*~ @@DOUBLE-BLANK@@ ~*/ } - + // #endregion + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region OVERRIDES: _canDragDrop, _onDrop, _onSubmit, close, render ~ _canDragDrop() { return game.user.isGM; } + /*~ @@DOUBLE-BLANK@@ ~*/ _onDrop(event) { const { type, uuid } = TextEditor.getDragEventData(event); const [id] = (new RegExp(`${type}\\.(.+)`).exec(uuid) ?? []).slice(1); @@ -2726,12 +3504,15 @@ class BladesRoll extends DocumentSheet { this.rollOpposition = new BladesRollOpposition(this, { rollOppDoc: oppDoc }); } } + /*~ @@DOUBLE-BLANK@@ ~*/ async _onSubmit(event, { updateData } = {}) { const returnVal = await super._onSubmit(event, { updateData, preventClose: true }); await socketlib.system.executeForEveryone("renderRollCollab", this.rollID); return returnVal; } + /*~ @@DOUBLE-BLANK@@ ~*/ async close(options = {}) { + /*~ @@DOUBLE-BLANK@@ ~*/ if (options.rollID) { return super.close({}); } @@ -2739,6 +3520,7 @@ class BladesRoll extends DocumentSheet { socketlib.system.executeForEveryone("closeRollCollab", this.rollID); return undefined; } + /*~ @@DOUBLE-BLANK@@ ~*/ render(force = false, options) { if (!this.document.getFlag(C.SYSTEM_ID, "rollCollab")) { return this; @@ -2746,6 +3528,9 @@ class BladesRoll extends DocumentSheet { return super.render(force, options); } } - +/*~ @@DOUBLE-BLANK@@ ~*/ +// #region EXPORTS ~ export { BladesRollMod, BladesRollPrimary, BladesRollOpposition, BladesRollParticipant }; -export default BladesRoll; \ No newline at end of file +export default BladesRoll; +// #endregion +/*~ @@DOUBLE-BLANK@@ ~*/ diff --git a/module/blades.js b/module/blades.js index 784fd8d2..6ae9ad02 100644 --- a/module/blades.js +++ b/module/blades.js @@ -1,45 +1,56 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - +// #region ▮▮▮▮▮▮▮ IMPORTS ▮▮▮▮▮▮▮ ~ import C, { ActionTrait, AttributeTrait, RollType, ConsequenceType, Position, RollResult } from "./core/constants.js"; import registerSettings, { initTinyMCEStyles, initCanvasStyles } from "./core/settings.js"; import { registerHandlebarHelpers, preloadHandlebarsTemplates } from "./core/helpers.js"; import BladesPushAlert from "./BladesPushAlert.js"; +import BladesChat from "./BladesChat.js"; import U from "./core/utilities.js"; import logger from "./core/logger.js"; import G, { Initialize as GsapInitialize } from "./core/gsap.js"; +/*~ @@DOUBLE-BLANK@@ ~*/ import BladesActorProxy, { BladesActor, BladesPC, BladesCrew, BladesNPC, BladesFaction } from "./documents/BladesActorProxy.js"; import BladesItemProxy, { BladesItem, BladesClockKeeper, BladesGMTracker, BladesLocation, BladesScore } from "./documents/BladesItemProxy.js"; +/*~ @@DOUBLE-BLANK@@ ~*/ import BladesItemSheet from "./sheets/item/BladesItemSheet.js"; import BladesPCSheet from "./sheets/actor/BladesPCSheet.js"; import BladesCrewSheet from "./sheets/actor/BladesCrewSheet.js"; import BladesNPCSheet from "./sheets/actor/BladesNPCSheet.js"; import BladesFactionSheet from "./sheets/actor/BladesFactionSheet.js"; import BladesRoll, { BladesRollMod, BladesRollPrimary, BladesRollOpposition, BladesRollParticipant } from "./BladesRoll.js"; +/*~ @@DOUBLE-BLANK@@ ~*/ import BladesDialog from "./BladesDialog.js"; import BladesAI, { AGENTS } from "./core/ai.js"; import BladesActiveEffect from "./BladesActiveEffect.js"; import BladesGMTrackerSheet from "./sheets/item/BladesGMTrackerSheet.js"; import BladesClockKeeperSheet from "./sheets/item/BladesClockKeeperSheet.js"; import { updateClaims, updateContacts, updateOps, updateFactions, updateDescriptions, updateRollMods } from "./data-import/data-import.js"; +/*~ @@DOUBLE-BLANK@@ ~*/ CONFIG.debug.logging = false; -CONFIG.debug.logging = true; +/* DEVCODE*/ CONFIG.debug.logging = true; Object.assign(globalThis, { eLog: logger }); -Handlebars.registerHelper("eLog", logger.hbsLog); -let socket; +Handlebars.registerHelper("eLog", logger.hbsLog); /* !DEVCODE*/ +/*~ @@DOUBLE-BLANK@@ ~*/ +let socket; // ~ SocketLib interface +/*~ @@DOUBLE-BLANK@@ ~*/ +// #endregion ▮▮▮▮[IMPORTS]▮▮▮▮ +/*~ @@DOUBLE-BLANK@@ ~*/ class GlobalGetter { get roll() { return BladesRoll.Active; } + /*~ @@DOUBLE-BLANK@@ ~*/ get user() { return this.roll?.document; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollFlags() { return this.roll?.flagData; } + /*~ @@DOUBLE-BLANK@@ ~*/ get userFlags() { return this.user?.flags?.["eunos-blades"]?.rollCollab; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollPrimary() { return this.roll?.rollPrimary; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollPrimaryDoc() { return this.roll?.rollPrimaryDoc; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollOpposition() { return this.roll?.rollOpposition; } + /*~ @@DOUBLE-BLANK@@ ~*/ get sheetData() { return this.roll?.getData(); } + /*~ @@DOUBLE-BLANK@@ ~*/ newActionRoll() { const pc = game.actors.getName("Alistair"); if (!pc) { @@ -64,6 +75,7 @@ class GlobalGetter { 0: { type: ConsequenceType.ProwessHarm2, attribute: AttributeTrait.prowess, + attributeVal: 3, name: "Broken Leg", resistOptions: { 0: { @@ -98,47 +110,179 @@ class GlobalGetter { isSelected: true, type: ConsequenceType.None, icon: C.ConsequenceIcons[ConsequenceType.None], + footerMsg: "If vs. Physical Harm", typeDisplay: "" }, icon: C.ConsequenceIcons[ConsequenceType.ProwessHarm2], typeDisplay: C.ConsequenceDisplay[ConsequenceType.ProwessHarm2] + }, + 1: { + type: ConsequenceType.ReducedEffect, + attribute: AttributeTrait.insight, + attributeVal: 4, + name: "You Lose Your Footing", + /* "resistOptions": { + "0": { + "name": "Stumble", + "isSelected": false + }, + "1": { + "name": "Trip", + "isSelected": false + }, + "2": { + "name": "", + "type": "None", + "isSelected": true + } + }, */ + resistedTo: { + name: "", + type: ConsequenceType.None, + isSelected: true + }, + icon: "main", + typeDisplay: "Reduced Effect" + }, + 2: { + type: ConsequenceType.ResolveHarm1, + attribute: AttributeTrait.resolve, + name: "Traumatic Flashbacks", + resistOptions: { + 0: { + name: "", + type: ConsequenceType.None, + isSelected: true + }, + 1: { + name: "", + type: ConsequenceType.None, + isSelected: false + }, + 2: { + name: "", + type: ConsequenceType.None, + isSelected: false + } + }, + resistedTo: { + name: "", + type: ConsequenceType.None, + isSelected: true + }, + icon: "spikes|eyeball|iris", + typeDisplay: "Level 1 Harm (Lesser)", + attributeVal: 4 } }, [RollResult.fail]: { 0: { - type: ConsequenceType.ProwessHarm2, + type: ConsequenceType.WorsePosition, + attribute: AttributeTrait.resolve, + attributeVal: 4, + name: "Time To Regroup", + resistOptions: { + 0: { + name: "Time to Rest and Recuperate", + isSelected: false + }, + 1: { + name: "Time to Reflect and Reevaluate", + isSelected: false + }, + 2: { + name: "Time to Reorganize and Strategize", + isSelected: false + } + }, + resistedTo: { + name: "", + type: ConsequenceType.None, + isSelected: true + }, + icon: "horizon|boot|ice", + typeDisplay: "Worse Position" + }, + 1: { + type: ConsequenceType.ComplicationMajor, attribute: AttributeTrait.prowess, - name: "Broken Leg", + name: "Your pick snaps off inside the lock.", resistOptions: { 0: { - name: "Sprained Ankle", - isSelected: true, - type: ConsequenceType.ProwessHarm1, - icon: C.ConsequenceIcons[ConsequenceType.ProwessHarm1], - typeDisplay: C.ConsequenceDisplay[ConsequenceType.ProwessHarm1] + name: "Lock remains intact but jammed", + isSelected: false, + type: ConsequenceType.ComplicationMinor, + icon: "main" }, 1: { - name: "Bruised Leg", + name: "Pick breaks, but lock is still pickable", + isSelected: true, + type: ConsequenceType.ComplicationMinor, + icon: "main", + typeDisplay: "Minor Complication" + }, + 2: { + name: "You manage to extract the broken pick from the lock.", isSelected: false, - type: ConsequenceType.ProwessHarm1, - icon: C.ConsequenceIcons[ConsequenceType.ProwessHarm1] + type: ConsequenceType.ComplicationMinor, + icon: "main" + } + }, + resistedTo: { + name: "Pick breaks, but lock is still pickable", + isSelected: true, + type: ConsequenceType.ComplicationMinor, + icon: "main", + typeDisplay: "Minor Complication" + }, + attributeVal: 3, + icon: "main", + typeDisplay: "Major Complication" + }, + 2: { + type: ConsequenceType.InsightHarm2, + attribute: AttributeTrait.insight, + name: "Completely Misled", + resistOptions: { + 0: { + name: "Partially Misinformed", + isSelected: false, + type: ConsequenceType.InsightHarm1, + icon: "eye|iris", + typeDisplay: "Level 1 Harm (Lesser)" + }, + 1: { + name: "Confused by Deception", + isSelected: true, + type: ConsequenceType.InsightHarm1, + icon: "eye|iris", + typeDisplay: "Level 1 Harm (Lesser)" }, 2: { - name: "Fractured Foot", + name: "Given Partially Incorrect Information", isSelected: false, - type: ConsequenceType.ProwessHarm1, - icon: C.ConsequenceIcons[ConsequenceType.ProwessHarm1] + type: ConsequenceType.InsightHarm1, + icon: "eye|iris" } }, resistedTo: { + name: "Confused by Deception", + isSelected: true, + type: ConsequenceType.InsightHarm1, + typeDisplay: "Level 1 Harm (Lesser)", + icon: "eye|iris" + }, + specialArmorTo: { name: "Sprained Ankle", isSelected: true, - type: ConsequenceType.ProwessHarm1, - typeDisplay: C.ConsequenceDisplay[ConsequenceType.ProwessHarm1], - icon: C.ConsequenceIcons[ConsequenceType.ProwessHarm1] + type: ConsequenceType.InsightHarm1, + footerMsg: "If vs. Supernatural, Arcane Forces", + icon: C.ConsequenceIcons[ConsequenceType.InsightHarm1], + typeDisplay: "Level 1 Harm (Lesser)" }, - icon: C.ConsequenceIcons[ConsequenceType.ProwessHarm2], - typeDisplay: C.ConsequenceDisplay[ConsequenceType.ProwessHarm2] + icon: "eye|iris", + typeDisplay: "Level 2 Harm (Moderate)", + attributeVal: 4 } } } @@ -146,6 +290,7 @@ class GlobalGetter { }; BladesRoll.NewRoll(conf); } + /*~ @@DOUBLE-BLANK@@ ~*/ newResistanceRoll() { const pc = game.actors.getName("Alistair"); if (!pc) { @@ -183,8 +328,9 @@ class GlobalGetter { BladesRoll.NewRoll(conf); } } - -Object.assign(globalThis, { +/*~ @@DOUBLE-BLANK@@ ~*/ +// #region Globals: Exposing Functionality to Global Scope ~ +/* DEVCODE*/ Object.assign(globalThis, { get: new GlobalGetter(), updateClaims, updateContacts, @@ -208,6 +354,7 @@ Object.assign(globalThis, { BladesRollPrimary, BladesRollOpposition, BladesRollParticipant, + BladesChat, G, U, C, @@ -220,20 +367,32 @@ Object.assign(globalThis, { BladesGMTrackerSheet, BladesAI, AGENTS -}); - +}); /* !DEVCODE*/ +// #endregion Globals +/*~ @@DOUBLE-BLANK@@ ~*/ +// #region ████████ SYSTEM INITIALIZATION: Initializing Blades In The Dark System on 'Init' Hook ████████ Hooks.once("init", async () => { + // Register System Settings registerSettings(); eLog.display("Initializing Blades In the Dark System"); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Initialize Fonts & Gsap Animations GsapInitialize(); + /*~ @@DOUBLE-BLANK@@ ~*/ CONFIG.Item.documentClass = BladesItemProxy; CONFIG.Actor.documentClass = BladesActorProxy; + CONFIG.ChatMessage.documentClass = BladesChat; + /*~ @@DOUBLE-BLANK@@ ~*/ + // Register sheet application classes Actors.unregisterSheet("core", ActorSheet); Actors.registerSheet("blades", BladesCrewSheet, { types: ["crew"], makeDefault: true }); Actors.registerSheet("blades", BladesFactionSheet, { types: ["faction"], makeDefault: true }); Actors.registerSheet("blades", BladesNPCSheet, { types: ["npc"], makeDefault: true }); + /*~ @@DOUBLE-BLANK@@ ~*/ Items.unregisterSheet("core", ItemSheet); Items.registerSheet("blades", BladesItemSheet, { types: C.ItemTypes, makeDefault: true }); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Initialize subclasses await Promise.all([ BladesPCSheet.Initialize(), BladesActiveEffect.Initialize(), @@ -245,20 +404,36 @@ Hooks.once("init", async () => { BladesRoll.Initialize(), preloadHandlebarsTemplates() ]); + /*~ @@DOUBLE-BLANK@@ ~*/ registerHandlebarHelpers(); }); +/*~ @@DOUBLE-BLANK@@ ~*/ Hooks.once("ready", () => { initCanvasStyles(); initTinyMCEStyles(); }); - +// #endregion ▄▄▄▄▄ SYSTEM INITIALIZATION ▄▄▄▄▄ +/*~ @@DOUBLE-BLANK@@ ~*/ +// #region ░░░░░░░[SocketLib]░░░░ SocketLib Initialization ░░░░░░░ ~ Hooks.once("socketlib.ready", () => { socket = socketlib.registerSystem("eunos-blades"); - Object.assign(globalThis, { socket, socketlib }); + /* DEVCODE*/ Object.assign(globalThis, { socket, socketlib }); /* !DEVCODE*/ + /*~ @@DOUBLE-BLANK@@ ~*/ BladesRoll.InitSockets(); + /*~ @@DOUBLE-BLANK@@ ~*/ let clockOverlayUp; let pushControllerUp; - function InitOverlaySockets() { + /*~ @@DOUBLE-BLANK@@ ~*/ + /** + * Initializes the overlay sockets for the BladesClockKeeperSheet and BladesPushAlert. + * It checks every 2 seconds if the overlays are up and running. + * If both overlays are up, it stops checking. + * + * @function + * @name InitOverlaySockets + * @returns {void} + */ + function InitOverlaySockets() { setTimeout(() => { clockOverlayUp = clockOverlayUp || BladesClockKeeperSheet.InitSockets(); pushControllerUp = pushControllerUp || BladesPushAlert.InitSockets(); @@ -280,4 +455,6 @@ Hooks.once("diceSoNiceReady", (dice3d) => { emissiveMaps: [undefined, undefined, undefined, undefined, undefined, "systems/eunos-blades/assets/dice/emission-maps/6.webp"], emissive: "#d89300" }); -}); \ No newline at end of file +}); +// #endregion ░░░░[Dice So Nice]░░░░ +/*~ @@DOUBLE-BLANK@@ ~*/ diff --git a/module/core/ai.js b/module/core/ai.js index 7b1dc6f2..5b812231 100644 --- a/module/core/ai.js +++ b/module/core/ai.js @@ -1,13 +1,11 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - +// Import axios from "axios.js"; import C from "./constants.js"; import U from "./utilities.js"; +/** + * AI class for querying OpenAI API + */ class BladesAI { + /*~ @@DOUBLE-BLANK@@ ~*/ static async GetModels() { const apiKey = U.getSetting("openAPIKey"); if (!apiKey) { @@ -19,21 +17,41 @@ class BladesAI { Authorization: `Bearer ${apiKey}` } }; + /*~ @@DOUBLE-BLANK@@ ~*/ + // Send a POST request to the OpenAI API const response = await fetch("https://api.openai.com/v1/models", fetchRequest); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Check if the response status is not 200 (OK) if (!response.ok) { + // Throw an error with the status code throw new Error(`OpenAI API request failed with status ${response.status}`); } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Parse the response body as JSON const data = await response.json(); + /*~ @@DOUBLE-BLANK@@ ~*/ eLog.checkLog3("BladesAI", "Available Models", { response: data }); } + /*~ @@DOUBLE-BLANK@@ ~*/ apiKey; + /*~ @@DOUBLE-BLANK@@ ~*/ model; + /*~ @@DOUBLE-BLANK@@ ~*/ temperature = 0.5; + /*~ @@DOUBLE-BLANK@@ ~*/ frequency_penalty = 0.8; + /*~ @@DOUBLE-BLANK@@ ~*/ presence_penalty = 0.8; + /*~ @@DOUBLE-BLANK@@ ~*/ systemMessage; + /*~ @@DOUBLE-BLANK@@ ~*/ examplePrompts; - constructor(config) { + /*~ @@DOUBLE-BLANK@@ ~*/ + /** + * AI class constructor + * @param {BladesAI.Config} [config] Configuration settings for the API + */ + constructor(config) { const apiKey = U.getSetting("openAPIKey"); if (!apiKey) { throw new Error("You must configure your OpenAI API Key in Settings to use AI features."); @@ -50,7 +68,9 @@ class BladesAI { this.frequency_penalty = config.frequency_penalty ?? this.frequency_penalty; this.presence_penalty = config.presence_penalty ?? this.presence_penalty; } + /*~ @@DOUBLE-BLANK@@ ~*/ _initialMessages = []; + /*~ @@DOUBLE-BLANK@@ ~*/ get initialMessages() { if (this._initialMessages.length === 0) { this._initialMessages.push({ @@ -70,15 +90,30 @@ class BladesAI { } return this._initialMessages; } + /*~ @@DOUBLE-BLANK@@ ~*/ prompts = {}; + /*~ @@DOUBLE-BLANK@@ ~*/ responses = {}; + /*~ @@DOUBLE-BLANK@@ ~*/ getResponse(queryID) { return this.responses[queryID] ?? null; } + /*~ @@DOUBLE-BLANK@@ ~*/ hasQueried(queryID) { return this.prompts[queryID] !== undefined; } - async query(queryID, prompt, modelMod, extendedContext = false) { + /*~ @@DOUBLE-BLANK@@ ~*/ + /** + * Query OpenAI API + * @param {string} queryID A label for later retrieval of the query data + * @param {string} prompt The prompt to send to the API + * @param {number} [modelMod] Optional modifier to the base model level. + * If provided, the final model quality will be adjusted by this number. + * @param {boolean} [extendedContext=false] Optional flag to indicate whether to use extended context models. + * If true, extended context models are used; otherwise, base context models are used. + * @returns {Promise} The API response + */ + async query(queryID, prompt, modelMod, extendedContext = false) { if (!prompt) { return; } @@ -89,6 +124,7 @@ class BladesAI { const model = extendedContext ? C.AI_MODELS.extendedContext[modelNum] : C.AI_MODELS.baseContext[modelNum]; + /*~ @@DOUBLE-BLANK@@ ~*/ const fetchRequest = { method: "POST", headers: { @@ -109,19 +145,72 @@ class BladesAI { ] }) }; - + /*~ @@DOUBLE-BLANK@@ ~*/ + // EeLog.checkLog3("BladesAI", "Fetch Request", fetchRequest); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Send a POST request to the OpenAI API const response = await fetch("https://api.openai.com/v1/chat/completions", fetchRequest); + // { + // method: "POST", + // headers: { + // // The content type of the request + // "Content-Type": "application/json", + // // The authorization header with the API key + // Authorization: `Bearer ${this.apiKey}` + // }, + // body: JSON.stringify({ + // model, + // messages: [ + // ...this.initialMessages, + // { + // role: "user", + // content: prompt + // } + // ], + // // Maximum number of tokens in the output. Min: 1, Max: 4096 + // // max_tokens: 60, + // // Controls randomness. Higher values mean the model will take more risks. + // temperature: 0.5, // 0 to 2.0 + // /* The 'top_p' parameter is an alternative to 'temperature' for controlling the randomness of + // the AI's responses. It represents the cumulative probability and its value ranges from 0 to 1. + // A lower value makes the AI's responses more deterministic, while a higher value makes them + // more diverse and unpredictable. */ + // // top_p: 1, // 0 to 1 + // /* The 'frequency_penalty' parameter is used to penalize new tokens based on their frequency in + // the training set. Its value ranges from 0 to 1. A higher value means the AI is less likely to + // use common phrases from its training set, leading to more unique responses. A lower value + // means the AI is more likely to use common phrases, leading to more predictable responses. */ + // frequency_penalty: 0.8, // -2.0 to 2.0 + // /* The 'presence_penalty' parameter is used to penalize tokens (words or phrases) that are out + // of context. Its value ranges from 0 to 1. A higher value means the AI is less likely to + // include out-of-context tokens in its responses, leading to more coherent and contextually + // appropriate responses. A lower value means the AI is more likely to include out-of-context + // tokens, which can lead to more creative but potentially less coherent responses. */ + // presence_penalty: 0.8 // -2.0 to 2.0 + // }) + // } + // ); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Check if the response status is not 200 (OK) if (!response.ok) { console.log("Failed AI Request:", JSON.parse(fetchRequest.body)); + // Throw an error with the status code throw new Error(`OpenAI API request failed with status ${response.status}`); } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Parse the response body as JSON const data = await response.json(); + /*~ @@DOUBLE-BLANK@@ ~*/ fetchRequest.body = JSON.parse(fetchRequest.body); + /*~ @@DOUBLE-BLANK@@ ~*/ eLog.checkLog3("BladesAI", "AI Query", { prompt: fetchRequest, response: data }); + /*~ @@DOUBLE-BLANK@@ ~*/ this.responses[queryID] = data.choices[0].message.content; + /*~ @@DOUBLE-BLANK@@ ~*/ return this.responses[queryID]; } } +/*~ @@DOUBLE-BLANK@@ ~*/ export const AGENTS = { GeneralContentGenerator: { systemMessage: "You will act as a creative content generator for a game of Blades In The Dark set in the city of Duskvol. You will be prompted with some element of the game world (a location, a character, an event, a faction, a dilemma) in the form of a JSON object. Your job is to analyze the JSON object and replace any values that equal \"\" with original content of your own creation. Original content must meet these requirements: (A) it should align with and be consistent with the provided contextual information, as well as your broader understanding of the game's themes. (B) It should be presented in a format that matches (in length and in style) other entries for that particular value, examples of which will also be provided. (C) It should be creative, interesting, and daring: Be bold with your creativity. Specific context for this prompt is as follows:", @@ -142,7 +231,13 @@ export const AGENTS = { human: "Setarra, a Demon. Patient, Defiant, Ruthless, Cold", ai: "[5 KEYWORDS]shadowy|sinister|unfathomable|enigmatic|tempting|[5 PHRASES]whispers that crawl under your skin|always watching, always plotting|in tones of silk and venom|intoxicating presence that draws you closer, despite your instincts urging you to run|eyes like black holes, swallowing all light around them|[3 QUIRKS/MOTIFFS]a disorienting mist clings to her form, obscuring her true shape|casually discusses the devastating acts of capricious revenge she has taken on those who crossed her|never forgets a slight or betrayal, no matter how small or insignificant it may seem at the time|[3 PLOT HOOKS]seeks revenge against Alistair for meddling in her affairs years ago|makes Ollie an offer he can't refuse: unlimited access to forbidden alchemical knowledge in exchange for a single favor, to be called in at some future time|tempts Spencer with forbidden knowledge about demons, promising answers to all their questions if they perform a dangerous ritual" } - ] + /* + "brutish,merciless,terrifying,savage,loyal, + bloody tools,hulking figures,blood-soaked alleys,grimy aprons,grisly displays, + never clean their tools,relishes the terror they inspire,occasional laughter among them, + recruiting a PC to perform a job for them from prison, + the gang blames one of the PCs for Tarvul's imprisonment and they're out for revenge" */ + ] }, ConsequenceAdjuster: { systemMessage: "You will act as a \"Setback Adjuster\" for a game of Blades In The Dark. You will be prompted with a short phrase describing an injury, lasting consequence or other setback. Your job is to respond with a pipe-delimited list of three possible alternative consequences that are less severe by one level, using the following scale as a rough guide: Level 1 = Lesser (e.g. 'Battered', 'Drained', 'Distracted', 'Scared', 'Confused'), Level 2 = Moderate (e.g. 'Exhausted', 'Deep Cut to Arm', 'Concussion', 'Panicked', 'Seduced'), Level 3 = Severe (e.g. 'Impaled', 'Broken Leg', 'Shot In Chest', 'Badly Burned', 'Terrified'), Level 4 = Fatal or Ruinous (e.g. 'Impaled Through Heart', 'Electrocuted', 'Headquarters Burned to the Ground'). So, if you determine that the consequence described in the prompt is severity level 3, you should respond with three narratively similar consequences that are severity level 2. Your three suggestions should be different from each other, but they should all logically follow from the initial harm described: You should not introduce new facts or make assumptions that are not indicated in the initial prompt. The consequences you suggest should always describe a NEGATIVE setback or complication, just one that is less severe than the one described in the prompt.", @@ -155,4 +250,6 @@ export const AGENTS = { ] } }; -export default BladesAI; \ No newline at end of file +/*~ @@DOUBLE-BLANK@@ ~*/ +export default BladesAI; +/*~ @@DOUBLE-BLANK@@ ~*/ diff --git a/module/core/constants.js b/module/core/constants.js index 86e49e69..ac645884 100644 --- a/module/core/constants.js +++ b/module/core/constants.js @@ -1,24 +1,20 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - +/* eslint-disable no-shadow */ export var BladesPermissions; (function (BladesPermissions) { BladesPermissions[BladesPermissions["NONE"] = CONST.DOCUMENT_PERMISSION_LEVELS.NONE] = "NONE"; BladesPermissions[BladesPermissions["BASIC"] = CONST.DOCUMENT_PERMISSION_LEVELS.LIMITED] = "BASIC"; BladesPermissions[BladesPermissions["FULL"] = CONST.DOCUMENT_PERMISSION_LEVELS.OBSERVER] = "FULL"; BladesPermissions[BladesPermissions["OWNER"] = CONST.DOCUMENT_PERMISSION_LEVELS.OWNER] = "OWNER"; -})(BladesPermissions || (BladesPermissions = {})); +})(BladesPermissions || (BladesPermissions = {})); +/*~ @@DOUBLE-BLANK@@ ~*/ export var BladesActorType; (function (BladesActorType) { BladesActorType["pc"] = "pc"; BladesActorType["npc"] = "npc"; BladesActorType["crew"] = "crew"; BladesActorType["faction"] = "faction"; -})(BladesActorType || (BladesActorType = {})); +})(BladesActorType || (BladesActorType = {})); +/*~ @@DOUBLE-BLANK@@ ~*/ export var BladesItemType; (function (BladesItemType) { BladesItemType["ability"] = "ability"; @@ -43,20 +39,23 @@ export var BladesItemType; BladesItemType["design"] = "design"; BladesItemType["location"] = "location"; BladesItemType["score"] = "score"; -})(BladesItemType || (BladesItemType = {})); +})(BladesItemType || (BladesItemType = {})); +/*~ @@DOUBLE-BLANK@@ ~*/ export var PrereqType; (function (PrereqType) { PrereqType["HasActiveItem"] = "HasActiveItem"; PrereqType["HasActiveItemsByTag"] = "HasActiveItemByTag"; PrereqType["AdvancedPlaybook"] = "AdvancedPlaybook"; PrereqType["HasAllTags"] = "HasAllTags"; - PrereqType["HasAnyTag"] = "HasAnyTag"; + PrereqType["HasAnyTag"] = "HasAnyTag"; + /*~ @@DOUBLE-BLANK@@ ~*/ PrereqType["Not_HasActiveItem"] = "Not_HasActiveItem"; PrereqType["Not_HasActiveItemsByTag"] = "Not_HasActiveItemsByTag"; PrereqType["Not_AdvancedPlaybook"] = "Not_AdvancedPlaybook"; PrereqType["Not_HasAllTags"] = "Not_HasAllTags"; PrereqType["Not_HasAnyTag"] = "Not_HasAnyTag"; -})(PrereqType || (PrereqType = {})); +})(PrereqType || (PrereqType = {})); +/*~ @@DOUBLE-BLANK@@ ~*/ export var District; (function (District) { District["Barrowcleft"] = "Barrowcleft"; @@ -101,7 +100,8 @@ export var OtherDistrict; OtherDistrict["Ironhook Prison"] = "Ironhook Prison"; OtherDistrict["Old North Port"] = "Old North Port"; OtherDistrict["Deathlands"] = "Deathlands"; -})(OtherDistrict || (OtherDistrict = {})); +})(OtherDistrict || (OtherDistrict = {})); +/*~ @@DOUBLE-BLANK@@ ~*/ export var AttributeTrait; (function (AttributeTrait) { AttributeTrait["insight"] = "insight"; @@ -143,7 +143,8 @@ export var ActionTrait; ActionTrait["command"] = "command"; ActionTrait["consort"] = "consort"; ActionTrait["sway"] = "sway"; -})(ActionTrait || (ActionTrait = {})); +})(ActionTrait || (ActionTrait = {})); +/*~ @@DOUBLE-BLANK@@ ~*/ export var DowntimeAction; (function (DowntimeAction) { DowntimeAction["AcquireAsset"] = "AcquireAsset"; @@ -152,21 +153,24 @@ export var DowntimeAction; DowntimeAction["Recover"] = "Recover"; DowntimeAction["ReduceHeat"] = "ReduceHeat"; DowntimeAction["Train"] = "Train"; -})(DowntimeAction || (DowntimeAction = {})); +})(DowntimeAction || (DowntimeAction = {})); +/*~ @@DOUBLE-BLANK@@ ~*/ export var RollPermissions; (function (RollPermissions) { RollPermissions["Primary"] = "Primary"; RollPermissions["Observer"] = "Observer"; RollPermissions["GM"] = "GM"; RollPermissions["Participant"] = "Participant"; -})(RollPermissions || (RollPermissions = {})); +})(RollPermissions || (RollPermissions = {})); +/*~ @@DOUBLE-BLANK@@ ~*/ export var RollType; (function (RollType) { RollType["Action"] = "Action"; RollType["Resistance"] = "Resistance"; RollType["Fortune"] = "Fortune"; RollType["IndulgeVice"] = "IndulgeVice"; -})(RollType || (RollType = {})); +})(RollType || (RollType = {})); +/*~ @@DOUBLE-BLANK@@ ~*/ export var RollSubType; (function (RollSubType) { RollSubType["Incarceration"] = "Incarceration"; @@ -174,7 +178,8 @@ export var RollSubType; RollSubType["GatherInfo"] = "GatherInfo"; RollSubType["GroupLead"] = "GroupLead"; RollSubType["GroupParticipant"] = "GroupParticipant"; -})(RollSubType || (RollSubType = {})); +})(RollSubType || (RollSubType = {})); +/*~ @@DOUBLE-BLANK@@ ~*/ export var ConsequenceType; (function (ConsequenceType) { ConsequenceType["ReducedEffect"] = "ReducedEffect"; @@ -196,7 +201,8 @@ export var ConsequenceType; ConsequenceType["ResolveHarm3"] = "ResolveHarm3"; ConsequenceType["ResolveHarm4"] = "ResolveHarm4"; ConsequenceType["None"] = "None"; -})(ConsequenceType || (ConsequenceType = {})); +})(ConsequenceType || (ConsequenceType = {})); +/*~ @@DOUBLE-BLANK@@ ~*/ export var RollModStatus; (function (RollModStatus) { RollModStatus["Hidden"] = "Hidden"; @@ -205,7 +211,8 @@ export var RollModStatus; RollModStatus["ToggledOn"] = "ToggledOn"; RollModStatus["ForcedOn"] = "ForcedOn"; RollModStatus["Dominant"] = "Dominant"; -})(RollModStatus || (RollModStatus = {})); +})(RollModStatus || (RollModStatus = {})); +/*~ @@DOUBLE-BLANK@@ ~*/ export var RollModSection; (function (RollModSection) { RollModSection["roll"] = "roll"; @@ -234,29 +241,41 @@ export var Factor; Factor["quality"] = "quality"; Factor["scale"] = "scale"; Factor["magnitude"] = "magnitude"; -})(Factor || (Factor = {})); +})(Factor || (Factor = {})); +/*~ @@DOUBLE-BLANK@@ ~*/ export var RollResult; (function (RollResult) { RollResult["critical"] = "critical"; RollResult["success"] = "success"; RollResult["partial"] = "partial"; RollResult["fail"] = "fail"; -})(RollResult || (RollResult = {})); +})(RollResult || (RollResult = {})); +/*~ @@DOUBLE-BLANK@@ ~*/ export var RollPhase; -(function (RollPhase) { - RollPhase["Collaboration"] = "Collaboration"; - RollPhase["AwaitingRoll"] = "AwaitingRoll"; - RollPhase["ApplyingConsequences"] = "ApplyingConsequences"; - RollPhase["AwaitingChatInput"] = "AwaitingChatInput"; +(function (RollPhase) { + // Collaboration: Before GM toggles "Roll" button for player to click. + RollPhase["Collaboration"] = "Collaboration"; + // AwaitingRoll: Waiting for player to click "ROLL" + RollPhase["AwaitingRoll"] = "AwaitingRoll"; + // ApplyingConsequences: Waiting for GM to select consequence(s), AI to + // respond with resistance options, and GM to select + // resistance options. + RollPhase["ApplyingConsequences"] = "ApplyingConsequences"; + // AwaitingChatInput: Consequences and player options output to chat; + // awaiting player choice there + RollPhase["AwaitingChatInput"] = "AwaitingChatInput"; + // Complete: Roll finished (but may trigger another roll, e.g. resistance) RollPhase["Complete"] = "Complete"; -})(RollPhase || (RollPhase = {})); +})(RollPhase || (RollPhase = {})); +/*~ @@DOUBLE-BLANK@@ ~*/ export var Harm; (function (Harm) { Harm["Weakened"] = "Weakened"; Harm["Impaired"] = "Impaired"; Harm["Broken"] = "Broken"; Harm["Dead"] = "Dead"; -})(Harm || (Harm = {})); +})(Harm || (Harm = {})); +/*~ @@DOUBLE-BLANK@@ ~*/ export var Vice; (function (Vice) { Vice["Faith"] = "Faith"; @@ -271,7 +290,8 @@ export var Vice; Vice["Living_Essence"] = "Living_Essence"; Vice["Electroplasmic_Power"] = "Electroplasmic_Power"; Vice["Servitude"] = "Servitude"; -})(Vice || (Vice = {})); +})(Vice || (Vice = {})); +/*~ @@DOUBLE-BLANK@@ ~*/ export var Playbook; (function (Playbook) { Playbook["Cutter"] = "Cutter"; @@ -291,7 +311,8 @@ export var Playbook; Playbook["Shadows"] = "Shadows"; Playbook["Smugglers"] = "Smugglers"; Playbook["Vigilantes"] = "Vigilantes"; -})(Playbook || (Playbook = {})); +})(Playbook || (Playbook = {})); +/*~ @@DOUBLE-BLANK@@ ~*/ export var AdvancementPoint; (function (AdvancementPoint) { AdvancementPoint["UpgradeOrAbility"] = "UpgradeOrAbility"; @@ -315,7 +336,8 @@ export var AdvancementPoint; AdvancementPoint["command"] = "command"; AdvancementPoint["consort"] = "consort"; AdvancementPoint["sway"] = "sway"; -})(AdvancementPoint || (AdvancementPoint = {})); +})(AdvancementPoint || (AdvancementPoint = {})); +/*~ @@DOUBLE-BLANK@@ ~*/ export var BladesPhase; (function (BladesPhase) { BladesPhase["CharGen"] = "CharGen"; @@ -324,7 +346,8 @@ export var BladesPhase; BladesPhase["Downtime"] = "Downtime"; })(BladesPhase || (BladesPhase = {})); export var Tag; -(function (Tag) { +(function (Tag) { + /*~ @@DOUBLE-BLANK@@ ~*/ let System; (function (System) { System["Archived"] = "Archived"; @@ -354,7 +377,7 @@ export var Tag; Invention["SparkCraft"] = "SparkCraft"; Invention["Alchemical"] = "Alchemical"; Invention["Mundane"] = "Mundane"; - Invention["Ritual"] = "Ritual"; + Invention["Ritual"] = "Ritual"; // Rituals })(Invention = Tag.Invention || (Tag.Invention = {})); let GearCategory; (function (GearCategory) { @@ -379,7 +402,8 @@ export var Tag; GangType["Skulks"] = "Skulks"; GangType["Vehicle"] = "Vehicle"; })(GangType = Tag.GangType || (Tag.GangType = {})); -})(Tag || (Tag = {})); +})(Tag || (Tag = {})); +/*~ @@DOUBLE-BLANK@@ ~*/ const C = { SYSTEM_ID: "eunos-blades", SYSTEM_NAME: "Euno's Blades", @@ -517,27 +541,50 @@ const C = { [ConsequenceType.ResolveHarm4]: "spikes|eyeball|iris", [ConsequenceType.None]: "" }, + RollResultDescriptions: { + [Position.controlled]: { + [RollResult.critical]: "You critically succeed from a controlled position!", + [RollResult.success]: "You fully succeed from a controlled position!", + [RollResult.partial]: "You partially succeed from a controlled position!", + [RollResult.fail]: "You fail from a controlled position!" + }, + [Position.risky]: { + [RollResult.critical]: "You critically succeed from a risky position!", + [RollResult.success]: "You fully succeed from a risky position!", + [RollResult.partial]: "You partially succeed from a risky position!", + [RollResult.fail]: "You fail from a risky position!" + }, + [Position.desperate]: { + [RollResult.critical]: "You critically succeed from a desperate position!", + [RollResult.success]: "You fully succeed from a desperate position!", + [RollResult.partial]: "You partially succeed from a desperate position!", + [RollResult.fail]: "You fail from a desperate position!" + }, + }, // Colors: { bWHITE: "rgba(255, 255, 255, 1)", WHITE: "rgba(200, 200, 200, 1)", bGREY: "rgba(170, 170, 170, 1)", - GREY: "rgba(128, 128, 128, 1)", - dGREY: "rgba(78, 78, 78, 1)", - BLACK: "rgba(16, 16, 16, 1)", - dBLACK: "rgba(0, 0, 0, 1)", - gGOLD: "rgba(255, 254, 200, 1)", - bGOLD: "rgba(171, 146, 84, 1)", - GOLD: "rgba(253, 212, 112, 1)", - dGOLD: "rgba(65, 61, 46, 1)", - RED: "rgba(155, 32, 32, 1)", - dRED: "rgba(70, 14, 14, 1)", - bRED: "rgba(240, 50, 50, 1)", - gRED: "rgba(255, 0, 0, 1)", - BLUE: "rgba(43, 85, 139, 1)", - dBLUE: "rgba(17, 33, 54, 1)", - bBLUE: "rgba(69, 137, 224, 1)", - gBLUE: "rgba(128, 185, 255, 1)" + GREY: "rgba(119, 119, 119, 1)", + dGREY: "rgba(68, 68, 68, 1)", + BLACK: "rgba(32, 32, 32, 1)", + dBLACK: "rgba(0, 0, 0, 1)", + /*~ @@DOUBLE-BLANK@@ ~*/ + bGOLD: "rgba(255,216, 44, 1)", + GOLD: "rgba(215,175, 0, 1)", + dGOLD: "rgba(165,134, 0, 1)", + ddGOLD: "rgba(103, 83, 0, 1)", + /*~ @@DOUBLE-BLANK@@ ~*/ + bRED: "rgba(255, 0, 0, 1)", + RED: "rgba(200, 0, 0, 1)", + dRED: "rgba(150, 0, 0, 1)", + ddRED: "rgba(50, 0, 0, 1)", + /*~ @@DOUBLE-BLANK@@ ~*/ + bBLUE: "rgba( 0,224,224, 1)", + BLUE: "rgba(52,213,213, 1)", + dBLUE: "rgba(0,118,118, 1)", + ddBLUE: "rgba(0, 77, 77, 1)" }, Loadout: { selections: [ @@ -862,7 +909,8 @@ const C = { }, Hound: { "system.bgImg": "systems/eunos-blades/assets/icons/class-icons/hound-trans.svg", - "system.tagline": "A Deadly Sharpshooter & Tracker", + "system.tagline": "A Deadly Sharpshooter & Tracker", + // "system.acquaintances_name": "Deadly Friends & Rivals", "system.friends_name": "Deadly Friends", "system.rivals_name": "Deadlier Rivals", "system.starting_stats.chargen": { @@ -888,7 +936,8 @@ const C = { }, Leech: { "system.bgImg": "systems/eunos-blades/assets/icons/class-icons/leech-trans.svg", - "system.tagline": "A Saboteur & Technician", + "system.tagline": "A Saboteur & Technician", + // "system.acquaintances_name": "Clever Friends & Rivals", "system.friends_name": "Clever Friends", "system.rivals_name": "Cleverer Rivals", "system.starting_stats.chargen": { @@ -914,7 +963,8 @@ const C = { }, Lurk: { "system.bgImg": "systems/eunos-blades/assets/icons/class-icons/lurk-trans.svg", - "system.tagline": "A Stealthy Infiltrator & Burglar", + "system.tagline": "A Stealthy Infiltrator & Burglar", + // "system.acquaintances_name": "Shady Friends & Rivals", "system.friends_name": "Shady Friends", "system.rivals_name": "Shadier Rivals", "system.starting_stats.chargen": { @@ -940,7 +990,8 @@ const C = { }, Slide: { "system.bgImg": "systems/eunos-blades/assets/icons/class-icons/slide-trans.svg", - "system.tagline": "A Subtle Manipulator & Spy", + "system.tagline": "A Subtle Manipulator & Spy", + // "system.acquaintances_name": "Sly Friends & Rivals", "system.friends_name": "Sly Friends", "system.rivals_name": "Slyer Rivals", "system.starting_stats.chargen": { @@ -966,7 +1017,8 @@ const C = { }, Spider: { "system.bgImg": "systems/eunos-blades/assets/icons/class-icons/spider-trans.svg", - "system.tagline": "A Devious Mastermind", + "system.tagline": "A Devious Mastermind", + // "system.acquaintances_name": "Shrewd Friends & Rivals", "system.friends_name": "Shrewd Friends", "system.rivals_name": "Very Shrewd Rivals", "system.starting_stats.chargen": { @@ -992,7 +1044,8 @@ const C = { }, Whisper: { "system.bgImg": "systems/eunos-blades/assets/icons/class-icons/whisper-trans.svg", - "system.tagline": "An Arcane Adept & Channeler", + "system.tagline": "An Arcane Adept & Channeler", + // "system.acquaintances_name": "Strange Friends & Rivals", "system.friends_name": "Strange Friends", "system.rivals_name": "Stranger Rivals", "system.starting_stats.chargen": { @@ -1159,7 +1212,8 @@ const C = { Vice.Life_Essence, Vice.Electroplasmic_Power ] -}; +}; +/*~ @@DOUBLE-BLANK@@ ~*/ export const Randomizers = { NPC: { heritage: [ @@ -4308,7 +4362,8 @@ export const Randomizers = { } ] } -}; +}; +/*~ @@DOUBLE-BLANK@@ ~*/ export const SVGDATA = { overlayScale: 0.25, keys: { @@ -4396,7 +4451,8 @@ export const SVGDATA = { 10: "M317.557,38.197C284.557,14.18,243.938,0,200,0v200L317.557,38.197z", 12: "M300.014,26.771C270.593,9.748,236.436,0,200,0v200L300.014,26.771z" }, - glows: { + glows: { + /*~ @@DOUBLE-BLANK@@ ~*/ } }, teeth: { @@ -4628,5 +4684,7 @@ export const SVGDATA = { iris: "fill-bright" } } -}; -export default C; \ No newline at end of file +}; +/*~ @@DOUBLE-BLANK@@ ~*/ +export default C; +/*~ @@DOUBLE-BLANK@@ ~*/ diff --git a/module/core/gsap.js b/module/core/gsap.js index 6c2a16d1..52f4963f 100644 --- a/module/core/gsap.js +++ b/module/core/gsap.js @@ -1,16 +1,508 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - import U from "./utilities.js"; +import C from "./constants.js"; +// eslint-disable-next-line import/no-unresolved import { TextPlugin } from "/scripts/greensock/esm/all.js"; +/*~ @@DOUBLE-BLANK@@ ~*/ const gsapPlugins = [ TextPlugin ]; +/*~ @@DOUBLE-BLANK@@ ~*/ const gsapEffects = { + csqEnter: { + effect: (csqContainer, config) => { + const csqRoot = U.gsap.utils.selector(csqContainer); + // eLog.checkLog3("gsap", "gsapEffects.consequenceEnter -> THIS", {this: this, csqRoot}); + const csqIconCircle = csqRoot(".consequence-icon-circle.base-consequence"); + const csqBaseElems = csqRoot(".base-consequence:not(.consequence-icon-circle)"); + const csqAcceptElems = csqRoot(".accept-consequence:not(.consequence-icon-circle):not(.consequence-button-container)"); + /*~ @@DOUBLE-BLANK@@ ~*/ + const tl = U.gsap.timeline({ paused: true }); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Fade out base-consequence components + tl.fromTo(csqBaseElems, { + opacity: 1 + }, { + opacity: 0, + duration: config.duration / 3, + ease: "none" + }, 0); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Fade in accept-consequence components + tl.fromTo(csqAcceptElems, { + opacity: 0 + }, { + opacity: 1, + duration: config.duration / 3, + ease: "none" + }, 0); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Brighten the entire container slightly + tl.fromTo(csqContainer, { + filter: "brightness(1)" + }, { + filter: `brightness(${config.brightness})`, + duration: config.duration / 3, + ease: "none" + }, 0); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Enlarge the icon circle, add stroke + tl.fromTo(csqIconCircle, { + xPercent: -50, + yPercent: -50, + scale: 0.75, + outlineColor: C.Colors.dBLACK, + outlineWidth: 0 + }, { + xPercent: -50, + yPercent: -50, + scale: 0.85, + outlineColor: C.Colors.GREY, + outlineWidth: 1, + duration: 0.55, + ease: "sine.out" + }, 0); + /*~ @@DOUBLE-BLANK@@ ~*/ + return tl; + }, + defaults: { + brightness: 1.5, + duration: 0.5, + scale: 1.5, + stagger: 0.05, + ease: "sine", + easeStrength: 1.5 + } + }, + csqClickIcon: { + effect: (csqIconContainer, config) => { + const csqRoot = U.gsap.utils.selector(csqIconContainer); + const csqInteractionPads = csqRoot(".consequence-interaction-pad"); + const csqIconCircleBase = csqRoot(".consequence-icon-circle.base-consequence"); + const csqIconCircleAccept = csqRoot(".consequence-icon-circle.accept-consequence"); + const csqButtonContainers = csqRoot(".consequence-button-container"); + /*~ @@DOUBLE-BLANK@@ ~*/ + const tl = U.gsap.timeline({ paused: true }); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Initialize interaction pads to display: none + if (csqInteractionPads.length) { + tl.set(csqInteractionPads, { display: "none" }); + } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Fade out the base consequence icon circle + tl.fromTo(csqIconCircleBase, { + opacity: 1 + }, { + opacity: 0, + duration: 0.25, + ease: "none" + }, 0); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Fade in the accept consequence icon circle, enlarging the stroke + tl.fromTo(csqIconCircleAccept, { + opacity: 0, + xPercent: -50, + yPercent: -50, + scale: 0.85 + }, { + opacity: 1, + xPercent: -50, + yPercent: -50, + duration: 0.15, + ease: "sine" + }, 0); + /*~ @@DOUBLE-BLANK@@ ~*/ + tl.fromTo(csqIconCircleAccept, { + outlineWidth: 1, + xPercent: -50, + yPercent: -50, + scale: 0.85 + }, { + scale: 1, + xPercent: -50, + yPercent: -50, + outlineWidth: 2, + duration: 0.25, + ease: "sine" + }, 0.175); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Scale and fade in the button containers + tl.fromTo(csqButtonContainers, { + scale: config.scale, + opacity: 0, + filter: "blur(25px)" + }, { + scale: 1, + opacity: 1, + filter: "blur(0px)", + stagger: config.stagger, + duration: config.duration, + ease: `${config.ease}.inOut(${config.easeStrength})` + }, 0); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Finally, toggle on interaction pads + if (csqInteractionPads.length) { + tl.set(csqInteractionPads, { display: "block" }); + } + /*~ @@DOUBLE-BLANK@@ ~*/ + return tl; + }, + defaults: { + duration: 0.5, + scale: 1.5, + stagger: 0.05, + ease: "sine", + easeStrength: 1.5 + } + }, + csqEnterAccept: { + effect: (csqContainer) => { + const csqRoot = U.gsap.utils.selector(csqContainer); + const typeLine = csqRoot(".consequence-type-container .consequence-type.accept-consequence"); + const typeLineBg = csqRoot(".consequence-type-container .consequence-type-bg.accept-consequence"); + const buttonRoot = U.gsap.utils.selector(csqRoot(".consequence-button-container.consequence-accept-button-container")); + /*~ @@DOUBLE-BLANK@@ ~*/ + const buttonBg = buttonRoot(".consequence-button-bg"); + const buttonIcon = buttonRoot(".button-icon i"); + const buttonLabel = buttonRoot(".consequence-button-label"); + /*~ @@DOUBLE-BLANK@@ ~*/ + const tl = U.gsap.timeline({ paused: true }); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Turn type line white + tl.fromTo(typeLine, { + color: C.Colors.RED + }, { + color: C.Colors.WHITE, + duration: 0.5, + ease: "sine.inOut" + }, 0); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Slide type line background out from under icon + tl.fromTo(typeLineBg, { + x: 5, + scaleX: 0, + color: C.Colors.RED, + skewX: 0 + }, { + scaleX: 1, + skewX: -45, + color: C.Colors.RED, + duration: 0.5, + ease: "back.out" + }, 0); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Slide accept button background out from under icon + tl.fromTo(buttonBg, { + scaleX: 0, + color: C.Colors.RED, + skewX: 0 + }, { + x: 0, + scaleX: 1, + skewX: -45, + color: C.Colors.RED, + duration: 0.25, + ease: "back.out" + }, 0); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Turn button icon black and scale + tl.fromTo(buttonIcon, { + color: C.Colors.GREY, + opacity: 0.75, + scale: 1 + }, { + color: C.Colors.dBLACK, + scale: 1.25, + opacity: 1, + duration: 0.5, + ease: "sine" + }, 0); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Turn button label black, add letter-spacing, bold + tl.fromTo(buttonLabel, { + color: C.Colors.GREY, + fontWeight: 400, + scale: 1 + }, { + color: C.Colors.dBLACK, + fontWeight: 800, + duration: 0.75, + ease: "sine" + }, 0); + /*~ @@DOUBLE-BLANK@@ ~*/ + return tl; + }, + defaults: {} + }, + csqEnterResist: { + effect: (csqContainer) => { + const csqRoot = U.gsap.utils.selector(csqContainer); + /*~ @@DOUBLE-BLANK@@ ~*/ + const typeLine = csqRoot(".consequence-type-container .consequence-type.resist-consequence"); + /*~ @@DOUBLE-BLANK@@ ~*/ + const acceptElems = csqRoot(".accept-consequence"); + const specialArmorElems = csqRoot(".special-armor-consequence"); + /*~ @@DOUBLE-BLANK@@ ~*/ + const footerBg = csqRoot(".consequence-footer-container .consequence-footer-bg.resist-consequence"); + const attrElem = csqRoot(".consequence-footer-container .consequence-resist-attribute"); + const resistCsqName = csqRoot(".consequence-name.resist-consequence"); + const iconCircle = csqRoot(".consequence-icon-circle.resist-consequence"); + /*~ @@DOUBLE-BLANK@@ ~*/ + const buttonRoot = U.gsap.utils.selector(csqRoot(".consequence-button-container.consequence-resist-button-container")); + /*~ @@DOUBLE-BLANK@@ ~*/ + const buttonBg = buttonRoot(".consequence-button-bg"); + const buttonIcon = buttonRoot(".button-icon i"); + const buttonLabel = buttonRoot(".consequence-button-label"); + /*~ @@DOUBLE-BLANK@@ ~*/ + const tl = U.gsap.timeline({ paused: true }); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Fade out all accept elems and special armor elems + tl.to([...acceptElems, ...specialArmorElems], { + opacity: 0, + duration: 0.25, + ease: "sine.out" + }); + /*~ @@DOUBLE-BLANK@@ ~*/ + if (typeLine.length) { + // Slide out .consequence-type.resist-consequence from left + tl.fromTo(typeLine, { + x: -15, + scaleX: 0, + opacity: 1, + color: C.Colors.dGOLD + }, { + x: 0, + scaleX: 1, + opacity: 1, + color: C.Colors.dGOLD, + duration: 0.5, + ease: "back.out" + }, 0); + } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Slide out .consequence-resist-button-bg from right + tl.fromTo(buttonBg, { + scaleX: 0, + skewX: 0, + opacity: 1 + }, { + scaleX: 1, + skewX: -45, + opacity: 1, + duration: 0.5, + ease: "back.inOut" + }, 0); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Slide out .consequence-footer-bg.resist-consequence from left + tl.fromTo(footerBg, { + scaleX: 0, + skewX: 0, + opacity: 1 + }, { + scaleX: 1, + skewX: -45, + opacity: 1, + duration: 0.5, + ease: "back.inOut" + }, 0); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Slide out .consequence-resist-attribute from left + tl.fromTo(attrElem, { + scaleX: 0, + opacity: 1 + }, { + scaleX: 1, + opacity: 1, + duration: 0.5, + ease: "back.inOut" + }, 0); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Slide out .consequence-name.resist-consequence from left + tl.fromTo(resistCsqName, { + scaleX: 0, + opacity: 1 + }, { + scaleX: 1, + opacity: 1, + duration: 0.5, + ease: "back.inOut" + }, 0); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Fade in .consequence-icon-circle.resist-consequence + tl.fromTo(iconCircle, { + opacity: 0 + }, { + opacity: 1, + duration: 0.5, + ease: "back.out" + }, 0); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Turn button icon black and scale + tl.fromTo(buttonIcon, { + color: C.Colors.GREY, + opacity: 0.75, + scale: 1 + }, { + color: C.Colors.dBLACK, + scale: 1.25, + opacity: 1, + duration: 0.5, + ease: "sine" + }, 0); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Turn button label black, bold + tl.fromTo(buttonLabel, { + color: C.Colors.GREY, + fontWeight: 400, + scale: 1 + }, { + color: C.Colors.dBLACK, + fontWeight: 800, + duration: 0.75, + ease: "sine" + }, 0); + /*~ @@DOUBLE-BLANK@@ ~*/ + return tl; + }, + defaults: {} + }, + csqEnterSpecialArmor: { + effect: (csqContainer) => { + const csqRoot = U.gsap.utils.selector(csqContainer); + /*~ @@DOUBLE-BLANK@@ ~*/ + const typeLine = csqRoot(".consequence-type-container .consequence-type.special-armor-consequence"); + /*~ @@DOUBLE-BLANK@@ ~*/ + const acceptElems = csqRoot(".accept-consequence"); + const resistElems = csqRoot(".resist-consequence"); + /*~ @@DOUBLE-BLANK@@ ~*/ + const footerBg = csqRoot(".consequence-footer-container .consequence-footer-bg.special-armor-consequence"); + const footerMsg = csqRoot(".consequence-footer-container .consequence-special-armor-message"); + const specialArmorCsqName = csqRoot(".consequence-name.special-armor-consequence"); + const iconCircle = csqRoot(".consequence-icon-circle.special-armor-consequence"); + /*~ @@DOUBLE-BLANK@@ ~*/ + const buttonRoot = U.gsap.utils.selector(csqRoot(".consequence-button-container.consequence-special-armor-button-container")); + /*~ @@DOUBLE-BLANK@@ ~*/ + const buttonBg = buttonRoot(".consequence-button-bg"); + const buttonIcon = buttonRoot(".button-icon i"); + const buttonLabel = buttonRoot(".consequence-button-label"); + /*~ @@DOUBLE-BLANK@@ ~*/ + const tl = U.gsap.timeline({ paused: true }); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Fade out all accept elems and resist elems + tl.to([...acceptElems, ...resistElems], { + opacity: 0, + duration: 0.25, + ease: "sine.out" + }); + /*~ @@DOUBLE-BLANK@@ ~*/ + if (typeLine) { + // Slide out .consequence-type.special-armor-consequence from left + tl.fromTo(typeLine, { + x: -15, + scaleX: 0, + opacity: 1, + color: C.Colors.dBLUE + }, { + x: 0, + scaleX: 1, + opacity: 1, + color: C.Colors.dBLUE, + duration: 0.5, + ease: "back.out" + }, 0); + } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Slide out .consequence-special-armor-button-bg from right + tl.fromTo(buttonBg, { + scaleX: 0, + skewX: 0, + opacity: 1 + }, { + scaleX: 1, + skewX: -45, + opacity: 1, + duration: 0.5, + ease: "back.inOut" + }, 0); + /*~ @@DOUBLE-BLANK@@ ~*/ + if (footerBg) { + // Slide out .consequence-footer-bg.special-armor-consequence from left + tl.fromTo(footerBg, { + scaleX: 0, + skewX: 0, + opacity: 1 + }, { + scaleX: 1, + skewX: -45, + opacity: 1, + duration: 0.5, + ease: "back.inOut" + }, 0); + } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Slide out .consequence-special-armor-message from left + if (footerMsg) { + tl.fromTo(footerMsg, { + scaleX: 0, + opacity: 1 + }, { + scaleX: 1, + opacity: 1, + duration: 0.5, + ease: "back.inOut" + }, 0); + } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Slide out .consequence-name.special-armor-consequence from left + if (specialArmorCsqName) { + tl.fromTo(specialArmorCsqName, { + scaleX: 0, + opacity: 1 + }, { + scaleX: 1, + opacity: 1, + duration: 0.5, + ease: "back.inOut" + }, 0); + } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Fade in .consequence-icon-circle.special-armor-consequence + tl.fromTo(iconCircle, { + opacity: 0 + }, { + opacity: 1, + duration: 0.5, + ease: "back.out" + }, 0); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Turn button icon black and scale + tl.fromTo(buttonIcon, { + color: C.Colors.GREY, + opacity: 0.75, + scale: 1 + }, { + color: C.Colors.dBLACK, + scale: 1.25, + opacity: 1, + duration: 0.5, + ease: "sine" + }, 0); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Turn button label black, bold + tl.fromTo(buttonLabel, { + color: C.Colors.GREY, + fontWeight: 400, + scale: 1 + }, { + color: C.Colors.dBLACK, + fontWeight: 800, + duration: 0.75, + ease: "sine" + }, 0); + /*~ @@DOUBLE-BLANK@@ ~*/ + return tl; + }, + defaults: {} + }, blurRemove: { effect: (targets, config) => U.gsap.timeline() .to(targets, { @@ -46,6 +538,8 @@ const gsapEffects = { slideUp: { effect: (targets) => U.gsap.to(targets, { height: 0, + // PaddingTop: 0, + // paddingBottom: 0, duration: 0.5, ease: "power3" }), @@ -90,15 +584,21 @@ const gsapEffects = { extendTimeline: true }, pulseClockWedges: { - effect: (targets, config) => U.gsap.timeline({ duration: 0 }), + effect: () => U.gsap.timeline({ duration: 0 }), defaults: {} }, reversePulseClockWedges: { - effect: (targets, config) => U.gsap.timeline({ duration: 0 }), + effect: () => U.gsap.timeline({ duration: 0 }), defaults: {} }, fillCoins: { effect: (targets, config) => { + // Targets will be all coins from zero to where fill currently is + // Some will already be full, others not. + // Stagger in timeline + // Pulse in size and color + // Shimmer as they shrink back ? + /*~ @@DOUBLE-BLANK@@ ~*/ return U.gsap.effects.throb(targets, { stagger: { amount: 0.25, from: "start", @@ -114,16 +614,19 @@ const gsapEffects = { if (!tooltip) { return tl; } + // Tooltip = $(tooltip); + /*~ @@DOUBLE-BLANK@@ ~*/ if (config.scalingElems.length > 0) { tl.to(config.scalingElems, { scale: "+=0.2", filter: "none", - color: "rgba(255, 255, 255, 1)", + color: C.Colors.WHITE, opacity: 1, duration: 0.125, ease: "back" }, 0.5); } + /*~ @@DOUBLE-BLANK@@ ~*/ if (tooltip) { tl.fromTo(tooltip, { filter: "blur(50px)", @@ -138,6 +641,7 @@ const gsapEffects = { ease: "power2" }, 1); } + /*~ @@DOUBLE-BLANK@@ ~*/ return tl; }, defaults: { @@ -146,6 +650,10 @@ const gsapEffects = { } } }; +/*~ @@DOUBLE-BLANK@@ ~*/ +/** + * Registers relevant GSAP plugins and effects. + */ export function Initialize() { if (gsapPlugins.length) { U.gsap.registerPlugin(...gsapPlugins); @@ -154,6 +662,11 @@ export function Initialize() { U.gsap.registerEffect(Object.assign(effect, { name })); }); } +/*~ @@DOUBLE-BLANK@@ ~*/ +/** + * Applies listeners to '.tooltip-trigger' elements in the document. + * @param {JQuery} html The document to be searched. + */ export function ApplyTooltipListeners(html) { html.find(".tooltip-trigger").each((_, el) => { const tooltipElem = $(el).find(".tooltip")[0] ?? $(el).next(".tooltip")[0]; @@ -178,118 +691,146 @@ export function ApplyTooltipListeners(html) { }); }); } +/*~ @@DOUBLE-BLANK@@ ~*/ +/** + * Applies listeners to .consequence-display-container and children found in document. + * @param {JQuery} html The document to be searched. + */ export function ApplyConsequenceListeners(html) { + /** + * TIMELINES + * .comp.consequence-display-container:mouseenter + * = fade in grey interaction buttons + * ...:mouseleave = reverse + * + * .consequence-accept-button-container:mouseenter + * = turn type line white, text shadow + * slide out .consequence-accept-button-bg from left + * turn .consequence-accept-button i black, and scale + * turn .consequence-accept-button-label black, add letter spacing, bold + * ...:mouseleave = reverse + * + * .consequence-resist-button-container:mouseenter + * = slide in .consequence-type-bg.base-consequence to left + * fade out all .base-consequence:not(.consequence-type-bg) + * slide out .consequence-type.resist-consequence from left + * slide out .consequence-resist-button-bg from right + * slide out .consequence-footer-bg.resist-consequence from left + * slide out .consequence-resist-attribute from left + * slide out .consequence-name.resist-consequence from left + * fade in .consequence-icon-circle.resist-consequence + * ...:mouseleave = reverse + * --> IF resistedTo.type === "None", blurRemove the base_consequence name and type instead of sliding them in, + * and don't slide the resistance ones out at all. + * */ + /*~ @@DOUBLE-BLANK@@ ~*/ html .find(".comp.consequence-display-container") .each((_i, csqContainer) => { - const resistButton = $(csqContainer).find(".consequence-resist-button-container")[0]; - const acceptButton = $(csqContainer).find(".consequence-accept-button-container")[0]; - const specialArmorButton = $(csqContainer).find(".consequence-special-armor-button-container")[0]; - const baseElems = Array.from($(csqContainer).find(".base-consequence")); - const resistElems = Array.from($(csqContainer).find(".resist-consequence")); - const specialArmorElems = Array.from($(csqContainer).find(".special-armor-consequence")); - if (resistButton) { - $(resistButton) - .on({ - mouseenter: function () { - $(resistButton).css("z-index", 10); - U.gsap.to(resistButton, { - scale: 1.25, - filter: "brightness(1.25)", - duration: 0.25, - ease: "sine.out" - }); - U.gsap.to(acceptButton, { - scale: 0.8, - filter: "greyscale(1)", - duration: 0.5, - ease: "sine.inOut" - }); - U.gsap.to(specialArmorButton, { - scale: 0.8, - filter: "greyscale(1)", - duration: 0.5, - ease: "sine.inOut" - }); - U.gsap.to(baseElems, { - opacity: 0, - duration: 0.5, - ease: "sine.out" - }); - U.gsap.to(resistElems, { - opacity: 1, - duration: 0.5, - ease: "sine.out" - }); - }, - mouseleave: function () { - U.gsap.to(resistButton, { - scale: 1, - filter: "brightness(1)", - duration: 0.25, - ease: "sine.out" - }).then(() => { - $(resistButton).css("z-index", ""); - }); - U.gsap.to(acceptButton, { - scale: 1, - filter: "", - duration: 0.5, - ease: "sine.inOut" - }); - U.gsap.to(specialArmorButton, { - scale: 1, - filter: "", - duration: 0.5, - ease: "sine.inOut" - }); - U.gsap.to(baseElems, { - opacity: 1, - duration: 0.5, - ease: "sine.out" - }); - U.gsap.to(resistElems, { - opacity: 0, - duration: 0.5, - ease: "sine.out" + /*~ @@DOUBLE-BLANK@@ ~*/ + const iconContainer$ = $(csqContainer).find(".consequence-icon-container"); + /*~ @@DOUBLE-BLANK@@ ~*/ + const acceptInteractionPad$ = $(csqContainer).find(".accept-consequence-pad"); + const resistInteractionPad$ = $(csqContainer).find(".resist-consequence-pad"); + const specialArmorInteractionPad$ = $(csqContainer).find(".special-armor-consequence-pad"); + /*~ @@DOUBLE-BLANK@@ ~*/ + $(csqContainer).data("hoverTimeline", U.gsap.effects.csqEnter(csqContainer)); + $(csqContainer).on({ + mouseenter: function () { + $(csqContainer).css("z-index", 10); + $(csqContainer).data("hoverTimeline").play(); + }, + mouseleave: function () { + if (!(iconContainer$.data("isToggled") || iconContainer$.data("isTogglingOn")) || iconContainer$.data("isTogglingOff")) { + $(csqContainer).data("hoverTimeline").reverse().then(() => { + $(csqContainer).css("z-index", ""); }); } - }); - } - if (acceptButton) { - $(acceptButton) - .on({ - mouseenter: function () { - $(acceptButton).css("z-index", 10); - U.gsap.to(acceptButton, { - scale: 1.25, - filter: "brightness(1.25)", - duration: 0.25, - ease: "sine.out" - }); - U.gsap.to(baseElems, { - filter: "brightness(1.25)", - duration: 0.5, - ease: "sine.out" + } + }); + /*~ @@DOUBLE-BLANK@@ ~*/ + iconContainer$.data("clickTimeline", U.gsap.effects.csqClickIcon(iconContainer$[0])); + iconContainer$.on({ + click: function () { + if (iconContainer$.data("isToggled") || iconContainer$.data("isTogglingOn")) { + iconContainer$.data("isTogglingOn", false); + iconContainer$.data("isTogglingOff", true); + iconContainer$.data("clickTimeline").reverse().then(() => { + iconContainer$.data("isTogglingOff", false); + iconContainer$.data("isToggled", false); }); - }, - mouseleave: function () { - U.gsap.to(acceptButton, { - scale: 1, - filter: "brightness(1)", - duration: 0.25, - ease: "sine.out" - }).then(() => { - $(acceptButton).css("z-index", ""); + } + else { + iconContainer$.data("isTogglingOn", true); + iconContainer$.data("isTogglingOff", false); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Find any siblings with toggled-on iconContainers, and toggle them off + Array.from($(csqContainer).siblings(".consequence-display-container")) + .forEach((containerElem) => { + const iContainer$ = $(containerElem).find(".consequence-icon-container"); + if (iContainer$?.data("isToggled") || iContainer$?.data("isTogglingOn")) { + iContainer$.data("isTogglingOn", false); + iContainer$.data("isTogglingOff", true); + iContainer$.data("clickTimeline").reverse().then(() => { + iContainer$.data("isTogglingOff", false); + iContainer$.data("isToggled", false); + $(containerElem).data("hoverTimeline").reverse().then(() => { + $(containerElem).css("z-index", ""); + }); + }); + } }); - U.gsap.to(baseElems, { - filter: "brightness(1)", - duration: 0.5, - ease: "sine.out" + iconContainer$.data("clickTimeline").play().then(() => { + iconContainer$.data("isTogglingOn", false); + iconContainer$.data("isToggled", true); }); } - }); - } + } + }); + /*~ @@DOUBLE-BLANK@@ ~*/ + acceptInteractionPad$.data("hoverTimeline", U.gsap.effects.csqEnterAccept(csqContainer)); + acceptInteractionPad$.on({ + mouseenter: function () { + if (iconContainer$.data("isToggled")) { + acceptInteractionPad$.data("hoverTimeline").play(); + } + }, + mouseleave: function () { + acceptInteractionPad$.data("hoverTimeline").reverse(); + } + }); + /*~ @@DOUBLE-BLANK@@ ~*/ + resistInteractionPad$.data("hoverTimeline", U.gsap.effects.csqEnterResist(csqContainer)); + resistInteractionPad$.on({ + mouseenter: function () { + if (iconContainer$.data("isToggled")) { + resistInteractionPad$.data("hoverTimeline").play(); + } + }, + mouseleave: function () { + if (iconContainer$.data("isToggled")) { + resistInteractionPad$.data("hoverTimeline").reverse(); + } + } + }); + /*~ @@DOUBLE-BLANK@@ ~*/ + specialArmorInteractionPad$.data("hoverTimeline", U.gsap.effects.csqEnterSpecialArmor(csqContainer)); + specialArmorInteractionPad$.on({ + mouseenter: function () { + if (iconContainer$.data("isToggled")) { + specialArmorInteractionPad$.data("hoverTimeline").play(); + } + }, + mouseleave: function () { + if (iconContainer$.data("isToggled")) { + specialArmorInteractionPad$.data("hoverTimeline").reverse(); + } + } + }); + /*~ @@DOUBLE-BLANK@@ ~*/ }); + /*~ @@DOUBLE-BLANK@@ ~*/ } -export default U.gsap; \ No newline at end of file +/*~ @@DOUBLE-BLANK@@ ~*/ +export default U.gsap; +/*~ @@DOUBLE-BLANK@@ ~*/ diff --git a/module/core/helpers.js b/module/core/helpers.js index 323c8ed8..19205338 100644 --- a/module/core/helpers.js +++ b/module/core/helpers.js @@ -1,15 +1,19 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - -import U from "./utilities.js"; -import { SVGDATA } from "./constants.js"; - -export async function preloadHandlebarsTemplates() { - const templatePaths = [ +// #region ▮▮▮▮▮▮▮ IMPORTS ▮▮▮▮▮▮▮ ~ +import U from "./utilities.js"; +/*~ @@DOUBLE-BLANK@@ ~*/ +import { SVGDATA } from "./constants.js"; +// #endregion ▮▮▮▮[IMPORTS]▮▮▮▮ +/*~ @@DOUBLE-BLANK@@ ~*/ +// #region ░░░░░░░[Templates]░░░░ Preload Partials, Components & Overlay Templates ░░░░░░░ ~ +/** + * Define a set of template paths to pre-load + * Pre-loaded templates are compiled and cached for fast access when rendering + */ +export async function preloadHandlebarsTemplates() { + // Define template paths to load + const templatePaths = [ + /*~ @@DOUBLE-BLANK@@ ~*/ + // General Components "systems/eunos-blades/templates/components/toggle-icon.hbs", "systems/eunos-blades/templates/components/button-icon.hbs", "systems/eunos-blades/templates/components/dotline.hbs", @@ -21,19 +25,27 @@ export async function preloadHandlebarsTemplates() { "systems/eunos-blades/templates/components/roll-collab-mod.hbs", "systems/eunos-blades/templates/components/roll-collab-opposition.hbs", "systems/eunos-blades/templates/components/slide-out-controls.hbs", - "systems/eunos-blades/templates/components/consequence.hbs", + "systems/eunos-blades/templates/components/consequence.hbs", + /*~ @@DOUBLE-BLANK@@ ~*/ + // Partials "systems/eunos-blades/templates/parts/tier-block.hbs", "systems/eunos-blades/templates/parts/turf-list.hbs", "systems/eunos-blades/templates/parts/cohort-block.hbs", "systems/eunos-blades/templates/parts/roll-opposition-creator.hbs", "systems/eunos-blades/templates/parts/active-effects.hbs", - "systems/eunos-blades/templates/parts/gm-pc-summary.hbs", + "systems/eunos-blades/templates/parts/gm-pc-summary.hbs", + /*~ @@DOUBLE-BLANK@@ ~*/ + // Overlays "systems/eunos-blades/templates/overlays/clock-overlay.hbs", "systems/eunos-blades/templates/overlays/clock-key.hbs" - ]; + ]; + /*~ @@DOUBLE-BLANK@@ ~*/ + // Load the template parts return loadTemplates(templatePaths); -} - +} +// #endregion ░░░░[Preload Templates]░░░░ +/*~ @@DOUBLE-BLANK@@ ~*/ +// #region ████████ Handlebars: Handlebar Helpers Definitions ████████ ~ const handlebarHelpers = { randString(param1 = 10) { return U.randString(param1); @@ -151,7 +163,9 @@ const handlebarHelpers = { return param.length; } return param ? 1 : 0; - }, + }, + // Concat helper + // Usage: (concat 'first 'second') concat(...args) { let outStr = ""; for (const arg of args) { @@ -160,7 +174,8 @@ const handlebarHelpers = { } } return outStr; - }, + }, + // For loop: {{#for [from = 0, to, stepSize = 1]}}{{/for}} for: (...args) => { const options = args.pop(); let [from, to, stepSize] = args; @@ -202,8 +217,10 @@ const handlebarHelpers = { dbLevel = args.shift(); } eLog.hbsLog(...args, dbLevel); - }, - isTurfBlock: (name) => U.fuzzyMatch(name, "Turf"), + }, + // Does the name of this turf block represent a standard 'Turf' claim? + isTurfBlock: (name) => U.fuzzyMatch(name, "Turf"), + // Which other connection does this connector overlap with? getConnectorPartner: (index, direction) => { index = parseInt(`${index}`, 10); const partners = { @@ -229,7 +246,8 @@ const handlebarHelpers = { return `${partnerNum}-${partnerDir}`; } return null; - }, + }, + // Is the value Turf side. isTurfOnEdge: (index, direction) => { index = parseInt(`${index}`, 10); const edges = { @@ -253,38 +271,53 @@ const handlebarHelpers = { return true; } return edges[index].includes(direction); - }, + }, + // Multiboxes multiboxes(selected, options) { let html = options.fn(this); - selected = [selected].flat(1); + selected = [selected].flat(1); + /*~ @@DOUBLE-BLANK@@ ~*/ selected.forEach((selectedVal) => { if (selectedVal !== false) { const escapedValue = RegExp.escape(Handlebars.escapeExpression(String(selectedVal))); const rgx = new RegExp(` value="${escapedValue}"`); html = html.replace(rgx, "$& checked=\"checked\""); } - }); + }); + /*~ @@DOUBLE-BLANK@@ ~*/ return html; }, repturf: (turfsAmount, options) => { let html = options.fn(this); - let turfsAmountInt = parseInt(turfsAmount, 10); + let turfsAmountInt = parseInt(turfsAmount, 10); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Can't be more than 6. if (turfsAmountInt > 6) { turfsAmountInt = 6; - } + } + /*~ @@DOUBLE-BLANK@@ ~*/ for (let i = 13 - turfsAmountInt; i <= 12; i++) { const rgx = new RegExp(` value="${i}"`); html = html.replace(rgx, "$& disabled=\"disabled\""); - } + } + /*~ @@DOUBLE-BLANK@@ ~*/ return html; } -}; +}; +/*~ @@DOUBLE-BLANK@@ ~*/ handlebarHelpers.eLog1 = function (...args) { handlebarHelpers.eLog(...[1, ...args.slice(0, 7)]); }; handlebarHelpers.eLog2 = function (...args) { handlebarHelpers.eLog(...[2, ...args.slice(0, 7)]); }; handlebarHelpers.eLog3 = function (...args) { handlebarHelpers.eLog(...[3, ...args.slice(0, 7)]); }; handlebarHelpers.eLog4 = function (...args) { handlebarHelpers.eLog(...[4, ...args.slice(0, 7)]); }; -handlebarHelpers.eLog5 = function (...args) { handlebarHelpers.eLog(...[5, ...args.slice(0, 7)]); }; -Object.assign(handlebarHelpers); +handlebarHelpers.eLog5 = function (...args) { handlebarHelpers.eLog(...[5, ...args.slice(0, 7)]); }; +/*~ @@DOUBLE-BLANK@@ ~*/ +Object.assign(handlebarHelpers); +/*~ @@DOUBLE-BLANK@@ ~*/ +/** + * + */ export function registerHandlebarHelpers() { Object.entries(handlebarHelpers).forEach(([name, func]) => Handlebars.registerHelper(name, func)); -} \ No newline at end of file +} +// #endregion ▄▄▄▄▄ Handlebars ▄▄▄▄▄ +/*~ @@DOUBLE-BLANK@@ ~*/ diff --git a/module/core/logger.js b/module/core/logger.js index fccebe47..72a05f52 100644 --- a/module/core/logger.js +++ b/module/core/logger.js @@ -1,40 +1,35 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - import U from "./utilities.js"; -import C from "./constants.js"; +import C from "./constants.js"; +/*~ @@DOUBLE-BLANK@@ ~*/ const LOGGERCONFIG = { fullName: "eLogger", aliases: ["dbLog"], stackTraceExclusions: { - handlebars: [/scripts\/handlebars/] + handlebars: [/scripts\/handlebars/] // From internal Handlebars module } -}; +}; +/*~ @@DOUBLE-BLANK@@ ~*/ const STYLES = { base: { background: C.Colors.BLACK, - color: C.Colors.bGOLD, + color: C.Colors.dGOLD, "font-family": "Pragmata Pro", padding: "0 25px", "margin-right": "25px" }, log0: { - background: C.Colors.bGOLD, + background: C.Colors.dGOLD, color: C.Colors.dBLACK, "font-size": "16px" }, log1: { background: C.Colors.dBLACK, - color: C.Colors.gGOLD, + color: C.Colors.bGOLD, "font-size": "16px" }, log2: { background: C.Colors.dBLACK, - color: C.Colors.bGOLD, + color: C.Colors.dGOLD, "font-size": "16px" }, log3: { @@ -49,15 +44,15 @@ const STYLES = { "font-size": "10px" }, display: { - color: C.Colors.gGOLD, + color: C.Colors.bGOLD, "font-family": "Kirsty Rg", "font-size": "16px", "margin-left": "-100px", padding: "0 100px" }, error: { - color: C.Colors.gRED, - background: C.Colors.dRED, + color: C.Colors.bRED, + background: C.Colors.ddRED, "font-weight": 500 }, handlebars: { @@ -73,12 +68,14 @@ const STYLES = { "font-size": "10px", "font-family": "Pragmata Pro" } -}; +}; +/*~ @@DOUBLE-BLANK@@ ~*/ const eLogger = (type = "base", ...content) => { if (!(type === "error" || CONFIG.debug.logging)) { return; } - const lastElem = U.getLast(content); + const lastElem = U.getLast(content); + /*~ @@DOUBLE-BLANK@@ ~*/ let dbLevel = typeof lastElem === "number" && [0, 1, 2, 3, 4, 5].includes(lastElem) ? content.pop() : 3; @@ -86,8 +83,10 @@ const eLogger = (type = "base", ...content) => { if (type === "checkLog") { key = content.shift(); type = `log${dbLevel}`; - } - const [message, ...data] = content; + } + /*~ @@DOUBLE-BLANK@@ ~*/ + const [message, ...data] = content; + /*~ @@DOUBLE-BLANK@@ ~*/ if (key) { const blacklist = (U.getSetting("blacklist") ?? "").split(/,/).map((pat) => new RegExp(`\\b${pat.trim()}\\b`, "igu")); const whitelist = (U.getSetting("whitelist") ?? "").split(/,/).map((pat) => new RegExp(`\\b${pat.trim()}\\b`, "igu")); @@ -112,7 +111,8 @@ const eLogger = (type = "base", ...content) => { const styleLine = Object.entries({ ...STYLES.base, ...STYLES[type] ?? {} - }).map(([prop, val]) => `${prop}: ${val};`).join(" "); + }).map(([prop, val]) => `${prop}: ${val};`).join(" "); + /*~ @@DOUBLE-BLANK@@ ~*/ let logFunc; if (stackTrace) { logFunc = console.groupCollapsed; @@ -122,7 +122,8 @@ const eLogger = (type = "base", ...content) => { } else { logFunc = console.group; - } + } + /*~ @@DOUBLE-BLANK@@ ~*/ if (data.length === 0) { if (typeof message === "string") { logFunc(`%c${message}`, styleLine); @@ -143,12 +144,17 @@ const eLogger = (type = "base", ...content) => { }); } if (stackTrace) { - console.group("%cSTACK TRACE", `color: ${C.Colors.bGOLD}; font-family: "Pragmata Pro"; font-size: 12px; background: ${C.Colors.BLACK}; font-weight: bold; padding: 0 10px;`); + console.group("%cSTACK TRACE", `color: ${C.Colors.dGOLD}; font-family: "Pragmata Pro"; font-size: 12px; background: ${C.Colors.BLACK}; font-weight: bold; padding: 0 10px;`); console.log(`%c${stackTrace}`, Object.entries(STYLES.stack).map(([prop, val]) => `${prop}: ${val};`).join(" ")); console.groupEnd(); } - console.groupEnd(); - function getStackTrace(regExpFilters = []) { + console.groupEnd(); + /*~ @@DOUBLE-BLANK@@ ~*/ + /** + * + * @param regExpFilters + */ + function getStackTrace(regExpFilters = []) { regExpFilters.push(new RegExp(`at (getStackTrace|${LOGGERCONFIG.fullName}|${LOGGERCONFIG.aliases.map(String).join("|")}|Object\\.(log|display|hbsLog|error))`), /^Error/); return ((new Error()).stack ?? "") .split(/\n/) @@ -175,5 +181,7 @@ const logger = { checkLog5: (...content) => eLogger("checkLog", ...content, 5), error: (...content) => eLogger("error", ...content), hbsLog: (...content) => eLogger("handlebars", ...content) -}; -export default logger; \ No newline at end of file +}; +/*~ @@DOUBLE-BLANK@@ ~*/ +export default logger; +/*~ @@DOUBLE-BLANK@@ ~*/ diff --git a/module/core/settings.js b/module/core/settings.js index 70af61a7..9f2523e1 100644 --- a/module/core/settings.js +++ b/module/core/settings.js @@ -1,12 +1,6 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - import U from "./utilities.js"; -import C from "./constants.js"; +import C from "./constants.js"; +/*~ @@DOUBLE-BLANK@@ ~*/ const registerSettings = function () { game.settings.register("eunos-blades", "debug", { name: "Debug Level", @@ -19,7 +13,7 @@ const registerSettings = function () { max: 5, step: 1 }, - default: 3 + default: 3 // The default value for the setting }); game.settings.register("eunos-blades", "openAPIModelLevel", { name: "AI Base Quality", @@ -39,7 +33,7 @@ const registerSettings = function () { scope: "client", config: true, type: String, - default: "" + default: "" // The default value for the setting }); game.settings.register("eunos-blades", "openAPIKey", { name: "OpenAI API Key", @@ -47,7 +41,7 @@ const registerSettings = function () { scope: "client", config: true, type: String, - default: "" + default: "" // The default value for the setting }); game.settings.register("eunos-blades", "whitelist", { name: "Debug Whitelist", @@ -55,16 +49,23 @@ const registerSettings = function () { scope: "client", config: true, type: String, - default: "" + default: "" // The default value for the setting }); - game.settings.register("eunos-blades", "systemMigrationVersion", { + /** + * Track the system version upon which point a migration was last applied + */ + game.settings.register("eunos-blades", "systemMigrationVersion", { name: "System Migration Version", scope: "world", config: false, type: Number, default: 0 }); -}; +}; +/*~ @@DOUBLE-BLANK@@ ~*/ +/** + * + */ export function initTinyMCEStyles() { CONFIG.TinyMCE = { ...CONFIG.TinyMCE, @@ -83,7 +84,8 @@ export function initTinyMCEStyles() { branding: false, resize: false, plugins: "lists image table code save autoresize searchreplace quickbars template", - save_enablewhendirty: false, + save_enablewhendirty: false, + // Table_default_styles: {}, style_formats: [ { title: "Headings", @@ -98,7 +100,8 @@ export function initTinyMCEStyles() { title: "Blocks", items: [ { title: "Paragraph", block: "p", wrapper: false }, - { title: "Block Quote", block: "blockquote", wrapper: true } + { title: "Block Quote", block: "blockquote", wrapper: true } + // {title: "Secret", block: "span", classes: "text-secret", attributes: {"data-is-secret": "true"}, wrapper: false} ] }, { @@ -142,7 +145,11 @@ export function initTinyMCEStyles() { quickbars_table_toolbar: "tableprops tabledelete | tableinsertrowbefore tableinsertrowafter tabledeleterow | tableinsertcolbefore tableinsertcolafter tabledeletecol" } }; -} +} +/*~ @@DOUBLE-BLANK@@ ~*/ +/** + * + */ export function initCanvasStyles() { CONFIG.canvasTextStyle = new PIXI.TextStyle({ align: "center", @@ -173,5 +180,7 @@ export function initCanvasStyles() { wordWrap: true, wordWrapWidth: 0.1 }); -} -export default registerSettings; \ No newline at end of file +} +/*~ @@DOUBLE-BLANK@@ ~*/ +export default registerSettings; +/*~ @@DOUBLE-BLANK@@ ~*/ diff --git a/module/core/tags.js b/module/core/tags.js index b736ecea..85a68cf1 100644 --- a/module/core/tags.js +++ b/module/core/tags.js @@ -1,13 +1,7 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - import Tagify from "../../lib/tagify/tagify.esm.js"; import { Tag, MainDistrict, OtherDistrict, Vice, Playbook, BladesActorType } from "./constants.js"; import U from "./utilities.js"; +/*~ @@DOUBLE-BLANK@@ ~*/ const _onTagifyChange = (event, doc, targetKey) => { const tagString = event.target.value; if (tagString) { @@ -18,9 +12,18 @@ const _onTagifyChange = (event, doc, targetKey) => { doc.update({ [targetKey]: [] }); } }; +/*~ @@DOUBLE-BLANK@@ ~*/ const Tags = { InitListeners: (html, doc) => { - function makeTagInput(elem, tags) { + /*~ @@DOUBLE-BLANK@@ ~*/ + /** + * Applies tags and Tagify functionality to a specified HTML element. + * @param {HTMLElement} elem The element to tagify. + * @param {Record} tags The tags, sorted into groups, to apply. + */ + function makeTagInput(elem, tags) { + /*~ @@DOUBLE-BLANK@@ ~*/ + // Create tagify instance; populate dropdown list with tags const tagify = new Tagify(elem, { enforceWhitelist: true, editTags: false, @@ -38,30 +41,45 @@ const Tags = { appendTarget: html[0] } }); + /*~ @@DOUBLE-BLANK@@ ~*/ tagify.dropdown.createListHTML = (optionsArr) => { const map = {}; + /*~ @@DOUBLE-BLANK@@ ~*/ return structuredClone(optionsArr) .map((suggestion, idx) => { + /*~ @@DOUBLE-BLANK@@ ~*/ const value = tagify.dropdown.getMappedValue.call(tagify, suggestion); let tagHTMLString = ""; + /*~ @@DOUBLE-BLANK@@ ~*/ if (!map[suggestion["data-group"]]) { map[suggestion["data-group"]] = true; + /*~ @@DOUBLE-BLANK@@ ~*/ if (Object.keys(map).length) { tagHTMLString += ""; } + /*~ @@DOUBLE-BLANK@@ ~*/ tagHTMLString += `

${suggestion["data-group"]}

`; } + /*~ @@DOUBLE-BLANK@@ ~*/ suggestion.value = value && typeof value === "string" ? U.escapeHTML(value) : value; + /*~ @@DOUBLE-BLANK@@ ~*/ tagHTMLString += tagify.settings.templates.dropdownItem.apply(tagify, [suggestion, idx]); + /*~ @@DOUBLE-BLANK@@ ~*/ return tagHTMLString; }) .join(""); }; - function findDataGroup(tag) { + /*~ @@DOUBLE-BLANK@@ ~*/ + /** + * Returns the tag group to which a tag belongs, or false if no group found. + * @param {BladesTag|string} tag + * @returns {string|false} Either the group containing the given tag, or false if no group found. + */ + function findDataGroup(tag) { for (const [group, tagList] of Object.entries(tags)) { if (tagList.includes(tag)) { return group; @@ -69,6 +87,8 @@ const Tags = { } return false; } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Check if element specifies an alternate schema target from doc.tags const targetKey = $(elem).data("tagTarget") ?? "system.tags"; const curTags = [getProperty(doc, targetKey) ?? []].flat().filter(Boolean); tagify.addTags(curTags @@ -77,9 +97,12 @@ const Tags = { value: (new Handlebars.SafeString(tag)).toString(), "data-group": findDataGroup(tag) })), true, true); - + /*~ @@DOUBLE-BLANK@@ ~*/ + // Add event listener for tag changes, setting defined target + // Wait briefly, so other tag elements' tags can be set before listener initializes setTimeout(() => elem.addEventListener("change", (event) => { _onTagifyChange(event, doc, targetKey); }), 1000); } + /*~ @@DOUBLE-BLANK@@ ~*/ const systemTags = { "System Tags": Object.values(Tag.System), "Gear Tags": [ @@ -102,9 +125,15 @@ const Tags = { const factionTags = { Factions: game.actors .filter((actor) => actor.type === BladesActorType.faction && actor.name !== null) .map((faction) => faction.name) }; + /*~ @@DOUBLE-BLANK@@ ~*/ $(html).find(".tags-gm").each((_, e) => makeTagInput(e, systemTags)); + /*~ @@DOUBLE-BLANK@@ ~*/ $(html).find(".tags-district").each((_, e) => makeTagInput(e, districtTags)); + /*~ @@DOUBLE-BLANK@@ ~*/ $(html).find(".tags-faction").each((_, e) => makeTagInput(e, factionTags)); + /*~ @@DOUBLE-BLANK@@ ~*/ } }; -export default Tags; \ No newline at end of file +/*~ @@DOUBLE-BLANK@@ ~*/ +export default Tags; +/*~ @@DOUBLE-BLANK@@ ~*/ diff --git a/module/core/utilities.js b/module/core/utilities.js index 47d4a5ef..18c7b4a2 100644 --- a/module/core/utilities.js +++ b/module/core/utilities.js @@ -1,18 +1,22 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - -import C from "./constants.js"; -import { gsap } from "/scripts/greensock/esm/all.js"; +// #region ▮▮▮▮▮▮▮ IMPORTS ▮▮▮▮▮▮▮ ~ +import C from "./constants.js"; +// eslint-disable-next-line import/no-unresolved +import { gsap } from "/scripts/greensock/esm/all.js"; +// #endregion ▮▮▮▮ IMPORTS ▮▮▮▮ +/*~ @@DOUBLE-BLANK@@ ~*/ +// #region ▮▮▮▮▮▮▮ [HELPERS] Internal Functions, Data & References Used by Utility Functions ▮▮▮▮▮▮▮ ~ +/*~ @@DOUBLE-BLANK@@ ~*/ +// _noCapWords -- Patterns matching words that should NOT be capitalized when converting to TITLE case. const _noCapWords = "a|above|after|an|and|at|below|but|by|down|for|for|from|in|nor|of|off|on|onto|or|out|so|the|to|under|up|with|yet" .split("|") - .map((word) => new RegExp(`\\b${word}\\b`, "gui")); + .map((word) => new RegExp(`\\b${word}\\b`, "gui")); +/*~ @@DOUBLE-BLANK@@ ~*/ +// _capWords -- Patterns matching words that should ALWAYS be capitalized when converting to SENTENCE case. const _capWords = [ "I", /[^a-z]{3,}|[.0-9]/gu -].map((word) => (/RegExp/.test(Object.prototype.toString.call(word)) ? word : new RegExp(`\\b${word}\\b`, "gui"))); +].map((word) => (/RegExp/.test(Object.prototype.toString.call(word)) ? word : new RegExp(`\\b${word}\\b`, "gui"))); +/*~ @@DOUBLE-BLANK@@ ~*/ +// _loremIpsumText -- Boilerplate lorem ipsum const _loremIpsumText = `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse ultricies nibh sed massa euismod lacinia. Aliquam nec est ac nunc ultricies scelerisque porta vulputate odio. Integer gravida mattis odio, semper volutpat tellus. Ut elit leo, auctor eget fermentum hendrerit, @@ -26,7 +30,9 @@ ut dui vel leo laoreet sodales nec ac tellus. In hac habitasse platea dictumst. sollicitudin interdum. Sed id lacus porttitor nisi vestibulum tincidunt. Nulla facilisi. Vestibulum feugiat finibus magna in pretium. Proin consectetur lectus nisi, non commodo lectus tempor et. Cras viverra, mi in consequat aliquet, justo mauris fringilla tellus, at accumsan magna metus in eros. Sed -vehicula, diam ut sagittis semper, purus massa mattis dolor, in posuere.`; +vehicula, diam ut sagittis semper, purus massa mattis dolor, in posuere.`; +/*~ @@DOUBLE-BLANK@@ ~*/ +// _randomWords -- A collection of random words for various debugging purposes. const _randomWords = ` aboveboard|account|achiever|acoustics|act|action|activity|actor|addition|adjustment|advertisement|advice|afterglow|afterimage|afterlife|aftermath|afternoon|afterthought|agreement air|aircraft|airfield|airlift|airline|airmen|airplane|airport|airtime|alarm|allover|allspice|alongside|also|amount|amusement|anger|angle|animal|another|ants|anyhow|anymore @@ -129,9 +135,17 @@ const _romanNumerals = { ["", "ↈ", "ↈↈ", "ↈↈↈ"] ] }; -const UUIDLOG = []; - -const GMID = () => game?.user?.find((user) => user.isGM)?.id ?? false; +const UUIDLOG = []; +/*~ @@DOUBLE-BLANK@@ ~*/ +// #endregion ▮▮▮▮[HELPERS]▮▮▮▮ +/*~ @@DOUBLE-BLANK@@ ~*/ +// #region ████████ GETTERS: Basic Data Lookup & Retrieval ████████ ~ +// @ts-expect-error Leauge of foundry developers is wrong about user not being on game. +const GMID = () => game?.user?.find((user) => user.isGM)?.id ?? false; +// #endregion ▄▄▄▄▄ GETTERS ▄▄▄▄▄ +/*~ @@DOUBLE-BLANK@@ ~*/ +// #region ████████ TYPES: Type Checking, Validation, Conversion, Casting ████████ ~ +/*~ @@DOUBLE-BLANK@@ ~*/ const isNumber = (ref) => typeof ref === "number" && !isNaN(ref); const isNumberString = (ref) => typeof ref === "string" && !isNaN(parseFloat(ref)) @@ -156,57 +170,98 @@ const isEmpty = (ref) => Object.keys(ref).length === 0; const hasItems = (ref) => !isEmpty(ref); const isInstance = (classRef, ref) => ref instanceof classRef; const isNullish = (ref) => isUndefined(ref) || ref === null; +/** + * Asserts that a given value is of a specified type. + * Throws an error if the value is not of the expected type. + * + * @template T The expected type of the value. + * @param {unknown} val The value to check. + * @param {(new(...args: unknown[]) => T) | string} type The expected type of the value. + * @throws {Error} If the value is not of the expected type. + */ function assertNonNullType(val, type) { - let valStr; + let valStr; + // Attempt to convert the value to a string for error messaging. try { valStr = JSON.stringify(val); } catch { valStr = String(val); - } + } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Check if the value is undefined if (val === undefined) { throw new Error(`Value ${valStr} is undefined!`); - } - if (typeof type === "string") { + } + /*~ @@DOUBLE-BLANK@@ ~*/ + // If the type is a string, compare the typeof the value to the type string. + if (typeof type === "string") { + // eslint-disable-next-line valid-typeof if (typeof val !== type) { throw new Error(`Value ${valStr} is not a ${type}!`); } } - else if (!(val instanceof type)) { + else if (!(val instanceof type)) { + // If the type is a function (constructor), check if the value is an instance of the type. throw new Error(`Value ${valStr} is not a ${type.name}!`); } } -const areFuzzyEqual = (val1, val2) => { +/** + * Checks if two values are "fuzzy" equal, simulating the behavior of the "==" operator. + * This function does not use the "==" operator directly to comply with linting rules. + * + * @param {unknown} val1 The first value to compare. + * @param {unknown} val2 The second value to compare. + * @returns {boolean} True if the values are "fuzzy" equal, false otherwise. + */ +const areFuzzyEqual = (val1, val2) => { + // If both values are null or undefined, they are considered equal if ([null, undefined].includes(val1) && [null, undefined].includes(val2)) { return true; - } + } + /*~ @@DOUBLE-BLANK@@ ~*/ + // If only one of the values is null or undefined, they are not equal if ([null, undefined].includes(val1) || [null, undefined].includes(val2)) { return false; - } + } + /*~ @@DOUBLE-BLANK@@ ~*/ + // If both values are numbers, they are considered equal if they are numerically equal if (typeof val1 === "number" && typeof val2 === "number") { return val1 === val2; - } + } + /*~ @@DOUBLE-BLANK@@ ~*/ + // If both values are booleans, they are considered equal if they are both true or both false if (typeof val1 === "boolean" && typeof val2 === "boolean") { return val1 === val2; - } + } + /*~ @@DOUBLE-BLANK@@ ~*/ + // If both values are strings, they are considered equal if they are identical if (typeof val1 === "string" && typeof val2 === "string") { return val1 === val2; - } - + } + /*~ @@DOUBLE-BLANK@@ ~*/ + // If one value is a number and the other is a string, they are considered + // equal if the string can be converted to the number if (typeof val1 === "number" && typeof val2 === "string") { return val1 === Number(val2); } if (typeof val1 === "string" && typeof val2 === "number") { return Number(val1) === val2; - } + } + /*~ @@DOUBLE-BLANK@@ ~*/ + // If one value is a boolean and the other is a non-null object, they are not equal if (typeof val1 === "boolean" && typeof val2 === "object") { return false; } if (typeof val1 === "object" && typeof val2 === "boolean") { return false; - } + } + /*~ @@DOUBLE-BLANK@@ ~*/ + // If one value is a boolean and the other is a string, they are considered equal ID: + // ... the boolean is true and the string is not empty, or + // ... the boolean is false and the string is empty if (typeof val1 === "boolean" && typeof val2 === "string") { return (val1 && val2 !== "") || (!val1 && val2 === ""); @@ -214,18 +269,25 @@ const areFuzzyEqual = (val1, val2) => { if (typeof val1 === "string" && typeof val2 === "boolean") { return (val2 && val1 !== "") || (!val2 && val1 === ""); - } + } + /*~ @@DOUBLE-BLANK@@ ~*/ + // If one value is a number or a string and the other is an object, they are not equal if ((typeof val1 === "number" || typeof val1 === "string") && typeof val2 === "object") { return false; } if (typeof val1 === "object" && (typeof val2 === "number" || typeof val2 === "string")) { return false; - } + } + /*~ @@DOUBLE-BLANK@@ ~*/ + // If both values are objects, they are considered equal if they are identical if (typeof val1 === "object" && typeof val2 === "object") { return val1 === val2; - } + } + /*~ @@DOUBLE-BLANK@@ ~*/ + // If none of the above conditions are met, the values are not equal return false; -}; +}; +/*~ @@DOUBLE-BLANK@@ ~*/ const areEqual = (...refs) => { do { const ref = refs.pop(); @@ -266,10 +328,15 @@ const getKey = (key, obj) => { return obj[key]; } return null; -}; +}; +/*~ @@DOUBLE-BLANK@@ ~*/ const FILTERS = { IsInstance: ((classRef) => ((item) => typeof classRef === "function" && item instanceof classRef)) -}; +}; +// #endregion ▄▄▄▄▄ TYPES ▄▄▄▄▄ +/*~ @@DOUBLE-BLANK@@ ~*/ +// #region ████████ STRINGS: String Parsing, Manipulation, Conversion, Regular Expressions ████████ +// #region ░░░░░░░[Case Conversion]░░░░ Upper, Lower, Sentence & Title Case ░░░░░░░ ~ const uCase = (str) => String(str).toUpperCase(); const lCase = (str) => String(str).toLowerCase(); const sCase = (str) => { @@ -282,13 +349,18 @@ const sCase = (str) => { }; const tCase = (str) => String(str).split(/\s/) .map((word, i) => (i && testRegExp(word, _noCapWords) ? lCase(word) : sCase(word))) - .join(" ").trim(); + .join(" ").trim(); +// #endregion ░░░░[Case Conversion]░░░░ +// #region ░░░░░░░[RegExp]░░░░ Regular Expressions ░░░░░░░ ~ const testRegExp = (str, patterns = [], flags = "gui", isTestingAll = false) => patterns .map((pattern) => (pattern instanceof RegExp ? pattern : new RegExp(`\\b${pattern}\\b`, flags)))[isTestingAll ? "every" : "some"]((pattern) => pattern.test(`${str}`)); const regExtract = (ref, pattern, flags) => { - const splitFlags = []; + /* Wrapper around String.match() that removes the need to worry about match()'s different handling of the 'g' flag. + - IF your pattern contains unescaped parentheses -> Returns Array of all matching groups. + - OTHERWISE -> Returns string that matches the provided pattern. */ + const splitFlags = []; [...(flags ?? "").replace(/g/g, ""), "u"].forEach((flag) => { if (flag && !splitFlags.includes(flag)) { splitFlags.push(flag); @@ -302,8 +374,11 @@ const regExtract = (ref, pattern, flags) => { pattern = new RegExp(pattern, flags); const matches = `${ref}`.match(pattern) || []; return isGrouping ? Array.from(matches) : matches.pop(); -}; - +}; +/*~ @@DOUBLE-BLANK@@ ~*/ +// #endregion ░░░░[RegExp]░░░░ +// #region ░░░░░░░[Formatting]░░░░ Hyphenation, Pluralization, "a"/"an" Fixing ░░░░░░░ ~ +// const hyphenate = (str: unknown) => (/^<|\u00AD|\u200B/.test(`${str}`) ? `${str}` : _hyph(`${str}`)); const unhyphenate = (str) => `${str}`.replace(/[\u00AD\u200B]/gu, ""); const parseArticles = (str) => `${str}`.replace(/\b([aA])\s([aeiouAEIOU])/gu, "$1n $2"); const pluralize = (singular, num = 2, plural) => { @@ -338,7 +413,8 @@ const pad = (text, minLength, delim = " ") => { } return str; }; -const toKey = (text) => (text ?? "").toLowerCase().replace(/ /g, "-").replace(/default/, "DEFAULT"); +const toKey = (text) => (text ?? "").toLowerCase().replace(/ /g, "-").replace(/default/, "DEFAULT"); +// #region ========== Numbers: Formatting Numbers Into Strings =========== ~ const signNum = (num, delim = "", zeroSign = "+") => { let sign; const parsedNum = pFloat(num); @@ -369,7 +445,9 @@ const padNum = (num, numDecDigits, includePlus = false) => { } return `${prefix}${leftDigits}.${"0".repeat(numDecDigits)}`; }; -const stringifyNum = (num) => { +const stringifyNum = (num) => { + // Can take string representations of numbers, either in standard or scientific/engineering notation. + // Returns a string representation of the number in standard notation. if (pFloat(num) === 0) { return "0"; } @@ -408,7 +486,8 @@ const stringifyNum = (num) => { } return `${num}`; }; -const verbalizeNum = (num) => { +const verbalizeNum = (num) => { + // Converts a float with absolute magnitude <= 9.99e303 into words. num = stringifyNum(num); const getTier = (trioNum) => { if (trioNum < _numberWords.tiers.length) { @@ -507,8 +586,10 @@ const romanizeNum = (num, isUsingGroupedChars = true) => { return isUsingGroupedChars ? romanNum.replace(/ⅩⅠ/gu, "Ⅺ").replace(/ⅩⅡ/gu, "Ⅻ") : romanNum; -}; - +}; +// #endregion _______ Numbers _______ +// #endregion ░░░░[Formatting]░░░░ +// #region ░░░░░░░[Content]░░░░ Lorem Ipsum, Random Content Generation, Randum UUID ░░░░░░░ ~ const loremIpsum = (numWords = 200) => { const lrWordList = _loremIpsumText.split(/\n?\s+/g); const words = [...lrWordList[randNum(0, lrWordList.length - 1)]]; @@ -529,12 +610,19 @@ const getUID = (id) => { eLog.log(`UUIDify(${id}) --> [${uuid}, ${indexNum}]`); Object.assign(globalThis, { UUIDLOG }); return uuid; -}; +}; +// #endregion ░░░░[Content]░░░░ +// #endregion ▄▄▄▄▄ STRINGS ▄▄▄▄▄ +/*~ @@DOUBLE-BLANK@@ ~*/ +// #region ████████ SEARCHING: Searching Various Data Types w/ Fuzzy Matching ████████ ~ const fuzzyMatch = (val1, val2) => { const [str1, str2] = [val1, val2].map((val) => lCase(String(val).replace(/[^a-zA-Z0-9.+-]/g, "").trim())); return str1.length > 0 && str1 === str2; }; -const isIn = (needle, haystack = [], fuzziness = 0) => { +const isIn = (needle, haystack = [], fuzziness = 0) => { + // Looks for needle in haystack using fuzzy matching, then returns value as it appears in haystack. + /*~ @@DOUBLE-BLANK@@ ~*/ + // STEP ONE: POPULATE SEARCH TESTS ACCORDING TO FUZZINESS SETTING const SearchTests = [ (ndl, item) => new RegExp(`^${ndl}$`, "gu").test(`${item}`), (ndl, item) => new RegExp(`^${ndl}$`, "gui").test(`${item}`) @@ -551,10 +639,12 @@ const isIn = (needle, haystack = [], fuzziness = 0) => { SearchTests.push(...fuzzyTests .map((func) => (ndl, item) => func(`${ndl}`.replace(/\W/g, ""), `${item}`.replace(/\W/gu, "")))); if (fuzziness >= 3) { - SearchTests.push(() => false); + SearchTests.push(() => false); // Have to implement Fuse matching } } - } + } + /*~ @@DOUBLE-BLANK@@ ~*/ + // STEP TWO: PARSE NEEDLE & CONSTRUCT SEARCHABLE HAYSTACK. const searchNeedle = `${needle}`; const searchStack = (() => { if (isArray(haystack)) { @@ -572,7 +662,9 @@ const isIn = (needle, haystack = [], fuzziness = 0) => { })(); if (!isArray(searchStack)) { return false; - } + } + /*~ @@DOUBLE-BLANK@@ ~*/ + // STEP THREE: SEARCH HAY FOR NEEDLE USING PROGRESSIVELY MORE FUZZY SEARCH TESTS let matchIndex = -1; while (!isPosInt(matchIndex)) { const testFunc = SearchTests.shift(); @@ -586,8 +678,10 @@ const isIn = (needle, haystack = [], fuzziness = 0) => { } return false; }; -const isInExact = (needle, haystack) => isIn(needle, haystack, 0); - +const isInExact = (needle, haystack) => isIn(needle, haystack, 0); +// #endregion ▄▄▄▄▄ SEARCHING ▄▄▄▄▄ +/*~ @@DOUBLE-BLANK@@ ~*/ +// #region ████████ NUMBERS: Number Casting, Mathematics, Conversion ████████ ~ const randNum = (min, max, snap = 0) => gsap.utils.random(min, max, snap); const randInt = (min, max) => randNum(min, max, 1); const coinFlip = () => randNum(0, 1, 1) === 1; @@ -596,7 +690,8 @@ const clampNum = (num, [min = 0, max = Infinity] = []) => gsap.utils.clamp(min, const cycleAngle = (angle, range = [0, 360]) => cycleNum(angle, range); const roundNum = (num, sigDigits = 0) => (sigDigits === 0 ? pInt(num) : pFloat(num, sigDigits)); const sum = (...nums) => Object.values(nums.flat()).reduce((num, tot) => tot + num, 0); -const average = (...nums) => sum(...nums) / nums.flat().length; +const average = (...nums) => sum(...nums) / nums.flat().length; +// #region ░░░░░░░[Positioning]░░░░ Relationships On 2D Cartesian Plane ░░░░░░░ ~ const getDistance = ({ x: x1, y: y1 }, { x: x2, y: y2 }) => (((x1 - x2) ** 2) + ((y1 - y2) ** 2)) ** 0.5; const getAngle = ({ x: x1, y: y1 }, { x: x2, y: y2 }, { x: xO, y: yO } = { x: 0, y: 0 }, range = [0, 360]) => { x1 -= xO; @@ -605,7 +700,11 @@ const getAngle = ({ x: x1, y: y1 }, { x: x2, y: y2 }, { x: xO, y: yO } = { x: 0, y2 -= yO; return cycleAngle(radToDeg(Math.atan2(y2 - y1, x2 - x1)), range); }; -const getAngleDelta = (angleStart, angleEnd, range = [0, 360]) => cycleAngle(angleEnd - angleStart, range); +const getAngleDelta = (angleStart, angleEnd, range = [0, 360]) => cycleAngle(angleEnd - angleStart, range); +// #endregion ░░░░[Positioning]░░░░ +// #endregion ▄▄▄▄▄ NUMBERS ▄▄▄▄▄ +/*~ @@DOUBLE-BLANK@@ ~*/ +// #region ████████ ARRAYS: Array Manipulation ████████ ~ const randElem = (array) => gsap.utils.random(array); const randIndex = (array) => randInt(0, array.length - 1); const makeIntRange = (min, max) => { @@ -615,7 +714,9 @@ const makeIntRange = (min, max) => { } return intRange; }; -const makeCycler = (array, index = 0) => { +const makeCycler = (array, index = 0) => { + // Given an array and a starting index, returns a generator function that can be used + // to iterate over the array indefinitely, or wrap out-of-bounds index values const wrapper = gsap.utils.wrap(array); index--; return (function* () { @@ -624,11 +725,19 @@ const makeCycler = (array, index = 0) => { yield wrapper(index); } })(); -}; +}; +/*~ @@DOUBLE-BLANK@@ ~*/ +/** + * Returns the last element of an array, or the last value of an object literal. + * + * @param {Index} array An array or object literal + * @returns {Type|undefined} The last element, or undefined if empty. + */ function getLast(array) { array = Object.values(array); return array.length === 0 ? undefined : array[array.length - 1]; -} +} +// Const getLast = (array: Type[]): typeof array extends [] ? undefined : Type => ; const unique = (array) => { const returnArray = []; array.forEach((item) => { if (!returnArray.includes(item)) { @@ -661,21 +770,42 @@ const sample = (array, numElems = 1, isUniqueOnly = true, uniqueTestFunc = (e, a } return elems; }; -const removeFirst = (array, element) => array.splice(array.findIndex((v) => v === element)); -function pullElement(array, checkFunc) { - let testFunction; +const removeFirst = (array, element) => array.splice(array.findIndex((v) => v === element)); +/*~ @@DOUBLE-BLANK@@ ~*/ +/** + * This function removes and returns the first element in an array that equals the provided value + * or satisfies the provided testing function. + * If no elements satisfy the testing function, the function will return undefined. + * + * @param {T[]} array The array to be searched. + * @param {(T|((_v: T, _i?: number, _a?: T[]) => boolean))} checkFunc The testing function or value to be searched for. + * @returns {T | undefined} The first element in the array that passes the test. + * If no elements pass the test, return undefined. + */ +function pullElement(array, checkFunc) { + // Define the test function + let testFunction; + /*~ @@DOUBLE-BLANK@@ ~*/ + // If checkFunc is not a function, create a function that checks for equality with checkFunc if (typeof checkFunc !== "function") { testFunction = (_v) => _v === checkFunc; } else { testFunction = checkFunc; - } - const index = array.findIndex((v, i, a) => testFunction(v, i, a)); + } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Find the index of the first element that passes the test + const index = array.findIndex((v, i, a) => testFunction(v, i, a)); + /*~ @@DOUBLE-BLANK@@ ~*/ + // If no element passes the test, return undefined if (index === -1) { return undefined; - } + } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Remove the element from the array and return it return array.splice(index, 1).pop(); -} +} +/*~ @@DOUBLE-BLANK@@ ~*/ const pullIndex = (array, index) => pullElement(array, (_, i) => i === index); const subGroup = (array, groupSize) => { const subArrays = []; @@ -691,16 +821,27 @@ const subGroup = (array, groupSize) => { }; const shuffle = (array) => { let currentIndex = array.length; - let randomIndex; - while (currentIndex !== 0) { + let randomIndex; + /*~ @@DOUBLE-BLANK@@ ~*/ + // While there remain elements to shuffle. + while (currentIndex !== 0) { + /*~ @@DOUBLE-BLANK@@ ~*/ + // Pick a remaining element. randomIndex = Math.floor(Math.random() * currentIndex); - currentIndex--; + currentIndex--; + /*~ @@DOUBLE-BLANK@@ ~*/ + // And swap it with the current element. [array[currentIndex], array[randomIndex]] = [ array[randomIndex], array[currentIndex] ]; - } + } + /*~ @@DOUBLE-BLANK@@ ~*/ return array; -}; +}; +// #endregion ▄▄▄▄▄ ARRAYS ▄▄▄▄▄ +/*~ @@DOUBLE-BLANK@@ ~*/ +// #region ████████ OBJECTS: Manipulation of Simple Key/Val Objects ████████ ~ +/*~ @@DOUBLE-BLANK@@ ~*/ const checkVal = ({ k, v }, checkTest) => { if (typeof checkTest === "function") { if (isDefined(v)) { @@ -713,6 +854,12 @@ const checkVal = ({ k, v }, checkTest) => { } return (new RegExp(checkTest)).test(`${v}`); }; +/** + * Given an array or list and a search function, will remove the first matching element and return it. + * @param {Index} obj The array or list to be searched. + * @param {testFunc | number | string} checkTest The search function. + * @returns {unknown | false} - The removed element or false if no element was found. + */ const remove = (obj, checkTest) => { if (isArray(obj)) { const index = obj.findIndex((v) => checkVal({ v }, checkTest)); @@ -727,7 +874,14 @@ const remove = (obj, checkTest) => { } } return false; -}; +}; +/*~ @@DOUBLE-BLANK@@ ~*/ +/** + * Removes an element from an array at a given index and returns it. + * @param {unknown[]} array The array to remove the element from. + * @param {number} index The index of the element to remove. + * @returns {unknown} - The removed element. + */ const removeElementFromArray = (array, index) => { let remVal; for (let i = 0; i <= array.length; i++) { @@ -739,13 +893,22 @@ const removeElementFromArray = (array, index) => { } } return remVal; -}; +}; +/*~ @@DOUBLE-BLANK@@ ~*/ +/** + * Removes an element from a list at a given key and returns it. + * @param {List} list The list to remove the element from. + * @param {string} key The key of the element to remove. + * @returns {unknown} - The removed element. + */ const removeElementFromList = (list, key) => { const remVal = list[key]; delete list[key]; return remVal; }; -const replace = (obj, checkTest, repVal) => { +const replace = (obj, checkTest, repVal) => { + // As remove, except instead replaces the element with the provided value. + // Returns true/false to indicate whether the replace action succeeded. let repKey; if (isList(obj)) { [repKey] = Object.entries(obj).find((v) => checkVal({ v }, checkTest)) || [false]; @@ -762,14 +925,25 @@ const replace = (obj, checkTest, repVal) => { if (typeof repKey !== "number") { repKey = `${repKey}`; } - if (typeof repVal === "function") { + if (typeof repVal === "function") { + // @ts-expect-error Need to figure out how to properly define testFunc (keyFunc/valFunc types?) obj[repKey] = repVal(obj[repKey], repKey); } - else { + else { + // @ts-expect-error Need to figure out how to properly define testFunc (keyFunc/valFunc types?) obj[repKey] = repVal; } return true; }; +/** + * Cleans an object or value by removing specified values recursively. + * + * @template T - The type of the input object or value. + * @param {T} data The object or value to be cleaned. + * @param {Array} [remVals] An array of values to be removed during the cleaning process. + * @returns {T | Partial | "KILL"} - The cleaned version of the input object or value. + * If marked for removal, returns "KILL". + */ const objClean = (data, remVals = [undefined, null, "", {}, []]) => { const remStrings = remVals.map((rVal) => JSON.stringify(rVal)); if (remStrings.includes(JSON.stringify(data)) || remVals.includes(data)) { @@ -787,7 +961,13 @@ const objClean = (data, remVals = [undefined, null, "", {}, []]) => { return newData.length ? Object.fromEntries(newData) : "KILL"; } return data; -}; +}; +/*~ @@DOUBLE-BLANK@@ ~*/ +/** + * + * @param items + * @param key + */ export function toDict(items, key) { const dict = {}; const mappedItems = items @@ -806,9 +986,16 @@ export function toDict(items, key) { newKey = indexString(newKey); } dict[newKey] = iData; - }); - return dict; - function indexString(str) { + }); + // @ts-expect-error Oh it definitely does. + return dict; + /*~ @@DOUBLE-BLANK@@ ~*/ + /** + * Given a string that could have an index suffix, returns the string with + * the suffix incremented by one, or set to one if no suffix exists. + * @param {string} str + */ + function indexString(str) { if (/_\d+$/.test(str)) { const [curIndex, ...subStr] = [...str.split(/_/)].reverse(); return [ @@ -818,50 +1005,91 @@ export function toDict(items, key) { } return `${str}_1`; } -} +} +// Given an object and a predicate function, returns array of two objects: +// one with entries that pass, one with entries that fail. const partition = (obj, predicate = () => true) => [ objFilter(obj, predicate), objFilter(obj, (v, k) => !predicate(v, k)) ]; +/** + * An object-equivalent Array.map() function, which accepts mapping functions to transform both keys and values. + * If only one function is provided, it's assumed to be mapping the values and will receive (v, k) args. + * @param {Index} obj + * @param {mapFunc|false} keyFunc + * @param {mapFunc} [valFunc] + */ function objMap(obj, keyFunc, valFunc) { // let valFuncTyped = valFunc; - let keyFuncTyped = keyFunc; + let keyFuncTyped = keyFunc; + /*~ @@DOUBLE-BLANK@@ ~*/ if (!valFuncTyped) { valFuncTyped = keyFunc; keyFuncTyped = false; } if (!keyFuncTyped) { keyFuncTyped = ((k) => k); - } + } + /*~ @@DOUBLE-BLANK@@ ~*/ if (isArray(obj)) { return obj.map(valFuncTyped); - } + } + /*~ @@DOUBLE-BLANK@@ ~*/ return Object.fromEntries(Object.entries(obj).map(([key, val]) => { assertNonNullType(valFuncTyped, "function"); return [keyFuncTyped(key, val), valFuncTyped(val, key)]; })); } -const objSize = (obj) => Object.values(obj).filter((val) => val !== undefined && val !== null).length; -function objFindKey(obj, keyFunc, valFunc) { +const objSize = (obj) => Object.values(obj).filter((val) => val !== undefined && val !== null).length; +/*~ @@DOUBLE-BLANK@@ ~*/ +/** + * This function is an object-equivalent of Array.findIndex() function. + * It accepts check functions for both keys and/or values. + * If only one function is provided, it's assumed to be searching via values and will receive (v, k) args. + * + * @param {Type} obj The object to be searched. + * @param {testFunc | testFunc | false} keyFunc The testing function for keys. + * @param {testFunc} valFunc The testing function for values. + * @returns {KeyOf | false} The key of the first entry that passes the test. + * If no entries pass the test, return false. + */ +function objFindKey(obj, keyFunc, valFunc) { + // If valFunc is not provided, assume keyFunc is meant to be valFunc if (!valFunc) { valFunc = keyFunc; keyFunc = false; - } + } + // If keyFunc is not provided, create a function that returns the key if (!keyFunc) { keyFunc = ((k) => k); - } + } + // If obj is an array, find the index of the first element that passes the test if (isArray(obj)) { return obj.findIndex(valFunc); - } + } + // Define the testing functions for keys and values const kFunc = keyFunc || (() => true); - const vFunc = valFunc || (() => true); - const validEntry = Object.entries(obj).find(([k, v]) => kFunc(k, v) && vFunc(v, k)); + const vFunc = valFunc || (() => true); + // Find the first entry that passes the test + const validEntry = Object.entries(obj).find(([k, v]) => kFunc(k, v) && vFunc(v, k)); + // If an entry passes the test, return its key if (validEntry) { return validEntry[0]; - } + } + // If no entries pass the test, return false return false; -} +} +/*~ @@DOUBLE-BLANK@@ ~*/ +/** + * An object-equivalent Array.filter() function, which accepts filter functions for both keys and/or values. + * If only one function is provided, it's assumed to be mapping the values and will receive (v, k) args. + * + * @param {Type} obj The object to be searched. + * @param {testFunc | testFunc | false} keyFunc The testing function for keys. + * @param {testFunc} [valFunc] The testing function for values. + * @returns {Type} The filtered object. + */ const objFilter = (obj, keyFunc, valFunc) => { // if (!valFunc) { @@ -879,15 +1107,18 @@ const objFilter = (obj, keyFunc, valFunc) => { return Object.fromEntries(Object.entries(obj) .filter(([key, val]) => kFunc(key, val) && vFunc(val, key))); }; -const objForEach = (obj, func) => { +const objForEach = (obj, func) => { + // An object-equivalent Array.forEach() function, which accepts one function(val, key) to perform for each member. if (isArray(obj)) { obj.forEach(func); } else { Object.entries(obj).forEach(([key, val]) => func(val, key)); } -}; -const objCompact = (obj, removeWhiteList = [undefined, null]) => objFilter(obj, (val) => !removeWhiteList.includes(val)); +}; +// Prunes an object of given set of values, [undefined, null] default +const objCompact = (obj, removeWhiteList = [undefined, null]) => objFilter(obj, (val) => !removeWhiteList.includes(val)); +/*~ @@DOUBLE-BLANK@@ ~*/ const objClone = (obj, isStrictlySafe = false) => { const cloneArray = (arr) => [...arr]; const cloneObject = (o) => ({ ...o }); @@ -907,44 +1138,83 @@ const objClone = (obj, isStrictlySafe = false) => { } return obj; }; -function objMerge(target, source, { isMutatingOk = false, isStrictlySafe = false, isConcatenatingArrays = true, isReplacingArrays = false } = {}) { - target = isMutatingOk ? target : objClone(target, isStrictlySafe); +/** + * Returns a deep merge of source into target. Does not mutate target unless isMutatingOk = true. + * @param {Tx} target The target object to be merged. + * @param {Ty} source The source object to be merged. + * @param {object} options An object containing various options for the merge operation. + * @param {boolean} options.isMutatingOk + * @param {boolean} options.isStrictlySafe + * @param {boolean} options.isConcatenatingArrays + * @param {boolean} options.isReplacingArrays + * @returns {Tx & Ty} - The merged object. + */ +function objMerge(target, source, { isMutatingOk = false, isStrictlySafe = false, isConcatenatingArrays = true, isReplacingArrays = false } = {}) { + // Clone the target if mutation is not allowed + target = isMutatingOk ? target : objClone(target, isStrictlySafe); + /*~ @@DOUBLE-BLANK@@ ~*/ + // If source is an instance of Application or target is undefined, return source if (source instanceof Application || isUndefined(target)) { return source; - } + } + /*~ @@DOUBLE-BLANK@@ ~*/ + // If source is undefined, return target if (isUndefined(source)) { return target; - } + } + /*~ @@DOUBLE-BLANK@@ ~*/ + // If source is not an index, return target if (!isIndex(source)) { return target; - } + } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Iterate over each entry in the source object for (const [key, val] of Object.entries(source)) { - const targetVal = target[key]; - + const targetVal = target[key]; + /*~ @@DOUBLE-BLANK@@ ~*/ + // If replacing arrays is enabled and both target and source values are + // arrays, replace target value with source value if (isReplacingArrays && isArray(targetVal) && isArray(val)) { target[key] = val; } - else if (isConcatenatingArrays && isArray(targetVal) && isArray(val)) { - + else if (isConcatenatingArrays && isArray(targetVal) && isArray(val)) { + /*~ @@DOUBLE-BLANK@@ ~*/ + // If concatenating arrays is enabled and both target and source values + // are arrays, concatenate source value to target value target[key].push(...val); } - else if (val !== null && typeof val === "object") { + else if (val !== null && typeof val === "object") { + // If source value is an object and not null, merge it into target value if (isUndefined(targetVal) && !(val instanceof Application)) { target[key] = new (Object.getPrototypeOf(val).constructor)(); } target[key] = objMerge(target[key], val, { isMutatingOk: true, isStrictlySafe }); } - else { + else { + // For all other cases, assign source value to target target[key] = val; } - } + } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Return the merged target return target; } +/** + * Deep-compares two objects and returns an object containing only the keys and values + * in the second object that differ from the first. + * If the second object is missing a key or value contained in the first, it sets the + * value in the returned object to null, and prefixes the key with "-=". + * @param {Tx} obj1 The first object to be compared. + * @param {Ty} obj2 The second object to be compared. + * @returns {Record} - An object containing the differences between the two input objects. + */ function objDiff(obj1, obj2) { const diff = {}; const bothObj1AndObj2Keys = Object.keys(obj2).filter((key) => Object.hasOwn(obj2, key) && Object.hasOwn(obj1, key)); - const onlyObj2Keys = Object.keys(obj2).filter((key) => Object.hasOwn(obj2, key) && !Object.hasOwn(obj1, key)); - for (const key of bothObj1AndObj2Keys) { + const onlyObj2Keys = Object.keys(obj2).filter((key) => Object.hasOwn(obj2, key) && !Object.hasOwn(obj1, key)); + /*~ @@DOUBLE-BLANK@@ ~*/ + for (const key of bothObj1AndObj2Keys) { + // If both values are non-array objects, recursively compare them if (typeof obj1[key] === "object" && typeof obj2[key] === "object" && !Array.isArray(obj1[key]) && !Array.isArray(obj2[key])) { const nestedDiff = objDiff(obj1[key], obj2[key]); if (Object.keys(nestedDiff).length > 0) { @@ -954,19 +1224,24 @@ function objDiff(obj1, obj2) { else if (Array.isArray(obj1[key]) && Array.isArray(obj2[key])) { const array1 = obj1[key]; const array2 = obj2[key]; - if (array1.toString() !== array2.toString()) { + if (array1.toString() !== array2.toString()) { + // If both values are arrays and they are not equal, add the second array to the diff diff[key] = obj2[key]; } } - else if (obj1[key] !== obj2[key]) { + else if (obj1[key] !== obj2[key]) { + // If the values are not equal, add the second value to the diff diff[key] = obj2[key]; } - } - for (const key of onlyObj2Keys) { + } + /*~ @@DOUBLE-BLANK@@ ~*/ + for (const key of onlyObj2Keys) { + // If the second object has a key that the first does not, add it to the diff with a "-=" prefix diff[`-=${key}`] = obj2[key]; } return diff; -} +} +/*~ @@DOUBLE-BLANK@@ ~*/ const objExpand = (obj) => { const expObj = {}; for (const [key, val] of Object.entries(obj)) { @@ -977,8 +1252,13 @@ const objExpand = (obj) => { else { setProperty(expObj, key, val); } - } - function arrayify(o) { + } + // Iterate through expanded Object, converting object literals to arrays where it makes sense + /** + * + * @param o + */ + function arrayify(o) { if (isList(o)) { if (/^\d+$/.test(Object.keys(o).join(""))) { return Object.values(o).map(arrayify); @@ -989,7 +1269,8 @@ const objExpand = (obj) => { return o.map(arrayify); } return o; - } + } + /*~ @@DOUBLE-BLANK@@ ~*/ return arrayify(expObj); }; const objFlatten = (obj) => { @@ -1006,23 +1287,44 @@ const objFlatten = (obj) => { } return flatObj; }; -function objNullify(obj) { +/** + * + * @param obj + */ +function objNullify(obj) { + // Check if the input is an object or an array if (!isIndex(obj)) { return obj; - } + } + /*~ @@DOUBLE-BLANK@@ ~*/ + // If the input is an array, nullify all its elements if (Array.isArray(obj)) { obj.forEach((_, i) => { obj[i] = null; }); return obj; - } + } + /*~ @@DOUBLE-BLANK@@ ~*/ + // If the input is an object, nullify all its properties Object.keys(obj).forEach((objKey) => { obj[objKey] = null; - }); + }); + /*~ @@DOUBLE-BLANK@@ ~*/ return obj; -} +} +/*~ @@DOUBLE-BLANK@@ ~*/ +/** + * This function freezes the properties of an object based on a provided schema or keys. + * If a property is missing, it throws an error. + * @param {Partial} data The object whose properties are to be frozen. + * @param {...Array | [T]} keysOrSchema The keys or schema to freeze the properties. + * @returns {T} - The object with frozen properties. + * @throws {Error} - Throws an error if a property is missing. + */ function objFreezeProps(data, ...keysOrSchema) { - const firstArg = keysOrSchema[0]; + const firstArg = keysOrSchema[0]; + /*~ @@DOUBLE-BLANK@@ ~*/ + // If the first argument is an object and not an array, treat it as a schema if (firstArg instanceof Object && !Array.isArray(firstArg)) { const schema = firstArg; for (const key in schema) { @@ -1031,31 +1333,47 @@ function objFreezeProps(data, ...keysOrSchema) { } } } - else { + else { + // If the first argument is not an object or is an array, treat it as an array of keys for (const key of keysOrSchema) { if (data[key] === undefined) { throw new Error(`Missing value for ${String(key)}`); } } - } + } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Return the data as type T return data; -} - +} +// #endregion ▄▄▄▄▄ OBJECTS ▄▄▄▄▄ +/*~ @@DOUBLE-BLANK@@ ~*/ +// #region ████████ FUNCTIONS: Function Wrapping, Queuing, Manipulation ████████ ~ const getDynamicFunc = (funcName, func, context) => { if (typeof func === "function") { const dFunc = { [funcName](...args) { return func(...args); } }[funcName]; return context ? dFunc.bind(context) : dFunc; } return false; -}; +}; +/*~ @@DOUBLE-BLANK@@ ~*/ const withLog = (fn) => { return (...args) => { console.log(`calling ${fn.name}`); return fn(...args); }; -}; - +}; +// #endregion ▄▄▄▄▄ FUNCTIONS ▄▄▄▄▄ +/*~ @@DOUBLE-BLANK@@ ~*/ +// #region ████████ HTML: Parsing HTML Code, Manipulating DOM Objects ████████ ~ +/*~ @@DOUBLE-BLANK@@ ~*/ +// #region ░░░░░░░[GreenSock]░░░░ Wrappers for GreenSock Functions ░░░░░░░ ~ const set = (targets, vars) => gsap.set(targets, vars); +/** + * + * @param target + * @param property + * @param unit + */ function get(target, property, unit) { if (unit) { const propVal = regExtract(gsap.getProperty(target, property, unit), /[\d.]+/); @@ -1065,9 +1383,12 @@ function get(target, property, unit) { throw new Error(`Unable to extract property '${property}' in '${unit}' units from ${target}`); } return gsap.getProperty(target, property); -} -const getGSAngleDelta = (startAngle, endAngle) => signNum(roundNum(getAngleDelta(startAngle, endAngle), 2)).replace(/^(.)/, "$1="); - +} +/*~ @@DOUBLE-BLANK@@ ~*/ +const getGSAngleDelta = (startAngle, endAngle) => signNum(roundNum(getAngleDelta(startAngle, endAngle), 2)).replace(/^(.)/, "$1="); +// #endregion ░░░░[GreenSock]░░░░ +/*~ @@DOUBLE-BLANK@@ ~*/ +// #region ░░░░░░░[SVG]░░░░ SVG Generation & Manipulation ░░░░░░░ ~ const getRawCirclePath = (r, { x: xO, y: yO } = { x: 0, y: 0 }) => { [r, xO, yO] = [r, xO, yO].map((val) => roundNum(val, 2)); const [b1, b2] = [0.4475 * r, (1 - 0.4475) * r]; @@ -1091,8 +1412,10 @@ const drawCirclePath = (radius, origin) => { }); path.push("z"); return path.join(" "); -}; - +}; +// #endregion ░░░░[SVG]░░░░ +/*~ @@DOUBLE-BLANK@@ ~*/ +// #region ░░░░░░░[Colors]░░░░ Color Manipulation ░░░░░░░ ~ const getColorVals = (red, green, blue, alpha) => { if (isRGBColor(red)) { [red, green, blue, alpha] = red @@ -1130,7 +1453,11 @@ const getRGBString = (red, green, blue, alpha) => { return null; }; const getHEXString = (red, green, blue) => { - function componentToHex(c) { + /** + * + * @param c + */ + function componentToHex(c) { const hex = c.toString(16); return hex.length === 1 ? `0${hex}` : hex; } @@ -1153,20 +1480,27 @@ const getContrastingColor = (...colorVals) => { } return null; }; -const getRandomColor = () => getRGBString(gsap.utils.random(0, 255, 1), gsap.utils.random(0, 255, 1), gsap.utils.random(0, 255, 1)); - +const getRandomColor = () => getRGBString(gsap.utils.random(0, 255, 1), gsap.utils.random(0, 255, 1), gsap.utils.random(0, 255, 1)); +// #endregion ░░░░[Colors]░░░░ +/*~ @@DOUBLE-BLANK@@ ~*/ +// #region ░░░░░░░[DOM]░░░░ DOM Manipulation ░░░░░░░ ~ const getSiblings = (elem) => { - const siblings = []; + const siblings = []; + // If no parent, return no sibling if (!elem.parentNode) { return siblings; - } + } + /*~ @@DOUBLE-BLANK@@ ~*/ Array.from(elem.parentNode.children).forEach((sibling) => { if (sibling !== elem) { siblings.push(sibling); } - }); + }); + /*~ @@DOUBLE-BLANK@@ ~*/ return siblings; -}; +}; +// #endregion ░░░░[DOM]░░░░ +/*~ @@DOUBLE-BLANK@@ ~*/ const escapeHTML = (str) => (typeof str === "string" ? str .replace(/&/g, "&") @@ -1174,11 +1508,16 @@ const escapeHTML = (str) => (typeof str === "string" .replace(/>/g, ">") .replace(/"/g, """) .replace(/[`']/g, "'") - : str); - + : str); +/*~ @@DOUBLE-BLANK@@ ~*/ +// #endregion ▄▄▄▄▄ HTML ▄▄▄▄▄ +/*~ @@DOUBLE-BLANK@@ ~*/ +// #region ████████ ASYNC: Async Functions, Asynchronous Flow Control ████████ ~ const sleep = (duration) => new Promise((resolve) => { setTimeout(resolve, duration >= 100 ? duration : duration * 1000); -}); +}); +// #endregion ▄▄▄▄▄ ASYNC ▄▄▄▄▄ +/*~ @@DOUBLE-BLANK@@ ~*/ const EventHandlers = { onTextInputBlur: async (inst, event) => { const elem = event.target; @@ -1203,7 +1542,8 @@ const EventHandlers = { }, onSelectChange: async (inst, event) => { const elem = event.currentTarget; - const { action, dtype, target, flagTarget } = elem.dataset; + const { action, dtype, target, flagTarget } = elem.dataset; + /*~ @@DOUBLE-BLANK@@ ~*/ if (!action) { throw new Error("Select elements require a data-action attribute."); } @@ -1245,14 +1585,18 @@ const EventHandlers = { } } } -}; +}; +/*~ @@DOUBLE-BLANK@@ ~*/ +// #region ████████ FOUNDRY: Requires Configuration of System ID in constants.ts ████████ ~ +/*~ @@DOUBLE-BLANK@@ ~*/ const isDocID = (docRef, isUUIDok = true) => { return typeof docRef === "string" && (isUUIDok ? /^(.*\.)?[A-Za-z0-9]{16}$/.test(docRef) : /^[A-Za-z0-9]{16}$/.test(docRef)); -}; +}; +/*~ @@DOUBLE-BLANK@@ ~*/ const loc = (locRef, formatDict = {}) => { - if (/[a-z]/.test(locRef)) { + if (/[a-z]/.test(locRef)) { // Reference contains lower-case characters: add system ID namespacing to dot notation locRef = locRef.replace(new RegExp(`^(${C.SYSTEM_ID}.)*`), `${C.SYSTEM_ID}.`); } if (typeof game.i18n.localize(locRef) === "string") { @@ -1262,14 +1606,30 @@ const loc = (locRef, formatDict = {}) => { return game.i18n.format(locRef, formatDict) || game.i18n.localize(locRef) || locRef; } return locRef; -}; +}; +/*~ @@DOUBLE-BLANK@@ ~*/ const getSetting = (setting) => game.settings.get(C.SYSTEM_ID, setting); +/** + * + * @param subFolder + * @param fileName + */ function getTemplatePath(subFolder, fileName) { if (typeof fileName === "string") { return `${C.TEMPLATE_ROOT}/${subFolder}/${fileName.replace(/\..*$/, "")}.hbs`; } return fileName.map((fName) => getTemplatePath(subFolder, fName)); -} +} +/*~ @@DOUBLE-BLANK@@ ~*/ +// DisplayImageSelector: Displays a file selector in tiles mode at the indicated path root. +/** + * + * @param callback + * @param pathRoot + * @param position + * @param position.top + * @param position.left + */ function displayImageSelector(callback, pathRoot = `systems/${C.SYSTEM_ID}/assets`, position = { top: 200, left: 200 }) { const fp = new FilePicker({ type: "image", @@ -1280,51 +1640,89 @@ function displayImageSelector(callback, pathRoot = `systems/${C.SYSTEM_ID}/asset left: position.left ?? 200 + 10 }); return fp.browse(pathRoot); -} -export default { - GMID, getUID, +} +/*~ @@DOUBLE-BLANK@@ ~*/ +// #endregion ▄▄▄▄▄ FOUNDRY ▄▄▄▄▄ +/*~ @@DOUBLE-BLANK@@ ~*/ +export default { + // ████████ GETTERS: Basic Data Lookup & Retrieval ████████ + GMID, getUID, + /*~ @@DOUBLE-BLANK@@ ~*/ + // ████████ TYPES: Type Checking, Validation, Conversion, Casting ████████ isNumber, isNumberString, isBooleanString, isSimpleObj, isList, isArray, isFunc, isInt, isFloat, isPosInt, isIterable, isHTMLCode, isRGBColor, isHexColor, isUndefined, isDefined, isEmpty, hasItems, isInstance, isNullish, areEqual, areFuzzyEqual, pFloat, pInt, radToDeg, degToRad, getKey, - assertNonNullType, - FILTERS, + assertNonNullType, + /*~ @@DOUBLE-BLANK@@ ~*/ + FILTERS, + /*~ @@DOUBLE-BLANK@@ ~*/ + // ████████ REGEXP: Regular Expressions, Replacing, Matching ████████ testRegExp, - regExtract, - - uCase, lCase, sCase, tCase, - unhyphenate, pluralize, oxfordize, ellipsize, pad, + regExtract, + /*~ @@DOUBLE-BLANK@@ ~*/ + // ████████ STRINGS: String Parsing, Manipulation, Conversion ████████ + // ░░░░░░░ Case Conversion ░░░░░░░ + uCase, lCase, sCase, tCase, + // ░░░░░░░ Formatting ░░░░░░░ + /* hyphenate, */ unhyphenate, pluralize, oxfordize, ellipsize, pad, toKey, parseArticles, - signNum, padNum, stringifyNum, verbalizeNum, ordinalizeNum, romanizeNum, - loremIpsum, randString, randWord, - fuzzyMatch, isIn, isInExact, + signNum, padNum, stringifyNum, verbalizeNum, ordinalizeNum, romanizeNum, + // ░░░░░░░ Content ░░░░░░░ + loremIpsum, randString, randWord, + /*~ @@DOUBLE-BLANK@@ ~*/ + // ████████ SEARCHING: Searching Various Data Types w/ Fuzzy Matching ████████ + fuzzyMatch, isIn, isInExact, + /*~ @@DOUBLE-BLANK@@ ~*/ + // ████████ NUMBERS: Number Casting, Mathematics, Conversion ████████ randNum, randInt, coinFlip, cycleNum, cycleAngle, roundNum, clampNum, - sum, average, + sum, average, + // ░░░░░░░ Positioning ░░░░░░░ getDistance, - getAngle, getAngleDelta, + getAngle, getAngleDelta, + /*~ @@DOUBLE-BLANK@@ ~*/ + // ████████ ARRAYS: Array Manipulation ████████ randElem, randIndex, makeIntRange, makeCycler, unique, group, sample, getLast, removeFirst, pullElement, pullIndex, - subGroup, shuffle, + subGroup, shuffle, + /*~ @@DOUBLE-BLANK@@ ~*/ + // ████████ OBJECTS: Manipulation of Simple Key/Val Objects ████████ remove, replace, partition, objClean, objSize, objMap, objFindKey, objFilter, objForEach, objCompact, objClone, objMerge, objDiff, objExpand, objFlatten, objNullify, - objFreezeProps, - getDynamicFunc, withLog, - - gsap, get, set, getGSAngleDelta, - getRawCirclePath, drawCirclePath, - getColorVals, getRGBString, getHEXString, getContrastingColor, getRandomColor, - getSiblings, - escapeHTML, - sleep, - EventHandlers, - isDocID, loc, getSetting, getTemplatePath, displayImageSelector -}; \ No newline at end of file + objFreezeProps, + /*~ @@DOUBLE-BLANK@@ ~*/ + // ████████ FUNCTIONS: Function Wrapping, Queuing, Manipulation ████████ + getDynamicFunc, withLog, + /*~ @@DOUBLE-BLANK@@ ~*/ + // ████████ HTML: Parsing HTML Code, Manipulating DOM Objects ████████ + // ░░░░░░░ GreenSock ░░░░░░░ + gsap, get, set, getGSAngleDelta, + /*~ @@DOUBLE-BLANK@@ ~*/ + getRawCirclePath, drawCirclePath, + /*~ @@DOUBLE-BLANK@@ ~*/ + getColorVals, getRGBString, getHEXString, getContrastingColor, getRandomColor, + /*~ @@DOUBLE-BLANK@@ ~*/ + getSiblings, + /*~ @@DOUBLE-BLANK@@ ~*/ + escapeHTML, + /*~ @@DOUBLE-BLANK@@ ~*/ + // ████████ ASYNC: Async Functions, Asynchronous Flow Control ████████ + sleep, + /*~ @@DOUBLE-BLANK@@ ~*/ + // EVENT HANDLERS + EventHandlers, + // ░░░░░░░ SYSTEM: System-Specific Functions (Requires Configuration of System ID in constants.js) ░░░░░░░ + isDocID, loc, getSetting, getTemplatePath, displayImageSelector + /*~ @@DOUBLE-BLANK@@ ~*/ +}; +// #endregion ▄▄▄▄▄ EXPORTS ▄▄▄▄▄ +/*~ @@DOUBLE-BLANK@@ ~*/ diff --git a/module/data-import/data-import.js b/module/data-import/data-import.js index f76b391b..9bf18863 100644 --- a/module/data-import/data-import.js +++ b/module/data-import/data-import.js @@ -1,13 +1,7 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - import { BladesActorType, BladesItemType } from "../core/constants.js"; import U from "../core/utilities.js"; import { BladesItem } from "../documents/BladesItemProxy.js"; +/*~ @@DOUBLE-BLANK@@ ~*/ const JSONDATA = { FACTIONS: { "the Billhooks": { @@ -3029,7 +3023,9 @@ const JSONDATA = { } } }; +/*~ @@DOUBLE-BLANK@@ ~*/ const problemLog = []; +/*~ @@DOUBLE-BLANK@@ ~*/ function parseColumnItem(item, isFaction = false) { if (isFaction) { const faction = game.actors.getName(item); @@ -3066,7 +3062,9 @@ function formatSplitColumns(elements, isFactionList = false) { "
" ].join(""); } +/*~ @@DOUBLE-BLANK@@ ~*/ const { claim, favoredOperation, contact } = U.group(JSONDATA.CREW_OBJECTS, "type"); +/*~ @@DOUBLE-BLANK@@ ~*/ export const updateClaims = async () => { const errorReport = []; const playbookUpdateData = {}; @@ -3089,11 +3087,14 @@ export const updateClaims = async () => { } playbookUpdateData[playbookName] = playbookData; }); + /*~ @@DOUBLE-BLANK@@ ~*/ await Promise.all(Object.entries(playbookUpdateData).map(async ([playbook, data]) => game.items.getName(playbook) ?.update(data) .then((item) => item?.addTag(playbook)))); + /*~ @@DOUBLE-BLANK@@ ~*/ console.log(errorReport); }; +/*~ @@DOUBLE-BLANK@@ ~*/ export const updateOps = async () => { const errorReport = []; await Promise.all((favoredOperation ?? []).map(async (op) => { @@ -3115,8 +3116,10 @@ export const updateOps = async () => { } return undefined; })); + /*~ @@DOUBLE-BLANK@@ ~*/ console.log(errorReport); }; +/*~ @@DOUBLE-BLANK@@ ~*/ export const updateContacts = async () => { const errorReport = []; await Promise.all((contact ?? []).map(async (ct) => { @@ -3135,8 +3138,10 @@ export const updateContacts = async () => { }); return actor.addTag(playbookObj.name); })); + /*~ @@DOUBLE-BLANK@@ ~*/ console.log(errorReport); }; +/*~ @@DOUBLE-BLANK@@ ~*/ const updateFactionData = async (factionData) => { const faction = game.actors.getName(factionData.name); const updateData = {}; @@ -3196,23 +3201,32 @@ const updateFactionData = async (factionData) => { problemLog.push(`Unable to find faction "${factionData.name}"`); } }; +/*~ @@DOUBLE-BLANK@@ ~*/ export const updateFactions = async () => { await Promise.all(Object.values(JSONDATA.FACTIONS).map(async (factionData) => updateFactionData(factionData))); console.log(problemLog); }; +/*~ @@DOUBLE-BLANK@@ ~*/ export const updateRollMods = async () => { await Promise.all([ ...Object.entries(JSONDATA.ABILITIES.RollMods) .map(async ([abilityName, eData]) => { + // Get ability doc const abilityDoc = game.items.getName(abilityName); if (!abilityDoc) { eLog.error("updateRollMods", `updateRollMods: Ability ${abilityName} Not Found.`); return undefined; } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Get active effects on abilityDoc const abilityEffects = Array.from(abilityDoc.effects ?? []); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Separate out 'APPLYTOMEMBERS' and 'APPLYTOCOHORTS' ActiveEffects const toMemberEffects = abilityEffects.filter((effect) => effect.changes.some((change) => change.key === "APPLYTOMEMBERS")); const toCohortEffects = abilityEffects.filter((effect) => effect.changes.some((change) => change.key === "APPLYTOCOHORTS")); const standardEffects = abilityEffects.filter((effect) => effect.changes.every((change) => !["APPLYTOMEMBERS", "APPLYTOCOHORTS"].includes(change.key))); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Confirm eData.isMember and eData.isCohort are consistent across all changes. const testChange = eData[0]; if ((testChange.isMember && eData.some((change) => !change.isMember)) || (!testChange.isMember && eData.some((change) => change.isMember))) { @@ -3222,10 +3236,14 @@ export const updateRollMods = async () => { || (!testChange.isCohort && eData.some((change) => change.isCohort))) { return eLog.error("updateRollMods", `updateRollMods: Ability ${abilityName} has inconsistent 'isCohort' entries.`); } + /*~ @@DOUBLE-BLANK@@ ~*/ + // If eData.isMember or eData.isCohort, first see if there already is such an effect on the doc if (testChange.isMember) { if (toMemberEffects.length > 1) { return eLog.error("updateRollMods", `updateRollMods: Ability ${abilityName} Has Multiple 'APPLYTOMEMBERS' Active Effects`); } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Initialize new effect data const effectData = { name: abilityName, icon: abilityDoc.img ?? "", @@ -3234,6 +3252,8 @@ export const updateRollMods = async () => { return change; }) }; + /*~ @@DOUBLE-BLANK@@ ~*/ + // Derive new effect data from existing effect, if any, then delete existing effect if (toMemberEffects.length === 1) { const abilityEffect = toMemberEffects[0]; effectData.name = abilityEffect.name ?? effectData.name; @@ -3249,6 +3269,8 @@ export const updateRollMods = async () => { value: `${abilityName.replace(/\s*\([^()]*? (Ability|Upgrade)\)\s*$/, "")} (Scoundrel Ability)` }); } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Create new ActiveEffect return abilityDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); } else if (testChange.isCohort) { @@ -3256,6 +3278,8 @@ export const updateRollMods = async () => { eLog.error("updateRollMods", `updateRollMods: Ability ${abilityName} Has Multiple 'APPLYTOCOHORTS' Active Effects`); return undefined; } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Initialize new effect data const effectData = { name: abilityName, icon: abilityDoc.img ?? "", @@ -3264,6 +3288,8 @@ export const updateRollMods = async () => { return change; }) }; + /*~ @@DOUBLE-BLANK@@ ~*/ + // Derive new effect data from existing effect, if any, then delete existing effect if (toCohortEffects.length === 1) { const abilityEffect = toCohortEffects[0]; effectData.name = abilityEffect.name ?? effectData.name; @@ -3279,6 +3305,8 @@ export const updateRollMods = async () => { value: `${abilityName.replace(/\s*\([^()]*? (Ability|Upgrade)\)\s*$/, "")} (Scoundrel Ability)` }); } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Create new ActiveEffect return abilityDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); } else { @@ -3286,11 +3314,15 @@ export const updateRollMods = async () => { eLog.error("updateRollMods", `updateRollMods: Ability ${abilityName} Has Multiple Active Effects`); return undefined; } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Initialize new effect data const effectData = { name: abilityName, icon: abilityDoc.img ?? "", changes: eData }; + /*~ @@DOUBLE-BLANK@@ ~*/ + // Derive new effect data from existing effect, if any, then delete existing effect if (standardEffects.length === 1) { const abilityEffect = standardEffects[0]; effectData.name = abilityEffect.name ?? effectData.name; @@ -3298,20 +3330,29 @@ export const updateRollMods = async () => { effectData.changes.unshift(...abilityEffect.changes.filter((change) => change.key !== "system.roll_mods")); await abilityEffect.delete(); } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Create new ActiveEffect return abilityDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); } }), ...Object.entries(JSONDATA.CREW_ABILITIES.RollMods) .map(async ([aName, eData]) => { + // Get crew ability doc const crewAbilityDoc = game.items.getName(aName); if (!crewAbilityDoc) { eLog.error("updateRollMods", `updateRollMods: Crew Ability ${aName} Not Found.`); return undefined; } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Get active effects on crewAbilityDoc const abilityEffects = Array.from(crewAbilityDoc.effects ?? []); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Separate out 'APPLYTOMEMBERS' and 'APPLYTOCOHORTS' ActiveEffects const toMemberEffects = abilityEffects.filter((effect) => effect.changes.some((change) => change.key === "APPLYTOMEMBERS")); const toCohortEffects = abilityEffects.filter((effect) => effect.changes.some((change) => change.key === "APPLYTOCOHORTS")); const standardEffects = abilityEffects.filter((effect) => effect.changes.every((change) => !["APPLYTOMEMBERS", "APPLYTOCOHORTS"].includes(change.key))); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Confirm eData.isMember and eData.isCohort are consistent across all changes. const testChange = eData[0]; if ((testChange.isMember && eData.some((change) => !change.isMember)) || (!testChange.isMember && eData.some((change) => change.isMember))) { @@ -3321,10 +3362,14 @@ export const updateRollMods = async () => { || (!testChange.isCohort && eData.some((change) => change.isCohort))) { return eLog.error("updateRollMods", `updateRollMods: Crew Ability ${aName} has inconsistent 'isCohort' entries.`); } + /*~ @@DOUBLE-BLANK@@ ~*/ + // If eData.isMember or eData.isCohort, first see if there already is such an effect on the doc if (testChange.isMember) { if (toMemberEffects.length > 1) { return eLog.error("updateRollMods", `updateRollMods: Crew Ability ${aName} Has Multiple 'APPLYTOMEMBERS' Active Effects`); } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Initialize new effect data const effectData = { name: aName, icon: crewAbilityDoc.img ?? "", @@ -3333,6 +3378,8 @@ export const updateRollMods = async () => { return change; }) }; + /*~ @@DOUBLE-BLANK@@ ~*/ + // Derive new effect data from existing effect, if any, then delete existing effect if (toMemberEffects.length === 1) { const abilityEffect = toMemberEffects[0]; effectData.name = abilityEffect.name ?? effectData.name; @@ -3348,6 +3395,8 @@ export const updateRollMods = async () => { value: `${aName.replace(/\s*\([^()]*? (Ability|Upgrade)\)\s*$/, "")} (Crew Ability)` }); } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Create new ActiveEffect return crewAbilityDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); } else if (testChange.isCohort) { @@ -3355,6 +3404,8 @@ export const updateRollMods = async () => { eLog.error("updateRollMods", `updateRollMods: Crew Ability ${aName} Has Multiple 'APPLYTOCOHORTS' Active Effects`); return undefined; } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Initialize new effect data const effectData = { name: aName, icon: crewAbilityDoc.img ?? "", @@ -3363,6 +3414,8 @@ export const updateRollMods = async () => { return change; }) }; + /*~ @@DOUBLE-BLANK@@ ~*/ + // Derive new effect data from existing effect, if any, then delete existing effect if (toCohortEffects.length === 1) { const abilityEffect = toCohortEffects[0]; effectData.name = abilityEffect.name ?? effectData.name; @@ -3378,6 +3431,8 @@ export const updateRollMods = async () => { value: `${aName.replace(/\s*\([^()]*? (Ability|Upgrade)\)\s*$/, "")} (Crew Ability)` }); } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Create new ActiveEffect return crewAbilityDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); } else { @@ -3385,11 +3440,15 @@ export const updateRollMods = async () => { eLog.error("updateRollMods", `updateRollMods: Crew Ability ${aName} Has Multiple Active Effects`); return undefined; } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Initialize new effect data const effectData = { name: aName, icon: crewAbilityDoc.img ?? "", changes: eData }; + /*~ @@DOUBLE-BLANK@@ ~*/ + // Derive new effect data from existing effect, if any, then delete existing effect if (standardEffects.length === 1) { const abilityEffect = standardEffects[0]; effectData.name = abilityEffect.name ?? effectData.name; @@ -3397,20 +3456,29 @@ export const updateRollMods = async () => { effectData.changes.unshift(...abilityEffect.changes.filter((change) => change.key !== "system.roll_mods")); await abilityEffect.delete(); } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Create new ActiveEffect return crewAbilityDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); } }), ...Object.entries(JSONDATA.CREW_UPGRADES.RollMods) .map(async ([aName, eData]) => { + // Get crew upgrade doc const crewUpgradeDoc = game.items.getName(aName); if (!crewUpgradeDoc) { eLog.error("updateRollMods", `updateRollMods: Crew Upgrade ${aName} Not Found.`); return undefined; } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Get active effects on crewUpgradeDoc const abilityEffects = Array.from(crewUpgradeDoc.effects ?? []); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Separate out 'APPLYTOMEMBERS' and 'APPLYTOCOHORTS' ActiveEffects const toMemberEffects = abilityEffects.filter((effect) => effect.changes.some((change) => change.key === "APPLYTOMEMBERS")); const toCohortEffects = abilityEffects.filter((effect) => effect.changes.some((change) => change.key === "APPLYTOCOHORTS")); const standardEffects = abilityEffects.filter((effect) => effect.changes.every((change) => !["APPLYTOMEMBERS", "APPLYTOCOHORTS"].includes(change.key))); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Confirm eData.isMember and eData.isCohort are consistent across all changes. const testChange = eData[0]; if ((testChange.isMember && eData.some((change) => !change.isMember)) || (!testChange.isMember && eData.some((change) => change.isMember))) { @@ -3420,10 +3488,14 @@ export const updateRollMods = async () => { || (!testChange.isCohort && eData.some((change) => change.isCohort))) { return eLog.error("updateRollMods", `updateRollMods: Crew Upgrade ${aName} has inconsistent 'isCohort' entries.`); } + /*~ @@DOUBLE-BLANK@@ ~*/ + // If eData.isMember or eData.isCohort, first see if there already is such an effect on the doc if (testChange.isMember) { if (toMemberEffects.length > 1) { return eLog.error("updateRollMods", `updateRollMods: Crew Upgrade ${aName} Has Multiple 'APPLYTOMEMBERS' Active Effects`); } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Initialize new effect data const effectData = { name: aName, icon: crewUpgradeDoc.img ?? "", @@ -3432,6 +3504,8 @@ export const updateRollMods = async () => { return change; }) }; + /*~ @@DOUBLE-BLANK@@ ~*/ + // Derive new effect data from existing effect, if any, then delete existing effect if (toMemberEffects.length === 1) { const abilityEffect = toMemberEffects[0]; effectData.name = abilityEffect.name ?? effectData.name; @@ -3447,6 +3521,8 @@ export const updateRollMods = async () => { value: `${aName.replace(/\s*\([^()]*? (Ability|Upgrade)\)\s*$/, "")} (Crew Upgrade)` }); } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Create new ActiveEffect return crewUpgradeDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); } else if (testChange.isCohort) { @@ -3454,6 +3530,8 @@ export const updateRollMods = async () => { eLog.error("updateRollMods", `updateRollMods: Crew Upgrade ${aName} Has Multiple 'APPLYTOCOHORTS' Active Effects`); return undefined; } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Initialize new effect data const effectData = { name: aName, icon: crewUpgradeDoc.img ?? "", @@ -3462,6 +3540,8 @@ export const updateRollMods = async () => { return change; }) }; + /*~ @@DOUBLE-BLANK@@ ~*/ + // Derive new effect data from existing effect, if any, then delete existing effect if (toCohortEffects.length === 1) { const abilityEffect = toCohortEffects[0]; effectData.name = abilityEffect.name ?? effectData.name; @@ -3477,6 +3557,8 @@ export const updateRollMods = async () => { value: `${aName.replace(/\s*\([^()]*? (Ability|Upgrade)\)\s*$/, "")} (Crew Upgrade)` }); } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Create new ActiveEffect return crewUpgradeDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); } else { @@ -3484,11 +3566,15 @@ export const updateRollMods = async () => { eLog.error("updateRollMods", `updateRollMods: Crew Upgrade ${aName} Has Multiple Active Effects`); return undefined; } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Initialize new effect data const effectData = { name: aName, icon: crewUpgradeDoc.img ?? "", changes: eData }; + /*~ @@DOUBLE-BLANK@@ ~*/ + // Derive new effect data from existing effect, if any, then delete existing effect if (standardEffects.length === 1) { const abilityEffect = standardEffects[0]; effectData.name = abilityEffect.name ?? effectData.name; @@ -3496,11 +3582,14 @@ export const updateRollMods = async () => { effectData.changes.unshift(...abilityEffect.changes.filter((change) => change.key !== "system.roll_mods")); await abilityEffect.delete(); } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Create new ActiveEffect return crewUpgradeDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); } }) ]); }; +/*~ @@DOUBLE-BLANK@@ ~*/ export const updateDescriptions = async () => { return Promise.all(Object.entries({ ...JSONDATA.ABILITIES.Descriptions, @@ -3513,6 +3602,8 @@ export const updateDescriptions = async () => { eLog.error("applyRollEffects", `ApplyDescriptions: Item Doc ${aName} Not Found.`); return undefined; } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Update system.notes return itemDoc.update({ "system.notes": desc }); })); -}; \ No newline at end of file +}; diff --git a/module/documents/BladesActorProxy.js b/module/documents/BladesActorProxy.js index aa3011be..be6f959a 100644 --- a/module/documents/BladesActorProxy.js +++ b/module/documents/BladesActorProxy.js @@ -1,10 +1,3 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - import U from "../core/utilities.js"; import { BladesActorType } from "../core/constants.js"; import BladesActor from "../BladesActor.js"; @@ -12,13 +5,18 @@ import BladesPC from "./actors/BladesPC.js"; import BladesNPC from "./actors/BladesNPC.js"; import BladesFaction from "./actors/BladesFaction.js"; import BladesCrew from "./actors/BladesCrew.js"; +/*~ @@DOUBLE-BLANK@@ ~*/ const ActorsMap = { [BladesActorType.pc]: BladesPC, [BladesActorType.npc]: BladesNPC, [BladesActorType.faction]: BladesFaction, [BladesActorType.crew]: BladesCrew + /*~ @@DOUBLE-BLANK@@ ~*/ }; +/*~ @@DOUBLE-BLANK@@ ~*/ +// eslint-disable-next-line @typescript-eslint/no-empty-function const BladesActorProxy = new Proxy(function () { }, { + /*~ @@DOUBLE-BLANK@@ ~*/ construct(_, args) { const [{ type }] = args; if (!type) { @@ -30,6 +28,7 @@ const BladesActorProxy = new Proxy(function () { }, { } return new MappedConstructor(...args); }, + /*~ @@DOUBLE-BLANK@@ ~*/ get(_, prop) { switch (prop) { case "create": @@ -38,6 +37,7 @@ const BladesActorProxy = new Proxy(function () { }, { if (U.isArray(data)) { return data.map((i) => CONFIG.Actor.documentClass.create(i, options)); } + /*~ @@DOUBLE-BLANK@@ ~*/ const MappedConstructor = ActorsMap[data.type]; if (!MappedConstructor) { return BladesActor.create(data, options); @@ -52,6 +52,8 @@ const BladesActorProxy = new Proxy(function () { }, { return BladesActor[prop]; } } + /*~ @@DOUBLE-BLANK@@ ~*/ }); +/*~ @@DOUBLE-BLANK@@ ~*/ export default BladesActorProxy; -export { BladesActor, BladesPC, BladesCrew, BladesNPC, BladesFaction }; \ No newline at end of file +export { BladesActor, BladesPC, BladesCrew, BladesNPC, BladesFaction }; diff --git a/module/documents/BladesItemProxy.js b/module/documents/BladesItemProxy.js index 227c84ea..5d699fef 100644 --- a/module/documents/BladesItemProxy.js +++ b/module/documents/BladesItemProxy.js @@ -1,10 +1,3 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - import U from "../core/utilities.js"; import { BladesItemType } from "../core/constants.js"; import BladesItem from "../BladesItem.js"; @@ -12,13 +5,17 @@ import BladesLocation from "./items/BladesLocation.js"; import BladesClockKeeper from "./items/BladesClockKeeper.js"; import BladesGMTracker from "./items/BladesGMTracker.js"; import BladesScore from "./items/BladesScore.js"; +/*~ @@DOUBLE-BLANK@@ ~*/ const ItemsMap = { [BladesItemType.clock_keeper]: BladesClockKeeper, [BladesItemType.gm_tracker]: BladesGMTracker, [BladesItemType.location]: BladesLocation, [BladesItemType.score]: BladesScore }; +/*~ @@DOUBLE-BLANK@@ ~*/ +// eslint-disable-next-line @typescript-eslint/no-empty-function const BladesItemProxy = new Proxy(function () { }, { + /*~ @@DOUBLE-BLANK@@ ~*/ construct(_, args) { const [{ type }] = args; if (!type) { @@ -30,6 +27,7 @@ const BladesItemProxy = new Proxy(function () { }, { } return new MappedConstructor(...args); }, + /*~ @@DOUBLE-BLANK@@ ~*/ get(_, prop) { switch (prop) { case "create": @@ -38,6 +36,7 @@ const BladesItemProxy = new Proxy(function () { }, { if (U.isArray(data)) { return data.map((i) => CONFIG.Item.documentClass.create(i, options)); } + /*~ @@DOUBLE-BLANK@@ ~*/ const MappedConstructor = ItemsMap[data.type]; if (!MappedConstructor) { return BladesItem.create(data, options); @@ -52,6 +51,8 @@ const BladesItemProxy = new Proxy(function () { }, { return BladesItem[prop]; } } + /*~ @@DOUBLE-BLANK@@ ~*/ }); +/*~ @@DOUBLE-BLANK@@ ~*/ export default BladesItemProxy; -export { BladesItem, BladesClockKeeper, BladesGMTracker, BladesLocation, BladesScore }; \ No newline at end of file +export { BladesItem, BladesClockKeeper, BladesGMTracker, BladesLocation, BladesScore }; diff --git a/module/documents/actors/BladesCrew.js b/module/documents/actors/BladesCrew.js index d208a1bc..0644d67e 100644 --- a/module/documents/actors/BladesCrew.js +++ b/module/documents/actors/BladesCrew.js @@ -1,35 +1,50 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - import { BladesItemType } from "../../core/constants.js"; import BladesActor from "../../BladesActor.js"; +/*~ @@DOUBLE-BLANK@@ ~*/ class BladesCrew extends BladesActor { - + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region Static Overrides: Create ~ static async create(data, options = {}) { data.token = data.token || {}; data.system = data.system ?? {}; + /*~ @@DOUBLE-BLANK@@ ~*/ eLog.checkLog2("actor", "BladesActor.create(data,options)", { data, options }); + /*~ @@DOUBLE-BLANK@@ ~*/ + // ~ For Crew and PC set the Token to sync with charsheet. data.token.actorLink = true; + /*~ @@DOUBLE-BLANK@@ ~*/ + // ~ Create world_name data.system.world_name = data.system.world_name ?? data.name.replace(/[^A-Za-z_0-9 ]/g, "").trim().replace(/ /g, "_"); + /*~ @@DOUBLE-BLANK@@ ~*/ + // ~ Initialize generic experience clues. data.system.experience = { playbook: { value: 0, max: 8 }, clues: [], ...data.system.experience ?? {} }; + /*~ @@DOUBLE-BLANK@@ ~*/ return super.create(data, options); } - + // #endregion + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region BladesRoll Implementation + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region BladesRoll.ParticipantDoc Implementation get rollParticipantID() { return this.id; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollParticipantDoc() { return this; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollParticipantIcon() { return this.playbook?.img ?? this.img; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollParticipantName() { return this.name; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollParticipantType() { return this.type; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollParticipantModsData() { return []; } - + // #endregion + /*~ @@DOUBLE-BLANK@@ ~*/ + // #endregion + /*~ @@DOUBLE-BLANK@@ ~*/ get abilities() { if (!this.playbook) { return []; @@ -37,12 +52,16 @@ class BladesCrew extends BladesActor { return this.activeSubItems .filter((item) => [BladesItemType.ability, BladesItemType.crew_ability].includes(item.type)); } + /*~ @@DOUBLE-BLANK@@ ~*/ get playbookName() { return this.playbook?.name; } + /*~ @@DOUBLE-BLANK@@ ~*/ get playbook() { return this.activeSubItems .find((item) => item.type === BladesItemType.crew_playbook); } } -export default BladesCrew; \ No newline at end of file +/*~ @@DOUBLE-BLANK@@ ~*/ +export default BladesCrew; +/*~ @@DOUBLE-BLANK@@ ~*/ diff --git a/module/documents/actors/BladesFaction.js b/module/documents/actors/BladesFaction.js index 4949a639..2f13e004 100644 --- a/module/documents/actors/BladesFaction.js +++ b/module/documents/actors/BladesFaction.js @@ -1,21 +1,27 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - import BladesActor from "../../BladesActor.js"; +/*~ @@DOUBLE-BLANK@@ ~*/ class BladesFaction extends BladesActor { - + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region BladesRoll Implementation + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region BladesRoll.OppositionDoc Implementation get rollOppID() { return this.id; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollOppDoc() { return this; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollOppImg() { return this.img ?? ""; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollOppName() { return this.name ?? ""; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollOppSubName() { return this.system.subtitle || this.system.concept || " "; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollOppType() { return this.type; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollOppModsData() { return []; } - + // #endregion + /*~ @@DOUBLE-BLANK@@ ~*/ + // #endregion + /*~ @@DOUBLE-BLANK@@ ~*/ async addClock(clockData = {}) { clockData.id ??= clockData.id ?? randomID(); clockData.color ??= "white"; @@ -26,10 +32,14 @@ class BladesFaction extends BladesActor { clockData.max ??= 4; clockData.target ??= `system.clocks.${clockData.id}.value`; clockData.value ??= 0; + /*~ @@DOUBLE-BLANK@@ ~*/ return this.update({ [`system.clocks.${clockData.id}`]: clockData }); } + /*~ @@DOUBLE-BLANK@@ ~*/ async deleteClock(clockID) { return this.update({ [`system.clocks.-=${clockID}`]: null }); } } -export default BladesFaction; \ No newline at end of file +/*~ @@DOUBLE-BLANK@@ ~*/ +export default BladesFaction; +/*~ @@DOUBLE-BLANK@@ ~*/ diff --git a/module/documents/actors/BladesNPC.js b/module/documents/actors/BladesNPC.js index c67a732e..d781fd23 100644 --- a/module/documents/actors/BladesNPC.js +++ b/module/documents/actors/BladesNPC.js @@ -1,15 +1,14 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - import { Factor } from "../../core/constants.js"; import BladesActor from "../../BladesActor.js"; +/*~ @@DOUBLE-BLANK@@ ~*/ class BladesNPC extends BladesActor { + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region BladesRoll Implementation + /*~ @@DOUBLE-BLANK@@ ~*/ get rollFactors() { + /*~ @@DOUBLE-BLANK@@ ~*/ const factorData = super.rollFactors; + /*~ @@DOUBLE-BLANK@@ ~*/ factorData[Factor.scale] = { name: Factor.scale, display: "Scale", @@ -33,22 +32,39 @@ class BladesNPC extends BladesActor { isDominant: false, highFavorsPC: true }; + /*~ @@DOUBLE-BLANK@@ ~*/ return factorData; } - + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region BladesRoll.OppositionDoc Implementation get rollOppID() { return this.id; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollOppDoc() { return this; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollOppImg() { return this.img; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollOppName() { return this.name; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollOppSubName() { return this.system.subtitle || this.system.concept || " "; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollOppType() { return this.type; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollOppModsData() { return []; } - + // #endregion + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region BladesRoll.ParticipantDoc Implementation get rollParticipantID() { return this.id; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollParticipantDoc() { return this; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollParticipantIcon() { return this.img; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollParticipantName() { return this.name; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollParticipantType() { return this.type; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollParticipantModsData() { return []; } } -export default BladesNPC; \ No newline at end of file +/*~ @@DOUBLE-BLANK@@ ~*/ +export default BladesNPC; +/*~ @@DOUBLE-BLANK@@ ~*/ diff --git a/module/documents/actors/BladesPC.js b/module/documents/actors/BladesPC.js index b52d2564..d05778d8 100644 --- a/module/documents/actors/BladesPC.js +++ b/module/documents/actors/BladesPC.js @@ -1,24 +1,25 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - import C, { AttributeTrait, Harm, BladesActorType, BladesItemType, Tag, RollModSection, RollModStatus } from "../../core/constants.js"; import U from "../../core/utilities.js"; import { BladesActor } from "../BladesActorProxy.js"; import { BladesItem } from "../BladesItemProxy.js"; +/*~ @@DOUBLE-BLANK@@ ~*/ class BladesPC extends BladesActor { - + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region Static Overrides: Create ~ static IsType(doc) { return super.IsType(doc, BladesActorType.pc); } + /*~ @@DOUBLE-BLANK@@ ~*/ static async create(data, options = {}) { data.token = data.token || {}; data.system = data.system ?? {}; + /*~ @@DOUBLE-BLANK@@ ~*/ eLog.checkLog2("actor", "BladesPC.create(data,options)", { data, options }); + /*~ @@DOUBLE-BLANK@@ ~*/ + // ~ Set the Token to sync with charsheet. data.token.actorLink = true; + /*~ @@DOUBLE-BLANK@@ ~*/ + // ~ Initialize generic experience clues. data.system.experience = { playbook: { value: 0, max: 8 }, insight: { value: 0, max: 6 }, @@ -29,10 +30,13 @@ class BladesPC extends BladesActor { }; return super.create(data, options); } - + // #endregion + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region BladesPrimaryActor Implementation ~ get primaryUser() { return game.users?.find((user) => user.character?.id === this?.id) || null; } + /*~ @@DOUBLE-BLANK@@ ~*/ async clearLoadout() { await this.update({ "system.loadout.selected": "" }); this.updateEmbeddedDocuments("Item", [ @@ -53,6 +57,8 @@ class BladesPC extends BladesActor { })) ]); } + // #endregion + /*~ @@DOUBLE-BLANK@@ ~*/ getSubActor(actorRef) { const actor = super.getSubActor(actorRef); if (!actor) { @@ -63,6 +69,7 @@ class BladesPC extends BladesActor { } return actor; } + /*~ @@DOUBLE-BLANK@@ ~*/ get armorStatus() { const armorData = {}; if (this.system.armor.active.special) { @@ -95,17 +102,23 @@ class BladesPC extends BladesActor { } return armorData; } + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region BladesScoundrel Implementation ~ + /*~ @@DOUBLE-BLANK@@ ~*/ isMember(crew) { return this.crew?.id === crew.id; } + /*~ @@DOUBLE-BLANK@@ ~*/ get vice() { if (this.type !== BladesActorType.pc) { return undefined; } return this.activeSubItems.find((item) => item.type === BladesItemType.vice); } + /*~ @@DOUBLE-BLANK@@ ~*/ get crew() { return this.activeSubActors .find((subActor) => BladesActor.IsType(subActor, BladesActorType.crew)); } + /*~ @@DOUBLE-BLANK@@ ~*/ get abilities() { if (!this.playbook) { return []; @@ -113,13 +126,16 @@ class BladesPC extends BladesActor { return this.activeSubItems .filter((item) => [BladesItemType.ability, BladesItemType.crew_ability].includes(item.type)); } + /*~ @@DOUBLE-BLANK@@ ~*/ get playbookName() { return this.playbook?.name; } + /*~ @@DOUBLE-BLANK@@ ~*/ get playbook() { return this.activeSubItems .find((item) => item.type === BladesItemType.playbook); } + /*~ @@DOUBLE-BLANK@@ ~*/ get attributes() { if (!BladesActor.IsType(this, BladesActorType.pc)) { return undefined; @@ -136,6 +152,7 @@ class BladesPC extends BladesActor { + this.system.resistance_bonus.resolve }; } + /*~ @@DOUBLE-BLANK@@ ~*/ get actions() { if (!BladesActor.IsType(this, BladesActorType.pc)) { return undefined; @@ -146,6 +163,7 @@ class BladesPC extends BladesActor { ...this.system.attributes.resolve }, ({ value, max }) => U.gsap.utils.clamp(0, max, value)); } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollable() { if (!BladesActor.IsType(this, BladesActorType.pc)) { return undefined; @@ -155,28 +173,35 @@ class BladesPC extends BladesActor { ...this.actions }; } + /*~ @@DOUBLE-BLANK@@ ~*/ get trauma() { if (!BladesActor.IsType(this, BladesActorType.pc)) { return 0; } return Object.keys(this.system.trauma.checked) .filter((traumaName) => + // @ts-ignore Compiler linter mismatch. this.system.trauma.active[traumaName] && this.system.trauma.checked[traumaName]) .length; } + /*~ @@DOUBLE-BLANK@@ ~*/ get traumaList() { + // @ts-ignore Compiler linter mismatch. return BladesActor.IsType(this, BladesActorType.pc) ? Object.keys(this.system.trauma.active).filter((key) => this.system.trauma.active[key]) : []; } + /*~ @@DOUBLE-BLANK@@ ~*/ get activeTraumaConditions() { if (!BladesActor.IsType(this, BladesActorType.pc)) { return {}; } return U.objFilter(this.system.trauma.checked, + // @ts-ignore Compiler linter mismatch. (_v, traumaName) => Boolean(traumaName in this.system.trauma.active && this.system.trauma.active[traumaName])); } + /*~ @@DOUBLE-BLANK@@ ~*/ get currentLoad() { if (!BladesActor.IsType(this, BladesActorType.pc)) { return 0; @@ -184,6 +209,7 @@ class BladesPC extends BladesActor { const activeLoadItems = this.activeSubItems.filter((item) => item.type === BladesItemType.gear); return U.gsap.utils.clamp(0, 10, activeLoadItems.reduce((tot, i) => tot + U.pInt(i.system.load), 0)); } + /*~ @@DOUBLE-BLANK@@ ~*/ get remainingLoad() { if (!BladesActor.IsType(this, BladesActorType.pc)) { return 0; @@ -195,15 +221,21 @@ class BladesPC extends BladesActor { .toLowerCase()]; return Math.max(0, maxLoad - this.currentLoad); } + /*~ @@DOUBLE-BLANK@@ ~*/ async addStash(amount) { if (!BladesActor.IsType(this, BladesActorType.pc)) { return; } await this.update({ "system.stash.value": Math.min(this.system.stash.value + amount, this.system.stash.max) }); } - + // #endregion + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region BladesRoll.PrimaryDoc Implementation get rollModsData() { + /*~ @@DOUBLE-BLANK@@ ~*/ const rollModsData = super.rollModsData; + /*~ @@DOUBLE-BLANK@@ ~*/ + // Add roll mods from harm [ [/1d/, RollModSection.roll], [/Less Effect/, RollModSection.effect] @@ -250,17 +282,28 @@ class BladesPC extends BladesActor { ].join("") }); } + /*~ @@DOUBLE-BLANK@@ ~*/ return rollModsData; } - - + /*~ @@DOUBLE-BLANK@@ ~*/ + // #endregion + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region BladesRoll.ParticipantDoc Implementation + /*~ @@DOUBLE-BLANK@@ ~*/ get rollParticipantID() { return this.id; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollParticipantDoc() { return this; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollParticipantIcon() { return this.playbook?.img ?? this.img; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollParticipantName() { return this.name ?? ""; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollParticipantType() { return this.type; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollParticipantModsData() { return []; } - + /*~ @@DOUBLE-BLANK@@ ~*/ + // #endregion + /*~ @@DOUBLE-BLANK@@ ~*/ get rollTraitPCTooltipActions() { const tooltipStrings = [""]; const actionRatings = this.actions; @@ -278,6 +321,7 @@ class BladesPC extends BladesActor { tooltipStrings.push("
"); return tooltipStrings.join(""); } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollTraitPCTooltipAttributes() { const tooltipStrings = [""]; const attributeRatings = this.attributes; @@ -294,4 +338,6 @@ class BladesPC extends BladesActor { return tooltipStrings.join(""); } } -export default BladesPC; \ No newline at end of file +/*~ @@DOUBLE-BLANK@@ ~*/ +export default BladesPC; +/*~ @@DOUBLE-BLANK@@ ~*/ diff --git a/module/documents/actors/blades-crew.js b/module/documents/actors/blades-crew.js deleted file mode 100644 index 6e2dd973..00000000 --- a/module/documents/actors/blades-crew.js +++ /dev/null @@ -1,84 +0,0 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - -import { BladesItemType, Factor } from "../../core/constants.js"; -import BladesActor from "../../blades-actor.js"; -import { BladesRollMod } from "../../blades-roll-collab.js"; -class BladesCrew extends BladesActor { - - static async create(data, options = {}) { - data.token = data.token || {}; - data.system = data.system ?? {}; - eLog.checkLog2("actor", "BladesActor.create(data,options)", { data, options }); - - data.token.actorLink = true; - - data.system.world_name = data.system.world_name ?? data.name.replace(/[^A-Za-z_0-9 ]/g, "").trim().replace(/ /g, "_"); - - data.system.experience = { - playbook: { value: 0, max: 8 }, - clues: [], - ...data.system.experience ?? {} - }; - return super.create(data, options); - } - get rollModsData() { - return BladesRollMod.ParseDocRollMods(this); - } - get rollFactors() { - const factorData = { - [Factor.tier]: { - name: Factor.tier, - value: this.getFactorTotal(Factor.tier), - max: this.getFactorTotal(Factor.tier), - baseVal: this.getFactorTotal(Factor.tier), - isActive: true, - isPrimary: true, - isDominant: false, - highFavorsPC: true - }, - [Factor.quality]: { - name: Factor.quality, - value: this.getFactorTotal(Factor.quality), - max: this.getFactorTotal(Factor.quality), - baseVal: this.getFactorTotal(Factor.quality), - isActive: false, - isPrimary: false, - isDominant: false, - highFavorsPC: true - } - }; - return factorData; - } - get rollPrimaryID() { return this.id; } - get rollPrimaryDoc() { return this; } - get rollPrimaryName() { return this.name; } - get rollPrimaryType() { return this.type; } - get rollPrimaryImg() { return this.img; } - - get rollParticipantID() { return this.id; } - get rollParticipantDoc() { return this; } - get rollParticipantIcon() { return this.playbook?.img ?? this.img; } - get rollParticipantName() { return this.name; } - get rollParticipantType() { return this.type; } - get rollParticipantModsData() { return []; } - - get abilities() { - if (!this.playbook) { - return []; - } - return this.activeSubItems.filter((item) => [BladesItemType.ability, BladesItemType.crew_ability].includes(item.type)); - } - get playbookName() { - return this.playbook?.name; - } - get playbook() { - return this.activeSubItems.find((item) => item.type === BladesItemType.crew_playbook); - } -} -export default BladesCrew; -//# sourceMappingURL=blades-crew.js.map \ No newline at end of file diff --git a/module/documents/actors/blades-faction.js b/module/documents/actors/blades-faction.js deleted file mode 100644 index cf8cd91c..00000000 --- a/module/documents/actors/blades-faction.js +++ /dev/null @@ -1,62 +0,0 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - -import { Factor } from "../../core/constants.js"; -import BladesActor from "../../blades-actor.js"; -class BladesFaction extends BladesActor { - get rollFactors() { - const factorData = { - [Factor.tier]: { - name: Factor.tier, - value: this.getFactorTotal(Factor.tier), - max: this.getFactorTotal(Factor.tier), - baseVal: this.getFactorTotal(Factor.tier), - isActive: true, - isPrimary: true, - isDominant: false, - highFavorsPC: true - }, - [Factor.quality]: { - name: Factor.quality, - value: this.getFactorTotal(Factor.quality), - max: this.getFactorTotal(Factor.quality), - baseVal: this.getFactorTotal(Factor.quality), - isActive: false, - isPrimary: false, - isDominant: false, - highFavorsPC: true - } - }; - return factorData; - } - - get rollOppID() { return this.id; } - get rollOppDoc() { return this; } - get rollOppImg() { return this.img ?? ""; } - get rollOppName() { return this.name ?? ""; } - get rollOppSubName() { return ""; } - get rollOppType() { return this.type; } - get rollOppModsData() { return []; } - - async addClock(clockData = {}) { - clockData.id ??= clockData.id ?? randomID(); - clockData.color ??= "white"; - clockData.display ??= ""; - clockData.isVisible ??= false; - clockData.isNameVisible ??= false; - clockData.isActive ??= false; - clockData.max ??= 4; - clockData.target ??= `system.clocks.${clockData.id}.value`; - clockData.value ??= 0; - return this.update({ [`system.clocks.${clockData.id}`]: clockData }); - } - async deleteClock(clockID) { - return this.update({ [`system.clocks.-=${clockID}`]: null }); - } -} -export default BladesFaction; -//# sourceMappingURL=blades-faction.js.map \ No newline at end of file diff --git a/module/documents/actors/blades-npc.js b/module/documents/actors/blades-npc.js deleted file mode 100644 index ef313ca9..00000000 --- a/module/documents/actors/blades-npc.js +++ /dev/null @@ -1,76 +0,0 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - -import { BladesActorType, Factor } from "../../core/constants.js"; -import BladesActor from "../../blades-actor.js"; -class BladesNPC extends BladesActor { - get rollFactors() { - const factorData = { - [Factor.tier]: { - name: Factor.tier, - value: this.getFactorTotal(Factor.tier), - max: this.getFactorTotal(Factor.tier), - baseVal: this.getFactorTotal(Factor.tier), - isActive: true, - isPrimary: true, - isDominant: false, - highFavorsPC: true - }, - [Factor.quality]: { - name: Factor.quality, - value: this.getFactorTotal(Factor.quality), - max: this.getFactorTotal(Factor.quality), - baseVal: this.getFactorTotal(Factor.quality), - isActive: false, - isPrimary: false, - isDominant: false, - highFavorsPC: true - } - }; - if (BladesActor.IsType(this, BladesActorType.npc)) { - factorData[Factor.scale] = { - name: Factor.scale, - value: this.getFactorTotal(Factor.scale), - max: this.getFactorTotal(Factor.scale), - baseVal: this.getFactorTotal(Factor.scale), - cssClasses: "factor-grey", - isActive: false, - isPrimary: false, - isDominant: false, - highFavorsPC: true - }; - factorData[Factor.magnitude] = { - name: Factor.magnitude, - value: this.getFactorTotal(Factor.magnitude), - max: this.getFactorTotal(Factor.magnitude), - baseVal: this.getFactorTotal(Factor.magnitude), - isActive: false, - isPrimary: false, - isDominant: false, - highFavorsPC: true - }; - } - return factorData; - } - - get rollOppID() { return this.id; } - get rollOppDoc() { return this; } - get rollOppImg() { return this.img ?? ""; } - get rollOppName() { return this.name ?? ""; } - get rollOppSubName() { return ""; } - get rollOppType() { return this.type; } - get rollOppModsData() { return []; } - - get rollParticipantID() { return this.id; } - get rollParticipantDoc() { return this; } - get rollParticipantIcon() { return this.img ?? ""; } - get rollParticipantName() { return this.name ?? ""; } - get rollParticipantType() { return this.type; } - get rollParticipantModsData() { return []; } -} -export default BladesNPC; -//# sourceMappingURL=blades-npc.js.map \ No newline at end of file diff --git a/module/documents/actors/blades-pc.js b/module/documents/actors/blades-pc.js deleted file mode 100644 index 93f1b83d..00000000 --- a/module/documents/actors/blades-pc.js +++ /dev/null @@ -1,306 +0,0 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - -import BladesItem from "../../blades-item.js"; -import C, { Attribute, Harm, BladesActorType, BladesItemType, Tag, RollModCategory, Factor, RollModStatus } from "../../core/constants.js"; -import U from "../../core/utilities.js"; -import BladesActor from "../../blades-actor.js"; -import { BladesRollMod } from "../../blades-roll-collab.js"; -class BladesPC extends BladesActor { - - static async create(data, options = {}) { - data.token = data.token || {}; - data.system = data.system ?? {}; - eLog.checkLog2("actor", "BladesPC.create(data,options)", { data, options }); - - data.token.actorLink = true; - - data.system.experience = { - playbook: { value: 0, max: 8 }, - insight: { value: 0, max: 6 }, - prowess: { value: 0, max: 6 }, - resolve: { value: 0, max: 6 }, - clues: [], - ...data.system.experience ?? {} - }; - return super.create(data, options); - } - - get primaryUser() { - return game.users?.find((user) => user.character?.id === this?.id) || null; - } - async clearLoadout() { - this.update({ "system.loadout.selected": "" }); - this.updateEmbeddedDocuments("Item", [ - ...this.activeSubItems.filter((item) => BladesItem.IsType(item, BladesItemType.gear) && !item.hasTag(Tag.System.Archived)) - .map((item) => ({ - "_id": item.id, - "system.tags": [...item.tags, Tag.System.Archived], - "system.uses_per_score.value": 0 - })), - ...this.activeSubItems.filter((item) => BladesItem.IsType(item, BladesItemType.ability) && item.system.uses_per_score.max) - .map((item) => ({ - "_id": item.id, - "system.uses_per_score.value": 0 - })) - ]); - } - getSubActor(actorRef) { - const actor = super.getSubActor(actorRef); - if (!actor) { - return undefined; - } - if (this.primaryUser?.id) { - actor.ownership[this.primaryUser.id] = CONST.DOCUMENT_PERMISSION_LEVELS.OWNER; - } - return actor; - } - get armorStatus() { - const armorData = {}; - if (this.system.armor.active.special) { - armorData.special = this.system.armor.checked.special; - } - if (this.system.armor.active.heavy) { - armorData.max = 2; - if (this.system.armor.checked.light) { - armorData.value = 0; - } - else if (this.system.armor.checked.heavy) { - armorData.value = 1; - } - else { - armorData.value = 2; - } - } - else if (this.system.armor.active.light) { - armorData.max = 1; - if (this.system.armor.checked.light) { - armorData.value = 0; - } - else { - armorData.value = 1; - } - } - else { - armorData.max = 0; - armorData.value = 0; - } - return armorData; - } - isMember(crew) { return this.crew?.id === crew.id; } - get vice() { - if (this.type !== BladesActorType.pc) { - return undefined; - } - return this.activeSubItems.find((item) => item.type === BladesItemType.vice); - } - get crew() { - return this.activeSubActors.find((subActor) => BladesActor.IsType(subActor, BladesActorType.crew)); - } - get abilities() { - if (!this.playbook) { - return []; - } - return this.activeSubItems.filter((item) => [BladesItemType.ability, BladesItemType.crew_ability].includes(item.type)); - } - get playbookName() { - return this.playbook?.name; - } - get playbook() { - return this.activeSubItems.find((item) => item.type === BladesItemType.playbook); - } - get attributes() { - if (!BladesActor.IsType(this, BladesActorType.pc)) { - return undefined; - } - return { - insight: Object.values(this.system.attributes.insight).filter(({ value }) => value > 0).length + this.system.resistance_bonus.insight, - prowess: Object.values(this.system.attributes.prowess).filter(({ value }) => value > 0).length + this.system.resistance_bonus.prowess, - resolve: Object.values(this.system.attributes.resolve).filter(({ value }) => value > 0).length + this.system.resistance_bonus.resolve - }; - } - get actions() { - if (!BladesActor.IsType(this, BladesActorType.pc)) { - return undefined; - } - return U.objMap({ - ...this.system.attributes.insight, - ...this.system.attributes.prowess, - ...this.system.attributes.resolve - }, ({ value, max }) => U.gsap.utils.clamp(0, max, value)); - } - get rollable() { - if (!BladesActor.IsType(this, BladesActorType.pc)) { - return undefined; - } - return { - ...this.attributes, - ...this.actions - }; - } - get trauma() { - if (!BladesActor.IsType(this, BladesActorType.pc)) { - return 0; - } - return Object.keys(this.system.trauma.checked) - .filter((traumaName) => - this.system.trauma.active[traumaName] && this.system.trauma.checked[traumaName]) - .length; - } - get traumaList() { - return BladesActor.IsType(this, BladesActorType.pc) ? Object.keys(this.system.trauma.active).filter((key) => this.system.trauma.active[key]) : []; - } - get activeTraumaConditions() { - if (!BladesActor.IsType(this, BladesActorType.pc)) { - return {}; - } - return U.objFilter(this.system.trauma.checked, - (_v, traumaName) => Boolean(traumaName in this.system.trauma.active && this.system.trauma.active[traumaName])); - } - get currentLoad() { - if (!BladesActor.IsType(this, BladesActorType.pc)) { - return 0; - } - const activeLoadItems = this.activeSubItems.filter((item) => item.type === BladesItemType.gear); - return U.gsap.utils.clamp(0, 10, activeLoadItems.reduce((tot, i) => tot + U.pInt(i.system.load), 0)); - } - get remainingLoad() { - if (!BladesActor.IsType(this, BladesActorType.pc)) { - return 0; - } - if (!this.system.loadout.selected) { - return 0; - } - const maxLoad = this.system.loadout.levels[game.i18n.localize(this.system.loadout.selected.toString()).toLowerCase()]; - return Math.max(0, maxLoad - this.currentLoad); - } - async addStash(amount) { - if (!BladesActor.IsType(this, BladesActorType.pc)) { - return; - } - this.update({ "system.stash.value": Math.min(this.system.stash.value + amount, this.system.stash.max) }); - } - get rollFactors() { - const factorData = { - [Factor.tier]: { - name: Factor.tier, - value: this.getFactorTotal(Factor.tier), - max: this.getFactorTotal(Factor.tier), - baseVal: this.getFactorTotal(Factor.tier), - isActive: true, - isPrimary: true, - isDominant: false, - highFavorsPC: true - }, - [Factor.quality]: { - name: Factor.quality, - value: this.getFactorTotal(Factor.quality), - max: this.getFactorTotal(Factor.quality), - baseVal: this.getFactorTotal(Factor.quality), - isActive: false, - isPrimary: false, - isDominant: false, - highFavorsPC: true - } - }; - return factorData; - } - get rollPrimaryID() { return this.id; } - get rollPrimaryDoc() { return this; } - get rollPrimaryName() { return this.name; } - get rollPrimaryType() { return this.type; } - get rollPrimaryImg() { return this.img; } - get rollModsData() { - const rollModsData = BladesRollMod.ParseDocRollMods(this); - [[/1d/, RollModCategory.roll], [/Less Effect/, RollModCategory.effect]].forEach(([effectPat, effectCat]) => { - const { one: harmConditionOne, two: harmConditionTwo } = Object.values(this.system.harm) - .find((harmData) => effectPat.test(harmData.effect)) ?? {}; - const harmString = U.objCompact([harmConditionOne, harmConditionTwo === "" ? null : harmConditionTwo]).join(" & "); - if (harmString.length > 0) { - rollModsData.push({ - id: `Harm-negative-${effectCat}`, - name: harmString, - category: effectCat, - posNeg: "negative", - base_status: RollModStatus.ToggledOn, - modType: "harm", - value: 1, - tooltip: [ - `

${effectCat === RollModCategory.roll ? Harm.Impaired : Harm.Weakened} (Harm)

`, - `

${harmString}

`, - effectCat === RollModCategory.roll - ? "

If your injuries apply to the situation at hand, you suffer −1d to your roll.

" - : "

If your injuries apply to the situation at hand, you suffer −1 effect." - ].join("") - }); - } - }); - const { one: harmCondition } = Object.values(this.system.harm).find((harmData) => /Need Help/.test(harmData.effect)) ?? {}; - if (harmCondition && harmCondition.trim() !== "") { - rollModsData.push({ - id: "Push-negative-roll", - name: "PUSH", - sideString: harmCondition.trim(), - category: RollModCategory.roll, - posNeg: "negative", - base_status: RollModStatus.ToggledOn, - modType: "harm", - value: 0, - effectKeys: ["Cost-Stress2"], - tooltip: [ - "

Broken (Harm)

", - `

${harmCondition.trim()}

`, - "

If your injuries apply to the situation at hand, you must Push to act.

" - ].join("") - }); - } - return rollModsData; - } - - - get rollParticipantID() { return this.id; } - get rollParticipantDoc() { return this; } - get rollParticipantIcon() { return this.playbook?.img ?? this.img; } - get rollParticipantName() { return this.name ?? ""; } - get rollParticipantType() { return this.type; } - get rollParticipantModsData() { return []; } - - get rollTraitPCTooltipActions() { - const tooltipStrings = ["
"]; - const actionRatings = this.actions; - Object.values(Attribute).forEach((attribute) => { - C.Action[attribute].forEach((action) => { - tooltipStrings.push([ - "", - ``, - ``, - ``, - "" - ].join("")); - }); - }); - tooltipStrings.push("
${U.uCase(action)}${"⚪".repeat(actionRatings[action])}(${C.ShortActionTooltips[action]})
"); - return tooltipStrings.join(""); - } - get rollTraitPCTooltipAttributes() { - const tooltipStrings = [""]; - const attributeRatings = this.attributes; - Object.values(Attribute).forEach((attribute) => { - tooltipStrings.push([ - "", - ``, - ``, - ``, - "" - ].join("")); - }); - tooltipStrings.push("
${U.uCase(attribute)}${"⚪".repeat(attributeRatings[attribute])}(${C.ShortAttributeTooltips[attribute]})
"); - return tooltipStrings.join(""); - } -} -export default BladesPC; -//# sourceMappingURL=blades-pc.js.map \ No newline at end of file diff --git a/module/documents/blades-actor-proxy.js b/module/documents/blades-actor-proxy.js deleted file mode 100644 index 9a508959..00000000 --- a/module/documents/blades-actor-proxy.js +++ /dev/null @@ -1,58 +0,0 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - -import U from "../core/utilities.js"; -import { BladesActorType } from "../core/constants.js"; -import BladesActor from "../blades-actor.js"; -import BladesPC from "./actors/blades-pc.js"; -import BladesNPC from "./actors/blades-npc.js"; -import BladesFaction from "./actors/blades-faction.js"; -import BladesCrew from "./actors/blades-crew.js"; -const ActorsMap = { - [BladesActorType.pc]: BladesPC, - [BladesActorType.npc]: BladesNPC, - [BladesActorType.faction]: BladesFaction, - [BladesActorType.crew]: BladesCrew -}; -const BladesActorProxy = new Proxy(function () { }, { - construct(_, args) { - const [{ type }] = args; - if (!type) { - throw new Error(`Invalid Actor Type: ${String(type)}`); - } - const MappedConstructor = ActorsMap[type]; - if (!MappedConstructor) { - return new BladesActor(...args); - } - return new MappedConstructor(...args); - }, - get(_, prop) { - switch (prop) { - case "create": - case "createDocuments": - return function (data, options = {}) { - if (U.isArray(data)) { - return data.map((i) => CONFIG.Actor.documentClass.create(i, options)); - } - const MappedConstructor = ActorsMap[data.type]; - if (!MappedConstructor) { - return BladesActor.create(data, options); - } - return MappedConstructor.create(data, options); - }; - case Symbol.hasInstance: - return function (instance) { - return Object.values(ActorsMap).some((i) => instance instanceof i); - }; - default: - return BladesActor[prop]; - } - } -}); -export default BladesActorProxy; -export { BladesActor, BladesPC, BladesCrew, BladesNPC, BladesFaction }; -//# sourceMappingURL=blades-actor-proxy.js.map \ No newline at end of file diff --git a/module/documents/blades-item-location.js b/module/documents/blades-item-location.js deleted file mode 100644 index ad9c1066..00000000 --- a/module/documents/blades-item-location.js +++ /dev/null @@ -1,8 +0,0 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - -"use strict"; \ No newline at end of file diff --git a/module/documents/blades-item-proxy.js b/module/documents/blades-item-proxy.js deleted file mode 100644 index 1109d613..00000000 --- a/module/documents/blades-item-proxy.js +++ /dev/null @@ -1,58 +0,0 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - -import U from "../core/utilities.js"; -import { BladesItemType } from "../core/constants.js"; -import BladesItem from "../blades-item.js"; -import BladesLocation from "./items/blades-location.js"; -import BladesClockKeeper from "./items/blades-clock-keeper.js"; -import BladesGMTracker from "./items/blades-gm-tracker.js"; -import BladesScore from "./items/blades-score.js"; -const ItemsMap = { - [BladesItemType.clock_keeper]: BladesClockKeeper, - [BladesItemType.gm_tracker]: BladesGMTracker, - [BladesItemType.location]: BladesLocation, - [BladesItemType.score]: BladesScore -}; -const BladesItemProxy = new Proxy(function () { }, { - construct(_, args) { - const [{ type }] = args; - if (!type) { - throw new Error(`Invalid Item Type: ${String(type)}`); - } - const MappedConstructor = ItemsMap[type]; - if (!MappedConstructor) { - return new BladesItem(...args); - } - return new MappedConstructor(...args); - }, - get(_, prop) { - switch (prop) { - case "create": - case "createDocuments": - return function (data, options = {}) { - if (U.isArray(data)) { - return data.map((i) => CONFIG.Item.documentClass.create(i, options)); - } - const MappedConstructor = ItemsMap[data.type]; - if (!MappedConstructor) { - return BladesItem.create(data, options); - } - return MappedConstructor.create(data, options); - }; - case Symbol.hasInstance: - return function (instance) { - return Object.values(ItemsMap).some((i) => instance instanceof i); - }; - default: - return BladesItem[prop]; - } - } -}); -export default BladesItemProxy; -export { BladesItem, BladesClockKeeper, BladesGMTracker, BladesLocation, BladesScore }; -//# sourceMappingURL=blades-item-proxy.js.map \ No newline at end of file diff --git a/module/documents/items/BladesClockKeeper.js b/module/documents/items/BladesClockKeeper.js index 68feb4e9..1a225b39 100644 --- a/module/documents/items/BladesClockKeeper.js +++ b/module/documents/items/BladesClockKeeper.js @@ -1,16 +1,11 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - import BladesItem from "../../BladesItem.js"; import C, { SVGDATA, BladesActorType, BladesItemType } from "../../core/constants.js"; import U from "../../core/utilities.js"; import BladesActor from "../../BladesActor.js"; +/*~ @@DOUBLE-BLANK@@ ~*/ class BladesClockKeeper extends BladesItem { - + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region CLOCKS OVERLAY _overlayElement; get overlayElement() { this._overlayElement ??= $("#clocks-overlay")[0]; @@ -20,6 +15,7 @@ class BladesClockKeeper extends BladesItem { } return this._overlayElement; } + /*~ @@DOUBLE-BLANK@@ ~*/ async renderOverlay() { if (!game.scenes?.current) { return; @@ -53,25 +49,33 @@ class BladesClockKeeper extends BladesItem { if (!(event.originalEvent instanceof WheelEvent)) { return; } + /*~ @@DOUBLE-BLANK@@ ~*/ event.preventDefault(); + /*~ @@DOUBLE-BLANK@@ ~*/ const clock$ = $(event.currentTarget).closest(".clock"); const [key] = clock$.closest(".clock-key"); + /*~ @@DOUBLE-BLANK@@ ~*/ if (!(key instanceof HTMLElement)) { return; } + /*~ @@DOUBLE-BLANK@@ ~*/ const keyID = key.id; const clockNum = clock$.data("index"); const curClockVal = U.pInt(clock$.data("value")); const delta = event.originalEvent.deltaY < 0 ? 1 : -1; const max = U.pInt(clock$.data("size")); + /*~ @@DOUBLE-BLANK@@ ~*/ const newClockVal = U.gsap.utils.clamp(0, max, curClockVal + delta); + /*~ @@DOUBLE-BLANK@@ ~*/ if (curClockVal === newClockVal) { return; } + /*~ @@DOUBLE-BLANK@@ ~*/ await game.eunoblades.ClockKeeper.update({ [`system.clock_keys.${keyID}.clocks.${clockNum}.value`]: `${newClockVal}` }); }); + /*~ @@DOUBLE-BLANK@@ ~*/ $("#clocks-overlay").find(".key-label").on({ click: async (event) => { if (!event.currentTarget) { @@ -80,13 +84,16 @@ class BladesClockKeeper extends BladesItem { if (!BladesItem.IsType(game.eunoblades.ClockKeeper, BladesItemType.clock_keeper)) { return; } + /*~ @@DOUBLE-BLANK@@ ~*/ event.preventDefault(); + /*~ @@DOUBLE-BLANK@@ ~*/ const keyID = $(event.currentTarget).data("keyId"); eLog.checkLog3("clocksOverlay", "Updating Key isActive", { current: game.eunoblades.ClockKeeper.system.clock_keys[keyID]?.isActive, update: !(game.eunoblades.ClockKeeper.system.clock_keys[keyID]?.isActive) }); await game.eunoblades.ClockKeeper.update({ [`system.clock_keys.${keyID}.isActive`]: !(game.eunoblades.ClockKeeper.system.clock_keys[keyID]?.isActive) }); + /*~ @@DOUBLE-BLANK@@ ~*/ }, contextmenu: () => { if (!game.user.isGM) { @@ -96,6 +103,7 @@ class BladesClockKeeper extends BladesItem { } }); } + /*~ @@DOUBLE-BLANK@@ ~*/ async addClockKey() { if (!BladesItem.IsType(game.eunoblades.ClockKeeper, BladesItemType.clock_keeper)) { return undefined; @@ -122,12 +130,14 @@ class BladesClockKeeper extends BladesItem { } } }); } + /*~ @@DOUBLE-BLANK@@ ~*/ async deleteClockKey(keyID) { if (!BladesItem.IsType(game.eunoblades.ClockKeeper, BladesItemType.clock_keeper)) { return undefined; } return game.eunoblades.ClockKeeper.update({ [`system.clock_keys.-=${keyID}`]: null }); } + /*~ @@DOUBLE-BLANK@@ ~*/ async setKeySize(keyID, keySize = 1) { if (!BladesItem.IsType(game.eunoblades.ClockKeeper, BladesItemType.clock_keeper)) { return undefined; @@ -161,10 +171,14 @@ class BladesClockKeeper extends BladesItem { } eLog.checkLog("clock_key", "Clock Key Update Data", { clockKey, updateData }); return game.eunoblades.ClockKeeper.update(updateData); + /*~ @@DOUBLE-BLANK@@ ~*/ } - + // #endregion + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region OVERRIDES: prepareDerivedData, _onUpdate prepareDerivedData() { super.prepareDerivedData(); + /*~ @@DOUBLE-BLANK@@ ~*/ this.system.scenes = game.scenes.map((scene) => ({ id: scene.id, name: scene.name ?? "" })); this.system.targetScene ??= game.scenes.current?.id || null; this.system.clock_keys = Object.fromEntries(Object.entries(this.system.clock_keys ?? {}) @@ -178,10 +192,12 @@ class BladesClockKeeper extends BladesItem { return [keyID, keyData]; })); } + /*~ @@DOUBLE-BLANK@@ ~*/ async _onUpdate(changed, options, userId) { super._onUpdate(changed, options, userId); BladesActor.GetTypeWithTags(BladesActorType.pc).forEach((actor) => actor.render()); socketlib.system.executeForEveryone("renderOverlay"); } } -export default BladesClockKeeper; \ No newline at end of file +/*~ @@DOUBLE-BLANK@@ ~*/ +export default BladesClockKeeper; diff --git a/module/documents/items/BladesGMTracker.js b/module/documents/items/BladesGMTracker.js index ec3fb5c8..c1eb3257 100644 --- a/module/documents/items/BladesGMTracker.js +++ b/module/documents/items/BladesGMTracker.js @@ -1,27 +1,25 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - import BladesItem from "../../BladesItem.js"; import { BladesActorType, BladesItemType, BladesPhase } from "../../core/constants.js"; import BladesActor from "../../BladesActor.js"; +/*~ @@DOUBLE-BLANK@@ ~*/ class BladesGMTracker extends BladesItem { + /*~ @@DOUBLE-BLANK@@ ~*/ get phase() { return BladesItem.IsType(this, BladesItemType.gm_tracker) && this.system.phase; } set phase(phase) { if (phase && BladesItem.IsType(this, BladesItemType.gm_tracker)) { this.update({ "system.phase": phase }); } } + /*~ @@DOUBLE-BLANK@@ ~*/ prepareDerivedData() { this.system.phases = Object.values(BladesPhase); } - + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region OVERRIDES: prepareDerivedData, _onUpdate async _onUpdate(changed, options, userId) { await super._onUpdate(changed, options, userId); BladesActor.GetTypeWithTags(BladesActorType.pc).forEach((actor) => actor.render()); } } -export default BladesGMTracker; \ No newline at end of file +/*~ @@DOUBLE-BLANK@@ ~*/ +export default BladesGMTracker; diff --git a/module/documents/items/BladesLocation.js b/module/documents/items/BladesLocation.js index 43fe1d99..53ce8137 100644 --- a/module/documents/items/BladesLocation.js +++ b/module/documents/items/BladesLocation.js @@ -1,16 +1,12 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - import BladesItem from "../../BladesItem.js"; import { BladesActorType, Factor } from "../../core/constants.js"; import U from "../../core/utilities.js"; import BladesActor from "../../BladesActor.js"; +/*~ @@DOUBLE-BLANK@@ ~*/ class BladesLocation extends BladesItem { + /*~ @@DOUBLE-BLANK@@ ~*/ get rollFactors() { + /*~ @@DOUBLE-BLANK@@ ~*/ const factorData = {}; [ Factor.tier, @@ -33,19 +29,24 @@ class BladesLocation extends BladesItem { }); return factorData; } + /*~ @@DOUBLE-BLANK@@ ~*/ getFactorTotal(factor) { switch (factor) { case Factor.tier: return this.system.tier.value; case Factor.quality: return this.getFactorTotal(Factor.tier); case Factor.scale: return this.system.scale; + // no default } return 0; } + /*~ @@DOUBLE-BLANK@@ ~*/ get rollOppImg() { return this.img ?? ""; } - + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region OVERRIDES: _onUpdate async _onUpdate(changed, options, userId) { await super._onUpdate(changed, options, userId); BladesActor.GetTypeWithTags(BladesActorType.pc).forEach((actor) => actor.render()); } } -export default BladesLocation; \ No newline at end of file +/*~ @@DOUBLE-BLANK@@ ~*/ +export default BladesLocation; diff --git a/module/documents/items/BladesScore.js b/module/documents/items/BladesScore.js index 4627fd5b..50c2a2bb 100644 --- a/module/documents/items/BladesScore.js +++ b/module/documents/items/BladesScore.js @@ -1,23 +1,20 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - import BladesItem from "../../BladesItem.js"; import { BladesActorType, BladesItemType, Factor } from "../../core/constants.js"; import U from "../../core/utilities.js"; import BladesActor from "../../BladesActor.js"; import BladesScoreSheet from "../../sheets/item/BladesScoreSheet.js"; +/*~ @@DOUBLE-BLANK@@ ~*/ class BladesScore extends BladesItem { - + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region INITIALIZATION ~ static async Initialize() { game.eunoblades ??= {}; Object.assign(globalThis, { BladesScore, BladesScoreSheet }); Items.registerSheet("blades", BladesScoreSheet, { types: ["score"], makeDefault: true }); return loadTemplates(["systems/eunos-blades/templates/items/score-sheet.hbs"]); } + // #endregion + /*~ @@DOUBLE-BLANK@@ ~*/ static get Active() { return BladesItem.GetTypeWithTags(BladesItemType.score).find((score) => score.system.isActive); } @@ -30,8 +27,10 @@ class BladesScore extends BladesItem { } }); } - + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region BladesRoll.OppositionDocData Implementation get rollFactors() { + /*~ @@DOUBLE-BLANK@@ ~*/ const tierTotal = this.getFactorTotal(Factor.tier); return { [Factor.tier]: { @@ -58,10 +57,13 @@ class BladesScore extends BladesItem { default: return 0; } } - + // #endregion + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region OVERRIDES: _onUpdate async _onUpdate(changed, options, userId) { super._onUpdate(changed, options, userId); BladesActor.GetTypeWithTags(BladesActorType.pc).forEach((actor) => actor.render()); } } -export default BladesScore; \ No newline at end of file +/*~ @@DOUBLE-BLANK@@ ~*/ +export default BladesScore; diff --git a/module/documents/items/blades-clock-keeper.js b/module/documents/items/blades-clock-keeper.js deleted file mode 100644 index c12b129e..00000000 --- a/module/documents/items/blades-clock-keeper.js +++ /dev/null @@ -1,188 +0,0 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - -import BladesItem from "../../blades-item.js"; -import C, { SVGDATA, BladesActorType, BladesItemType } from "../../core/constants.js"; -import U from "../../core/utilities.js"; -import BladesActor from "../../blades-actor.js"; -class BladesClockKeeper extends BladesItem { - - _overlayElement; - get overlayElement() { - this._overlayElement ??= $("#clocks-overlay")[0]; - if (!this._overlayElement) { - $("body.vtt.game.system-eunos-blades").append("
"); - [this._overlayElement] = $("#clocks-overlay"); - } - return this._overlayElement; - } - async renderOverlay() { - if (!game.scenes?.current) { - return; - } - if (!game.eunoblades.ClockKeeper) { - return; - } - if (!game.eunoblades.ClockKeeper.overlayElement) { - eLog.error("clocksOverlay", "[ClocksOverlay] Cannot locate overlay element."); - return; - } - game.eunoblades.ClockKeeper.overlayElement.innerHTML = (await getTemplate("systems/eunos-blades/templates/overlays/clock-overlay.hbs"))({ - ...game.eunoblades.ClockKeeper.system, - currentScene: game.scenes?.current.id, - clockSizes: C.ClockSizes, - svgData: SVGDATA - }); - game.eunoblades.ClockKeeper.activateOverlayListeners(); - } - async activateOverlayListeners() { - if (!game?.user?.isGM) { - return; - } - $("#clocks-overlay").find(".clock-frame").on("wheel", async (event) => { - if (!event.currentTarget) { - return; - } - if (!BladesItem.IsType(game.eunoblades.ClockKeeper, BladesItemType.clock_keeper)) { - return; - } - if (!(event.originalEvent instanceof WheelEvent)) { - return; - } - event.preventDefault(); - const clock$ = $(event.currentTarget).closest(".clock"); - const [key] = clock$.closest(".clock-key"); - if (!(key instanceof HTMLElement)) { - return; - } - const keyID = key.id; - const clockNum = clock$.data("index"); - const curClockVal = U.pInt(clock$.data("value")); - const delta = event.originalEvent.deltaY < 0 ? 1 : -1; - const max = U.pInt(clock$.data("size")); - const newClockVal = U.gsap.utils.clamp(0, max, curClockVal + delta); - if (curClockVal === newClockVal) { - return; - } - await game.eunoblades.ClockKeeper.update({ - [`system.clock_keys.${keyID}.clocks.${clockNum}.value`]: `${newClockVal}` - }); - }); - $("#clocks-overlay").find(".key-label").on({ - click: async (event) => { - if (!event.currentTarget) { - return; - } - if (!BladesItem.IsType(game.eunoblades.ClockKeeper, BladesItemType.clock_keeper)) { - return; - } - event.preventDefault(); - const keyID = $(event.currentTarget).data("keyId"); - eLog.checkLog3("clocksOverlay", "Updating Key isActive", { - current: game.eunoblades.ClockKeeper.system.clock_keys[keyID]?.isActive, - update: !(game.eunoblades.ClockKeeper.system.clock_keys[keyID]?.isActive) - }); - await game.eunoblades.ClockKeeper.update({ [`system.clock_keys.${keyID}.isActive`]: !(game.eunoblades.ClockKeeper.system.clock_keys[keyID]?.isActive) }); - }, - contextmenu: () => { - if (!game.user.isGM) { - return; - } - game.eunoblades.ClockKeeper?.sheet?.render(true); - } - }); - } - async addClockKey() { - if (!BladesItem.IsType(game.eunoblades.ClockKeeper, BladesItemType.clock_keeper)) { - return undefined; - } - const keyID = randomID(); - return game.eunoblades.ClockKeeper.update({ [`system.clock_keys.${keyID}`]: { - id: keyID, - display: "", - isVisible: false, - isNameVisible: true, - isActive: true, - scene: game.eunoblades.ClockKeeper.system.targetScene, - numClocks: 1, - clocks: { - 1: { - display: "", - isVisible: false, - isNameVisible: false, - isActive: false, - color: "yellow", - max: 4, - value: 0 - } - } - } }); - } - async deleteClockKey(keyID) { - if (!BladesItem.IsType(game.eunoblades.ClockKeeper, BladesItemType.clock_keeper)) { - return undefined; - } - return game.eunoblades.ClockKeeper.update({ [`system.clock_keys.-=${keyID}`]: null }); - } - async setKeySize(keyID, keySize = 1) { - if (!BladesItem.IsType(game.eunoblades.ClockKeeper, BladesItemType.clock_keeper)) { - return undefined; - } - keySize = parseInt(`${keySize}`, 10); - const updateData = { - [`system.clock_keys.${keyID}.numClocks`]: keySize - }; - const clockKey = game.eunoblades.ClockKeeper.system.clock_keys[keyID]; - if (!clockKey) { - return game.eunoblades.ClockKeeper; - } - const currentSize = Object.values(clockKey.clocks).length; - if (currentSize < keySize) { - for (let i = (currentSize + 1); i <= keySize; i++) { - updateData[`system.clock_keys.${keyID}.clocks.${i}`] = { - display: "", - value: 0, - max: 4, - color: "yellow", - isVisible: false, - isNameVisible: true, - isActive: false - }; - } - } - else if (currentSize > keySize) { - for (let i = (keySize + 1); i <= currentSize; i++) { - updateData[`system.clock_keys.${keyID}.clocks.-=${i}`] = null; - } - } - eLog.checkLog("clock_key", "Clock Key Update Data", { clockKey, updateData }); - return game.eunoblades.ClockKeeper.update(updateData); - } - - prepareDerivedData() { - super.prepareDerivedData(); - this.system.scenes = game.scenes.map((scene) => ({ id: scene.id, name: scene.name ?? "" })); - this.system.targetScene ??= game.scenes.current?.id || null; - this.system.clock_keys = Object.fromEntries(Object.entries(this.system.clock_keys ?? {}) - .filter(([_, keyData]) => keyData?.id) - .map(([keyID, keyData]) => { - if (keyData === null) { - return [keyID, null]; - } - keyData.clocks = Object.fromEntries(Object.entries(keyData.clocks ?? {}) - .filter(([_, clockData]) => Boolean(clockData))); - return [keyID, keyData]; - })); - } - async _onUpdate(changed, options, userId) { - await super._onUpdate(changed, options, userId); - BladesActor.GetTypeWithTags(BladesActorType.pc).forEach((actor) => actor.render()); - socketlib.system.executeForEveryone("renderOverlay"); - } -} -export default BladesClockKeeper; -//# sourceMappingURL=blades-clock-keeper.js.map \ No newline at end of file diff --git a/module/documents/items/blades-gm-tracker.js b/module/documents/items/blades-gm-tracker.js deleted file mode 100644 index 4de9eec7..00000000 --- a/module/documents/items/blades-gm-tracker.js +++ /dev/null @@ -1,28 +0,0 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - -import BladesItem from "../../blades-item.js"; -import { BladesActorType, BladesItemType, BladesPhase } from "../../core/constants.js"; -import BladesActor from "../../blades-actor.js"; -class BladesGMTracker extends BladesItem { - get phase() { return BladesItem.IsType(this, BladesItemType.gm_tracker) && this.system.phase; } - set phase(phase) { - if (phase && BladesItem.IsType(this, BladesItemType.gm_tracker)) { - this.update({ "system.phase": phase }); - } - } - prepareDerivedData() { - this.system.phases = Object.values(BladesPhase); - } - - async _onUpdate(changed, options, userId) { - await super._onUpdate(changed, options, userId); - BladesActor.GetTypeWithTags(BladesActorType.pc).forEach((actor) => actor.render()); - } -} -export default BladesGMTracker; -//# sourceMappingURL=blades-gm-tracker.js.map \ No newline at end of file diff --git a/module/documents/items/blades-item-location.js b/module/documents/items/blades-item-location.js deleted file mode 100644 index ad9c1066..00000000 --- a/module/documents/items/blades-item-location.js +++ /dev/null @@ -1,8 +0,0 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - -"use strict"; \ No newline at end of file diff --git a/module/documents/items/blades-location copy.js b/module/documents/items/blades-location copy.js deleted file mode 100644 index 19935eda..00000000 --- a/module/documents/items/blades-location copy.js +++ /dev/null @@ -1,11 +0,0 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - -import BladesItem from "../../blades-item.js"; -class BladesLocation extends BladesItem { -} -export default BladesLocation; \ No newline at end of file diff --git a/module/documents/items/blades-location.js b/module/documents/items/blades-location.js deleted file mode 100644 index 134c3743..00000000 --- a/module/documents/items/blades-location.js +++ /dev/null @@ -1,52 +0,0 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - -import BladesItem from "../../blades-item.js"; -import { BladesActorType, Factor } from "../../core/constants.js"; -import U from "../../core/utilities.js"; -import BladesActor from "../../blades-actor.js"; -class BladesLocation extends BladesItem { - get rollFactors() { - const factorData = {}; - [ - Factor.tier, - Factor.quality, - Factor.scale - ].forEach((factor, i) => { - const factorTotal = this.getFactorTotal(factor); - factorData[factor] = { - name: factor, - value: factorTotal, - max: factorTotal, - baseVal: factorTotal, - display: factor === Factor.tier ? U.romanizeNum(factorTotal) : `${factorTotal}`, - isActive: i === 0, - isPrimary: i === 0, - isDominant: false, - highFavorsPC: true, - cssClasses: `factor-gold${i === 0 ? " factor-main" : ""}` - }; - }); - return factorData; - } - getFactorTotal(factor) { - switch (factor) { - case Factor.tier: return this.system.tier.value; - case Factor.quality: return this.getFactorTotal(Factor.tier); - case Factor.scale: return this.system.scale; - } - return 0; - } - get rollOppImg() { return this.img ?? ""; } - - async _onUpdate(changed, options, userId) { - await super._onUpdate(changed, options, userId); - BladesActor.GetTypeWithTags(BladesActorType.pc).forEach((actor) => actor.render()); - } -} -export default BladesLocation; -//# sourceMappingURL=blades-location.js.map \ No newline at end of file diff --git a/module/documents/items/blades-score.js b/module/documents/items/blades-score.js deleted file mode 100644 index 8029412c..00000000 --- a/module/documents/items/blades-score.js +++ /dev/null @@ -1,68 +0,0 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - -import BladesItem from "../../blades-item.js"; -import { BladesActorType, BladesItemType, Factor } from "../../core/constants.js"; -import U from "../../core/utilities.js"; -import BladesActor from "../../blades-actor.js"; -import BladesScoreSheet from "../../sheets/item/blades-score-sheet.js"; -class BladesScore extends BladesItem { - - static async Initialize() { - game.eunoblades ??= {}; - Object.assign(globalThis, { BladesScore, BladesScoreSheet }); - Items.registerSheet("blades", BladesScoreSheet, { types: ["score"], makeDefault: true }); - return loadTemplates(["systems/eunos-blades/templates/items/score-sheet.hbs"]); - } - static get Active() { - return BladesItem.GetTypeWithTags(BladesItemType.score).find((score) => score.system.isActive); - } - static set Active(val) { - BladesItem.GetTypeWithTags(BladesItemType.score) - .find((score) => score.system.isActive)?.update({ "system.isActive": false }) - .then(() => { - if (val) { - val.update({ "system.isActive": true }); - } - }); - } - - get rollFactors() { - const tierTotal = this.getFactorTotal(Factor.tier); - return { - [Factor.tier]: { - name: "Tier", - value: tierTotal, - max: tierTotal, - baseVal: tierTotal, - display: U.romanizeNum(tierTotal), - isActive: true, - isPrimary: true, - isDominant: false, - highFavorsPC: true, - cssClasses: "factor-gold factor-main" - } - }; - } - get rollOppImg() { return this.img ?? ""; } - getFactorTotal(factor) { - switch (factor) { - case Factor.tier: return this.system.tier.value; - case Factor.quality: return this.getFactorTotal(Factor.tier); - case Factor.scale: return 0; - case Factor.magnitude: return 0; - } - return 0; - } - - async _onUpdate(changed, options, userId) { - await super._onUpdate(changed, options, userId); - BladesActor.GetTypeWithTags(BladesActorType.pc).forEach((actor) => actor.render()); - } -} -export default BladesScore; -//# sourceMappingURL=blades-score.js.map \ No newline at end of file diff --git a/module/sheets/BladesSheet.js b/module/sheets/BladesSheet.js deleted file mode 100644 index ad9c1066..00000000 --- a/module/sheets/BladesSheet.js +++ /dev/null @@ -1,8 +0,0 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - -"use strict"; \ No newline at end of file diff --git a/module/sheets/actor/BladesActorSheet.js b/module/sheets/actor/BladesActorSheet.js index 1116e847..b9c0b56b 100644 --- a/module/sheets/actor/BladesActorSheet.js +++ b/module/sheets/actor/BladesActorSheet.js @@ -1,11 +1,5 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - - +// #region IMPORTS~ +/*~ @@DOUBLE-BLANK@@ ~*/ import U from "../../core/utilities.js"; import G, { ApplyTooltipListeners } from "../../core/gsap.js"; import C, { BladesActorType, BladesItemType, AttributeTrait, ActionTrait, Factor, RollType } from "../../core/constants.js"; @@ -15,10 +9,25 @@ import BladesItem from "../../BladesItem.js"; import BladesDialog from "../../BladesDialog.js"; import BladesActiveEffect from "../../BladesActiveEffect.js"; import BladesRoll, { BladesRollPrimary, BladesRollOpposition } from "../../BladesRoll.js"; +/*~ @@DOUBLE-BLANK@@ ~*/ +// #endregion +/*~ @@DOUBLE-BLANK@@ ~*/ class BladesActorSheet extends ActorSheet { - getData() { + /*~ @@DOUBLE-BLANK@@ ~*/ + /** + * Override the default getData method to provide additional data for the actor sheet. + * This includes: cssClass, editable, isGM, actor, system, tierTotal, rollData, activeEffects, + * hasFullVision, hasLimitedVision, hasControl, preparedItems. + * @returns {BladesActorSheetData} The data object for the actor sheet. + */ + getData() { + /*~ @@DOUBLE-BLANK@@ ~*/ + // Get the base data context from the parent class. const context = super.getData(); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Prepare additional data specific to this actor's sheet. const sheetData = { + // Basic actor data. cssClass: this.actor.type, editable: this.options.editable, isGM: game.eunoblades.Tracker?.system.is_spoofing_player ? false : game.user.isGM, @@ -32,11 +41,14 @@ class BladesActorSheet extends ActorSheet { hasLimitedVision: game.user.isGM || this.actor.testUserPermission(game.user, CONST.DOCUMENT_PERMISSION_LEVELS.LIMITED), hasControl: game.user.isGM || this.actor.testUserPermission(game.user, CONST.DOCUMENT_PERMISSION_LEVELS.OWNER), + /*~ @@DOUBLE-BLANK@@ ~*/ + // Prepare items for display on the actor sheet. preparedItems: { cohorts: { gang: this.actor.activeSubItems .filter((item) => item.type === BladesItemType.cohort_gang) .map((item) => { + // Prepare gang cohort items. const subtypes = U.unique(Object.values(item.system.subtypes) .map((subtype) => subtype.trim()) .filter((subtype) => /[A-Za-z]/.test(subtype))); @@ -48,6 +60,8 @@ class BladesActorSheet extends ActorSheet { .map((subtype) => subtype.trim()) .filter((subtype) => /[A-Za-z]/ .test(subtype) && subtypes.includes(subtype))); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Prepare images for gang cohort items. const imgTypes = [...eliteSubtypes]; if (imgTypes.length < 2) { imgTypes.push(...subtypes.filter((subtype) => !imgTypes.includes(subtype))); @@ -60,6 +74,8 @@ class BladesActorSheet extends ActorSheet { item.system.imageLeft = Object.values(item.system.elite_subtypes).includes(leftType) ? `elite-${U.lCase(leftType)}.svg` : `${U.lCase(leftType)}.svg`; item.system.imageRight = Object.values(item.system.elite_subtypes).includes(rightType) ? `elite-${U.lCase(rightType)}.svg` : `${U.lCase(rightType)}.svg`; } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Prepare additional data for gang cohort items. Object.assign(item.system, { tierTotal: item.getFactorTotal(Factor.tier) > 0 ? U.romanizeNum(item.getFactorTotal(Factor.tier)) : "0", cohortRollData: [ @@ -77,6 +93,7 @@ class BladesActorSheet extends ActorSheet { expert: this.actor.activeSubItems .filter((item) => item.type === BladesItemType.cohort_expert) .map((item) => { + // Prepare expert cohort items. Object.assign(item.system, { tierTotal: item.getFactorTotal(Factor.tier) > 0 ? U.romanizeNum(item.getFactorTotal(Factor.tier)) : "0", cohortRollData: [ @@ -94,6 +111,8 @@ class BladesActorSheet extends ActorSheet { } } }; + /*~ @@DOUBLE-BLANK@@ ~*/ + // Prepare additional data for PC and Crew actors. if (BladesActor.IsType(this.actor, BladesActorType.pc) || BladesActor.IsType(this.actor, BladesActorType.crew)) { sheetData.playbookData = { dotline: { @@ -114,6 +133,7 @@ class BladesActorSheet extends ActorSheet { "" ].join(""))).toString(); } + /*~ @@DOUBLE-BLANK@@ ~*/ sheetData.coinsData = { dotline: { data: this.actor.system.coins, @@ -123,31 +143,49 @@ class BladesActorSheet extends ActorSheet { } }; } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Return the combined data context for the actor sheet. return { ...context, ...sheetData }; + /*~ @@DOUBLE-BLANK@@ ~*/ } + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region LISTENERS & EVENT HANDLERS + /*~ @@DOUBLE-BLANK@@ ~*/ activateListeners(html) { super.activateListeners(html); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Handle removal or revealing of secret information content. if (game.user.isGM) { html.attr("style", "--secret-text-display: initial"); } else { html.find('.editor:not(.tinymce) [data-is-secret="true"]').remove(); } + /*~ @@DOUBLE-BLANK@@ ~*/ + // ~ Tooltips ApplyTooltipListeners(html); + /*~ @@DOUBLE-BLANK@@ ~*/ Tags.InitListeners(html, this.actor); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Everything below here is only needed if the sheet is editable if (!this.options.editable) { return; } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Add dotline functionality html.find(".dotline").each((__, elem) => { if ($(elem).hasClass("locked")) { return; } + /*~ @@DOUBLE-BLANK@@ ~*/ let targetDoc = this.actor; let targetField = $(elem).data("target"); + /*~ @@DOUBLE-BLANK@@ ~*/ const comp$ = $(elem).closest("comp"); + /*~ @@DOUBLE-BLANK@@ ~*/ if (targetField.startsWith("item")) { targetField = targetField.replace(/^item\./, ""); const itemId = $(elem).closest("[data-comp-id]").data("compId"); @@ -160,6 +198,7 @@ class BladesActorSheet extends ActorSheet { } targetDoc = item; } + /*~ @@DOUBLE-BLANK@@ ~*/ const curValue = U.pInt($(elem).data("value")); $(elem) .find(".dot") @@ -188,12 +227,16 @@ class BladesActorSheet extends ActorSheet { }); }); }); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Clock Functionality html .find(".clock-container") .on({ click: this._onClockLeftClick.bind(this) }); html .find(".clock-container") .on({ contextmenu: this._onClockRightClick.bind(this) }); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Component Functionality: Open, Add (via SelectorDialog), Archive, Delete, Toggle, Select html .find("[data-comp-id]") .find(".comp-title") @@ -216,19 +259,27 @@ class BladesActorSheet extends ActorSheet { select[data-action='gm-select'] `) .on({ change: this._onSelectChange.bind(this) }); + /*~ @@DOUBLE-BLANK@@ ~*/ html .find(".advance-button") .on({ click: this._onAdvanceClick.bind(this) }); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Active Effects Functionality html .find(".effect-control") .on({ click: this._onActiveEffectControlClick.bind(this) }); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Roll Functionality html .find("[data-roll-trait]") .on({ click: this._onRollTraitClick.bind(this) }); + /*~ @@DOUBLE-BLANK@@ ~*/ + // This is a workaround until is being fixed in FoundryVTT. if (this.options.submitOnChange) { - html.on("change", "textarea", this._onChangeInput.bind(this)); + html.on("change", "textarea", this._onChangeInput.bind(this)); // Use delegated listener on the form } } + /*~ @@DOUBLE-BLANK@@ ~*/ async _onSubmit(event, params = {}) { if (!game.user.isGM && !this.actor.testUserPermission(game.user, CONST.DOCUMENT_PERMISSION_LEVELS.OWNER)) { eLog.checkLog("actorSheetTrigger", "User does not have permission to edit this actor", { user: game.user, actor: this.actor }); @@ -236,6 +287,7 @@ class BladesActorSheet extends ActorSheet { } return super._onSubmit(event, params); } + /*~ @@DOUBLE-BLANK@@ ~*/ async close(options) { if (this.actor.type === BladesActorType.pc) { return super.close(options).then(() => this.actor.clearSubActors()); @@ -245,7 +297,8 @@ class BladesActorSheet extends ActorSheet { } return super.close(options); } - + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region Clock Handlers ~ async _onClockLeftClick(event) { event.preventDefault(); const clock$ = $(event.currentTarget).find(".clock[data-target]"); @@ -255,10 +308,12 @@ class BladesActorSheet extends ActorSheet { const target = clock$.data("target"); const curValue = U.pInt(clock$.data("value")); const maxValue = U.pInt(clock$.data("size")); + /*~ @@DOUBLE-BLANK@@ ~*/ await G.effects.pulseClockWedges(clock$.find("wedges")).then(async () => await this.actor.update({ [target]: G.utils.wrap(0, maxValue + 1, curValue + 1) })); } + /*~ @@DOUBLE-BLANK@@ ~*/ async _onClockRightClick(event) { event.preventDefault(); const clock$ = $(event.currentTarget).find(".clock[data-target]"); @@ -267,11 +322,14 @@ class BladesActorSheet extends ActorSheet { } const target = clock$.data("target"); const curValue = U.pInt(clock$.data("value")); + /*~ @@DOUBLE-BLANK@@ ~*/ await G.effects.reversePulseClockWedges(clock$.find("wedges")).then(async () => await this.actor.update({ [target]: Math.max(0, curValue - 1) })); } - + // #endregion + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region Component Handlers _getCompData(event) { const elem$ = $(event.currentTarget).closest(".comp"); const compData = { @@ -282,6 +340,7 @@ class BladesActorSheet extends ActorSheet { docTags: (elem$.data("compTags") ?? "").split(/\s+/g) }; eLog.checkLog2("dialog", "Component Data", { elem: elem$, ...compData }); + /*~ @@DOUBLE-BLANK@@ ~*/ if (compData.docID && compData.docType) { compData.doc = { Actor: this.actor.getSubActor(compData.docID), @@ -294,8 +353,10 @@ class BladesActorSheet extends ActorSheet { Item: this.actor.getDialogItems(compData.docCat) }[compData.docType]; } + /*~ @@DOUBLE-BLANK@@ ~*/ return compData; } + /*~ @@DOUBLE-BLANK@@ ~*/ _onItemOpenClick(event) { event.preventDefault(); const { doc } = this._getCompData(event); @@ -304,6 +365,7 @@ class BladesActorSheet extends ActorSheet { } doc.sheet?.render(true); } + /*~ @@DOUBLE-BLANK@@ ~*/ async _onItemAddClick(event) { event.preventDefault(); const addType = $(event.currentTarget).closest(".comp").data("addType"); @@ -325,6 +387,7 @@ class BladesActorSheet extends ActorSheet { } await BladesDialog.DisplaySelectionDialog(this.actor, U.tCase(`Add ${docCat.replace(/_/g, " ")}`), docType, dialogDocs, docTags); } + /*~ @@DOUBLE-BLANK@@ ~*/ async _onItemRemoveClick(event) { event.preventDefault(); const { elem$, doc } = this._getCompData(event); @@ -340,6 +403,7 @@ class BladesActorSheet extends ActorSheet { } }); } + /*~ @@DOUBLE-BLANK@@ ~*/ async _onItemFullRemoveClick(event) { event.preventDefault(); const { elem$, doc } = this._getCompData(event); @@ -348,6 +412,7 @@ class BladesActorSheet extends ActorSheet { } await G.effects.blurRemove(elem$).then(async () => await doc.delete()); } + /*~ @@DOUBLE-BLANK@@ ~*/ async _onItemToggleClick(event) { event.preventDefault(); const target = $(event.currentTarget).data("target"); @@ -355,17 +420,21 @@ class BladesActorSheet extends ActorSheet { [target]: !getProperty(this.actor, target) }); } + /*~ @@DOUBLE-BLANK@@ ~*/ async _onSelectChange(event) { event.preventDefault(); await U.EventHandlers.onSelectChange(this, event); } + /*~ @@DOUBLE-BLANK@@ ~*/ async _onAdvanceClick(event) { event.preventDefault(); if ($(event.currentTarget).data("action") === "advance-playbook") { await this.actor.advancePlaybook(); } } - + // #endregion + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region Roll Handlers async _onRollTraitClick(event) { const traitName = $(event.currentTarget).data("rollTrait"); const rollType = $(event.currentTarget).data("rollType"); @@ -376,6 +445,7 @@ class BladesActorSheet extends ActorSheet { else if (U.isInt(traitName)) { rollData.rollTrait = U.pInt(traitName); } + /*~ @@DOUBLE-BLANK@@ ~*/ if (U.tCase(rollType) in RollType) { rollData.rollType = U.tCase(rollType); } @@ -387,6 +457,7 @@ class BladesActorSheet extends ActorSheet { rollData.rollType = RollType.Action; } } + /*~ @@DOUBLE-BLANK@@ ~*/ if (game.user.isGM) { if (BladesRollPrimary.IsDoc(this.actor)) { rollData.rollPrimaryData = this.actor; @@ -395,11 +466,16 @@ class BladesActorSheet extends ActorSheet { rollData.rollOppData = this.actor; } } + /*~ @@DOUBLE-BLANK@@ ~*/ await BladesRoll.NewRoll(rollData); } - + // #endregion + /*~ @@DOUBLE-BLANK@@ ~*/ + // #region Active Effect Handlers _onActiveEffectControlClick(event) { BladesActiveEffect.onManageActiveEffect(event, this.actor); } } -export default BladesActorSheet; \ No newline at end of file +/*~ @@DOUBLE-BLANK@@ ~*/ +export default BladesActorSheet; +/*~ @@DOUBLE-BLANK@@ ~*/ diff --git a/module/sheets/actor/BladesCrewSheet.js b/module/sheets/actor/BladesCrewSheet.js index 6f85c376..d4b0b007 100644 --- a/module/sheets/actor/BladesCrewSheet.js +++ b/module/sheets/actor/BladesCrewSheet.js @@ -1,13 +1,8 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - import BladesActorSheet from "./BladesActorSheet.js"; import { BladesItemType } from "../../core/constants.js"; +/*~ @@DOUBLE-BLANK@@ ~*/ class BladesCrewSheet extends BladesActorSheet { + /*~ @@DOUBLE-BLANK@@ ~*/ static get defaultOptions() { return foundry.utils.mergeObject(super.defaultOptions, { classes: ["eunos-blades", "sheet", "actor", "crew"], @@ -17,12 +12,16 @@ class BladesCrewSheet extends BladesActorSheet { tabs: [{ navSelector: ".nav-tabs", contentSelector: ".tab-content", initial: "claims" }] }); } + /*~ @@DOUBLE-BLANK@@ ~*/ getData() { const context = super.getData(); + /*~ @@DOUBLE-BLANK@@ ~*/ eLog.checkLog("actor", "[BladesCrewSheet] super.getData()", { ...context }); const { activeSubItems } = this.actor; + /*~ @@DOUBLE-BLANK@@ ~*/ const sheetData = {}; - + /*~ @@DOUBLE-BLANK@@ ~*/ + //~ Assemble embedded actors and items sheetData.preparedItems = Object.assign(context.preparedItems ?? {}, { abilities: activeSubItems.filter((item) => item.type === BladesItemType.crew_ability), playbook: this.actor.playbook, @@ -30,10 +29,12 @@ class BladesCrewSheet extends BladesActorSheet { upgrades: activeSubItems.filter((item) => item.type === BladesItemType.crew_upgrade), preferredOp: activeSubItems.find((item) => item.type === BladesItemType.preferred_op) }); + /*~ @@DOUBLE-BLANK@@ ~*/ sheetData.preparedActors = { members: this.actor.members, contacts: this.actor.contacts }; + /*~ @@DOUBLE-BLANK@@ ~*/ sheetData.tierData = { label: "Tier", dotline: { @@ -45,6 +46,7 @@ class BladesCrewSheet extends BladesActorSheet { iconFullHover: "dot-full-hover.svg" } }; + /*~ @@DOUBLE-BLANK@@ ~*/ sheetData.upgradeData = { dotline: { dotlineClass: "dotline-right", @@ -57,6 +59,7 @@ class BladesCrewSheet extends BladesActorSheet { iconFull: "dot-full.svg" } }; + /*~ @@DOUBLE-BLANK@@ ~*/ sheetData.abilityData = { dotline: { dotlineClass: "dotline-right", @@ -69,6 +72,7 @@ class BladesCrewSheet extends BladesActorSheet { iconFull: "dot-full.svg" } }; + /*~ @@DOUBLE-BLANK@@ ~*/ sheetData.cohortData = { dotline: { dotlineClass: "dotline-right", @@ -81,6 +85,7 @@ class BladesCrewSheet extends BladesActorSheet { iconFull: "dot-full.svg" } }; + /*~ @@DOUBLE-BLANK@@ ~*/ sheetData.repData = { label: "Rep", dotlines: [ @@ -108,6 +113,7 @@ class BladesCrewSheet extends BladesActorSheet { } ] }; + /*~ @@DOUBLE-BLANK@@ ~*/ sheetData.heatData = { label: "Heat", dotline: { @@ -118,6 +124,7 @@ class BladesCrewSheet extends BladesActorSheet { svgEmpty: "full|half|frame" } }; + /*~ @@DOUBLE-BLANK@@ ~*/ sheetData.wantedData = { label: "Wanted", dotline: { @@ -128,23 +135,34 @@ class BladesCrewSheet extends BladesActorSheet { svgEmpty: "frame" } }; + /*~ @@DOUBLE-BLANK@@ ~*/ eLog.checkLog("actor", "[BladesCrewSheet] return getData()", { ...context, ...sheetData }); return { ...context, ...sheetData }; } + /*~ @@DOUBLE-BLANK@@ ~*/ activateListeners(html) { super.activateListeners(html); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Everything below here is only needed if the sheet is editable if (!this.options.editable) { return; } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Update Inventory Item html.find(".item-sheet-open").on("click", (event) => { const element = $(event.currentTarget).parents(".item"); const item = this.actor.items.get(element.data("itemId")); item?.sheet?.render(true); }); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Toggle Hold html.find(".hold-toggle").on("click", () => { this.actor.update({ "system.hold": this.actor.system.hold === "weak" ? "strong" : "weak" }); }); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Toggle Turf html.find(".turf-select").on("click", async (event) => { + /*~ @@DOUBLE-BLANK@@ ~*/ const turf_id = $(event.currentTarget).data("turfId"); const turf_current_status = $(event.currentTarget).data("turfStatus"); this.actor.playbook?.update({ ["system.turfs." + turf_id + ".value"]: !turf_current_status }) @@ -152,4 +170,5 @@ class BladesCrewSheet extends BladesActorSheet { }); } } -export default BladesCrewSheet; \ No newline at end of file +/*~ @@DOUBLE-BLANK@@ ~*/ +export default BladesCrewSheet; diff --git a/module/sheets/actor/BladesFactionSheet.js b/module/sheets/actor/BladesFactionSheet.js index 3acaf313..28aa5824 100644 --- a/module/sheets/actor/BladesFactionSheet.js +++ b/module/sheets/actor/BladesFactionSheet.js @@ -1,14 +1,10 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - +/*~ @@DOUBLE-BLANK@@ ~*/ import BladesActor from "../../BladesActor.js"; import BladesActorSheet from "./BladesActorSheet.js"; import { BladesActorType } from "../../core/constants.js"; +/*~ @@DOUBLE-BLANK@@ ~*/ class BladesFactionSheet extends BladesActorSheet { + /*~ @@DOUBLE-BLANK@@ ~*/ static get defaultOptions() { return foundry.utils.mergeObject(super.defaultOptions, { classes: ["eunos-blades", "sheet", "actor", "faction"], @@ -18,11 +14,13 @@ class BladesFactionSheet extends BladesActorSheet { tabs: [{ navSelector: ".nav-tabs", contentSelector: ".tab-content", initial: "overview" }] }); } + /*~ @@DOUBLE-BLANK@@ ~*/ getData() { const context = super.getData(); if (!BladesActor.IsType(this.actor, BladesActorType.faction)) { return context; } + /*~ @@DOUBLE-BLANK@@ ~*/ const sheetData = { tierData: { "class": "comp-tier comp-vertical comp-teeth", @@ -37,15 +35,18 @@ class BladesFactionSheet extends BladesActorSheet { } } }; + /*~ @@DOUBLE-BLANK@@ ~*/ return { ...context, ...sheetData }; } + /*~ @@DOUBLE-BLANK@@ ~*/ async _onClockAddClick(event) { event.preventDefault(); this.actor.addClock(); } + /*~ @@DOUBLE-BLANK@@ ~*/ async _onClockDeleteClick(event) { event.preventDefault(); const clockID = $(event.currentTarget).data("clockId"); @@ -54,11 +55,16 @@ class BladesFactionSheet extends BladesActorSheet { } this.actor.deleteClock(clockID); } + /*~ @@DOUBLE-BLANK@@ ~*/ activateListeners(html) { super.activateListeners(html); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Everything below here is only needed if the sheet is editable if (!this.options.editable) { return; } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Update Inventory Item html.find(".item-body").on("click", (event) => { const element = $(event.currentTarget).parents(".item"); const item = this.actor.items.get(element.data("itemId")); @@ -70,6 +76,8 @@ class BladesFactionSheet extends BladesActorSheet { html .find(".comp-control.comp-delete-clock") .on("click", this._onClockDeleteClick.bind(this)); + /*~ @@DOUBLE-BLANK@@ ~*/ } } -export default BladesFactionSheet; \ No newline at end of file +/*~ @@DOUBLE-BLANK@@ ~*/ +export default BladesFactionSheet; diff --git a/module/sheets/actor/BladesNPCSheet.js b/module/sheets/actor/BladesNPCSheet.js index c63a8a86..88d7d81f 100644 --- a/module/sheets/actor/BladesNPCSheet.js +++ b/module/sheets/actor/BladesNPCSheet.js @@ -1,41 +1,45 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - +/*~ @@DOUBLE-BLANK@@ ~*/ import BladesActorSheet from "./BladesActorSheet.js"; import U from "../../core/utilities.js"; class BladesNPCSheet extends BladesActorSheet { + /*~ @@DOUBLE-BLANK@@ ~*/ static get defaultOptions() { return foundry.utils.mergeObject(super.defaultOptions, { classes: ["eunos-blades", "sheet", "actor", "npc"], template: "systems/eunos-blades/templates/npc-sheet.hbs", width: 500, height: 400, + // height: "auto", tabs: [{ navSelector: ".nav-tabs", contentSelector: ".tab-content", initial: "description" }] }); } + /*~ @@DOUBLE-BLANK@@ ~*/ getData() { const context = super.getData(); + /*~ @@DOUBLE-BLANK@@ ~*/ context.isSubActor = context.actor.isSubActor; context.parentActor = context.actor.parentActor; context.persona = context.actor.system.persona; context.random = context.actor.system.random; context.secret = context.actor.system.secret; + /*~ @@DOUBLE-BLANK@@ ~*/ const rStatus = { name: { size: 3, label: "Name" }, gender: { size: "half", label: "Gender" }, + /*~ @@DOUBLE-BLANK@@ ~*/ heritage: { size: "third", label: "Heritage" }, background: { size: "third", label: "Background" }, profession: { size: "third", label: "Profession" }, + /*~ @@DOUBLE-BLANK@@ ~*/ appearance: { size: 2, label: "Appearance" }, style: { size: 2, label: "Style" }, quirk: { size: 4, label: "Quirk" }, + /*~ @@DOUBLE-BLANK@@ ~*/ goal: { size: 2, label: "Goal" }, method: { size: 2, label: "Method" }, + /*~ @@DOUBLE-BLANK@@ ~*/ interests: { size: 4, label: "Interests" }, + /*~ @@DOUBLE-BLANK@@ ~*/ trait: { size: "half", label: "Trait" }, trait1: { size: "half", label: null }, trait2: { size: "half", label: null }, @@ -48,23 +52,36 @@ class BladesNPCSheet extends BladesActorSheet { } } } + /*~ @@DOUBLE-BLANK@@ ~*/ console.log({ persona: context.persona, random: context.random, secret: context.secret }); + /*~ @@DOUBLE-BLANK@@ ~*/ return context; } + /*~ @@DOUBLE-BLANK@@ ~*/ activateListeners(html) { super.activateListeners(html); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Everything below here is only needed if the sheet is editable if (!this.options.editable) { return; } + /*~ @@DOUBLE-BLANK@@ ~*/ html.find(".gm-alert-header").on("click", async (event) => { event.preventDefault(); + /*~ @@DOUBLE-BLANK@@ ~*/ this.actor.clearParentActor(); }); - + /*~ @@DOUBLE-BLANK@@ ~*/ + //~ Configure Tagify input elements + // const inputElement = document.querySelector('input[name="system.harm.heavy.one"]'); + // if (inputElement instanceof HTMLInputElement) { new Tagify(inputElement, {}) } else { console.log("Not an HTMLInputElement")} + /*~ @@DOUBLE-BLANK@@ ~*/ + //~ Enable Randomize Button for NPCs html.find("[data-action=\"randomize\"").on("click", (event) => { this.actor.updateRandomizers(); }); - + /*~ @@DOUBLE-BLANK@@ ~*/ + //~ Enable status toggles for NPC subactors html.find(".comp-status-toggle") .on("click", () => { const { tags } = this.actor; @@ -88,6 +105,8 @@ class BladesNPCSheet extends BladesActorSheet { .on("contextmenu", () => { this.actor.update({ "system.status": 0 }); }); + /*~ @@DOUBLE-BLANK@@ ~*/ } } -export default BladesNPCSheet; \ No newline at end of file +/*~ @@DOUBLE-BLANK@@ ~*/ +export default BladesNPCSheet; diff --git a/module/sheets/actor/BladesPCSheet.js b/module/sheets/actor/BladesPCSheet.js index 22b83e88..3ffee4d0 100644 --- a/module/sheets/actor/BladesPCSheet.js +++ b/module/sheets/actor/BladesPCSheet.js @@ -1,16 +1,12 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - +/*~ @@DOUBLE-BLANK@@ ~*/ import C, { BladesActorType, BladesItemType, AttributeTrait, Tag, BladesPhase } from "../../core/constants.js"; import U from "../../core/utilities.js"; import BladesActorSheet from "./BladesActorSheet.js"; import { BladesActor } from "../../documents/BladesActorProxy.js"; import BladesGMTrackerSheet from "../item/BladesGMTrackerSheet.js"; +/*~ @@DOUBLE-BLANK@@ ~*/ class BladesPCSheet extends BladesActorSheet { + /*~ @@DOUBLE-BLANK@@ ~*/ static get defaultOptions() { return foundry.utils.mergeObject(super.defaultOptions, { classes: ["eunos-blades", "sheet", "actor", "pc"], @@ -20,15 +16,19 @@ class BladesPCSheet extends BladesActorSheet { tabs: [{ navSelector: ".nav-tabs", contentSelector: ".tab-content", initial: "abilities" }] }); } + /*~ @@DOUBLE-BLANK@@ ~*/ static Initialize() { Actors.registerSheet("blades", BladesPCSheet, { types: ["pc"], makeDefault: true }); + /*~ @@DOUBLE-BLANK@@ ~*/ Hooks.on("dropActorSheetData", async (parentActor, _, { uuid }) => { const doc = await fromUuid(uuid); if (doc instanceof BladesActor) { if (parentActor.type === BladesActorType.crew && doc.type === BladesActorType.pc) { + // Dropping a PC onto a Crew Sheet: Add Crew to PC doc.addSubActor(parentActor); } else if (parentActor.type === BladesActorType.pc && doc.type === BladesActorType.crew) { + // Dropping a Crew onto a PC Sheet: Add parentActor.addSubActor(doc); } } @@ -38,14 +38,20 @@ class BladesPCSheet extends BladesActorSheet { "systems/eunos-blades/templates/parts/clock-sheet-row.hbs" ]); } + /*~ @@DOUBLE-BLANK@@ ~*/ getData() { const context = super.getData(); + /*~ @@DOUBLE-BLANK@@ ~*/ const { activeSubItems, activeSubActors } = this.actor; + /*~ @@DOUBLE-BLANK@@ ~*/ const sheetData = {}; + /*~ @@DOUBLE-BLANK@@ ~*/ + // ~ Assemble embedded actors and items sheetData.preparedItems = Object.assign(context.preparedItems ?? {}, { abilities: activeSubItems .filter((item) => item.type === BladesItemType.ability) .map((item) => { + // ~ Assign dotlines to abilities with usage data if (item.system.uses_per_score.max) { Object.assign(item, { inRuleDotline: { @@ -67,6 +73,7 @@ class BladesPCSheet extends BladesActorSheet { loadout: activeSubItems .filter((item) => item.type === BladesItemType.gear) .map((item) => { + // Assign load and usage data to gear if (item.system.load) { Object.assign(item, { numberCircle: item.system.load, @@ -90,6 +97,7 @@ class BladesPCSheet extends BladesActorSheet { }), playbook: this.actor.playbook }); + /*~ @@DOUBLE-BLANK@@ ~*/ sheetData.preparedActors = { crew: activeSubActors .find((actor) => actor.type === BladesActorType.crew), @@ -98,8 +106,10 @@ class BladesPCSheet extends BladesActorSheet { acquaintances: activeSubActors .filter((actor) => actor.hasTag(Tag.NPC.Acquaintance)) }; + /*~ @@DOUBLE-BLANK@@ ~*/ sheetData.hasVicePurveyor = Boolean(this.actor.playbook?.hasTag(Tag.Gear.Advanced) === false && activeSubItems.find((item) => item.type === BladesItemType.vice)); + /*~ @@DOUBLE-BLANK@@ ~*/ sheetData.healing_clock = { display: "Healing", target: "system.healing.value", @@ -110,6 +120,7 @@ class BladesPCSheet extends BladesActorSheet { ...this.actor.system.healing, id: randomID() }; + /*~ @@DOUBLE-BLANK@@ ~*/ sheetData.stashData = { label: "Stash:", dotline: { @@ -124,6 +135,7 @@ class BladesPCSheet extends BladesActorSheet { altIconStep: 10 } }; + /*~ @@DOUBLE-BLANK@@ ~*/ sheetData.stressData = { label: this.actor.system.stress.name, dotline: { @@ -135,6 +147,7 @@ class BladesPCSheet extends BladesActorSheet { svgEmpty: "full|half|frame" } }; + /*~ @@DOUBLE-BLANK@@ ~*/ if (BladesActor.IsType(this.actor, BladesActorType.pc)) { sheetData.traumaData = { label: this.actor.system.trauma.name, @@ -176,6 +189,7 @@ class BladesPCSheet extends BladesActorSheet { } }; } + /*~ @@DOUBLE-BLANK@@ ~*/ sheetData.abilityData = { dotline: { dotlineClass: "dotline-right dotline-glow", @@ -188,18 +202,21 @@ class BladesPCSheet extends BladesActorSheet { iconFull: "dot-full.svg" } }; + /*~ @@DOUBLE-BLANK@@ ~*/ sheetData.loadData = { curLoad: this.actor.currentLoad, selLoadCount: this.actor.system.loadout.levels[U.lCase(this.actor.system.loadout.selected)], options: C.Loadout.selections, selected: this.actor.system.loadout.selected ?? "" }; + /*~ @@DOUBLE-BLANK@@ ~*/ sheetData.armor = Object.fromEntries(Object.entries(this.actor.system.armor.active) .filter(([, isActive]) => isActive) .map(([armor]) => [ armor, this.actor.system.armor.checked[armor] ])); + /*~ @@DOUBLE-BLANK@@ ~*/ sheetData.attributeData = {}; const attrEntries = Object.entries(this.actor.system.attributes); for (const [attribute, attrData] of attrEntries) { @@ -218,27 +235,35 @@ class BladesPCSheet extends BladesActorSheet { }; } } + /*~ @@DOUBLE-BLANK@@ ~*/ sheetData.gatherInfoTooltip = (new Handlebars.SafeString([ "

Gathering Information: Questions to Consider

", "
    ", ...Object.values(this.actor.system.gather_info ?? []).map((line) => `
  • ${line}
  • `) ?? [], "
" ].join(""))).toString(); + /*~ @@DOUBLE-BLANK@@ ~*/ eLog.checkLog("Attribute", "[BladesPCSheet] attributeData", { attributeData: sheetData.attributeData }); + /*~ @@DOUBLE-BLANK@@ ~*/ eLog.checkLog("actor", "[BladesPCSheet] getData()", { ...context, ...sheetData }); + /*~ @@DOUBLE-BLANK@@ ~*/ return { ...context, ...sheetData }; } + /*~ @@DOUBLE-BLANK@@ ~*/ get activeArmor() { return Object.keys(U.objFilter(this.actor.system.armor.active, (val) => val === true)); } + /*~ @@DOUBLE-BLANK@@ ~*/ get checkedArmor() { return Object.keys(U.objFilter(this.actor.system.armor.checked, (val, key) => val === true && this.actor.system.armor.active[key] === true)); } + /*~ @@DOUBLE-BLANK@@ ~*/ get uncheckedArmor() { return Object.keys(U.objFilter(this.actor.system.armor.active, (val, key) => val === true && this.actor.system.armor.checked[key] === false)); } + /*~ @@DOUBLE-BLANK@@ ~*/ _getHoverArmor() { if (!this.activeArmor.length) { return false; @@ -251,6 +276,7 @@ class BladesPCSheet extends BladesActorSheet { } return "special"; } + /*~ @@DOUBLE-BLANK@@ ~*/ _getClickArmor() { if (!this.uncheckedArmor.length) { return false; @@ -263,6 +289,7 @@ class BladesPCSheet extends BladesActorSheet { } return "special"; } + /*~ @@DOUBLE-BLANK@@ ~*/ _getContextMenuArmor() { if (!this.checkedArmor.length) { return false; @@ -275,6 +302,7 @@ class BladesPCSheet extends BladesActorSheet { } return "special"; } + /*~ @@DOUBLE-BLANK@@ ~*/ async _onAdvanceClick(event) { event.preventDefault(); super._onAdvanceClick(event); @@ -283,12 +311,19 @@ class BladesPCSheet extends BladesActorSheet { await this.actor.advanceAttribute(action); } } + /*~ @@DOUBLE-BLANK@@ ~*/ activateListeners(html) { + /*~ @@DOUBLE-BLANK@@ ~*/ super.activateListeners(html); + /*~ @@DOUBLE-BLANK@@ ~*/ + // ~ Everything below here is only needed if the sheet is editable if (!this.options.editable) { return; } + /*~ @@DOUBLE-BLANK@@ ~*/ const self = this; + /*~ @@DOUBLE-BLANK@@ ~*/ + // ~ Armor Control html.find(".main-armor-control").on({ click() { const targetArmor = self._getClickArmor(); @@ -346,6 +381,9 @@ class BladesPCSheet extends BladesActorSheet { $(this).siblings(".svg-armor.armor-special").removeClass("hover-over"); } }); + /*~ @@DOUBLE-BLANK@@ ~*/ } } -export default BladesPCSheet; \ No newline at end of file +/*~ @@DOUBLE-BLANK@@ ~*/ +export default BladesPCSheet; +/*~ @@DOUBLE-BLANK@@ ~*/ diff --git a/module/sheets/actor/blades-actor-sheet.js b/module/sheets/actor/blades-actor-sheet.js deleted file mode 100644 index f0bd4399..00000000 --- a/module/sheets/actor/blades-actor-sheet.js +++ /dev/null @@ -1,346 +0,0 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - -import C, { BladesActorType, BladesItemType, Attribute, Tag, BladesPhase } from "../../core/constants.js"; -import U from "../../core/utilities.js"; -import BladesSheet from "./blades-sheet.js"; -import BladesActor from "../../blades-actor.js"; -import BladesTrackerSheet from "../item/blades-tracker-sheet.js"; - -class BladesActorSheet extends BladesSheet { - static get defaultOptions() { - return foundry.utils.mergeObject(super.defaultOptions, { - classes: ["eunos-blades", "sheet", "actor", "pc"], - template: "systems/eunos-blades/templates/actor-sheet.hbs", - width: 775, - height: 775, - tabs: [{ navSelector: ".nav-tabs", contentSelector: ".tab-content", initial: "abilities" }] - }); - } - static Initialize() { - Actors.registerSheet("blades", BladesActorSheet, { types: ["pc"], makeDefault: true }); - Hooks.on("dropActorSheetData", async (parentActor, _, { uuid }) => { - const doc = await fromUuid(uuid); - if (doc instanceof BladesActor) { - if (parentActor.type === BladesActorType.crew && doc.type === BladesActorType.pc) { - doc.addSubActor(parentActor); - } - else if (parentActor.type === BladesActorType.pc && doc.type === BladesActorType.crew) { - parentActor.addSubActor(doc); - } - } - }); - return loadTemplates([ - "systems/eunos-blades/templates/items/clock_keeper-sheet.hbs", - "systems/eunos-blades/templates/parts/clock-sheet-row.hbs" - ]); - } - getData() { - const context = super.getData(); - const { activeSubItems, activeSubActors } = this.actor; - const sheetData = {}; - - sheetData.preparedItems = Object.assign(context.preparedItems ?? {}, { - abilities: activeSubItems - .filter((item) => item.type === BladesItemType.ability) - .map((item) => { - if (item.system.uses_per_score.max) { - Object.assign(item, { - inRuleDotline: { - data: item.system.uses_per_score, - dotlineLabel: "Uses", - target: "item.system.uses_per_score.value", - iconEmpty: "dot-empty.svg", - iconEmptyHover: "dot-empty-hover.svg", - iconFull: "dot-full.svg", - iconFullHover: "dot-full-hover.svg" - } - }); - } - return item; - }), - background: activeSubItems.find((item) => item.type === BladesItemType.background), - heritage: activeSubItems.find((item) => item.type === BladesItemType.heritage), - vice: activeSubItems.find((item) => item.type === BladesItemType.vice), - loadout: activeSubItems.filter((item) => item.type === BladesItemType.gear).map((item) => { - if (item.system.load) { - Object.assign(item, { - numberCircle: item.system.load, - numberCircleClass: "item-load" - }); - } - if (item.system.uses_per_score.max) { - Object.assign(item, { - inRuleDotline: { - data: item.system.uses_per_score, - dotlineLabel: "Uses", - target: "item.system.uses_per_score.value", - iconEmpty: "dot-empty.svg", - iconEmptyHover: "dot-empty-hover.svg", - iconFull: "dot-full.svg", - iconFullHover: "dot-full-hover.svg" - } - }); - } - return item; - }), - playbook: this.actor.playbook - }); - sheetData.preparedActors = { - crew: activeSubActors.find((actor) => actor.type === BladesActorType.crew), - vice_purveyor: activeSubActors.find((actor) => actor.hasTag(Tag.NPC.VicePurveyor)), - acquaintances: activeSubActors.filter((actor) => actor.hasTag(Tag.NPC.Acquaintance)) - }; - sheetData.hasVicePurveyor = Boolean(this.actor.playbook?.hasTag(Tag.Gear.Advanced) === false - && activeSubItems.find((item) => item.type === BladesItemType.vice)); - sheetData.healing_clock = { - display: "Healing", - target: "system.healing.value", - color: "white", - isVisible: true, - isNameVisible: false, - isActive: false, - ...this.actor.system.healing, - id: randomID() - }; - sheetData.stashData = { - label: "Stash:", - dotline: { - data: this.actor.system.stash, - target: "system.stash.value", - iconEmpty: "coin-empty.svg", - iconEmptyHover: "coin-empty-hover.svg", - iconFull: "coin-full.svg", - iconFullHover: "coin-full-hover.svg", - altIconFull: "coin-ten.svg", - altIconFullHover: "coin-ten-hover.svg", - altIconStep: 10 - } - }; - sheetData.stressData = { - label: this.actor.system.stress.name, - dotline: { - data: this.actor.system.stress, - dotlineClass: this.actor.system.stress.max >= 13 ? "narrow-stress" : "", - target: "system.stress.value", - svgKey: "teeth.tall", - svgFull: "full|half|frame", - svgEmpty: "full|half|frame" - } - }; - if (BladesActor.IsType(this.actor, BladesActorType.pc)) { - sheetData.traumaData = { - label: this.actor.system.trauma.name, - dotline: { - data: { value: this.actor.trauma, max: this.actor.system.trauma.max }, - svgKey: "teeth.short", - svgFull: "full|frame", - svgEmpty: "frame", - isLocked: true - }, - compContainer: { - "class": "comp-trauma-conditions comp-vertical full-width", - "blocks": [ - this.actor.traumaList.slice(0, Math.ceil(this.actor.traumaList.length / 2)) - .map((tName) => ({ - checkLabel: tName, - checkClasses: { - active: "comp-toggle-red", - inactive: "comp-toggle-grey" - }, - checkTarget: `system.trauma.checked.${tName}`, - checkValue: this.actor.system.trauma.checked[tName] ?? false, - tooltip: C.TraumaTooltips[tName], - tooltipClass: "tooltip-trauma" - })), - this.actor.traumaList.slice(Math.ceil(this.actor.traumaList.length / 2)) - .map((tName) => ({ - checkLabel: tName, - checkClasses: { - active: "comp-toggle-red", - inactive: "comp-toggle-grey" - }, - checkTarget: `system.trauma.checked.${tName}`, - checkValue: this.actor.system.trauma.checked[tName] ?? false, - tooltip: C.TraumaTooltips[tName], - tooltipClass: "tooltip-trauma" - })) - ] - } - }; - } - sheetData.abilityData = { - dotline: { - dotlineClass: "dotline-right dotline-glow", - data: { - value: this.actor.getAvailableAdvancements("Ability"), - max: this.actor.getAvailableAdvancements("Ability") - }, - dotlineLabel: "Available Abilities", - isLocked: true, - iconFull: "dot-full.svg" - } - }; - sheetData.loadData = { - curLoad: this.actor.currentLoad, - selLoadCount: this.actor.system.loadout.levels[U.lCase(game.i18n.localize(this.actor.system.loadout.selected.toString()))], - selections: C.Loadout.selections, - selLoadLevel: this.actor.system.loadout.selected.toString() - }; - sheetData.armor = Object.fromEntries(Object.entries(this.actor.system.armor.active) - .filter(([, isActive]) => isActive) - .map(([armor]) => [armor, this.actor.system.armor.checked[armor]])); - sheetData.attributeData = {}; - const attrEntries = Object.entries(this.actor.system.attributes); - for (const [attribute, attrData] of attrEntries) { - sheetData.attributeData[attribute] = { - tooltip: C.AttributeTooltips[attribute], - actions: {} - }; - const actionEntries = Object.entries(attrData); - for (const [action, actionData] of actionEntries) { - sheetData.attributeData[attribute].actions[action] = { - tooltip: C.ActionTooltips[action], - value: actionData.value, - max: BladesTrackerSheet.Get().phase === BladesPhase.CharGen ? 2 : this.actor.system.attributes[attribute][action].max - }; - } - } - sheetData.gatherInfoTooltip = (new Handlebars.SafeString([ - "

Gathering Information: Questions to Consider

", - "
    ", - ...Object.values(this.actor.system.gather_info ?? []).map((line) => `
  • ${line}
  • `) ?? [], - "
" - ].join(""))).toString(); - eLog.checkLog("Attribute", "[BladesActorSheet] attributeData", { attributeData: sheetData.attributeData }); - eLog.checkLog("actor", "[BladesActorSheet] getData()", { ...context, ...sheetData }); - return { ...context, ...sheetData }; - } - get activeArmor() { - return Object.keys(U.objFilter(this.actor.system.armor.active, (val) => val === true)); - } - get checkedArmor() { - return Object.keys(U.objFilter(this.actor.system.armor.checked, (val, key) => val === true - && this.actor.system.armor.active[key] === true)); - } - get uncheckedArmor() { - return Object.keys(U.objFilter(this.actor.system.armor.active, (val, key) => val === true - && this.actor.system.armor.checked[key] === false)); - } - _getHoverArmor() { - if (!this.activeArmor.length) { - return false; - } - if (this.activeArmor.includes("heavy")) { - return this.checkedArmor.includes("heavy") ? "light" : "heavy"; - } - else if (this.activeArmor.includes("light")) { - return "light"; - } - return "special"; - } - _getClickArmor() { - if (!this.uncheckedArmor.length) { - return false; - } - if (this.uncheckedArmor.includes("heavy")) { - return "heavy"; - } - if (this.uncheckedArmor.includes("light")) { - return "light"; - } - return "special"; - } - _getContextMenuArmor() { - if (!this.checkedArmor.length) { - return false; - } - if (this.checkedArmor.includes("light")) { - return "light"; - } - if (this.checkedArmor.includes("heavy")) { - return "heavy"; - } - return "special"; - } - async _onAdvanceClick(event) { - event.preventDefault(); - super._onAdvanceClick(event); - const action = $(event.currentTarget).data("action").replace(/^advance-/, ""); - if (action in Attribute) { - this.actor.advanceAttribute(action); - } - } - activateListeners(html) { - super.activateListeners(html); - - if (!this.options.editable) { - return; - } - const self = this; - - html.find(".main-armor-control").on({ - click: function () { - const targetArmor = self._getClickArmor(); - if (!targetArmor) { - return; - } - self.actor.update({ [`system.armor.checked.${targetArmor}`]: true }); - }, - contextmenu: function () { - const targetArmor = self._getContextMenuArmor(); - if (!targetArmor) { - return; - } - self.actor.update({ [`system.armor.checked.${targetArmor}`]: false }); - }, - mouseenter: function () { - const targetArmor = self._getHoverArmor(); - eLog.log4("Mouse Enter", targetArmor, this, $(this), $(this).next()); - if (!targetArmor) { - return; - } - $(this).siblings(`.svg-armor.armor-${targetArmor}`).addClass("hover-over"); - }, - mouseleave: function () { - const targetArmor = self._getHoverArmor(); - if (!targetArmor) { - return; - } - $(this).siblings(`.svg-armor.armor-${targetArmor}`).removeClass("hover-over"); - } - }); - html.find(".special-armor-control").on({ - click: function () { - if (!self.activeArmor.includes("special")) { - return; - } - self.actor.update({ ["system.armor.checked.special"]: self.uncheckedArmor.includes("special") }); - }, - contextmenu: function () { - if (!self.activeArmor.includes("special")) { - return; - } - self.actor.update({ ["system.armor.checked.special"]: self.uncheckedArmor.includes("special") }); - }, - mouseenter: function () { - if (!self.activeArmor.includes("special") || self.activeArmor.length === 1) { - return; - } - $(this).siblings(".svg-armor.armor-special").addClass("hover-over"); - }, - mouseleave: function () { - if (!self.activeArmor.includes("special") || self.activeArmor.length === 1) { - return; - } - $(this).siblings(".svg-armor.armor-special").removeClass("hover-over"); - } - }); - } -} -export default BladesActorSheet; -//# sourceMappingURL=blades-actor-sheet.js.map \ No newline at end of file diff --git a/module/sheets/actor/blades-crew-sheet.js b/module/sheets/actor/blades-crew-sheet.js deleted file mode 100644 index 26d5f18d..00000000 --- a/module/sheets/actor/blades-crew-sheet.js +++ /dev/null @@ -1,156 +0,0 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - -import BladesSheet from "./blades-sheet.js"; -import { BladesItemType } from "../../core/constants.js"; -class BladesCrewSheet extends BladesSheet { - static get defaultOptions() { - return foundry.utils.mergeObject(super.defaultOptions, { - classes: ["eunos-blades", "sheet", "actor", "crew"], - template: "systems/eunos-blades/templates/crew-sheet.hbs", - width: 940, - height: 820, - tabs: [{ navSelector: ".nav-tabs", contentSelector: ".tab-content", initial: "claims" }] - }); - } - getData() { - const context = super.getData(); - eLog.checkLog("actor", "[BladesCrewSheet] super.getData()", { ...context }); - const { activeSubItems } = this.actor; - const sheetData = {}; - - sheetData.preparedItems = Object.assign(context.preparedItems ?? {}, { - abilities: activeSubItems.filter((item) => item.type === BladesItemType.crew_ability), - playbook: this.actor.playbook, - reputation: activeSubItems.find((item) => item.type === BladesItemType.crew_reputation), - upgrades: activeSubItems.filter((item) => item.type === BladesItemType.crew_upgrade), - preferredOp: activeSubItems.find((item) => item.type === BladesItemType.preferred_op) - }); - sheetData.preparedActors = { - members: this.actor.members, - contacts: this.actor.contacts - }; - sheetData.tierData = { - label: "Tier", - dotline: { - data: this.actor.system.tier, - target: "system.tier.value", - iconEmpty: "dot-empty.svg", - iconEmptyHover: "dot-empty-hover.svg", - iconFull: "dot-full.svg", - iconFullHover: "dot-full-hover.svg" - } - }; - sheetData.upgradeData = { - dotline: { - dotlineClass: "dotline-right", - data: { - value: this.actor.availableUpgradePoints, - max: this.actor.availableUpgradePoints - }, - dotlineLabel: "Available Upgrade Points", - isLocked: true, - iconFull: "dot-full.svg" - } - }; - sheetData.abilityData = { - dotline: { - dotlineClass: "dotline-right", - data: { - value: this.actor.availableAbilityPoints, - max: this.actor.availableAbilityPoints - }, - dotlineLabel: "Available Ability Points", - isLocked: true, - iconFull: "dot-full.svg" - } - }; - sheetData.cohortData = { - dotline: { - dotlineClass: "dotline-right", - data: { - value: this.actor.availableCohortPoints, - max: this.actor.availableCohortPoints - }, - dotlineLabel: "Available Cohort Points", - isLocked: true, - iconFull: "dot-full.svg" - } - }; - sheetData.repData = { - label: "Rep", - dotlines: [ - { - data: { - value: Math.min(this.actor.system.rep.value, this.actor.system.rep.max - this.actor.turfCount), - max: this.actor.system.rep.max - this.actor.turfCount - }, - target: "system.rep.value", - svgKey: "teeth.tall", - svgFull: "full|half|frame", - svgEmpty: "full|half|frame" - }, - { - data: { - value: this.actor.turfCount, - max: this.actor.turfCount - }, - target: "none", - svgKey: "teeth.tall", - svgFull: "full|half|frame", - svgEmpty: "full|half|frame", - dotlineClass: "flex-row-reverse", - isLocked: true - } - ] - }; - sheetData.heatData = { - label: "Heat", - dotline: { - data: this.actor.system.heat, - target: "system.heat.value", - svgKey: "teeth.tall", - svgFull: "full|half|frame", - svgEmpty: "full|half|frame" - } - }; - sheetData.wantedData = { - label: "Wanted", - dotline: { - data: this.actor.system.wanted, - target: "system.wanted.value", - svgKey: "teeth.short", - svgFull: "full|frame", - svgEmpty: "frame" - } - }; - eLog.checkLog("actor", "[BladesCrewSheet] return getData()", { ...context, ...sheetData }); - return { ...context, ...sheetData }; - } - activateListeners(html) { - super.activateListeners(html); - if (!this.options.editable) { - return; - } - html.find(".item-sheet-open").on("click", (event) => { - const element = $(event.currentTarget).parents(".item"); - const item = this.actor.items.get(element.data("itemId")); - item?.sheet?.render(true); - }); - html.find(".hold-toggle").on("click", () => { - this.actor.update({ "system.hold": this.actor.system.hold === "weak" ? "strong" : "weak" }); - }); - html.find(".turf-select").on("click", async (event) => { - const turf_id = $(event.currentTarget).data("turfId"); - const turf_current_status = $(event.currentTarget).data("turfStatus"); - this.actor.playbook?.update({ ["system.turfs." + turf_id + ".value"]: !turf_current_status }) - .then(() => this.render(false)); - }); - } -} -export default BladesCrewSheet; -//# sourceMappingURL=blades-crew-sheet.js.map \ No newline at end of file diff --git a/module/sheets/actor/blades-faction-sheet.js b/module/sheets/actor/blades-faction-sheet.js deleted file mode 100644 index 05d8ba73..00000000 --- a/module/sheets/actor/blades-faction-sheet.js +++ /dev/null @@ -1,76 +0,0 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - -import BladesActor from "../../blades-actor.js"; -import BladesSheet from "./blades-sheet.js"; -import { BladesActorType } from "../../core/constants.js"; -class BladesFactionSheet extends BladesSheet { - static get defaultOptions() { - return foundry.utils.mergeObject(super.defaultOptions, { - classes: ["eunos-blades", "sheet", "actor", "faction"], - template: "systems/eunos-blades/templates/faction-sheet.hbs", - width: 900, - height: "auto", - tabs: [{ navSelector: ".nav-tabs", contentSelector: ".tab-content", initial: "overview" }] - }); - } - getData() { - const context = super.getData(); - if (!BladesActor.IsType(this.actor, BladesActorType.faction)) { - return context; - } - const sheetData = { - tierData: { - "class": "comp-tier comp-vertical comp-teeth", - "label": "Tier", - "labelClass": "filled-label full-width", - "dotline": { - data: this.actor.system.tier, - target: "system.tier.value", - svgKey: "teeth.tall", - svgFull: "full|half|frame", - svgEmpty: "full|half|frame" - } - } - }; - return { - ...context, - ...sheetData - }; - } - async _onClockAddClick(event) { - event.preventDefault(); - this.actor.addClock(); - } - async _onClockDeleteClick(event) { - event.preventDefault(); - const clockID = $(event.currentTarget).data("clockId"); - if (!clockID) { - return; - } - this.actor.deleteClock(clockID); - } - activateListeners(html) { - super.activateListeners(html); - if (!this.options.editable) { - return; - } - html.find(".item-body").on("click", (event) => { - const element = $(event.currentTarget).parents(".item"); - const item = this.actor.items.get(element.data("itemId")); - item?.sheet?.render(true); - }); - html - .find(".comp-control.comp-add-clock") - .on("click", this._onClockAddClick.bind(this)); - html - .find(".comp-control.comp-delete-clock") - .on("click", this._onClockDeleteClick.bind(this)); - } -} -export default BladesFactionSheet; -//# sourceMappingURL=blades-faction-sheet.js.map \ No newline at end of file diff --git a/module/sheets/actor/blades-npc-sheet.js b/module/sheets/actor/blades-npc-sheet.js deleted file mode 100644 index e9a3913d..00000000 --- a/module/sheets/actor/blades-npc-sheet.js +++ /dev/null @@ -1,94 +0,0 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - -import BladesSheet from "./blades-sheet.js"; -import U from "../../core/utilities.js"; -class BladesNPCSheet extends BladesSheet { - static get defaultOptions() { - return foundry.utils.mergeObject(super.defaultOptions, { - classes: ["eunos-blades", "sheet", "actor", "npc"], - template: "systems/eunos-blades/templates/npc-sheet.hbs", - width: 500, - height: 400, - tabs: [{ navSelector: ".nav-tabs", contentSelector: ".tab-content", initial: "description" }] - }); - } - getData() { - const context = super.getData(); - context.isSubActor = context.actor.isSubActor; - context.parentActor = context.actor.parentActor; - context.persona = context.actor.system.persona; - context.random = context.actor.system.random; - context.secret = context.actor.system.secret; - const rStatus = { - name: { size: 3, label: "Name" }, - gender: { size: "half", label: "Gender" }, - heritage: { size: "third", label: "Heritage" }, - background: { size: "third", label: "Background" }, - profession: { size: "third", label: "Profession" }, - appearance: { size: 2, label: "Appearance" }, - style: { size: 2, label: "Style" }, - quirk: { size: 4, label: "Quirk" }, - goal: { size: 2, label: "Goal" }, - method: { size: 2, label: "Method" }, - interests: { size: 4, label: "Interests" }, - trait: { size: "half", label: "Trait" }, - trait1: { size: "half", label: null }, - trait2: { size: "half", label: null }, - trait3: { size: "half", label: null } - }; - for (const cat of ["persona", "random", "secret"]) { - for (const [key] of Object.entries(context[cat])) { - if (key in rStatus) { - Object.assign(context[cat][key], rStatus[key]); - } - } - } - console.log({ persona: context.persona, random: context.random, secret: context.secret }); - return context; - } - activateListeners(html) { - super.activateListeners(html); - if (!this.options.editable) { - return; - } - html.find(".gm-alert-header").on("click", async (event) => { - event.preventDefault(); - this.actor.clearParentActor(); - }); - - html.find("[data-action=\"randomize\"").on("click", (event) => { - this.actor.updateRandomizers(); - }); - - html.find(".comp-status-toggle") - .on("click", () => { - const { tags } = this.actor; - if (this.actor.system.status === 1) { - U.remove(tags, "Friend"); - tags.push("Rival"); - this.actor.update({ - "system.status": -1, - "system.tags": U.unique(tags) - }); - } - else { - U.remove(tags, "Rival"); - tags.push("Friend"); - this.actor.update({ - "system.status": 1, - "system.tags": U.unique(tags) - }); - } - }) - .on("contextmenu", () => { - this.actor.update({ "system.status": 0 }); - }); - } -} -export default BladesNPCSheet; -//# sourceMappingURL=blades-npc-sheet.js.map \ No newline at end of file diff --git a/module/sheets/actor/blades-pc-sheet.js b/module/sheets/actor/blades-pc-sheet.js deleted file mode 100644 index d783bf3b..00000000 --- a/module/sheets/actor/blades-pc-sheet.js +++ /dev/null @@ -1,345 +0,0 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - -import C, { BladesActorType, BladesItemType, Attribute, Tag, BladesPhase } from "../../core/constants.js"; -import U from "../../core/utilities.js"; -import BladesSheet from "./blades-sheet.js"; -import { BladesActor } from "../../documents/blades-actor-proxy.js"; -import BladesTrackerSheet from "../item/blades-tracker-sheet.js"; -class BladesPCSheet extends BladesSheet { - static get defaultOptions() { - return foundry.utils.mergeObject(super.defaultOptions, { - classes: ["eunos-blades", "sheet", "actor", "pc"], - template: "systems/eunos-blades/templates/actor-sheet.hbs", - width: 775, - height: 775, - tabs: [{ navSelector: ".nav-tabs", contentSelector: ".tab-content", initial: "abilities" }] - }); - } - static Initialize() { - Actors.registerSheet("blades", BladesPCSheet, { types: ["pc"], makeDefault: true }); - Hooks.on("dropActorSheetData", async (parentActor, _, { uuid }) => { - const doc = await fromUuid(uuid); - if (doc instanceof BladesActor) { - if (parentActor.type === BladesActorType.crew && doc.type === BladesActorType.pc) { - doc.addSubActor(parentActor); - } - else if (parentActor.type === BladesActorType.pc && doc.type === BladesActorType.crew) { - parentActor.addSubActor(doc); - } - } - }); - return loadTemplates([ - "systems/eunos-blades/templates/items/clock_keeper-sheet.hbs", - "systems/eunos-blades/templates/parts/clock-sheet-row.hbs" - ]); - } - getData() { - const context = super.getData(); - const { activeSubItems, activeSubActors } = this.actor; - const sheetData = {}; - - sheetData.preparedItems = Object.assign(context.preparedItems ?? {}, { - abilities: activeSubItems - .filter((item) => item.type === BladesItemType.ability) - .map((item) => { - if (item.system.uses_per_score.max) { - Object.assign(item, { - inRuleDotline: { - data: item.system.uses_per_score, - dotlineLabel: "Uses", - target: "item.system.uses_per_score.value", - iconEmpty: "dot-empty.svg", - iconEmptyHover: "dot-empty-hover.svg", - iconFull: "dot-full.svg", - iconFullHover: "dot-full-hover.svg" - } - }); - } - return item; - }), - background: activeSubItems.find((item) => item.type === BladesItemType.background), - heritage: activeSubItems.find((item) => item.type === BladesItemType.heritage), - vice: activeSubItems.find((item) => item.type === BladesItemType.vice), - loadout: activeSubItems.filter((item) => item.type === BladesItemType.gear).map((item) => { - if (item.system.load) { - Object.assign(item, { - numberCircle: item.system.load, - numberCircleClass: "item-load" - }); - } - if (item.system.uses_per_score.max) { - Object.assign(item, { - inRuleDotline: { - data: item.system.uses_per_score, - dotlineLabel: "Uses", - target: "item.system.uses_per_score.value", - iconEmpty: "dot-empty.svg", - iconEmptyHover: "dot-empty-hover.svg", - iconFull: "dot-full.svg", - iconFullHover: "dot-full-hover.svg" - } - }); - } - return item; - }), - playbook: this.actor.playbook - }); - sheetData.preparedActors = { - crew: activeSubActors.find((actor) => actor.type === BladesActorType.crew), - vice_purveyor: activeSubActors.find((actor) => actor.hasTag(Tag.NPC.VicePurveyor)), - acquaintances: activeSubActors.filter((actor) => actor.hasTag(Tag.NPC.Acquaintance)) - }; - sheetData.hasVicePurveyor = Boolean(this.actor.playbook?.hasTag(Tag.Gear.Advanced) === false - && activeSubItems.find((item) => item.type === BladesItemType.vice)); - sheetData.healing_clock = { - display: "Healing", - target: "system.healing.value", - color: "white", - isVisible: true, - isNameVisible: false, - isActive: false, - ...this.actor.system.healing, - id: randomID() - }; - sheetData.stashData = { - label: "Stash:", - dotline: { - data: this.actor.system.stash, - target: "system.stash.value", - iconEmpty: "coin-empty.svg", - iconEmptyHover: "coin-empty-hover.svg", - iconFull: "coin-full.svg", - iconFullHover: "coin-full-hover.svg", - altIconFull: "coin-ten.svg", - altIconFullHover: "coin-ten-hover.svg", - altIconStep: 10 - } - }; - sheetData.stressData = { - label: this.actor.system.stress.name, - dotline: { - data: this.actor.system.stress, - dotlineClass: this.actor.system.stress.max >= 13 ? "narrow-stress" : "", - target: "system.stress.value", - svgKey: "teeth.tall", - svgFull: "full|half|frame", - svgEmpty: "full|half|frame" - } - }; - if (BladesActor.IsType(this.actor, BladesActorType.pc)) { - sheetData.traumaData = { - label: this.actor.system.trauma.name, - dotline: { - data: { value: this.actor.trauma, max: this.actor.system.trauma.max }, - svgKey: "teeth.short", - svgFull: "full|frame", - svgEmpty: "frame", - isLocked: true - }, - compContainer: { - "class": "comp-trauma-conditions comp-vertical full-width", - "blocks": [ - this.actor.traumaList.slice(0, Math.ceil(this.actor.traumaList.length / 2)) - .map((tName) => ({ - checkLabel: tName, - checkClasses: { - active: "comp-toggle-red", - inactive: "comp-toggle-grey" - }, - checkTarget: `system.trauma.checked.${tName}`, - checkValue: this.actor.system.trauma.checked[tName] ?? false, - tooltip: C.TraumaTooltips[tName], - tooltipClass: "tooltip-trauma" - })), - this.actor.traumaList.slice(Math.ceil(this.actor.traumaList.length / 2)) - .map((tName) => ({ - checkLabel: tName, - checkClasses: { - active: "comp-toggle-red", - inactive: "comp-toggle-grey" - }, - checkTarget: `system.trauma.checked.${tName}`, - checkValue: this.actor.system.trauma.checked[tName] ?? false, - tooltip: C.TraumaTooltips[tName], - tooltipClass: "tooltip-trauma" - })) - ] - } - }; - } - sheetData.abilityData = { - dotline: { - dotlineClass: "dotline-right dotline-glow", - data: { - value: this.actor.getAvailableAdvancements("Ability"), - max: this.actor.getAvailableAdvancements("Ability") - }, - dotlineLabel: "Available Abilities", - isLocked: true, - iconFull: "dot-full.svg" - } - }; - sheetData.loadData = { - curLoad: this.actor.currentLoad, - selLoadCount: this.actor.system.loadout.levels[U.lCase(game.i18n.localize(this.actor.system.loadout.selected.toString()))], - selections: C.Loadout.selections, - selLoadLevel: this.actor.system.loadout.selected.toString() - }; - sheetData.armor = Object.fromEntries(Object.entries(this.actor.system.armor.active) - .filter(([, isActive]) => isActive) - .map(([armor]) => [armor, this.actor.system.armor.checked[armor]])); - sheetData.attributeData = {}; - const attrEntries = Object.entries(this.actor.system.attributes); - for (const [attribute, attrData] of attrEntries) { - sheetData.attributeData[attribute] = { - tooltip: C.AttributeTooltips[attribute], - actions: {} - }; - const actionEntries = Object.entries(attrData); - for (const [action, actionData] of actionEntries) { - sheetData.attributeData[attribute].actions[action] = { - tooltip: C.ActionTooltips[action], - value: actionData.value, - max: BladesTrackerSheet.Get().phase === BladesPhase.CharGen ? 2 : this.actor.system.attributes[attribute][action].max - }; - } - } - sheetData.gatherInfoTooltip = (new Handlebars.SafeString([ - "

Gathering Information: Questions to Consider

", - "
    ", - ...Object.values(this.actor.system.gather_info ?? []).map((line) => `
  • ${line}
  • `) ?? [], - "
" - ].join(""))).toString(); - eLog.checkLog("Attribute", "[BladesPCSheet] attributeData", { attributeData: sheetData.attributeData }); - eLog.checkLog("actor", "[BladesPCSheet] getData()", { ...context, ...sheetData }); - return { ...context, ...sheetData }; - } - get activeArmor() { - return Object.keys(U.objFilter(this.actor.system.armor.active, (val) => val === true)); - } - get checkedArmor() { - return Object.keys(U.objFilter(this.actor.system.armor.checked, (val, key) => val === true - && this.actor.system.armor.active[key] === true)); - } - get uncheckedArmor() { - return Object.keys(U.objFilter(this.actor.system.armor.active, (val, key) => val === true - && this.actor.system.armor.checked[key] === false)); - } - _getHoverArmor() { - if (!this.activeArmor.length) { - return false; - } - if (this.activeArmor.includes("heavy")) { - return this.checkedArmor.includes("heavy") ? "light" : "heavy"; - } - else if (this.activeArmor.includes("light")) { - return "light"; - } - return "special"; - } - _getClickArmor() { - if (!this.uncheckedArmor.length) { - return false; - } - if (this.uncheckedArmor.includes("heavy")) { - return "heavy"; - } - if (this.uncheckedArmor.includes("light")) { - return "light"; - } - return "special"; - } - _getContextMenuArmor() { - if (!this.checkedArmor.length) { - return false; - } - if (this.checkedArmor.includes("light")) { - return "light"; - } - if (this.checkedArmor.includes("heavy")) { - return "heavy"; - } - return "special"; - } - async _onAdvanceClick(event) { - event.preventDefault(); - super._onAdvanceClick(event); - const action = $(event.currentTarget).data("action").replace(/^advance-/, ""); - if (action in Attribute) { - this.actor.advanceAttribute(action); - } - } - activateListeners(html) { - super.activateListeners(html); - - if (!this.options.editable) { - return; - } - const self = this; - - html.find(".main-armor-control").on({ - click: function () { - const targetArmor = self._getClickArmor(); - if (!targetArmor) { - return; - } - self.actor.update({ [`system.armor.checked.${targetArmor}`]: true }); - }, - contextmenu: function () { - const targetArmor = self._getContextMenuArmor(); - if (!targetArmor) { - return; - } - self.actor.update({ [`system.armor.checked.${targetArmor}`]: false }); - }, - mouseenter: function () { - const targetArmor = self._getHoverArmor(); - eLog.log4("Mouse Enter", targetArmor, this, $(this), $(this).next()); - if (!targetArmor) { - return; - } - $(this).siblings(`.svg-armor.armor-${targetArmor}`).addClass("hover-over"); - }, - mouseleave: function () { - const targetArmor = self._getHoverArmor(); - if (!targetArmor) { - return; - } - $(this).siblings(`.svg-armor.armor-${targetArmor}`).removeClass("hover-over"); - } - }); - html.find(".special-armor-control").on({ - click: function () { - if (!self.activeArmor.includes("special")) { - return; - } - self.actor.update({ ["system.armor.checked.special"]: self.uncheckedArmor.includes("special") }); - }, - contextmenu: function () { - if (!self.activeArmor.includes("special")) { - return; - } - self.actor.update({ ["system.armor.checked.special"]: self.uncheckedArmor.includes("special") }); - }, - mouseenter: function () { - if (!self.activeArmor.includes("special") || self.activeArmor.length === 1) { - return; - } - $(this).siblings(".svg-armor.armor-special").addClass("hover-over"); - }, - mouseleave: function () { - if (!self.activeArmor.includes("special") || self.activeArmor.length === 1) { - return; - } - $(this).siblings(".svg-armor.armor-special").removeClass("hover-over"); - } - }); - } -} -export default BladesPCSheet; -//# sourceMappingURL=blades-pc-sheet.js.map \ No newline at end of file diff --git a/module/sheets/actor/blades-sheet.js b/module/sheets/actor/blades-sheet.js deleted file mode 100644 index d6361922..00000000 --- a/module/sheets/actor/blades-sheet.js +++ /dev/null @@ -1,399 +0,0 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - - -import U from "../../core/utilities.js"; -import G, { ApplyTooltipListeners } from "../../core/gsap.js"; -import C, { BladesActorType, BladesItemType, Attribute, Action, Factor, RollType } from "../../core/constants.js"; -import Tags from "../../core/tags.js"; -import BladesActor from "../../blades-actor.js"; -import BladesItem from "../../blades-item.js"; -import BladesSelectorDialog from "../../blades-dialog.js"; -import BladesActiveEffect from "../../blades-active-effect.js"; -import BladesRollCollab, { BladesRollCollabComps } from "../../blades-roll-collab.js"; -class BladesSheet extends ActorSheet { - getData() { - const context = super.getData(); - const sheetData = { - cssClass: this.actor.type, - editable: this.options.editable, - isGM: game.eunoblades.Tracker?.system.is_spoofing_player ? false : game.user.isGM, - actor: this.actor, - system: this.actor.system, - tierTotal: this.actor.getFactorTotal(Factor.tier) > 0 ? U.romanizeNum(this.actor.getFactorTotal(Factor.tier)) : "0", - rollData: this.actor.getRollData(), - activeEffects: Array.from(this.actor.effects), - hasFullVision: game.user.isGM || this.actor.testUserPermission(game.user, CONST.DOCUMENT_PERMISSION_LEVELS.OBSERVER), - hasLimitedVision: game.user.isGM || this.actor.testUserPermission(game.user, CONST.DOCUMENT_PERMISSION_LEVELS.LIMITED), - hasControl: game.user.isGM || this.actor.testUserPermission(game.user, CONST.DOCUMENT_PERMISSION_LEVELS.OWNER), - preparedItems: { - cohorts: { - gang: this.actor.activeSubItems - .filter((item) => item.type === BladesItemType.cohort_gang) - .map((item) => { - const subtypes = U.unique(Object.values(item.system.subtypes) - .map((subtype) => subtype.trim()) - .filter((subtype) => /[A-Za-z]/.test(subtype))); - const eliteSubtypes = U.unique([ - ...Object.values(item.system.elite_subtypes), - ...(item.parent?.upgrades ?? []) - .map((upgrade) => (upgrade.name ?? "").trim().replace(/^Elite /, "")) - ] - .map((subtype) => subtype.trim()) - .filter((subtype) => /[A-Za-z]/.test(subtype) && subtypes.includes(subtype))); - const imgTypes = [...eliteSubtypes]; - if (imgTypes.length < 2) { - imgTypes.push(...subtypes.filter((subtype) => !imgTypes.includes(subtype))); - } - if (U.unique(imgTypes).length === 1) { - item.system.image = Object.values(item.system.elite_subtypes).includes(imgTypes[0]) ? `elite-${U.lCase(imgTypes[0])}.svg` : `${U.lCase(imgTypes[0])}.svg`; - } - else if (U.unique(imgTypes).length > 1) { - const [rightType, leftType] = imgTypes; - item.system.imageLeft = Object.values(item.system.elite_subtypes).includes(leftType) ? `elite-${U.lCase(leftType)}.svg` : `${U.lCase(leftType)}.svg`; - item.system.imageRight = Object.values(item.system.elite_subtypes).includes(rightType) ? `elite-${U.lCase(rightType)}.svg` : `${U.lCase(rightType)}.svg`; - } - Object.assign(item.system, { - tierTotal: item.getFactorTotal(Factor.tier) > 0 ? U.romanizeNum(item.getFactorTotal(Factor.tier)) : "0", - cohortRollData: [ - { mode: "untrained", label: "Untrained", color: "transparent", tooltip: "

Roll Untrained

" } - ], - edgeData: Object.fromEntries(Object.values(item.system.edges ?? []) - .filter((edge) => /[A-Za-z]/.test(edge)) - .map((edge) => [edge.trim(), C.EdgeTooltips[edge]])), - flawData: Object.fromEntries(Object.values(item.system.flaws ?? []) - .filter((flaw) => /[A-Za-z]/.test(flaw)) - .map((flaw) => [flaw.trim(), C.FlawTooltips[flaw]])) - }); - return item; - }), - expert: this.actor.activeSubItems - .filter((item) => item.type === BladesItemType.cohort_expert) - .map((item) => { - Object.assign(item.system, { - tierTotal: item.getFactorTotal(Factor.tier) > 0 ? U.romanizeNum(item.getFactorTotal(Factor.tier)) : "0", - cohortRollData: [ - { mode: "untrained", label: "Untrained", tooltip: "

Roll Untrained

" } - ], - edgeData: Object.fromEntries(Object.values(item.system.edges ?? []) - .filter((edge) => /[A-Za-z]/.test(edge)) - .map((edge) => [edge.trim(), C.EdgeTooltips[edge]])), - flawData: Object.fromEntries(Object.values(item.system.flaws ?? []) - .filter((flaw) => /[A-Za-z]/.test(flaw)) - .map((flaw) => [flaw.trim(), C.FlawTooltips[flaw]])) - }); - return item; - }) - } - } - }; - if (BladesActor.IsType(this.actor, BladesActorType.pc) || BladesActor.IsType(this.actor, BladesActorType.crew)) { - sheetData.playbookData = { - dotline: { - data: this.actor.system.experience.playbook, - dotlineClass: "xp-playbook", - target: "system.experience.playbook.value", - svgKey: "teeth.tall", - svgFull: "full|frame", - svgEmpty: "full|half|frame", - advanceButton: "advance-playbook" - } - }; - if (this.actor.system.experience.playbook.value !== this.actor.system.experience.playbook.max) { - sheetData.playbookData.tooltip = (new Handlebars.SafeString([ - "

At the End of the Session, Gain XP If ...

", - "
    ", - ...Object.values(this.actor.system.experience.clues ?? []).map((line) => `
  • ${line.replace(/^Y/, "... y")}
  • `) ?? [], - "
" - ].join(""))).toString(); - } - sheetData.coinsData = { - dotline: { - data: this.actor.system.coins, - target: "system.coins.value", - iconEmpty: "coin-full.svg", - iconFull: "coin-full.svg" - } - }; - } - return { - ...context, - ...sheetData - }; - } - activateListeners(html) { - super.activateListeners(html); - if (game.user.isGM) { - html.attr("style", "--secret-text-display: initial"); - } - else { - html.find('.editor:not(.tinymce) [data-is-secret="true"]').remove(); - } - - ApplyTooltipListeners(html); - Tags.InitListeners(html, this.actor); - if (!this.options.editable) { - return; - } - html.find(".dotline").each((__, elem) => { - if ($(elem).hasClass("locked")) { - return; - } - let targetDoc = this.actor; - let targetField = $(elem).data("target"); - const comp$ = $(elem).closest("comp"); - if (targetField.startsWith("item")) { - targetField = targetField.replace(/^item\./, ""); - const itemId = $(elem).closest("[data-comp-id]").data("compId"); - if (!itemId) { - return; - } - const item = this.actor.items.get(itemId); - if (!item) { - return; - } - targetDoc = item; - } - const curValue = U.pInt($(elem).data("value")); - $(elem) - .find(".dot") - .each((_, dot) => { - $(dot).on("click", (event) => { - event.preventDefault(); - const thisValue = U.pInt($(dot).data("value")); - if (thisValue !== curValue) { - if (comp$.hasClass("comp-coins") - || comp$.hasClass("comp-stash")) { - G.effects - .fillCoins($(dot).prevAll(".dot")) - .then(() => targetDoc.update({ [targetField]: thisValue })); - } - else { - targetDoc.update({ [targetField]: thisValue }); - } - } - }); - $(dot).on("contextmenu", (event) => { - event.preventDefault(); - const thisValue = U.pInt($(dot).data("value")) - 1; - if (thisValue !== curValue) { - targetDoc.update({ [targetField]: thisValue }); - } - }); - }); - }); - html - .find(".clock-container") - .on("click", this._onClockLeftClick.bind(this)); - html - .find(".clock-container") - .on("contextmenu", this._onClockRightClick.bind(this)); - html - .find("[data-comp-id]") - .find(".comp-title") - .on("click", this._onItemOpenClick.bind(this)); - html - .find(".comp-control.comp-add") - .on("click", this._onItemAddClick.bind(this)); - html - .find(".comp-control.comp-delete") - .on("click", this._onItemRemoveClick.bind(this)); - html - .find(".comp-control.comp-delete-full") - .on("click", this._onItemFullRemoveClick.bind(this)); - html - .find(".comp-control.comp-toggle") - .on("click", this._onItemToggleClick.bind(this)); - html - .find(".advance-button") - .on("click", this._onAdvanceClick.bind(this)); - html - .find(".effect-control") - .on("click", this._onActiveEffectControlClick.bind(this)); - html - .find("[data-roll-trait]") - .on("click", this._onRollTraitClick.bind(this)); - if (this.options.submitOnChange) { - html.on("change", "textarea", this._onChangeInput.bind(this)); - } - } - async _onSubmit(event, params = {}) { - if (!game.user.isGM && !this.actor.testUserPermission(game.user, CONST.DOCUMENT_PERMISSION_LEVELS.OWNER)) { - eLog.checkLog("actorSheetTrigger", "User does not have permission to edit this actor", { user: game.user, actor: this.actor }); - return {}; - } - return super._onSubmit(event, params); - } - async close(options) { - if (this.actor.type === BladesActorType.pc) { - return super.close(options).then(() => this.actor.clearSubActors()); - } - else if (this.actor.type === BladesActorType.npc && this.actor.parentActor) { - return super.close(options).then(() => this.actor.clearParentActor(false)); - } - return super.close(options); - } - - async _onClockLeftClick(event) { - event.preventDefault(); - const clock$ = $(event.currentTarget).find(".clock[data-target]"); - if (!clock$[0]) { - return; - } - const target = clock$.data("target"); - const curValue = U.pInt(clock$.data("value")); - const maxValue = U.pInt(clock$.data("size")); - G.effects.pulseClockWedges(clock$.find("wedges")).then(() => this.actor.update({ - [target]: G.utils.wrap(0, maxValue + 1, curValue + 1) - })); - } - async _onClockRightClick(event) { - event.preventDefault(); - const clock$ = $(event.currentTarget).find(".clock[data-target]"); - if (!clock$[0]) { - return; - } - const target = clock$.data("target"); - const curValue = U.pInt(clock$.data("value")); - G.effects.reversePulseClockWedges(clock$.find("wedges")).then(() => this.actor.update({ - [target]: Math.max(0, curValue - 1) - })); - } - - _getCompData(event) { - const elem$ = $(event.currentTarget).closest(".comp"); - const compData = { - elem$, - docID: elem$.data("compId"), - docCat: elem$.data("compCat"), - docType: elem$.data("compType"), - docTags: (elem$.data("compTags") ?? "").split(/\s+/g) - }; - eLog.checkLog2("dialog", "Component Data", { elem: elem$, ...compData }); - if (compData.docID && compData.docType) { - compData.doc = { - Actor: this.actor.getSubActor(compData.docID), - Item: this.actor.getSubItem(compData.docID) - }[compData.docType]; - } - if (compData.docCat && compData.docType) { - compData.dialogDocs = { - Actor: this.actor.getDialogActors(compData.docCat), - Item: this.actor.getDialogItems(compData.docCat) - }[compData.docType]; - } - return compData; - } - async _onItemOpenClick(event) { - event.preventDefault(); - const { doc } = this._getCompData(event); - if (!doc) { - return; - } - doc.sheet?.render(true); - } - async _onItemAddClick(event) { - event.preventDefault(); - const addType = $(event.currentTarget).closest(".comp").data("addType"); - if (addType && addType in BladesItemType) { - await this.actor.createEmbeddedDocuments("Item", [ - { - name: { - [BladesItemType.cohort_gang]: "A Gang", - [BladesItemType.cohort_expert]: "An Expert" - }[addType] ?? randomID(), - type: addType - } - ]); - return; - } - const { docCat, docType, dialogDocs, docTags } = this._getCompData(event); - if (!dialogDocs || !docCat || !docType) { - return; - } - await BladesSelectorDialog.Display(this.actor, U.tCase(`Add ${docCat.replace(/_/g, " ")}`), docType, dialogDocs, docTags); - } - async _onItemRemoveClick(event) { - event.preventDefault(); - const { elem$, doc } = this._getCompData(event); - if (!doc) { - return; - } - G.effects.blurRemove(elem$).then(() => { - if (doc instanceof BladesItem) { - this.actor.remSubItem(doc); - } - else { - this.actor.remSubActor(doc); - } - }); - } - async _onItemFullRemoveClick(event) { - event.preventDefault(); - const { elem$, doc } = this._getCompData(event); - if (!doc) { - return; - } - G.effects.blurRemove(elem$).then(() => doc.delete()); - } - async _onItemToggleClick(event) { - event.preventDefault(); - const target = $(event.currentTarget).data("target"); - this.actor.update({ - [target]: !getProperty(this.actor, target) - }); - } - async _onAdvanceClick(event) { - event.preventDefault(); - if ($(event.currentTarget).data("action") === "advance-playbook") { - this.actor.advancePlaybook(); - } - } - - async _onRollTraitClick(event) { - const traitName = $(event.currentTarget).data("rollTrait"); - const rollType = $(event.currentTarget).data("rollType"); - const rollData = {}; - if (U.lCase(traitName) in { ...Action, ...Attribute, ...Factor }) { - rollData.rollTrait = U.lCase(traitName); - } - else if (U.isInt(traitName)) { - rollData.rollTrait = U.pInt(traitName); - } - if (U.tCase(rollType) in RollType) { - rollData.rollType = U.tCase(rollType); - } - else if (typeof rollData.rollTrait === "string") { - if (rollData.rollTrait in Attribute) { - rollData.rollType = RollType.Resistance; - } - else if (rollData.rollTrait in Action) { - rollData.rollType = RollType.Action; - } - } - if (game.user.isGM) { - if (BladesRollCollabComps.Primary.IsDoc(this.actor)) { - rollData.rollPrimary = this.actor; - } - else if (BladesRollCollabComps.Opposition.IsDoc(this.actor)) { - rollData.rollOpposition = this.actor; - } - } - if (rollData.rollType) { - BladesRollCollab.NewRoll(rollData); - } - else { - throw new Error("Unable to determine roll type of roll."); - } - } - - async _onActiveEffectControlClick(event) { - BladesActiveEffect.onManageActiveEffect(event, this.actor); - } -} -export default BladesSheet; -//# sourceMappingURL=blades-sheet.js.map \ No newline at end of file diff --git a/module/sheets/blades-actor-sheet.js b/module/sheets/blades-actor-sheet.js deleted file mode 100644 index 3766dbd1..00000000 --- a/module/sheets/blades-actor-sheet.js +++ /dev/null @@ -1,315 +0,0 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - -import C, { BladesActorType, BladesItemType, Tag } from "../core/constants.js"; -import { BladesPhase } from "./blades-tracker-sheet.js"; -import U from "../core/utilities.js"; -import BladesSheet from "./blades-sheet.js"; -import BladesItem from "../blades-item.js"; -import BladesActor from "../blades-actor.js"; -class BladesActorSheet extends BladesSheet { - static get defaultOptions() { - return foundry.utils.mergeObject(super.defaultOptions, { - classes: ["eunos-blades", "sheet", "actor", "pc"], - template: "systems/eunos-blades/templates/actor-sheet.hbs", - width: 775, - height: 775, - tabs: [{ navSelector: ".nav-tabs", contentSelector: ".tab-content", initial: "abilities" }] - }); - } - static Initialize() { - Actors.registerSheet("blades", BladesActorSheet, { types: ["pc"], makeDefault: true }); - Hooks.on("dropActorSheetData", async (parentActor, _, { uuid }) => { - const doc = await fromUuid(uuid); - if (doc instanceof BladesActor) { - if (parentActor.type === BladesActorType.crew && doc.type === BladesActorType.pc) { - doc.addSubActor(parentActor); - } - else if (parentActor.type === BladesActorType.pc && doc.type === BladesActorType.crew) { - parentActor.addSubActor(doc); - } - } - if (doc instanceof BladesItem) { - BladesItem.create(doc, { parent: parentActor }); - return; - } - }); - return loadTemplates([ - "systems/eunos-blades/templates/items/clock_keeper-sheet.hbs", - "systems/eunos-blades/templates/parts/clock-sheet-row.hbs" - ]); - } - getData() { - const context = super.getData(); - const sheetData = {}; - sheetData.isOwner = this.actor.testUserPermission(game.user, CONST.DOCUMENT_PERMISSION_LEVELS.OWNER); - context.attributes = U.objMap(context.system.attributes, (attrData) => U.objMap(attrData, (value) => ({ - value: value.value, - max: game.eunoblades.Tracker.system.game_phase === BladesPhase.CharGen ? 2 : value.max - }))); - const { activeSubItems, activeSubActors } = this.actor; - Object.assign(sheetData, { - phases: Object.values(BladesPhase), - items: { - abilities: activeSubItems.filter((item) => item.type === BladesItemType.ability).map((item) => { - if (item.system.uses?.max) { - Object.assign(item, { - inRuleDotline: { - data: item.system.uses, - dotlineLabel: "Uses", - target: "item.system.uses.value", - iconEmpty: "dot-empty.svg", - iconEmptyHover: "dot-empty-hover.svg", - iconFull: "dot-full.svg", - iconFullHover: "dot-full-hover.svg" - } - }); - } - return item; - }), - background: activeSubItems.find((item) => item.type === BladesItemType.background), - heritage: activeSubItems.find((item) => item.type === BladesItemType.heritage), - vice: activeSubItems.find((item) => item.type === BladesItemType.vice), - loadout: activeSubItems.filter((item) => item.type === BladesItemType.item).map((item) => { - if (item.system.load) { - Object.assign(item, { - numberCircle: item.system.load, - numberCircleClass: "item-load" - }); - } - if (item.system.uses?.max) { - Object.assign(item, { - inRuleDotline: { - data: item.system.uses, - dotlineLabel: "Uses", - target: "item.system.uses.value", - iconEmpty: "dot-empty.svg", - iconEmptyHover: "dot-empty-hover.svg", - iconFull: "dot-full.svg", - iconFullHover: "dot-full-hover.svg" - } - }); - } - return item; - }), - playbook: this.actor.playbook - }, - actors: { - crew: activeSubActors.find((actor) => actor.type === BladesActorType.crew), - vice_purveyor: activeSubActors.find((actor) => actor.hasTag(Tag.NPC.VicePurveyor)), - friends: activeSubActors.filter((actor) => actor.hasTag(Tag.NPC.Friend)), - rivals: activeSubActors.filter((actor) => actor.hasTag(Tag.NPC.Rival)) - }, - stashData: { - label: "Stash:", - dotline: { - data: this.actor.system.stash, - target: "system.stash.value", - iconEmpty: "coin-empty.svg", - iconEmptyHover: "coin-empty-hover.svg", - iconFull: "coin-full.svg", - iconFullHover: "coin-full-hover.svg", - altIconFull: "coin-ten.svg", - altIconFullHover: "coin-ten-hover.svg", - altIconStep: 10 - } - }, - healing_clock: { - value: this.actor.system.healing.value, - size: this.actor.system.healing.max - }, - armor: Object.fromEntries(Object.entries(this.actor.system.armor.active) - .filter(([, isActive]) => isActive) - .map(([armor]) => [armor, this.actor.system.armor.checked[armor]])), - loadData: { - curLoad: this.actor.currentLoad, - selLoadCount: this.actor.system.loadout.levels[U.lCase(game.i18n.localize(this.actor.system.loadout.selected.toString()))], - selections: C.Loadout.selections, - selLoadLevel: this.actor.system.loadout.selected.toString() - }, - stressData: { - name: this.actor.system.stress.name, - dotline: { - data: this.actor.system.stress, - target: "system.stress.value", - svgKey: "teeth.tall", - svgFull: "full|half|frame", - svgEmpty: "full|half|frame" - } - }, - traumaData: { - name: this.actor.system.trauma.name, - dotline: { - data: { value: this.actor.trauma, max: this.actor.system.trauma.max }, - svgKey: "teeth.short", - svgFull: "full|frame", - svgEmpty: "frame", - isLocked: true - }, - compContainer: { - "class": "comp-trauma-conditions comp-vertical full-width", - "blocks": [ - this.actor.traumaList.slice(0, Math.ceil(this.actor.traumaList.length / 2)) - .map((tName) => ({ - checkLabel: tName, - checkClasses: { - active: "comp-toggle-red", - inactive: "comp-toggle-grey" - }, - checkTarget: `system.trauma.checked.${tName}`, - checkValue: this.actor.system.trauma.checked[tName] ?? false - })), - this.actor.traumaList.slice(Math.ceil(this.actor.traumaList.length / 2)) - .map((tName) => ({ - checkLabel: tName, - checkClasses: { - active: "comp-toggle-red", - inactive: "comp-toggle-grey" - }, - checkTarget: `system.trauma.checked.${tName}`, - checkValue: this.actor.system.trauma.checked[tName] ?? false - })) - ] - } - }, - acquaintancesName: this.actor.system.acquaintances_name ?? "Friends & Rivals", - friendsName: this.actor.system.friends_name, - rivalsName: this.actor.system.rivals_name, - abilityData: { - dotline: { - "class": "dotline-right", - "data": { - value: this.actor.availableAbilityPoints, - max: this.actor.availableAbilityPoints - }, - "dotlineLabel": "Available Abilities", - "isLocked": true, - "iconFull": "dot-full.svg" - } - } - }); - eLog.checkLog("actor", "[BladesActorSheet] getData()", { ...context, ...sheetData }); - return { - ...context, - ...sheetData - }; - } - get activeArmor() { - return Object.keys(U.objFilter(this.actor.system.armor.active, (val) => val === true)); - } - get checkedArmor() { - return Object.keys(U.objFilter(this.actor.system.armor.checked, (val, key) => val === true - && this.actor.system.armor.active[key] === true)); - } - get uncheckedArmor() { - return Object.keys(U.objFilter(this.actor.system.armor.active, (val, key) => val === true - && this.actor.system.armor.checked[key] === false)); - } - _getHoverArmor() { - if (!this.activeArmor.length) { - return false; - } - if (this.activeArmor.includes("heavy")) { - return this.checkedArmor.includes("heavy") ? "light" : "heavy"; - } - else if (this.activeArmor.includes("light")) { - return "light"; - } - return "special"; - } - _getClickArmor() { - if (!this.uncheckedArmor.length) { - return false; - } - if (this.uncheckedArmor.includes("heavy")) { - return "heavy"; - } - if (this.uncheckedArmor.includes("light")) { - return "light"; - } - return "special"; - } - _getContextMenuArmor() { - if (!this.checkedArmor.length) { - return false; - } - if (this.checkedArmor.includes("light")) { - return "light"; - } - if (this.checkedArmor.includes("heavy")) { - return "heavy"; - } - return "special"; - } - activateListeners(html) { - super.activateListeners(html); - - if (!this.options.editable) { - return; - } - const self = this; - - html.find(".main-armor-control").on({ - click: function () { - const targetArmor = self._getClickArmor(); - if (!targetArmor) { - return; - } - self.actor.update({ [`system.armor.checked.${targetArmor}`]: true }); - }, - contextmenu: function () { - const targetArmor = self._getContextMenuArmor(); - if (!targetArmor) { - return; - } - self.actor.update({ [`system.armor.checked.${targetArmor}`]: false }); - }, - mouseenter: function () { - const targetArmor = self._getHoverArmor(); - eLog.log4("Mouse Enter", targetArmor, this, $(this), $(this).next()); - if (!targetArmor) { - return; - } - $(this).siblings(`.svg-armor.armor-${targetArmor}`).addClass("hover-over"); - }, - mouseleave: function () { - const targetArmor = self._getHoverArmor(); - if (!targetArmor) { - return; - } - $(this).siblings(`.svg-armor.armor-${targetArmor}`).removeClass("hover-over"); - } - }); - html.find(".special-armor-control").on({ - click: function () { - if (!self.activeArmor.includes("special")) { - return; - } - self.actor.update({ ["system.armor.checked.special"]: self.uncheckedArmor.includes("special") }); - }, - contextmenu: function () { - if (!self.activeArmor.includes("special")) { - return; - } - self.actor.update({ ["system.armor.checked.special"]: self.uncheckedArmor.includes("special") }); - }, - mouseenter: function () { - if (!self.activeArmor.includes("special") || self.activeArmor.length === 1) { - return; - } - $(this).siblings(".svg-armor.armor-special").addClass("hover-over"); - }, - mouseleave: function () { - if (!self.activeArmor.includes("special") || self.activeArmor.length === 1) { - return; - } - $(this).siblings(".svg-armor.armor-special").removeClass("hover-over"); - } - }); - } -} -export default BladesActorSheet; \ No newline at end of file diff --git a/module/sheets/blades-clock-keeper-sheet.js b/module/sheets/blades-clock-keeper-sheet.js deleted file mode 100644 index fd18e3d2..00000000 --- a/module/sheets/blades-clock-keeper-sheet.js +++ /dev/null @@ -1,75 +0,0 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - -import BladesItemSheet from "./blades-item-sheet.js"; -import BladesItem from "../blades-item.js"; -export default class BladesClockKeeperSheet extends BladesItemSheet { - static get defaultOptions() { - return foundry.utils.mergeObject(super.defaultOptions, { - classes: ["eunos-blades", "sheet", "item", "clock-keeper"], - template: "systems/eunos-blades/templates/items/clock_keeper-sheet.hbs", - width: 700, - height: 970 - }); - } - static async Initialize() { - game.eunoblades ??= {}; - Items.registerSheet("blades", BladesClockKeeperSheet, { types: ["clock_keeper"], makeDefault: true }); - Hooks.once("ready", async () => { - let clockKeeper = game.items.find((item) => item.type === "clock_keeper"); - if (!(clockKeeper instanceof BladesItem)) { - clockKeeper = (await BladesItem.create({ - name: "Clock Keeper", - type: "clock_keeper", - img: "systems/eunos-blades/assets/icons/clock-keeper.svg" - })); - } - game.eunoblades.ClockKeeper = clockKeeper; - game.eunoblades.ClockKeeper.renderOverlay(); - }); - Hooks.on("canvasReady", async () => { game.eunoblades.ClockKeeper?.renderOverlay(); }); - return loadTemplates([ - "systems/eunos-blades/templates/items/clock_keeper-sheet.hbs", - "systems/eunos-blades/templates/parts/clock-sheet-row.hbs" - ]); - } - async _updateObject(event, formData) { - const updateData = await this.object.update(formData); - socketlib.system.executeForEveryone("renderOverlay"); - return updateData; - } - async getData() { - const context = super.getData(); - context.clock_keys = Object.fromEntries(Object.entries(context.clock_keys) - .filter(([keyID, keyData]) => Boolean(keyData && keyData.scene === context.system.targetScene))); - return context; - } - addKey(event) { - event.preventDefault(); - this.item.addClockKey(); - } - deleteKey(event) { - event.preventDefault(); - const keyID = event.currentTarget.dataset.id; - if (keyID) { - this.item.deleteClockKey(keyID); - } - } - setKeySize(event) { - event.preventDefault(); - const keyID = event.target.dataset.id; - if (keyID) { - this.item.setKeySize(keyID, parseInt(event.target.value)); - } - } - async activateListeners(html) { - super.activateListeners(html); - html.find("[data-action=\"add-key\"").on("click", this.addKey.bind(this)); - html.find("[data-action=\"delete-key\"").on("click", this.deleteKey.bind(this)); - html.find(".clock-counter").on("change", this.setKeySize.bind(this)); - } -} \ No newline at end of file diff --git a/module/sheets/blades-crew-sheet.js b/module/sheets/blades-crew-sheet.js deleted file mode 100644 index be6a3039..00000000 --- a/module/sheets/blades-crew-sheet.js +++ /dev/null @@ -1,177 +0,0 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - -import BladesSheet from "./blades-sheet.js"; -import { BladesItemType } from "../core/constants.js"; -class BladesCrewSheet extends BladesSheet { - static get defaultOptions() { - return foundry.utils.mergeObject(super.defaultOptions, { - classes: ["eunos-blades", "sheet", "actor", "crew"], - template: "systems/eunos-blades/templates/crew-sheet.hbs", - width: 940, - height: 820, - tabs: [{ navSelector: ".nav-tabs", contentSelector: ".tab-content", initial: "claims" }] - }); - } - getData() { - const context = super.getData(); - eLog.checkLog("actor", "[BladesCrewSheet] super.getData()", { ...context }); - context.actor = this.actor; - context.system = this.actor.system; - const { activeSubItems } = this.actor; - - context.items = { - abilities: activeSubItems.filter((item) => item.type === BladesItemType.crew_ability), - playbook: this.actor.playbook, - reputation: activeSubItems.find((item) => item.type === BladesItemType.crew_reputation), - upgrades: activeSubItems.filter((item) => item.type === BladesItemType.crew_upgrade), - cohorts: activeSubItems.filter((item) => item.type === BladesItemType.cohort), - preferredOp: activeSubItems.find((item) => item.type === BladesItemType.preferred_op) - }; - context.actors = { - members: this.actor.members, - contacts: this.actor.contacts - }; - context.tierData = { - label: "Tier", - dotline: { - data: this.actor.system.tier, - target: "system.tier.value", - iconEmpty: "dot-empty.svg", - iconEmptyHover: "dot-empty-hover.svg", - iconFull: "dot-full.svg", - iconFullHover: "dot-full-hover.svg" - } - }; - context.upgradeData = { - dotline: { - "class": "dotline-right", - "data": { - value: this.actor.availableUpgradePoints, - max: this.actor.availableUpgradePoints - }, - "dotlineLabel": "Available Upgrade Points", - "isLocked": true, - "iconFull": "dot-full.svg" - } - }; - context.abilityData = { - dotline: { - "class": "dotline-right", - "data": { - value: this.actor.availableAbilityPoints, - max: this.actor.availableAbilityPoints - }, - "dotlineLabel": "Available Ability Points", - "isLocked": true, - "iconFull": "dot-full.svg" - } - }; - context.cohortData = { - dotline: { - "class": "dotline-right", - "data": { - value: this.actor.availableCohortPoints, - max: this.actor.availableCohortPoints - }, - "dotlineLabel": "Available Cohort Points", - "isLocked": true, - "iconFull": "dot-full.svg" - } - }; - context.repData = { - name: "Rep", - dotlines: [ - { - data: { - value: Math.min(this.actor.system.rep.value, this.actor.system.rep.max - this.actor.turfCount), - max: this.actor.system.rep.max - this.actor.turfCount - }, - target: "system.rep.value", - svgKey: "teeth.tall", - svgFull: "full|half|frame", - svgEmpty: "full|half|frame" - }, - { - "data": { - value: this.actor.turfCount, - max: this.actor.turfCount - }, - "target": "none", - "svgKey": "teeth.tall", - "svgFull": "full|half|frame", - "svgEmpty": "full|half|frame", - "class": "flex-row-reverse", - "isLocked": true - } - ] - }; - context.heatData = { - name: "Heat", - dotline: { - data: this.actor.system.heat, - target: "system.heat.value", - svgKey: "teeth.tall", - svgFull: "full|half|frame", - svgEmpty: "full|half|frame" - } - }; - context.wantedData = { - name: "Wanted", - dotline: { - data: this.actor.system.wanted, - target: "system.wanted.value", - svgKey: "teeth.short", - svgFull: "full|frame", - svgEmpty: "frame" - } - }; - eLog.checkLog("actor", "[BladesCrewSheet] return getData()", { ...context }); - return context; - } - activateListeners(html) { - super.activateListeners(html); - if (!this.options.editable) { - return; - } - html.find(".item-sheet-open").on("click", (event) => { - const element = $(event.currentTarget).parents(".item"); - const item = this.actor.items.get(element.data("itemId")); - item?.sheet?.render(true); - }); - html.find(".add-item").on("click", (event) => { - event.preventDefault(); - const a = event.currentTarget; - const item_type = a.dataset.itemType; - const data = { - name: randomID(), - type: item_type - }; - return this.actor.createEmbeddedDocuments("Item", [data]); - }); - html.find(".hold-toggle").on("click", () => { - this.actor.update({ "system.hold": this.actor.system.hold === "weak" ? "strong" : "weak" }); - }); - html.find(".turf-select").on("click", async (event) => { - const turf_id = $(event.currentTarget).data("turfId"); - const turf_current_status = $(event.currentTarget).data("turfStatus"); - this.actor.playbook?.update({ ["system.turfs." + turf_id + ".value"]: !turf_current_status }) - .then(() => this.render(false)); - }); - html.find('.cohort-block-harm input[type="radio"]').change(async (ev) => { - const element = $(ev.currentTarget).parents(".item"); - const item_id = element.data("itemId"); - const harm_id = $(ev.currentTarget).val(); - await this.actor.updateEmbeddedDocuments("Item", [{ - "_id": item_id, - "system.harm": [harm_id] - }]); - this.render(false); - }); - } -} -export default BladesCrewSheet; \ No newline at end of file diff --git a/module/sheets/blades-faction-sheet.js b/module/sheets/blades-faction-sheet.js deleted file mode 100644 index dfc02701..00000000 --- a/module/sheets/blades-faction-sheet.js +++ /dev/null @@ -1,31 +0,0 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - -import BladesSheet from "./blades-sheet.js"; -class BladesFactionSheet extends BladesSheet { - static get defaultOptions() { - return foundry.utils.mergeObject(super.defaultOptions, { - classes: ["eunos-blades", "sheet", "actor", "faction"], - template: "systems/eunos-blades/templates/faction-sheet.hbs", - width: 900, - height: "auto", - tabs: [{ navSelector: ".nav-tabs", contentSelector: ".tab-content" }] - }); - } - activateListeners(html) { - super.activateListeners(html); - if (!this.options.editable) { - return; - } - html.find(".item-body").on("click", (event) => { - const element = $(event.currentTarget).parents(".item"); - const item = this.actor.items.get(element.data("itemId")); - item?.sheet?.render(true); - }); - } -} -export default BladesFactionSheet; \ No newline at end of file diff --git a/module/sheets/blades-item-sheet.js b/module/sheets/blades-item-sheet.js deleted file mode 100644 index 5be81f26..00000000 --- a/module/sheets/blades-item-sheet.js +++ /dev/null @@ -1,186 +0,0 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - -import C, { Tag, District, Playbook, Vice } from "../core/constants.js"; -import U from "../core/utilities.js"; -import BladesActiveEffect from "../blades-active-effect.js"; -import Tagify from "../../lib/tagify/tagify.esm.js"; -class BladesItemSheet extends ItemSheet { - static get defaultOptions() { - return foundry.utils.mergeObject(super.defaultOptions, { - classes: ["eunos-blades", "sheet", "item"], - width: 560, - height: 500, - tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description" }] - }); - } - - constructor(item, options = {}) { - options.classes = [...options.classes ?? [], "eunos-blades", "sheet", "item", item.type]; - super(item, options); - } - get template() { - const pathComps = [ - "systems/eunos-blades/templates/items" - ]; - if (C.SimpleItemTypes.includes(this.item.type)) { - pathComps.push("simple-sheet.hbs"); - } - else { - pathComps.push(`${this.item.type}-sheet.hbs`); - } - return pathComps.join("/"); - } - - activateListeners(html) { - super.activateListeners(html); - const self = this; - if (!this.options.editable) { - return; - } - const tagElem = html.find(".tag-entry")[0]; - if (tagElem) { - const tagify = new Tagify(tagElem, { - enforceWhitelist: true, - editTags: false, - whitelist: [ - ...Object.values(Tag.System).map((tag) => ({ - "value": tag, - "data-group": "System Tags" - })), - ...Object.values(Tag.Item).map((tag) => ({ - "value": tag, - "data-group": "Item Tags" - })), - ...Object.values(Tag.PC).map((tag) => ({ - "value": tag, - "data-group": "Actor Tags" - })), - ...Object.values(Tag.NPC).map((tag) => ({ - "value": tag, - "data-group": "Actor Tags" - })), - ...Object.values(District).map((tag) => ({ - "value": tag, - "data-group": "Districts" - })), - ...Object.values(Vice).map((tag) => ({ - "value": tag, - "data-group": "Vices" - })), - ...Object.values(Playbook).map((tag) => ({ - "value": tag, - "data-group": "Playbooks" - })) - ], - dropdown: { - enabled: 0, - maxItems: 10000, - placeAbove: false, - appendTarget: html[0] - } - }); - tagify.dropdown.createListHTML = (optionsArr) => { - const map = {}; - return structuredClone(optionsArr) - .map((suggestion, idx) => { - const value = tagify.dropdown.getMappedValue.call(tagify, suggestion); - let tagHTMLString = ""; - if (!map[suggestion["data-group"]]) { - map[suggestion["data-group"]] = true; - if (Object.keys(map).length) { - tagHTMLString += ""; - } - tagHTMLString += ` -
-

${suggestion["data-group"]}

- `; - } - suggestion.value - = value && typeof value === "string" ? U.escapeHTML(value.replace(/_/g, " ")) : value; - tagHTMLString += tagify.settings.templates.dropdownItem.apply(tagify, [suggestion, idx]); - return tagHTMLString; - }) - .join(""); - }; - tagify.addTags(this.item.tags.map((tag) => { - if (Object.values(Tag.System).includes(tag)) { - return { "value": tag, "data-group": "System Tags" }; - } - if (Object.values(Tag.Item).includes(tag)) { - return { "value": tag, "data-group": "Item Tags" }; - } - if (Object.values(Tag.PC).includes(tag) || Object.values(Tag.NPC).includes(tag)) { - return { "value": tag, "data-group": "Actor Tags" }; - } - if (Object.values(District).includes(tag)) { - return { "value": tag, "data-group": "Districts" }; - } - if (Object.values(Playbook).includes(tag)) { - return { "value": tag, "data-group": "Playbooks" }; - } - if (Object.values(Vice).includes(tag)) { - return { "value": tag, "data-group": "Vices" }; - } - return { "value": tag, "data-group": "Other" }; - }), false, false); - tagElem.addEventListener("change", this._onTagifyChange.bind(this)); - } - if (this.options.submitOnChange) { - html.on("change", "textarea", this._onChangeInput.bind(this)); - } - html.find(".effect-control").on("click", (ev) => { - if (self.item.isOwned) { - ui.notifications.warn(game.i18n.localize("BITD.EffectWarning")); - return; - } - BladesActiveEffect.onManageActiveEffect(ev, self.item); - }); - html.find("[data-action=\"toggle-turf-connection\"").on("click", this.toggleTurfConnection.bind(this)); - } - async _onTagifyChange(event) { - const tagString = event.target.value; - if (tagString) { - const tags = JSON.parse(tagString) - .map(({ value }) => value); - this.item.update({ "system.tags": tags }); - } - else { - this.item.update({ "system.tags": [] }); - } - } - toggleTurfConnection(event) { - const button$ = $(event.currentTarget); - const connector$ = button$.parent(); - const turfNum = parseInt(connector$.data("index") ?? 0); - const turfDir = connector$.data("dir"); - if (!turfNum || !turfDir) { - return; - } - const toggleState = connector$.hasClass("no-connect"); - const updateData = { - [`system.turfs.${turfNum}.connects.${turfDir}`]: toggleState - }; - const partner = connector$.data("partner"); - if (typeof partner === "string" && /-/.test(partner)) { - const [partnerNum, partnerDir] = partner.split("-"); - updateData[`system.turfs.${partnerNum}.connects.${partnerDir}`] = toggleState; - } - this.item.update(updateData); - } - async getData() { - const context = (await super.getData()); - context.editable = this.options.editable; - context.isGM = game.user.isGM; - context.isEmbeddedItem = this.item.parent !== null; - context.item = this.item; - context.system = this.item.system; - context.effects = this.item.effects; - return context; - } -} -export default BladesItemSheet; \ No newline at end of file diff --git a/module/sheets/blades-npc-sheet.js b/module/sheets/blades-npc-sheet.js deleted file mode 100644 index c3835664..00000000 --- a/module/sheets/blades-npc-sheet.js +++ /dev/null @@ -1,41 +0,0 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - -import BladesSheet from "./blades-sheet.js"; -class BladesNPCSheet extends BladesSheet { - static get defaultOptions() { - return foundry.utils.mergeObject(super.defaultOptions, { - classes: ["eunos-blades", "sheet", "actor", "npc"], - template: "systems/eunos-blades/templates/npc-sheet.hbs", - width: 500, - height: 350, - tabs: [{ navSelector: ".nav-tabs", contentSelector: ".tab-content", initial: "description" }] - }); - } - getData() { - const context = super.getData(); - context.isSubActor = context.actor.isSubActor; - context.parentActor = context.actor.parentActor; - context.randomizers = context.actor.system.randomizers; - return context; - } - activateListeners(html) { - super.activateListeners(html); - if (!this.options.editable) { - return; - } - html.find(".gm-alert-header").on("click", async (event) => { - event.preventDefault(); - this.actor.clearParentActor(); - }); - - html.find("[data-action=\"randomize\"").on("click", (event) => { - this.actor.updateRandomizers(); - }); - } -} -export default BladesNPCSheet; \ No newline at end of file diff --git a/module/sheets/blades-roll-collab-sheet.js b/module/sheets/blades-roll-collab-sheet.js deleted file mode 100644 index 8d519006..00000000 --- a/module/sheets/blades-roll-collab-sheet.js +++ /dev/null @@ -1,570 +0,0 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - -import U from "../core/utilities.js"; -import C, { BladesActorType, RollType, RollModStatus, RollModCategory, Action, Attribute, Position, Effect, Factor, Harm } from "../core/constants.js"; -import BladesActor from "../blades-actor.js"; -import BladesItem from "../blades-item.js"; -import { ApplyTooltipListeners } from "../core/gsap.js"; -function isAction(trait) { - return Boolean(trait && typeof trait === "string" && trait in Action); -} -function isAttribute(trait) { - return Boolean(trait && typeof trait === "string" && trait in Attribute); -} -function isTier(trait) { return U.lCase(trait) === "tier"; } -function isNumber(trait) { return U.isInt(trait); } -class BladesRollCollabSheet extends DocumentSheet { - static get defaultOptions() { - return foundry.utils.mergeObject(super.defaultOptions, { - classes: ["eunos-blades", "sheet", "roll-collab"], - template: `systems/eunos-blades/templates/roll-collab${game.user.isGM ? "-gm" : ""}.hbs`, - submitOnChange: true, - width: 500 - }); - } - static Initialize() { - return loadTemplates([ - "systems/eunos-blades/templates/roll-collab-gm.hbs", - "systems/eunos-blades/templates/roll-collab.hbs" - ]); - } - static InitSockets() { - socketlib.system.register("renderRollCollab", BladesRollCollabSheet.RenderRollCollab); - socketlib.system.register("closeRollCollab", BladesRollCollabSheet.CloseRollCollab); - } - static Current = {}; - static get DefaultFlagData() { - return { - rollID: randomID(), - rollType: RollType.Action, - rollSourceType: "Actor", - rollSourceID: "", - rollTrait: Factor.tier, - rollMods: { - [RollModCategory.roll]: { - positive: { - Push: { - status: RollModStatus.ToggledOff, - value: 1, - tooltip: "

Take 2 Stress to add 1 die to your pool. (You cannot also accept a Devil's Bargain to increase your dice pool: It's one or the other.)

" - }, - Bargain: { - status: RollModStatus.ForcedOff, - value: 1, - tooltip: "

Accept a Devil's Bargain from the GM to add 1 die to your pool (You cannot also Push to increase your dice pool: It's one or the other. You can, however, Push to increase your Effect.)

" - }, - Assist: { - status: RollModStatus.Hidden, - value: 1, - sideString: "", - tooltip: "

Another character is Assisting your efforts, adding 1 die to your pool. (It costs them 1 Stress to do so.)

" - } - }, - negative: {} - }, - [RollModCategory.position]: { - positive: { - Setup: { - status: RollModStatus.Hidden, - value: 1, - sideString: undefined, - tooltip: "

Another character has set you up for success, increasing your Position by one level.

" - } - }, - negative: {} - }, - [RollModCategory.effect]: { - positive: { - Push: { - status: RollModStatus.ToggledOff, - value: 1, - tooltip: "

Take 2 Stress to increase your Effect by one level. (You can both Push for Effect and Push for +1d if you like, for a total cost of 4 Stress.)

" - }, - Setup: { - status: RollModStatus.Hidden, - value: 1, - sideString: undefined, - tooltip: "

Another character has set you up for success, increasing your Effect by one level.

" - }, - Potency: { - status: RollModStatus.Hidden, - value: 1, - tooltip: "" - } - }, - negative: {} - }, - [RollModCategory.result]: { positive: {}, negative: {} }, - [RollModCategory.after]: { positive: {}, negative: {} } - }, - rollPositionInitial: Position.risky, - rollEffectInitial: Effect.standard, - rollPosEffectTrade: false, - rollFactors: { - [Factor.tier]: { name: "Tier", value: 0, max: 0, isActive: false, isDominant: false, highFavorsPC: true }, - [Factor.quality]: { name: "Quality", value: 0, max: 0, isActive: false, isDominant: false, highFavorsPC: true }, - [Factor.force]: { name: "Force", value: 0, max: 0, isActive: false, isDominant: false, highFavorsPC: true }, - [Factor.scale]: { name: "Scale", value: 0, max: 0, isActive: false, isDominant: false, highFavorsPC: true }, - [Factor.area]: { name: "Area", value: 0, max: 0, isActive: false, isDominant: false, highFavorsPC: true }, - [Factor.duration]: { name: "Duration", value: 0, max: 0, isActive: false, isDominant: false, highFavorsPC: true }, - [Factor.range]: { name: "Range", value: 0, max: 0, isActive: false, isDominant: false, highFavorsPC: true }, - [Factor.magnitude]: { name: "Magnitude", value: 0, max: 0, isActive: false, isDominant: false, highFavorsPC: true } - }, - isGMReady: false, - GMBoosts: { - Dice: 0, - [Factor.tier]: 0, - [Factor.quality]: 0, - [Factor.force]: 0, - [Factor.scale]: 0, - [Factor.area]: 0, - [Factor.duration]: 0, - [Factor.range]: 0, - [Factor.magnitude]: 0, - Result: 0 - } - }; - } - static async RenderRollCollab({ userID, rollID }) { - const user = game.users.get(userID); - if (!user) { - return; - } - BladesRollCollabSheet.Current[rollID] = new BladesRollCollabSheet(user, rollID); - BladesRollCollabSheet.Current[rollID].render(true); - } - static async CloseRollCollab(rollID) { - eLog.checkLog3("rollCollab", "CloseRollCollab()", { rollID }); - await BladesRollCollabSheet.Current[rollID]?.close({ rollID }); - delete BladesRollCollabSheet.Current[rollID]; - } - static async NewRoll(config) { - if (game.user.isGM) { - eLog.error("rollCollab", "GM Cannot Use New Roll!"); - return; - } - const user = game.users.get(config.userID ?? game.user._id); - if (!(user instanceof User)) { - eLog.error("rollCollab", `[NewRoll()] Can't Find User '${config.userID}'`, config); - return; - } - const flagUpdateData = BladesRollCollabSheet.DefaultFlagData; - flagUpdateData.rollType = config.rollType; - if (!(flagUpdateData.rollType in RollType)) { - eLog.error("rollCollab", `[RenderRollCollab()] Invalid rollType: ${flagUpdateData.rollType}`, config); - return; - } - const rollSource = config.rollSource ?? user.character; - if (!(rollSource instanceof BladesActor || rollSource instanceof BladesItem)) { - eLog.error("rollCollab", "[RenderRollCollab()] Invalid rollSource", { rollSource, config }); - return; - } - flagUpdateData.rollSourceID = rollSource.id; - flagUpdateData.rollSourceType = rollSource instanceof BladesActor ? "Actor" : "Item"; - if (U.isInt(config.rollTrait)) { - flagUpdateData.rollTrait = config.rollTrait; - } - else if (!config.rollTrait) { - eLog.error("rollCollab", "[RenderRollCollab()] No RollTrait in Config", config); - return; - } - else { - switch (flagUpdateData.rollType) { - case RollType.Action: { - if (!(U.lCase(config.rollTrait) in { ...Action, ...Factor })) { - eLog.error("rollCollab", `[RenderRollCollab()] Bad RollTrait for Action Roll: ${config.rollTrait}`, config); - return; - } - flagUpdateData.rollTrait = U.lCase(config.rollTrait); - break; - } - case RollType.Downtime: { - if (!(U.lCase(config.rollTrait) in { ...Action, ...Factor })) { - eLog.error("rollCollab", `[RenderRollCollab()] Bad RollTrait for Downtime Roll: ${config.rollTrait}`, config); - return; - } - flagUpdateData.rollTrait = U.lCase(config.rollTrait); - break; - } - case RollType.Fortune: { - if (!(U.lCase(config.rollTrait) in { ...Action, ...Attribute, ...Factor })) { - eLog.error("rollCollab", `[RenderRollCollab()] Bad RollTrait for Fortune Roll: ${config.rollTrait}`, config); - return; - } - flagUpdateData.rollTrait = U.lCase(config.rollTrait); - break; - } - case RollType.Resistance: { - if (!(U.lCase(config.rollTrait) in Attribute)) { - eLog.error("rollCollab", `[RenderRollCollab()] Bad RollTrait for Resistance Roll: ${config.rollTrait}`, config); - return; - } - break; - } - } - flagUpdateData.rollTrait = U.lCase(config.rollTrait); - } - - flagUpdateData.rollMods = { - [RollModCategory.roll]: { - positive: { - "Push": { - status: RollModStatus.ToggledOff, - value: 1, - tooltip: "

Take 2 Stress to add 1 die to your pool.

(You cannot also accept a Devil's Bargain to increase your dice pool: It's one or the other.)

" - }, - "Bargain": { - status: RollModStatus.Hidden, - value: 1, - tooltip: "

Accept a Devil's Bargain from the GM to add 1 die to your pool.

(You cannot also Push to increase your dice pool: It's one or the other. You can, however, Push to increase your Effect.)

" - }, - "Assist": { - status: RollModStatus.ForcedOn, - value: 1, - sideString: "Ollie", - tooltip: "

Ollie is Assisting your efforts, adding 1 die to your pool. (It costs them 1 Stress to do so.)

" - }, - "Mastermind": { - status: RollModStatus.ToggledOff, - value: 1, - isAbility: true, - tooltip: "

You may expend your special armor to protect a teammate, or to push yourself when you gather information or work on a long-term project.

" - }, - "Trust In Me": { - status: RollModStatus.ToggledOn, - value: 1, - isAbility: true, - tooltip: "

You may expend your special armor to protect a teammate, or to push yourself when you gather information or work on a long-term project.

" - }, - "Forged in the Fire": { - status: RollModStatus.ToggledOff, - value: 1, - isAbility: true, - tooltip: "

You may expend your special armor to protect a teammate, or to push yourself when you gather information or work on a long-term project.

" - }, - "A Little Something on the Side": { - status: RollModStatus.ToggledOff, - value: 1, - isAbility: true, - tooltip: "

You may expend your special armor to protect a teammate, or to push yourself when you gather information or work on a long-term project.

" - }, - "Ghost Hunter (Arrow-Swift)": { - status: RollModStatus.ToggledOff, - value: 1, - isAbility: true, - tooltip: "

You may expend your special armor to protect a teammate, or to push yourself when you gather information or work on a long-term project.

" - } - }, - negative: { - [Harm.Impaired]: { - status: RollModStatus.ForcedOn, - value: 1, - tooltip: `

${Harm.Impaired}: Your injuries reduce your dice pool by one.

` - } - } - }, - [RollModCategory.position]: { - positive: { - Setup: { - status: RollModStatus.ForcedOn, - value: 1, - sideString: "Jax", - tooltip: "

Jax has set you up for success with a preceding action, increasing your Position by one level.

" - } - }, - negative: {} - }, - [RollModCategory.effect]: { - positive: { - "Push": { - status: RollModStatus.ToggledOn, - value: 1, - tooltip: "

Take 2 Stress to increase your Effect by one level.

(You can both Push for Effect and Push for +1d, for a total cost of 4 Stress.)

" - }, - "Setup": { - status: RollModStatus.ForcedOn, - value: 1, - sideString: "High-Flyer", - tooltip: "

High-Flyer has set you up for success with a preceding action, increasing your Effect by one level.

" - }, - "Potency": { - status: RollModStatus.Hidden, - value: 1, - tooltip: "

Circumstances in your favor make this action especially Potent, increasing your Effect by one level.

" - }, - "Cloak & Dagger": { - status: RollModStatus.ToggledOff, - value: 1, - isAbility: true, - tooltip: "

You may expend your special armor to protect a teammate, or to push yourself when you gather information or work on a long-term project.

" - } - }, - negative: { - [Harm.Impaired]: { - status: RollModStatus.ForcedOn, - value: 1, - tooltip: `

${Harm.Impaired}: Your injuries reduce your Effect by one level.

` - }, - Opposition: { - status: RollModStatus.ForcedOn, - value: 1, - tooltip: "

The following Factors combine to reduce your Effect by one level:

  • Inferior Quality
  • Detrimental Scale
" - } - } - }, - [RollModCategory.result]: { - positive: { - Mastermind: { - status: RollModStatus.ToggledOff, - value: 1, - isAbility: true, - tooltip: "

You may expend your special armor to protect a teammate, or to push yourself when you gather information or work on a long-term project.

" - } - }, negative: {} - }, - [RollModCategory.after]: { - positive: { - Mesmerism: { - status: RollModStatus.ToggledOff, - value: 1, - isAbility: true, - tooltip: "

You may expend your special armor to protect a teammate, or to push yourself when you gather information or work on a long-term project.

" - } - }, - negative: {} - } - }; - await user.setFlag(C.SYSTEM_ID, "rollCollab", flagUpdateData); - BladesRollCollabSheet.RenderRollCollab({ userID: user._id, rollID: flagUpdateData.rollID }); - socketlib.system.executeForAllGMs("renderRollCollab", { userID: user._id, rollID: flagUpdateData.rollID }); - } - rollID; - constructor(user, rollID) { - super(user); - this.rollID = rollID; - } - get rData() { - if (!this.document.getFlag(C.SYSTEM_ID, "rollCollab")) { - eLog.error("rollCollab", "[get flags()] No RollCollab Flags Found on User", { user: this.document, flags: this.document.flags }); - return null; - } - return this.document.flags["eunos-blades"].rollCollab; - } - get rollSource() { - if (!this.rData) { - return undefined; - } - return this.rData.rollSourceType === "Actor" - ? game.actors.get(this.rData.rollSourceID) - : game.items.get(this.rData.rollSourceID); - } - getData() { - const context = super.getData(); - const { rData } = this; - if (!rData) { - return context; - } - const sheetData = { - cssClass: "roll-collab", - editable: this.options.editable, - isGM: game.eunoblades.Tracker.system.is_spoofing_player ? false : game.user.isGM, - ...rData - }; - if (!this.rollSource) { - eLog.error("rollCollab", `[getData()] No '${rData.rollSourceType}' Found with ID '${rData.rollSourceID}'`, { user: this.document, rData: rData }); - return null; - } - sheetData.system = this.rollSource.system; - sheetData.rollSource = this.rollSource; - if (BladesActor.IsType(this.rollSource, BladesActorType.pc) && isAction(rData.rollTrait)) { - const { rollSource } = this; - sheetData.rollTraitData = { - name: rData.rollTrait, - value: rollSource.actions[rData.rollTrait], - max: rollSource.actions[rData.rollTrait] - }; - sheetData.rollTraitOptions = Object.values(Action) - .map((action) => ({ - name: U.uCase(action), - value: action - })); - } - else if (BladesActor.IsType(this.rollSource, BladesActorType.pc) && isAttribute(rData.rollTrait)) { - const { rollSource } = this; - sheetData.rollTraitData = { - name: rData.rollTrait, - value: rollSource.attributes[rData.rollTrait], - max: rollSource.attributes[rData.rollTrait] - }; - sheetData.rollTraitOptions = Object.values(Attribute) - .map((attribute) => ({ - name: U.uCase(attribute), - value: attribute - })); - } - else if (rData.rollTrait === "tier") { - const { rollSource } = this; - sheetData.rollTraitData = { - name: "Tier", - value: rollSource.getTierTotal(), - max: rollSource.getTierTotal() - }; - sheetData.rollTraitOptions = false; - } - else if (U.isInt(rData.rollTrait)) { - sheetData.rollTraitData = { - name: `+${rData.rollTrait}`, - value: rData.rollTrait, - max: rData.rollTrait - }; - sheetData.rollTraitOptions = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - .map((num) => ({ - name: `+${num}`, - value: num - })); - } - - sheetData.rollFactorData = [ - { name: Factor.quality, value: U.romanizeNum(2), cssClasses: "factor-gold factor-main" }, - { name: Factor.scale, value: `${2}`, cssClasses: "factor-gold" } - ]; - sheetData.rollOpposition = undefined; - sheetData.stressData = { cost: 4, tooltip: "
  • 2 Stress from Pushing for +1d
  • 2 Stress from Pushing for Effect
" }; - const getModsDelta = (cat) => { - const activePosMods = Object.values(sheetData.rollMods?.[cat]?.positive ?? {}) - .filter((mod) => [RollModStatus.ToggledOn, RollModStatus.ForcedOn].includes(mod.status)); - const posModVals = activePosMods.map((mod) => mod.value); - const posModSum = U.sum(posModVals); - const activeNegMods = Object.values(sheetData.rollMods?.[cat]?.negative ?? {}) - .filter((mod) => [RollModStatus.ToggledOn, RollModStatus.ForcedOn].includes(mod.status)); - const negModVals = activeNegMods.map((mod) => mod.value); - const negModSum = U.sum(negModVals); - eLog.checkLog3("rollMods", `getModsDelta(${cat}`, { activePosMods, posModVals, posModSum, activeNegMods, negModVals, negModSum, returnVal: U.sum(Object.values(sheetData.rollMods?.[cat]?.positive ?? {}) - .filter((mod) => [RollModStatus.ToggledOn, RollModStatus.ForcedOn].includes(mod.status)) - .map((mod) => mod.value)) - - U.sum(Object.values(sheetData.rollMods?.[cat]?.negative ?? {}) - .filter((mod) => [RollModStatus.ToggledOn, RollModStatus.ForcedOn].includes(mod.status)) - .map((mod) => mod.value)) }); - return U.sum(Object.values(sheetData.rollMods?.[cat]?.positive ?? {}) - .filter((mod) => [RollModStatus.ToggledOn, RollModStatus.ForcedOn].includes(mod.status)) - .map((mod) => mod.value)) - - U.sum(Object.values(sheetData.rollMods?.[cat]?.negative ?? {}) - .filter((mod) => [RollModStatus.ToggledOn, RollModStatus.ForcedOn].includes(mod.status)) - .map((mod) => mod.value)); - }; - sheetData.diceTotal = Math.max(0, (sheetData.rollTraitData?.value ?? 0) - + getModsDelta(RollModCategory.roll) - + (rData.GMBoosts.Dice ?? 0)); - let finalPosIndex = Object.values(Position).indexOf(sheetData.rollPositionInitial ?? Position.risky) - + getModsDelta(RollModCategory.position); - let finalEffectIndex = Object.values(Effect).indexOf(sheetData.rollEffectInitial ?? Effect.standard) - + getModsDelta(RollModCategory.effect); - const isPosEffectTradeValid = rData.rollPosEffectTrade === "position" - ? (finalPosIndex < 2 && finalEffectIndex > 0) - : (rData.rollPosEffectTrade === "effect" - ? (finalPosIndex > 0 && finalEffectIndex < 4) - : true); - if (isPosEffectTradeValid) { - if (rData.rollPosEffectTrade === "position") { - finalPosIndex++; - finalEffectIndex--; - } - if (rData.rollPosEffectTrade === "effect") { - finalPosIndex--; - finalEffectIndex++; - } - } - sheetData.rollPositionFinal = Object.values(Position)[U.clampNum(finalPosIndex, [0, 2])]; - sheetData.rollEffectFinal = Object.values(Effect)[U.clampNum(finalEffectIndex, [0, 4])]; - sheetData.isAffectingResult = getModsDelta(RollModCategory.result) !== 0 - || (rData.GMBoosts.Result ?? 0) !== 0; - if (sheetData.isAffectingResult) { - sheetData.rollResultFinal = getModsDelta(RollModCategory.result) - + (rData.GMBoosts.Result ?? 0); - } - sheetData.hasInactiveAbilities = { - [RollModCategory.roll]: Object.values(rData.rollMods.roll?.positive ?? {}) - .filter((mod) => mod.isAbility && mod.status === RollModStatus.ToggledOff) - .length > 0, - [RollModCategory.position]: Object.values(rData.rollMods.position?.positive ?? {}) - .filter((mod) => mod.isAbility && mod.status === RollModStatus.ToggledOff) - .length > 0, - [RollModCategory.effect]: Object.values(rData.rollMods.effect?.positive ?? {}) - .filter((mod) => mod.isAbility && mod.status === RollModStatus.ToggledOff) - .length > 0, - [RollModCategory.result]: Object.values(rData.rollMods.result?.positive ?? {}) - .filter((mod) => mod.isAbility && mod.status === RollModStatus.ToggledOff) - .length > 0, - [RollModCategory.after]: Object.values(rData.rollMods.after?.positive ?? {}) - .filter((mod) => mod.isAbility && mod.status === RollModStatus.ToggledOff) - .length > 0 - }; - const { success, partial, fail } = C.DiceOdds[sheetData.diceTotal ?? 0]; - sheetData.oddsGradient = [ - "linear-gradient(to right", - `var(--blades-black-dark) ${fail}%`, - `var(--blades-grey) ${fail + partial}%`, - `var(--blades-white-bright) ${fail + partial + success}%`, - "var(--blades-gold-bright))" - ].join(", "); - - eLog.checkLog3("getData", "RollCollab.getData()", { ...context, ...sheetData }); - return { - ...context, - ...sheetData - }; - } - activateListeners(html) { - super.activateListeners(html); - ApplyTooltipListeners(html); - const trade$ = html.find("[data-action='trade']"); - eLog.checkLog3("rollCollab", "Trade$", { trade: trade$ }); - html.find("[data-action='trade']").on({ - click: (event) => { - const curVal = `${$(event.currentTarget).data("value")}`; - eLog.checkLog3("rollCollab", "Click Event", { event, curVal }); - if (curVal === "false") { - this.document.setFlag(C.SYSTEM_ID, "rollCollab.rollPosEffectTrade", "position"); - } - else { - this.document.setFlag(C.SYSTEM_ID, "rollCollab.rollPosEffectTrade", false); - } - }, - contextmenu: (event) => { - const curVal = `${$(event.currentTarget).data("value")}`; - eLog.checkLog3("rollCollab", "Context Event", { event, curVal }); - if (curVal === "false") { - this.document.setFlag(C.SYSTEM_ID, "rollCollab.rollPosEffectTrade", "effect"); - } - else { - this.document.setFlag(C.SYSTEM_ID, "rollCollab.rollPosEffectTrade", false); - } - } - }); - } - async _onSubmit(event, { updateData } = {}) { - return super._onSubmit(event, { updateData, preventClose: true }) - .then((returnVal) => { this.render(); return returnVal; }); - } - async close(options = {}) { - eLog.checkLog3("rollCollab", "RollCollab.close()", { options }); - if (options.rollID) { - return super.close({}); - } - this.document.setFlag(C.SYSTEM_ID, "rollCollab", null); - socketlib.system.executeForEveryone("closeRollCollab", this.rollID); - return undefined; - } - render(force, options) { - if (!this.document.getFlag(C.SYSTEM_ID, "rollCollab")) { - return this; - } - return super.render(force, options); - } -} -export default BladesRollCollabSheet; \ No newline at end of file diff --git a/module/sheets/blades-sheet.js b/module/sheets/blades-sheet.js deleted file mode 100644 index 23b34d6e..00000000 --- a/module/sheets/blades-sheet.js +++ /dev/null @@ -1,377 +0,0 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - - -import U from "../core/utilities.js"; -import G from "../core/gsap.js"; -import { Tag, District, Playbook, Vice, BladesActorType } from "../core/constants.js"; -import Tagify from "../../lib/tagify/tagify.esm.js"; -import BladesSelectorDialog from "../blades-dialog.js"; -import BladesActiveEffect from "../blades-active-effect.js"; -class BladesSheet extends ActorSheet { - getData() { - const context = super.getData(); - const sheetData = { - editable: this.options.editable, - isGM: game.user.isGM, - actor: this.actor, - system: this.actor.system, - activeEffects: Array.from(this.actor.effects), - hasFullVision: game.user.isGM || this.actor.testUserPermission(game.user, CONST.DOCUMENT_PERMISSION_LEVELS.OBSERVER), - hasLimitedVision: game.user.isGM || this.actor.testUserPermission(game.user, CONST.DOCUMENT_PERMISSION_LEVELS.LIMITED), - hasControl: game.user.isGM || this.actor.testUserPermission(game.user, CONST.DOCUMENT_PERMISSION_LEVELS.OWNER), - playbookData: { - tooltip: (new Handlebars.SafeString([ - "
    ", - ...this.actor.playbook?.system.experience_clues?.map((line) => `
  • ${line}
  • `) ?? [], - "
" - ].join(""))).toString(), - dotline: { - data: this.actor.system.experience?.playbook, - target: "system.experience.playbook.value", - svgKey: "teeth.tall", - svgFull: "full|frame", - svgEmpty: "full|half|frame" - } - }, - coinsData: { - dotline: { - data: this.actor.system.coins, - target: "system.coins.value", - iconEmpty: "coin-full.svg", - iconFull: "coin-full.svg" - } - } - }; - return { - ...context, - ...sheetData - }; - } - activateListeners(html) { - super.activateListeners(html); - if (game.user.isGM) { - html.attr("style", "--secret-text-display: initial"); - } - else { - html.find('.editor:not(.tinymce) [data-is-secret="true"]').remove(); - } - - html.find(".tooltip").siblings(".comp-body") - .each(function (i, elem) { - $(elem).data("hoverTimeline", G.effects.hoverTooltip(elem)); - }) - .on({ - mouseenter: function () { - $(this).parent().css("z-index", 1); - $(this).data("hoverTimeline").play(); - }, - mouseleave: function () { - $(this).data("hoverTimeline").reverse().then(() => { - $(this).parent().removeAttr("style"); - }); - } - }); - if (!this.options.editable) { - return; - } - html.find(".dotline").each((_, elem) => { - if ($(elem).hasClass("locked")) { - return; - } - let targetDoc = this.actor; - let targetField = $(elem).data("target"); - const comp$ = $(elem).closest("comp"); - if (targetField.startsWith("item")) { - targetField = targetField.replace(/^item\./, ""); - const itemId = $(elem).closest("[data-comp-id]").data("compId"); - if (!itemId) { - return; - } - const item = this.actor.items.get(itemId); - if (!item) { - return; - } - targetDoc = item; - } - const curValue = U.pInt($(elem).data("value")); - $(elem) - .find(".dot") - .each((j, dot) => { - $(dot).on("click", (event) => { - event.preventDefault(); - const thisValue = U.pInt($(dot).data("value")); - if (thisValue !== curValue) { - if (comp$.hasClass("comp-coins") - || comp$.hasClass("comp-stash")) { - G.effects - .fillCoins($(dot).prevAll(".dot")) - .then(() => targetDoc.update({ [targetField]: thisValue })); - } - else { - targetDoc.update({ [targetField]: thisValue }); - } - } - }); - $(dot).on("contextmenu", (event) => { - event.preventDefault(); - const thisValue = U.pInt($(dot).data("value")) - 1; - if (thisValue !== curValue) { - targetDoc.update({ [targetField]: thisValue }); - } - }); - }); - }); - html - .find(".clock-container") - .on("click", this._onClockLeftClick.bind(this)); - html - .find(".clock-container") - .on("contextmenu", this._onClockRightClick.bind(this)); - html - .find("[data-comp-id]") - .find(".comp-title") - .on("click", this._onItemOpenClick.bind(this)); - html - .find(".comp-control.comp-add") - .on("click", this._onItemAddClick.bind(this)); - html - .find(".comp-control.comp-delete") - .on("click", this._onItemRemoveClick.bind(this)); - html - .find(".comp-control.comp-delete-full") - .on("click", this._onItemFullRemoveClick.bind(this)); - html - .find(".comp-control.comp-toggle") - .on("click", this._onItemToggleClick.bind(this)); - html - .find("[data-roll-attribute]") - .on("click", this._onRollAttributeDieClick.bind(this)); - html - .find(".effect-control") - .on("click", this._onActiveEffectControlClick.bind(this)); - const tagElem = html.find(".tag-entry")[0]; - if (tagElem) { - const tagify = new Tagify(tagElem, { - enforceWhitelist: false, - editTags: true, - whitelist: [ - ...Object.values(Tag.System).map((tag) => ({ - "value": (new Handlebars.SafeString(tag)).toString(), - "data-group": "System Tags" - })), - ...Object.values(Tag.Item).map((tag) => ({ - "value": (new Handlebars.SafeString(tag)).toString(), - "data-group": "Item Tags" - })), - ...Object.values(Tag.PC).map((tag) => ({ - "value": (new Handlebars.SafeString(tag)).toString(), - "data-group": "Actor Tags" - })), - ...Object.values(Tag.NPC).map((tag) => ({ - "value": (new Handlebars.SafeString(tag)).toString(), - "data-group": "Actor Tags" - })), - ...Object.values(District).map((tag) => ({ - "value": (new Handlebars.SafeString(tag)).toString(), - "data-group": "Districts" - })), - ...Object.values(Vice).map((tag) => ({ - "value": (new Handlebars.SafeString(tag)).toString(), - "data-group": "Vices" - })), - ...Object.values(Playbook).map((tag) => ({ - "value": (new Handlebars.SafeString(tag)).toString(), - "data-group": "Playbooks" - })) - ], - dropdown: { - enabled: 0, - maxItems: 10000, - placeAbove: false, - appendTarget: html[0] - } - }); - tagify.dropdown.createListHTML = (optionsArr) => { - const map = {}; - return structuredClone(optionsArr) - .map((suggestion, idx) => { - const value = tagify.dropdown.getMappedValue.call(tagify, suggestion); - let tagHTMLString = ""; - if (!map[suggestion["data-group"]]) { - map[suggestion["data-group"]] = true; - if (Object.keys(map).length) { - tagHTMLString += "
"; - } - tagHTMLString += ` -
-

${suggestion["data-group"]}

- `; - } - suggestion.value - = value && typeof value === "string" ? U.escapeHTML(value) : value; - tagHTMLString += tagify.settings.templates.dropdownItem.apply(tagify, [suggestion, idx]); - return tagHTMLString; - }) - .join(""); - }; - tagify.addTags(this.actor.tags.map((tag) => { - if (Object.values(Tag.System).includes(tag)) { - return { "value": (new Handlebars.SafeString(tag)).toString(), "data-group": "System Tags" }; - } - if (Object.values(Tag.Item).includes(tag)) { - return { "value": (new Handlebars.SafeString(tag)).toString(), "data-group": "Item Tags" }; - } - if (Object.values(Tag.PC).includes(tag) || Object.values(Tag.NPC).includes(tag)) { - return { "value": (new Handlebars.SafeString(tag)).toString(), "data-group": "Actor Tags" }; - } - if (Object.values(District).includes(tag)) { - return { "value": (new Handlebars.SafeString(tag)).toString(), "data-group": "Districts" }; - } - if (Object.values(Playbook).includes(tag)) { - return { "value": (new Handlebars.SafeString(tag)).toString(), "data-group": "Playbooks" }; - } - if (Object.values(Vice).includes(tag)) { - return { "value": (new Handlebars.SafeString(tag)).toString(), "data-group": "Vices" }; - } - return { "value": (new Handlebars.SafeString(tag)).toString(), "data-group": "Other" }; - }), false, false); - tagElem.addEventListener("change", this._onTagifyChange.bind(this)); - } - if (this.options.submitOnChange) { - html.on("change", "textarea", this._onChangeInput.bind(this)); - } - } - async _onSubmit(event, params = {}) { - if (!game.user.isGM && !this.actor.testUserPermission(game.user, CONST.DOCUMENT_PERMISSION_LEVELS.OWNER)) { - eLog.checkLog("actorSheetTrigger", "User does not have permission to edit this actor", { user: game.user, actor: this.actor }); - return {}; - } - eLog.checkLog("actorSheetTrigger", "Submitting Form Data", { parentActor: this.actor.parentActor, systemTags: this.actor.system.tags, sourceTags: this.actor._source.system.tags, params }); - return super._onSubmit(event, params); - } - async close(options) { - if (this.actor.type === BladesActorType.pc) { - return super.close(options).then(() => this.actor.clearSubActors()); - } - else if (this.actor.type === BladesActorType.npc && this.actor.parentActor) { - return super.close(options).then(() => this.actor.clearParentActor(false)); - } - return super.close(options); - } - - async _onClockLeftClick(event) { - event.preventDefault(); - const clock$ = $(event.currentTarget).find(".clock[data-target]"); - if (!clock$[0]) { - return; - } - const target = clock$.data("target"); - const curValue = U.pInt(clock$.data("value")); - const maxValue = U.pInt(clock$.data("size")); - G.effects.pulseClockWedges(clock$.find("wedges")).then(() => this.actor.update({ - [target]: G.utils.wrap(0, maxValue + 1, curValue + 1) - })); - } - async _onClockRightClick(event) { - event.preventDefault(); - const clock$ = $(event.currentTarget).find(".clock[data-target]"); - if (!clock$[0]) { - return; - } - const target = clock$.data("target"); - const curValue = U.pInt(clock$.data("value")); - G.effects.reversePulseClockWedges(clock$.find("wedges")).then(() => this.actor.update({ - [target]: Math.max(0, curValue - 1) - })); - } - async _onTagifyChange(event) { - const tagString = event.target.value; - if (tagString) { - const tags = JSON.parse(tagString).map(({ value }) => value); - this.actor.update({ "system.tags": tags }); - } - else { - this.actor.update({ "system.tags": [] }); - } - } - - _getCompData(event) { - const elem$ = $(event.currentTarget).closest(".comp"); - const compData = { - elem$, - docID: elem$.data("compId"), - docCat: elem$.data("compCat"), - docType: elem$.data("compType"), - docTags: (elem$.data("compTags") ?? "").split(/\s+/g) - }; - if (compData.docID && compData.docType) { - compData.doc = { - Actor: this.actor.getSubActor(compData.docID), - Item: this.actor.getSubItem(compData.docID) - }[compData.docType]; - } - if (compData.docCat && compData.docType) { - compData.dialogDocs = { - Actor: this.actor.getDialogActors(compData.docCat), - Item: this.actor.getDialogItems(compData.docCat) - }[compData.docType]; - } - eLog.checkLog2("dialog", "Component Data", { ...compData }); - return compData; - } - async _onItemOpenClick(event) { - event.preventDefault(); - const { doc } = this._getCompData(event); - if (!doc) { - return; - } - doc.sheet?.render(true); - } - async _onItemAddClick(event) { - event.preventDefault(); - const { docCat, docType, dialogDocs, docTags } = this._getCompData(event); - eLog.checkLog("_onItemAddClick", { docCat, dialogDocs }); - if (!dialogDocs || !docCat || !docType) { - return; - } - await BladesSelectorDialog.Display(this.actor, U.tCase(`Add ${docCat.replace(/_/g, " ")}`), docType, dialogDocs, docTags); - } - async _onItemRemoveClick(event) { - event.preventDefault(); - const { elem$, doc } = this._getCompData(event); - if (!doc) { - return; - } - G.effects.blurRemove(elem$).then(() => doc.addTag(Tag.System.Archived)); - } - async _onItemFullRemoveClick(event) { - event.preventDefault(); - const { elem$, doc } = this._getCompData(event); - if (!doc) { - return; - } - G.effects.blurRemove(elem$).then(() => doc.delete()); - } - async _onItemToggleClick(event) { - event.preventDefault(); - const target = $(event.currentTarget).data("target"); - this.actor.update({ - [target]: !getProperty(this.actor, target) - }); - } - - async _onRollAttributeDieClick(event) { - const attribute_name = $(event.currentTarget).data("rollAttribute"); - this.actor.rollAttributePopup(attribute_name); - } - - async _onActiveEffectControlClick(event) { - BladesActiveEffect.onManageActiveEffect(event, this.actor); - } -} -export default BladesSheet; \ No newline at end of file diff --git a/module/sheets/blades-tracker-sheet.js b/module/sheets/blades-tracker-sheet.js deleted file mode 100644 index 6447bae2..00000000 --- a/module/sheets/blades-tracker-sheet.js +++ /dev/null @@ -1,94 +0,0 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - -import BladesItemSheet from "./blades-item-sheet.js"; -import BladesItem from "../blades-item.js"; -export var BladesPhase; -(function (BladesPhase) { - BladesPhase["CharGen"] = "CharGen"; - BladesPhase["Freeplay"] = "Freeplay"; - BladesPhase["Score"] = "Score"; - BladesPhase["Downtime"] = "Downtime"; -})(BladesPhase || (BladesPhase = {})); -export var BladesTipContext; -(function (BladesTipContext) { - BladesTipContext["DiceRoll"] = "DiceRoll"; - BladesTipContext["Combat"] = "Combat"; - BladesTipContext["General"] = "General"; -})(BladesTipContext || (BladesTipContext = {})); -class BladesTipGenerator { - static get Tips() { - return { - [BladesTipContext.DiceRoll]: [], - [BladesTipContext.Combat]: [ - "Every combat encounter should advance the main plot, or else it's filler.", - "Inject dialogue into combat encounters, especially from important adversaries.", - "Combat encounters should be a challenge, but not a slog. Don't be afraid to end them early.", - "Infiltrate/Rescue/Destroy: Use these as additional/secondary goals in combat encounters.", - "Tell the next player in the initiative order that they're on deck.", - "Don't trigger combats automatically: Use alternate objectives to incite the players to fight, giving them agency.", - "Add another layer by drawing focus to collateral effects of the combat: a fire, a hostage, a collapsing building, innocents in danger" - ], - [BladesTipContext.General]: [ - "Rolling the dice always means SOMETHING happens.", - "Jump straight to the action; don't waste time on establishing scenes or filler.", - "Invoke elements of characters' backstories or beliefs to make any scene more personal." - ] - }; - } - tipContext; - constructor(tipContext) { - this.tipContext = tipContext; - } -} -class BladesTrackerSheet extends BladesItemSheet { - static async Get() { - return game.eunoblades.Tracker || (await BladesItem.create({ - name: "GM Tracker", - type: "gm_tracker", - img: "systems/eunos-blades/assets/icons/gm-tracker.svg" - })); - } - static get defaultOptions() { - return foundry.utils.mergeObject(super.defaultOptions, { - classes: ["eunos-blades", "sheet", "item", "gm-tracker"], - template: "systems/eunos-blades/templates/items/gm_tracker-sheet.hbs", - width: 700, - height: 970 - }); - } - static async Initialize() { - game.eunoblades ??= {}; - Items.registerSheet("blades", BladesTrackerSheet, { types: ["gm_tracker"], makeDefault: true }); - Hooks.once("ready", async () => { - let tracker = game.items.find((item) => item.type === "gm_tracker"); - if (!(tracker instanceof BladesItem)) { - tracker = (await BladesItem.create({ - name: "GM Tracker", - type: "gm_tracker", - img: "systems/eunos-blades/assets/icons/gm-tracker.svg" - })); - } - game.eunoblades.Tracker = tracker; - }); - return loadTemplates([ - "systems/eunos-blades/templates/items/gm_tracker-sheet.hbs" - ]); - } - get phase() { return this.item.system.game_phase; } - set phase(phase) { this.item.update({ system: { game_phase: phase } }); } - get actionMax() { return this.phase === BladesPhase.CharGen ? 2 : undefined; } - async getData() { - const context = await super.getData(); - context.system.phases = Object.values(BladesPhase); - return context; - } - async activateListeners(html) { - super.activateListeners(html); - } -} -export default BladesTrackerSheet; \ No newline at end of file diff --git a/module/sheets/item/BladesClockKeeperSheet.js b/module/sheets/item/BladesClockKeeperSheet.js index 79ad3445..63d05dbb 100644 --- a/module/sheets/item/BladesClockKeeperSheet.js +++ b/module/sheets/item/BladesClockKeeperSheet.js @@ -1,14 +1,11 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - +/*~ @@DOUBLE-BLANK@@ ~*/ import BladesItemSheet from "./BladesItemSheet.js"; import BladesClockKeeper from "../../documents/items/BladesClockKeeper.js"; +/*~ @@DOUBLE-BLANK@@ ~*/ class BladesClockKeeperSheet extends BladesItemSheet { + /*~ @@DOUBLE-BLANK@@ ~*/ static Get() { return game.eunoblades.ClockKeeper; } + /*~ @@DOUBLE-BLANK@@ ~*/ static get defaultOptions() { return foundry.utils.mergeObject(super.defaultOptions, { classes: ["eunos-blades", "sheet", "item", "clock-keeper"], @@ -17,6 +14,7 @@ class BladesClockKeeperSheet extends BladesItemSheet { height: 970 }); } + /*~ @@DOUBLE-BLANK@@ ~*/ static async Initialize() { game.eunoblades ??= {}; Items.registerSheet("blades", BladesClockKeeperSheet, { types: ["clock_keeper"], makeDefault: true }); @@ -38,6 +36,7 @@ class BladesClockKeeperSheet extends BladesItemSheet { "systems/eunos-blades/templates/parts/clock-sheet-row.hbs" ]); } + /*~ @@DOUBLE-BLANK@@ ~*/ static InitSockets() { if (game.eunoblades.ClockKeeper) { socketlib.system.register("renderOverlay", game.eunoblades.ClockKeeper.renderOverlay); @@ -45,18 +44,30 @@ class BladesClockKeeperSheet extends BladesItemSheet { } return false; } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Override async _updateObject(event: unknown, formData: any) { + // const updateData = await`this.object.update(formData); + // socketlib.system.executeForEveryone("renderOverlay"); + // // this.item.renderOverlay(); + // return updateData; + // } + /*~ @@DOUBLE-BLANK@@ ~*/ getData() { const context = super.getData(); + /*~ @@DOUBLE-BLANK@@ ~*/ const sheetData = { clock_keys: Object.fromEntries((Object.entries(context.system.clock_keys ?? {}) .filter(([keyID, keyData]) => Boolean(keyData && keyData.scene === context.system.targetScene)))) }; + /*~ @@DOUBLE-BLANK@@ ~*/ return { ...context, ...sheetData }; } + /*~ @@DOUBLE-BLANK@@ ~*/ addKey(event) { event.preventDefault(); this.item.addClockKey(); } + /*~ @@DOUBLE-BLANK@@ ~*/ deleteKey(event) { event.preventDefault(); const keyID = event.currentTarget.dataset.id; @@ -64,6 +75,7 @@ class BladesClockKeeperSheet extends BladesItemSheet { this.item.deleteClockKey(keyID); } } + /*~ @@DOUBLE-BLANK@@ ~*/ setKeySize(event) { event.preventDefault(); const keyID = event.target.dataset.id; @@ -71,11 +83,18 @@ class BladesClockKeeperSheet extends BladesItemSheet { this.item.setKeySize(keyID, parseInt(event.target.value, 10)); } } + /*~ @@DOUBLE-BLANK@@ ~*/ async activateListeners(html) { super.activateListeners(html); + /*~ @@DOUBLE-BLANK@@ ~*/ + // @ts-expect-error Fuck. html.find("[data-action=\"add-key\"").on("click", this.addKey.bind(this)); + // @ts-expect-error Fuck. html.find("[data-action=\"delete-key\"").on("click", this.deleteKey.bind(this)); + // @ts-expect-error Fuck. html.find(".key-clock-counter").on("change", this.setKeySize.bind(this)); } } -export default BladesClockKeeperSheet; \ No newline at end of file +/*~ @@DOUBLE-BLANK@@ ~*/ +export default BladesClockKeeperSheet; +/*~ @@DOUBLE-BLANK@@ ~*/ diff --git a/module/sheets/item/BladesGMTrackerSheet.js b/module/sheets/item/BladesGMTrackerSheet.js index 9591ffab..0092c4d6 100644 --- a/module/sheets/item/BladesGMTrackerSheet.js +++ b/module/sheets/item/BladesGMTrackerSheet.js @@ -1,30 +1,30 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - +/*~ @@DOUBLE-BLANK@@ ~*/ import { BladesActorType, BladesItemType, BladesPhase } from "../../core/constants.js"; import BladesItemSheet from "./BladesItemSheet.js"; import BladesItem from "../../BladesItem.js"; import BladesGMTracker from "../../documents/items/BladesGMTracker.js"; import BladesActor from "../../BladesActor.js"; import BladesPC from "../../documents/actors/BladesPC.js"; +/*~ @@DOUBLE-BLANK@@ ~*/ +// eslint-disable-next-line no-shadow export var BladesTipContext; (function (BladesTipContext) { BladesTipContext["DiceRoll"] = "DiceRoll"; BladesTipContext["Combat"] = "Combat"; BladesTipContext["General"] = "General"; })(BladesTipContext || (BladesTipContext = {})); +/*~ @@DOUBLE-BLANK@@ ~*/ class BladesTipGenerator { + /*~ @@DOUBLE-BLANK@@ ~*/ static Test(pcActor) { if (BladesActor.IsType(pcActor, BladesActorType.pc)) { return pcActor; } return undefined; } + /*~ @@DOUBLE-BLANK@@ ~*/ testActor = new BladesPC({ name: "blah", type: "pc" }); + /*~ @@DOUBLE-BLANK@@ ~*/ static get Tips() { return { [BladesTipContext.DiceRoll]: [], @@ -41,16 +41,22 @@ class BladesTipGenerator { "Rolling the dice always means SOMETHING happens.", "Jump straight to the action; don't waste time on establishing scenes or filler.", "Invoke elements of characters' backstories or beliefs to make any scene more personal." + /*~ @@DOUBLE-BLANK@@ ~*/ ] }; } + /*~ @@DOUBLE-BLANK@@ ~*/ tipContext; + /*~ @@DOUBLE-BLANK@@ ~*/ constructor(tipContext) { this.tipContext = tipContext; } } +/*~ @@DOUBLE-BLANK@@ ~*/ class BladesGMTrackerSheet extends BladesItemSheet { + /*~ @@DOUBLE-BLANK@@ ~*/ static Get() { return game.eunoblades.Tracker; } + /*~ @@DOUBLE-BLANK@@ ~*/ static get defaultOptions() { return foundry.utils.mergeObject(super.defaultOptions, { classes: ["eunos-blades", "sheet", "item", "gm-tracker"], @@ -59,6 +65,7 @@ class BladesGMTrackerSheet extends BladesItemSheet { height: 970 }); } + /*~ @@DOUBLE-BLANK@@ ~*/ static async Initialize() { game.eunoblades ??= {}; Items.registerSheet("blades", BladesGMTrackerSheet, { types: ["gm_tracker"], makeDefault: true }); @@ -78,9 +85,12 @@ class BladesGMTrackerSheet extends BladesItemSheet { "systems/eunos-blades/templates/items/gm_tracker-sheet.hbs" ]); } + /*~ @@DOUBLE-BLANK@@ ~*/ async activateListeners(html) { super.activateListeners(html); + /*~ @@DOUBLE-BLANK@@ ~*/ } + /*~ @@DOUBLE-BLANK@@ ~*/ async _onSubmit(event, params = {}) { const prevPhase = this.item.system.phase; const submitData = await super._onSubmit(event, params); @@ -89,9 +99,11 @@ class BladesGMTrackerSheet extends BladesItemSheet { if (prevPhase !== newPhase) { switch (prevPhase) { case BladesPhase.CharGen: { + /*~ @@DOUBLE-BLANK@@ ~*/ break; } case BladesPhase.Freeplay: { + /*~ @@DOUBLE-BLANK@@ ~*/ break; } case BladesPhase.Score: { @@ -101,21 +113,26 @@ class BladesGMTrackerSheet extends BladesItemSheet { break; } case BladesPhase.Downtime: { + /*~ @@DOUBLE-BLANK@@ ~*/ break; } default: break; } switch (newPhase) { case BladesPhase.CharGen: { + /*~ @@DOUBLE-BLANK@@ ~*/ break; } case BladesPhase.Freeplay: { + /*~ @@DOUBLE-BLANK@@ ~*/ break; } case BladesPhase.Score: { + /*~ @@DOUBLE-BLANK@@ ~*/ break; } case BladesPhase.Downtime: { + /*~ @@DOUBLE-BLANK@@ ~*/ break; } default: break; @@ -128,5 +145,7 @@ class BladesGMTrackerSheet extends BladesItemSheet { return submitData; } } +/*~ @@DOUBLE-BLANK@@ ~*/ export default BladesGMTrackerSheet; -export { BladesTipGenerator }; \ No newline at end of file +export { BladesTipGenerator }; +/*~ @@DOUBLE-BLANK@@ ~*/ diff --git a/module/sheets/item/BladesItemSheet.js b/module/sheets/item/BladesItemSheet.js index 64a0ca61..25db4276 100644 --- a/module/sheets/item/BladesItemSheet.js +++ b/module/sheets/item/BladesItemSheet.js @@ -1,17 +1,13 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - import C, { BladesItemType, BladesPhase, Factor } from "../../core/constants.js"; import U from "../../core/utilities.js"; import G, { ApplyTooltipListeners } from "../../core/gsap.js"; import BladesItem from "../../BladesItem.js"; import BladesActiveEffect from "../../BladesActiveEffect.js"; +/*~ @@DOUBLE-BLANK@@ ~*/ import Tags from "../../core/tags.js"; +/*~ @@DOUBLE-BLANK@@ ~*/ class BladesItemSheet extends ItemSheet { + /*~ @@DOUBLE-BLANK@@ ~*/ static get defaultOptions() { return foundry.utils.mergeObject(super.defaultOptions, { classes: ["eunos-blades", "sheet", "item"], @@ -20,10 +16,19 @@ class BladesItemSheet extends ItemSheet { tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description" }] }); } - - + /*~ @@DOUBLE-BLANK@@ ~*/ + /* -------------------------------------------- */ + /*~ @@DOUBLE-BLANK@@ ~*/ + // constructor(item: BladesItem, options: Partial = {}) { + // options.classes = [...options.classes ?? [], "eunos-blades", "sheet", "item", item.type]; + // super(item, options); + // } + /*~ @@DOUBLE-BLANK@@ ~*/ + // override async getData() { + /*~ @@DOUBLE-BLANK@@ ~*/ getData() { const context = super.getData(); + /*~ @@DOUBLE-BLANK@@ ~*/ const sheetData = { cssClass: this.item.type, editable: this.options.editable, @@ -34,8 +39,10 @@ class BladesItemSheet extends ItemSheet { tierTotal: this.item.getFactorTotal(Factor.tier) > 0 ? U.romanizeNum(this.item.getFactorTotal(Factor.tier)) : "0", activeEffects: Array.from(this.item.effects) }; + /*~ @@DOUBLE-BLANK@@ ~*/ return this._getTypedItemData[this.item.type]({ ...context, ...sheetData }); } + /*~ @@DOUBLE-BLANK@@ ~*/ _getTypedItemData = { [BladesItemType.ability]: (context) => { if (!BladesItem.IsType(this.item, BladesItemType.ability)) { @@ -89,12 +96,14 @@ class BladesItemSheet extends ItemSheet { } } }; + /*~ @@DOUBLE-BLANK@@ ~*/ sheetData.edgeData = Object.fromEntries(Object.values(context.system.edges ?? []) .filter((edge) => /[A-Za-z]/.test(edge)) .map((edge) => [edge.trim(), C.EdgeTooltips[edge]])); sheetData.flawData = Object.fromEntries(Object.values(context.system.flaws ?? []) .filter((flaw) => /[A-Za-z]/.test(flaw)) .map((flaw) => [flaw.trim(), C.FlawTooltips[flaw]])); + /*~ @@DOUBLE-BLANK@@ ~*/ return { ...context, ...sheetData @@ -195,6 +204,7 @@ class BladesItemSheet extends ItemSheet { } } }; + /*~ @@DOUBLE-BLANK@@ ~*/ return { ...context, ...sheetData @@ -295,6 +305,7 @@ class BladesItemSheet extends ItemSheet { return context; } }; + /*~ @@DOUBLE-BLANK@@ ~*/ get template() { const pathComps = [ "systems/eunos-blades/templates/items" @@ -307,22 +318,32 @@ class BladesItemSheet extends ItemSheet { } return pathComps.join("/"); } - + /*~ @@DOUBLE-BLANK@@ ~*/ + /* -------------------------------------------- */ + /*~ @@DOUBLE-BLANK@@ ~*/ activateListeners(html) { super.activateListeners(html); const self = this; + /*~ @@DOUBLE-BLANK@@ ~*/ Tags.InitListeners(html, this.item); ApplyTooltipListeners(html); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Everything below here is only needed if the sheet is editable if (!this.options.editable) { return; } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Add dotline functionality html.find(".dotline").each((__, elem) => { if ($(elem).hasClass("locked")) { return; } + /*~ @@DOUBLE-BLANK@@ ~*/ const targetDoc = this.item; const targetField = $(elem).data("target"); + /*~ @@DOUBLE-BLANK@@ ~*/ const comp$ = $(elem).closest("comp"); + /*~ @@DOUBLE-BLANK@@ ~*/ const curValue = U.pInt($(elem).data("value")); $(elem) .find(".dot") @@ -351,6 +372,8 @@ class BladesItemSheet extends ItemSheet { }); }); }); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Harm Bar Functionality for Cohorts if (BladesItem.IsType(this.item, BladesItemType.cohort_expert, BladesItemType.cohort_gang)) { html.find("[data-harm-click]").on({ click: (event) => { @@ -369,9 +392,12 @@ class BladesItemSheet extends ItemSheet { } }); } + /*~ @@DOUBLE-BLANK@@ ~*/ + // This is a workaround until is being fixed in FoundryVTT. if (this.options.submitOnChange) { - html.on("change", "textarea", this._onChangeInput.bind(this)); + html.on("change", "textarea", this._onChangeInput.bind(this)); // Use delegated listener on the form } + /*~ @@DOUBLE-BLANK@@ ~*/ html.find(".effect-control").on("click", (ev) => { if (self.item.isOwned) { ui.notifications?.warn(game.i18n.localize("BITD.EffectWarning")); @@ -379,8 +405,10 @@ class BladesItemSheet extends ItemSheet { } BladesActiveEffect.onManageActiveEffect(ev, self.item); }); + /*~ @@DOUBLE-BLANK@@ ~*/ html.find("[data-action=\"toggle-turf-connection\"").on("click", this.toggleTurfConnection.bind(this)); } + /*~ @@DOUBLE-BLANK@@ ~*/ toggleTurfConnection(event) { const button$ = $(event.currentTarget); const connector$ = button$.parent(); @@ -401,4 +429,6 @@ class BladesItemSheet extends ItemSheet { this.item.update(updateData); } } -export default BladesItemSheet; \ No newline at end of file +/*~ @@DOUBLE-BLANK@@ ~*/ +export default BladesItemSheet; +/*~ @@DOUBLE-BLANK@@ ~*/ diff --git a/module/sheets/item/BladesScoreSheet.js b/module/sheets/item/BladesScoreSheet.js index df997456..9afa9cdf 100644 --- a/module/sheets/item/BladesScoreSheet.js +++ b/module/sheets/item/BladesScoreSheet.js @@ -1,23 +1,23 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - import U from "../../core/utilities.js"; import { BladesActorType, BladesPhase, Tag, Randomizers } from "../../core/constants.js"; import BladesItemSheet from "./BladesItemSheet.js"; +/*~ @@DOUBLE-BLANK@@ ~*/ import { BladesActor } from "../../documents/BladesActorProxy.js"; import { BladesScore } from "../../documents/BladesItemProxy.js"; import BladesRoll, { BladesRollOpposition } from "../../BladesRoll.js"; +/*~ @@DOUBLE-BLANK@@ ~*/ +/* #region BladesTipGenerator */ +/*~ @@DOUBLE-BLANK@@ ~*/ +// eslint-disable-next-line no-shadow export var BladesTipContext; (function (BladesTipContext) { BladesTipContext["DiceRoll"] = "DiceRoll"; BladesTipContext["Combat"] = "Combat"; BladesTipContext["General"] = "General"; })(BladesTipContext || (BladesTipContext = {})); +/*~ @@DOUBLE-BLANK@@ ~*/ class BladesTipGenerator { + /*~ @@DOUBLE-BLANK@@ ~*/ static get Tips() { return { [BladesTipContext.DiceRoll]: [], @@ -34,15 +34,21 @@ class BladesTipGenerator { "Rolling the dice always means SOMETHING happens.", "Jump straight to the action; don't waste time on establishing scenes or filler.", "Invoke elements of characters' backstories or beliefs to make any scene more personal." + /*~ @@DOUBLE-BLANK@@ ~*/ ] }; } + /*~ @@DOUBLE-BLANK@@ ~*/ tipContext; + /*~ @@DOUBLE-BLANK@@ ~*/ constructor(tipContext) { this.tipContext = tipContext; } } +/* #endregion */ +/*~ @@DOUBLE-BLANK@@ ~*/ class BladesScoreSheet extends BladesItemSheet { + /*~ @@DOUBLE-BLANK@@ ~*/ static get defaultOptions() { return foundry.utils.mergeObject(super.defaultOptions, { classes: ["eunos-blades", "sheet", "item", "score-sheet"], @@ -52,7 +58,9 @@ class BladesScoreSheet extends BladesItemSheet { height: 970 }); } + /*~ @@DOUBLE-BLANK@@ ~*/ async generateRandomizerData(category) { + // Generate full set of random data. const randomData = { Bargains: Object.fromEntries(Object.entries(U.sample(Randomizers.GM.Bargains .filter((bData) => !Object.values(this.document.system.randomizers.Bargains) @@ -92,6 +100,8 @@ class BladesScoreSheet extends BladesItemSheet { return [k, v]; })) }; + /*~ @@DOUBLE-BLANK@@ ~*/ + // If category specified, replace all other categories with stored data if (category) { Object.keys(randomData) .filter((cat) => cat !== category) @@ -100,12 +110,16 @@ class BladesScoreSheet extends BladesItemSheet { randomData[_cat] = this.document.system.randomizers[_cat]; }); } + /*~ @@DOUBLE-BLANK@@ ~*/ + // Combine locked data stored in system with randomly-generated data const finalRandomData = { Bargains: {}, Obstacles: {}, NPCs: {}, Scores: {} }; + /*~ @@DOUBLE-BLANK@@ ~*/ + // Iterate through all randomizer categories. If system entry isLocked, use that, or use newly-generated data Object.keys(randomData).forEach((cat) => { const _cat = cat; Object.keys(randomData[_cat]).forEach((index) => { @@ -117,11 +131,17 @@ class BladesScoreSheet extends BladesItemSheet { } }); }); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Overwrite stored data with newly generated & merged randomizer data await this.document.update({ "system.randomizers": finalRandomData }); } + /*~ @@DOUBLE-BLANK@@ ~*/ getData() { const context = super.getData(); + /*~ @@DOUBLE-BLANK@@ ~*/ const sheetData = {}; + /*~ @@DOUBLE-BLANK@@ ~*/ + // Get player characters, assign simplified actionData that I probably should have coded them with from the start sheetData.playerCharacters = BladesActor.GetTypeWithTags(BladesActorType.pc, Tag.PC.ActivePC) .map((pc) => { return Object.assign(pc, { @@ -140,6 +160,8 @@ class BladesScoreSheet extends BladesItemSheet { })) }); }); + /*~ @@DOUBLE-BLANK@@ ~*/ + // Prune system data for blank/empty opposition entries const validOppositions = {}; for (const [id, data] of Object.entries(context.system.oppositions)) { if (!data.rollOppName && !data.rollOppSubName) { @@ -148,11 +170,13 @@ class BladesScoreSheet extends BladesItemSheet { validOppositions[id] = data; } context.system.oppositions = validOppositions; + /*~ @@DOUBLE-BLANK@@ ~*/ return { ...context, ...sheetData }; } + /*~ @@DOUBLE-BLANK@@ ~*/ _toggleRandomizerLock(event) { const elem$ = $(event.currentTarget); const elemCat = elem$.data("category"); @@ -162,18 +186,22 @@ class BladesScoreSheet extends BladesItemSheet { this.document.update({ [`system.randomizers.${elemCat}.${elemIndex}.isLocked`]: false }); } else { + /*~ @@DOUBLE-BLANK@@ ~*/ this.document.update({ [`system.randomizers.${elemCat}.${elemIndex}.isLocked`]: true }); } } + /*~ @@DOUBLE-BLANK@@ ~*/ _selectImage(event) { const elem$ = $(event.currentTarget); const imageNum = elem$.data("imgNum"); this.document.update({ "system.imageSelected": imageNum }); } + /*~ @@DOUBLE-BLANK@@ ~*/ _deselectOrDeleteImage(event) { const elem$ = $(event.currentTarget); const imageNum = elem$.data("imgNum"); if (this.document.system.imageSelected === imageNum) { + /*~ @@DOUBLE-BLANK@@ ~*/ this.document.update({ "system.-=imageSelected": null }); return; } @@ -183,12 +211,14 @@ class BladesScoreSheet extends BladesItemSheet { .filter((_, i) => U.pInt(imageNum) !== i))) })); } + /*~ @@DOUBLE-BLANK@@ ~*/ _addImage() { U.displayImageSelector((path) => { const imgIndex = U.objSize(this.document.system.images); return this.document.update({ [`system.images.${imgIndex}`]: path }); }, "systems/eunos-blades/assets", this.position); } + /*~ @@DOUBLE-BLANK@@ ~*/ _selectRollOpposition(event) { eLog.checkLog3("Select Roll Opposition", { event }); const elem$ = $(event.currentTarget); @@ -198,6 +228,7 @@ class BladesScoreSheet extends BladesItemSheet { BladesRoll.Active.rollOpposition = new BladesRollOpposition(BladesRoll.Active, this.document.system.oppositions[oppId]); } } + /*~ @@DOUBLE-BLANK@@ ~*/ _triggerRandomize(event) { const elem$ = $(event.currentTarget); const category = elem$.data("category"); @@ -208,6 +239,7 @@ class BladesScoreSheet extends BladesItemSheet { this.generateRandomizerData(); } } + /*~ @@DOUBLE-BLANK@@ ~*/ async _updateGMNotesOnPC(event) { const elem$ = $(event.currentTarget); const actor = BladesActor.Get(elem$.data("id")); @@ -219,8 +251,10 @@ class BladesScoreSheet extends BladesItemSheet { await actor.update({ "system.gm_notes": updateText }); eLog.checkLog3("scoreSheet", "Updated!", { gm_notes: actor.system.gm_notes }); } + /*~ @@DOUBLE-BLANK@@ ~*/ activateListeners(html) { super.activateListeners(html); + /*~ @@DOUBLE-BLANK@@ ~*/ html.find("[data-action='select-image']").on({ click: this._selectImage.bind(this), contextmenu: this._deselectOrDeleteImage.bind(this) @@ -237,22 +271,28 @@ class BladesScoreSheet extends BladesItemSheet { html.find("[data-action='randomize'").on({ click: this._triggerRandomize.bind(this) }); + /*~ @@DOUBLE-BLANK@@ ~*/ html.find("textarea.pc-summary-notes-body").on({ change: this._updateGMNotesOnPC.bind(this) }); } + /*~ @@DOUBLE-BLANK@@ ~*/ async _onSubmit(event, params = {}) { eLog.checkLog3("scoreSheet", "_onSubmit()", { event, params, elemText: event.currentTarget.innerHTML }); let isForcingRender = true; + /*~ @@DOUBLE-BLANK@@ ~*/ const prevPhase = this.item.system.phase; const submitData = await super._onSubmit(event, params); + /*~ @@DOUBLE-BLANK@@ ~*/ const newPhase = this.item.system.phase; if (prevPhase !== newPhase) { switch (prevPhase) { case BladesPhase.CharGen: { + /*~ @@DOUBLE-BLANK@@ ~*/ break; } case BladesPhase.Freeplay: { + /*~ @@DOUBLE-BLANK@@ ~*/ break; } case BladesPhase.Score: { @@ -262,22 +302,29 @@ class BladesScoreSheet extends BladesItemSheet { break; } case BladesPhase.Downtime: { + /*~ @@DOUBLE-BLANK@@ ~*/ break; } + // No default } switch (newPhase) { case BladesPhase.CharGen: { + /*~ @@DOUBLE-BLANK@@ ~*/ break; } case BladesPhase.Freeplay: { + /*~ @@DOUBLE-BLANK@@ ~*/ break; } case BladesPhase.Score: { + /*~ @@DOUBLE-BLANK@@ ~*/ break; } case BladesPhase.Downtime: { + /*~ @@DOUBLE-BLANK@@ ~*/ break; } + // No default } } if (isForcingRender) { @@ -287,5 +334,7 @@ class BladesScoreSheet extends BladesItemSheet { return submitData; } } +/*~ @@DOUBLE-BLANK@@ ~*/ export { BladesTipGenerator }; -export default BladesScoreSheet; \ No newline at end of file +export default BladesScoreSheet; +/*~ @@DOUBLE-BLANK@@ ~*/ diff --git a/module/sheets/item/blades-clock-keeper-sheet.js b/module/sheets/item/blades-clock-keeper-sheet.js deleted file mode 100644 index 7f420165..00000000 --- a/module/sheets/item/blades-clock-keeper-sheet.js +++ /dev/null @@ -1,82 +0,0 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - -import BladesItemSheet from "./blades-item-sheet.js"; -import BladesClockKeeper from "../../documents/items/blades-clock-keeper.js"; -class BladesClockKeeperSheet extends BladesItemSheet { - static Get() { return game.eunoblades.ClockKeeper; } - static get defaultOptions() { - return foundry.utils.mergeObject(super.defaultOptions, { - classes: ["eunos-blades", "sheet", "item", "clock-keeper"], - template: "systems/eunos-blades/templates/items/clock_keeper-sheet.hbs", - width: 700, - height: 970 - }); - } - static async Initialize() { - game.eunoblades ??= {}; - Items.registerSheet("blades", BladesClockKeeperSheet, { types: ["clock_keeper"], makeDefault: true }); - Hooks.once("ready", async () => { - let clockKeeper = game.items.find((item) => item.type === "clock_keeper"); - if (!clockKeeper) { - clockKeeper = (await BladesClockKeeper.create({ - name: "Clock Keeper", - type: "clock_keeper", - img: "systems/eunos-blades/assets/icons/misc-icons/clock-keeper.svg" - })); - } - game.eunoblades.ClockKeeper = clockKeeper; - game.eunoblades.ClockKeeper.renderOverlay(); - }); - Hooks.on("canvasReady", async () => { game.eunoblades.ClockKeeper?.renderOverlay(); }); - return loadTemplates([ - "systems/eunos-blades/templates/items/clock_keeper-sheet.hbs", - "systems/eunos-blades/templates/parts/clock-sheet-row.hbs" - ]); - } - static InitSockets() { - if (game.eunoblades.ClockKeeper) { - socketlib.system.register("renderOverlay", game.eunoblades.ClockKeeper.renderOverlay); - return true; - } - return false; - } - getData() { - const context = super.getData(); - const sheetData = { - clock_keys: Object.fromEntries((Object.entries(context.system.clock_keys ?? {}) - .filter(([keyID, keyData]) => Boolean(keyData && keyData.scene === context.system.targetScene)))) - }; - return { ...context, ...sheetData }; - } - addKey(event) { - event.preventDefault(); - this.item.addClockKey(); - } - deleteKey(event) { - event.preventDefault(); - const keyID = event.currentTarget.dataset.id; - if (keyID) { - this.item.deleteClockKey(keyID); - } - } - setKeySize(event) { - event.preventDefault(); - const keyID = event.target.dataset.id; - if (keyID) { - this.item.setKeySize(keyID, parseInt(event.target.value, 10)); - } - } - async activateListeners(html) { - super.activateListeners(html); - html.find("[data-action=\"add-key\"").on("click", this.addKey.bind(this)); - html.find("[data-action=\"delete-key\"").on("click", this.deleteKey.bind(this)); - html.find(".key-clock-counter").on("change", this.setKeySize.bind(this)); - } -} -export default BladesClockKeeperSheet; -//# sourceMappingURL=blades-clock-keeper-sheet.js.map \ No newline at end of file diff --git a/module/sheets/item/blades-item-sheet.js b/module/sheets/item/blades-item-sheet.js deleted file mode 100644 index c0e8a307..00000000 --- a/module/sheets/item/blades-item-sheet.js +++ /dev/null @@ -1,405 +0,0 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - -import C, { BladesItemType, BladesPhase, Factor } from "../../core/constants.js"; -import U from "../../core/utilities.js"; -import G, { ApplyTooltipListeners } from "../../core/gsap.js"; -import BladesItem from "../../blades-item.js"; -import BladesActiveEffect from "../../blades-active-effect.js"; -import Tags from "../../core/tags.js"; -class BladesItemSheet extends ItemSheet { - static get defaultOptions() { - return foundry.utils.mergeObject(super.defaultOptions, { - classes: ["eunos-blades", "sheet", "item"], - width: 560, - height: 500, - tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description" }] - }); - } - - - getData() { - const context = super.getData(); - const sheetData = { - cssClass: this.item.type, - editable: this.options.editable, - isGM: (game.eunoblades.Tracker?.system.is_spoofing_player ? false : Boolean(game.user.isGM)), - isEmbeddedItem: Boolean(this.item.parent), - item: this.item, - system: this.item.system, - tierTotal: this.item.getFactorTotal(Factor.tier) > 0 ? U.romanizeNum(this.item.getFactorTotal(Factor.tier)) : "0", - activeEffects: Array.from(this.item.effects) - }; - return this._getTypedItemData[this.item.type]({ ...context, ...sheetData }); - } - _getTypedItemData = { - [BladesItemType.ability]: (context) => { - if (!BladesItem.IsType(this.item, BladesItemType.ability)) { - return undefined; - } - const sheetData = {}; - return { - ...context, - ...sheetData - }; - }, - [BladesItemType.background]: (context) => { - if (!BladesItem.IsType(this.item, BladesItemType.background)) { - return undefined; - } - const sheetData = {}; - return { - ...context, - ...sheetData - }; - }, - [BladesItemType.clock_keeper]: (context) => { - if (!BladesItem.IsType(this.item, BladesItemType.clock_keeper)) { - return undefined; - } - const sheetData = { - phases: Object.values(BladesPhase) - }; - return { - ...context, - ...sheetData - }; - }, - [BladesItemType.cohort_gang]: (context) => { - if (!BladesItem.IsType(this.item, BladesItemType.cohort_gang, BladesItemType.cohort_expert)) { - return undefined; - } - context.tierTotal = this.item.system.quality > 0 ? U.romanizeNum(this.item.system.quality) : "0"; - context.system.subtypes ??= {}; - context.system.elite_subtypes ??= {}; - const sheetData = { - tierData: { - "class": "comp-tier comp-vertical comp-teeth", - "dotline": { - data: this.item.system.tier, - target: "system.tier.value", - iconEmpty: "dot-empty.svg", - iconEmptyHover: "dot-empty-hover.svg", - iconFull: "dot-full.svg", - iconFullHover: "dot-full-hover.svg" - } - } - }; - sheetData.edgeData = Object.fromEntries(Object.values(context.system.edges ?? []) - .filter((edge) => /[A-Za-z]/.test(edge)) - .map((edge) => [edge.trim(), C.EdgeTooltips[edge]])); - sheetData.flawData = Object.fromEntries(Object.values(context.system.flaws ?? []) - .filter((flaw) => /[A-Za-z]/.test(flaw)) - .map((flaw) => [flaw.trim(), C.FlawTooltips[flaw]])); - return { - ...context, - ...sheetData - }; - }, - [BladesItemType.cohort_expert]: (context) => this._getTypedItemData[BladesItemType.cohort_gang](context), - [BladesItemType.crew_ability]: (context) => { - if (!BladesItem.IsType(this.item, BladesItemType.crew_ability)) { - return undefined; - } - const sheetData = {}; - return { - ...context, - ...sheetData - }; - }, - [BladesItemType.crew_reputation]: (context) => { - if (!BladesItem.IsType(this.item, BladesItemType.crew_reputation)) { - return undefined; - } - const sheetData = {}; - return { - ...context, - ...sheetData - }; - }, - [BladesItemType.crew_playbook]: (context) => { - if (!BladesItem.IsType(this.item, BladesItemType.crew_playbook)) { - return undefined; - } - if (context.isGM) { - const expClueData = {}; - [...Object.values(context.system.experience_clues ?? []).filter((clue) => /[A-Za-z]/.test(clue)), " "].forEach((clue, i) => { expClueData[(i + 1).toString()] = clue; }); - context.system.experience_clues = expClueData; - } - const sheetData = {}; - return { - ...context, - ...sheetData - }; - }, - [BladesItemType.crew_upgrade]: (context) => { - if (!BladesItem.IsType(this.item, BladesItemType.crew_upgrade)) { - return undefined; - } - const sheetData = {}; - return { - ...context, - ...sheetData - }; - }, - [BladesItemType.feature]: (context) => { - if (!BladesItem.IsType(this.item, BladesItemType.feature)) { - return undefined; - } - const sheetData = {}; - return { - ...context, - ...sheetData - }; - }, - [BladesItemType.gm_tracker]: (context) => { - if (!BladesItem.IsType(this.item, BladesItemType.gm_tracker)) { - return undefined; - } - const sheetData = {}; - return { - ...context, - ...sheetData - }; - }, - [BladesItemType.heritage]: (context) => { - if (!BladesItem.IsType(this.item, BladesItemType.heritage)) { - return undefined; - } - const sheetData = {}; - return { - ...context, - ...sheetData - }; - }, - [BladesItemType.gear]: (context) => { - if (!BladesItem.IsType(this.item, BladesItemType.gear)) { - return undefined; - } - const sheetData = { - tierData: { - "class": "comp-tier comp-vertical comp-teeth", - "label": "Quality", - "labelClass": "filled-label full-width", - "dotline": { - data: this.item.system.tier, - target: "system.tier.value", - iconEmpty: "dot-empty.svg", - iconEmptyHover: "dot-empty-hover.svg", - iconFull: "dot-full.svg", - iconFullHover: "dot-full-hover.svg" - } - } - }; - return { - ...context, - ...sheetData - }; - }, - [BladesItemType.playbook]: (context) => { - if (!BladesItem.IsType(this.item, BladesItemType.playbook)) { - return undefined; - } - if (context.isGM) { - const expClueData = {}; - [...Object.values(context.system.experience_clues ?? []).filter((clue) => /[A-Za-z]/.test(clue)), " "].forEach((clue, i) => { expClueData[(i + 1).toString()] = clue; }); - context.system.experience_clues = expClueData; - const gatherInfoData = {}; - [...Object.values(context.system.gather_info_questions ?? []).filter((question) => /[A-Za-z]/.test(question)), " "].forEach((question, i) => { gatherInfoData[(i + 1).toString()] = question; }); - context.system.gather_info_questions = gatherInfoData; - } - const sheetData = {}; - return { - ...context, - ...sheetData - }; - }, - [BladesItemType.preferred_op]: (context) => { - if (!BladesItem.IsType(this.item, BladesItemType.preferred_op)) { - return undefined; - } - const sheetData = {}; - return { - ...context, - ...sheetData - }; - }, - [BladesItemType.stricture]: (context) => { - if (!BladesItem.IsType(this.item, BladesItemType.stricture)) { - return undefined; - } - const sheetData = {}; - return { - ...context, - ...sheetData - }; - }, - [BladesItemType.vice]: (context) => { - if (!BladesItem.IsType(this.item, BladesItemType.vice)) { - return undefined; - } - const sheetData = {}; - return { - ...context, - ...sheetData - }; - }, - [BladesItemType.project]: (context) => { - if (!BladesItem.IsType(this.item, BladesItemType.project)) { - return undefined; - } - const sheetData = {}; - return { - ...context, - ...sheetData - }; - }, - [BladesItemType.ritual]: (context) => { - if (!BladesItem.IsType(this.item, BladesItemType.ritual)) { - return undefined; - } - const sheetData = {}; - return { - ...context, - ...sheetData - }; - }, - [BladesItemType.design]: (context) => { - if (!BladesItem.IsType(this.item, BladesItemType.design)) { - return undefined; - } - const sheetData = {}; - return { - ...context, - ...sheetData - }; - }, - [BladesItemType.location]: (context) => { - if (!BladesItem.IsType(this.item, BladesItemType.location)) { - return undefined; - } - const sheetData = {}; - return { - ...context, - ...sheetData - }; - }, - [BladesItemType.score]: (context) => { - if (!BladesItem.IsType(this.item, BladesItemType.score)) { - return undefined; - } - return context; - } - }; - get template() { - const pathComps = [ - "systems/eunos-blades/templates/items" - ]; - if (C.SimpleItemTypes.includes(this.item.type)) { - pathComps.push("simple-sheet.hbs"); - } - else { - pathComps.push(`${this.item.type}-sheet.hbs`); - } - return pathComps.join("/"); - } - - activateListeners(html) { - super.activateListeners(html); - const self = this; - Tags.InitListeners(html, this.item); - ApplyTooltipListeners(html); - if (!this.options.editable) { - return; - } - html.find(".dotline").each((__, elem) => { - if ($(elem).hasClass("locked")) { - return; - } - const targetDoc = this.item; - const targetField = $(elem).data("target"); - const comp$ = $(elem).closest("comp"); - const curValue = U.pInt($(elem).data("value")); - $(elem) - .find(".dot") - .each((_, dot) => { - $(dot).on("click", (event) => { - event.preventDefault(); - const thisValue = U.pInt($(dot).data("value")); - if (thisValue !== curValue) { - if (comp$.hasClass("comp-coins") - || comp$.hasClass("comp-stash")) { - G.effects - .fillCoins($(dot).prevAll(".dot")) - .then(() => targetDoc.update({ [targetField]: thisValue })); - } - else { - targetDoc.update({ [targetField]: thisValue }); - } - } - }); - $(dot).on("contextmenu", (event) => { - event.preventDefault(); - const thisValue = U.pInt($(dot).data("value")) - 1; - if (thisValue !== curValue) { - targetDoc.update({ [targetField]: thisValue }); - } - }); - }); - }); - if (BladesItem.IsType(this.item, BladesItemType.cohort_expert, BladesItemType.cohort_gang)) { - html.find("[data-harm-click]").on({ - click: (event) => { - event.preventDefault(); - const harmLevel = U.pInt($(event.currentTarget).data("harmClick")); - if (this.item.system.harm?.value !== harmLevel) { - this.item.update({ "system.harm.value": harmLevel }); - } - }, - contextmenu: (event) => { - event.preventDefault(); - const harmLevel = Math.max(0, U.pInt($(event.currentTarget).data("harmClick")) - 1); - if (this.item.system.harm?.value !== harmLevel) { - this.item.update({ "system.harm.value": harmLevel }); - } - } - }); - } - if (this.options.submitOnChange) { - html.on("change", "textarea", this._onChangeInput.bind(this)); - } - html.find(".effect-control").on("click", (ev) => { - if (self.item.isOwned) { - ui.notifications?.warn(game.i18n.localize("BITD.EffectWarning")); - return; - } - BladesActiveEffect.onManageActiveEffect(ev, self.item); - }); - html.find("[data-action=\"toggle-turf-connection\"").on("click", this.toggleTurfConnection.bind(this)); - } - toggleTurfConnection(event) { - const button$ = $(event.currentTarget); - const connector$ = button$.parent(); - const turfNum = parseInt(connector$.data("index") ?? 0, 10); - const turfDir = connector$.data("dir"); - if (!turfNum || !turfDir) { - return; - } - const toggleState = connector$.hasClass("no-connect"); - const updateData = { - [`system.turfs.${turfNum}.connects.${turfDir}`]: toggleState - }; - const partner = connector$.data("partner"); - if (typeof partner === "string" && /-/.test(partner)) { - const [partnerNum, partnerDir] = partner.split("-"); - updateData[`system.turfs.${partnerNum}.connects.${partnerDir}`] = toggleState; - } - this.item.update(updateData); - } -} -export default BladesItemSheet; -//# sourceMappingURL=blades-item-sheet.js.map \ No newline at end of file diff --git a/module/sheets/item/blades-score-sheet.js b/module/sheets/item/blades-score-sheet.js deleted file mode 100644 index 8260383a..00000000 --- a/module/sheets/item/blades-score-sheet.js +++ /dev/null @@ -1,290 +0,0 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - -import U from "../../core/utilities.js"; -import { BladesActorType, BladesPhase, Tag, Randomizers } from "../../core/constants.js"; -import BladesItemSheet from "./blades-item-sheet.js"; -import { BladesActor } from "../../documents/blades-actor-proxy.js"; -import { BladesScore } from "../../documents/blades-item-proxy.js"; -import BladesRollCollab from "../../blades-roll-collab.js"; -export var BladesTipContext; -(function (BladesTipContext) { - BladesTipContext["DiceRoll"] = "DiceRoll"; - BladesTipContext["Combat"] = "Combat"; - BladesTipContext["General"] = "General"; -})(BladesTipContext || (BladesTipContext = {})); -class BladesTipGenerator { - static get Tips() { - return { - [BladesTipContext.DiceRoll]: [], - [BladesTipContext.Combat]: [ - "Every combat encounter should advance the main plot, or else it's filler.", - "Inject dialogue into combat encounters, especially from important adversaries.", - "Combat encounters should be a challenge, but not a slog. Don't be afraid to end them early.", - "Infiltrate/Rescue/Destroy: Use these as additional/secondary goals in combat encounters.", - "Tell the next player in the initiative order that they're on deck.", - "Don't trigger combats automatically: Use alternate objectives to incite the players to fight, giving them agency.", - "Add another layer by drawing focus to collateral effects of the combat: a fire, a hostage, a collapsing building, innocents in danger" - ], - [BladesTipContext.General]: [ - "Rolling the dice always means SOMETHING happens.", - "Jump straight to the action; don't waste time on establishing scenes or filler.", - "Invoke elements of characters' backstories or beliefs to make any scene more personal." - ] - }; - } - tipContext; - constructor(tipContext) { - this.tipContext = tipContext; - } -} -class BladesScoreSheet extends BladesItemSheet { - static get defaultOptions() { - return foundry.utils.mergeObject(super.defaultOptions, { - classes: ["eunos-blades", "sheet", "item", "score-sheet"], - template: "systems/eunos-blades/templates/items/score-sheet.hbs", - width: 900, - submitOnChange: false, - height: 970 - }); - } - async generateRandomizerData(category) { - const randomData = { - Bargains: Object.fromEntries(Object.entries(U.sample(Randomizers.GM.Bargains - .filter((bData) => !Object.values(this.document.system.randomizers.Bargains) - .some((_bData) => _bData.name === bData.name || _bData.effect === bData.effect)), 3, true, (e, a) => a - .filter((_e) => e.category === _e.category).length === 0)) - .map(([k, v]) => { - k = `${k}`; - Object.assign(v, { notes: "" }); - return [k, v]; - })), - Obstacles: Object.fromEntries(Object.entries(U.sample(Randomizers.GM.Obstacles - .filter((bData) => !Object.values(this.document.system.randomizers.Obstacles) - .some((_bData) => _bData.name === bData.name || _bData.desc === bData.desc)), 3, true, (e, a) => a - .filter((_e) => e.category === _e.category).length === 0)) - .map(([k, v]) => { - k = `${k}`; - Object.assign(v, { notes: "" }); - return [k, v]; - })), - NPCs: Object.fromEntries(Object.entries(U.sample(Randomizers.GM.NPCs - .filter((bData) => !Object.values(this.document.system.randomizers.NPCs) - .some((_bData) => _bData.name === bData.name || _bData.description === bData.description)), 3, true, (e, a) => a - .filter((_e) => e.arena === _e.arena).length === 0)) - .map(([k, v]) => { - k = `${k}`; - Object.assign(v, { notes: "" }); - return [k, v]; - })), - Scores: Object.fromEntries(Object.entries(U.sample(Randomizers.GM.Scores - .filter((bData) => !Object.values(this.document.system.randomizers.Scores) - .some((_bData) => _bData.name === bData.name || _bData.desc === bData.desc)), 3, true, (e, a) => a - .filter((_e) => e.category === _e.category).length === 0)) - .map(([k, v]) => { - k = `${k}`; - Object.assign(v, { notes: "" }); - return [k, v]; - })) - }; - if (category) { - Object.keys(randomData) - .filter((cat) => cat !== category) - .forEach((cat) => { - const _cat = cat; - randomData[_cat] = this.document.system.randomizers[_cat]; - }); - } - const finalRandomData = { - Bargains: {}, - Obstacles: {}, - NPCs: {}, - Scores: {} - }; - Object.keys(randomData).forEach((cat) => { - const _cat = cat; - Object.entries(randomData[_cat]).forEach(([index, randData]) => { - if (this.document.system.randomizers?.[_cat][index].isLocked) { - finalRandomData[_cat][index] = this.document.system.randomizers[_cat][index]; - } - else { - finalRandomData[_cat][index] = randomData[_cat][index]; - } - }); - }); - this.document.update({ "system.randomizers": finalRandomData }); - } - getData() { - const context = super.getData(); - const sheetData = {}; - sheetData.playerCharacters = BladesActor.GetTypeWithTags(BladesActorType.pc, Tag.PC.ActivePC) - .map((pc) => { - return Object.assign(pc, { - actionData: Object.fromEntries(Object.entries(pc.system.attributes) - .map(([attrName, attrData]) => { - return [ - attrName, - Object.fromEntries(Object.entries(attrData) - .map(([actionName, actionData]) => { - return [ - U.uCase(actionName).slice(0, 3), - actionData - ]; - })) - ]; - })) - }); - }); - const validOppositions = {}; - for (const [id, data] of Object.entries(context.system.oppositions)) { - if (!data.rollOppName && !data.rollOppSubName) { - continue; - } - validOppositions[id] = data; - } - context.system.oppositions = validOppositions; - return { - ...context, - ...sheetData - }; - } - _toggleRandomizerLock(event) { - const elem$ = $(event.currentTarget); - const elemCat = elem$.data("category"); - const elemIndex = `${elem$.data("index")}`; - const elemValue = elem$.data("value"); - if (`${elemValue}` === "true") { - this.document.update({ [`system.randomizers.${elemCat}.${elemIndex}.isLocked`]: false }); - } - else { - this.document.update({ [`system.randomizers.${elemCat}.${elemIndex}.isLocked`]: true }); - } - } - _selectImage(event) { - const elem$ = $(event.currentTarget); - const imageNum = elem$.data("imgNum"); - this.document.update({ "system.imageSelected": imageNum }); - } - _deselectOrDeleteImage(event) { - const elem$ = $(event.currentTarget); - const imageNum = elem$.data("imgNum"); - if (this.document.system.imageSelected === imageNum) { - this.document.update({ "system.-=imageSelected": null }); - return; - } - const images = { ...this.document.system.images }; - this.document.update({ "system.-=images": null }).then(() => this.document.update({ - "system.images": Object.fromEntries(Object.entries(Object.values(images) - .filter((_, i) => U.pInt(imageNum) !== i))) - })); - } - _addImage() { - U.displayImageSelector(path => { - const imgIndex = U.objSize(this.document.system.images); - return this.document.update({ [`system.images.${imgIndex}`]: path }); - }, "systems/eunos-blades/assets", this.position); - } - _selectRollOpposition(event) { - eLog.checkLog3("Select Roll Opposition", { event }); - const elem$ = $(event.currentTarget); - const oppId = elem$.data("oppId"); - this.document.update({ "system.oppositionSelected": oppId }); - if (BladesScore.Active?.id === this.document.id && BladesRollCollab.Active) { - BladesRollCollab.Active.rollOpposition = this.document.system.oppositions[oppId]; - } - } - _triggerRandomize(event) { - const elem$ = $(event.currentTarget); - const category = elem$.data("category"); - if (category && category in Randomizers.GM) { - this.generateRandomizerData(category); - } - else { - this.generateRandomizerData(); - } - } - async _updateGMNotesOnPC(event) { - const elem$ = $(event.currentTarget); - const actor = BladesActor.Get(elem$.data("id")); - if (!actor) { - throw new Error(`Unable to retrieve actor with id '${elem$.data("id")}'`); - } - const updateText = event.currentTarget.innerHTML; - eLog.checkLog3("scoreSheet", "Retrieved Text, Updating ...", { updateText }); - await actor.update({ "system.gm_notes": updateText }); - eLog.checkLog3("scoreSheet", "Updated!", { gm_notes: actor.system.gm_notes }); - } - async activateListeners(html) { - super.activateListeners(html); - html.find("[data-action='select-image']").on({ - click: this._selectImage.bind(this), - contextmenu: this._deselectOrDeleteImage.bind(this) - }); - html.find("[data-action='add-image']").on({ - click: this._addImage.bind(this) - }); - html.find(".roll-opposition-name").on({ - dblclick: this._selectRollOpposition.bind(this) - }); - html.find(".toggle-lock").on({ - click: this._toggleRandomizerLock.bind(this) - }); - html.find("[data-action='randomize'").on({ - click: this._triggerRandomize.bind(this) - }); - html.find("textarea.pc-summary-notes-body").on({ - change: this._updateGMNotesOnPC.bind(this) - }); - } - async _onSubmit(event, params = {}) { - eLog.checkLog3("scoreSheet", "_onSubmit()", { event, params, elemText: event.currentTarget.innerHTML }); - let isForcingRender = true; - const prevPhase = this.item.system.phase; - const submitData = await super._onSubmit(event, params); - const newPhase = this.item.system.phase; - if (prevPhase !== newPhase) { - switch (prevPhase) { - case BladesPhase.CharGen: { - break; - } - case BladesPhase.Freeplay: { - break; - } - case BladesPhase.Score: { - isForcingRender = false; - game.actors.filter((actor) => BladesActor.IsType(actor, BladesActorType.pc)) - .forEach((actor) => actor.clearLoadout()); - break; - } - case BladesPhase.Downtime: { - break; - } - } - switch (newPhase) { - case BladesPhase.CharGen: { - break; - } - case BladesPhase.Freeplay: { - break; - } - case BladesPhase.Score: { - break; - } - case BladesPhase.Downtime: { - break; - } - } - } - if (isForcingRender) { - game.actors.filter((actor) => actor.type === BladesActorType.pc) - .forEach((actor) => actor.sheet?.render()); - } - return submitData; - } -} -export default BladesScoreSheet; -//# sourceMappingURL=blades-score-sheet.js.map \ No newline at end of file diff --git a/module/sheets/item/blades-tracker-sheet copy.js b/module/sheets/item/blades-tracker-sheet copy.js deleted file mode 100644 index 4564d00f..00000000 --- a/module/sheets/item/blades-tracker-sheet copy.js +++ /dev/null @@ -1,130 +0,0 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - -import { BladesActorType, BladesItemType, BladesPhase } from "../../core/constants.js"; -import BladesItemSheet from "./blades-item-sheet.js"; -import BladesItem from "../../blades-item.js"; -import BladesGMTracker from "../../documents/items/blades-gm-tracker.js"; -import BladesActor from "../../blades-actor.js"; -import BladesPC from "../../documents/actors/blades-pc.js"; -export var BladesTipContext; -(function (BladesTipContext) { - BladesTipContext["DiceRoll"] = "DiceRoll"; - BladesTipContext["Combat"] = "Combat"; - BladesTipContext["General"] = "General"; -})(BladesTipContext || (BladesTipContext = {})); -class BladesTipGenerator { - static Test(pcActor) { - if (BladesActor.IsType(pcActor, BladesActorType.pc)) { - return pcActor; - } - return undefined; - } - testActor = new BladesPC({ name: "blah", type: "pc" }); - static get Tips() { - return { - [BladesTipContext.DiceRoll]: [], - [BladesTipContext.Combat]: [ - "Every combat encounter should advance the main plot, or else it's filler.", - "Inject dialogue into combat encounters, especially from important adversaries.", - "Combat encounters should be a challenge, but not a slog. Don't be afraid to end them early.", - "Infiltrate/Rescue/Destroy: Use these as additional/secondary goals in combat encounters.", - "Tell the next player in the initiative order that they're on deck.", - "Don't trigger combats automatically: Use alternate objectives to incite the players to fight, giving them agency.", - "Add another layer by drawing focus to collateral effects of the combat: a fire, a hostage, a collapsing building, innocents in danger" - ], - [BladesTipContext.General]: [ - "Rolling the dice always means SOMETHING happens.", - "Jump straight to the action; don't waste time on establishing scenes or filler.", - "Invoke elements of characters' backstories or beliefs to make any scene more personal." - ] - }; - } - tipContext; - constructor(tipContext) { - this.tipContext = tipContext; - } -} -class BladesTrackerSheet extends BladesItemSheet { - static Get() { return game.eunoblades.Tracker; } - static get defaultOptions() { - return foundry.utils.mergeObject(super.defaultOptions, { - classes: ["eunos-blades", "sheet", "item", "gm-tracker"], - template: "systems/eunos-blades/templates/items/gm_tracker-sheet.hbs", - width: 700, - height: 970 - }); - } - static async Initialize() { - game.eunoblades ??= {}; - Items.registerSheet("blades", BladesTrackerSheet, { types: ["gm_tracker"], makeDefault: true }); - Hooks.once("ready", async () => { - let tracker = game.items.find((item) => BladesItem.IsType(item, BladesItemType.gm_tracker)); - if (!tracker) { - tracker = (await BladesGMTracker.create({ - name: "GM Tracker", - type: "gm_tracker", - img: "systems/eunos-blades/assets/icons/misc-icons/gm-tracker.svg" - })); - } - game.eunoblades.Tracker = tracker; - }); - return loadTemplates([ - "systems/eunos-blades/templates/items/gm_tracker-sheet.hbs" - ]); - } - - - async activateListeners(html) { - super.activateListeners(html); - } - async _onSubmit(event, params = {}) { - const prevPhase = this.item.system.phase; - const submitData = await super._onSubmit(event, params); - const newPhase = this.item.system.phase; - let isForcingRender = true; - if (prevPhase !== newPhase) { - switch (prevPhase) { - case BladesPhase.CharGen: { - break; - } - case BladesPhase.Freeplay: { - break; - } - case BladesPhase.Score: { - isForcingRender = false; - game.actors.filter((actor) => BladesActor.IsType(actor, BladesActorType.pc)) - .forEach((actor) => actor.clearLoadout()); - break; - } - case BladesPhase.Downtime: { - break; - } - } - switch (newPhase) { - case BladesPhase.CharGen: { - break; - } - case BladesPhase.Freeplay: { - break; - } - case BladesPhase.Score: { - break; - } - case BladesPhase.Downtime: { - break; - } - } - } - if (isForcingRender) { - game.actors.filter((actor) => actor.type === BladesActorType.pc) - .forEach((actor) => actor.sheet?.render()); - } - return submitData; - } -} -export default BladesTrackerSheet; \ No newline at end of file diff --git a/module/sheets/item/blades-tracker-sheet.js b/module/sheets/item/blades-tracker-sheet.js deleted file mode 100644 index d8df0208..00000000 --- a/module/sheets/item/blades-tracker-sheet.js +++ /dev/null @@ -1,129 +0,0 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - -import { BladesActorType, BladesItemType, BladesPhase } from "../../core/constants.js"; -import BladesItemSheet from "./blades-item-sheet.js"; -import BladesItem from "../../blades-item.js"; -import BladesGMTracker from "../../documents/items/blades-gm-tracker.js"; -import BladesActor from "../../blades-actor.js"; -import BladesPC from "../../documents/actors/blades-pc.js"; -export var BladesTipContext; -(function (BladesTipContext) { - BladesTipContext["DiceRoll"] = "DiceRoll"; - BladesTipContext["Combat"] = "Combat"; - BladesTipContext["General"] = "General"; -})(BladesTipContext || (BladesTipContext = {})); -class BladesTipGenerator { - static Test(pcActor) { - if (BladesActor.IsType(pcActor, BladesActorType.pc)) { - return pcActor; - } - return undefined; - } - testActor = new BladesPC({ name: "blah", type: "pc" }); - static get Tips() { - return { - [BladesTipContext.DiceRoll]: [], - [BladesTipContext.Combat]: [ - "Every combat encounter should advance the main plot, or else it's filler.", - "Inject dialogue into combat encounters, especially from important adversaries.", - "Combat encounters should be a challenge, but not a slog. Don't be afraid to end them early.", - "Infiltrate/Rescue/Destroy: Use these as additional/secondary goals in combat encounters.", - "Tell the next player in the initiative order that they're on deck.", - "Don't trigger combats automatically: Use alternate objectives to incite the players to fight, giving them agency.", - "Add another layer by drawing focus to collateral effects of the combat: a fire, a hostage, a collapsing building, innocents in danger" - ], - [BladesTipContext.General]: [ - "Rolling the dice always means SOMETHING happens.", - "Jump straight to the action; don't waste time on establishing scenes or filler.", - "Invoke elements of characters' backstories or beliefs to make any scene more personal." - ] - }; - } - tipContext; - constructor(tipContext) { - this.tipContext = tipContext; - } -} -class BladesTrackerSheet extends BladesItemSheet { - static Get() { return game.eunoblades.Tracker; } - static get defaultOptions() { - return foundry.utils.mergeObject(super.defaultOptions, { - classes: ["eunos-blades", "sheet", "item", "gm-tracker"], - template: "systems/eunos-blades/templates/items/gm_tracker-sheet.hbs", - width: 700, - height: 970 - }); - } - static async Initialize() { - game.eunoblades ??= {}; - Items.registerSheet("blades", BladesTrackerSheet, { types: ["gm_tracker"], makeDefault: true }); - Hooks.once("ready", async () => { - let tracker = game.items.find((item) => BladesItem.IsType(item, BladesItemType.gm_tracker)); - if (!tracker) { - tracker = (await BladesGMTracker.create({ - name: "GM Tracker", - type: "gm_tracker", - img: "systems/eunos-blades/assets/icons/misc-icons/gm-tracker.svg" - })); - } - game.eunoblades.Tracker = tracker; - }); - return loadTemplates([ - "systems/eunos-blades/templates/items/gm_tracker-sheet.hbs" - ]); - } - async activateListeners(html) { - super.activateListeners(html); - } - async _onSubmit(event, params = {}) { - const prevPhase = this.item.system.phase; - const submitData = await super._onSubmit(event, params); - const newPhase = this.item.system.phase; - let isForcingRender = true; - if (prevPhase !== newPhase) { - switch (prevPhase) { - case BladesPhase.CharGen: { - break; - } - case BladesPhase.Freeplay: { - break; - } - case BladesPhase.Score: { - isForcingRender = false; - game.actors.filter((actor) => BladesActor.IsType(actor, BladesActorType.pc)) - .forEach((actor) => actor.clearLoadout()); - break; - } - case BladesPhase.Downtime: { - break; - } - } - switch (newPhase) { - case BladesPhase.CharGen: { - break; - } - case BladesPhase.Freeplay: { - break; - } - case BladesPhase.Score: { - break; - } - case BladesPhase.Downtime: { - break; - } - } - } - if (isForcingRender) { - game.actors.filter((actor) => actor.type === BladesActorType.pc) - .forEach((actor) => actor.sheet?.render()); - } - return submitData; - } -} -export default BladesTrackerSheet; -//# sourceMappingURL=blades-tracker-sheet.js.map \ No newline at end of file diff --git a/module/sheets/roll/blades-roll-collab-sheet.js b/module/sheets/roll/blades-roll-collab-sheet.js deleted file mode 100644 index dd9eddc3..00000000 --- a/module/sheets/roll/blades-roll-collab-sheet.js +++ /dev/null @@ -1,641 +0,0 @@ -/* ****▌███████████████████████████████████████████████████████████████████████████▐**** *\ -|* ▌█░░░░░░░░░ Euno's Blades in the Dark for Foundry VTT ░░░░░░░░░░░█▐ *| -|* ▌██████████████████░░░░░░░░░░░░░ by Eunomiac ░░░░░░░░░░░░░██████████████████▐ *| -|* ▌█ License █ v0.1.0 ██▐ *| -|* ▌████░░░░ ░░░░█████▐ *| -\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ - -import U from "../../core/utilities.js"; -import C, { BladesActorType, RollType, RollModStatus, RollModCategory, Action, Attribute, Position, Effect, Factor } from "../../core/constants.js"; -import BladesActor from "../../blades-actor.js"; -import BladesItem from "../../blades-item.js"; -import { ApplyTooltipListeners } from "../../core/gsap.js"; -function isAction(trait) { - return Boolean(trait && typeof trait === "string" && trait in Action); -} -function isAttribute(trait) { - return Boolean(trait && typeof trait === "string" && trait in Attribute); -} -function isTier(trait) { return U.lCase(trait) === "tier"; } -function isNumber(trait) { return U.isInt(trait); } -export const ModEffects = { - NegateTierPenalty: (mod, sheetData) => { - return sheetData; - }, - NegateQualityPenalty: (mod, sheetData) => { - return sheetData; - }, - IsPush: (mod, sheetData) => { - return sheetData; - } -}; -class BladesRollCollabSheet extends DocumentSheet { - static get defaultOptions() { - return foundry.utils.mergeObject(super.defaultOptions, { - classes: ["eunos-blades", "sheet", "roll-collab"], - template: `systems/eunos-blades/templates/roll/roll-collab${game.user.isGM ? "-gm" : ""}.hbs`, - submitOnChange: true, - width: 500 - }); - } - static Initialize() { - return loadTemplates([ - "systems/eunos-blades/templates/roll/roll-collab.hbs", - "systems/eunos-blades/templates/roll/roll-collab-gm.hbs", - "systems/eunos-blades/templates/roll/partials/roll-collab-gm-number-line.hbs", - "systems/eunos-blades/templates/roll/partials/roll-collab-gm-select-doc.hbs", - "systems/eunos-blades/templates/roll/partials/roll-collab-action.hbs", - "systems/eunos-blades/templates/roll/partials/roll-collab-action-gm.hbs", - "systems/eunos-blades/templates/roll/partials/roll-collab-resistance.hbs", - "systems/eunos-blades/templates/roll/partials/roll-collab-resistance-gm.hbs", - "systems/eunos-blades/templates/roll/partials/roll-collab-downtime.hbs", - "systems/eunos-blades/templates/roll/partials/roll-collab-downtime-gm.hbs", - "systems/eunos-blades/templates/roll/partials/roll-collab-fortune.hbs", - "systems/eunos-blades/templates/roll/partials/roll-collab-fortune-gm.hbs", - "systems/eunos-blades/templates/roll/partials/roll-collab-incarceration.hbs", - "systems/eunos-blades/templates/roll/partials/roll-collab-incarceration-gm.hbs" - ]); - } - static InitSockets() { - socketlib.system.register("renderRollCollab", BladesRollCollabSheet.RenderRollCollab); - socketlib.system.register("closeRollCollab", BladesRollCollabSheet.CloseRollCollab); - } - static Current = {}; - static get DefaultFlagData() { - return { - rollID: randomID(), - rollType: RollType.Action, - rollPrimaryType: "Actor", - rollPrimaryID: "", - rollTrait: Factor.tier, - rollMods: { - [RollModCategory.roll]: { - positive: { - Push: { - name: "Push", - category: RollModCategory.roll, - status: RollModStatus.ToggledOff, - posNeg: "positive", - modType: "general", - stressCost: 2, - value: 1, - tooltip: "

Push for +1d

For 2 Stress, add 1 die to your pool.

(You cannot also accept a Devil's Bargain to increase your dice pool: It's one or the other.)

" - }, - Bargain: { - name: "Bargain", - category: RollModCategory.roll, - status: RollModStatus.Hidden, - posNeg: "positive", - modType: "general", - value: 1, - tooltip: "

Devil's Bargain

The GM has offered you a Devil's Bargain.

Accept the terms to add 1 die to your pool.

(You cannot also Push for +1d to increase your dice pool: It's one or the other.)

" - }, - Assist: { - name: "Assist", - category: RollModCategory.roll, - status: RollModStatus.Hidden, - posNeg: "positive", - modType: "teamwork", - value: 1, - sideString: "", - tooltip: "

@CHARACTER_NAME@ Assists

@CHARACTER_NAME@ is Assisting your efforts, adding 1 die to your pool.

" - } - }, - negative: {} - }, - [RollModCategory.position]: { - positive: { - Setup: { - name: "Setup", - category: RollModCategory.position, - status: RollModStatus.Hidden, - posNeg: "positive", - modType: "teamwork", - value: 1, - sideString: undefined, - tooltip: "

@CHARACTER_NAME@ Sets You Up

@CHARACTER_NAME@ has set you up for success with a preceding Setup action, increasing your Position by one level.

" - } - }, - negative: {} - }, - [RollModCategory.effect]: { - positive: { - Push: { - name: "Push", - category: RollModCategory.effect, - status: RollModStatus.ToggledOff, - posNeg: "positive", - modType: "general", - stressCost: 2, - value: 1, - tooltip: "

Push for Effect

For 2 Stress, increase your Effect by one level.

" - }, - Setup: { - name: "Setup", - category: RollModCategory.effect, - status: RollModStatus.Hidden, - posNeg: "positive", - modType: "teamwork", - value: 1, - sideString: undefined, - tooltip: "

@CHARACTER_NAME@ Sets You Up

@CHARACTER_NAME@ has set you up for success with a preceding Setup action, increasing your Effect by one level.

" - }, - Potency: { - name: "Potency", - category: RollModCategory.effect, - status: RollModStatus.Hidden, - posNeg: "positive", - modType: "general", - value: 1, - tooltip: "

Potency

By circumstance or advantage, you have Potency in this action, increasing your Effect by one level.

" - } - }, - negative: { - Potency: { - name: "Potency", - category: RollModCategory.effect, - status: RollModStatus.Hidden, - posNeg: "negative", - modType: "general", - value: 1, - tooltip: "

Potency

By circumstance or advantage, @OPPOSITION_NAME@ has Potency against you, reducing your Effect by one level." - } - } - }, - [RollModCategory.result]: { positive: {}, negative: {} }, - [RollModCategory.after]: { positive: {}, negative: {} } - }, - rollPositionInitial: Position.risky, - rollEffectInitial: Effect.standard, - rollPosEffectTrade: false, - rollFactors: { - [Factor.tier]: { name: "Tier", cssClasses: "roll-factor roll-factor-tier", value: 0, max: 0, isActive: false, isDominant: false, highFavorsPC: true } - }, - isGMReady: false, - GMBoosts: {}, - GMOppBoosts: {}, - docSelections: { - [RollModCategory.roll]: { - Assist: false, - Group_1: false, - Group_2: false, - Group_3: false, - Group_4: false, - Group_5: false, - Group_6: false - }, - [RollModCategory.position]: { - Setup: false - }, - [RollModCategory.effect]: { - Setup: false - } - } - }; - } - static async RenderRollCollab({ userID, rollID }) { - const user = game.users.get(userID); - if (!user) { - return; - } - BladesRollCollabSheet.Current[rollID] = new BladesRollCollabSheet(user, rollID); - BladesRollCollabSheet.Current[rollID].render(true); - } - static async CloseRollCollab(rollID) { - eLog.checkLog3("rollCollab", "CloseRollCollab()", { rollID }); - await BladesRollCollabSheet.Current[rollID]?.close({ rollID }); - delete BladesRollCollabSheet.Current[rollID]; - } - static async NewRoll(config) { - - const user = game.users.get(config.userID ?? game.user._id); - if (!(user instanceof User)) { - eLog.error("rollCollab", `[NewRoll()] Can't Find User '${config.userID}'`, config); - return; - } - await user.unsetFlag(C.SYSTEM_ID, "rollCollab"); - const flagUpdateData = { ...BladesRollCollabSheet.DefaultFlagData }; - flagUpdateData.rollType = config.rollType; - if (!(flagUpdateData.rollType in RollType)) { - eLog.error("rollCollab", `[RenderRollCollab()] Invalid rollType: ${flagUpdateData.rollType}`, config); - return; - } - const rollPrimary = config.rollPrimary ?? user.character; - if (!(rollPrimary instanceof BladesActor || rollPrimary instanceof BladesItem)) { - eLog.error("rollCollab", "[RenderRollCollab()] Invalid rollPrimary", { rollPrimary, config }); - return; - } - flagUpdateData.rollPrimaryID = rollPrimary.id; - flagUpdateData.rollPrimaryType = rollPrimary instanceof BladesActor ? "Actor" : "Item"; - if (U.isInt(config.rollTrait)) { - flagUpdateData.rollTrait = config.rollTrait; - } - else if (!config.rollTrait) { - eLog.error("rollCollab", "[RenderRollCollab()] No RollTrait in Config", config); - return; - } - else { - switch (flagUpdateData.rollType) { - case RollType.Action: { - if (!(U.lCase(config.rollTrait) in { ...Action, ...Factor })) { - eLog.error("rollCollab", `[RenderRollCollab()] Bad RollTrait for Action Roll: ${config.rollTrait}`, config); - return; - } - flagUpdateData.rollTrait = U.lCase(config.rollTrait); - break; - } - case RollType.Downtime: { - if (!(U.lCase(config.rollTrait) in { ...Action, ...Factor })) { - eLog.error("rollCollab", `[RenderRollCollab()] Bad RollTrait for Downtime Roll: ${config.rollTrait}`, config); - return; - } - flagUpdateData.rollTrait = U.lCase(config.rollTrait); - break; - } - case RollType.Fortune: { - if (!(U.lCase(config.rollTrait) in { ...Action, ...Attribute, ...Factor })) { - eLog.error("rollCollab", `[RenderRollCollab()] Bad RollTrait for Fortune Roll: ${config.rollTrait}`, config); - return; - } - flagUpdateData.rollTrait = U.lCase(config.rollTrait); - break; - } - case RollType.Resistance: { - if (!(U.lCase(config.rollTrait) in Attribute)) { - eLog.error("rollCollab", `[RenderRollCollab()] Bad RollTrait for Resistance Roll: ${config.rollTrait}`, config); - return; - } - break; - } - } - flagUpdateData.rollTrait = U.lCase(config.rollTrait); - } - await user.setFlag(C.SYSTEM_ID, "rollCollab", flagUpdateData); - BladesRollCollabSheet.RenderRollCollab({ userID: user._id, rollID: flagUpdateData.rollID }); - socketlib.system.executeForAllGMs("renderRollCollab", { userID: user._id, rollID: flagUpdateData.rollID }); - } - rollID; - constructor(user, rollID) { - super(user); - this.rollID = rollID; - } - get rData() { - if (!this.document.getFlag(C.SYSTEM_ID, "rollCollab")) { - eLog.error("rollCollab", "[get flags()] No RollCollab Flags Found on User", { user: this.document, flags: this.document.flags }); - return null; - } - return this.document.flags["eunos-blades"].rollCollab; - } - get rollPrimary() { - if (!this.rData) { - return undefined; - } - return this.rData.rollPrimaryType === "Actor" - ? game.actors.get(this.rData.rollPrimaryID) - : game.items.get(this.rData.rollPrimaryID); - } - getData() { - const context = super.getData(); - const { rData } = this; - if (!rData) { - return context; - } - const sheetData = { - cssClass: "roll-collab", - editable: this.options.editable, - isGM: game.eunoblades.Tracker.system.is_spoofing_player ? false : game.user.isGM, - rollPositions: Object.values(Position), - rollEffects: Object.values(Effect), - ...rData - }; - if (!this.rollPrimary) { - eLog.error("rollCollab", `[getData()] No '${sheetData.rollPrimaryType}' Found with ID '${sheetData.rollPrimaryID}'`, { user: this.document, rData: rData }); - return null; - } - sheetData.system = this.rollPrimary.system; - sheetData.rollPrimary = this.rollPrimary; - if (sheetData.rollOppositionID) { - const rollOpposition = BladesActor.Get(sheetData.rollOppositionID) ?? BladesItem.Get(sheetData.rollOppositionID); - if (!rollOpposition) { - throw new Error(`Cannot find Roll Opposition with ID '${sheetData.rollOppositionID}'`); - } - sheetData.rollOpposition = rollOpposition; - } - if (BladesActor.IsType(this.rollPrimary, BladesActorType.pc) && isAction(sheetData.rollTrait)) { - const { rollPrimary } = this; - sheetData.rollTraitData = { - name: sheetData.rollTrait, - value: rollPrimary.actions[sheetData.rollTrait], - max: rollPrimary.actions[sheetData.rollTrait] - }; - sheetData.rollTraitOptions = Object.values(Action) - .map((action) => ({ - name: U.uCase(action), - value: action - })); - } - else if (BladesActor.IsType(this.rollPrimary, BladesActorType.pc) && isAttribute(sheetData.rollTrait)) { - const { rollPrimary } = this; - sheetData.rollTraitData = { - name: sheetData.rollTrait, - value: rollPrimary.attributes[sheetData.rollTrait], - max: rollPrimary.attributes[sheetData.rollTrait] - }; - sheetData.rollTraitOptions = Object.values(Attribute) - .map((attribute) => ({ - name: U.uCase(attribute), - value: attribute - })); - } - else if (sheetData.rollTrait === "tier") { - const { rollPrimary } = this; - sheetData.rollTraitData = { - name: "Tier", - value: rollPrimary.getTierTotal(), - max: rollPrimary.getTierTotal() - }; - sheetData.rollTraitOptions = false; - } - else if (U.isInt(sheetData.rollTrait)) { - sheetData.rollTraitData = { - name: `+${sheetData.rollTrait}`, - value: sheetData.rollTrait, - max: sheetData.rollTrait - }; - sheetData.rollTraitOptions = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - .map((num) => ({ - name: `+${num}`, - value: num - })); - } - sheetData.rollMods = mergeObject(sheetData.rollSource.rollMods, sheetData.rollMods); - function isModAutoActive(mod) { - const autoRollTypes = mod.autoRollTypes ?? []; - const autoRollTraits = mod.autoRollTraits ?? []; - return autoRollTypes.length + autoRollTraits.length > 0 - && (autoRollTypes.length === 0 || autoRollTypes.includes(sheetData.rollType)) - && (autoRollTraits.length === 0 || autoRollTraits.includes(sheetData.rollTrait)); - } - function isModConditional(mod) { - const conditionalRollTypes = mod.conditionalRollTypes ?? []; - const conditionalRollTraits = mod.conditionalRollTraits ?? []; - return conditionalRollTypes.length + conditionalRollTraits.length > 0 - && (conditionalRollTypes.length === 0 || conditionalRollTypes.includes(sheetData.rollType)) - && (conditionalRollTraits.length === 0 || conditionalRollTraits.includes(sheetData.rollTrait)); - } - Object.values(RollModCategory).forEach((modCat) => { - Object.values(sheetData.rollMods[modCat]?.positive ?? {}) - .filter((mod) => mod.isConditional && mod.status === RollModStatus.ToggledOff) - .forEach((mod) => { - if (isModAutoActive(mod)) { - sheetData.rollMods[modCat].positive[mod.name].status = RollModStatus.ForcedOn; - } - else if (!isModConditional(mod)) { - sheetData.rollMods[modCat].positive[mod.name].status = RollModStatus.Hidden; - } - }); - }); - function getModsDelta(cat) { - const activePosMods = Object.values(sheetData.rollMods?.[cat]?.positive ?? {}) - .filter((mod) => [RollModStatus.ToggledOn, RollModStatus.ForcedOn].includes(mod.status)); - const posModVals = activePosMods.map((mod) => mod.value); - const posModSum = U.sum(posModVals); - const activeNegMods = Object.values(sheetData.rollMods?.[cat]?.negative ?? {}) - .filter((mod) => [RollModStatus.ToggledOn, RollModStatus.ForcedOn].includes(mod.status)); - const negModVals = activeNegMods.map((mod) => mod.value); - const negModSum = U.sum(negModVals); - eLog.checkLog3("rollMods", `getModsDelta(${cat})`, { activePosMods, posModVals, posModSum, activeNegMods, negModVals, negModSum, returnVal: U.sum(Object.values(sheetData.rollMods?.[cat]?.positive ?? {}) - .filter((mod) => [RollModStatus.ToggledOn, RollModStatus.ForcedOn].includes(mod.status)) - .map((mod) => mod.value)) - - U.sum(Object.values(sheetData.rollMods?.[cat]?.negative ?? {}) - .filter((mod) => [RollModStatus.ToggledOn, RollModStatus.ForcedOn].includes(mod.status)) - .map((mod) => mod.value)) }); - return U.sum(Object.values(sheetData.rollMods?.[cat]?.positive ?? {}) - .filter((mod) => [RollModStatus.ToggledOn, RollModStatus.ForcedOn].includes(mod.status)) - .map((mod) => mod.value)) - - U.sum(Object.values(sheetData.rollMods?.[cat]?.negative ?? {}) - .filter((mod) => [RollModStatus.ToggledOn, RollModStatus.ForcedOn].includes(mod.status)) - .map((mod) => mod.value)); - } - sheetData.diceTotal = Math.max(0, (sheetData.rollTraitData?.value ?? 0) - + getModsDelta(RollModCategory.roll) - + (sheetData.GMBoosts.Dice ?? 0)); - let finalPosIndex = Object.values(Position).indexOf(sheetData.rollPositionInitial ?? Position.risky) - + getModsDelta(RollModCategory.position); - let finalEffectIndex = Object.values(Effect).indexOf(sheetData.rollEffectInitial ?? Effect.standard) - + getModsDelta(RollModCategory.effect); - sheetData.canTradePosition = sheetData.rollPosEffectTrade === "position" - || (sheetData.rollPosEffectTrade === false && (finalPosIndex > 0 && finalEffectIndex < 4)); - sheetData.canTradeEffect = sheetData.rollPosEffectTrade === "effect" - || (sheetData.rollPosEffectTrade === false && (finalPosIndex < 2 && finalEffectIndex > 1)); - if (sheetData.rollPosEffectTrade === "position") { - finalPosIndex++; - finalEffectIndex--; - } - if (sheetData.rollPosEffectTrade === "effect") { - finalPosIndex--; - finalEffectIndex++; - } - sheetData.rollPositionFinal = Object.values(Position)[U.clampNum(finalPosIndex, [0, 2])]; - sheetData.rollEffectFinal = Object.values(Effect)[U.clampNum(finalEffectIndex, [0, 4])]; - sheetData.isAffectingResult = getModsDelta(RollModCategory.result) !== 0 - || (sheetData.GMBoosts.Result ?? 0) !== 0 - || Object.values({ - ...(sheetData.rollMods.result?.negative ?? {}), - ...(sheetData.rollMods.result?.positive ?? {}) - }).filter((mod) => sheetData.isGM || mod.status !== RollModStatus.Hidden).length > 0; - if (sheetData.isAffectingResult) { - sheetData.rollResultFinal = getModsDelta(RollModCategory.result) - + (sheetData.GMBoosts.Result ?? 0); - } - if (sheetData.rollFactors) { - for (const [factorName] of Object.entries(sheetData.rollFactors)) { - if (sheetData.GMBoosts && factorName in sheetData.GMBoosts) { - sheetData.rollFactors[factorName].value += sheetData.GMBoosts[factorName] ?? 0; - } - if ([Factor.tier, Factor.quality].includes(factorName)) { - sheetData.rollFactors[factorName].display = U.romanizeNum(sheetData.rollFactors[factorName].value); - } - } - } - if (sheetData.rollOpposition) { - sheetData.rollOppositionFactors = sheetData.rollOpposition.rollFactors; - if (sheetData.rollOppositionFactors) { - for (const [factorName] of Object.entries(sheetData.rollOppositionFactors)) { - if (sheetData.GMOppBoosts && factorName in sheetData.GMOppBoosts) { - sheetData.rollOppositionFactors[factorName].value += sheetData.GMOppBoosts[factorName] ?? 0; - } - if ([Factor.tier, Factor.quality].includes(factorName)) { - sheetData.rollOppositionFactors[factorName].display = U.romanizeNum(sheetData.rollOppositionFactors[factorName].value); - } - } - } - } - sheetData.hasInactiveConditionals = { - [RollModCategory.roll]: Object.values(sheetData.rollMods?.roll?.positive ?? {}) - .filter((mod) => mod.isConditional && mod.status === RollModStatus.ToggledOff) - .length > 0, - [RollModCategory.position]: Object.values(sheetData.rollMods?.position?.positive ?? {}) - .filter((mod) => mod.isConditional && mod.status === RollModStatus.ToggledOff) - .length > 0, - [RollModCategory.effect]: Object.values(sheetData.rollMods?.effect?.positive ?? {}) - .filter((mod) => mod.isConditional && mod.status === RollModStatus.ToggledOff) - .length > 0, - [RollModCategory.result]: Object.values(sheetData.rollMods?.result?.positive ?? {}) - .filter((mod) => mod.isConditional && mod.status === RollModStatus.ToggledOff) - .length > 0, - [RollModCategory.after]: Object.values(sheetData.rollMods?.after?.positive ?? {}) - .filter((mod) => mod.isConditional && mod.status === RollModStatus.ToggledOff) - .length > 0 - }; - const { success, partial, fail } = C.DiceOdds[sheetData.diceTotal ?? 0]; - sheetData.oddsGradient = [ - "linear-gradient(to right", - `var(--blades-black-dark) ${fail}%`, - `var(--blades-grey) ${fail + partial}%`, - `var(--blades-white-bright) ${fail + partial + success}%`, - "var(--blades-gold-bright))" - ].join(", "); - const stressMods = Object.values(sheetData.rollMods ?? {}) - .map((catModData) => Object.values(catModData) - .map((posNegModData) => Object.values(posNegModData))) - .flat(3) - .filter((modData) => [RollModStatus.ForcedOn, RollModStatus.ToggledOn].includes(modData.status) && (modData.stressCost ?? 0) > 0); - const stressTotal = U.sum(stressMods.map((mod) => mod.stressCost)); - if (stressTotal > 0) { - sheetData.stressData = { - cost: stressTotal, - tooltip: [ - `

Stress Cost: ${stressTotal}

    `, - ...stressMods - .map((mod) => `
  • ${mod.name} (${mod.category}): ${mod.stressCost} Stress.
  • `), - "
" - ].join("") - }; - } - eLog.checkLog3("getData", "RollCollab.getData()", { ...context, ...sheetData }); - return { - ...context, - ...sheetData - }; - } - _toggleRollModClick(target, status) { - switch (status) { - case RollModStatus.Hidden: { - return this.document.setFlag(C.SYSTEM_ID, target, RollModStatus.ForcedOn); - } - case RollModStatus.ToggledOff: { - return this.document.setFlag(C.SYSTEM_ID, target, RollModStatus.ToggledOn); - } - case RollModStatus.ToggledOn: { - return this.document.setFlag(C.SYSTEM_ID, target, RollModStatus.ToggledOff); - } - case RollModStatus.ForcedOn: { - if (game.user.isGM) { - return this.document.setFlag(C.SYSTEM_ID, target, RollModStatus.ToggledOff); - } - return undefined; - } - } - return undefined; - } - async _toggleRollModContext(target, status) { - if (!game.user.isGM) { - return undefined; - } - switch (status) { - case RollModStatus.Hidden: { - return this.document.setFlag(C.SYSTEM_ID, target, RollModStatus.ToggledOff); - } - case RollModStatus.ToggledOff: { - return this.document.setFlag(C.SYSTEM_ID, target, RollModStatus.Hidden); - } - case RollModStatus.ToggledOn: { - return this.document.setFlag(C.SYSTEM_ID, target, RollModStatus.Hidden); - } - case RollModStatus.ForcedOn: { - if (game.user.isGM) { - return this.document.setFlag(C.SYSTEM_ID, target, RollModStatus.Hidden); - } - return undefined; - } - } - return undefined; - } - activateListeners(html) { - super.activateListeners(html); - ApplyTooltipListeners(html); - html.find("[data-action='toggle']").on({ - click: async (event) => { - event.preventDefault(); - const elem$ = $(event.currentTarget); - const status = elem$.data("status"); - const cat = elem$.data("cat"); - const posNeg = elem$.data("posNeg"); - const name = elem$.data("name"); - await this._toggleRollModClick(`rollCollab.rollMods.${cat}.${posNeg}.${name}.status`, status); - const bargainStatus = this.document.getFlag(C.SYSTEM_ID, "rollCollab.rollMods.roll.positive.Bargain.status"); - const pushStatus = this.document.getFlag(C.SYSTEM_ID, "rollCollab.rollMods.roll.positive.Push.status"); - if (name === "Bargain") { - if ([RollModStatus.ForcedOn, RollModStatus.ToggledOn].includes(bargainStatus ?? "") && pushStatus !== RollModStatus.Hidden) { - await this.document.setFlag(C.SYSTEM_ID, "rollCollab.rollMods.roll.positive.Push.status", RollModStatus.Hidden); - } - else if ([RollModStatus.ToggledOff, RollModStatus.Hidden].includes(bargainStatus ?? "") && pushStatus === RollModStatus.Hidden) { - await this.document.setFlag(C.SYSTEM_ID, "rollCollab.rollMods.roll.positive.Push.status", RollModStatus.ToggledOff); - } - } - }, - contextmenu: (event) => { - event.preventDefault(); - const elem$ = $(event.currentTarget); - const status = elem$.data("status"); - const cat = elem$.data("cat"); - const posNeg = elem$.data("posNeg"); - const name = elem$.data("name"); - this._toggleRollModContext(`rollCollab.rollMods.${cat}.${posNeg}.${name}.status`, status); - } - }); - html.find("[data-action='tradePosition']").on({ - click: (event) => { - const curVal = `${$(event.currentTarget).data("value")}`; - if (curVal === "false") { - this.document.setFlag(C.SYSTEM_ID, "rollCollab.rollPosEffectTrade", "effect"); - } - else { - this.document.setFlag(C.SYSTEM_ID, "rollCollab.rollPosEffectTrade", false); - } - } - }); - html.find("[data-action='tradeEffect']").on({ - click: (event) => { - const curVal = `${$(event.currentTarget).data("value")}`; - if (curVal === "false") { - this.document.setFlag(C.SYSTEM_ID, "rollCollab.rollPosEffectTrade", "position"); - } - else { - this.document.setFlag(C.SYSTEM_ID, "rollCollab.rollPosEffectTrade", false); - } - } - }); - } - async _onSubmit(event, { updateData } = {}) { - return super._onSubmit(event, { updateData, preventClose: true }) - .then((returnVal) => { this.render(); return returnVal; }); - } - async close(options = {}) { - eLog.checkLog3("rollCollab", "RollCollab.close()", { options }); - if (options.rollID) { - return super.close({}); - } - this.document.setFlag(C.SYSTEM_ID, "rollCollab", null); - socketlib.system.executeForEveryone("closeRollCollab", this.rollID); - return undefined; - } - render(force, options) { - if (!this.document.getFlag(C.SYSTEM_ID, "rollCollab")) { - return this; - } - return super.render(force, options); - } -} -export default BladesRollCollabSheet; \ No newline at end of file diff --git a/scss/chat/_chat.scss b/scss/chat/_chat.scss index 6906f88b..13efcec1 100644 --- a/scss/chat/_chat.scss +++ b/scss/chat/_chat.scss @@ -1,53 +1,144 @@ .chat-message { background: var(--blades-black); - // .message-header { + .message-header { + position: relative; + z-index: 1; - // } + .message-sender { + color: var(--blades-grey); + } + } .message-content { - .dice-roll-strip { - display: flex; - flex-direction: row; - flex-wrap: nowrap; - height: 30px; + .chat-message-bg { + position: absolute; + top: 0; + left: 0; + height: 100%; width: 100%; - align-items: stretch; - justify-content: space-evenly; + z-index: 0; - .blades-die { - display: block; - height: 30px; + &.roll-position-risky { + background-image: url("../assets/animations/chat/roll-position-risky.webp"); + background-size: cover; + } + } - img { - height: 30px; - width: 30px; - display: block; - } + .blades-roll { + position: relative; + z-index: 2; + + .chat-header { + margin: 0; + padding: 0; + background: transparent; + box-shadow: none; + color: var(--blades-grey); + } + + h1.chat-header { + margin: 0; + padding: 0; + text-align: center; + background: transparent; + box-shadow: none; + font-size: 32px; + color: var(--blades-gold); + } - &.blades-die-critical { - outline: 2px solid var(--blades-gold-bright); + .roll-states { + justify-content: space-between; + padding: 0 20px; + .roll-state-container { + flex-basis: 30%; + flex-grow: 0; + flex-shrink: 0; } - &.blades-die-success { - outline: 2px solid var(--blades-white-bright); + + h4.roll-state-label { + font-family: var(--font-primary); + // border-bottom: 1px solid white; + font-size: 12px; + display: block; + width: 100%; + text-align: center; + white-space: nowrap; + color: var(--blades-grey); + margin-top: -4px; + } + + h3.roll-state { + white-space: nowrap; } - // &.blades-die-partial { + } - // } - // &.blades-die-fail { + h2.chat-header.roll-position { + text-align: right; + font-family: var(--font-primary); - // } - &.blades-die-ghost { - img { opacity: 0.5 } + .position-text { + margin: 0 15px; + transform-origin: 50% 50%; + scale: 1.5; + display: inline-block; + font-family: var(--font-emphasis); + + &.position-text-controlled {} + &.position-text-risky {} + &.position-text-desperate { } } - &.blades-die-resistance { - outline: 2px solid var(--blades-cyan-bright); + } + + .dice-roll-strip { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + height: 30px; + width: 100%; + align-items: stretch; + justify-content: center; + gap: 10px; + margin: 10px 0; + + .blades-die { + display: block; + height: 30px; + + img { + height: 30px; + width: 30px; + display: block; + } + + &.blades-die-critical { + outline: 2px solid var(--blades-gold-bright); + + } + &.blades-die-success { + outline: 2px solid var(--blades-white-bright); + } + // &.blades-die-partial { + + // } + // &.blades-die-fail { + + // } + &.blades-die-ghost { + img { opacity: 0.5 } + } + &.blades-die-resistance { + outline: 2px solid var(--blades-blue-bright); + } } } } + .dice-roll-strip { + } + .chat-label, .chat-trait-label { background-color: var(--blades-grey-bright); font-family: var(--font-emphasis); diff --git a/scss/components/_comps.scss b/scss/components/_comps.scss index be89bd5a..60c6f5b7 100644 --- a/scss/components/_comps.scss +++ b/scss/components/_comps.scss @@ -825,27 +825,63 @@ } } +// .consequence-container.flex-vertical.full-width { +// --container-height: 40px; +// height: calc(var(--container-height) * 0.75); +// max-height: calc(var(--container-height) * 0.75); +// justify-content: flex-start; +// } .comp.consequence-display-container { --container-height: 40px; - --container-left-shift: 50px; - - --csq-icon-dark: var(--blades-red-dark); - --csq-icon-med: var(--blades-red); - --csq-icon-bright: var(--blades-red-bright); + --container-left-shift: 70px; + --csq-icon-bg-color: var(--blades-black-dark); --csq-type-bg: var(--csq-icon-dark); - --csq-type-color: var(--blades-black-dark); - // --csq-name-color: var(--csq-icon-bright); + --csq-button-size-mult: 0.33; - --csq-icon-bg-color: var(--blades-black-dark); + @keyframes anim-glow { + 0% { + box-shadow: 0 0 0px var(--blades-red-bright); + background-color: var(--blades-red-darkest); + } - // --csq-icon-border-color: var(--blades-red); - // --csq-icon-stroke-color: var(--blades-grey-bright); + 10% { + background-color: var(--blades-red-bright) + } - --csq-button-size-mult: 0.33; - // --csq-button-bg-color: var(--blades-black); - // --csq-button-color: var(--blades-grey-bright); + 100% { + box-shadow: 0 0 10px 10px transparent; + background-color: var(--blades-red-darkest); + } + } + + @keyframes icon-glow { + 0% { + filter: brightness(1); + } + 10% { + filter: brightness(1); + } + 100% { + filter: brightness(1); + } + } + + @keyframes icon-red-pulse { + 0% { + scale: 1; + fill: var(--blades-grey); + } + 10% { + scale: 1.1; + fill: var(--blades-red-bright); + } + 100% { + scale: 1; + fill: var(--blades-grey); + } + } position: relative; display: block; @@ -853,9 +889,25 @@ max-height: var(--container-height); min-height: var(--container-height); - // .base-consequence { opacity: 0 !important } + .base-consequence { + --csq-icon-dark: var(--blades-black); + --csq-icon-med: var(--blades-grey); + --csq-icon-bright: var(--blades-white); + --csq-type-color: var(--blades-grey); + --csq-name-color: var(--blades-white); + } + + .accept-consequence { + opacity: 0; + --csq-icon-dark: var(--blades-red-dark); + --csq-icon-med: var(--blades-red); + --csq-icon-bright: var(--blades-red-bright); + --csq-type-color: var(--blades-black-dark); + --csq-name-color: var(--blades-red); + } + .resist-consequence { - // opacity: 1 !important; + opacity: 0; --csq-icon-dark: var(--blades-gold-dark); --csq-icon-med: var(--blades-gold); --csq-icon-bright: var(--blades-gold-bright); @@ -863,24 +915,62 @@ --csq-name-color: var(--blades-gold-bright); } + .special-armor-consequence { + opacity: 0; + --csq-icon-dark: var(--blades-blue-dark); + --csq-icon-med: var(--blades-blue); + --csq-icon-bright: var(--blades-blue-bright); + --csq-type-color: var(--blades-blue-dark); + --csq-name-color: var(--blades-blue-bright); + } + + .consequence-interaction-pad { + display: none; + display: block; + position: absolute; + z-index: 2; + pointer-events: auto; + height: 100%; + top: 0; + // outline: 1px dotted cyan; + + &.accept-consequence-pad { + --pad-left-shift: calc(var(--container-left-shift) + (var(--container-height))); + left: var(--pad-left-shift); + width: calc(100% - var(--pad-left-shift)); + } + + &.resist-consequence-pad, &.special-armor-consequence-pad { + left: 0; + width: calc(var(--container-left-shift) - (var(--container-height) * 0)); + } + + &.special-armor-consequence-pad { + height: 40%; + z-index: 3; + } + } + .consequence-icon-container { position: relative; - height: var(--container-height); - width: var(--container-height); - min-width: var(--container-height); - max-width: var(--container-height); - min-height: var(--container-height); - max-height: var(--container-height); + height: calc(var(--container-height) * 0.75); + max-width: calc(var(--container-height) * 0.75); background: transparent; left: var(--container-left-shift); z-index: 1; + pointer-events: auto; + transition: 0.2s; + + &:hover { + filter: brightness(2); + } .consequence-icon-circle { position: absolute; translate: -50% -50%; - transform-origin: 50% 50%; - top: 50%; - left: 50%; + transform-origin: 100% 0%; + top: calc(var(--container-height) * 0.5); + left: calc(var(--container-height) * 0.5); border-radius: 50%; height: var(--container-height); width: var(--container-height); @@ -902,6 +992,27 @@ .fill-bright { fill: var(--csq-icon-bright) } .fill-radial { fill: var(--csq-icon-med) } .fill-linear { fill: var(--csq-icon-med) } + + path { transform-origin: 50% 50% } + } + + &.base-consequence { + scale: 0.75; + animation: icon-glow 2s ease infinite; + pointer-events: auto; + + svg path { + animation: icon-red-pulse 2s ease infinite; + } + + &:hover { + filter: brightness(2) !important; + animation: none; + } + } + + &.resist-consequence, &.special-armor-consequence { + outline-width: 2px; } .consequence-icon { @@ -915,16 +1026,22 @@ display: flex; flex-direction: row; flex-wrap: nowrap; - bottom: 0px; + bottom: -10px; .consequence-button-bg { position: absolute; z-index: -1; height: 100%; width: calc(100% + 24px); + transform-origin: 0% 50%; top: 0px; background: var(--csq-icon-bright); display: block; + + &.consequence-resist-button-bg, + &.consequence-special-armor-button-bg { + transform-origin: 100% 50%; + } } .consequence-button-label { @@ -934,6 +1051,12 @@ font-size: 10px; line-height: 14px; color: var(--blades-grey); + font-weight: 800; + text-shadow: + // 0px 0px 1px var(--blades-black-dark), + // 0px 0px 0.5px var(--blades-black-dark), + 0px 0px 1px var(--blades-black-dark); + letter-spacing: 1; // color: var(--blades-black-dark); text-transform: uppercase; // text-indent: 3px; @@ -955,7 +1078,7 @@ } &.consequence-resist-button-container { - right: calc(100% - 3px); + right: 100%; .consequence-button-bg { left: -7px; @@ -963,7 +1086,7 @@ } } &.consequence-accept-button-container { - left: 100%; + left: 140%; .consequence-button-bg { right: -7px; @@ -982,7 +1105,7 @@ height: calc(var(--container-height) * 0.33); transform-origin: 0% 50%; left: calc(var(--container-height) + var(--container-left-shift) - 10px); - top: 0px; + top: -2px; padding: 0 5px 0 15px; .consequence-type-bg { @@ -991,21 +1114,23 @@ left: -20px; height: 100%; width: 170px; + transform-origin: 0% 50%; transform: skewX(-45deg); - background: var(--csq-type-bg); + background: var(--csq-icon-dark); } .consequence-type { position: absolute; top: 0; - left: 10px; + // left: calc(var(--container-height) + var(--container-left-shift) - 75px); + transform-origin: 0% 50%; white-space: nowrap; font-family: Oswald, sans-serif; text-transform: uppercase; text-align: right; font-size: 10px; color: var(--csq-type-color); - font-weight: bold; + font-weight: normal; } } @@ -1023,9 +1148,10 @@ z-index: 1; padding: 0 5px 0 35px; font-size: 14px; - line-height: calc(var(--container-height) * 0.55); + line-height: 17px; font-family: Kirsty, serif; font-variant: small-caps; + transform-origin: 0% 50%; // text-shadow: var(--text-shadow-dark); color: var(--csq-icon-bright); font-style: italic; @@ -1040,7 +1166,7 @@ .consequence-footer-container { position: absolute; height: calc(var(--container-height) * var(--csq-button-size-mult)); - width: 120px; + width: auto; bottom: 0; top: unset; left: calc(var(--container-height) + var(--container-left-shift) - 20px); @@ -1054,9 +1180,20 @@ background: var(--csq-icon-bright); display: block; transform: skewX(45deg); + transform-origin: 0% 50%; + + &.resist-consequence { + width: 120px; + } + + &.special-armor-consequence { + width: 250px; + } } - .consequence-resist-attribute { + .consequence-footer-message { + position: absolute; + white-space: nowrap; font-family: Oswald, sans-serif; font-weight: bold; color: var(--blades-black-dark); @@ -1064,6 +1201,7 @@ line-height: 14px; padding-left: 25px; justify-content: flex-start; + transform-origin: 0% 50%; gap: 5px; } .dotline { diff --git a/scss/core/_globals.scss b/scss/core/_globals.scss index a06de769..49274237 100644 --- a/scss/core/_globals.scss +++ b/scss/core/_globals.scss @@ -137,7 +137,7 @@ .grey { color: var(--blades-grey) !important } .white { color: var(--blades-white) !important } .white-bright { color: var(--blades-white-bright) !important } - .cyan-bright { color: var(--blades-cyan-bright) !important } + .cyan-bright { color: var(--blades-blue-bright) !important } .uppercase { text-transform: uppercase !important } .inline-code { font-family: var(--font-mono) !important; diff --git a/scss/core/_vars.scss b/scss/core/_vars.scss index 2a530d6b..a72569c6 100644 --- a/scss/core/_vars.scss +++ b/scss/core/_vars.scss @@ -143,25 +143,70 @@ $isCompactLayout: true; --blades-black-nums: 32, 32, 32; --blades-black-dark-nums: 0, 0, 0; - --blades-gold-bright-nums: 255, 231, 92; // 253, 212, 112; - --blades-gold-nums: 255, 215, 0; // 171, 146, 84; - --blades-gold-dark-nums: 184, 156, 0; - --blades-gold-darkest-nums: 55, 53, 0; + --blades-gold-bright-nums: 206,180, 71; // 175,149, 37; // 255, 231, 92; + --blades-gold-nums: 143,118, 11; // 255, 215, 0; + --blades-gold-dark-nums: 105, 86, 0; // 184, 156, 0; + --blades-gold-darkest-nums: 64, 52, 0; // 55, 53, 0; - --blades-red-bright-nums: 220, 20, 60; - --blades-red-nums: 204, 0, 0; - --blades-red-dark-nums: 122, 0, 0; - --blades-red-darkest-nums: 50, 0, 0; + --blades-red-bright-nums: 255, 0, 0; + --blades-red-nums: 200, 0, 0; // 255, 0, 0; + --blades-red-dark-nums: 150, 0, 0; + --blades-red-darkest-nums: 50, 0, 0; --blades-green-bright-nums: 20, 220, 60; --blades-green-nums: 0, 204, 0; --blades-green-dark-nums: 0, 122, 0; --blades-green-darkest-nums: 0, 60, 0; - --blades-cyan-bright-nums: 198, 255, 255; - --blades-cyan-nums: 150, 255, 255; - --blades-cyan-dark-nums: 40, 120, 120; - --blades-cyan-darkest-nums: 25, 49, 49; + --blades-blue-bright-nums: 198, 255, 255; + --blades-blue-nums: 150, 255, 255; + --blades-blue-dark-nums: 40, 120, 120; + --blades-blue-darkest-nums: 25, 49, 49; + + /* + NEW COLOR PALETTE OVERRIDE + + == GOLD == + http://paletton.com/#uid=11n0u0kNTr2qtG1K2DKRbkEVqcT + + shade 0 = #D7AF00 = rgb(215,175, 0) = rgba(215,175, 0,1) = rgb0(0.843,0.686,0) + shade 1 = #FFD82C = rgb(255,216, 44) = rgba(255,216, 44,1) = rgb0(1,0.847,0.173) + shade 2 = #FFCF00 = rgb(255,207, 0) = rgba(255,207, 0,1) = rgb0(1,0.812,0) + shade 3 = #A58600 = rgb(165,134, 0) = rgba(165,134, 0,1) = rgb0(0.647,0.525,0) + shade 4 = #675300 = rgb(103, 83, 0) = rgba(103, 83, 0,1) = rgb0(0.404,0.325,0)' + + == RED == + http://paletton.com/#uid=1000u0kTixTijNOwGQpTXmEXg9Y + shade 0 = #FF0000 = rgb(255, 0, 0) = rgba(255, 0, 0,1) = rgb0(1,0,0) + shade 1 = #FF6D6D = rgb(255,109,109) = rgba(255,109,109,1) = rgb0(1,0.427,0.427) + shade 2 = #FF0000 = rgb(255, 0, 0) = rgba(255, 0, 0,1) = rgb0(1,0,0) + shade 3 = #B40000 = rgb(180, 0, 0) = rgba(180, 0, 0,1) = rgb0(0.706,0,0) + shade 4 = #4F0000 = rgb( 79, 0, 0) = rgba( 79, 0, 0,1) = rgb0(0.31,0,0) + + == BLUE == + http://paletton.com/#uid=13i0u0kTixTodNREARdTRoAV1g4 + shade 0 = #009F9F = rgb( 0,159,159) = rgba( 0,159,159,1) = rgb0(0,0.624,0.624) + shade 1 = #34D5D5 = rgb( 52,213,213) = rgba( 52,213,213,1) = rgb0(0.204,0.835,0.835) + shade 2 = #00E0E0 = rgb( 0,224,224) = rgba( 0,224,224,1) = rgb0(0,0.878,0.878) + shade 3 = #007676 = rgb( 0,118,118) = rgba( 0,118,118,1) = rgb0(0,0.463,0.463) + shade 4 = #004D4D = rgb( 0, 77, 77) = rgba( 0, 77, 77,1) = rgb0(0,0.302,0.302) + */ + + --blades-gold-bright-nums: 255,216, 44; + --blades-gold-nums: 215,175, 0; // 255,207, 0 + --blades-gold-dark-nums: 165,134, 0; + --blades-gold-darkest-nums: 103, 83, 0; + + /* --blades-red-bright-nums: 255,109,109; + --blades-red-nums: 255, 0, 0; + --blades-red-dark-nums: 180, 0, 0; + --blades-red-darkest-nums: 79, 0, 0; */ + + --blades-blue-bright-nums: 0,224,224; + --blades-blue-nums: 52,213,213; // 0,159,159 + --blades-blue-dark-nums: 0,118,118; + --blades-blue-darkest-nums: 0, 77, 77; + /* END OVERRIDE */ --blades-white-bright: rgba(var(--blades-white-bright-nums), 1); --blades-white: rgba(var(--blades-white-nums), 1); @@ -171,6 +216,7 @@ $isCompactLayout: true; --blades-black: rgba(var(--blades-black-nums), 1); --blades-black-dark: rgba(var(--blades-black-dark-nums), 1); + --blades-gold-brightest: rgba(var(--blades-gold-brightest-nums), 1); --blades-gold-bright: rgba(var(--blades-gold-bright-nums), 1); --blades-gold: rgba(var(--blades-gold-nums), 1); --blades-gold-dark: rgba(var(--blades-gold-dark-nums), 1); @@ -186,10 +232,10 @@ $isCompactLayout: true; --blades-green-dark: rgba(var(--blades-green-dark-nums), 1); --blades-green-darkest: rgba(var(--blades-green-darkest-nums), 1); - --blades-cyan-bright: rgba(var(--blades-cyan-bright-nums), 1); - --blades-cyan: rgba(var(--blades-cyan-nums), 1); - --blades-cyan-dark: rgba(var(--blades-cyan-dark-nums), 1); - --blades-cyan-darkest: rgba(var(--blades-cyan-darkest-nums), 1); + --blades-blue-bright: rgba(var(--blades-blue-bright-nums), 1); + --blades-blue: rgba(var(--blades-blue-nums), 1); + --blades-blue-dark: rgba(var(--blades-blue-dark-nums), 1); + --blades-blue-darkest: rgba(var(--blades-blue-darkest-nums), 1); --blades-white-fade: rgba(var(--blades-white-nums), 0.5); --blades-white-fade-strong: rgba(var(--blades-white-nums), 0.25); @@ -203,10 +249,10 @@ $isCompactLayout: true; --blades-red-dark-fade: rgba(var(--blades-red-dark-nums), 0.5); --blades-green-dark-fade: rgba(var(--blades-green-dark-nums), 0.5); - --blades-cyan-dark-fade: rgba(var(--blades-cyan-dark-nums), 0.5); + --blades-blue-dark-fade: rgba(var(--blades-blue-dark-nums), 0.5); --blades-red-dark-fade-strong: rgba(var(--blades-red-dark-nums), 0.25); --blades-green-dark-fade-strong: rgba(var(--blades-green-dark-nums), 0.25); - --blades-cyan-dark-fade-strong: rgba(var(--blades-cyan-dark-nums), 0.25); + --blades-blue-dark-fade-strong: rgba(var(--blades-blue-dark-nums), 0.25); // #endregion ░░░░[Blades Colors & Fades]░░░░ // #region ░░░░░░░ Numerically-Defined Colors (for Emu Stylesheet) ░░░░░░░ ~ --color-primary: var(--blades-white-nums); diff --git a/scss/dialog/_dialogs.scss b/scss/dialog/_dialogs.scss index 243fe99f..4d41d6b7 100644 --- a/scss/dialog/_dialogs.scss +++ b/scss/dialog/_dialogs.scss @@ -84,7 +84,7 @@ .comp.fine-quality { .comp-body { - .comp-title { color: var(--blades-cyan) } + .comp-title { color: var(--blades-blue) } } } @@ -185,7 +185,7 @@ width: 600px; background: var(--section-bg-color); - &.consequence-section-controlled { --section-bg-color: var(--blades-cyan-dark-fade) } + &.consequence-section-controlled { --section-bg-color: var(--blades-blue-dark-fade) } &.consequence-section-risky { --section-bg-color: transparent } &.consequence-section-desperate { --section-bg-color: var(--blades-red-dark-fade-strong) } @@ -201,7 +201,7 @@ text-shadow: none; box-shadow: none; - &.consequence-header-controlled { --h1-color: var(--blades-cyan) } + &.consequence-header-controlled { --h1-color: var(--blades-blue) } &.consequence-header-risky { --h1-color: var(--blades-grey-bright) } &.consequence-header-desperate { --h1-color: var(--blades-red) } } diff --git a/scss/sheets/_roll-collab-sheet.scss b/scss/sheets/_roll-collab-sheet.scss index 8febaa27..133d22f8 100644 --- a/scss/sheets/_roll-collab-sheet.scss +++ b/scss/sheets/_roll-collab-sheet.scss @@ -620,9 +620,9 @@ } &.position-controlled { - --final-block-text-color: var(--blades-cyan-bright); - --final-block-background-color: var(--blades-cyan-dark-fade); - --final-block-border-color: var(--blades-cyan); + --final-block-text-color: var(--blades-blue-bright); + --final-block-background-color: var(--blades-blue-dark-fade); + --final-block-border-color: var(--blades-blue); } } @@ -640,9 +640,9 @@ } &.effect-great { - --final-block-text-color: var(--blades-cyan-bright); - --final-block-background-color: var(--blades-cyan-dark-fade); - --final-block-border-color: var(--blades-cyan); + --final-block-text-color: var(--blades-blue-bright); + --final-block-background-color: var(--blades-blue-dark-fade); + --final-block-border-color: var(--blades-blue); } &.effect-extreme { @@ -815,8 +815,8 @@ &.gm-control-effect-great, &.gm-control-position-controlled { - --gm-control-border: var(--blades-cyan-bright); - --gm-control-background: var(--blades-cyan-dark); + --gm-control-border: var(--blades-blue-bright); + --gm-control-background: var(--blades-blue-dark); } &.gm-control-effect-extreme { @@ -1079,10 +1079,10 @@ } &.roll-type-fortune { - --roll-type-header-color: var(--blades-cyan-bright); - --roll-type-header-bg-color: var(--blades-cyan-dark); - --roll-type-header-underline-color: var(--blades-cyan-bright); - --roll-type-header-shadow-color: var(--blades-cyan-dark); + --roll-type-header-color: var(--blades-blue-bright); + --roll-type-header-bg-color: var(--blades-blue-dark); + --roll-type-header-underline-color: var(--blades-blue-bright); + --roll-type-header-shadow-color: var(--blades-blue-dark); } .roll-type-header { @@ -1779,7 +1779,7 @@ } .cyan-bright, .cyan-bright * { - color: var(--blades-cyan-bright) !important; + color: var(--blades-blue-bright) !important; } .tooltip.tooltip-roll-stress { diff --git a/scss/sheets/_score-sheet.scss b/scss/sheets/_score-sheet.scss index df6d43dc..a6eca24f 100644 --- a/scss/sheets/_score-sheet.scss +++ b/scss/sheets/_score-sheet.scss @@ -50,7 +50,7 @@ position: relative; &:last-child { grid-area: controls-right; - // background: var(--blades-cyan-dark); + // background: var(--blades-blue-dark); } .toggle-icon { diff --git a/scss/style.scss b/scss/style.scss index 2b9a7ac0..1c0bb192 100644 --- a/scss/style.scss +++ b/scss/style.scss @@ -89,6 +89,7 @@ #chat { @import "./core/reset"; + @import './core/globals'; &, * { diff --git a/templates/chat/roll-result-action-roll.hbs b/templates/chat/roll-result-action-roll.hbs new file mode 100644 index 00000000..7c515888 --- /dev/null +++ b/templates/chat/roll-result-action-roll.hbs @@ -0,0 +1,65 @@ +
+ +
+ + {{!-- Roll Trait --}} +

+ {{case "upper" rollTrait}} +

+ + {{!-- Opposition --}} + {{#if rollOpposition}} +

+ vs. + {{case "title" rollOpposition.rollOppName}} +

+ {{/if}} + + {{!-- Position & Effect --}} +
+
+

{{case "title" finalPosition}}

+

Position

+
+
+

{{case "title" finalEffect}}

+

Effect

+
+
+ + {{!-- Dice Rolls --}} +
{{{dieValsHTML}}}
+ + {{!-- Roll Result --}} +

{{case "upper" rollResult}}

+ + {{!-- Roll Result Description --}} +

{{rollResultDescription}}

+ + {{!-- Consequences --}} + {{#if (test rollResult "==" "partial")}} +
+ {{log "ConsequenceContainer" this}} + {{#with (lookup flagData.consequenceData finalPosition) as |csqData|}} + {{#each csqData.partial as |cData cIndex|}} + {{#if cData.resistedTo}} + {{> "systems/eunos-blades/templates/components/consequence.hbs" cData + isResistanceVisible=true }} + {{/if}} + {{/each}} + {{/with}} +
+ {{/if}} + {{#if (test rollResult "==" "fail")}} +
+ {{#with (lookup flagData.consequenceData finalPosition) as |csqData|}} + {{#each csqData.fail as |cData cIndex|}} + {{#if cData.resistedTo}} + {{> "systems/eunos-blades/templates/components/consequence.hbs" cData + isResistanceVisible=true }} + {{/if}} + {{/each}} + {{/with}} +
+ {{/if}} +
diff --git a/templates/chat/action-roll.hbs b/templates/chat/roll-result-fortune-roll.hbs similarity index 100% rename from templates/chat/action-roll.hbs rename to templates/chat/roll-result-fortune-roll.hbs diff --git a/templates/chat/resistance-roll.hbs b/templates/chat/roll-result-indulgevice-roll.hbs similarity index 100% rename from templates/chat/resistance-roll.hbs rename to templates/chat/roll-result-indulgevice-roll.hbs diff --git a/templates/chat/roll-result-resistance-roll.hbs b/templates/chat/roll-result-resistance-roll.hbs new file mode 100644 index 00000000..733c89ed --- /dev/null +++ b/templates/chat/roll-result-resistance-roll.hbs @@ -0,0 +1,26 @@ +
+ {{#if attribute_label}}
{{localize attribute_label}}
{{/if}} + + {{#if (eq roll_status "critical-success")}} +
{{localize "BITD.RollCriticalSuccess"}}
+
+

{{{localize "BITD.RollResistanceCritical"}}}

+
+ {{else}} +
{{localize "BITD.RollSuccess"}}
+

{{{localize "BITD.RollResistance" stress=stress}}}

+ {{/if}} + {{#if note}} + {{note}} + {{/if}} + +
    + {{#each this.rolls}} + {{#if this.result}} +
  1. {{{this.result}}}
  2. + {{else}} +
  3. {{{this.roll}}}
  4. + {{/if}} + {{/each}} +
+
diff --git a/templates/components/consequence.hbs b/templates/components/consequence.hbs index dac8af43..6a71a9e6 100644 --- a/templates/components/consequence.hbs +++ b/templates/components/consequence.hbs @@ -7,22 +7,31 @@ data-resist-name="{{resistedTo.name}}" data-resist-type="{{resistedTo.type}}" > +
+
+ {{#if specialArmorTo}} +
+ {{/if}} +
{{{compileSvg type icon}}}
-
+
+ {{{compileSvg type icon}}} +
+
{{{compileSvg resistedTo.type resistedTo.icon}}}
{{#if specialArmorTo}} -
+
{{{compileSvg specialArmorTo.type specialArmorTo.icon}}}
{{/if}} -
-
+
+
{{> "systems/eunos-blades/templates/components/button-icon.hbs" blockClass="consequence-resist-button" @@ -30,18 +39,18 @@ action="resist-consequence" }}
-
-
+
+
{{> "systems/eunos-blades/templates/components/button-icon.hbs" blockClass="consequence-accept-button" - buttonClass="fa-regular fa-square-check" + buttonClass="fa-solid fa-square-check" action="accept-consequence" }}
{{#if specialArmorTo}} -
-
+
+
{{> "systems/eunos-blades/templates/components/button-icon.hbs" blockClass="consequence-special-armor-button" @@ -55,16 +64,18 @@
-
+
+ + {{#unless (test resistedTo.type "==" "None")}} - + {{/unless}} {{#if specialArmorTo}} {{#unless (test specialArmorTo.type "==" "None")}} - + {{/unless}} {{/if}} @@ -73,26 +84,27 @@
+ {{#if (test resistedTo.type "==" "None")}} - + {{else}} - + {{/if}} {{#if specialArmorTo}} {{#if (test specialArmorTo.type "==" "None")}} - + {{else}} - + {{/if}} {{/if}}
{{!-- AI Options --}}
diff --git a/templates/roll/partials/roll-collab-action-gm.hbs b/templates/roll/partials/roll-collab-action-gm.hbs index f61a7f33..c6f24bb9 100644 --- a/templates/roll/partials/roll-collab-action-gm.hbs +++ b/templates/roll/partials/roll-collab-action-gm.hbs @@ -239,8 +239,6 @@ {{#if consequenceData}} {{#with (lookup consequenceData rollPositionFinal) as |csqData|}}

On Partial Success:

-
-
{{#each csqData.partial as |cData cIndex|}} {{eLog "Partial cData" cData}} @@ -250,11 +248,7 @@ {{/if}} {{/each}}
-
-

On Failure:

-
-
{{#each csqData.fail as |cData cIndex|}} {{#if cData.resistedTo}} @@ -263,8 +257,6 @@ {{/if}} {{/each}}
-
-
{{/with}} {{/if}} {{> "systems/eunos-blades/templates/components/button-icon.hbs" @@ -323,10 +315,9 @@ {{{oddsHTMLStart}}}{{{oddsHTMLStop}}}
-
+
{{#if (test rollPhase "==" "Collaboration")}} - {{/if}} diff --git a/templates/roll/partials/roll-collab-resistance-gm.hbs b/templates/roll/partials/roll-collab-resistance-gm.hbs index 8925a05a..ecc967d7 100644 --- a/templates/roll/partials/roll-collab-resistance-gm.hbs +++ b/templates/roll/partials/roll-collab-resistance-gm.hbs @@ -152,7 +152,7 @@
-
+
{{#if (test rollPhase "==" "Collaboration")}} , resistedTo?: ConsequenceResistOption|false, + canArmorA?: boolean, + armorToAOptions?: Record< + string, // stringified index + ConsequenceResistOption // ai + >, + armorToA?: ConsequenceResistOption|false, + canArmorB?: boolean, + armorToBOptions?: Record< + string, // stringified index + ConsequenceResistOption // ai + >, + armorToB?: ConsequenceResistOption|false, specialArmorTo?: ConsequenceResistOption } @@ -209,7 +222,8 @@ declare global { |BladesActorOfType |BladesItemOfType |BladesItemOfType - |BladesItemOfType; + |BladesItemOfType + |BladesItemOfType; export interface PrimaryDocData { rollPrimaryID?: string, diff --git a/ts/BladesChat.ts b/ts/BladesChat.ts new file mode 100644 index 00000000..2b7388ac --- /dev/null +++ b/ts/BladesChat.ts @@ -0,0 +1,90 @@ +// #region IMPORTS ~ +/* import U from "./core/utilities"; +import C, { + BladesActorType, BladesItemType, RollPermissions, RollType, RollSubType, + RollModStatus, RollModSection, ActionTrait, DowntimeAction, AttributeTrait, + Position, Effect, Factor, RollResult, RollPhase, ConsequenceType, Tag +} from "./core/constants"; +import {BladesActor, BladesPC, BladesCrew} from "./documents/BladesActorProxy"; +import {BladesItem, BladesGMTracker} from "./documents/BladesItemProxy"; +import {ApplyTooltipListeners, ApplyConsequenceListeners} from "./core/gsap"; +import BladesDialog from "./BladesDialog"; */ + +import U from "./core/utilities"; +import C, {BladesActorType, BladesItemType, RollType} from "./core/constants"; +import {BladesPC, BladesCrew} from "./documents/BladesActorProxy"; +import {BladesItem} from "./documents/BladesItemProxy"; +import {ApplyTooltipListeners, ApplyConsequenceListeners} from "./core/gsap"; + +import BladesRoll from "./BladesRoll"; +// #endregion + +class BladesChat extends ChatMessage { + + static Initialize() { + Hooks.on("renderChatMessage", (_msg: ChatMessage, html: JQuery) => { + ApplyTooltipListeners(html); + ApplyConsequenceListeners(html); + }); + return loadTemplates([ + "systems/eunos-blades/templates/chat/roll-result-action-roll.hbs", + "systems/eunos-blades/templates/chat/roll-result-resistance-roll.hbs", + "systems/eunos-blades/templates/chat/roll-result-fortune-roll.hbs", + "systems/eunos-blades/templates/chat/roll-result-indulgevice-roll.hbs" + ]); + } + + static GetRollSpeaker(rollInst: BladesRoll) { + + // Get initial speaker data + const speaker = BladesChat.getSpeaker(); + + // Compare against rollInst.rollPrimary and modify accordingly. + const {rollPrimaryID, rollPrimaryName, rollPrimaryType, rollPrimaryDoc} = rollInst.rollPrimary; + + speaker.alias = rollPrimaryName; + + if (BladesItem.IsType(BladesItemType.cohort_gang, BladesItemType.cohort_expert)) { + speaker.actor = rollPrimaryDoc?.parent?.id ?? speaker.actor; + if (rollPrimaryDoc?.parent instanceof BladesPC) { + speaker.alias = `${speaker.alias} (${rollPrimaryDoc.parent.name})`; + } + } else if (BladesItem.IsType(BladesItemType.gm_tracker, BladesItemType.score)) { + speaker.actor = null; + speaker.alias = "The Gamemaster"; + } else if (rollPrimaryID) { + speaker.actor = rollPrimaryID; + } + + speaker.alias = `${speaker.alias} Rolls ...`; + + return speaker; + } + + static async ConstructRollOutput(rollInst: BladesRoll) { + const speaker = BladesChat.GetRollSpeaker(rollInst); + + const template = `systems/eunos-blades/templates/chat/roll-result-${U.lCase(rollInst.rollType)}-roll.hbs`; + + const templateData: BladesRoll & {rollResultDescription?: string} = rollInst; + if (rollInst.rollResult) { + templateData.rollResultDescription = C.RollResultDescriptions[rollInst.finalPosition][rollInst.rollResult]; + } + + const renderedHTML = await renderTemplate(template, rollInst); + + const messageData = { + speaker, + content: renderedHTML + }; + + BladesChat.create(messageData, {}); + } +} + + +interface BladesChat { + +} + +export default BladesChat; diff --git a/ts/BladesPushAlert.ts b/ts/BladesPushAlert.ts index 482e0ec8..9806413f 100644 --- a/ts/BladesPushAlert.ts +++ b/ts/BladesPushAlert.ts @@ -1,4 +1,5 @@ import U from "./core/utilities"; +import C from "./core/constants"; export default class BladesPushAlert { @@ -75,8 +76,8 @@ export default class BladesPushAlert { U.gsap.from( pushElem$[0], { - background: "rgb(255, 231, 92)", - borderColor: "rgb(255, 255, 255)", + background: C.Colors.bGOLD, + borderColor: C.Colors.WHITE, duration: 10, ease: "power2" } diff --git a/ts/BladesRoll.ts b/ts/BladesRoll.ts index cbf8c868..129ec789 100644 --- a/ts/BladesRoll.ts +++ b/ts/BladesRoll.ts @@ -5,6 +5,7 @@ import {BladesActor, BladesPC, BladesCrew} from "./documents/BladesActorProxy"; import {BladesItem, BladesGMTracker} from "./documents/BladesItemProxy"; import {ApplyTooltipListeners, ApplyConsequenceListeners} from "./core/gsap"; import BladesDialog from "./BladesDialog"; +import BladesChat from "./BladesChat"; // #endregion // #region Types & Type Checking ~ @@ -3139,60 +3140,7 @@ class BladesRoll extends DocumentSheet { } async outputRollToChat() { - const speaker = ChatMessage.getSpeaker(); - - // Const renderedHTML = await this.getChatHTML(); - let renderedHTML = ""; - this.rollPhase = RollPhase.AwaitingChatInput; - - switch (this.rollType) { - case RollType.Action: { - renderedHTML = - - - await renderTemplate("systems/eunos-blades/templates/chat/action-roll.hbs", { - sourceName: this.rollPrimary.rollPrimaryName, - oppName: this.rollOpposition?.rollOppName, - type: U.lCase(this.rollType), - subType: U.lCase(this.rollSubType), - downtimeAction: U.lCase(this.rollDowntimeAction), - position: this.finalPosition, - effect: this.finalEffect, - result: this.rollResult, - trait_label: typeof this.rollTrait === "number" ? `${this.rollTrait} Dice` : U.tCase(this.rollTrait), - dieVals: this.dieValsHTML - }); - break; - } - case RollType.Resistance: { - renderedHTML = await renderTemplate("systems/eunos-blades/templates/chat/resistance-roll.hbs", { - dieVals: this.dieValsHTML, - result: this.rollResult, - trait_label: typeof this.rollTrait === "number" ? `${this.rollTrait} Dice` : U.tCase(this.rollTrait), - stress: this.resistanceStressCost - }); - - break; - } - case RollType.Fortune: { - - break; - } - case RollType.IndulgeVice: { - - break; - } - default: throw new Error(`Unrecognized RollType: ${this.rollType}`); - } - - const messageData = { - speaker, - content: renderedHTML, - type: CONST.CHAT_MESSAGE_TYPES.ROLL, - roll: this.roll - }; - - CONFIG.ChatMessage.documentClass.create(messageData, {}); + BladesChat.ConstructRollOutput(this); } async resolveRoll() { @@ -3260,6 +3208,29 @@ class BladesRoll extends DocumentSheet { await this.document.setFlag(C.SYSTEM_ID, target, value).then(() => socketlib.system.executeForEveryone("renderRollCollab", this.rollID)); } + async _gmControlCycleTarget(event: ClickEvent) { + event.preventDefault(); + if (!game.user.isGM) { return; } + const elem$ = $(event.currentTarget); + const flagTarget = elem$.data("flagTarget"); + const curVal = elem$.data("curVal"); + const cycleVals = elem$.data("vals")?.split(/\|/); + if (!cycleVals) { + throw new Error(`Unable to parse cycle values from data-vals = ${elem$.data("vals")}`); + } + const curValIndex = cycleVals.indexOf(curVal); + if (curValIndex === -1) { + throw new Error(`Unable to find current value '${curVal}' in cycle values '${elem$.data("vals")}'`); + } + let newValIndex = curValIndex + 1; + if (newValIndex >= cycleVals.length) { + newValIndex = 0; + } + const newVal = cycleVals[newValIndex]; + eLog.checkLog3("gmControlCycleTarget", "gmControlCycleTarget", {flagTarget, curVal, cycleVals, curValIndex, newValIndex, newVal}); + await this.setFlagVal(flagTarget, newVal); + } + /** * Handles resetting value associated with GM number line on a right-click. * @param {ClickEvent} event JQuery context menu event sent to listener. @@ -3487,6 +3458,13 @@ class BladesRoll extends DocumentSheet { click: this._gmControlSetTargetToValue.bind(this), contextmenu: this._gmControlResetTarget.bind(this) }); + /** + * Handles setting values via GM number line (e.g. roll factor boosts/modifications). + * Handles resetting value associated with GM number line on a right-click. + */ + html.find("[data-action=\"gm-cycle-target\"]").on({ + click: this._gmControlCycleTarget.bind(this) + }); /** * Handles setting of Factor toggles: isActive, isPrimary, highFavorsPC, isDominant */ diff --git a/ts/blades.ts b/ts/blades.ts index f2d85df6..6e5a717f 100644 --- a/ts/blades.ts +++ b/ts/blades.ts @@ -3,6 +3,7 @@ import C, {ActionTrait, AttributeTrait, RollType, ConsequenceType, Position, Rol import registerSettings, {initTinyMCEStyles, initCanvasStyles} from "./core/settings"; import {registerHandlebarHelpers, preloadHandlebarsTemplates} from "./core/helpers"; import BladesPushAlert from "./BladesPushAlert"; +import BladesChat from "./BladesChat"; import U from "./core/utilities"; import logger from "./core/logger"; import G, {Initialize as GsapInitialize} from "./core/gsap"; @@ -73,6 +74,7 @@ class GlobalGetter { 0: { type: ConsequenceType.ProwessHarm2, attribute: AttributeTrait.prowess, + attributeVal: 3, name: "Broken Leg", resistOptions: { 0: { @@ -107,47 +109,179 @@ class GlobalGetter { isSelected: true, type: ConsequenceType.None, icon: C.ConsequenceIcons[ConsequenceType.None], + footerMsg: "If vs. Physical Harm", typeDisplay: "" }, icon: C.ConsequenceIcons[ConsequenceType.ProwessHarm2], typeDisplay: C.ConsequenceDisplay[ConsequenceType.ProwessHarm2] + }, + 1: { + type: ConsequenceType.ReducedEffect, + attribute: AttributeTrait.insight, + attributeVal: 4, + name: "You Lose Your Footing", + /* "resistOptions": { + "0": { + "name": "Stumble", + "isSelected": false + }, + "1": { + "name": "Trip", + "isSelected": false + }, + "2": { + "name": "", + "type": "None", + "isSelected": true + } + }, */ + resistedTo: { + name: "", + type: ConsequenceType.None, + isSelected: true + }, + icon: "main", + typeDisplay: "Reduced Effect" + }, + 2: { + type: ConsequenceType.ResolveHarm1, + attribute: AttributeTrait.resolve, + name: "Traumatic Flashbacks", + resistOptions: { + 0: { + name: "", + type: ConsequenceType.None, + isSelected: true + }, + 1: { + name: "", + type: ConsequenceType.None, + isSelected: false + }, + 2: { + name: "", + type: ConsequenceType.None, + isSelected: false + } + }, + resistedTo: { + name: "", + type: ConsequenceType.None, + isSelected: true + }, + icon: "spikes|eyeball|iris", + typeDisplay: "Level 1 Harm (Lesser)", + attributeVal: 4 } }, [RollResult.fail]: { 0: { - type: ConsequenceType.ProwessHarm2, + type: ConsequenceType.WorsePosition, + attribute: AttributeTrait.resolve, + attributeVal: 4, + name: "Time To Regroup", + resistOptions: { + 0: { + name: "Time to Rest and Recuperate", + isSelected: false + }, + 1: { + name: "Time to Reflect and Reevaluate", + isSelected: false + }, + 2: { + name: "Time to Reorganize and Strategize", + isSelected: false + } + }, + resistedTo: { + name: "", + type: ConsequenceType.None, + isSelected: true + }, + icon: "horizon|boot|ice", + typeDisplay: "Worse Position" + }, + 1: { + type: ConsequenceType.ComplicationMajor, attribute: AttributeTrait.prowess, - name: "Broken Leg", + name: "Your pick snaps off inside the lock.", resistOptions: { 0: { - name: "Sprained Ankle", - isSelected: true, - type: ConsequenceType.ProwessHarm1, - icon: C.ConsequenceIcons[ConsequenceType.ProwessHarm1], - typeDisplay: C.ConsequenceDisplay[ConsequenceType.ProwessHarm1] + name: "Lock remains intact but jammed", + isSelected: false, + type: ConsequenceType.ComplicationMinor, + icon: "main" }, 1: { - name: "Bruised Leg", + name: "Pick breaks, but lock is still pickable", + isSelected: true, + type: ConsequenceType.ComplicationMinor, + icon: "main", + typeDisplay: "Minor Complication" + }, + 2: { + name: "You manage to extract the broken pick from the lock.", isSelected: false, - type: ConsequenceType.ProwessHarm1, - icon: C.ConsequenceIcons[ConsequenceType.ProwessHarm1] + type: ConsequenceType.ComplicationMinor, + icon: "main" + } + }, + resistedTo: { + name: "Pick breaks, but lock is still pickable", + isSelected: true, + type: ConsequenceType.ComplicationMinor, + icon: "main", + typeDisplay: "Minor Complication" + }, + attributeVal: 3, + icon: "main", + typeDisplay: "Major Complication" + }, + 2: { + type: ConsequenceType.InsightHarm2, + attribute: AttributeTrait.insight, + name: "Completely Misled", + resistOptions: { + 0: { + name: "Partially Misinformed", + isSelected: false, + type: ConsequenceType.InsightHarm1, + icon: "eye|iris", + typeDisplay: "Level 1 Harm (Lesser)" + }, + 1: { + name: "Confused by Deception", + isSelected: true, + type: ConsequenceType.InsightHarm1, + icon: "eye|iris", + typeDisplay: "Level 1 Harm (Lesser)" }, 2: { - name: "Fractured Foot", + name: "Given Partially Incorrect Information", isSelected: false, - type: ConsequenceType.ProwessHarm1, - icon: C.ConsequenceIcons[ConsequenceType.ProwessHarm1] + type: ConsequenceType.InsightHarm1, + icon: "eye|iris" } }, resistedTo: { + name: "Confused by Deception", + isSelected: true, + type: ConsequenceType.InsightHarm1, + typeDisplay: "Level 1 Harm (Lesser)", + icon: "eye|iris" + }, + specialArmorTo: { name: "Sprained Ankle", isSelected: true, - type: ConsequenceType.ProwessHarm1, - typeDisplay: C.ConsequenceDisplay[ConsequenceType.ProwessHarm1], - icon: C.ConsequenceIcons[ConsequenceType.ProwessHarm1] + type: ConsequenceType.InsightHarm1, + footerMsg: "If vs. Supernatural, Arcane Forces", + icon: C.ConsequenceIcons[ConsequenceType.InsightHarm1], + typeDisplay: "Level 1 Harm (Lesser)" }, - icon: C.ConsequenceIcons[ConsequenceType.ProwessHarm2], - typeDisplay: C.ConsequenceDisplay[ConsequenceType.ProwessHarm2] + icon: "eye|iris", + typeDisplay: "Level 2 Harm (Moderate)", + attributeVal: 4 } } } @@ -220,6 +354,7 @@ class GlobalGetter { BladesRollPrimary, BladesRollOpposition, BladesRollParticipant, + BladesChat, G, U, C, @@ -247,6 +382,7 @@ Hooks.once("init", async () => { CONFIG.Item.documentClass = BladesItemProxy as unknown as typeof Item; CONFIG.Actor.documentClass = BladesActorProxy as unknown as typeof Actor; + CONFIG.ChatMessage.documentClass = BladesChat; // Register sheet application classes Actors.unregisterSheet("core", ActorSheet); diff --git a/ts/core/constants.ts b/ts/core/constants.ts index c07b9d61..a97a52b5 100644 --- a/ts/core/constants.ts +++ b/ts/core/constants.ts @@ -504,30 +504,50 @@ const C = { [ConsequenceType.ResolveHarm4]: "spikes|eyeball|iris", [ConsequenceType.None]: "" }, + RollResultDescriptions: { + [Position.controlled]: { + [RollResult.critical]: "You critically succeed from a controlled position!", + [RollResult.success]: "You fully succeed from a controlled position!", + [RollResult.partial]: "You partially succeed from a controlled position!", + [RollResult.fail]: "You fail from a controlled position!" + }, + [Position.risky]: { + [RollResult.critical]: "You critically succeed from a risky position!", + [RollResult.success]: "You fully succeed from a risky position!", + [RollResult.partial]: "You partially succeed from a risky position!", + [RollResult.fail]: "You fail from a risky position!" + }, + [Position.desperate]: { + [RollResult.critical]: "You critically succeed from a desperate position!", + [RollResult.success]: "You fully succeed from a desperate position!", + [RollResult.partial]: "You partially succeed from a desperate position!", + [RollResult.fail]: "You fail from a desperate position!" + } + }, // Colors: { bWHITE: "rgba(255, 255, 255, 1)", WHITE: "rgba(200, 200, 200, 1)", bGREY: "rgba(170, 170, 170, 1)", - GREY: "rgba(128, 128, 128, 1)", - dGREY: "rgba(78, 78, 78, 1)", - BLACK: "rgba(16, 16, 16, 1)", + GREY: "rgba(119, 119, 119, 1)", + dGREY: "rgba(68, 68, 68, 1)", + BLACK: "rgba(32, 32, 32, 1)", dBLACK: "rgba(0, 0, 0, 1)", - gGOLD: "rgba(255, 254, 200, 1)", - bGOLD: "rgba(171, 146, 84, 1)", - GOLD: "rgba(253, 212, 112, 1)", - dGOLD: "rgba(65, 61, 46, 1)", + bGOLD: "rgba(255,216, 44, 1)", + GOLD: "rgba(215,175, 0, 1)", + dGOLD: "rgba(165,134, 0, 1)", + ddGOLD: "rgba(103, 83, 0, 1)", - RED: "rgba(155, 32, 32, 1)", - dRED: "rgba(70, 14, 14, 1)", - bRED: "rgba(240, 50, 50, 1)", - gRED: "rgba(255, 0, 0, 1)", + bRED: "rgba(255, 0, 0, 1)", + RED: "rgba(200, 0, 0, 1)", + dRED: "rgba(150, 0, 0, 1)", + ddRED: "rgba(50, 0, 0, 1)", - BLUE: "rgba(43, 85, 139, 1)", - dBLUE: "rgba(17, 33, 54, 1)", - bBLUE: "rgba(69, 137, 224, 1)", - gBLUE: "rgba(128, 185, 255, 1)" + bBLUE: "rgba( 0,224,224, 1)", + BLUE: "rgba(52,213,213, 1)", + dBLUE: "rgba(0,118,118, 1)", + ddBLUE: "rgba(0, 77, 77, 1)" }, Loadout: { selections: [ diff --git a/ts/core/gsap.ts b/ts/core/gsap.ts index dfccf7be..b8e4fe76 100644 --- a/ts/core/gsap.ts +++ b/ts/core/gsap.ts @@ -1,4 +1,5 @@ import U from "./utilities"; +import C from "./constants"; // eslint-disable-next-line import/no-unresolved import {TextPlugin} from "gsap/all"; @@ -18,6 +19,515 @@ type gsapEffect = { } const gsapEffects: Record = { + csqEnter: { + effect: (csqContainer: HTMLElement, config) => { + const csqRoot = U.gsap.utils.selector(csqContainer); + // ELog.checkLog3("gsap", "gsapEffects.consequenceEnter -> THIS", {this: this, csqRoot}); + const csqIconCircle = csqRoot(".consequence-icon-circle.base-consequence"); + const csqBaseElems = csqRoot(".base-consequence:not(.consequence-icon-circle)"); + const csqAcceptElems = csqRoot(".accept-consequence:not(.consequence-icon-circle):not(.consequence-button-container)"); + + const tl = U.gsap.timeline({paused: true}); + + // Fade out base-consequence components + tl.fromTo(csqBaseElems, { + opacity: 1 + }, { + opacity: 0, + duration: config.duration / 3, + ease: "none" + }, 0); + + // Fade in accept-consequence components + tl.fromTo(csqAcceptElems, { + opacity: 0 + }, { + opacity: 1, + duration: config.duration / 3, + ease: "none" + }, 0); + + // Brighten the entire container slightly + tl.fromTo(csqContainer, { + filter: "brightness(1)" + }, { + filter: `brightness(${config.brightness})`, + duration: config.duration / 3, + ease: "none" + }, 0); + + // Enlarge the icon circle, add stroke + tl.fromTo(csqIconCircle, { + xPercent: -50, + yPercent: -50, + scale: 0.75, + outlineColor: C.Colors.dBLACK, + outlineWidth: 0 + }, { + xPercent: -50, + yPercent: -50, + scale: 0.85, + outlineColor: C.Colors.GREY, + outlineWidth: 1, + duration: 0.55, + ease: "sine.out" + }, 0); + + return tl; + }, + defaults: { + brightness: 1.5, + duration: 0.5, + scale: 1.5, + stagger: 0.05, + ease: "sine", + easeStrength: 1.5 + } + }, + csqClickIcon: { + effect: (csqIconContainer: HTMLElement, config) => { + const csqRoot = U.gsap.utils.selector(csqIconContainer); + const csqInteractionPads = csqRoot(".consequence-interaction-pad"); + const csqIconCircleBase = csqRoot(".consequence-icon-circle.base-consequence"); + const csqIconCircleAccept = csqRoot(".consequence-icon-circle.accept-consequence"); + const csqButtonContainers = csqRoot(".consequence-button-container"); + + const tl = U.gsap.timeline({paused: true}); + + // Initialize interaction pads to display: none + if (csqInteractionPads.length) { + tl.set(csqInteractionPads, {display: "none"}); + } + + // Fade out the base consequence icon circle + tl.fromTo(csqIconCircleBase, { + opacity: 1 + }, { + opacity: 0, + duration: 0.25, + ease: "none" + }, 0); + + // Fade in the accept consequence icon circle, enlarging the stroke + tl.fromTo(csqIconCircleAccept, { + opacity: 0, + xPercent: -50, + yPercent: -50, + scale: 0.85 + }, { + opacity: 1, + xPercent: -50, + yPercent: -50, + duration: 0.15, + ease: "sine" + }, 0); + + tl.fromTo(csqIconCircleAccept, { + outlineWidth: 1, + xPercent: -50, + yPercent: -50, + scale: 0.85 + }, { + scale: 1, + xPercent: -50, + yPercent: -50, + outlineWidth: 2, + duration: 0.25, + ease: "sine" + }, 0.175); + + // Scale and fade in the button containers + tl.fromTo(csqButtonContainers, { + scale: config.scale, + opacity: 0, + filter: "blur(25px)" + }, { + scale: 1, + opacity: 1, + filter: "blur(0px)", + stagger: config.stagger, + duration: config.duration, + ease: `${config.ease}.inOut(${config.easeStrength})` + }, 0); + + // Finally, toggle on interaction pads + if (csqInteractionPads.length) { + tl.set(csqInteractionPads, {display: "block"}); + } + + return tl; + }, + defaults: { + duration: 0.5, + scale: 1.5, + stagger: 0.05, + ease: "sine", + easeStrength: 1.5 + } + }, + csqEnterAccept: { + effect: (csqContainer: HTMLElement) => { + const csqRoot = U.gsap.utils.selector(csqContainer); + const typeLine = csqRoot(".consequence-type-container .consequence-type.accept-consequence"); + const typeLineBg = csqRoot(".consequence-type-container .consequence-type-bg.accept-consequence"); + const buttonRoot = U.gsap.utils.selector(csqRoot(".consequence-button-container.consequence-accept-button-container")); + + const buttonBg = buttonRoot(".consequence-button-bg"); + const buttonIcon = buttonRoot(".button-icon i"); + const buttonLabel = buttonRoot(".consequence-button-label"); + + const tl = U.gsap.timeline({paused: true}); + + // Turn type line white + tl.fromTo(typeLine, + { + color: C.Colors.RED + }, + { + color: C.Colors.WHITE, + duration: 0.5, + ease: "sine.inOut" + }, 0); + + // Slide type line background out from under icon + tl.fromTo(typeLineBg, { + x: 5, + scaleX: 0, + color: C.Colors.RED, + skewX: 0 + }, { + scaleX: 1, + skewX: -45, + color: C.Colors.RED, + duration: 0.5, + ease: "back.out" + }, 0); + + // Slide accept button background out from under icon + tl.fromTo(buttonBg, { + scaleX: 0, + color: C.Colors.RED, + skewX: 0 + }, { + x: 0, + scaleX: 1, + skewX: -45, + color: C.Colors.RED, + duration: 0.25, + ease: "back.out" + }, 0); + + // Turn button icon black and scale + tl.fromTo(buttonIcon, + { + color: C.Colors.GREY, + opacity: 0.75, + scale: 1 + }, + { + color: C.Colors.dBLACK, + scale: 1.25, + opacity: 1, + duration: 0.5, + ease: "sine" + }, 0); + + // Turn button label black, add letter-spacing, bold + tl.fromTo(buttonLabel, + { + color: C.Colors.GREY, + fontWeight: 400, + scale: 1 + }, + { + color: C.Colors.dBLACK, + fontWeight: 800, + duration: 0.75, + ease: "sine" + }, 0); + + return tl; + }, + defaults: {} + }, + csqEnterResist: { + effect: (csqContainer: HTMLElement) => { + const csqRoot = U.gsap.utils.selector(csqContainer); + + const typeLine = csqRoot(".consequence-type-container .consequence-type.resist-consequence"); + + const acceptElems = csqRoot(".accept-consequence"); + const specialArmorElems = csqRoot(".special-armor-consequence"); + + const footerBg = csqRoot(".consequence-footer-container .consequence-footer-bg.resist-consequence"); + const attrElem = csqRoot(".consequence-footer-container .consequence-resist-attribute"); + const resistCsqName = csqRoot(".consequence-name.resist-consequence"); + const iconCircle = csqRoot(".consequence-icon-circle.resist-consequence"); + + const buttonRoot = U.gsap.utils.selector(csqRoot(".consequence-button-container.consequence-resist-button-container")); + + const buttonBg = buttonRoot(".consequence-button-bg"); + const buttonIcon = buttonRoot(".button-icon i"); + const buttonLabel = buttonRoot(".consequence-button-label"); + + const tl = U.gsap.timeline({paused: true}); + + // Fade out all accept elems and special armor elems + tl.to([...acceptElems, ...specialArmorElems], { + opacity: 0, + duration: 0.25, + ease: "sine.out" + }); + + if (typeLine.length) { + // Slide out .consequence-type.resist-consequence from left + tl.fromTo(typeLine, { + x: -15, + scaleX: 0, + opacity: 1, + color: C.Colors.dGOLD + }, { + x: 0, + scaleX: 1, + opacity: 1, + color: C.Colors.dGOLD, + duration: 0.5, + ease: "back.out" + }, 0); + } + + // Slide out .consequence-resist-button-bg from right + tl.fromTo(buttonBg, { + scaleX: 0, + skewX: 0, + opacity: 1 + }, { + scaleX: 1, + skewX: -45, + opacity: 1, + duration: 0.5, + ease: "back.inOut" + }, 0); + + // Slide out .consequence-footer-bg.resist-consequence from left + tl.fromTo(footerBg, { + scaleX: 0, + skewX: 0, + opacity: 1 + }, { + scaleX: 1, + skewX: -45, + opacity: 1, + duration: 0.5, + ease: "back.inOut" + }, 0); + + // Slide out .consequence-resist-attribute from left + tl.fromTo(attrElem, { + scaleX: 0, + opacity: 1 + }, { + scaleX: 1, + opacity: 1, + duration: 0.5, + ease: "back.inOut" + }, 0); + + // Slide out .consequence-name.resist-consequence from left + tl.fromTo(resistCsqName, { + scaleX: 0, + opacity: 1 + }, { + scaleX: 1, + opacity: 1, + duration: 0.5, + ease: "back.inOut" + }, 0); + + // Fade in .consequence-icon-circle.resist-consequence + tl.fromTo(iconCircle, { + opacity: 0 + }, { + opacity: 1, + duration: 0.5, + ease: "back.out" + }, 0); + + // Turn button icon black and scale + tl.fromTo(buttonIcon, + { + color: C.Colors.GREY, + opacity: 0.75, + scale: 1 + }, + { + color: C.Colors.dBLACK, + scale: 1.25, + opacity: 1, + duration: 0.5, + ease: "sine" + }, 0); + + // Turn button label black, bold + tl.fromTo(buttonLabel, + { + color: C.Colors.GREY, + fontWeight: 400, + scale: 1 + }, + { + color: C.Colors.dBLACK, + fontWeight: 800, + duration: 0.75, + ease: "sine" + }, 0); + + return tl; + }, + defaults: {} + }, + csqEnterSpecialArmor: { + effect: (csqContainer: HTMLElement) => { + const csqRoot = U.gsap.utils.selector(csqContainer); + + const typeLine = csqRoot(".consequence-type-container .consequence-type.special-armor-consequence"); + + const acceptElems = csqRoot(".accept-consequence"); + const resistElems = csqRoot(".resist-consequence"); + + const footerBg = csqRoot(".consequence-footer-container .consequence-footer-bg.special-armor-consequence"); + const footerMsg = csqRoot(".consequence-footer-container .consequence-special-armor-message"); + const specialArmorCsqName = csqRoot(".consequence-name.special-armor-consequence"); + const iconCircle = csqRoot(".consequence-icon-circle.special-armor-consequence"); + + const buttonRoot = U.gsap.utils.selector(csqRoot(".consequence-button-container.consequence-special-armor-button-container")); + + const buttonBg = buttonRoot(".consequence-button-bg"); + const buttonIcon = buttonRoot(".button-icon i"); + const buttonLabel = buttonRoot(".consequence-button-label"); + + const tl = U.gsap.timeline({paused: true}); + + // Fade out all accept elems and resist elems + tl.to([...acceptElems, ...resistElems], { + opacity: 0, + duration: 0.25, + ease: "sine.out" + }); + + if (typeLine) { + // Slide out .consequence-type.special-armor-consequence from left + tl.fromTo(typeLine, { + x: -15, + scaleX: 0, + opacity: 1, + color: C.Colors.dBLUE + }, { + x: 0, + scaleX: 1, + opacity: 1, + color: C.Colors.dBLUE, + duration: 0.5, + ease: "back.out" + }, 0); + } + + // Slide out .consequence-special-armor-button-bg from right + tl.fromTo(buttonBg, { + scaleX: 0, + skewX: 0, + opacity: 1 + }, { + scaleX: 1, + skewX: -45, + opacity: 1, + duration: 0.5, + ease: "back.inOut" + }, 0); + + if (footerBg) { + // Slide out .consequence-footer-bg.special-armor-consequence from left + tl.fromTo(footerBg, { + scaleX: 0, + skewX: 0, + opacity: 1 + }, { + scaleX: 1, + skewX: -45, + opacity: 1, + duration: 0.5, + ease: "back.inOut" + }, 0); + } + + // Slide out .consequence-special-armor-message from left + if (footerMsg) { + tl.fromTo(footerMsg, { + scaleX: 0, + opacity: 1 + }, { + scaleX: 1, + opacity: 1, + duration: 0.5, + ease: "back.inOut" + }, 0); + } + + // Slide out .consequence-name.special-armor-consequence from left + if (specialArmorCsqName) { + tl.fromTo(specialArmorCsqName, { + scaleX: 0, + opacity: 1 + }, { + scaleX: 1, + opacity: 1, + duration: 0.5, + ease: "back.inOut" + }, 0); + } + + // Fade in .consequence-icon-circle.special-armor-consequence + tl.fromTo(iconCircle, { + opacity: 0 + }, { + opacity: 1, + duration: 0.5, + ease: "back.out" + }, 0); + + // Turn button icon black and scale + tl.fromTo(buttonIcon, + { + color: C.Colors.GREY, + opacity: 0.75, + scale: 1 + }, + { + color: C.Colors.dBLACK, + scale: 1.25, + opacity: 1, + duration: 0.5, + ease: "sine" + }, 0); + + // Turn button label black, bold + tl.fromTo(buttonLabel, + { + color: C.Colors.GREY, + fontWeight: 400, + scale: 1 + }, + { + color: C.Colors.dBLACK, + fontWeight: 800, + duration: 0.75, + ease: "sine" + }, 0); + + return tl; + }, + defaults: {} + }, blurRemove: { effect: (targets, config) => U.gsap.timeline() .to( @@ -119,11 +629,11 @@ const gsapEffects: Record = { extendTimeline: true }, pulseClockWedges: { - effect: (targets, config) => U.gsap.timeline({duration: 0}), + effect: () => U.gsap.timeline({duration: 0}), defaults: {} }, reversePulseClockWedges: { - effect: (targets, config) => U.gsap.timeline({duration: 0}), + effect: () => U.gsap.timeline({duration: 0}), defaults: {} }, fillCoins: { @@ -155,7 +665,7 @@ const gsapEffects: Record = { { scale: "+=0.2", filter: "none", - color: "rgba(255, 255, 255, 1)", + color: C.Colors.WHITE, opacity: 1, duration: 0.125, ease: "back" @@ -194,7 +704,7 @@ const gsapEffects: Record = { }; /** - * + * Registers relevant GSAP plugins and effects. */ export function Initialize() { if (gsapPlugins.length) { @@ -206,8 +716,8 @@ export function Initialize() { } /** - * - * @param html + * Applies listeners to '.tooltip-trigger' elements in the document. + * @param {JQuery} html The document to be searched. */ export function ApplyTooltipListeners(html: JQuery) { html.find(".tooltip-trigger").each((_, el) => { @@ -236,8 +746,8 @@ export function ApplyTooltipListeners(html: JQuery) { } /** - * - * @param html + * Applies listeners to .consequence-display-container and children found in document. + * @param {JQuery} html The document to be searched. */ export function ApplyConsequenceListeners(html: JQuery) { /** @@ -267,135 +777,113 @@ export function ApplyConsequenceListeners(html: JQuery) { * and don't slide the resistance ones out at all. * */ - html .find(".comp.consequence-display-container") .each((_i, csqContainer) => { - const resistButton = $(csqContainer).find(".consequence-resist-button-container")[0]; - const acceptButton = $(csqContainer).find(".consequence-accept-button-container")[0]; - const specialArmorButton = $(csqContainer).find(".consequence-special-armor-button-container")[0]; - const baseElems = Array.from($(csqContainer).find(".base-consequence")); - const resistElems = Array.from($(csqContainer).find(".resist-consequence")); - const specialArmorElems = Array.from($(csqContainer).find(".special-armor-consequence")); - if (resistButton) { - $(resistButton) - .on({ - mouseenter: function() { - $(resistButton).css("z-index", 10); - U.gsap.to(resistButton, { - scale: 1.25, - filter: "brightness(1.25)", - // xPercent: -50, - // yPercent: -50, - duration: 0.25, - ease: "sine.out" - }); - U.gsap.to(acceptButton, { - scale: 0.8, - filter: "greyscale(1)", - // xPercent: -50, - // yPercent: -50, - duration: 0.5, - ease: "sine.inOut" - }); - U.gsap.to(specialArmorButton, { - scale: 0.8, - filter: "greyscale(1)", - // xPercent: -50, - // yPercent: -50, - duration: 0.5, - ease: "sine.inOut" - }); - U.gsap.to(baseElems, { - opacity: 0, - duration: 0.5, - ease: "sine.out" - }); - U.gsap.to(resistElems, { - opacity: 1, - duration: 0.5, - ease: "sine.out" - }); - }, - mouseleave: function() { - U.gsap.to(resistButton, { - scale: 1, - filter: "brightness(1)", - duration: 0.25, - // xPercent: -50, - // yPercent: -50, - ease: "sine.out" - }).then(() => { - $(resistButton).css("z-index", ""); - }); - U.gsap.to(acceptButton, { - scale: 1, - filter: "", - // xPercent: -50, - // yPercent: -50, - duration: 0.5, - ease: "sine.inOut" - }); - U.gsap.to(specialArmorButton, { - scale: 1, - filter: "", - // xPercent: -50, - // yPercent: -50, - duration: 0.5, - ease: "sine.inOut" - }); - U.gsap.to(baseElems, { - opacity: 1, - duration: 0.5, - ease: "sine.out" - }); - U.gsap.to(resistElems, { - opacity: 0, - duration: 0.5, - ease: "sine.out" - }); - } - }); - } - if (acceptButton) { - $(acceptButton) - .on({ - mouseenter: function() { - $(acceptButton).css("z-index", 10); - U.gsap.to(acceptButton, { - scale: 1.25, - // xPercent: -50, - // yPercent: -50, - filter: "brightness(1.25)", - duration: 0.25, - ease: "sine.out" - }); - U.gsap.to(baseElems, { - filter: "brightness(1.25)", - duration: 0.5, - ease: "sine.out" - }); - }, - mouseleave: function() { - U.gsap.to(acceptButton, { - scale: 1, - filter: "brightness(1)", - // xPercent: -50, - // yPercent: -50, - duration: 0.25, - ease: "sine.out" - }).then(() => { - $(acceptButton).css("z-index", ""); - }); - U.gsap.to(baseElems, { - filter: "brightness(1)", - duration: 0.5, - ease: "sine.out" + + const iconContainer$ = $(csqContainer).find(".consequence-icon-container"); + + const acceptInteractionPad$ = $(csqContainer).find(".accept-consequence-pad"); + const resistInteractionPad$ = $(csqContainer).find(".resist-consequence-pad"); + const specialArmorInteractionPad$ = $(csqContainer).find(".special-armor-consequence-pad"); + + $(csqContainer).data("hoverTimeline", U.gsap.effects.csqEnter(csqContainer)); + $(csqContainer).on({ + mouseenter: function() { + $(csqContainer).css("z-index", 10); + $(csqContainer).data("hoverTimeline").play(); + }, + mouseleave: function() { + if (!(iconContainer$.data("isToggled") || iconContainer$.data("isTogglingOn")) || iconContainer$.data("isTogglingOff")) { + $(csqContainer).data("hoverTimeline").reverse().then(() => { + $(csqContainer).css("z-index", ""); + }); + } + } + }); + + iconContainer$.data("clickTimeline", U.gsap.effects.csqClickIcon(iconContainer$[0])); + iconContainer$.on({ + click: function() { + if (iconContainer$.data("isToggled") || iconContainer$.data("isTogglingOn")) { + iconContainer$.data("isTogglingOn", false); + iconContainer$.data("isTogglingOff", true); + iconContainer$.data("clickTimeline").reverse().then(() => { + iconContainer$.data("isTogglingOff", false); + iconContainer$.data("isToggled", false); + }); + } else { + iconContainer$.data("isTogglingOn", true); + iconContainer$.data("isTogglingOff", false); + + // Find any siblings with toggled-on iconContainers, and toggle them off + Array.from($(csqContainer).siblings(".consequence-display-container")) + .forEach((containerElem) => { + const iContainer$ = $(containerElem).find(".consequence-icon-container"); + if (iContainer$?.data("isToggled") || iContainer$?.data("isTogglingOn")) { + iContainer$.data("isTogglingOn", false); + iContainer$.data("isTogglingOff", true); + iContainer$.data("clickTimeline").reverse().then(() => { + iContainer$.data("isTogglingOff", false); + iContainer$.data("isToggled", false); + $(containerElem).data("hoverTimeline").reverse().then(() => { + $(containerElem).css("z-index", ""); + }); + }); + } }); - } - }); - } + iconContainer$.data("clickTimeline").play().then(() => { + iconContainer$.data("isTogglingOn", false); + iconContainer$.data("isToggled", true); + }); + } + } + }); + + acceptInteractionPad$.data("hoverTimeline", U.gsap.effects.csqEnterAccept(csqContainer)); + acceptInteractionPad$.on({ + mouseenter: function() { + if (iconContainer$.data("isToggled")) { + acceptInteractionPad$.data("hoverTimeline").play(); + } + }, + mouseleave: function() { + acceptInteractionPad$.data("hoverTimeline").reverse(); + } + }); + + resistInteractionPad$.data("hoverTimeline", U.gsap.effects.csqEnterResist(csqContainer)); + resistInteractionPad$.on({ + mouseenter: function() { + if (iconContainer$.data("isToggled")) { + resistInteractionPad$.data("hoverTimeline").play(); + } + }, + mouseleave: function() { + if (iconContainer$.data("isToggled")) { + resistInteractionPad$.data("hoverTimeline").reverse(); + } + } + }); + + specialArmorInteractionPad$.data("hoverTimeline", U.gsap.effects.csqEnterSpecialArmor(csqContainer)); + specialArmorInteractionPad$.on({ + mouseenter: function() { + if (iconContainer$.data("isToggled")) { + specialArmorInteractionPad$.data("hoverTimeline").play(); + } + }, + mouseleave: function() { + if (iconContainer$.data("isToggled")) { + specialArmorInteractionPad$.data("hoverTimeline").reverse(); + } + } + }); + + }); + + } export default U.gsap; diff --git a/ts/core/logger.ts b/ts/core/logger.ts index b8bf9783..cf878c70 100644 --- a/ts/core/logger.ts +++ b/ts/core/logger.ts @@ -13,24 +13,24 @@ const LOGGERCONFIG = { const STYLES = { base: { background: C.Colors.BLACK, - color: C.Colors.bGOLD, + color: C.Colors.dGOLD, "font-family": "Pragmata Pro", padding: "0 25px", "margin-right": "25px" }, log0: { - background: C.Colors.bGOLD, + background: C.Colors.dGOLD, color: C.Colors.dBLACK, "font-size": "16px" }, log1: { background: C.Colors.dBLACK, - color: C.Colors.gGOLD, + color: C.Colors.bGOLD, "font-size": "16px" }, log2: { background: C.Colors.dBLACK, - color: C.Colors.bGOLD, + color: C.Colors.dGOLD, "font-size": "16px" }, log3: { @@ -45,15 +45,15 @@ const STYLES = { "font-size": "10px" }, display: { - color: C.Colors.gGOLD, + color: C.Colors.bGOLD, "font-family": "Kirsty Rg", "font-size": "16px", "margin-left": "-100px", padding: "0 100px" }, error: { - color: C.Colors.gRED, - background: C.Colors.dRED, + color: C.Colors.bRED, + background: C.Colors.ddRED, "font-weight": 500 }, handlebars: { @@ -136,7 +136,7 @@ const eLogger = (type: "checkLog"|"log"|KeyOf = "base", ...conten }); } if (stackTrace) { - console.group("%cSTACK TRACE", `color: ${C.Colors.bGOLD}; font-family: "Pragmata Pro"; font-size: 12px; background: ${C.Colors.BLACK}; font-weight: bold; padding: 0 10px;`); + console.group("%cSTACK TRACE", `color: ${C.Colors.dGOLD}; font-family: "Pragmata Pro"; font-size: 12px; background: ${C.Colors.BLACK}; font-weight: bold; padding: 0 10px;`); console.log(`%c${stackTrace}`, Object.entries(STYLES.stack).map(([prop, val]) => `${prop}: ${val};`).join(" ")); console.groupEnd(); }