diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..cc4f7d5 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +packs/** binary \ No newline at end of file diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml index 62024ea..0ca3ea6 100644 --- a/.github/workflows/draft-release.yml +++ b/.github/workflows/draft-release.yml @@ -35,6 +35,7 @@ jobs: # Compile the stuff that needs to be compiled - run: npm run build + - run: node scripts/buildCompendia.mjs - name: Move system.json to a temp file id: manifest-move diff --git a/.gitignore b/.gitignore index cf6fbe1..4beb05c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,18 @@ node_modules/ *.tmp *.bak.* -references.txt \ No newline at end of file +references.txt +references/ +/.*/ +!/.vscode/ +!/.github/ +/*.ref.* +*.lock +*.zip + +# Ignore all of the binaries and stuff that gets built for Foundry from the raw +# JSON data because it's annoying seeing it in my git changes when it isn't actually +# needed. +/packs/**/* +!/packs/**/*/ +!/packs/**/*.json \ No newline at end of file diff --git a/.styles/generic.css b/.styles/generic.css deleted file mode 100644 index b7e854d..0000000 --- a/.styles/generic.css +++ /dev/null @@ -1,69 +0,0 @@ -@import url("https://fonts.googleapis.com/css2?family=Pixelify+Sans&display=swap"); -.dotdungeon > .window-content ::-webkit-scrollbar { - width: 10px; -} -.dotdungeon > .window-content ::-webkit-scrollbar-thumb { - border-radius: 5px; -} -.dotdungeon > .window-content h2, .dotdungeon > .window-content h3, .dotdungeon > .window-content h4, .dotdungeon > .window-content h5, .dotdungeon > .window-content h6 { - all: initial; - display: block; - box-sizing: border-box; - font-family: "Pixelify Sans", sans-serif; - margin: 0; -} -.dotdungeon > .window-content label { - cursor: pointer; -} -.dotdungeon > .window-content button, .dotdungeon > .window-content button:hover { - all: initial; - display: block; - box-sizing: border-box; - font-family: inherit; - cursor: pointer; -} -.dotdungeon > .window-content button:disabled, .dotdungeon > .window-content button:hover:disabled { - cursor: default; -} -.dotdungeon > .window-content input[type=text], -.dotdungeon > .window-content input[type=number], -.dotdungeon > .window-content textarea { - padding: 5px 7px; - border-width: 2px; - border-radius: 4px; - border-style: solid; - border-color: rgba(0, 0, 0, 0.4); - background-color: rgba(0, 0, 0, 0.1); - font-family: sans-serif; -} -.dotdungeon > .window-content input[type=text]:focus, .dotdungeon > .window-content input[type=text]:active, -.dotdungeon > .window-content input[type=number]:focus, -.dotdungeon > .window-content input[type=number]:active, -.dotdungeon > .window-content textarea:focus, -.dotdungeon > .window-content textarea:active { - border-color: rgb(0, 0, 0); -} -.dotdungeon > .window-content textarea { - resize: vertical; -} -.dotdungeon > .window-content select, .dotdungeon > .window-content select:hover { - cursor: pointer; -} -.dotdungeon > .window-content select :disabled, .dotdungeon > .window-content select:hover :disabled { - cursor: default; -} - -.dotdungeon.dotdungeon.dotdungeon.dotdungeon { - container-type: size; -} -.dotdungeon.dotdungeon.dotdungeon.dotdungeon > .window-content { - padding: 0; - background: #f2f2f2; -} -.dotdungeon.dotdungeon.dotdungeon.dotdungeon > .window-content .debug-data { - opacity: 60%; - font-family: sans-serif; - word-break: break-all; -} - -/*# sourceMappingURL=data:application/json;charset=utf-8,%7B%22version%22:3,%22sourceRoot%22:%22%22,%22sources%22:%5B%22../styles/generic.scss%22,%22../styles/mixins/_foundry.scss%22,%22../styles/_vars.scss%22,%22../styles/mixins/_partials.scss%22%5D,%22names%22:%5B%5D,%22mappings%22:%22AAIQ;AAKP;EACC;;AAED;EACC;;AAGD;ECfA;EACA;EACA;EDeC,aEhBW;EFiBX;;AAGD;EACC;;AAGD;ECzBA;EACA;EACA;EDyBC;EACA;;AAEA;EACC;;AAIF;AAAA;AAAA;EAGC;EGpCD;EACA;EACA;EACA;EACA;EACA,aDLW;;ACOX;AAAA;AAAA;AAAA;AAAA;EAEC;;AH+BD;EACC;;AAGD;EACC;;AAEA;EACC;;;AAMH;EACC;;AAEA;EACC;EACA,YEzDW;;AF2DX;EACC;EACA,aE/DS;EFgET%22,%22file%22:%22generic.css%22%7D */ diff --git a/.styles/global/buttons.css b/.styles/global/buttons.css deleted file mode 100644 index 97e931c..0000000 --- a/.styles/global/buttons.css +++ /dev/null @@ -1,52 +0,0 @@ -.dotdungeon.dotdungeon.dotdungeon.dotdungeon > .window-content button { - border-radius: 4px; - box-sizing: border-box; - border-style: solid; - border-color: transparent; - border-width: 2px; - border-radius: 4px; - transition: 400ms; - padding: 4px 8px; - display: inline-flex; - justify-content: center; - align-items: center; - gap: 4px; -} -.dotdungeon.dotdungeon.dotdungeon.dotdungeon > .window-content button.confirm { - background: #048A81; - color: white; -} -.dotdungeon.dotdungeon.dotdungeon.dotdungeon > .window-content button.confirm:hover, .dotdungeon.dotdungeon.dotdungeon.dotdungeon > .window-content button.confirm:focus-visible { - background: transparent; - color: #048A81; - border-color: #048A81; -} -.dotdungeon.dotdungeon.dotdungeon.dotdungeon > .window-content button.neutral { - background: #007ACC; - color: white; -} -.dotdungeon.dotdungeon.dotdungeon.dotdungeon > .window-content button.neutral:hover, .dotdungeon.dotdungeon.dotdungeon.dotdungeon > .window-content button.neutral:focus-visible { - background: transparent; - color: #007ACC; - border-color: #007ACC; -} -.dotdungeon.dotdungeon.dotdungeon.dotdungeon > .window-content button.danger { - background: #960200; - color: white; -} -.dotdungeon.dotdungeon.dotdungeon.dotdungeon > .window-content button.danger:hover, .dotdungeon.dotdungeon.dotdungeon.dotdungeon > .window-content button.danger:focus-visible { - background: transparent; - color: #960200; - border-color: #960200; -} -.dotdungeon.dotdungeon.dotdungeon.dotdungeon > .window-content button.reduced-padding { - padding: 2px 4px; -} -.dotdungeon.dotdungeon.dotdungeon.dotdungeon > .window-content button.equal-padding { - padding: 4px 4px; -} -.dotdungeon.dotdungeon.dotdungeon.dotdungeon > .window-content button.equal-padding.reduced-padding { - padding: 2px 2px; -} - -/*# sourceMappingURL=data:application/json;charset=utf-8,%7B%22version%22:3,%22sourceRoot%22:%22%22,%22sources%22:%5B%22../../styles/global/buttons.scss%22,%22../../styles/_vars.scss%22%5D,%22names%22:%5B%5D,%22mappings%22:%22AAGC;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACC,YCXc;EDYd,OCXe;;ADYf;EACC;EACA,OCfa;EDgBb,cChBa;;ADoBf;EACC,YClBc;EDmBd,OClBe;;ADmBf;EACC;EACA,OCtBa;EDuBb,cCvBa;;AD2Bf;EACC,YCzBa;ED0Bb,OCzBc;;AD0Bd;EACC;EACA,OC7BY;ED8BZ,cC9BY;;ADkCd;EACC;;AAID;EACC;;AACA;EACC%22,%22file%22:%22buttons.css%22%7D */ diff --git a/.styles/global/icons.css b/.styles/global/icons.css deleted file mode 100644 index d71fbc5..0000000 --- a/.styles/global/icons.css +++ /dev/null @@ -1,35 +0,0 @@ -.dotdungeon.dotdungeon.dotdungeon.dotdungeon .icon { - display: inline-flex; - justify-content: center; - align-items: center; -} -.dotdungeon.dotdungeon.dotdungeon.dotdungeon .icon--12 { - height: 12px; - width: 12px; -} -.dotdungeon.dotdungeon.dotdungeon.dotdungeon .icon--14 { - height: 14px; - width: 14px; -} -.dotdungeon.dotdungeon.dotdungeon.dotdungeon .icon--16 { - height: 16px; - width: 16px; -} -.dotdungeon.dotdungeon.dotdungeon.dotdungeon .icon--18 { - height: 18px; - width: 18px; -} -.dotdungeon.dotdungeon.dotdungeon.dotdungeon .icon--20 { - height: 20px; - width: 20px; -} -.dotdungeon.dotdungeon.dotdungeon.dotdungeon .icon--22 { - height: 22px; - width: 22px; -} -.dotdungeon.dotdungeon.dotdungeon.dotdungeon .icon--24 { - height: 24px; - width: 24px; -} - -/*# sourceMappingURL=data:application/json;charset=utf-8,%7B%22version%22:3,%22sourceRoot%22:%22%22,%22sources%22:%5B%22../../styles/global/icons.scss%22%5D,%22names%22:%5B%5D,%22mappings%22:%22AAGC;EACC;EACA;EACA;;AAIC;EACC;EACA;;AAFD;EACC;EACA;;AAFD;EACC;EACA;;AAFD;EACC;EACA;;AAFD;EACC;EACA;;AAFD;EACC;EACA;;AAFD;EACC;EACA%22,%22file%22:%22icons.css%22%7D */ diff --git a/.styles/root.css b/.styles/root.css deleted file mode 100644 index fd950aa..0000000 --- a/.styles/root.css +++ /dev/null @@ -1,530 +0,0 @@ -@import url("https://fonts.googleapis.com/css2?family=Pixelify+Sans&display=swap"); -.dotdungeon > .window-content ::-webkit-scrollbar { - width: 10px; -} -.dotdungeon > .window-content ::-webkit-scrollbar-thumb { - border-radius: 5px; -} -.dotdungeon > .window-content h2, .dotdungeon > .window-content h3, .dotdungeon > .window-content h4, .dotdungeon > .window-content h5, .dotdungeon > .window-content h6 { - all: initial; - display: block; - box-sizing: border-box; - font-family: "Pixelify Sans", sans-serif; - margin: 0; -} -.dotdungeon > .window-content label { - cursor: pointer; -} -.dotdungeon > .window-content button, .dotdungeon > .window-content button:hover { - all: initial; - display: block; - box-sizing: border-box; - font-family: inherit; - cursor: pointer; -} -.dotdungeon > .window-content button:disabled, .dotdungeon > .window-content button:hover:disabled { - cursor: default; -} -.dotdungeon > .window-content input[type=text], -.dotdungeon > .window-content input[type=number], -.dotdungeon > .window-content textarea { - padding: 5px 7px; - border-width: 2px; - border-radius: 4px; - border-style: solid; - border-color: rgba(0, 0, 0, 0.4); - background-color: rgba(0, 0, 0, 0.1); - font-family: sans-serif; -} -.dotdungeon > .window-content input[type=text]:focus, .dotdungeon > .window-content input[type=text]:active, -.dotdungeon > .window-content input[type=number]:focus, -.dotdungeon > .window-content input[type=number]:active, -.dotdungeon > .window-content textarea:focus, -.dotdungeon > .window-content textarea:active { - border-color: rgb(0, 0, 0); -} -.dotdungeon > .window-content textarea { - resize: vertical; -} -.dotdungeon > .window-content select, .dotdungeon > .window-content select:hover { - cursor: pointer; -} -.dotdungeon > .window-content select :disabled, .dotdungeon > .window-content select:hover :disabled { - cursor: default; -} - -.dotdungeon.dotdungeon.dotdungeon.dotdungeon { - container-type: size; -} -.dotdungeon.dotdungeon.dotdungeon.dotdungeon > .window-content { - padding: 0; - background: #f2f2f2; -} -.dotdungeon.dotdungeon.dotdungeon.dotdungeon > .window-content .debug-data { - opacity: 60%; - font-family: sans-serif; - word-break: break-all; -} - -.dotdungeon.dotdungeon.dotdungeon.dotdungeon .icon { - display: inline-flex; - justify-content: center; - align-items: center; -} -.dotdungeon.dotdungeon.dotdungeon.dotdungeon .icon--12 { - height: 12px; - width: 12px; -} -.dotdungeon.dotdungeon.dotdungeon.dotdungeon .icon--14 { - height: 14px; - width: 14px; -} -.dotdungeon.dotdungeon.dotdungeon.dotdungeon .icon--16 { - height: 16px; - width: 16px; -} -.dotdungeon.dotdungeon.dotdungeon.dotdungeon .icon--18 { - height: 18px; - width: 18px; -} -.dotdungeon.dotdungeon.dotdungeon.dotdungeon .icon--20 { - height: 20px; - width: 20px; -} -.dotdungeon.dotdungeon.dotdungeon.dotdungeon .icon--22 { - height: 22px; - width: 22px; -} -.dotdungeon.dotdungeon.dotdungeon.dotdungeon .icon--24 { - height: 24px; - width: 24px; -} - -.dotdungeon.dotdungeon.dotdungeon.dotdungeon > .window-content button { - border-radius: 4px; - box-sizing: border-box; - border-style: solid; - border-color: transparent; - border-width: 2px; - border-radius: 4px; - transition: 400ms; - padding: 4px 8px; - display: inline-flex; - justify-content: center; - align-items: center; - gap: 4px; -} -.dotdungeon.dotdungeon.dotdungeon.dotdungeon > .window-content button.confirm { - background: #048A81; - color: white; -} -.dotdungeon.dotdungeon.dotdungeon.dotdungeon > .window-content button.confirm:hover, .dotdungeon.dotdungeon.dotdungeon.dotdungeon > .window-content button.confirm:focus-visible { - background: transparent; - color: #048A81; - border-color: #048A81; -} -.dotdungeon.dotdungeon.dotdungeon.dotdungeon > .window-content button.neutral { - background: #007ACC; - color: white; -} -.dotdungeon.dotdungeon.dotdungeon.dotdungeon > .window-content button.neutral:hover, .dotdungeon.dotdungeon.dotdungeon.dotdungeon > .window-content button.neutral:focus-visible { - background: transparent; - color: #007ACC; - border-color: #007ACC; -} -.dotdungeon.dotdungeon.dotdungeon.dotdungeon > .window-content button.danger { - background: #960200; - color: white; -} -.dotdungeon.dotdungeon.dotdungeon.dotdungeon > .window-content button.danger:hover, .dotdungeon.dotdungeon.dotdungeon.dotdungeon > .window-content button.danger:focus-visible { - background: transparent; - color: #960200; - border-color: #960200; -} -.dotdungeon.dotdungeon.dotdungeon.dotdungeon > .window-content button.reduced-padding { - padding: 2px 4px; -} -.dotdungeon.dotdungeon.dotdungeon.dotdungeon > .window-content button.equal-padding { - padding: 4px 4px; -} -.dotdungeon.dotdungeon.dotdungeon.dotdungeon > .window-content button.equal-padding.reduced-padding { - padding: 2px 2px; -} - -.dotdungeon .stat { - display: flex; - flex-direction: column; - align-items: center; -} -.dotdungeon .skill { - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - width: 100%; - gap: 8px; -} -.dotdungeon .skill-group { - display: flex; - flex-direction: column; - justify-content: center; - gap: 4px; -} -.dotdungeon .skill-group h3 { - margin-top: 8px; - font-size: 1.2em; - width: 100%; - text-align: center; -} - -.dotdungeon .panel { - display: grid; - grid-template-rows: min-content 1fr; - border: 2px solid black; -} -.dotdungeon .panel__header { - background: black; - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - gap: 4px; - padding: 0 4px; -} -.dotdungeon .panel__header h2 { - all: initial; - display: block; - box-sizing: border-box; - color: white; - flex-grow: 1; - font-family: "Pixelify Sans", sans-serif; - font-size: 20px; -} -.dotdungeon .panel__header .icon { - height: 20px; - width: 20px; - aspect-ratio: 1/1; -} -.dotdungeon .panel__content { - padding: 8px; -} - -@container (max-width: 400px) { - .dotdungeon .panel__header .icon { - display: none; - visibility: hidden; - } -} -.dotdungeon .actor--pc { - display: grid; - grid-template-areas: "profile stats stats" "profile skills skills" "sync skills skills" "spells weapons aspect" "spells weapons aspect" "backpack roles mounts" "backpack roles mounts" "pets storage storage" "pets storage storage"; - grid-template-columns: repeat(3, minmax(0, 1fr)); - grid-template-rows: min-content 1fr repeat(7, min-content); - padding: 4px; - gap: 4px; -} -.dotdungeon .actor--pc details { - border-radius: 4px; - background-color: rgba(0, 0, 0, 0.2); - padding: 4px; - margin-bottom: 8px; -} -.dotdungeon .actor--pc details summary { - cursor: pointer; -} -.dotdungeon .actor--pc details[open] .expanded-rotate { - transform: rotate(90deg); -} -.dotdungeon .actor--pc .panel--profile { - grid-area: profile; -} -.dotdungeon .actor--pc .panel--profile .avatar { - width: 100%; - aspect-ratio: 1/1; -} -.dotdungeon .actor--pc .panel--profile label, .dotdungeon .actor--pc .panel--profile input { - width: 100%; -} -.dotdungeon .actor--pc .panel--stats { - grid-area: stats; -} -.dotdungeon .actor--pc .panel--stats .panel__content { - display: flex; - flex-direction: row; - gap: 8px; - justify-content: space-evenly; - padding: 8px; - flex-wrap: wrap; -} -.dotdungeon .actor--pc .panel--skills { - grid-area: skills; -} -.dotdungeon .actor--pc .panel--skills .panel__content { - display: grid; - row-gap: 8px; - grid-template-columns: repeat(2, minmax(0, 1fr)); - grid-template-rows: repeat(2, minmax(0, min-content)); -} -.dotdungeon .actor--pc .panel--backpack { - grid-area: backpack; -} -.dotdungeon .actor--pc .panel--backpack .row { - display: flex; - flex-direction: row; - align-items: center; -} -.dotdungeon .actor--pc .panel--backpack .col { - display: flex; - flex-direction: column; -} -.dotdungeon .actor--pc .panel--backpack .grow { - flex-grow: 1; -} -.dotdungeon .actor--pc .panel--backpack .panel__content { - display: flex; - flex-direction: column; - gap: 4px; -} -.dotdungeon .actor--pc .panel--backpack .bytes-input, -.dotdungeon .actor--pc .panel--backpack .supplies-count, -.dotdungeon .actor--pc .panel--backpack .materials-count { - width: 25%; - text-align: center; -} -.dotdungeon .actor--pc .panel--backpack textarea { - resize: vertical; -} -.dotdungeon .actor--pc .panel--sync { - grid-area: sync; -} -.dotdungeon .actor--pc .panel--sync .panel__content { - display: flex; - flex-direction: column; - gap: 4px; -} -.dotdungeon .actor--pc .panel--sync .respawns, -.dotdungeon .actor--pc .panel--sync .sync { - display: flex; - flex-direction: row; - align-items: center; -} -.dotdungeon .actor--pc .panel--sync .respawns__header, -.dotdungeon .actor--pc .panel--sync .sync__header { - flex-grow: 1; -} -.dotdungeon .actor--pc .panel--sync .sync__input { - width: 80px; - margin: 3px 5px; -} -.dotdungeon .actor--pc .panel--aspect { - grid-area: aspect; -} -.dotdungeon .actor--pc .panel--aspect .panel__content { - display: grid; - grid-template-rows: min-content min-content; - gap: 4px; - text-align: center; -} -.dotdungeon .actor--pc .panel--aspect .panel__content textarea { - resize: vertical; -} -.dotdungeon .actor--pc .panel--aspect .aspect__used { - display: flex; - align-items: center; - gap: 4px; -} -.dotdungeon .actor--pc .panel--aspect .aspect__used--input { - margin: 0; -} -.dotdungeon .actor--pc .panel--weapons { - grid-area: weapons; -} -.dotdungeon .actor--pc .panel--weapons .weapon { - margin-top: 4px; - display: flex; - flex-direction: column; - gap: 4px; -} -.dotdungeon .actor--pc .panel--weapons .weapon__name { - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; -} -.dotdungeon .actor--pc .panel--weapons .weapon__name input { - width: 50%; -} -.dotdungeon .actor--pc .panel--weapons .weapon__group { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 4px; -} -.dotdungeon .actor--pc .panel--weapons .weapon__ammo-type, .dotdungeon .actor--pc .panel--weapons .weapon__damage-type { - display: flex; - flex-direction: column; - align-items: center; -} -.dotdungeon .actor--pc .panel--weapons .weapon__is-scoped, .dotdungeon .actor--pc .panel--weapons .weapon__is-ranged { - display: flex; - flex-direction: row; - align-items: center; - justify-content: center; -} -.dotdungeon .actor--pc .panel--roles { - grid-area: roles; -} -.dotdungeon .actor--pc .panel--roles .panel__content { - display: grid; - grid-template-rows: repeat(4, min-content); - height: 100%; - gap: 4px; -} -.dotdungeon .actor--pc .panel--roles .panel__content textarea { - resize: vertical; -} -.dotdungeon .actor--pc .panel--spells { - grid-area: spells; -} -.dotdungeon .actor--pc .panel--spells .spell { - display: flex; - flex-direction: column; - gap: 4px; -} -.dotdungeon .actor--pc .panel--spells .spell .placeholder { - opacity: 75%; -} -.dotdungeon .actor--pc .panel--spells .panel__content { - display: flex; - gap: 4px; - flex-direction: column; -} -.dotdungeon .actor--pc .panel--mounts { - grid-area: mounts; -} -.dotdungeon .actor--pc .panel--pets { - grid-area: pets; -} -.dotdungeon .actor--pc .panel--pets .panel__content { - display: grid; - grid-template-rows: min-content min-content; - gap: 4px; -} -.dotdungeon .actor--pc .panel--pets .panel__content textarea { - resize: vertical; -} -.dotdungeon .actor--pc .panel--storage { - grid-area: storage; -} -.dotdungeon .actor--pc .actions { - display: flex; - flex-direction: row; - justify-content: end; - gap: 4px; -} -.dotdungeon .actor--pc .panel__content .center { - text-align: center; -} - -@container (max-width: 620px) { - .dotdungeon .actor--pc { - grid-template-columns: repeat(2, minmax(0, 1fr)); - grid-template-rows: repeat(15, min-content); - grid-template-areas: "profile stats" "profile skills" "sync skills" "weapons skills" "weapons skills" "backpack aspect" "backpack aspect" "backpack roles" "backpack roles" "backpack roles" "backpack spells" "pets spells" "pets storage" "mounts storage" "mounts storage"; - } - .dotdungeon .actor--pc .panel--stats .panel__content { - flex-wrap: wrap; - } - .dotdungeon .actor--pc .panel--skills .panel__content { - display: flex; - flex-direction: column; - } -} -@container (max-width: 400px) { - .dotdungeon .actor--pc { - grid-template-columns: 1fr; - grid-template-rows: repeat(12, min-content); - grid-template-areas: "profile" "stats" "sync" "skills" "aspect" "roles" "backpack" "weapons" "spells" "mounts" "pets" "storage"; - } - .dotdungeon .actor--pc .panel--skills .skill { - flex-direction: column; - } -} -.dotdungeon .actor--mob { - padding: 4px; -} -.dotdungeon .actor--mob textarea { - width: 100%; - resize: vertical; -} - -.dotdungeon .actor--basic-sync { - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; - gap: 4px; -} -.dotdungeon .actor--basic-sync label { - display: flex; - flex-direction: column; - align-items: center; -} -.dotdungeon .actor--basic-sync .sync-input { - width: 30%; - text-align: center; -} -.dotdungeon .actor--basic-sync .name-input { - width: 60%; - text-align: center; -} -.dotdungeon--sync-sheet header .configure-token { - display: none; - visibility: hidden; -} - -@container (max-width: 300px) { - .dotdungeon--sync-sheet header .configure-sheet { - display: none; - visibility: hidden; - } -} -.dotdungeon .item--aspect { - padding: 4px; -} -.dotdungeon .item--aspect input[type=text] { - font-size: 1.5em; - height: 1.5em; - width: 100%; -} -.dotdungeon .item--aspect textarea { - width: 100%; - resize: vertical; -} - -.dotdungeon .item--spell { - padding: 4px; -} -.dotdungeon .item--spell input[type=text] { - font-size: 1.5em; - height: 1.5em; - width: 100%; -} -.dotdungeon .item--spell textarea { - width: 100%; - resize: vertical; -} - -.dotdungeon .item--pet { - padding: 4px; -} -.dotdungeon .item--pet input[type=text] { - font-size: 1.5em; - height: 1.5em; - width: 100%; -} -.dotdungeon .item--pet textarea { - width: 100%; - resize: vertical; -} - -/*# sourceMappingURL=data:application/json;charset=utf-8,%7B%22version%22:3,%22sourceRoot%22:%22%22,%22sources%22:%5B%22../styles/generic.scss%22,%22../styles/mixins/_foundry.scss%22,%22../styles/_vars.scss%22,%22../styles/mixins/_partials.scss%22,%22../styles/global/icons.scss%22,%22../styles/global/buttons.scss%22,%22../styles/sheets/partials/stat.scss%22,%22../styles/sheets/partials/skill.scss%22,%22../styles/sheets/partials/panel.scss%22,%22../styles/mixins/_breakpoints.scss%22,%22../styles/sheets/actor/mvp.scss%22,%22../styles/sheets/actor/mob.scss%22,%22../styles/sheets/actor/sync/basic.scss%22,%22../styles/sheets/items/aspect.scss%22,%22../styles/sheets/items/spell.scss%22,%22../styles/sheets/items/pet.scss%22%5D,%22names%22:%5B%5D,%22mappings%22:%22AAIQ;AAKP;EACC;;AAED;EACC;;AAGD;ECfA;EACA;EACA;EDeC,aEhBW;EFiBX;;AAGD;EACC;;AAGD;ECzBA;EACA;EACA;EDyBC;EACA;;AAEA;EACC;;AAIF;AAAA;AAAA;EAGC;EGpCD;EACA;EACA;EACA;EACA;EACA,aDLW;;ACOX;AAAA;AAAA;AAAA;AAAA;EAEC;;AH+BD;EACC;;AAGD;EACC;;AAEA;EACC;;;AAMH;EACC;;AAEA;EACC;EACA,YEzDW;;AF2DX;EACC;EACA,aE/DS;EFgET;;;AIhEF;EACC;EACA;EACA;;AAIC;EACC;EACA;;AAFD;EACC;EACA;;AAFD;EACC;EACA;;AAFD;EACC;EACA;;AAFD;EACC;EACA;;AAFD;EACC;EACA;;AAFD;EACC;EACA;;;ACTH;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACC,YHXc;EGYd,OHXe;;AGYf;EACC;EACA,OHfa;EGgBb,cHhBa;;AGoBf;EACC,YHlBc;EGmBd,OHlBe;;AGmBf;EACC;EACA,OHtBa;EGuBb,cHvBa;;AG2Bf;EACC,YHzBa;EG0Bb,OHzBc;;AG0Bd;EACC;EACA,OH7BY;EG8BZ,cH9BY;;AGkCd;EACC;;AAID;EACC;;AACA;EACC;;;ACvDJ;EACC;EACA;EACA;;ACHD;EACC;EACA;EACA;EACA;EACA;EACA;;AAEA;EACC;EACA;EACA;EACA;;AAEA;EACC;EACA;EACA;EACA;;;ACdH;EACC;EACA;EAEA;;AAEA;EACC;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EPlBD;EACA;EACA;EOkBE;EACA;EACA,aNrBU;EMsBV;;AAGD;EAEC,QADO;EAEP,OAFO;EAGP;;AAIF;EACC;;;AC7BD;EDkCA;IACC;IACA;;;AExCF;EACC;EACA,qBACC;EASD;EACA,oBACC;EAGD;EACA;;AAEA;EACC;EACA;EACA;EACA;;AAEA;EACC;;AAIA;EACC;;AAMF;EACC;;AACA;EACC;EACA;;AAGD;EACC;;AAGF;EACC;;AACA;EACC;EACA;EACA;EACA;EACA;EACA;;AAGF;EACC;;AAEA;EACC;EACA;EACA;EACA;;AAGF;EACC;;AAEA;EACC;EACA;EACA;;AAED;EACC;EACA;;AAED;EACC;;AAGD;EACC;EACA;EACA;;AAGD;AAAA;AAAA;EAGC;EACA;;AAGD;EACC;;AAGF;EACC;;AAEA;EACC;EACA;EACA;;AAGD;AAAA;EAEC;EACA;EACA;;AAEA;AAAA;EACC;;AAIF;EACC;EACA;;AAGF;EACC;;AACA;EACC;EACA;EACA;EACA;;AAEA;EACC;;AAKD;EACC;EACA;EACA;;AAEA;EACC;;AAKJ;EACC;;AAEA;EACC;EACA;EACA;EACA;;AAEA;EACC;EACA;EACA;EACA;;AAEA;EACC;;AAGF;EACC;EACA;EACA;;AAED;EACC;EACA;EACA;;AAED;EACC;EACA;EACA;EACA;;AAIH;EACC;;AACA;EACC;EACA;EACA;EACA;;AAEA;EACC;;AAIH;EACC;;AAEA;EACC;EACA;EACA;;AAEA;EACC;;AAIF;EACC;EACA;EACA;;AAGF;EACC;;AAED;EACC;;AAEA;EACC;EACA;EACA;;AAEA;EACC;;AAIH;EACC;;AAIF;EACC;EACA;EACA;EACA;;AAIA;EACC;;;AD9PF;ECsQC;IACC;IACA;IACA,qBACC;;EAkBC;IACC;;EAKD;IACC;IACA;;;AD9RL;ECwSC;IACC;IACA;IACA,qBACC;;EAeC;IACC;;;ACjUN;EACC;;AAEA;EACC;EACA;;;ACJD;EACC;EACA;EACA;EACA;EACA;;AAEA;EACC;EACA;EACA;;AAGD;EACC;EACA;;AAGD;EACC;EACA;;AAMA;EACC;EACA;;;AHlBH;EG4BG;IACC;IACA;;;ACzCL;EACC;;AAEA;EACC;EACA;EACA;;AAGD;EACC;EACA;;;ACXF;EACC;;AAEA;EACC;EACA;EACA;;AAGD;EACC;EACA;;;ACXF;EACC;;AAEA;EACC;EACA;EACA;;AAGD;EACC;EACA%22,%22file%22:%22root.css%22%7D */ diff --git a/.styles/sheets/actor/mob.css b/.styles/sheets/actor/mob.css deleted file mode 100644 index 0764af5..0000000 --- a/.styles/sheets/actor/mob.css +++ /dev/null @@ -1,9 +0,0 @@ -.dotdungeon .actor--mob { - padding: 4px; -} -.dotdungeon .actor--mob textarea { - width: 100%; - resize: vertical; -} - -/*# sourceMappingURL=data:application/json;charset=utf-8,%7B%22version%22:3,%22sourceRoot%22:%22%22,%22sources%22:%5B%22../../../styles/sheets/actor/mob.scss%22%5D,%22names%22:%5B%5D,%22mappings%22:%22AAEA;EACC;;AAEA;EACC;EACA%22,%22file%22:%22mob.css%22%7D */ diff --git a/.styles/sheets/actor/mvp.css b/.styles/sheets/actor/mvp.css deleted file mode 100644 index 0eedafc..0000000 --- a/.styles/sheets/actor/mvp.css +++ /dev/null @@ -1,236 +0,0 @@ -.dotdungeon .actor--pc { - display: grid; - grid-template-areas: "profile stats stats" "profile skills skills" "sync skills skills" "spells weapons aspect" "spells weapons aspect" "backpack roles mounts" "backpack roles mounts" "pets storage storage" "pets storage storage"; - grid-template-columns: repeat(3, minmax(0, 1fr)); - grid-template-rows: min-content 1fr repeat(7, min-content); - padding: 4px; - gap: 4px; -} -.dotdungeon .actor--pc details { - border-radius: 4px; - background-color: rgba(0, 0, 0, 0.2); - padding: 4px; - margin-bottom: 8px; -} -.dotdungeon .actor--pc details summary { - cursor: pointer; -} -.dotdungeon .actor--pc details[open] .expanded-rotate { - transform: rotate(90deg); -} -.dotdungeon .actor--pc .panel--profile { - grid-area: profile; -} -.dotdungeon .actor--pc .panel--profile .avatar { - width: 100%; - aspect-ratio: 1/1; -} -.dotdungeon .actor--pc .panel--profile label, .dotdungeon .actor--pc .panel--profile input { - width: 100%; -} -.dotdungeon .actor--pc .panel--stats { - grid-area: stats; -} -.dotdungeon .actor--pc .panel--stats .panel__content { - display: flex; - flex-direction: row; - gap: 8px; - justify-content: space-evenly; - padding: 8px; - flex-wrap: wrap; -} -.dotdungeon .actor--pc .panel--skills { - grid-area: skills; -} -.dotdungeon .actor--pc .panel--skills .panel__content { - display: grid; - row-gap: 8px; - grid-template-columns: repeat(2, minmax(0, 1fr)); - grid-template-rows: repeat(2, minmax(0, min-content)); -} -.dotdungeon .actor--pc .panel--backpack { - grid-area: backpack; -} -.dotdungeon .actor--pc .panel--backpack .row { - display: flex; - flex-direction: row; - align-items: center; -} -.dotdungeon .actor--pc .panel--backpack .col { - display: flex; - flex-direction: column; -} -.dotdungeon .actor--pc .panel--backpack .grow { - flex-grow: 1; -} -.dotdungeon .actor--pc .panel--backpack .panel__content { - display: flex; - flex-direction: column; - gap: 4px; -} -.dotdungeon .actor--pc .panel--backpack .bytes-input, -.dotdungeon .actor--pc .panel--backpack .supplies-count, -.dotdungeon .actor--pc .panel--backpack .materials-count { - width: 25%; - text-align: center; -} -.dotdungeon .actor--pc .panel--backpack textarea { - resize: vertical; -} -.dotdungeon .actor--pc .panel--sync { - grid-area: sync; -} -.dotdungeon .actor--pc .panel--sync .panel__content { - display: flex; - flex-direction: column; - gap: 4px; -} -.dotdungeon .actor--pc .panel--sync .respawns, -.dotdungeon .actor--pc .panel--sync .sync { - display: flex; - flex-direction: row; - align-items: center; -} -.dotdungeon .actor--pc .panel--sync .respawns__header, -.dotdungeon .actor--pc .panel--sync .sync__header { - flex-grow: 1; -} -.dotdungeon .actor--pc .panel--sync .sync__input { - width: 80px; - margin: 3px 5px; -} -.dotdungeon .actor--pc .panel--aspect { - grid-area: aspect; -} -.dotdungeon .actor--pc .panel--aspect .panel__content { - display: grid; - grid-template-rows: min-content min-content; - gap: 4px; - text-align: center; -} -.dotdungeon .actor--pc .panel--aspect .panel__content textarea { - resize: vertical; -} -.dotdungeon .actor--pc .panel--aspect .aspect__used { - display: flex; - align-items: center; - gap: 4px; -} -.dotdungeon .actor--pc .panel--aspect .aspect__used--input { - margin: 0; -} -.dotdungeon .actor--pc .panel--weapons { - grid-area: weapons; -} -.dotdungeon .actor--pc .panel--weapons .weapon { - margin-top: 4px; - display: flex; - flex-direction: column; - gap: 4px; -} -.dotdungeon .actor--pc .panel--weapons .weapon__name { - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; -} -.dotdungeon .actor--pc .panel--weapons .weapon__name input { - width: 50%; -} -.dotdungeon .actor--pc .panel--weapons .weapon__group { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 4px; -} -.dotdungeon .actor--pc .panel--weapons .weapon__ammo-type, .dotdungeon .actor--pc .panel--weapons .weapon__damage-type { - display: flex; - flex-direction: column; - align-items: center; -} -.dotdungeon .actor--pc .panel--weapons .weapon__is-scoped, .dotdungeon .actor--pc .panel--weapons .weapon__is-ranged { - display: flex; - flex-direction: row; - align-items: center; - justify-content: center; -} -.dotdungeon .actor--pc .panel--roles { - grid-area: roles; -} -.dotdungeon .actor--pc .panel--roles .panel__content { - display: grid; - grid-template-rows: repeat(4, min-content); - height: 100%; - gap: 4px; -} -.dotdungeon .actor--pc .panel--roles .panel__content textarea { - resize: vertical; -} -.dotdungeon .actor--pc .panel--spells { - grid-area: spells; -} -.dotdungeon .actor--pc .panel--spells .spell { - display: flex; - flex-direction: column; - gap: 4px; -} -.dotdungeon .actor--pc .panel--spells .spell .placeholder { - opacity: 75%; -} -.dotdungeon .actor--pc .panel--spells .panel__content { - display: flex; - gap: 4px; - flex-direction: column; -} -.dotdungeon .actor--pc .panel--mounts { - grid-area: mounts; -} -.dotdungeon .actor--pc .panel--pets { - grid-area: pets; -} -.dotdungeon .actor--pc .panel--pets .panel__content { - display: grid; - grid-template-rows: min-content min-content; - gap: 4px; -} -.dotdungeon .actor--pc .panel--pets .panel__content textarea { - resize: vertical; -} -.dotdungeon .actor--pc .panel--storage { - grid-area: storage; -} -.dotdungeon .actor--pc .actions { - display: flex; - flex-direction: row; - justify-content: end; - gap: 4px; -} -.dotdungeon .actor--pc .panel__content .center { - text-align: center; -} - -@container (max-width: 620px) { - .dotdungeon .actor--pc { - grid-template-columns: repeat(2, minmax(0, 1fr)); - grid-template-rows: repeat(15, min-content); - grid-template-areas: "profile stats" "profile skills" "sync skills" "weapons skills" "weapons skills" "backpack aspect" "backpack aspect" "backpack roles" "backpack roles" "backpack roles" "backpack spells" "pets spells" "pets storage" "mounts storage" "mounts storage"; - } - .dotdungeon .actor--pc .panel--stats .panel__content { - flex-wrap: wrap; - } - .dotdungeon .actor--pc .panel--skills .panel__content { - display: flex; - flex-direction: column; - } -} -@container (max-width: 400px) { - .dotdungeon .actor--pc { - grid-template-columns: 1fr; - grid-template-rows: repeat(12, min-content); - grid-template-areas: "profile" "stats" "sync" "skills" "aspect" "roles" "backpack" "weapons" "spells" "mounts" "pets" "storage"; - } - .dotdungeon .actor--pc .panel--skills .skill { - flex-direction: column; - } -} - -/*# sourceMappingURL=data:application/json;charset=utf-8,%7B%22version%22:3,%22sourceRoot%22:%22%22,%22sources%22:%5B%22../../../styles/sheets/actor/mvp.scss%22,%22../../../styles/mixins/_breakpoints.scss%22%5D,%22names%22:%5B%5D,%22mappings%22:%22AAGA;EACC;EACA,qBACC;EASD;EACA,oBACC;EAGD;EACA;;AAEA;EACC;EACA;EACA;EACA;;AAEA;EACC;;AAIA;EACC;;AAMF;EACC;;AACA;EACC;EACA;;AAGD;EACC;;AAGF;EACC;;AACA;EACC;EACA;EACA;EACA;EACA;EACA;;AAGF;EACC;;AAEA;EACC;EACA;EACA;EACA;;AAGF;EACC;;AAEA;EACC;EACA;EACA;;AAED;EACC;EACA;;AAED;EACC;;AAGD;EACC;EACA;EACA;;AAGD;AAAA;AAAA;EAGC;EACA;;AAGD;EACC;;AAGF;EACC;;AAEA;EACC;EACA;EACA;;AAGD;AAAA;EAEC;EACA;EACA;;AAEA;AAAA;EACC;;AAIF;EACC;EACA;;AAGF;EACC;;AACA;EACC;EACA;EACA;EACA;;AAEA;EACC;;AAKD;EACC;EACA;EACA;;AAEA;EACC;;AAKJ;EACC;;AAEA;EACC;EACA;EACA;EACA;;AAEA;EACC;EACA;EACA;EACA;;AAEA;EACC;;AAGF;EACC;EACA;EACA;;AAED;EACC;EACA;EACA;;AAED;EACC;EACA;EACA;EACA;;AAIH;EACC;;AACA;EACC;EACA;EACA;EACA;;AAEA;EACC;;AAIH;EACC;;AAEA;EACC;EACA;EACA;;AAEA;EACC;;AAIF;EACC;EACA;EACA;;AAGF;EACC;;AAED;EACC;;AAEA;EACC;EACA;EACA;;AAEA;EACC;;AAIH;EACC;;AAIF;EACC;EACA;EACA;EACA;;AAIA;EACC;;;AC9PF;EDsQC;IACC;IACA;IACA,qBACC;;EAkBC;IACC;;EAKD;IACC;IACA;;;AC9RL;EDwSC;IACC;IACA;IACA,qBACC;;EAeC;IACC%22,%22file%22:%22mvp.css%22%7D */ diff --git a/.styles/sheets/actor/sync.css b/.styles/sheets/actor/sync.css deleted file mode 100644 index 83ee62d..0000000 --- a/.styles/sheets/actor/sync.css +++ /dev/null @@ -1,19 +0,0 @@ -.dotdungeon .actor--sync { - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; -} -.dotdungeon .actor--sync .sync-input { - width: 30%; - text-align: center; -} - -@container (max-width: 400px) { - .dotdungeon--sync-sheet header .configure-sheet, - .dotdungeon--sync-sheet header .configure-token { - display: none; - } -} - -/*# sourceMappingURL=data:application/json;charset=utf-8,%7B%22version%22:3,%22sourceRoot%22:%22%22,%22sources%22:%5B%22../../../styles/sheets/actor/sync.scss%22,%22../../../styles/mixins/_breakpoints.scss%22%5D,%22names%22:%5B%5D,%22mappings%22:%22AAGC;EACC;EACA;EACA;EACA;;AAEA;EACC;EACA;;;ACJF;EDaG;AAAA;IAEC%22,%22file%22:%22sync.css%22%7D */ diff --git a/.styles/sheets/actor/sync/basic.css b/.styles/sheets/actor/sync/basic.css deleted file mode 100644 index 297b16a..0000000 --- a/.styles/sheets/actor/sync/basic.css +++ /dev/null @@ -1,33 +0,0 @@ -.dotdungeon .actor--basic-sync { - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; - gap: 4px; -} -.dotdungeon .actor--basic-sync label { - display: flex; - flex-direction: column; - align-items: center; -} -.dotdungeon .actor--basic-sync .sync-input { - width: 30%; - text-align: center; -} -.dotdungeon .actor--basic-sync .name-input { - width: 60%; - text-align: center; -} -.dotdungeon--sync-sheet header .configure-token { - display: none; - visibility: hidden; -} - -@container (max-width: 300px) { - .dotdungeon--sync-sheet header .configure-sheet { - display: none; - visibility: hidden; - } -} - -/*# sourceMappingURL=data:application/json;charset=utf-8,%7B%22version%22:3,%22sourceRoot%22:%22%22,%22sources%22:%5B%22../../../../styles/sheets/actor/sync/basic.scss%22,%22../../../../styles/mixins/_breakpoints.scss%22%5D,%22names%22:%5B%5D,%22mappings%22:%22AAGC;EACC;EACA;EACA;EACA;EACA;;AAEA;EACC;EACA;EACA;;AAGD;EACC;EACA;;AAGD;EACC;EACA;;AAMA;EACC;EACA;;;AClBH;ED4BG;IACC;IACA%22,%22file%22:%22basic.css%22%7D */ diff --git a/.styles/sheets/items/aspect.css b/.styles/sheets/items/aspect.css deleted file mode 100644 index 8572a97..0000000 --- a/.styles/sheets/items/aspect.css +++ /dev/null @@ -1,14 +0,0 @@ -.dotdungeon .item--aspect { - padding: 4px; -} -.dotdungeon .item--aspect input[type=text] { - font-size: 1.5em; - height: 1.5em; - width: 100%; -} -.dotdungeon .item--aspect textarea { - width: 100%; - resize: vertical; -} - -/*# sourceMappingURL=data:application/json;charset=utf-8,%7B%22version%22:3,%22sourceRoot%22:%22%22,%22sources%22:%5B%22../../../styles/sheets/items/aspect.scss%22%5D,%22names%22:%5B%5D,%22mappings%22:%22AAEA;EACC;;AAEA;EACC;EACA;EACA;;AAGD;EACC;EACA%22,%22file%22:%22aspect.css%22%7D */ diff --git a/.styles/sheets/items/pet.css b/.styles/sheets/items/pet.css deleted file mode 100644 index 6b0b96c..0000000 --- a/.styles/sheets/items/pet.css +++ /dev/null @@ -1,14 +0,0 @@ -.dotdungeon .item--pet { - padding: 4px; -} -.dotdungeon .item--pet input[type=text] { - font-size: 1.5em; - height: 1.5em; - width: 100%; -} -.dotdungeon .item--pet textarea { - width: 100%; - resize: vertical; -} - -/*# sourceMappingURL=data:application/json;charset=utf-8,%7B%22version%22:3,%22sourceRoot%22:%22%22,%22sources%22:%5B%22../../../styles/sheets/items/pet.scss%22%5D,%22names%22:%5B%5D,%22mappings%22:%22AAEA;EACC;;AAEA;EACC;EACA;EACA;;AAGD;EACC;EACA%22,%22file%22:%22pet.css%22%7D */ diff --git a/.styles/sheets/items/spell.css b/.styles/sheets/items/spell.css deleted file mode 100644 index 7747e6e..0000000 --- a/.styles/sheets/items/spell.css +++ /dev/null @@ -1,14 +0,0 @@ -.dotdungeon .item--spell { - padding: 4px; -} -.dotdungeon .item--spell input[type=text] { - font-size: 1.5em; - height: 1.5em; - width: 100%; -} -.dotdungeon .item--spell textarea { - width: 100%; - resize: vertical; -} - -/*# sourceMappingURL=data:application/json;charset=utf-8,%7B%22version%22:3,%22sourceRoot%22:%22%22,%22sources%22:%5B%22../../../styles/sheets/items/spell.scss%22%5D,%22names%22:%5B%5D,%22mappings%22:%22AAEA;EACC;;AAEA;EACC;EACA;EACA;;AAGD;EACC;EACA%22,%22file%22:%22spell.css%22%7D */ diff --git a/.styles/sheets/partials/panel.css b/.styles/sheets/partials/panel.css deleted file mode 100644 index 42aa311..0000000 --- a/.styles/sheets/partials/panel.css +++ /dev/null @@ -1,40 +0,0 @@ -.dotdungeon .panel { - display: grid; - grid-template-rows: min-content 1fr; - border: 2px solid black; -} -.dotdungeon .panel__header { - background: black; - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - gap: 4px; - padding: 0 4px; -} -.dotdungeon .panel__header h2 { - all: initial; - display: block; - box-sizing: border-box; - color: white; - flex-grow: 1; - font-family: "Pixelify Sans", sans-serif; - font-size: 20px; -} -.dotdungeon .panel__header .icon { - height: 20px; - width: 20px; - aspect-ratio: 1/1; -} -.dotdungeon .panel__content { - padding: 8px; -} - -@container (max-width: 400px) { - .dotdungeon .panel__header .icon { - display: none; - visibility: hidden; - } -} - -/*# sourceMappingURL=data:application/json;charset=utf-8,%7B%22version%22:3,%22sourceRoot%22:%22%22,%22sources%22:%5B%22../../../styles/sheets/partials/panel.scss%22,%22../../../styles/mixins/_foundry.scss%22,%22../../../styles/_vars.scss%22,%22../../../styles/mixins/_breakpoints.scss%22%5D,%22names%22:%5B%5D,%22mappings%22:%22AAIA;EACC;EACA;EAEA;;AAEA;EACC;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EClBD;EACA;EACA;EDkBE;EACA;EACA,aErBU;EFsBV;;AAGD;EAEC,QADO;EAEP,OAFO;EAGP;;AAIF;EACC;;;AG7BD;EHkCA;IACC;IACA%22,%22file%22:%22panel.css%22%7D */ diff --git a/.styles/sheets/partials/skill.css b/.styles/sheets/partials/skill.css deleted file mode 100644 index 3d34727..0000000 --- a/.styles/sheets/partials/skill.css +++ /dev/null @@ -1,22 +0,0 @@ -.dotdungeon .skill { - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - width: 100%; - gap: 8px; -} -.dotdungeon .skill-group { - display: flex; - flex-direction: column; - justify-content: center; - gap: 4px; -} -.dotdungeon .skill-group h3 { - margin-top: 8px; - font-size: 1.2em; - width: 100%; - text-align: center; -} - -/*# sourceMappingURL=data:application/json;charset=utf-8,%7B%22version%22:3,%22sourceRoot%22:%22%22,%22sources%22:%5B%22../../../styles/sheets/partials/skill.scss%22%5D,%22names%22:%5B%5D,%22mappings%22:%22AAAA;EACC;EACA;EACA;EACA;EACA;EACA;;AAEA;EACC;EACA;EACA;EACA;;AAEA;EACC;EACA;EACA;EACA%22,%22file%22:%22skill.css%22%7D */ diff --git a/.styles/sheets/partials/stat.css b/.styles/sheets/partials/stat.css deleted file mode 100644 index 5973333..0000000 --- a/.styles/sheets/partials/stat.css +++ /dev/null @@ -1,7 +0,0 @@ -.dotdungeon .stat { - display: flex; - flex-direction: column; - align-items: center; -} - -/*# sourceMappingURL=data:application/json;charset=utf-8,%7B%22version%22:3,%22sourceRoot%22:%22%22,%22sources%22:%5B%22../../../styles/sheets/partials/stat.scss%22%5D,%22names%22:%5B%5D,%22mappings%22:%22AAAA;EACC;EACA;EACA%22,%22file%22:%22stat.css%22%7D */ diff --git a/.vscode/components.html-data.json b/.vscode/components.html-data.json new file mode 100644 index 0000000..e1c3ebd --- /dev/null +++ b/.vscode/components.html-data.json @@ -0,0 +1,27 @@ +{ + "version": 1.1, + "tags": [ + { + "name": "dd-incrementer", + "description": "A number input that allows more flexible increase/decrease buttons", + "attributes": [ + { "name": "value", "description": "The initial value to put in the input" }, + { "name": "name", "description": "The form name to use when this input is used to submit data" }, + { "name": "min", "description": "The minimum value that this input can contain" }, + { "name": "max", "description": "The maximum value that this input can contain" }, + { "name": "smallStep", "description": "The value that the input is changed by when clicking a delta button or using the up/down arrow key" }, + { "name": "largeStep", "description": "The value that the input is changed by when clicking a delta button with control held or using the page up/ page down arrow key" } + ] + }, + { + "name": "dd-icon", + "description": "Loads an icon asynchronously, caching the result for future uses", + "attributes": [ + { "name": "name", "description": "The name of the icon, this is relative to the assets folder of the dotdungeon system" }, + { "name": "path", "description": "The full path of the icon, this will only be used if `name` isn't provided or fails to fetch." } + ] + } + ], + "globalAttributes": [], + "valueSets": [] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 82812b8..9cd44b8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,11 +1,19 @@ { "files.autoSave": "onWindowChange", "editor.tabSize": 2, - "[javascript]": { - "editor.tabSize": 4 - }, "[yaml,yml]": { "editor.insertSpaces": true, "editor.tabSize": 2 - } -} \ No newline at end of file + }, + "git.branchProtection": [], + "files.exclude": { + "dotdungeon.lock": true, + ".styles": true, + "node_modules": true, + "packs": true, + ".gitattributes": true, + }, + "html.customData": [ + "./.vscode/components.html-data.json" + ] +} diff --git a/How-to-release.md b/How-to-release.md deleted file mode 100644 index dceaddc..0000000 --- a/How-to-release.md +++ /dev/null @@ -1,7 +0,0 @@ -1. Make sure that the `version` is the correct version number. -2. Copy the `system.json` in order to make the version-specific manifest -3. Update the `download` to point to the version-specific file from Github -4. Compress all of the necessary files and folders -5. Create the release on Github with the correct tag being created -6. Upload the compressed files and the version-specific manifest file -7. Save the release as draft, prerelease, or stable (pre-releases won't be caught in the `/latest` URL that is used to check for updates) \ No newline at end of file diff --git a/System-Notes b/System-Notes deleted file mode 100644 index e61ae94..0000000 --- a/System-Notes +++ /dev/null @@ -1,2 +0,0 @@ -- "Resources" and "Supplies" are used interchangeably in the book -- \ No newline at end of file diff --git a/TODO b/TODO deleted file mode 100644 index be81a47..0000000 --- a/TODO +++ /dev/null @@ -1,29 +0,0 @@ -## MVP: -- Text-box PC Character sheet -- PC Data Structure -- Dice Rolling - -## Full Release -- Data Models: - - Actors: - - NPC - - PC - - Mob - - Items: - - Weapons - - Armour - - Equipment - - Foil - - Pet - - Transportation - - Structure - - Service - - Legendary Items - - Spells - - Aspects - - Status - - Roles -- Character Sheet: - - PC - - NPC - - Mob \ No newline at end of file diff --git a/assets/caret-right.svg b/assets/caret-right.svg deleted file mode 100644 index 7d1d59b..0000000 --- a/assets/caret-right.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/assets/chat-bubble.svg b/assets/chat-bubble.svg deleted file mode 100644 index 8dde604..0000000 --- a/assets/chat-bubble.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/assets/close.svg b/assets/close.svg deleted file mode 100644 index f6c80ed..0000000 --- a/assets/close.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/assets/dice/d10.svg b/assets/dice/d10.svg index 96a39a1..3debc8e 100644 --- a/assets/dice/d10.svg +++ b/assets/dice/d10.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/assets/dice/d12.svg b/assets/dice/d12.svg index dac2e4c..df2787c 100644 --- a/assets/dice/d12.svg +++ b/assets/dice/d12.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/assets/dice/d20.svg b/assets/dice/d20.svg index 82cf8b3..a829cdf 100644 --- a/assets/dice/d20.svg +++ b/assets/dice/d20.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/assets/dice/d4.svg b/assets/dice/d4.svg index 3388bda..f31809b 100644 --- a/assets/dice/d4.svg +++ b/assets/dice/d4.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/assets/dice/d6.svg b/assets/dice/d6.svg index bea7528..00dfed7 100644 --- a/assets/dice/d6.svg +++ b/assets/dice/d6.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/assets/dice/d8.svg b/assets/dice/d8.svg index ca3b00b..7731f96 100644 --- a/assets/dice/d8.svg +++ b/assets/dice/d8.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/assets/edit.svg b/assets/edit.svg deleted file mode 100644 index 7cc344b..0000000 --- a/assets/edit.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/assets/garbage-bin.svg b/assets/garbage-bin.svg deleted file mode 100644 index b9268a5..0000000 --- a/assets/garbage-bin.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/assets/sheet.svg b/assets/sheet.svg deleted file mode 100644 index eaf555b..0000000 --- a/assets/sheet.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/assets/sources.txt b/assets/sources.txt index e9d03fc..2896d5f 100644 --- a/assets/sources.txt +++ b/assets/sources.txt @@ -1,11 +1,15 @@ +Disclaimer: + All icons included in this repo have been scaled and optimized as needed. + Amer Alamer - caret-right.svg (https://thenounproject.com/icon/arrow-caret-right-1143917/) + ui/caret/right.svg (https://thenounproject.com/icon/arrow-caret-right-1143917/) + ui/caret/down.svg (https://thenounproject.com/icon/arrow-caret-down-1143911/) Alice Design: - garbage-bin.svg (https://thenounproject.com/icon/garbage-2025492/) + ui/garbage-bin.svg (https://thenounproject.com/icon/garbage-2025492/) zapesicon: - chat-bubble.svg (https://thenounproject.com/icon/chat-6423186/) + ui/chat-bubble.svg (https://thenounproject.com/icon/chat-6423186/) Fritz Duggan: dice/d4.svg (https://thenounproject.com/icon/d4-4570604/) @@ -16,18 +20,19 @@ Fritz Duggan: dice/d20.svg (https://thenounproject.com/icon/d20-4570607/) Landan Lloyd: - create.svg (https://thenounproject.com/icon/create-1447560/) + ui/plus.svg (https://thenounproject.com/icon/create-1447560/) + +Bismillah + ui/minus.svg (https://thenounproject.com/icon/minus-1727966/) Rokhman Kharis: - close.svg (https://thenounproject.com/icon/close-4996834/) + ui/close.svg (https://thenounproject.com/icon/close-4996834/) Athok: - sheet.svg (https://thenounproject.com/icon/sheet-5939348/) + ui/sheet.svg (https://thenounproject.com/icon/sheet-5939348/) Icon Depot: - edit.svg (https://thenounproject.com/icon/edit-1489252/) + ui/pencil.svg (https://thenounproject.com/icon/edit-1489252/) -Oliver Akins: - chat-bubble.svg : Scaling - create.svg : Scaling, Optimization - sheet.svg : Scaling \ No newline at end of file +Muhammad Ahsanu Nadia: + ui/help.svg (https://thenounproject.com/icon/help-6778522/) diff --git a/assets/ui/caret/down.svg b/assets/ui/caret/down.svg new file mode 100644 index 0000000..5c15836 --- /dev/null +++ b/assets/ui/caret/down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/ui/caret/right.svg b/assets/ui/caret/right.svg new file mode 100644 index 0000000..3b19a49 --- /dev/null +++ b/assets/ui/caret/right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/ui/chat-bubble.svg b/assets/ui/chat-bubble.svg new file mode 100644 index 0000000..a9182c1 --- /dev/null +++ b/assets/ui/chat-bubble.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/ui/close.svg b/assets/ui/close.svg new file mode 100644 index 0000000..3082802 --- /dev/null +++ b/assets/ui/close.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/ui/garbage-bin.svg b/assets/ui/garbage-bin.svg new file mode 100644 index 0000000..dd96a44 --- /dev/null +++ b/assets/ui/garbage-bin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/ui/help.svg b/assets/ui/help.svg new file mode 100644 index 0000000..bd06071 --- /dev/null +++ b/assets/ui/help.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/ui/minus.svg b/assets/ui/minus.svg new file mode 100644 index 0000000..d1d3e94 --- /dev/null +++ b/assets/ui/minus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/ui/pencil.svg b/assets/ui/pencil.svg new file mode 100644 index 0000000..455379f --- /dev/null +++ b/assets/ui/pencil.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/create.svg b/assets/ui/plus.svg similarity index 100% rename from assets/create.svg rename to assets/ui/plus.svg diff --git a/assets/ui/sheet.svg b/assets/ui/sheet.svg new file mode 100644 index 0000000..32a3268 --- /dev/null +++ b/assets/ui/sheet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/augments.d.ts b/augments.d.ts new file mode 100644 index 0000000..df16590 --- /dev/null +++ b/augments.d.ts @@ -0,0 +1,9 @@ +interface Actor { + /** The system-specific data */ + system: any; +}; + +interface Item { + /** The system-specific data */ + system: any; +}; diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 0000000..2304d94 --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "types": [ + "./augments.d.ts" + ] + } +} \ No newline at end of file diff --git a/langs/en-ca.2.json b/langs/en-ca.2.json new file mode 100644 index 0000000..dce0efa --- /dev/null +++ b/langs/en-ca.2.json @@ -0,0 +1,129 @@ +{ + "dotdungeon": { + "stat": { + "build": "Build", + "meta": "Meta", + "presence": "Presence", + "hands": "Hands", + "tilt": "Tilt", + "rng": "RNG" + }, + "skills": { + "defense": "Defense", + "magic": "Magic", + "melee": "Melee", + "platforming": "Platforming", + "strength": "Strength", + "alchemy": "Alchemy", + "arcanum": "Arcanum", + "dreams": "Dreams", + "lore": "Lore", + "navigation": "Navigation", + "animal_handling": "Animal Handling", + "perception": "Perception", + "sneak": "Sneak", + "speech": "Speech", + "vibes": "Vibes", + "accuracy": "Accuracy", + "crafting": "Crafting", + "engineering": "Engineering", + "explosives": "Explosives", + "piloting": "Piloting" + }, + "trainingLevel": { + "untrained": "Untrained", + "trained": "Trained", + "expert": "Expert", + "locked": "Locked" + }, + "die": { + "d4": "d4", + "d6": "d6", + "d8": "d8", + "d10": "d10", + "d12": "d12", + "d20": "d20" + }, + "sheet": { + "actor": { + "v2": { + "stat-not-chosen": "Select a dice to see the {name} skills", + "skill-roll-locked": "@dotdungeon.trainingLevel.locked", + "toggle-item-information": "Toggle the item's extra information visibility", + "create-item": "Create @TYPES.Item.{type}" + } + }, + "item": { + "untyped": { + "quantity": "Quantity", + "usage": "Usage Cost", + "rarity": "Tier", + "no-description": "This item hasn't been described yet..." + } + } + }, + "rarity": { + "simple": "Simple", + "greater": "Greater", + "rare": "Rare", + "legendary": "Legendary" + }, + "location": { + "unknown": "@dotdungeon.common.empty", + "inventory": "Inventory", + "equipped": "Equipped", + "storage": "Storage" + }, + "default": { + "name": "(Unnamed @TYPES.{document}.{type})" + }, + "common": { + "send-to-chat": "Send to Chat", + "edit": "Edit", + "delete": "Delete", + "reset": "Reset", + "empty": "---", + "help": "Help", + "gm": "Server", + "view-larger": "View Larger" + }, + "sheet-names": { + "*DataSheet": "Data Sheet" + }, + "help-tooltips": { + "calculated-capacity": { + "title": "What is Calculated Capacity?", + "content": "

The calculated capacity is how much space in your inventory that the item will take up, the way it is calculated is determined by the item. Usually the main thing that affects the capacity is the item's quantity, but this can be turned off by the @dotdungeon.common.gm, which means that no matter the quantity it will only use up one capacity. The @dotdungeon.common.gm can also entirely disable capacity usage which will make the used capacity always be zero.

" + } + }, + "delete": { + "ActiveEffect": { + "title": "Delete Effect", + "content": "

Are you sure you would like to delete the active effect: {name}

" + } + } + }, + "TYPES": { + "Actor": { + "player": "Player", + "sync": "Sync" + }, + "Item": { + "aspect": "Aspect", + "weapon": "Weapon", + "armour": "Armour", + "equipment": "Equipment", + "foil": "Foil", + "pet": "Pet", + "structure": "Structure", + "service": "Service", + "material": "Materials", + "legendaryItem": "Legendary Item", + "spell": "Spell", + "untyped": "Custom" + }, + "ActiveEffect": { + "base": "Effect" + } + } +} \ No newline at end of file diff --git a/langs/en-ca.json b/langs/en-ca.json index e346d41..f2c7ceb 100644 --- a/langs/en-ca.json +++ b/langs/en-ca.json @@ -78,13 +78,17 @@ } }, "sheet-names": { - "PlayerSheet": "PC/PUG Sheet", + "PlayerSheet": { + "MVP": "MVP PC Sheet", + "v2": "PC Sheet" + }, "SyncSheet": { "basic": "Theme: Basic" }, "AspectSheet": "Aspect Sheet", "SpellSheet": "Spell Sheet", - "PetSheet": "Pet Sheet" + "PetSheet": "Pet Sheet", + "UntypedItemSheet": "Custom Item" }, "actor": { "pc": { @@ -253,6 +257,9 @@ }, "warn": { "negative-aspect-limit": "The Aspect limit must be 0 or greater" + }, + "info": { + "increased-item-quantity": "Increased the item quantity for: {name}" } }, "dialogs": { @@ -273,6 +280,12 @@ "title": "You want to kill your pet?!", "content": "Are you sure you would like to kill the pet: {name}

This action cannot be undone." } + }, + "untyped": { + "delete": { + "title": "Confirm Item Deletion", + "content": "Are you sure you would like to delete the item: {name}

This action cannot be undone." + } } }, "keyword": { @@ -316,6 +329,9 @@ }, "pet": { "name": "(Unnamed Pet)" + }, + "untyped": { + "name": "Unknown Item" } } }, diff --git a/module/components/icon.mjs b/module/components/icon.mjs new file mode 100644 index 0000000..8c70d40 --- /dev/null +++ b/module/components/icon.mjs @@ -0,0 +1,125 @@ +import { StyledShadowElement } from "./mixins/Styles.mjs"; + +/** +Attributes: +@property {string} name - The name of the icon, takes precedence over the path +@property {string} path - The path of the icon file +*/ +export class DotDungeonIcon extends StyledShadowElement(HTMLElement) { + static elementName = `dd-icon`; + static formAssociated = false; + + /* Stuff for the mixin to use */ + static _stylePath = `v3/components/icon.css`; + + + static _cache = new Map(); + #container; + /** @type {null | string} */ + _name; + /** @type {null | string} */ + _path; + + /* Stored IDs for all of the hooks that are in this component */ + #svgHmr; + + constructor() { + super(); + // this._shadow = this.attachShadow({ mode: `open`, delegatesFocus: true }); + + this.#container = document.createElement(`div`); + this._shadow.appendChild(this.#container); + }; + + _mounted = false; + async connectedCallback() { + super.connectedCallback(); + if (this._mounted) return; + + this._name = this.getAttribute(`name`); + this._path = this.getAttribute(`path`); + + /* + This converts all of the double-dash prefixed properties on the element to + CSS variables so that they don't all need to be provided by doing style="" + */ + for (const attrVar of this.attributes) { + if (attrVar.name?.startsWith(`var:`)) { + const prop = attrVar.name.replace(`var:`, ``); + this.style.setProperty(`--` + prop, attrVar.value); + }; + }; + + /* + Try to retrieve the icon if it isn't present, try the path then default to + the slot content, as then we can have a default per-icon usage + */ + let content; + if (this._name) { + content = await this.#getIcon(`./systems/dotdungeon/assets/${this._name}.svg`); + }; + + if (this._path && !content) { + content = await this.#getIcon(this._path); + }; + + if (content) { + this.#container.appendChild(content.cloneNode(true)); + }; + + /* + This is so that when we get an HMR event from Foundry we can appropriately + handle it using our logic to update the component and the icon cache. + */ + if (game.settings.get(`dotdungeon`, `devMode`)) { + this.#svgHmr = Hooks.on(`dd-hmr:svg`, (iconName, data) => { + if (this._name === iconName || this._path?.endsWith(data.path)) { + const svg = this.#parseSVG(data.content); + this.constructor._cache.set(iconName, svg); + this.#container.replaceChildren(svg.cloneNode(true)); + }; + }); + }; + + this._mounted = true; + }; + + disconnectedCallback() { + super.disconnectedCallback(); + if (!this._mounted) return; + + Hooks.off(`dd-hmr:svg`, this.#svgHmr); + + this._mounted = false; + }; + + async #getIcon(path) { + // Cache hit! + if (this.constructor._cache.has(path)) { + console.debug(`.dungeon | Icon ${path} cache hit`); + return this.constructor._cache.get(path); + }; + + const r = await fetch(path); + switch (r.status) { + case 200: + case 201: + break; + default: + console.error(`.dungeon | Failed to fetch icon: ${path}`); + return; + }; + + console.debug(`.dungeon | Adding icon ${path} to the cache`); + const svg = this.#parseSVG(await r.text()); + this.constructor._cache.set(path, svg); + return svg; + }; + + /** Takes an SVG string and returns it as a DOM node */ + #parseSVG(content) { + const temp = document.createElement(`div`); + temp.innerHTML = content; + return temp.querySelector(`svg`); + }; +}; diff --git a/module/components/incrementer.mjs b/module/components/incrementer.mjs new file mode 100644 index 0000000..68e426a --- /dev/null +++ b/module/components/incrementer.mjs @@ -0,0 +1,149 @@ +import { DotDungeonIcon } from "./icon.mjs"; +import { StyledShadowElement } from "./mixins/Styles.mjs"; + +/** +Attributes: +@property {string} name - The path to the value to update +@property {number} value - The actual value of the input +@property {number} min - The minimum value of the input +@property {number} max - The maximum value of the input +@property {number?} smallStep - The step size used for the buttons and arrow keys +@property {number?} largeStep - The step size used for the buttons + Ctrl and page up / down + +Styling: +- `--height`: Controls the height of the element + the width of the buttons (default: 1.25rem) +- `--width`: Controls the width of the number input (default 50px) +*/ +export class DotDungeonIncrementer extends StyledShadowElement(HTMLElement) { + static elementName = `dd-incrementer`; + static formAssociated = true; + + static _stylePath = `v3/components/incrementer.css`; + + _internals; + #input; + + _min; + _max; + _smallStep; + _largeStep; + + constructor() { + super(); + + // Form internals + this._internals = this.attachInternals(); + this._internals.role = `spinbutton`; + }; + + get form() { + return this._internals.form; + } + + get name() { + return this.getAttribute(`name`); + } + set name(value) { + this.setAttribute(`name`, value); + } + + get value() { + return this.getAttribute(`value`); + }; + set value(value) { + this.setAttribute(`value`, value); + }; + + get type() { + return `number`; + } + + connectedCallback() { + super.connectedCallback(); + this.replaceChildren(); + + // Attribute parsing / registration + const value = this.getAttribute(`value`); + this._min = parseInt(this.getAttribute(`min`) ?? 0); + this._max = parseInt(this.getAttribute(`max`) ?? 0); + this._smallStep = parseInt(this.getAttribute(`smallStep`) ?? 1); + this._largeStep = parseInt(this.getAttribute(`largeStep`) ?? 5); + + this._internals.ariaValueMin = this._min; + this._internals.ariaValueMax = this._max; + + const container = document.createElement(`div`); + + // The input that the user can see / modify + const input = document.createElement(`input`); + this.#input = input; + input.type = `number`; + input.ariaHidden = true; + input.min = this.getAttribute(`min`); + input.max = this.getAttribute(`max`); + input.addEventListener(`change`, this.#updateValue.bind(this)); + input.value = value; + + // plus button + const increment = document.createElement(DotDungeonIcon.elementName); + increment.setAttribute(`name`, `ui/plus`); + increment.setAttribute(`var:size`, `0.75rem`); + increment.setAttribute(`var:fill`, `currentColor`); + increment.ariaHidden = true; + increment.classList.value = `increment`; + increment.addEventListener(`mousedown`, this.#increment.bind(this)); + + // minus button + const decrement = document.createElement(DotDungeonIcon.elementName); + decrement.setAttribute(`name`, `ui/minus`); + decrement.setAttribute(`var:size`, `0.75rem`); + decrement.setAttribute(`var:fill`, `currentColor`); + decrement.ariaHidden = true; + decrement.classList.value = `decrement`; + decrement.addEventListener(`mousedown`, this.#decrement.bind(this)); + + // Construct the DOM + container.appendChild(decrement); + container.appendChild(input); + container.appendChild(increment); + this._shadow.appendChild(container); + + /* + This converts all of the namespace prefixed properties on the element to + CSS variables so that they don't all need to be provided by doing style="" + */ + for (const attrVar of this.attributes) { + if (attrVar.name?.startsWith(`var:`)) { + const prop = attrVar.name.replace(`var:`, ``); + this.style.setProperty(`--` + prop, attrVar.value); + }; + }; + }; + + #updateValue() { + let value = parseInt(this.#input.value); + if (this.getAttribute(`min`)) value = Math.max(this._min, value); + if (this.getAttribute(`max`)) value = Math.min(this._max, value); + this.#input.value = value; + this.value = value; + this.dispatchEvent(new Event(`change`, { bubbles: true })); + }; + + /** @param {Event} $e */ + #increment($e) { + $e.preventDefault(); + let value = parseInt(this.#input.value); + value += $e.ctrlKey ? this._largeStep : this._smallStep; + this.#input.value = value; + this.#updateValue(); + }; + + /** @param {Event} $e */ + #decrement($e) { + $e.preventDefault(); + let value = parseInt(this.#input.value); + value -= $e.ctrlKey ? this._largeStep : this._smallStep; + this.#input.value = value; + this.#updateValue(); + }; +}; diff --git a/module/components/index.mjs b/module/components/index.mjs new file mode 100644 index 0000000..f4d39e9 --- /dev/null +++ b/module/components/index.mjs @@ -0,0 +1,23 @@ +import { DotDungeonIncrementer } from "./incrementer.mjs"; +import { DotDungeonIcon } from "./icon.mjs"; + +const components = [ + DotDungeonIcon, + DotDungeonIncrementer, +]; + +export function registerCustomComponents() { + (CONFIG.CACHE ??= {}).componentListeners ??= []; + for (const component of components) { + if (!window.customElements.get(component.elementName)) { + console.debug(`.dungeon | Registering component "${component.elementName}"`); + window.customElements.define( + component.elementName, + component + ); + if (component.formAssociated) { + CONFIG.CACHE.componentListeners.push(component.elementName); + } + }; + } +}; diff --git a/module/components/mixins/Styles.mjs b/module/components/mixins/Styles.mjs new file mode 100644 index 0000000..33d5eb5 --- /dev/null +++ b/module/components/mixins/Styles.mjs @@ -0,0 +1,80 @@ +/** + * @param {HTMLElement} Base + */ +export function StyledShadowElement(Base) { + return class extends Base { + /** + * The path to the CSS that is loaded + * @type {string} + */ + static _stylePath; + + /** + * The stringified CSS to use + * @type {string} + */ + static _styles; + + /** + * The HTML element of the stylesheet + * @type {HTMLStyleElement} + */ + _style; + + /** @type {ShadowRoot} */ + _shadow; + + /** + * The hook ID for this element's CSS hot reload + * @type {number} + */ + #cssHmr; + + constructor() { + super(); + + this._shadow = this.attachShadow({ mode: `open` }); + this._style = document.createElement(`style`); + this._shadow.appendChild(this._style); + }; + + #mounted = false; + connectedCallback() { + if (this.#mounted) return; + + this._getStyles(); + + if (game.settings.get(`dotdungeon`, `devMode`)) { + this.#cssHmr = Hooks.on(`dd-hmr:css`, (data) => { + if (data.path.endsWith(this.constructor._stylePath)) { + this._style.innerHTML = data.content; + }; + }); + }; + + this.#mounted = true; + }; + + disconnectedCallback() { + if (!this.#mounted) return; + if (this.#cssHmr != null) { + Hooks.off(`dd-hmr:css`, this.#cssHmr); + this.#cssHmr = null; + }; + this.#mounted = false; + }; + + _getStyles() { + if (this.constructor._styles) { + this._style.innerHTML = this.constructor._styles; + } else { + fetch(`./systems/dotdungeon/.styles/${this.constructor._stylePath}`) + .then(r => r.text()) + .then(t => { + this.constructor._styles = t; + this._style.innerHTML = t; + }); + } + }; + }; +}; diff --git a/module/config.mjs b/module/config.mjs index 95150f6..56a0fb4 100644 --- a/module/config.mjs +++ b/module/config.mjs @@ -1,35 +1,71 @@ -const statDice = [ `d4`, `d6`, `d8`, `d10`, `d12`, `d20` ]; +export const statDice = [ `d4`, `d6`, `d8`, `d10`, `d12`, `d20` ]; -const trainingLevels = [``, `locked`, `+2`, `+4`]; +export const trainingLevels = [ + { key: "locked", label: "dotdungeon.trainingLevel.locked", value: -1 }, + { key: "untrained", label: "dotdungeon.trainingLevel.untrained", value: 0 }, + { key: "trained", label: "dotdungeon.trainingLevel.trained", value: 2 }, + { key: "expert", label: "dotdungeon.trainingLevel.expert", value: 4 }, +]; -const damageTypes = [ `slashing`, `piercing`, `smashing`, `gun`, `neon`, `shadow`, `solar` ]; +export const damageTypes = [ `slashing`, `piercing`, `smashing`, `gun`, `neon`, `shadow`, `solar` ]; -const ammoTypes = [`quivers`, `mags`, `cells`]; +export const ammoTypes = [`quivers`, `mags`, `cells`]; -const stats = [ `build`, `meta`, `presence`, `hands`, `tilt`, `rng` ]; +export const stats = [ `build`, `meta`, `presence`, `hands`, `tilt`, `rng` ]; -const buildSkills = [ `defense`, `magic`, `melee`, `platforming`, `strength`, ]; -const metaSkills = [ `alchemy`, `arcanum`, `dreams`, `lore`, `navigation`, ]; -const presenceSkills = [ `animal_handling`, `perception`, `sneak`, `speech`, `vibes`, ]; -const handsSkills = [ `accuracy`, `crafting`, `engineering`, `explosives`, `piloting`, ]; +export const buildSkills = [ `defense`, `magic`, `melee`, `platforming`, `strength`, ]; +export const metaSkills = [ `alchemy`, `arcanum`, `dreams`, `lore`, `navigation`, ]; +export const presenceSkills = [ `animal_handling`, `perception`, `sneak`, `speech`, `vibes`, ]; +export const handsSkills = [ `accuracy`, `crafting`, `engineering`, `explosives`, `piloting`, ]; -const allSkills = [ +export const allSkills = [ ...buildSkills, ...metaSkills, ...presenceSkills, ...handsSkills, ]; -const skills = { +export const skills = { build: buildSkills, meta: metaSkills, presence: presenceSkills, hands: handsSkills, }; -const itemTiers = [ - `simple`, `greater`, - `rare`, `legendary` +export const defaultItemTier = `simple`; +export const itemTiers = [ + { value: `simple`, label: `dotdungeon.rarity.simple` }, + { value: `greater`, label: `dotdungeon.rarity.greater` }, + { value: `rare`, label: `dotdungeon.rarity.rare` }, + { value: `legendary`, label: `dotdungeon.rarity.legendary` }, +]; + +export const syncMilestones = [ + { value: 20, andReturn: true }, + { value: 40, andReturn: false }, + { value: 60, andReturn: true }, + { value: 80, andReturn: false }, + { value: 100, andReturn: true }, +]; + +export const syncDice = `1d20`; + +export const localizerConfig = { + subKeyPattern: /@(?[a-zA-Z\.]+)/gm, + maxDepth: 10, +}; + +export const itemFilters = [ + `material`, + `untyped`, + `aspect`, + `weapon`, + `armour`, + `equipment`, + `foil`, + `pet`, + `structure`, + `service`, ]; export default { @@ -44,5 +80,10 @@ export default { handsSkills, allSkills, skills, + defaultItemTier, itemTiers, -}; \ No newline at end of file + syncMilestones, + syncDice, + localizerConfig, + itemFilters, +}; diff --git a/module/dialogs/DiceList.mjs b/module/dialogs/DiceList.mjs new file mode 100644 index 0000000..3c305c8 --- /dev/null +++ b/module/dialogs/DiceList.mjs @@ -0,0 +1,82 @@ +import { GenericDialog } from "./GenericDialog.mjs"; + +export class DiceList extends GenericDialog { + + constructor(mobActor) { + super({}, { title: `${mobActor.name}'s Dice List` }); + this.actor = mobActor; + this.dice = this.actor.system.dice.map((d) => ({ + ...d, + id: randomID(), + })); + }; + + static get defaultOptions() { + const opts = foundry.utils.mergeObject({ + ...super.defaultOptions, + template: `systems/dotdungeon/templates/dialogs/diceList.hbs`, + width: 275, + height: 400, + submitOnClose: false, + resizable: true, + }); + opts.classes?.push(`dotdungeon`); + return opts; + }; + + async getData() { + const ctx = await super.getData(); + ctx.dice = this.dice; + return ctx; + }; + + async activateListeners(html) { + super.activateListeners(html); + + if (!this.isEditable) return; + console.debug(`.dungeon | DiceList adding event listeners`); + + html.find(`[data-die-update]`) + .on(`change`, this.updateDieInMemoryOnly.bind(this)) + }; + + async _updateObject(_event, formData) { + const newDice = this.dice.map(d => { + return { + count: formData[`${d.id}.count`], + sides: formData[`${d.id}.sides`], + repeat: formData[`${d.id}.repeat`], + }; + }); + await this.actor.update({ "system.dice": newDice }); + }; + + updateDieInMemoryOnly($e) { + const target = $e.currentTarget; + const data = target.dataset; + const value = target.value; + const [ dieId, field ] = data.dieUpdate.split(`.`); + for (const die of this.dice) { + if (die.id === dieId) { + die[field] = value; + return + }; + }; + }; + + addDie() { + this.dice.push({ + count: 1, + sides: 2, + repeat: 1, + id: randomID(), + }); + this.render(); + }; + + deleteDie($e) { + const data = $e.currentTarget.dataset; + this.dice = this.dice.filter(d => d.id !== data.id); + this.render(); + }; +}; diff --git a/module/sheets/GenericItemSheet.mjs b/module/dialogs/GenericDialog.mjs similarity index 58% rename from module/sheets/GenericItemSheet.mjs rename to module/dialogs/GenericDialog.mjs index 7299004..1cd1c02 100644 --- a/module/sheets/GenericItemSheet.mjs +++ b/module/dialogs/GenericDialog.mjs @@ -1,23 +1,11 @@ import DOTDUNGEON from "../config.mjs"; -import { preloadIcons } from "../handlebars.mjs"; -export class GenericItemSheet extends ItemSheet { - _expanded = new Set(); +export class GenericDialog extends FormApplication { #propogatedSettings = [ `devMode`, - `showAvatarOnSheet`, - `playersCanChangeGroup`, - `resourcesOrSupplies`, ]; - activateListeners(html) { - super.activateListeners(html); - - if (!this.isEditable) return; - console.debug(`.dungeon | Adding event listeners for Generic Item: ${this.id}`); - }; - async getData() { const ctx = {}; @@ -31,12 +19,31 @@ export class GenericItemSheet extends ItemSheet { ctx.meta = { expanded: this._expanded, - idp: this.item.uuid, }; ctx.config = DOTDUNGEON; - ctx.icons = await preloadIcons(); + ctx.icons = CONFIG.CACHE.icons; return ctx; }; -}; \ No newline at end of file + + activateListeners(html) { + super.activateListeners(html); + + if (!this.isEditable) return; + console.debug(`.dungeon | Generic dialog adding listeners`); + + html.find(`[data-action]`) + .on(`click`, this._handleActionClick.bind(this)); + }; + + _handleActionClick($e) { + const data = $e.currentTarget.dataset; + if (!this[data.action]) return; + this[data.action].bind(this)($e); + }; + + closeNoSave() { + this.close({ submit: false, }); + }; +}; diff --git a/module/dialogs/diceSelect.js b/module/dialogs/diceSelect.js deleted file mode 100644 index e218c3a..0000000 --- a/module/dialogs/diceSelect.js +++ /dev/null @@ -1,27 +0,0 @@ -const diceOptions = [ - `d4`, - `d6`, - `d8`, - `d10`, - `d12`, - `d20` -] - -export default Dialog({ - title: `Die Selector`, - content: `

Select a Dice

`, - buttons: { - d4: { - label: "d4", - callback() { - console.log(`Selected a d4`) - } - }, - d6: { - label: "d6", - callback() { - console.log(`Selected a d6`) - } - } - } -}) \ No newline at end of file diff --git a/module/documents/ActiveEffect/GenericActiveEffect.mjs b/module/documents/ActiveEffect/GenericActiveEffect.mjs new file mode 100644 index 0000000..8ee70f3 --- /dev/null +++ b/module/documents/ActiveEffect/GenericActiveEffect.mjs @@ -0,0 +1,7 @@ +export class DotDungeonActiveEffect extends ActiveEffect { + + // Invert the logic of the disabled property so it's easier to modify via + // embedded controls + get enabled() { return !this.disabled }; + set enabled(newValue) { this.disabled = !newValue }; +}; diff --git a/module/documents/ActiveEffect/_proxy.mjs b/module/documents/ActiveEffect/_proxy.mjs new file mode 100644 index 0000000..4b51b54 --- /dev/null +++ b/module/documents/ActiveEffect/_proxy.mjs @@ -0,0 +1,42 @@ +import { DotDungeonActiveEffect } from "./GenericActiveEffect.mjs"; + +const classes = {}; + +const defaultClass = DotDungeonActiveEffect; + +export const ActiveEffectProxy = new Proxy(function () {}, { + construct(target, args) { + const [data] = args; + + if (!classes.hasOwnProperty(data.type)) { + return new defaultClass(...args); + } + + return new classes[data.type](...args); + }, + get(target, prop, receiver) { + + if (["create", "createDocuments"].includes(prop)) { + return function (data, options) { + if (data.constructor === Array) { + return data.map(i => ActiveEffectProxy.create(i, options)) + } + + if (!classes.hasOwnProperty(data.type)) { + return defaultClass.create(data, options); + } + + return classes[data.type].create(data, options); + }; + }; + + if (prop == Symbol.hasInstance) { + return function (instance) { + if (instance instanceof defaultClass) return true; + return Object.values(classes).some(i => instance instanceof i); + }; + }; + + return defaultClass[prop]; + }, +}); diff --git a/module/documents/Actor/GenericActor.mjs b/module/documents/Actor/GenericActor.mjs new file mode 100644 index 0000000..171af63 --- /dev/null +++ b/module/documents/Actor/GenericActor.mjs @@ -0,0 +1,49 @@ +export class DotDungeonActor extends Actor { + + /* + Using this to take a "snapshot" of the system data prior to applying AE's so + that the inputs can still have the non-modified value in them, while we still + provide all that data to AE's without needing to disable any inputs. + */ + prepareEmbeddedDocuments() { + this.preAE = foundry.utils.deepClone(this.system); + super.prepareEmbeddedDocuments(); + }; + + async createEmbeddedItem(defaults, opts = {}) { + let items = await this.createEmbeddedDocuments(`Item`, defaults); + if (!Array.isArray(items)) items = items ? [items] : []; + if (items.length == 0) { + throw new Error(`Failed to create any items`); + }; + this.sheet.render(); + if ( + game.settings.get(`dotdungeon`, `openEmbeddedOnCreate`) + && !opts.overrideSheetOpen + ) { + for (const item of items) { + item.sheet.render(true); + }; + }; + }; + + async preItemEmbed(item) { + + // Increases the quantity of already present items if they match via source + let embedded = this.itemTypes[item.type].find(i => { + return i.getFlag(`core`, `sourceId`) === `Item.${item.id}` + }); + if (embedded) { + await embedded.update({"system.quantity": embedded.system.quantity + 1}); + ui.notifications.info( + game.i18n.format( + `dotdungeon.notification.info.increased-item-quantity`, + { name: embedded.name, quantity: embedded.system.quantity } + ), + { console: false } + ); + return false; + }; + return true; + }; +}; diff --git a/module/documents/Actor/Handler.mjs b/module/documents/Actor/Handler.mjs deleted file mode 100644 index 342521a..0000000 --- a/module/documents/Actor/Handler.mjs +++ /dev/null @@ -1,92 +0,0 @@ -import PlayerActor from "./Player.mjs"; -import MobActor from "./Mob.mjs"; - -/** @extends {Actor} */ -export class ActorHandler extends Actor { - proxyTargets = { - player: PlayerActor, - mob: MobActor, - }; - - constructor(data, ctx) { - super(data, ctx); - }; - - /** @type {class|undefined} */ - get fn() { - return this.proxyTargets[this.type]; - }; - - async proxyFunction(funcName, ...args) { - if (!this.fn?.[funcName]) return; - return await this.fn?.[funcName].bind(this)(...args); - }; - - async openEmbeddedSheet($event) { - if (this.fn?.openEmbeddedSheet) { - this.fn.openEmbeddedSheet.bind(this)($event); - } else { - const data = $event.target.dataset; - let item = await fromUuid(data.embeddedEdit); - item?.sheet.render(true); - }; - }; - - async genericEmbeddedUpdate($event) { - if (this.fn?.genericEmbeddedUpdate) { - return this.fn.genericEmbeddedUpdate.bind(this)($event); - }; - const target = $event.delegateTarget; - const data = target.dataset; - const item = await fromUuid(data.embeddedId); - - let value = target.value; - switch (target.type) { - case "checkbox": value = target.checked; break; - }; - - await item?.update({ [data.embeddedUpdate]: value }); - }; - - async genericEmbeddedDelete($event) { - if (!this.fn?.genericEmbeddedDelete) return; - this.fn.genericEmbeddedDelete.bind(this)($event); - }; - - async genericEmbeddedCreate($event) { - const data = $event.currentTarget.dataset; - if (!this.fn?.[`createCustom${data.embeddedCreate}`]) return; - this.fn?.[`createCustom${data.embeddedCreate}`].bind(this)($event); - }; - - async genericSendToChat($event) { - const data = $event.currentTarget.dataset; - const type = data.messageType; - console.log(data) - if (this.fn?.[`send${type}ToChat`]) { - return await this.fn?.[`send${type}ToChat`].bind(this)($event); - }; - if (!data.messageContent) { - console.warn(`.dungeon | Tried to send a chat message with no content`); - return; - }; - let message = await ChatMessage.create({ - content: data.messageContent, - flavor: data.messageFlavor, - speaker: { actor: this.actor } - }); - message.render(); - }; - - /** - * @param {ItemHandler} item - * @returns {boolean} true to allow the document to be embedded - */ - async preItemEmbed(item) { - let type = item.type[0].toUpperCase() + item.type.slice(1); - if (this.fn?.[`pre${type}Embed`]) { - return await this.fn?.[`pre${type}Embed`].bind(this)(item); - }; - return true; - }; -}; diff --git a/module/documents/Actor/Mob.mjs b/module/documents/Actor/Mob.mjs index 7c645e4..cca7e96 100644 --- a/module/documents/Actor/Mob.mjs +++ b/module/documents/Actor/Mob.mjs @@ -1 +1,10 @@ -export default {}; \ No newline at end of file +import { DotDungeonActor } from "./GenericActor.mjs"; + +export class Mob extends DotDungeonActor { + getRollData() { + const data = { + initiative: this.system.initiative ?? 0, + }; + return data; + }; +}; diff --git a/module/documents/Actor/Player.mjs b/module/documents/Actor/Player.mjs index a1282c9..2c5840d 100644 --- a/module/documents/Actor/Player.mjs +++ b/module/documents/Actor/Player.mjs @@ -1,116 +1,51 @@ -import { ItemHandler } from "../Item/Handler.mjs"; +import { DotDungeonActor } from "./GenericActor.mjs"; -/** @this {Actor} */ -async function genericEmbeddedDelete($event) { - let data = $event.currentTarget.dataset; - let item = await fromUuid(data.embeddedId); +export class Player extends DotDungeonActor { - if (!item) { - ui.notifications.error( - `dotdungeon.notification.error.item-not-found`, - { console: false } + applyActiveEffects() { + super.applyActiveEffects(); + + /* + These are the (groups of) fields that ActiveEffects may modify safely and + remain editable in the sheet. This needs to be done because of default + Foundry behaviour that otherwise prevents these fields from being edited. + The deletes must use optional chaining otherwise they can cause issues + during the document preparation lifecycle as an actor with no AE's affecting + anything in one of these areas will result in these paths being undefined. + */ + delete this.overrides.system?.stats; + delete this.overrides.system?.skills; + }; + + async createCustomPet() { + const body = new URLSearchParams({ + number: 1, + animal: `Cat`, + "X-Requested-With": "fetch" + }); + const r = await fetch( + `https://randommer.io/pet-names`, + { + method: "POST", + body + } ); - return; + await this.createEmbeddedItem([{ + type: `pet`, + name: (await r.json())[0] ?? game.i18n.localize(`dotdungeon.defaults.pet.name`), + }]); }; - Dialog.confirm({ - title: game.i18n.format( - `dotdungeon.dialogs.${item.type}.delete.title`, - item - ), - content: game.i18n.format( - `dotdungeon.dialogs.${item.type}.delete.content`, - item - ), - yes: () => { - item.delete(); - }, - defaultYes: false, - }); -}; - -/** @this {Actor} */ -async function createCustomItem(defaults, opts = {}) { - let items = await this.createEmbeddedDocuments(`Item`, defaults); - if (items.length == 0) { - throw new Error(); + get atAspectLimit() { + let limit = game.settings.get(`dotdungeon`, `aspectLimit`); + return this.itemTypes.aspect.length >= limit; }; - this.sheet.render(); - if ( - game.settings.get(`dotdungeon`, `openEmbeddedOnCreate`) - && !opts.overrideSheetOpen - ) { - for (const item of items) { - item.sheet.render(true); + + getRollData() { + const data = { + initiative: this.system.stats.hands ?? 0, + stats: this.system.stats, }; + return data; }; }; - -/** @this {Actor} */ -async function createCustomAspect() { - await createCustomItem.bind(this)([{ - type: `aspect`, - name: game.i18n.format(`dotdungeon.defaults.aspect.name`), - }]); -}; - -/** @this {Actor} */ -async function createCustomSpell() { - await createCustomItem.bind(this)([{ - type: `spell`, - name: game.i18n.format(`dotdungeon.defaults.spell.name`), - }]); -}; - -/** @this {Actor} */ -async function createCustomPet() { - const body = new URLSearchParams({ - number: 1, - animal: `Cat`, - "X-Requested-With": "fetch" - }) - const r = await fetch( - `https://randommer.io/pet-names`, - { - method: "POST", - body - } - ); - await createCustomItem.bind(this)([{ - type: `pet`, - name: (await r.json())[0] ?? game.i18n.localize(`dotdungeon.defaults.pet.name`), - }]); -}; - -/** @this {Actor} */ -async function atAspectLimit() { - let limit = game.settings.get(`dotdungeon`, `aspectLimit`); - return this.itemTypes.aspect.length >= limit; -}; - -/** - * @param {ItemHandler} item - * @this {Actor} - */ -async function preAspectEmbed(item) { - if (await atAspectLimit.bind(this)()) { - ui.notifications.error( - game.i18n.format( - `dotdungeon.notification.error.aspect-limit-reached`, - { limit: game.settings.get(`dotdungeon`, `aspectLimit`) } - ), - { console: false } - ); - return false; - }; -}; - -export default { - atAspectLimit, - createCustomItem, - createCustomAspect, - createCustomSpell, - createCustomPet, - genericEmbeddedDelete, - preAspectEmbed, -}; \ No newline at end of file diff --git a/module/documents/Actor/Sync.mjs b/module/documents/Actor/Sync.mjs new file mode 100644 index 0000000..171a67c --- /dev/null +++ b/module/documents/Actor/Sync.mjs @@ -0,0 +1,56 @@ +import { DotDungeonActor } from "./GenericActor.mjs"; +import { syncMilestones } from "../../config.mjs"; + +export class Sync extends DotDungeonActor { + async useRestDie() { + let addToSync = await (new Roll(syncDice)).evaluate(); + await addToSync.toMessage({ + speaker: ChatMessage.getSpeaker({ actor: this.actor }), + flavor: `Sync Restoration`, + }); + this.update({ + "system.rest_dice": this.system.rest_dice - 1, + "system.value": this.system.value + addToSync.total, + }); + }; + + async _preUpdate(data, options) { + if (options.diff) { + if (data.system?.value != null) { + let currentSync = this.system.value; + let newSync = data.system.value; + + let minSync = Math.min(currentSync, newSync); + let maxSync = Math.max(currentSync, newSync); + let milestones = syncMilestones.filter( + m => minSync < m.value && m.value <= maxSync + ); + + if (milestones.length > 0) data.system.rest_dice ??= this.system.rest_dice; + + for (const milestone of milestones) { + // Damage + if (newSync < currentSync) { + if (!this.system.milestones_hit.has(milestone.value)) { + data.system.rest_dice += 1; + this.system.milestones_hit.add(milestone.value); + }; + } + + // Healing + else if (newSync > currentSync) { + if ( + this.system.milestones_hit.has(milestone.value) + && milestone.andReturn + && milestone.value <= newSync + ) { + this.system.milestones_hit.delete(milestone.value); + }; + }; + }; + + data.system.milestones_hit = [ ...this.system.milestones_hit ]; + }; + }; + }; +}; diff --git a/module/documents/Actor/_proxy.mjs b/module/documents/Actor/_proxy.mjs new file mode 100644 index 0000000..dd6cf6c --- /dev/null +++ b/module/documents/Actor/_proxy.mjs @@ -0,0 +1,49 @@ +import { DotDungeonActor } from "./GenericActor.mjs"; +import { Player } from "./Player.mjs"; +import { Sync } from "./Sync.mjs"; +import { Mob } from "./Mob.mjs"; + +const classes = { + player: Player, + mob: Mob, + sync: Sync, +}; + +const defaultClass = DotDungeonActor; + +export const ActorProxy = new Proxy(function () {}, { + construct(target, args) { + const [data] = args; + + if (!classes.hasOwnProperty(data.type)) { + return new defaultClass(...args); + } + + return new classes[data.type](...args); + }, + get(target, prop, receiver) { + + if (["create", "createDocuments"].includes(prop)) { + return function (data, options) { + if (data.constructor === Array) { + return data.map(i => ActorProxy.create(i, options)) + } + + if (!classes.hasOwnProperty(data.type)) { + return defaultClass.create(data, options); + } + + return classes[data.type].create(data, options); + }; + }; + + if (prop == Symbol.hasInstance) { + return function (instance) { + if (instance instanceof defaultClass) return true; + return Object.values(classes).some(i => instance instanceof i); + }; + }; + + return defaultClass[prop]; + }, +}); diff --git a/module/documents/Item/Aspect.mjs b/module/documents/Item/Aspect.mjs index 733e252..ea66b6c 100644 --- a/module/documents/Item/Aspect.mjs +++ b/module/documents/Item/Aspect.mjs @@ -1,10 +1,42 @@ -/** @this {ItemHandler} */ -async function _preCreate(_data, _options, _user) { - if (this.isEmbedded) { - return await this.actor?.preItemEmbed(this); +import { DotDungeonItem } from "./GenericItem.mjs"; + +const secondsInAMinute = 60; +const secondsInAnHour = 60 * secondsInAMinute; + +export class Aspect extends DotDungeonItem { + async _preCreate() { + if (this.isEmbedded) { + if (this.actor.atAspectLimit) { + ui.notifications.error( + game.i18n.format( + `dotdungeon.notification.error.aspect-limit-reached`, + { limit: game.settings.get(`dotdungeon`, `aspectLimit`) } + ), + { console: false } + ); + return false; + }; + + return await this.actor?.preItemEmbed(this); + }; + }; + + get friendlyDuration() { + let friendly = ``; + let duration = this.system.deactivateAfter; + if (duration >= secondsInAnHour) { + let hours = Math.floor(duration / secondsInAnHour); + friendly += `${hours}h`; + duration -= hours * secondsInAnHour; + }; + if (duration >= secondsInAMinute) { + let minutes = Math.floor(duration / secondsInAMinute); + friendly += `${minutes}m`; + duration -= minutes * secondsInAMinute; + }; + if (duration > 0) { + friendly += `${duration}s`; + }; + return friendly; }; }; - -export default { - _preCreate, -}; \ No newline at end of file diff --git a/module/documents/Item/GenericItem.mjs b/module/documents/Item/GenericItem.mjs new file mode 100644 index 0000000..786824e --- /dev/null +++ b/module/documents/Item/GenericItem.mjs @@ -0,0 +1,24 @@ +export class DotDungeonItem extends Item { + async _preCreate() { + if (this.isEmbedded) { + return await this.actor?.preItemEmbed(this); + }; + }; + + get usedCapacity() { + if (!this.system.uses_inventory_slot) return 0; + if (!this.system.quantity_affects_used_capacity) { + return 1; + }; + return this.system.quantity; + }; + + get availableLocations() { + return [ + { value: null, label: `dotdungeon.location.unknown` }, + { value: `inventory`, label: `dotdungeon.location.inventory` }, + { value: `equipped`, label: `dotdungeon.location.equipped` }, + { value: `storage`, label: `dotdungeon.location.storage` }, + ]; + }; +}; diff --git a/module/documents/Item/Handler.mjs b/module/documents/Item/Handler.mjs deleted file mode 100644 index c072abd..0000000 --- a/module/documents/Item/Handler.mjs +++ /dev/null @@ -1,34 +0,0 @@ -import AspectItem from "./Aspect.mjs"; -import SpellItem from "./Spell.mjs"; - -/** @extends {Item} */ -export class ItemHandler extends Item { - proxyTargets = { - aspect: AspectItem, - spell: SpellItem, - }; - - constructor(data, ctx) { - super(data, ctx); - }; - - /** @type {class|undefined} */ - get fn() { - return this.proxyTargets[this.type]; - }; - - async migrateSystemData() { - if (!this.fn?.migrateSystemData) return; - this.fn?.migrateSystemData.bind(this)(); - }; - - async proxyFunction(funcName, ...args) { - if (!this.fn?.[funcName]) return; - return await this.fn?.[funcName].bind(this)(...args); - }; - - async _preCreate(...args) { - if (!this.fn?._preCreate) return; - return this.fn?._preCreate.bind(this)(...args); - }; -}; diff --git a/module/documents/Item/Material.mjs b/module/documents/Item/Material.mjs new file mode 100644 index 0000000..c6c5b72 --- /dev/null +++ b/module/documents/Item/Material.mjs @@ -0,0 +1,15 @@ +import { DotDungeonItem } from "./GenericItem.mjs"; + +export class Material extends DotDungeonItem { + get usedCapacity() { + let affects = game.settings.get(`dotdungeon`, `materialsAffectCapacity`); + return affects ? super.usedCapacity : 0; + }; + + get availableLocations() { + return [ + { value: null, label: `dotdungeon.location.unknown` }, + { value: `inventory`, label: `dotdungeon.location.inventory` }, + ]; + }; +}; diff --git a/module/documents/Item/Spell.mjs b/module/documents/Item/Spell.mjs deleted file mode 100644 index ea7da45..0000000 --- a/module/documents/Item/Spell.mjs +++ /dev/null @@ -1,10 +0,0 @@ -import { ItemHandler } from "./Handler.mjs"; - -/** @this {ItemHandler} */ -async function migrateSystemData() { - this.system -}; - -export default { - migrateSystemData, -}; diff --git a/module/documents/Item/_proxy.mjs b/module/documents/Item/_proxy.mjs new file mode 100644 index 0000000..b579136 --- /dev/null +++ b/module/documents/Item/_proxy.mjs @@ -0,0 +1,47 @@ +import { DotDungeonItem } from "./GenericItem.mjs"; +import { Aspect } from "./Aspect.mjs"; +import { Material } from "./Material.mjs"; + +const classes = { + aspect: Aspect, + material: Material, +}; + +const defaultClass = DotDungeonItem; + +export const ItemProxy = new Proxy(function () {}, { + construct(target, args) { + const [data] = args; + + if (!classes.hasOwnProperty(data.type)) { + return new defaultClass(...args); + } + + return new classes[data.type](...args); + }, + get(target, prop, receiver) { + + if (["create", "createDocuments"].includes(prop)) { + return function (data, options) { + if (data.constructor === Array) { + return data.map(i => ItemProxy.create(i, options)) + } + + if (!classes.hasOwnProperty(data.type)) { + return defaultClass.create(data, options); + } + + return classes[data.type].create(data, options); + }; + }; + + if (prop == Symbol.hasInstance) { + return function (instance) { + if (instance instanceof defaultClass) return true; + return Object.values(classes).some(i => instance instanceof i); + }; + }; + + return defaultClass[prop]; + }, +}); diff --git a/module/dotdungeon.mjs b/module/dotdungeon.mjs index 49c6374..8731314 100644 --- a/module/dotdungeon.mjs +++ b/module/dotdungeon.mjs @@ -1,4 +1,7 @@ // Data Models +import { DescribedItemData } from "./models/Item/DescribedItemData.mjs"; +import { CommonItemData } from "./models/Item/CommonItemData.mjs"; +import { WeaponItemData } from "./models/Item/Weapon.mjs"; import { AspectItemData } from "./models/Item/Aspect.mjs"; import { SpellItemData } from "./models/Item/Spell.mjs"; import { PlayerData } from "./models/Actor/Player.mjs"; @@ -7,16 +10,21 @@ import { SyncData } from "./models/Actor/Sync.mjs"; import { MobData } from "./models/Actor/Mob.mjs"; // Main Documents -import { ActorHandler } from "./documents/Actor/Handler.mjs"; -import { ItemHandler } from "./documents/Item/Handler.mjs"; +import { ActiveEffectProxy } from "./documents/ActiveEffect/_proxy.mjs"; +import { ActorProxy } from "./documents/Actor/_proxy.mjs"; +import { ItemProxy } from "./documents/Item/_proxy.mjs"; -// Character Sheets -import { SpellSheet } from "./sheets/SpellSheet.mjs"; -import { AspectSheet } from "./sheets/AspectSheet.mjs"; -import { PlayerSheet } from "./sheets/PlayerSheet.mjs"; +// Item Sheets +import { UntypedItemSheet } from "./sheets/Items/UntypedItemSheet.mjs"; +import { AspectSheet } from "./sheets/Items/AspectSheet.mjs"; +import { SpellSheet } from "./sheets/Items/SpellSheet.mjs"; +import { PetSheet } from "./sheets/Items/PetSheet.mjs"; + +// Actor Sheets import { BasicSyncSheet } from "./sheets/SyncVariations/BasicSyncSheet.mjs"; +import { PlayerSheetv2 } from "./sheets/Actors/PC/PlayerSheetV2.mjs"; +import { MVPPCSheet } from "./sheets/MVPPCSheet.mjs"; import { MobSheet } from "./sheets/MobSheet.mjs"; -import { PetSheet } from "./sheets/PetSheet.mjs"; // Utility imports import * as hbs from "./handlebars.mjs"; @@ -25,31 +33,44 @@ import * as hbs from "./handlebars.mjs"; import "./hooks/hotReload.mjs"; // Misc Imports +import { registerCustomComponents } from "./components/index.mjs"; import loadSettings from "./settings/index.mjs"; +import { devInit } from "./hooks/devInit.mjs"; import DOTDUNGEON from "./config.mjs"; -Hooks.once(`init`, () => { +Hooks.once(`init`, async () => { console.debug(`.dungeon | Initializing`); + CONFIG.ActiveEffect.legacyTransferral = false; loadSettings(); CONFIG.Actor.dataModels.player = PlayerData; CONFIG.Actor.dataModels.sync = SyncData; CONFIG.Actor.dataModels.mob = MobData; + CONFIG.Item.dataModels.untyped = DescribedItemData; + CONFIG.Item.dataModels.material = CommonItemData; + CONFIG.Item.dataModels.foil = DescribedItemData; + CONFIG.Item.dataModels.weapon = WeaponItemData; CONFIG.Item.dataModels.aspect = AspectItemData; CONFIG.Item.dataModels.spell = SpellItemData; CONFIG.Item.dataModels.pet = PetItemData; - CONFIG.Actor.documentClass = ActorHandler; - CONFIG.Item.documentClass = ItemHandler; + CONFIG.Actor.documentClass = ActorProxy; + CONFIG.Item.documentClass = ItemProxy; + CONFIG.ActiveEffect.documentClass = ActiveEffectProxy; CONFIG.DOTDUNGEON = DOTDUNGEON; - // Actors.unregisterSheet("core", ActorSheet); - Actors.registerSheet("dotdungeon", PlayerSheet, { + + Actors.registerSheet("dotdungeon", MVPPCSheet, { makeDefault: true, types: ["player"], - label: "dotdungeon.sheet-names.PlayerSheet" + label: "dotdungeon.sheet-names.PlayerSheet.MVP" + }); + Actors.registerSheet("dotdungeon", PlayerSheetv2, { + makeDefault: false, + types: ["player"], + label: "dotdungeon.sheet-names.PlayerSheet.v2" }); Actors.registerSheet("dotdungeon", MobSheet, { makeDefault: true, @@ -62,6 +83,13 @@ Hooks.once(`init`, () => { label: "dotdungeon.sheet-names.SyncSheet.basic" }); + Items.registerSheet("dotdungeon", UntypedItemSheet, { + makeDefault: true, + label: "dotdungeon.sheet-names.UntypedItemSheet", + }); + Items.unregisterSheet("dotdungeon", UntypedItemSheet, { + types: ["aspect"], + }); Items.registerSheet("dotdungeon", AspectSheet, { makeDefault: true, types: ["aspect"], @@ -75,11 +103,16 @@ Hooks.once(`init`, () => { Items.registerSheet("dotdungeon", PetSheet, { makeDefault: true, types: ["pet"], - lable: "dotdungeon.sheet-names.PetSheet" - }) + label: "dotdungeon.sheet-names.PetSheet" + }); + + if (true || game.settings.get(`dotdungeon`, `devMode`)) { + devInit(); + }; hbs.registerHandlebarsHelpers(); hbs.preloadHandlebarsTemplates(); + registerCustomComponents(); }); @@ -89,10 +122,10 @@ Hooks.once(`ready`, () => { let defaultTab = game.settings.get(`dotdungeon`, `defaultTab`); if (defaultTab) { if (!ui.sidebar?.tabs?.[defaultTab]) { - console.error(`Couldn't find a sidebar tab with ID:`, defaultTab); + console.error(`.dungeon | Couldn't find a sidebar tab with ID:`, defaultTab); } else { - console.debug(`Switching sidebar tab to:`, defaultTab); + console.debug(`.dungeon | Switching sidebar tab to:`, defaultTab); ui.sidebar.tabs[defaultTab].activate(); }; }; -}); \ No newline at end of file +}); diff --git a/module/handlebars.mjs b/module/handlebars.mjs index 8cec531..cdaa1b7 100644 --- a/module/handlebars.mjs +++ b/module/handlebars.mjs @@ -7,7 +7,7 @@ export const partials = [ `partials/panel.hbs`, `items/aspect.hbs`, - // All of the partials for the PC sheet panels + // All of the partials for the PC MVP sheet panels `actors/char-sheet-mvp/panels/aspect.pc.hbs`, `actors/char-sheet-mvp/panels/backpack.pc.hbs`, `actors/char-sheet-mvp/panels/mounts.pc.hbs`, @@ -18,27 +18,32 @@ export const partials = [ `actors/char-sheet-mvp/panels/pets.pc.hbs`, `actors/char-sheet-mvp/panels/sync.pc.hbs`, `actors/char-sheet-mvp/panels/weapons.pc.hbs`, + + // The v2 PC sheet partials + `actors/char-sheet/v2/partials/stats.v2.pc.hbs`, + `actors/char-sheet/v2/partials/effects.v2.pc.hbs`, + `actors/char-sheet/v2/partials/inventory/inventory.v2.pc.hbs`, + `actors/char-sheet/v2/partials/inventory/player.v2.pc.hbs`, + `actors/char-sheet/v2/partials/inventory/item-list.v2.pc.hbs`, + `actors/char-sheet/v2/partials/inventory/storage.v2.pc.hbs`, + `actors/char-sheet/v2/partials/inventory/items/material.v2.pc.hbs`, + `actors/char-sheet/v2/partials/inventory/items/untyped.v2.pc.hbs`, + `actors/char-sheet/v2/partials/inventory/items/aspect.v2.pc.hbs`, + `actors/char-sheet/v2/partials/inventory/items/weapon.v2.pc.hbs`, + `actors/char-sheet/v2/partials/inventory/items/pet.v2.pc.hbs`, + + // The v2 Untyped sheet partials + `items/untyped/v2/tabs/general.v2.untyped.hbs`, + `items/untyped/v2/tabs/details.v2.untyped.hbs`, + `items/untyped/v2/tabs/effects.v2.untyped.hbs`, + `items/untyped/v2/tabs/settings.v2.untyped.hbs`, ]; -export const icons = [ - `caret-right.svg`, - `garbage-bin.svg`, - `chat-bubble.svg`, - `dice/d4.svg`, - `dice/d6.svg`, - `dice/d8.svg`, - `dice/d10.svg`, - `dice/d12.svg`, - `dice/d20.svg`, - `create.svg`, - `close.svg`, - `edit.svg`, - `sheet.svg`, -]; - +export const preAliasedPartials = { + "dotdungeon.pc.v2.foil": "actors/char-sheet/v2/partials/inventory/items/untyped.v2.pc.hbs", +}; export async function registerHandlebarsHelpers() { - console.log(Handlebars) Handlebars.registerHelper(helpers); }; @@ -48,6 +53,10 @@ export async function preloadHandlebarsTemplates() { const paths = {}; + for (const alias in preAliasedPartials) { + paths[alias] = `${pathPrefix}${preAliasedPartials[alias]}`; + }; + for ( const partial of partials ) { console.debug(`Loading partial: ${partial}`); const path = `${pathPrefix}${partial}`; @@ -69,42 +78,3 @@ export async function preloadHandlebarsTemplates() { console.groupEnd(); return loadTemplates(paths); }; - -/** - * Loads all of the icons that are needed in the handlebars templating to make - * the sheet look nicer. - * - * @returns An object containing icon names to the corresponding HTML data for - * displaying the icon - */ -export async function preloadIcons() { - console.groupCollapsed(`.dungeon | Loading icons for handlebars`); - const pathPrefix = `systems/dotdungeon/assets/` - const parsedIcons = {}; - - for (const icon of icons) { - const iconName = icon.split(`/`).slice(-1)[0].slice(0, -4); - if (icon.endsWith(`.svg`)) { - try { - const response = await fetchWithTimeout(`${pathPrefix}${icon}`); - if (response.status !== 200) { continue }; - const svgData = await response.text(); - parsedIcons[iconName] = svgData; - console.debug(`Loaded icon: ${icon}`); - } catch { - console.error(`Failed to fetch/parse icon: ${icon}`); - continue; - }; - } - else if (icon.endsWith(`.png`)) { - parsedIcons[iconName] = ``; - console.debug(`Loaded icon: ${icon}`); - } - else { - console.warn(`Icon "${icon}" failed to be handled by a loader`) - }; - }; - - console.groupEnd(); - return parsedIcons; -}; \ No newline at end of file diff --git a/module/helpers/createArray.mjs b/module/helpers/createArray.mjs index 6fb6500..d92c6b7 100644 --- a/module/helpers/createArray.mjs +++ b/module/helpers/createArray.mjs @@ -1,3 +1,3 @@ export function createArray(...args) { return args.slice(0, -1); -}; \ No newline at end of file +}; diff --git a/module/helpers/detailsExpanded.mjs b/module/helpers/detailsExpanded.mjs index 0a277aa..6094849 100644 --- a/module/helpers/detailsExpanded.mjs +++ b/module/helpers/detailsExpanded.mjs @@ -11,4 +11,4 @@ export function detailsExpanded(expanded, collapseId) { return `open`; } return ``; -}; \ No newline at end of file +}; diff --git a/module/helpers/index.mjs b/module/helpers/index.mjs index 6489fb4..b48baa3 100644 --- a/module/helpers/index.mjs +++ b/module/helpers/index.mjs @@ -2,20 +2,24 @@ import { schemaOptions } from "./schemaOptions.mjs"; import { createArray } from "./createArray.mjs"; import { detailsExpanded } from "./detailsExpanded.mjs"; import { objectValue } from "./objectValue.mjs"; -import { toFriendlyDuration } from "./toFriendlyDuration.mjs"; +import { handlebarsLocalizer, localizer } from "../utils/localizer.mjs"; +import { options } from "./options.mjs"; export default { // Complex helpers "dd-schemaOptions": schemaOptions, "dd-array": createArray, - "dd-toFriendlyDuration": toFriendlyDuration, "dd-objectValue": objectValue, "dd-expanded": detailsExpanded, + "dd-i18n": handlebarsLocalizer, + "dd-options": options, // Simple helpers "dd-stringify": v => JSON.stringify(v, null, ` `), "dd-empty": v => v.length == 0, + "dd-set-has": (s, k) => s.has(k), + "dd-empty-state": (v) => v ?? localizer(`dotdungeon.common.empty`), // Logic helpers "eq": (a, b) => a == b, @@ -28,4 +32,4 @@ export default { "xor": (a, b) => (a || b) && !(a && b), "xnor": (a, b) => !((a || b) && !(a && b)), "defined": v => v != null, -}; \ No newline at end of file +}; diff --git a/module/helpers/objectValue.mjs b/module/helpers/objectValue.mjs index be22aed..43f3913 100644 --- a/module/helpers/objectValue.mjs +++ b/module/helpers/objectValue.mjs @@ -8,4 +8,4 @@ export function objectValue(obj, keypath) { }; let resp = helper(obj, keypath.string.split(`.`)); return resp; -}; \ No newline at end of file +}; diff --git a/module/helpers/options.mjs b/module/helpers/options.mjs new file mode 100644 index 0000000..97a2d62 --- /dev/null +++ b/module/helpers/options.mjs @@ -0,0 +1,35 @@ +import { localizer } from "../utils/localizer.mjs"; + +/** + * @typedef {object} Option + * @property {string} [label] + * @property {string|number} value + * @property {boolean} [disabled] + */ + +/** + * @param {string | number} selected + * @param {Array` + ); + }; + return htmlOptions.join(`\n`); +}; diff --git a/module/helpers/schemaOptions.mjs b/module/helpers/schemaOptions.mjs index 24f3b78..32ca167 100644 --- a/module/helpers/schemaOptions.mjs +++ b/module/helpers/schemaOptions.mjs @@ -7,4 +7,4 @@ export function schemaOptions(document, schemaPath) { } return CONFIG.Actor.dataModels.player.schema.fields.weapon.fields.mainHand.fields.damage.options.options; -}; \ No newline at end of file +}; diff --git a/module/helpers/toFriendlyDuration.mjs b/module/helpers/toFriendlyDuration.mjs deleted file mode 100644 index 9a2ad30..0000000 --- a/module/helpers/toFriendlyDuration.mjs +++ /dev/null @@ -1,26 +0,0 @@ -const secondsInAMinute = 60; -const secondsInAnHour = 60 * secondsInAMinute; - - -/** - * Converts a duration into a more human-friendly format - * @param {number} duration The length of time in seconds - * @returns The human-friendly time string - */ -export function toFriendlyDuration(duration) { - let friendly = ``; - if (duration >= secondsInAnHour) { - let hours = Math.floor(duration / secondsInAnHour); - friendly += `${hours}h`; - duration -= hours * secondsInAnHour; - }; - if (duration >= secondsInAMinute) { - let minutes = Math.floor(duration / secondsInAMinute); - friendly += `${minutes}m`; - duration -= minutes * secondsInAMinute; - }; - if (duration > 0) { - friendly += `${duration}s`; - }; - return friendly; -}; \ No newline at end of file diff --git a/module/hooks/devInit.mjs b/module/hooks/devInit.mjs new file mode 100644 index 0000000..b7ef326 --- /dev/null +++ b/module/hooks/devInit.mjs @@ -0,0 +1,27 @@ +/* +Initialization of dev-specific features for the init hook, this is primarily +used to register all of the data sheets of various entity types. +*/ + +import { GroupDataSheet } from "../sheets/Datasheets/GroupDataSheet.mjs"; +import { UntypedDataSheet } from "../sheets/Datasheets/UntypedDataSheet.mjs"; + +export function devInit() { + Items.registerSheet( + `dotdungeon`, + UntypedDataSheet, + { + types: [`untyped`, `foil`], + label: `dotdungeon.sheet-names.*DataSheet`, + } + ); + + Actors.registerSheet( + `dotdungeon`, + GroupDataSheet, + { + types: [`sync`], + label: `dotdungeon.sheet-names.*DataSheet`, + } + ); +}; diff --git a/module/hooks/hotReload.mjs b/module/hooks/hotReload.mjs index ee4d283..8c44a46 100644 --- a/module/hooks/hotReload.mjs +++ b/module/hooks/hotReload.mjs @@ -1,34 +1,47 @@ import * as hbs from "../handlebars.mjs"; +const loaders = { + svg(data) { + const iconName = data.path.split(`/`).slice(-1)[0].slice(0, -4); + console.debug(`.dungeon | hot-reloading icon: ${iconName}`); + Hooks.call(`dd-hmr:svg`, iconName, data); + }, + hbs(data) { + if (!hbs.partials.some(p => data.path.endsWith(p))) { + return true; + }; + + // Compile the new template data. + let template; + try { + template = Handlebars.compile(data.content); + } catch (err) { + return console.error(err); + }; + + // Re-register our new partial template & cache it. + const alias = data.path + .split(`/`) + .pop() + .split(`.`) + .slice(0, -1) + .reverse() + .join(`.`); + const templateName = `dotdungeon.${alias}`; + Handlebars.registerPartial(templateName, template); + _templateCache[templateName] = template; + + return false; + }, + js() {window.location.reload()}, + mjs() {window.location.reload()}, + css(data) { + console.debug(`.dungeon | Hot-reloading CSS: ${data.path}`); + Hooks.call(`dd-hmr:css`, data); + }, +}; + Hooks.on(`hotReload`, async (data) => { - if (data.extension !== 'hbs') { - return true; - }; - - if (!hbs.partials.some(p => data.path.endsWith(p))) { - return true; - }; - - - // Compile the new template data. - let template; - try { - template = Handlebars.compile(data.content); - } catch (err) { - return console.error(err); - }; - - // Re-register our new partial template & cache it. - const alias = data.path - .split(`/`) - .pop() - .split(`.`) - .slice(0, -1) - .reverse() - .join(`.`); - const templateName = `dotdungeon.${alias}`; - Handlebars.registerPartial(templateName, template); - _templateCache[templateName] = template; - - return false; -}); \ No newline at end of file + if (!loaders[data.extension]) return; + return loaders[data.extension](data); +}); diff --git a/module/models/Actor/Mob.mjs b/module/models/Actor/Mob.mjs index e56c572..57d6e40 100644 --- a/module/models/Actor/Mob.mjs +++ b/module/models/Actor/Mob.mjs @@ -2,9 +2,6 @@ export class MobData extends foundry.abstract.TypeDataModel { static defineSchema() { const fields = foundry.data.fields; return { - dice: new fields.StringField({ - initial: ``, - }), bonus: new fields.NumberField({ initial: 0, nullable: false, @@ -25,6 +22,36 @@ export class MobData extends foundry.abstract.TypeDataModel { initial: ``, blank: true, }), + immune: new fields.StringField({ + initial: ``, + blank: true, + }), + weak: new fields.StringField({ + initial: ``, + blank: true, + }), + bytes: new fields.NumberField({ + initial: 0, + min: 0, + }), + description: new fields.StringField({ + initial: ``, + blank: true, + }), + dice: new fields.ArrayField( + new fields.SchemaField({ + // {count}d{sides} x {repeat} + count: new fields.NumberField({ min: 1 }), + sides: new fields.NumberField({ min: 2 }), + repeat: new fields.NumberField({ min: 1 }), + }), + { initial: [] } + ), }; }; + + // Called during create, read, and update + static migrateData(source) { + return source; + }; }; diff --git a/module/models/Actor/Player.mjs b/module/models/Actor/Player.mjs index 7baeb04..244c887 100644 --- a/module/models/Actor/Player.mjs +++ b/module/models/Actor/Player.mjs @@ -1,4 +1,4 @@ -import { MappingField } from "../fields/MappingField.mjs"; +import DOTDUNGEON from "../../config.mjs"; function diceChoiceField() { return new foundry.data.fields.StringField({ @@ -6,33 +6,17 @@ function diceChoiceField() { blank: true, trim: true, options() { - return CONFIG.DOTDUNGEON.statDice; + return DOTDUNGEON.statDice; }, }); }; function trainingLevelField() { - return new foundry.data.fields.StringField({ - initial: ``, - blank: true, - trim: true, - options: CONFIG.DOTDUNGEON.trainingLevels, - }); -}; - -function weaponDamageTypeField() { - return new foundry.data.fields.StringField({ - initial: ``, - blank: true, - options: [ ``, ...CONFIG.DOTDUNGEON.damageTypes ], - }); -}; - -function ammoTypeField() { - return new foundry.data.fields.StringField({ - initial: ``, - blank: true, - options: [ ``, ...CONFIG.DOTDUNGEON.ammoTypes ], + return new foundry.data.fields.NumberField({ + initial: 0, + min: -1, + integer: true, + options: Object.values(DOTDUNGEON.trainingLevels), }); }; @@ -40,6 +24,14 @@ export class PlayerData extends foundry.abstract.TypeDataModel { static defineSchema() { const fields = foundry.data.fields; return { + /* + These are special data properties that will be used by ActiveEffects + to modify certain limits within the actor, allowing for neat hacks + that change these + */ + weapon_slots: new fields.NumberField({ initial: 2 }), + inventory_slots: new fields.NumberField({ initial: 0 }), + bytes: new fields.NumberField({ initial: 0, min: 0, @@ -83,67 +75,18 @@ export class PlayerData extends foundry.abstract.TypeDataModel { piloting: trainingLevelField(), }) }), - aspect: new fields.SchemaField({ - name: new fields.StringField({ blank: true, trim: true }), - description: new fields.StringField({ blank: true, trim: true }), - deactivateAfter: new fields.NumberField({ min: 0, integer: true}), - used: new fields.BooleanField(), - }), + // ! Delete roles: new fields.SchemaField({ r1: new fields.StringField({ blank: true, trim: true }), r2: new fields.StringField({ blank: true, trim: true }), r3: new fields.StringField({ blank: true, trim: true }), r4: new fields.StringField({ blank: true, trim: true }), }), - weapon: new fields.SchemaField({ - mainHand: new fields.SchemaField({ - name: new fields.StringField({ blank: true, trim: true }), - damage: weaponDamageTypeField(), - ranged: new fields.BooleanField({ initial: false }), - scope: new fields.BooleanField({ initial: false }), - ammo: ammoTypeField(), - }), - offHand: new fields.SchemaField({ - name: new fields.StringField({ blank: true, trim: true }), - damage: weaponDamageTypeField(), - ranged: new fields.BooleanField({ initial: false }), - scope: new fields.BooleanField({ initial: false }), - ammo: ammoTypeField(), - }), - ammo: new fields.SchemaField({ - quivers: new fields.NumberField({ min: 0, max: 10, integer: true }), - mags: new fields.NumberField({ min: 0, max: 10, integer: true }), - cells: new fields.NumberField({ min: 0, max: 10, integer: true }), - }), - }), supplies: new fields.NumberField({ initial: 0, min: 0, - max: 5, integer: true }), - materials: new fields.NumberField({ - initial: 0, - min: 0, - max: 5, - integer: true - }), - pet: new fields.SchemaField({ - name: new fields.StringField(), - info: new fields.StringField(), - }), - transport: new fields.SchemaField({ - name: new fields.StringField(), - upkeep: new fields.NumberField({ min: 0, integer: true }), - info: new fields.StringField(), - }), - spells: new MappingField( - new fields.SchemaField({ - name: new fields.StringField({ initial: ``, blank: true, trim: true }), - cost: new fields.NumberField({ initial: 0, min: 0 }), - info: new fields.StringField({ initial: ``, blank: true, trim: true }), - }) - ), respawns: new fields.SchemaField({ r1: new fields.BooleanField(), r2: new fields.BooleanField(), @@ -154,7 +97,6 @@ export class PlayerData extends foundry.abstract.TypeDataModel { integer: true, initial: 0, }), - inventoryString: new fields.StringField({ blank: true, trim: true }), }; }; -}; \ No newline at end of file +}; diff --git a/module/models/Actor/Sync.mjs b/module/models/Actor/Sync.mjs index 64bf260..9d8082e 100644 --- a/module/models/Actor/Sync.mjs +++ b/module/models/Actor/Sync.mjs @@ -3,10 +3,18 @@ export class SyncData extends foundry.abstract.TypeDataModel { const fields = foundry.data.fields; return { value: new fields.NumberField({ - required: true, integer: true, initial: 100, }), + rest_dice: new fields.NumberField({ + integer: true, + initial: 0, + min: 0, + }), + milestones_hit: new fields.SetField( + new fields.NumberField({ integer: true, }), + { initial: [] }, + ), }; }; -}; \ No newline at end of file +}; diff --git a/module/models/Item/Aspect.mjs b/module/models/Item/Aspect.mjs index 23d1f04..92b3eac 100644 --- a/module/models/Item/Aspect.mjs +++ b/module/models/Item/Aspect.mjs @@ -1,11 +1,19 @@ -export class AspectItemData extends foundry.abstract.TypeDataModel { +import { DescribedItemData } from "./DescribedItemData.mjs"; + +export class AspectItemData extends DescribedItemData { static defineSchema() { const fields = foundry.data.fields; - return { + const parentSchema = super.defineSchema(); + + // Purge fields that I don't want in this schema + delete parentSchema.quantity; + delete parentSchema.quantity_affects_used_capacity; + delete parentSchema.usage_cost; + + return foundry.utils.mergeObject(parentSchema, { used: new fields.BooleanField({ initial: false }), /** The number of seconds that the effect of the aspect stays */ deactivateAfter: new fields.NumberField({ nullable: true }), - info: new fields.HTMLField({ nullable: true, blank: false, trim: true }), - }; + }); }; -}; \ No newline at end of file +}; diff --git a/module/models/Item/CommonItemData.mjs b/module/models/Item/CommonItemData.mjs index a8ad598..9e11800 100644 --- a/module/models/Item/CommonItemData.mjs +++ b/module/models/Item/CommonItemData.mjs @@ -4,18 +4,46 @@ export class CommonItemData extends foundry.abstract.TypeDataModel { static defineSchema() { const fields = foundry.data.fields; return { + quantity: new fields.NumberField({ + initial: 1, + min: 0, + nullable: false, + integer: true, + }), + uses_inventory_slot: new fields.BooleanField({ + initial: true, + nullable: false, + }), + quantity_affects_used_capacity: new fields.BooleanField({ + initial: true, + nullable: false, + }), buy: new fields.NumberField({ initial: null, nullable: true, + integer: true, }), usage_cost: new fields.NumberField({ initial: null, nullable: true, + integer: true, }), tier: new fields.StringField({ - initial: `simple`, + initial: DOTDUNGEON.defaultItemTier, + nullable: false, + choices: DOTDUNGEON.itemTiers.map(tier => tier.value), + }), + /* + If this property is set to true, the item will be shown in the combat tab + list of items. This is shown whether or not the item is marked as "equipped". + */ + combat_relevant: new fields.BooleanField({ + initial: false, + nullable: false, + }), + location: new fields.StringField({ + initial: "", nullable: false, - choices: DOTDUNGEON.itemTiers, }), }; }; diff --git a/module/models/Item/DescribedItemData.mjs b/module/models/Item/DescribedItemData.mjs index ebfce48..eb913a0 100644 --- a/module/models/Item/DescribedItemData.mjs +++ b/module/models/Item/DescribedItemData.mjs @@ -3,7 +3,7 @@ import { CommonItemData } from "./CommonItemData.mjs"; export class DescribedItemData extends CommonItemData { static defineSchema() { const fields = foundry.data.fields; - return mergeObject(super.defineSchema(), { + return foundry.utils.mergeObject(super.defineSchema(), { description: new fields.StringField({ initial: ``, blank: true, diff --git a/module/models/Item/Equipment.mjs b/module/models/Item/Equipment.mjs index 6e9d7f9..309fd3b 100644 --- a/module/models/Item/Equipment.mjs +++ b/module/models/Item/Equipment.mjs @@ -3,12 +3,6 @@ import { DescribedItemData } from "./DescribedItemData.mjs"; export class EquipmentItemData extends DescribedItemData { static defineSchema() { const fields = foundry.data.fields; - return mergeObject(super.defineSchema(), { - extra_inventory: new fields.NumberField({ - initial: null, - nullable: true, - required: false, - }), - }); + return foundry.utils.mergeObject(super.defineSchema(), {}); }; }; diff --git a/module/models/Item/Pet.mjs b/module/models/Item/Pet.mjs index 7d46e94..34c6de5 100644 --- a/module/models/Item/Pet.mjs +++ b/module/models/Item/Pet.mjs @@ -3,7 +3,13 @@ import { DescribedItemData } from "./DescribedItemData.mjs"; export class PetItemData extends DescribedItemData { static defineSchema() { const fields = foundry.data.fields; - return mergeObject(super.defineSchema(), { + const parentSchema = super.defineSchema(); + + delete parentSchema.quantity; + delete parentSchema.quantity_affects_used_capacity; + delete parentSchema.usage_cost; + + return foundry.utils.mergeObject(parentSchema, { upkeep: new fields.NumberField({ initial: null, nullable: true }), pokeballd: new fields.BooleanField({ initial: true }), }); diff --git a/module/models/Item/Spell.mjs b/module/models/Item/Spell.mjs index 00bab71..f96381f 100644 --- a/module/models/Item/Spell.mjs +++ b/module/models/Item/Spell.mjs @@ -4,7 +4,7 @@ import DOTDUNGEON from "../../config.mjs"; export class SpellItemData extends DescribedItemData { static defineSchema() { const fields = foundry.data.fields; - return mergeObject(super.defineSchema(), { + return foundry.utils.mergeObject(super.defineSchema(), { skill: new fields.StringField({ initial: ``, blank: true, @@ -18,4 +18,4 @@ export class SpellItemData extends DescribedItemData { }), }); }; -}; \ No newline at end of file +}; diff --git a/module/models/Item/Transportation.mjs b/module/models/Item/Transportation.mjs deleted file mode 100644 index fd1f427..0000000 --- a/module/models/Item/Transportation.mjs +++ /dev/null @@ -1,28 +0,0 @@ -import { DescribedItemData } from "./DescribedItemData.mjs"; - -export class TransportationItemData extends DescribedItemData { - static defineSchema() { - const fields = foundry.data.fields; - return mergeObject(super.defineSchema(), { - single_trip: new fields.NumberField({ - initial: null, - nullable: true, - }), - upkeep: new fields.NumberField({ - initial: null, - nullable: true, - }), - can_be_in_inventory: new fields.BooleanField({ - initial: false, - }), - inventory_slots: new fields.NumberField({ - initial: 0, - min: 0, - }), - logon_bonus: new fields.NumberField({ - initial: null, - nullable: true, - }) - }); - }; -}; diff --git a/module/models/Item/Weapon.mjs b/module/models/Item/Weapon.mjs new file mode 100644 index 0000000..20db6b7 --- /dev/null +++ b/module/models/Item/Weapon.mjs @@ -0,0 +1,24 @@ +import { DescribedItemData } from "./DescribedItemData.mjs"; +import DOTDUNGEON from "../../config.mjs"; + +export class WeaponItemData extends DescribedItemData { + static defineSchema() { + const fields = foundry.data.fields; + return foundry.utils.mergeObject(super.defineSchema(), { + damage: new fields.StringField({ + initial: null, + nullable: true, + blank: true, + options: DOTDUNGEON.damageTypes, + }), + ranged: new fields.BooleanField({ initial: false, }), + scoped: new fields.BooleanField({ initial: false, }), + ammo: new fields.StringField({ + initial: null, + nullable: true, + blank: true, + options: DOTDUNGEON.ammoTypes, + }), + }); + }; +}; diff --git a/module/models/fields/MappingField.mjs b/module/models/fields/MappingField.mjs index 9d62f66..3547be7 100644 --- a/module/models/fields/MappingField.mjs +++ b/module/models/fields/MappingField.mjs @@ -129,4 +129,4 @@ export class MappingField extends foundry.data.fields.ObjectField { path.shift(); return this.model._getField(path); } -} \ No newline at end of file +}; diff --git a/module/models/template.mjs b/module/models/template.mjs index f3cea2a..77340ee 100644 --- a/module/models/template.mjs +++ b/module/models/template.mjs @@ -3,7 +3,7 @@ import { DescribedItemData } from "./DescribedItemData.mjs"; export class TemplateData extends DescribedItemData { static defineSchema() { const fields = foundry.data.fields; - return mergeObject(super.defineSchema(), { + return foundry.utils.mergeObject(super.defineSchema(), { }); }; }; diff --git a/module/settings/client_settings.mjs b/module/settings/client_settings.mjs index c3e568f..703c94a 100644 --- a/module/settings/client_settings.mjs +++ b/module/settings/client_settings.mjs @@ -18,4 +18,4 @@ export default function() { default: true, requiresReload: false, }); -}; \ No newline at end of file +}; diff --git a/module/settings/dev_settings.mjs b/module/settings/dev_settings.mjs index 939afda..51c278c 100644 --- a/module/settings/dev_settings.mjs +++ b/module/settings/dev_settings.mjs @@ -13,4 +13,4 @@ export default function() { config: false, requiresReload: false, }); -}; \ No newline at end of file +}; diff --git a/module/settings/index.mjs b/module/settings/index.mjs index 0a0b83f..5ad91cc 100644 --- a/module/settings/index.mjs +++ b/module/settings/index.mjs @@ -6,4 +6,4 @@ export default function registerSettings() { registerClientSettings(); registerWorldSettings(); registerDevSettings(); -}; \ No newline at end of file +}; diff --git a/module/settings/world_settings.mjs b/module/settings/world_settings.mjs index 314ae3b..cac1757 100644 --- a/module/settings/world_settings.mjs +++ b/module/settings/world_settings.mjs @@ -9,6 +9,16 @@ export default function() { requiresReload: false, }); + game.settings.register(`dotdungeon`, `materialsAffectCapacity`, { + name: `dotdungeon.settings.materialsAffectCapacity.name`, + hint: `dotdungeon.settings.materialsAffectCapacity.description`, + scope: `world`, + config: true, + type: Boolean, + default: true, + requiresReload: false, + }); + game.settings.register(`dotdungeon`, `resourcesOrSupplies`, { name: `dotdungeon.settings.resourcesOrSupplies.name`, hint: `dotdungeon.settings.resourcesOrSupplies.description`, @@ -59,4 +69,4 @@ export default function() { game.settings.set(`dotdungeon`, `preSaveAspectLimit`, floored); }, }); -}; \ No newline at end of file +}; diff --git a/module/sheets/Actors/PC/PlayerSheetV2.mjs b/module/sheets/Actors/PC/PlayerSheetV2.mjs new file mode 100644 index 0000000..ac1a378 --- /dev/null +++ b/module/sheets/Actors/PC/PlayerSheetV2.mjs @@ -0,0 +1,172 @@ +import { GenericActorSheet } from "../../GenericActorSheet.mjs"; +import DOTDUNGEON from "../../../config.mjs"; +import { localizer } from "../../../utils/localizer.mjs"; +import { modifierToString } from "../../../utils/modifierToString.mjs"; +import { GenericContextMenu } from "../../../utils/GenericContextMenu.mjs"; + +export class PlayerSheetv2 extends GenericActorSheet { + static get defaultOptions() { + let opts = foundry.utils.mergeObject( + super.defaultOptions, + { + template: `systems/dotdungeon/templates/actors/char-sheet/v2/sheet.hbs`, + tabs: [ + { + group: `page`, + navSelector: `nav.page`, + contentSelector: `.page-content`, + initial: `inventory`, + }, + { + group: `inventory`, + navSelector: `nav.inventory`, + contentSelector: `.tab[data-tab="inventory"]`, + initial: `player`, + } + ], + } + ); + opts.classes.push(`style-v3`); + return opts; + }; + + activateListeners(html) { + super.activateListeners(html); + + if (this.document.isEmbedded) return; + if (!this.isEditable) return; + console.debug(`.dungeon | Adding event listeners for Actor: ${this.id}`); + + html.find(`.create-ae`).on(`click`, async ($e) => { + console.debug(`Creating an ActiveEffect?`); + const ae = this.actor.createEmbeddedDocuments(`ActiveEffect`, [{name: "Default AE"}]); + ae.sheet.render(true); + }); + html.find(`[data-filter-toggle]`).on(`change`, ($e) => { + const target = $e.delegateTarget; + const filter = target.dataset.filterToggle; + this.toggleItemFilter(filter); + this._renderInner(); + }); + + // Make materials be able to be edited/deleted + new GenericContextMenu(html, `.material`, [ + { + name: localizer(`dotdungeon.common.edit`), + callback: (html) => { + const data = html[0].dataset; + this.openEmbeddedSheet.bind(this)(data.embeddedId); + }, + }, + { + name: localizer(`dotdungeon.common.delete`), + callback: (html) => { + const data = html[0].dataset; + this.genericEmbeddedDelete.bind(this)(data.embeddedId); + }, + }, + ]); + }; + + async getData() { + const ctx = await super.getData(); + /** @type {ActorHandler} */ + const actor = this.actor; + + ctx.preAE = actor.preAE; + ctx.system = actor.system; + ctx.flags = actor.flags; + ctx.items = this.actor.itemTypes; + + ctx.computed = { + canChangeGroup: ctx.settings.playersCanChangeGroup || ctx.isGM, + canAddAspect: !this.actor.atAspectLimit, + stats: this.#statData, + itemFilters: this.#itemFilters, + noItemTypesVisible: this._itemTypesHidden.size === DOTDUNGEON.itemFilters.length, + capacity: this.#inventoryCapacity, + }; + console.log(ctx) + return ctx; + }; + + get #statData() { + const stats = []; + const usedDice = new Set(Object.values(this.actor.system.stats)); + for (const statName in this.actor.system.stats) { + const stat = { + key: statName, + name: localizer(`dotdungeon.stat.${statName}`), + original: this.actor.preAE.stats[statName], + value: this.actor.system.stats[statName], + }; + + /* + Determine what dice are available to the user in the dropdown + selector. Disables all dice options that are selected, but not used + by this stat. + */ + stat.dieOptions = [ + { label: `---`, value: `` }, + ...DOTDUNGEON.statDice.map(die => { + return { + value: die, + label: localizer(`dotdungeon.die.${die}`, { stat: statName }), + disabled: usedDice.has(die) && this.actor.preAE.stats[statName] !== die, + }; + }) + ]; + + /* + Calculating the data needed in order to display all of the skills + for this character. + */ + stat.skills = []; + for (const skill in this.actor.system.skills[statName]) { + const value = this.actor.system.skills[statName][skill]; + stat.skills.push({ + key: skill, + name: game.i18n.format(`dotdungeon.skills.${skill}`), + value, + original: this.actor.preAE.skills[statName][skill], + formula: `1` + stat.value + modifierToString(value, { spaces: true }), + rollDisabled: this.actor.preAE.skills[statName][skill] === -1, + }); + }; + + stats.push(stat); + }; + return stats; + }; + + _itemTypesHidden = new Set([`armour`, `equipment`, `structure`, `service`]); + toggleItemFilter(filterName) { + if (this._itemTypesHidden.has(filterName)) { + this._itemTypesHidden.delete(filterName); + } else { + this._itemTypesHidden.add(filterName); + }; + this.render(); + }; + + get #itemFilters() { + const types = DOTDUNGEON.itemFilters; + const filters = {}; + for (const type of types) { + filters[type] = { + label: localizer(`TYPES.Item.${type}`), + active: !this._itemTypesHidden.has(type), + createLabel: localizer(`dotdungeon.sheet.actor.v2.create-item`, {type}), + }; + }; + return filters; + }; + + get #inventoryCapacity() { + return { + used: this.actor.items + .reduce((sum, i) => sum + i.usedCapacity, 0), + max: this.actor.system.inventory_slots, + }; + }; +} diff --git a/module/sheets/Datasheets/GroupDataSheet.mjs b/module/sheets/Datasheets/GroupDataSheet.mjs new file mode 100644 index 0000000..6c9527a --- /dev/null +++ b/module/sheets/Datasheets/GroupDataSheet.mjs @@ -0,0 +1,31 @@ +export class GroupDataSheet extends ActorSheet { + static get defaultOptions() { + let opts = foundry.utils.mergeObject( + super.defaultOptions, + { + template: `systems/dotdungeon/templates/datasheets/actor/group.hbs`, + width: 200, + height: 275 + }, + ); + opts.classes.push(`dotdungeon`, `style-v3`); + return opts; + }; + + async getData() { + const ctx = {}; + + ctx.actor = this.actor; + ctx.system = this.actor.system; + + ctx.computed = { + milestones_hit_viewable: [...this.actor.system.milestones_hit.values()].join(`, `) + } + + ctx.meta = { + idp: this.actor.uuid, + }; + + return ctx; + }; +}; diff --git a/module/sheets/Datasheets/UntypedDataSheet.mjs b/module/sheets/Datasheets/UntypedDataSheet.mjs new file mode 100644 index 0000000..c3d7702 --- /dev/null +++ b/module/sheets/Datasheets/UntypedDataSheet.mjs @@ -0,0 +1,27 @@ +export class UntypedDataSheet extends ItemSheet { + static get defaultOptions() { + let opts = foundry.utils.mergeObject( + super.defaultOptions, + { + template: `systems/dotdungeon/templates/datasheets/untyped.hbs`, + width: 650, + height: 700 + }, + ); + opts.classes.push(`dotdungeon`, `style-v3`); + return opts; + }; + + async getData() { + const ctx = {}; + + ctx.item = this.item; + ctx.system = this.item.system; + + ctx.meta = { + idp: this.item.uuid, + }; + + return ctx; + }; +}; diff --git a/module/sheets/GenericActorSheet.mjs b/module/sheets/GenericActorSheet.mjs index 1dcc4c8..2010304 100644 --- a/module/sheets/GenericActorSheet.mjs +++ b/module/sheets/GenericActorSheet.mjs @@ -1,7 +1,18 @@ +import { localizer } from "../utils/localizer.mjs"; import DOTDUNGEON from "../config.mjs"; -import { preloadIcons } from "../handlebars.mjs"; export class GenericActorSheet extends ActorSheet { + static get defaultOptions() { + let opts = foundry.utils.mergeObject( + super.defaultOptions, + { + scrollY: [`.scrollable`], + } + ); + opts.classes.push(`dotdungeon`); + return opts; + }; + _expanded = new Set(); #propogatedSettings = [ @@ -29,7 +40,7 @@ export class GenericActorSheet extends ActorSheet { ctx.actor = this.actor; ctx.config = DOTDUNGEON; - ctx.icons = await preloadIcons(); + ctx.icons = {}; return ctx; }; @@ -41,26 +52,49 @@ export class GenericActorSheet extends ActorSheet { if (!this.isEditable) return; console.debug(`.dungeon | Generic sheet adding listeners`); - html.find(`summary`).on(`click`, this._handleSummaryToggle.bind(this)); - html.find(`.roll`).on(`click`, this._handleRoll.bind(this)); - html.find(`[data-embedded-update]`) - .on(`change`, this.actor.genericEmbeddedUpdate.bind(this.actor)); + /* + Custom element event listeners because Foundry doesn't listen to them by + default. + */ + html.find( + CONFIG.CACHE.componentListeners.map(n => `${n}[name]`).join(`,`) + ).on(`change`, () => this._onChangeInput.bind(this)); + + /* + Utility event listeners that apply + */ + html.find(`[data-collapse-id]`).on(`click`, this._handleSummaryToggle.bind(this)); + html.find(`[data-roll-formula]`).on(`click`, this._handleRoll.bind(this)); + html.find(`[data-embedded-update-on="change"]`) + .on(`change`, this.genericEmbeddedUpdate.bind(this)); + html.find(`[data-embedded-update-on="blur"]`) + .on(`blur`, this.genericEmbeddedUpdate.bind(this)); html.find(`[data-embedded-delete]`) - .on(`click`, this.actor.genericEmbeddedDelete.bind(this.actor)); + .on(`click`, ($e) => { + const id = $e.currentTarget.dataset.embeddedDelete; + this.genericEmbeddedDelete.bind(this)(id); + }); html.find(`[data-embedded-create]`) - .on(`click`, this.actor.genericEmbeddedCreate.bind(this.actor)); + .on(`click`, this.genericEmbeddedCreate.bind(this)); html.find(`[data-message-type]`) - .on(`click`, this.actor.genericSendToChat.bind(this.actor)); + .on(`click`, this.genericSendToChat.bind(this)); html.find(`[data-embedded-edit]`) - .on(`click`, this.actor.openEmbeddedSheet.bind(this.actor)); + .on(`click`, ($e) => { + const id = $e.currentTarget.dataset.embeddedEdit; + this.openEmbeddedSheet.bind(this)(id); + }) + html.find(`button[data-increment]`) + .on(`click`, this._incrementValue.bind(this)); + html.find(`button[data-decrement]`) + .on(`click`, this._decrementValue.bind(this)); + html.find(`button[data-embedded-increment]`) + .on(`click`, this.genericEmbeddedIncrement.bind(this)); + html.find(`button[data-embedded-decrement]`) + .on(`click`, this.genericEmbeddedDecrement.bind(this)); }; async _handleRoll($e) { let data = $e.currentTarget.dataset; - if (!data.rollFormula) { - console.warn(`.dungeon | Element has .roll class with no roll formula`, $e.target); - return; - }; console.debug(`.dungeon | Attempting to roll with formula "${data.rollFormula}"`); let flavor; @@ -76,21 +110,139 @@ export class GenericActorSheet extends ActorSheet { }); }; - _handleSummaryToggle($e) { - let data = $e.currentTarget.dataset; - let open = $e.currentTarget.parentNode.open; - console.debug(`.dungeon | Collapse ID: ${data.collapseId} (open: ${open})`); + async _incrementValue($e) { + const target = $e.currentTarget; + const data = target.dataset; + const value = getProperty(this.actor, data.increment); + if (typeof value != "number") { + return; + }; + this.actor.update({ [data.increment]: value + 1 }); + }; - /* - This seeming inversion of logic is due to the fact that this handler - gets called before the element is updated to include/reflect the - change, so if the parentNode doesn't actually have it, then we're - opening it and vice-versa. - */ - if (!open) { + async _decrementValue($e) { + const target = $e.currentTarget; + const data = target.dataset; + const value = getProperty(this.actor, data.decrement); + if (typeof value != "number") { + return; + }; + this.actor.update({ [data.decrement]: value - 1 }); + }; + + async _handleSummaryToggle($e) { + $e.stopPropagation(); + let target = $e.currentTarget; + let parent = target.closest(`.collapse`); + let data = target.dataset; + console.debug(`.dungeon | Collapse ID: ${data.collapseId}`); + + if (!this._expanded.has(data.collapseId)) { this._expanded.add(data.collapseId); + parent.setAttribute(`open`, ``); } else { this._expanded.delete(data.collapseId); + parent.removeAttribute(`open`, ``); }; }; -}; \ No newline at end of file + + async openEmbeddedSheet(item_id) { + let item = await fromUuid(item_id); + item?.sheet.render(true); + }; + + async genericEmbeddedCreate($event) { + const data = $event.currentTarget.dataset; + if (!this[`createCustom${data.embeddedCreate}`]) { + this.actor.createEmbeddedItem({ + type: data.embeddedCreate, + name: localizer( + `dotdungeon.default.name`, + { document: `Item`, type: data.embeddedCreate } + ), + }); + } else { + this[`createCustom${data.embeddedCreate}`]($event); + }; + }; + + async genericEmbeddedUpdate($event) { + const target = $event.currentTarget; + const data = target.dataset; + const item = await fromUuid(data.embeddedId); + + let value = target.value; + switch (target.type) { + case "checkbox": value = target.checked; break; + }; + + await item?.update({ [data.embeddedUpdate]: value }); + }; + + async genericEmbeddedIncrement($event) { + const target = $event.currentTarget; + const data = target.dataset; + const item = await fromUuid(data.embeddedId); + const value = getProperty(item, data.embeddedIncrement); + if (typeof value != "number") { + return; + }; + await item?.update({ [data.embeddedIncrement]: value + 1 }); + }; + + async genericEmbeddedDecrement($event) { + const target = $event.currentTarget; + const data = target.dataset; + const item = await fromUuid(data.embeddedId); + const value = getProperty(item, data.embeddedDecrement); + if (typeof value != "number") { + return; + }; + await item?.update({ [data.embeddedDecrement]: value - 1 }); + }; + + async genericEmbeddedDelete(item_uuid) { + let item = await fromUuid(item_uuid); + + if (!item) { + ui.notifications.error( + `dotdungeon.notification.error.item-not-found`, + { console: false } + ); + return; + }; + + Dialog.confirm({ + title: game.i18n.format( + `dotdungeon.dialogs.${item.type}.delete.title`, + item + ), + content: game.i18n.format( + `dotdungeon.dialogs.${item.type}.delete.content`, + item + ), + yes: () => { + item.delete(); + }, + defaultYes: false, + }); + }; + + async genericSendToChat($event) { + const data = $event.currentTarget.dataset; + const type = data.messageType; + if (this[`send${type}ToChat`]) { + return await this[`send${type}ToChat`]($event); + }; + if (!data.messageContent) { + console.warn(`.dungeon | Tried to send a chat message with no content`); + return; + }; + let message = await ChatMessage.create({ + content: data.messageContent, + flavor: data.messageFlavor, + speaker: { actor: this.actor }, + }); + message.render(); + }; +}; diff --git a/module/sheets/AspectSheet.mjs b/module/sheets/Items/AspectSheet.mjs similarity index 74% rename from module/sheets/AspectSheet.mjs rename to module/sheets/Items/AspectSheet.mjs index 976fa4d..2bf8638 100644 --- a/module/sheets/AspectSheet.mjs +++ b/module/sheets/Items/AspectSheet.mjs @@ -2,7 +2,7 @@ import { GenericItemSheet } from "./GenericItemSheet.mjs"; export class AspectSheet extends GenericItemSheet { static get defaultOptions() { - let opts = mergeObject( + let opts = foundry.utils.mergeObject( super.defaultOptions, { template: `systems/dotdungeon/templates/items/aspect.hbs`, @@ -22,14 +22,7 @@ export class AspectSheet extends GenericItemSheet { }; async getData() { - const ctx = {}; - const item = this.item; - - ctx.item = item; - ctx.system = item.system; - ctx.flags = item.flags; - - console.log(item.uuid, `context:`, ctx); + const ctx = await super.getData(); return ctx; }; -}; \ No newline at end of file +}; diff --git a/module/sheets/Items/GenericItemSheet.mjs b/module/sheets/Items/GenericItemSheet.mjs new file mode 100644 index 0000000..936bd6c --- /dev/null +++ b/module/sheets/Items/GenericItemSheet.mjs @@ -0,0 +1,86 @@ +import { DialogManager } from "../../utils/DialogManager.mjs"; +import DOTDUNGEON from "../../config.mjs"; + +export class GenericItemSheet extends ItemSheet { + _expanded = new Set(); + + #propogatedSettings = [ + `devMode`, + `showAvatarOnSheet`, + `playersCanChangeGroup`, + `resourcesOrSupplies`, + ]; + + async getData() { + const ctx = {}; + + // Send all of the settings that sheets need into their context + ctx.settings = {}; + for (const setting of this.#propogatedSettings) { + ctx.settings[setting] = game.settings.get(`dotdungeon`, setting); + }; + + ctx.isGM = game.users.current.hasRole(CONST.USER_ROLES.ASSISTANT); + + ctx.meta = { + expanded: this._expanded, + idp: this.item.uuid, + }; + + ctx.item = this.item; + ctx.system = this.item.system; + ctx.flags = this.item.flags; + ctx.effects = this.item.effects; + + ctx.config = DOTDUNGEON; + ctx.icons = {}; + + return ctx; + }; + + activateListeners(html) { + super.activateListeners(html); + + if (!this.isEditable) return; + console.debug(`.dungeon | Adding event listeners for Generic Item: ${this.id}`); + html.find(`button[data-increment]`) + .on(`click`, this._incrementValue.bind(this)); + html.find(`button[data-decrement]`) + .on(`click`, this._decrementValue.bind(this)); + + + html.find(`[data-help-id]`) + .on(`click`, this._helpPopup.bind(this)); + }; + + async _incrementValue($e) { + const target = $e.currentTarget; + const data = target.dataset; + const value = getProperty(this.actor, data.increment); + if (typeof value != "number") { + return; + }; + this.actor.update({ [data.increment]: value + 1 }); + }; + + async _decrementValue($e) { + const target = $e.currentTarget; + const data = target.dataset; + const value = getProperty(this.actor, data.decrement); + if (typeof value != "number") { + return; + }; + this.actor.update({ [data.decrement]: value - 1 }); + }; + + async _helpPopup($e) { + const target = $e.currentTarget; + const data = target.dataset; + if (!data.helpId) return; + DialogManager.helpDialog( + data.helpId, + data.helpContent, + data.helpTitle + ); + }; +}; diff --git a/module/sheets/PetSheet.mjs b/module/sheets/Items/PetSheet.mjs similarity index 83% rename from module/sheets/PetSheet.mjs rename to module/sheets/Items/PetSheet.mjs index f3955af..a2db3b9 100644 --- a/module/sheets/PetSheet.mjs +++ b/module/sheets/Items/PetSheet.mjs @@ -2,7 +2,7 @@ import { GenericItemSheet } from "./GenericItemSheet.mjs"; export class PetSheet extends GenericItemSheet { static get defaultOptions() { - let opts = mergeObject( + let opts = foundry.utils.mergeObject( super.defaultOptions, { template: `systems/dotdungeon/templates/items/pet.hbs`, @@ -23,10 +23,6 @@ export class PetSheet extends GenericItemSheet { async getData() { const ctx = await super.getData(); - - ctx.item = this.item; - ctx.system = this.item.system; - ctx.flags = this.item.flags; return ctx; }; }; diff --git a/module/sheets/SpellSheet.mjs b/module/sheets/Items/SpellSheet.mjs similarity index 83% rename from module/sheets/SpellSheet.mjs rename to module/sheets/Items/SpellSheet.mjs index 281408a..8e92831 100644 --- a/module/sheets/SpellSheet.mjs +++ b/module/sheets/Items/SpellSheet.mjs @@ -2,7 +2,7 @@ import { GenericItemSheet } from "./GenericItemSheet.mjs"; export class SpellSheet extends GenericItemSheet { static get defaultOptions() { - let opts = mergeObject( + let opts = foundry.utils.mergeObject( super.defaultOptions, { template: `systems/dotdungeon/templates/items/spell.hbs`, @@ -23,10 +23,6 @@ export class SpellSheet extends GenericItemSheet { async getData() { const ctx = await super.getData(); - - ctx.item = this.item; - ctx.system = this.item.system; - ctx.flags = this.item.flags; return ctx; }; }; diff --git a/module/sheets/Items/UntypedItemSheet.mjs b/module/sheets/Items/UntypedItemSheet.mjs new file mode 100644 index 0000000..5ae0f24 --- /dev/null +++ b/module/sheets/Items/UntypedItemSheet.mjs @@ -0,0 +1,116 @@ +import { GenericContextMenu } from "../../utils/GenericContextMenu.mjs"; +import { DialogManager } from "../../utils/DialogManager.mjs"; +import { GenericItemSheet } from "./GenericItemSheet.mjs"; +import { localizer } from "../../utils/localizer.mjs"; + +export class UntypedItemSheet extends GenericItemSheet { + static get defaultOptions() { + let opts = foundry.utils.mergeObject( + super.defaultOptions, + { + template: `systems/dotdungeon/templates/items/untyped/v2/index.hbs`, + width: 300, + height: 340, + tabs: [ + { + group: `page`, + navSelector: `nav.page`, + contentSelector: `.page-content`, + initial: `general`, + }, + ], + } + ); + opts.classes.push(`dotdungeon`, `style-v3`); + return opts; + }; + + activateListeners(html) { + super.activateListeners(html); + + new GenericContextMenu(html, `.photo.panel`, [ + { + name: localizer(`dotdungeon.common.view-larger`), + callback: () => { + (new ImagePopout(this.item.img)).render(true); + }, + }, + { + name: localizer(`dotdungeon.common.edit`), + condition: () => this.isEditable, + callback: () => { + const fp = new FilePicker({ + callback: (path) => { + this.item.update({"img": path}); + }, + }); + fp.render(true); + }, + }, + { + name: localizer(`dotdungeon.common.reset`), + condition: () => this.isEditable, + callback: () => { + console.log(`.dungeon | Reset Item Image`) + }, + } + ]); + + if (!this.isEditable) return; + console.debug(`.dungeon | Adding event listeners for Untyped Item: ${this.item.id}`); + + html.find(`.create-ae`).on(`click`, async () => { + await this.item.createEmbeddedDocuments( + `ActiveEffect`, + [{name: localizer(`dotdungeon.default.name`, { document: `ActiveEffect`, type: `base` })}], + { renderSheet: true } + ); + }); + + new GenericContextMenu(html, `.effect.panel`, [ + { + name: localizer(`dotdungeon.common.edit`), + callback: async (html) => { + (await fromUuid(html.closest(`.effect`)[0].dataset.embeddedId))?.sheet.render(true); + }, + }, + { + name: localizer(`dotdungeon.common.delete`), + callback: async (html) => { + const target = html.closest(`.effect`)[0]; + const data = target.dataset; + const id = data.embeddedId; + const doc = await fromUuid(id); + DialogManager.createOrFocus( + `${doc.uuid}-delete`, + { + title: localizer(`dotdungeon.delete.ActiveEffect.title`, doc), + content: localizer(`dotdungeon.delete.ActiveEffect.content`, doc), + buttons: { + yes: { + label: localizer(`Yes`), + callback() { + doc.delete(); + }, + }, + no: { + label: localizer(`No`), + } + } + } + ); + }, + } + ]); + }; + + async getData() { + const ctx = await super.getData(); + + ctx.meta.showSettingsTab = ctx.isGM || this.item.isOwned; + ctx.meta.isEmbedded = this.item.isOwned; + ctx.meta.isEditable = this.isEditable; + + return ctx; + }; +}; diff --git a/module/sheets/PlayerSheet.mjs b/module/sheets/MVPPCSheet.mjs similarity index 76% rename from module/sheets/PlayerSheet.mjs rename to module/sheets/MVPPCSheet.mjs index bf15fba..a5f05f5 100644 --- a/module/sheets/PlayerSheet.mjs +++ b/module/sheets/MVPPCSheet.mjs @@ -1,12 +1,11 @@ -import { ActorHandler } from "../documents/Actor/Handler.mjs"; import { GenericActorSheet } from "./GenericActorSheet.mjs"; -export class PlayerSheet extends GenericActorSheet { +export class MVPPCSheet extends GenericActorSheet { /** @override {ActorHandler} actor */ static get defaultOptions() { - let opts = mergeObject( + let opts = foundry.utils.mergeObject( super.defaultOptions, { template: `systems/dotdungeon/templates/actors/char-sheet-mvp/sheet.hbs` @@ -35,10 +34,9 @@ export class PlayerSheet extends GenericActorSheet { ctx.computed = { canChangeGroup: ctx.settings.playersCanChangeGroup || ctx.isGM, - canAddAspect: !await actor.proxyFunction.bind(actor)(`atAspectLimit`), + canAddAspect: !this.actor.atAspectLimit, }; - console.log(actor.uuid, `context:`, ctx) return ctx; }; -}; \ No newline at end of file +}; diff --git a/module/sheets/MobSheet.mjs b/module/sheets/MobSheet.mjs index 2b359df..b2f2213 100644 --- a/module/sheets/MobSheet.mjs +++ b/module/sheets/MobSheet.mjs @@ -1,14 +1,14 @@ -import { ActorHandler } from "../documents/Actor/Handler.mjs"; import { GenericActorSheet } from "./GenericActorSheet.mjs"; +import { DiceList } from "../dialogs/DiceList.mjs"; export class MobSheet extends GenericActorSheet { static get defaultOptions() { - let opts = mergeObject( + let opts = foundry.utils.mergeObject( super.defaultOptions, { template: `systems/dotdungeon/templates/actors/mobs/main.hbs`, - width: 300, - height: 360, + width: 750, + height: 390, } ); opts.classes.push(`dotdungeon`); @@ -21,6 +21,12 @@ export class MobSheet extends GenericActorSheet { if (this.document.isEmbedded) return; if (!this.isEditable) return; console.debug(`.dungeon | Adding event listeners for Mob: ${this.id}`); + + html.find(`.edit-dice`) + .on(`click`, async () => { + let d = new DiceList(this.actor); + d.render(true); + }); }; async getData() { @@ -34,7 +40,8 @@ export class MobSheet extends GenericActorSheet { ctx.computed = {}; - console.log(actor.uuid, `context:`, ctx) + // Compute rolls here + return ctx; }; -}; \ No newline at end of file +}; diff --git a/module/sheets/SyncVariations/AbstractSyncSheet.mjs b/module/sheets/SyncVariations/AbstractSyncSheet.mjs index 7af1899..80e372b 100644 --- a/module/sheets/SyncVariations/AbstractSyncSheet.mjs +++ b/module/sheets/SyncVariations/AbstractSyncSheet.mjs @@ -2,11 +2,11 @@ import { GenericActorSheet } from "../GenericActorSheet.mjs"; export class AbstractSyncSheet extends GenericActorSheet { static get defaultOptions() { - let opts = mergeObject( + let opts = foundry.utils.mergeObject( super.defaultOptions, { width: 200, - height: 200, + height: 275, } ); opts.classes.push( @@ -22,11 +22,12 @@ export class AbstractSyncSheet extends GenericActorSheet { ctx.system = actor.system; ctx.flags = actor.flags; - - console.groupCollapsed(`SyncSheet.getData`); - console.log(`ctx`, ctx); - console.log(`actor`, actor); - console.groupEnd(); return ctx; }; -}; \ No newline at end of file + + activateListeners(html) { + super.activateListeners(html); + html.find(`.use-rest-die`) + .on(`click`, this.actor.useRestDie.bind(this.actor)); + }; +}; diff --git a/module/sheets/SyncVariations/BasicSyncSheet.mjs b/module/sheets/SyncVariations/BasicSyncSheet.mjs index 197b95b..115fe13 100644 --- a/module/sheets/SyncVariations/BasicSyncSheet.mjs +++ b/module/sheets/SyncVariations/BasicSyncSheet.mjs @@ -4,4 +4,4 @@ export class BasicSyncSheet extends AbstractSyncSheet { get template() { return `systems/dotdungeon/templates/actors/sync/basic.hbs`; }; -}; \ No newline at end of file +}; diff --git a/module/utils.mjs b/module/utils.mjs deleted file mode 100644 index 7d63d62..0000000 --- a/module/utils.mjs +++ /dev/null @@ -1,13 +0,0 @@ -export function reloadWindows(type = null) { - if (!type) { - for (const window of globalThis.ui.windows) { - window.render(true); - }; - return; - }; - for (const window of globalThis.ui.windows) { - if (window instanceof type) { - window.render(true); - }; - }; -}; \ No newline at end of file diff --git a/module/utils/DialogManager.mjs b/module/utils/DialogManager.mjs new file mode 100644 index 0000000..7c40407 --- /dev/null +++ b/module/utils/DialogManager.mjs @@ -0,0 +1,83 @@ +import { localizer } from "./localizer.mjs"; + +/** + * A utility class that allows managing Dialogs that are created for various + * purposes such as deleting items, help popups, etc. This is a singleton class + * that upon instantiating after the first time will just return the first instance + */ +export class DialogManager { + + /** @type {Map} */ + static #dialogs = new Map(); + + /** + * Focuses a dialog if it already exists, or creates a new one and renders it. + * + * @param {string} dialogId The ID to associate with the dialog, should be unique + * @param {object} data The data to pass to the Dialog constructor + * @param {DialogOptions} opts The options to pass to the Dialog constructor + * @returns {Dialog} The Dialog instance + */ + static async createOrFocus(dialogId, data, opts = {}) { + if (DialogManager.#dialogs.has(dialogId)) { + const dialog = DialogManager.#dialogs.get(dialogId); + dialog.bringToTop(); + return dialog; + }; + + /* + This makes sure that if I provide a close function as a part of the data, + that the dialog still gets removed from the set once it's closed, otherwise + it could lead to dangling references that I don't care to keep. Or if I don't + provide the close function, it just sets the function as there isn't anything + extra that's needed to be called. + */ + if (data?.close) { + const provided = data.close; + data.close = () => { + DialogManager.#dialogs.delete(dialogId); + provided(); + }; + } + else { + data.close = () => DialogManager.#dialogs.delete(dialogId); + }; + + // Create the Dialog with the modified data + const dialog = new Dialog(data, opts); + DialogManager.#dialogs.set(dialogId, dialog); + dialog.render(true); + return dialog; + }; + + /** + * Closes a dialog if it is rendered + * + * @param {string} dialogId The ID of the dialog to close + */ + static async close(dialogId) { + const dialog = DialogManager.#dialogs.get(dialogId); + dialog?.close(); + }; + + static async helpDialog( + helpId, + helpContent, + helpTitle = `dotdungeon.common.help`, + localizationData = {}, + ) { + DialogManager.createOrFocus( + helpId, + { + title: localizer(helpTitle, localizationData), + content: localizer(helpContent, localizationData), + buttons: {}, + }, + { resizable: true, } + ); + }; + + static get size() { + return DialogManager.#dialogs.size; + } +}; diff --git a/module/utils/GenericContextMenu.mjs b/module/utils/GenericContextMenu.mjs new file mode 100644 index 0000000..9749b4f --- /dev/null +++ b/module/utils/GenericContextMenu.mjs @@ -0,0 +1,6 @@ +export class GenericContextMenu extends ContextMenu { + constructor(element, selector, menuItems, opts = {}) { + super(element, selector, menuItems, opts); + this.menuItems.forEach(i => i.icon ??= ``); + }; +}; diff --git a/module/utils/localizer.mjs b/module/utils/localizer.mjs new file mode 100644 index 0000000..7cfebb0 --- /dev/null +++ b/module/utils/localizer.mjs @@ -0,0 +1,37 @@ +import { localizerConfig } from "../config.mjs"; + +export function handlebarsLocalizer(key, ...args) { + let data = args[0] + if (args.length === 1) data = args[0].hash; + if (key instanceof Handlebars.SafeString) key = key.toString(); + const localized = localizer(key, data); + return localized; +}; + +export function localizer(key, args = {}, depth = 0) { + /** @type {string} */ + let localized = game.i18n.format(key, args); + const subkeys = localized.matchAll(localizerConfig.subKeyPattern); + + // Short-cut to help prevent infinite recursion + if (depth > localizerConfig.maxDepth) { + return localized; + }; + + /* + Helps prevent recursion on the same key so that we aren't doing excess work. + */ + const localizedSubkeys = new Map(); + for (const match of subkeys) { + const subkey = match.groups.key; + if (localizedSubkeys.has(subkey)) continue; + localizedSubkeys.set(subkey, localizer(subkey, args, depth + 1)); + }; + + return localized.replace( + localizerConfig.subKeyPattern, + (_fullMatch, subkey) => { + return localizedSubkeys.get(subkey); + } + ); +}; diff --git a/module/utils/modifierToString.mjs b/module/utils/modifierToString.mjs new file mode 100644 index 0000000..2d1c59c --- /dev/null +++ b/module/utils/modifierToString.mjs @@ -0,0 +1,18 @@ +/** + * Takes in an integer and converts it into a string format that can be used in + * roll formulas or for displaying to the user. + * + * @param {number} mod The modifier to stringify + * @param {object} opts + * @param {boolean} opts.spaces Puts spaces on either side of the operand + * @returns {string} + */ +export function modifierToString(mod, opts = {}) { + if (mod == 0) return ``; + + let value = [``, `+`, mod] + if (mod < 0) { + value = [``, `-`, Math.abs(mod)] + }; + return value.join(opts.spaces ? ` ` : ``); +}; diff --git a/new-pc-sheet.md b/new-pc-sheet.md new file mode 100644 index 0000000..f43ce4c --- /dev/null +++ b/new-pc-sheet.md @@ -0,0 +1,80 @@ +## Tabs: +- Main + - Stats + - Skills +- Inventory + - Player + - Containers + - Inventory (divided into category of items) + - This is the items that the player will have "on them" + - Storage (divided into category of items) + - This is all of the items that the players owns and has put into storage *somewhere* + - Transportation +- Combat + - Easy skill buttons: + - Melee + - Accuracy + - Weapons + - Sync / Respawns +- Info + - Account name + - PFP + - Group name + - Aspects + - Roles +- Spells + +====== + +## Requirements: + +Stats: + - Needs to list all 6 of the primary stats + - Needs to have a dropdown for to select a die + - Nice to have: disables dice that have been selected in other stats + - Needs to have a button to roll the stat (when a die is selected) + - Foundry v12: ActiveEffect - Die needs to be able to be affected by ActiveEffects + +Skills: + - Each of the 25 skills needs to be grouped under a header of what stat it's + associated with + - Each skill must have a dropdown to indicate training level (null, trained, + expert, locked) + - Every skill must have a button to roll the dice that is labelled with the + correct formula for that skill (or "Locked" if the skill is locked) + - ActiveEffect - Increase Modifier + - Foundry v12: ActiveEffect - Increase Training Level + +Combat: + - Two weapon slots for the equipped weapon(s) + - A single armor slot + - Quick-access to the Melee / Accuracy skills + +Inventory: + - Needs three sub-tabs: + - Player + - Storage + - Transportation + - Player Subtab: + - Needs to have a section for container items, and indicating how many slots + each one has. + - List all of the items that the player has with the "inventory" location + - Show the total number of items the player on their character and how many + total slots are available + - Needs some way to move items to a different storage area (embedded-only + item sheet field maybe) + - Storage Subtab: + - List all of the items that the player has marked as in-storage + - Transportation: + - This is currently just a placeholder tab, no functionality needed other + than existing + +Spells: + - Lists all spells on a page (sortable by: alphabetical, cost, etc.) + +Info: + - Needs a place to edit the actor's name + - Needs a place to edit the actor's image + - Needs a place to edit the group name (if enabled by the GM, or is the GM) + - Needs a place to see and manage all equipped aspects + - Needs a place to see and manage all equipped roles \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 2e25431..8fe7e1c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,10 +5,32 @@ "packages": { "": { "devDependencies": { + "@foundryvtt/foundryvtt-cli": "^1.0.2", "@league-of-foundry-developers/foundry-vtt-types": "^9.280.0", "sass": "^1.69.5" } }, + "node_modules/@foundryvtt/foundryvtt-cli": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@foundryvtt/foundryvtt-cli/-/foundryvtt-cli-1.0.2.tgz", + "integrity": "sha512-pERML7ViBiqwP11NS1kci0Q38t4h557F/Mj+DjYmmgumMJIZqDsVv2XU3bwOJS7+6yzbUmUc2/jRD6EIx+U/fw==", + "dev": true, + "dependencies": { + "chalk": "^5.2.0", + "classic-level": "^1.2.0", + "esm": "^3.2.25", + "js-yaml": "^4.1.0", + "mkdirp": "^3.0.0", + "nedb-promises": "^6.2.1", + "yargs": "^17.7.1" + }, + "bin": { + "fvtt": "fvtt.mjs" + }, + "engines": { + "node": ">17.0.0" + } + }, "node_modules/@league-of-foundry-developers/foundry-vtt-types": { "version": "9.280.0", "resolved": "https://registry.npmjs.org/@league-of-foundry-developers/foundry-vtt-types/-/foundry-vtt-types-9.280.0.tgz", @@ -2152,6 +2174,23 @@ "@pixi/settings": "6.5.10" } }, + "node_modules/@seald-io/binary-search-tree": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@seald-io/binary-search-tree/-/binary-search-tree-1.0.3.tgz", + "integrity": "sha512-qv3jnwoakeax2razYaMsGI/luWdliBLHTdC6jU55hQt1hcFqzauH/HsBollQ7IR4ySTtYhT+xyHoijpA16C+tA==", + "dev": true + }, + "node_modules/@seald-io/nedb": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@seald-io/nedb/-/nedb-4.0.4.tgz", + "integrity": "sha512-CUNcMio7QUHTA+sIJ/DC5JzVNNsHe743TPmC4H5Gij9zDLMbmrCT2li3eVB72/gF63BPS8pWEZrjlAMRKA8FDw==", + "dev": true, + "dependencies": { + "@seald-io/binary-search-tree": "^1.0.3", + "localforage": "^1.9.0", + "util": "^0.12.4" + } + }, "node_modules/@socket.io/component-emitter": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.0.0.tgz", @@ -2175,9 +2214,9 @@ } }, "node_modules/@types/node": { - "version": "20.10.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.6.tgz", - "integrity": "sha512-Vac8H+NlRNNlAmDfGUP7b5h/KA+AtWIzuXy0E6OyP8f1tCLYAtPvKRRDJjAPqhpCb0t6U2j7/xqAuLEebW2kiw==", + "version": "20.11.16", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.16.tgz", + "integrity": "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -2205,6 +2244,48 @@ "integrity": "sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg==", "dev": true }, + "node_modules/abstract-level": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/abstract-level/-/abstract-level-1.0.4.tgz", + "integrity": "sha512-eUP/6pbXBkMbXFdx4IH2fVgvB7M0JvR7/lIL33zcs0IBcwjdzSSl31TOJsaCzmKSSDF9h8QYSOJux4Nd4YJqFg==", + "dev": true, + "dependencies": { + "buffer": "^6.0.3", + "catering": "^2.1.0", + "is-buffer": "^2.0.5", + "level-supports": "^4.0.0", + "level-transcoder": "^1.0.1", + "module-error": "^1.0.1", + "queue-microtask": "^1.2.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -2218,12 +2299,50 @@ "node": ">= 8" } }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/available-typed-arrays": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.6.tgz", + "integrity": "sha512-j1QzY8iPNPG4o4xmO3ptzpRxTciqD3MgEHtifP/YnJpIo58Xu+ne4BejlbkuaLfXn/nz6HFiw29bLpj2PNMdGg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/backo2": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", "integrity": "sha512-zj6Z6M7Eq+PBZ7PQxl5NT665MvJdAkzp0f60nAJ+sLaSCBPMwVak5ZegFbgVCzFcCJTKFoMizvM5Ld7+JrRJHA==", "dev": true }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -2245,20 +2364,69 @@ "node": ">=8" } }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.6.tgz", + "integrity": "sha512-Mj50FLHtlsoVfRfnHaZvyrooHcrlceNZdL/QBvJJVd9Ta55qCQK0gs4ss2oZDeV9zFCs6ewzYgVE5yfVmfFpVg==", "dev": true, "dependencies": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.3", + "set-function-length": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/catering": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/catering/-/catering-2.1.1.tgz", + "integrity": "sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -2286,6 +2454,55 @@ "fsevents": "~2.3.2" } }, + "node_modules/classic-level": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/classic-level/-/classic-level-1.4.1.tgz", + "integrity": "sha512-qGx/KJl3bvtOHrGau2WklEZuXhS3zme+jf+fsu6Ej7W7IP/C49v7KNlWIsT1jZu0YnfzSIYDGcEWpCa1wKGWXQ==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "abstract-level": "^1.0.2", + "catering": "^2.1.0", + "module-error": "^1.0.1", + "napi-macros": "^2.2.2", + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -2304,14 +2521,15 @@ } }, "node_modules/define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.2.tgz", + "integrity": "sha512-SRtsSqsDbgpJBbW3pABMCOt6rQyeM8s8RiyeSN8jYG8sYmt/kGJejbydttUsnDs1tadr19tvhT4ShwMyoqAm4g==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.1", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.2", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -2323,6 +2541,12 @@ "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==", "dev": true }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, "node_modules/engine.io-client": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.0.3.tgz", @@ -2349,12 +2573,39 @@ "node": ">=10.0.0" } }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es6-promise-polyfill": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/es6-promise-polyfill/-/es6-promise-polyfill-1.2.0.tgz", "integrity": "sha512-HHb0vydCpoclpd0ySPkRXMmBw80MRt1wM4RBJBlXkux97K7gleabZdsR0gvE1nNPM9mgOZIBTzjjXiPxf4lIqQ==", "dev": true }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/esm": { + "version": "3.2.25", + "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", + "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/eventemitter3": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", @@ -2373,6 +2624,15 @@ "node": ">=8" } }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -2396,17 +2656,30 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dev": true, "dependencies": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", "hasown": "^2.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -2498,6 +2771,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", @@ -2510,12 +2798,60 @@ "node": ">= 0.4" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "dev": true + }, "node_modules/immutable": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz", "integrity": "sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==", "dev": true }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -2528,6 +2864,41 @@ "node": ">=8" } }, + "node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "engines": { + "node": ">=4" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2537,6 +2908,30 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -2558,12 +2953,79 @@ "node": ">=0.12.0" } }, + "node_modules/is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dev": true, + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/ismobilejs": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ismobilejs/-/ismobilejs-1.1.1.tgz", "integrity": "sha512-VaFW53yt8QO61k2WJui0dHf4SlL8lxBofUuUmwBo0ljPk0Drz2TiuDW4jo3wDcv41qy/SxrJ+VAzJ/qYqsmzRw==", "dev": true }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/level-supports": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-4.0.1.tgz", + "integrity": "sha512-PbXpve8rKeNcZ9C1mUicC9auIYFyGpkV9/i6g76tLgANwWhtG2v7I4xNBUlkn3lE2/dZF3Pi0ygYGtLc4RXXdA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/level-transcoder": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/level-transcoder/-/level-transcoder-1.0.1.tgz", + "integrity": "sha512-t7bFwFtsQeD8cl8NIoQ2iwxA0CL/9IFw7/9gAjOonH0PWTTiRfY7Hq+Ejbsxh86tXobDQ6IOiddjNYIfOBs06w==", + "dev": true, + "dependencies": { + "buffer": "^6.0.3", + "module-error": "^1.0.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/lie": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", + "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", + "dev": true, + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/localforage": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", + "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", + "dev": true, + "dependencies": { + "lie": "3.1.1" + } + }, "node_modules/mini-signals": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mini-signals/-/mini-signals-1.2.0.tgz", @@ -2579,18 +3041,68 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/module-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/module-error/-/module-error-1.0.2.tgz", + "integrity": "sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "node_modules/napi-macros": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.2.2.tgz", + "integrity": "sha512-hmEVtAGYzVQpCKdbQea4skABsdXW4RUh5t5mJ2zzqowJS2OyXZTU1KhDVFhx+NlWZ4ap9mqR9TcDO3LTTttd+g==", + "dev": true + }, + "node_modules/nedb-promises": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/nedb-promises/-/nedb-promises-6.2.3.tgz", + "integrity": "sha512-enq0IjNyBz9Qy9W/QPCcLGh/QORGBjXbIeZeWvIjO3OMLyAvlKT3hiJubP2BKEiFniUlR3L01o18ktqgn5jxqA==", + "dev": true, + "dependencies": { + "@seald-io/nedb": "^4.0.2" + } + }, "node_modules/neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "node_modules/node-gyp-build": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.0.tgz", + "integrity": "sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==", + "dev": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -2833,6 +3345,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -2845,6 +3377,15 @@ "node": ">=8.10.0" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resource-loader": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/resource-loader/-/resource-loader-3.0.1.tgz", @@ -2873,29 +3414,35 @@ } }, "node_modules/set-function-length": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", - "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz", + "integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==", "dev": true, "dependencies": { - "define-data-property": "^1.1.1", - "get-intrinsic": "^1.2.1", + "define-data-property": "^1.1.2", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.3", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.1" }, "engines": { "node": ">= 0.4" } }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.5.tgz", + "integrity": "sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2949,6 +3496,32 @@ "node": ">=0.10.0" } }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/tinymce": { "version": "5.10.1", "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-5.10.1.tgz", @@ -2996,12 +3569,61 @@ "qs": "^6.11.2" } }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.14.tgz", + "integrity": "sha512-VnXFiIW8yNn9kIHN88xvZ4yOWchftKDsRJ8fEPacX/wl1lOvBrhsJ/OeJCXq7B0AaijRuqgzSKalJoPk+D8MPg==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.6", + "call-bind": "^1.0.5", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", "dev": true }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/ws": { "version": "8.2.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", @@ -3032,6 +3654,42 @@ "node": ">=0.4.0" } }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/yeast": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", diff --git a/package.json b/package.json index dd1e0cf..879d5e8 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "build": "sass --embed-source-map --no-error-css styles/:.styles/" }, "devDependencies": { + "@foundryvtt/foundryvtt-cli": "^1.0.2", "@league-of-foundry-developers/foundry-vtt-types": "^9.280.0", "sass": "^1.69.5" } diff --git a/packs/adventures/_source/Tutorial_Dungeon_P4I3A3FYvW0Yedqm.json b/packs/adventures/_source/Tutorial_Dungeon_P4I3A3FYvW0Yedqm.json new file mode 100644 index 0000000..a9be6ac --- /dev/null +++ b/packs/adventures/_source/Tutorial_Dungeon_P4I3A3FYvW0Yedqm.json @@ -0,0 +1,29 @@ +{ + "name": "Tutorial Dungeon", + "img": null, + "caption": "", + "sort": 0, + "description": "", + "actors": [], + "combats": [], + "items": [], + "journal": [], + "scenes": [], + "tables": [], + "macros": [], + "cards": [], + "playlists": [], + "folders": [], + "_id": "P4I3A3FYvW0Yedqm", + "folder": null, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1708052903525, + "modifiedTime": 1708052903525, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!adventures!P4I3A3FYvW0Yedqm" +} diff --git a/packs/beastiary/_source/Arya_oCHOQYCQEI1zk5zO.json b/packs/beastiary/_source/Arya_oCHOQYCQEI1zk5zO.json new file mode 100644 index 0000000..4474fb8 --- /dev/null +++ b/packs/beastiary/_source/Arya_oCHOQYCQEI1zk5zO.json @@ -0,0 +1,105 @@ +{ + "name": "Arya", + "type": "mob", + "_id": "oCHOQYCQEI1zk5zO", + "img": "icons/svg/mystery-man.svg", + "system": { + "bonus": 4, + "initiative": null, + "morale": 20, + "drops": "Arya's Cloak", + "stunts": "Arya's Shroud - On her initiative, Arya drags a player into her shroud, a foggy one-on-one arena. They leave at the beginning of Arya's next turn if she chooses a new victim.", + "immune": "Solar, Shadow, Neon", + "weak": "Gun", + "bytes": 0, + "description": "The Queen of the Wode. A gargantuan fox who converses with her all-seeing children (the Palims) through hidden strings of code. If you find her, perform a /bow and offer anything of value. She can guide you where you need to go.", + "dice": [ + { + "count": 3, + "sides": 6, + "repeat": 5 + } + ] + }, + "prototypeToken": { + "name": "Arya", + "displayName": 0, + "actorLink": false, + "appendNumber": false, + "prependAdjective": false, + "texture": { + "src": "icons/svg/mystery-man.svg", + "scaleX": 1, + "scaleY": 1, + "offsetX": 0, + "offsetY": 0, + "rotation": 0 + }, + "width": 1, + "height": 1, + "lockRotation": false, + "rotation": 0, + "alpha": 1, + "disposition": -1, + "displayBars": 0, + "bar1": { + "attribute": null + }, + "bar2": { + "attribute": null + }, + "light": { + "alpha": 0.5, + "angle": 360, + "bright": 0, + "coloration": 1, + "dim": 0, + "attenuation": 0.5, + "luminosity": 0.5, + "saturation": 0, + "contrast": 0, + "shadows": 0, + "animation": { + "type": null, + "speed": 5, + "intensity": 5, + "reverse": false + }, + "darkness": { + "min": 0, + "max": 1 + } + }, + "sight": { + "enabled": false, + "range": 0, + "angle": 360, + "visionMode": "basic", + "attenuation": 0.1, + "brightness": 0, + "saturation": 0, + "contrast": 0 + }, + "detectionModes": [], + "flags": {}, + "randomImg": false + }, + "items": [], + "effects": [], + "folder": null, + "sort": 0, + "ownership": { + "default": 0, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1708052549670, + "modifiedTime": 1708052728103, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!actors!oCHOQYCQEI1zk5zO" +} diff --git a/packs/beastiary/_source/Dragon_TxMsleI1qG86zj6B.json b/packs/beastiary/_source/Dragon_TxMsleI1qG86zj6B.json new file mode 100644 index 0000000..29986f4 --- /dev/null +++ b/packs/beastiary/_source/Dragon_TxMsleI1qG86zj6B.json @@ -0,0 +1,110 @@ +{ + "name": "Dragon", + "type": "mob", + "_id": "TxMsleI1qG86zj6B", + "img": "icons/svg/mystery-man.svg", + "system": { + "bonus": 6, + "initiative": 20, + "morale": 2, + "drops": "Dragon Materials", + "stunts": "Breath Weapon: At the end of the round, the Dragon breathes its fire onto the battlefield, forcing everyone to make a contest against its current dice.", + "immune": "Piercing, Slashing, Smashing", + "weak": "Gun, Neon", + "bytes": 0, + "description": "Children of Annwn that explode from her body and conquer the hex of their birth. Stylized after the lung Dragons", + "dice": [ + { + "count": 3, + "sides": 6, + "repeat": 4 + }, + { + "count": 2, + "sides": 6, + "repeat": 2 + } + ] + }, + "prototypeToken": { + "name": "Dragon", + "displayName": 0, + "actorLink": false, + "appendNumber": false, + "prependAdjective": false, + "texture": { + "src": "icons/svg/mystery-man.svg", + "scaleX": 1, + "scaleY": 1, + "offsetX": 0, + "offsetY": 0, + "rotation": 0 + }, + "width": 1, + "height": 1, + "lockRotation": false, + "rotation": 0, + "alpha": 1, + "disposition": -1, + "displayBars": 0, + "bar1": { + "attribute": null + }, + "bar2": { + "attribute": null + }, + "light": { + "alpha": 0.5, + "angle": 360, + "bright": 0, + "coloration": 1, + "dim": 0, + "attenuation": 0.5, + "luminosity": 0.5, + "saturation": 0, + "contrast": 0, + "shadows": 0, + "animation": { + "type": null, + "speed": 5, + "intensity": 5, + "reverse": false + }, + "darkness": { + "min": 0, + "max": 1 + } + }, + "sight": { + "enabled": false, + "range": 0, + "angle": 360, + "visionMode": "basic", + "attenuation": 0.1, + "brightness": 0, + "saturation": 0, + "contrast": 0 + }, + "detectionModes": [], + "flags": {}, + "randomImg": false + }, + "items": [], + "effects": [], + "folder": null, + "sort": 0, + "ownership": { + "default": 0, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1707676733262, + "modifiedTime": 1707676892258, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!actors!TxMsleI1qG86zj6B" +} diff --git a/packs/rules/_source/Exploration_RT4uYaDJYd2RkDaA.json b/packs/rules/_source/Exploration_RT4uYaDJYd2RkDaA.json new file mode 100644 index 0000000..f0a3d7d --- /dev/null +++ b/packs/rules/_source/Exploration_RT4uYaDJYd2RkDaA.json @@ -0,0 +1,21 @@ +{ + "name": "Exploration", + "_id": "RT4uYaDJYd2RkDaA", + "pages": [], + "folder": null, + "sort": 0, + "ownership": { + "default": 0, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1708054253372, + "modifiedTime": 1708054253372, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!journal!RT4uYaDJYd2RkDaA" +} diff --git a/packs/rules/_source/Inventory_y7CfRycKl5A8SpnH.json b/packs/rules/_source/Inventory_y7CfRycKl5A8SpnH.json new file mode 100644 index 0000000..ec5324f --- /dev/null +++ b/packs/rules/_source/Inventory_y7CfRycKl5A8SpnH.json @@ -0,0 +1,94 @@ +{ + "name": "Inventory", + "_id": "y7CfRycKl5A8SpnH", + "pages": [ + { + "sort": 100000, + "name": "Bytes", + "type": "text", + "_id": "3uVI4u2ByT8Tt6jq", + "title": { + "show": true, + "level": 1 + }, + "image": {}, + "text": { + "format": 1, + "content": "

Bytes are the currency of .dungeon. They can only be gained by interacting with the game: travelling to far away places, fighting mobs, exploring randomly generated structures, and taking on quests. They are used for everything from buying gear, to casting spells, and uncovering secrets in the world.

Gaining Bytes

A character gains 1 Byte (1b) per day as long as they log in and play. Traveling into a new hex for the first time also nets 1b. Defeating an encounter without taking Sync damage will gain the party an additional 1b each.

The Hidden World of Bytes

Last year, players discovered that an entire region of the map was unlocked once they accumulated enough Bytes, leading to speculation that the developers have hidden other things behind Bytes. Secret doors have been found in the tutorial dungeon. Some mobs are hidden until a certain number of Bytes are acquired. There are many secrets we've yet to uncover.

", + "markdown": "" + }, + "video": { + "controls": true, + "volume": 0.5 + }, + "src": null, + "system": {}, + "ownership": { + "default": -1, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1708053617428, + "modifiedTime": 1708058964191, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!journal.pages!y7CfRycKl5A8SpnH.3uVI4u2ByT8Tt6jq" + }, + { + "sort": 200000, + "name": "Resources/Supplies", + "type": "text", + "_id": "u0e8iLKCdCbdBkRG", + "title": { + "show": true, + "level": 1 + }, + "image": {}, + "text": { + "format": 1, + "content": "

Note: The wiki uses the terms \"Resources\" and \"Supplies\" interchangeably, they are functionally equivalent in every way.

", + "markdown": "" + }, + "video": { + "controls": true, + "volume": 0.5 + }, + "src": null, + "system": {}, + "ownership": { + "default": -1, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1708053891257, + "modifiedTime": 1708060045044, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!journal.pages!y7CfRycKl5A8SpnH.u0e8iLKCdCbdBkRG" + } + ], + "folder": null, + "sort": 0, + "ownership": { + "default": 0, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1708053607510, + "modifiedTime": 1708060045044, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!journal!y7CfRycKl5A8SpnH" +} diff --git a/packs/rules/_source/Stats___Skills_PZlkwX6W6TXpvl5C.json b/packs/rules/_source/Stats___Skills_PZlkwX6W6TXpvl5C.json new file mode 100644 index 0000000..fe22426 --- /dev/null +++ b/packs/rules/_source/Stats___Skills_PZlkwX6W6TXpvl5C.json @@ -0,0 +1,232 @@ +{ + "name": "Stats & Skills", + "_id": "PZlkwX6W6TXpvl5C", + "pages": [ + { + "sort": 100000, + "name": "Skills", + "type": "text", + "_id": "tf71tBEsorEzwg5D", + "title": { + "show": true, + "level": 1 + }, + "image": {}, + "text": { + "format": 1, + "content": "

Skills are in-game abilities that dictate how good your Avatar is at accomplishing tasks. Choose three skills to gain training in, alongside the one skill gifted by your job. Training in a skill means you get +2 to rolls when using it. Expertise in a skill means you get +4 to rolls when using it.

Skills are categorized by which Stat they fall under. Add your bonus to Contests where the Skill is applicable, rolling your Stat Dice and adding the Skill to the result.

Skill Training

To learn a new skill, PCs must find someone who can teach it to them. This requires a payment of 20 Bytes and a week of downtime. To gain Expertise in a skill, the player and the Server Host must agree to a challenge. Failure to accomplish the challenge locks you out of the skill into another challenge is agreed upon. If the challenge is accomplished the player gains Expertise in the skill.

Detailed Skill Breakdowns

" + }, + "video": { + "controls": true, + "volume": 0.5 + }, + "src": null, + "system": {}, + "ownership": { + "default": -1, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1707675426941, + "modifiedTime": 1707676418844, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!journal.pages!PZlkwX6W6TXpvl5C.tf71tBEsorEzwg5D" + }, + { + "sort": 200000, + "name": "Build Skills", + "type": "text", + "_id": "hcU7kvIyJEyQfjZI", + "title": { + "show": true, + "level": 2 + }, + "image": {}, + "text": { + "format": 1, + "content": "

Defense

Instead of armor, characters have a base defense that acts as damage reduction. Training in defense gives you 1dr, and Expertise gives you 2dr. Some clothes and armour may offer more damage reduction.

Magic

Training in magic lets you cast spells. Without training, you might be able to read the spells but their magic is lost on you. Expertise allows you to identify who cast a particular spell and detect magic in the area.

Melee

Training in melee allows you to use big melee weapons. Expertise allows you a unique \"stance\" with your weapon of choice. Whether you're swinging a sword or a punch, add your training when making attacks against against opponents.

Platforming

Acrobatic leaps, climbing, and navigating dangerous terrain. Training in platforming gives you tighter control over your Avatar's movements. Expertise gives you a double jump.

Strength

Knocking this over, pushing things out of the way, and general, hand-made destruction. Training in strength makes objects weigh less to your character. Expertise lets you throw large objects.

" + }, + "video": { + "controls": true, + "volume": 0.5 + }, + "src": null, + "system": {}, + "ownership": { + "default": -1, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1707675453145, + "modifiedTime": 1707675622437, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!journal.pages!PZlkwX6W6TXpvl5C.hcU7kvIyJEyQfjZI" + }, + { + "sort": 300000, + "name": "Meta Skills", + "type": "text", + "_id": "eEKc5g8nyJo4gzcA", + "title": { + "show": true, + "level": 2 + }, + "image": {}, + "text": { + "format": 1, + "content": "

Alchemy

Creating and identifying foils. Training in alchemy allows your character to craft lesser foils out of Materials. Expertise lets you craft greater foils.

Arcanum

The knowledge and training to use magic items and sci-fi tech. Training in arcanum allows you to use neon weapons. Expertise gives you basic information about magic items that are within your line of sight.

Dreams

Considered a hidden skill of the game, because it's not clearly defined on the wiki. [Call to action! The .dungeon wiki is open for contributors who know more about this skill]

Training in dreams allows you to make pacts with the pantheon of .dungeon. Expertise in it allows you to open yourself to the world of Annwn and receive messages from it. Often cryptic or prophetic.

Lore

Training in lore means the game expands descriptive text. Expertise means the game might butt in to give you information, even when you didn't ask for it.

Navigation

Training in navigation improves your HUD radar and quest markers. Expertise gives you active hints to help you search and travel.

" + }, + "video": { + "controls": true, + "volume": 0.5 + }, + "src": null, + "system": {}, + "ownership": { + "default": -1, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1707675480467, + "modifiedTime": 1707675613151, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!journal.pages!PZlkwX6W6TXpvl5C.eEKc5g8nyJo4gzcA" + }, + { + "sort": 400000, + "name": "Presence Skills", + "type": "text", + "_id": "Bv7FKq69XgfNgq0G", + "title": { + "show": true, + "level": 2 + }, + "image": {}, + "text": { + "format": 1, + "content": "

Animal Handling

Training in animal handling allows you to take on animals (and some monsters) as pets. Expertise in animal handling lets you reroll animal reactions to you, and take on a mount.

Perception

Training in perception improves colours, sounds, and smells to enhance your senses. Expertise in perception does this even more and also adds faint outlines to things of interest. The invisible hands of the game like you.

Sneak

Training in sneak reduces the ambient and active sounds of your Avatar. Expertise makes you completely silent while crouched.

Speech

Training in speech improves NPC reactions to you. Expertise in speech rerolls reaction rolls even in dangerous situations.

Vibes

Training in vibes improves the haptic feedback of your VR rig, giving you subtle signals when things are off. Expertise improves this even more and makes NPCs more readable.

" + }, + "video": { + "controls": true, + "volume": 0.5 + }, + "src": null, + "system": {}, + "ownership": { + "default": -1, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1707675495034, + "modifiedTime": 1707675630479, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!journal.pages!PZlkwX6W6TXpvl5C.Bv7FKq69XgfNgq0G" + }, + { + "sort": 500000, + "name": "Hands Skills", + "type": "text", + "_id": "YjSClMTdT8psEy3k", + "title": { + "show": true, + "level": 2 + }, + "image": {}, + "text": { + "format": 1, + "content": "

Accuracy

Training in accuracy reduces arrow and bullet drop and other things that affect trajectory of ranged attacks. Expertise subtly slows aiming down to give you greater control.

Crafting

Training in crafting allows you to create lesser versions of items as long as you have the supplies. Expertise allows you to create greater versions.

Engineering

Training in engineering aids you in repairing vehicles and other machinery in the world. Expertise aids you further by reducing repair costs.

Explosives

Training in explosives allows you more wiggle room for failure while using them in combat and aids you in building and disarming explosives. Expertise allows you to disarm explosives without destroying them.

Piloting

Training in piloting improves vehicle responsiveness and makes them easier to control. Expertise allows for a semi-auto-pilot so you can take on simple tasks while also piloting.

" + }, + "video": { + "controls": true, + "volume": 0.5 + }, + "src": null, + "system": {}, + "ownership": { + "default": -1, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1707675529083, + "modifiedTime": 1707675636786, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!journal.pages!PZlkwX6W6TXpvl5C.YjSClMTdT8psEy3k" + }, + { + "sort": 0, + "name": "Stats", + "type": "text", + "_id": "tv9jEZflqkGgQ1Az", + "title": { + "show": true, + "level": 1 + }, + "image": {}, + "text": { + "format": 1, + "content": "

Build

System mastery, character optimization, the crunch of the game.

Meta

Knowledge of the game, its setting, and its development.

Presence

Your senses, charisma, and immersion in the virtual world.

Hands

Your reaction time, hand-eye coordination, and precision.

Tilt

Tilt is how you keep your cool when the virtual world is unfair. Other RPGs call this a \"save\" or a \"saving throw\". Whenever your character would take on a status effect, roll your Tilt dice, getting a 4 or higher is a success, avoiding the status.

RNG

RNG is also known as \"luck\" in other RPGs, RNG rolls are done for random events, loot drops, and other acts of chance the Server needs to account for. You can also roll RNG against an NPC to see if they are nearby.

" + }, + "video": { + "controls": true, + "volume": 0.5 + }, + "src": null, + "system": {}, + "ownership": { + "default": -1, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1707675599891, + "modifiedTime": 1707675641462, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!journal.pages!PZlkwX6W6TXpvl5C.tv9jEZflqkGgQ1Az" + } + ], + "folder": null, + "sort": 0, + "ownership": { + "default": 0, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1707675420080, + "modifiedTime": 1707676418844, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!journal!PZlkwX6W6TXpvl5C" +} diff --git a/packs/rules/_source/Statuses_yAgJW3Aa4cXf8OpM.json b/packs/rules/_source/Statuses_yAgJW3Aa4cXf8OpM.json new file mode 100644 index 0000000..826e0bb --- /dev/null +++ b/packs/rules/_source/Statuses_yAgJW3Aa4cXf8OpM.json @@ -0,0 +1,757 @@ +{ + "name": "Statuses", + "_id": "yAgJW3Aa4cXf8OpM", + "pages": [ + { + "sort": 100000, + "name": "Overview", + "type": "text", + "_id": "p8wlxSqcQhZQRnaO", + "title": { + "show": true, + "level": 1 + }, + "image": {}, + "text": { + "format": 1, + "content": "

Status are applied when traps are triggered, mobs land unique attacks, or by fumbling certain actions. When a status is applied, its effects are immediate. A player can use their action to make a tilt roll to end the status. Otherwise they'll need foils or magic to cure the status.

" + }, + "video": { + "controls": true, + "volume": 0.5 + }, + "src": null, + "system": {}, + "ownership": { + "default": -1, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1707675677774, + "modifiedTime": 1707675687491, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!journal.pages!yAgJW3Aa4cXf8OpM.p8wlxSqcQhZQRnaO" + }, + { + "sort": 200000, + "name": "Bleeding", + "type": "text", + "_id": "Vu6iNCHNbrf4Dywe", + "title": { + "show": true, + "level": 1 + }, + "image": {}, + "text": { + "format": 1, + "content": "

Take 1 Sync damage at the start of each of your turns until the bleeding is stopped.

" + }, + "video": { + "controls": true, + "volume": 0.5 + }, + "src": null, + "system": {}, + "ownership": { + "default": -1, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1707675698797, + "modifiedTime": 1707675710703, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!journal.pages!yAgJW3Aa4cXf8OpM.Vu6iNCHNbrf4Dywe" + }, + { + "sort": 300000, + "name": "Blinded", + "type": "text", + "_id": "UgOXwykPoHH6mxuH", + "title": { + "show": true, + "level": 1 + }, + "image": {}, + "text": { + "format": 1, + "content": "

All navigation or targetting requires a successful luck roll.

" + }, + "video": { + "controls": true, + "volume": 0.5 + }, + "src": null, + "system": {}, + "ownership": { + "default": -1, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1707675723698, + "modifiedTime": 1707675725827, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!journal.pages!yAgJW3Aa4cXf8OpM.UgOXwykPoHH6mxuH" + }, + { + "sort": 400000, + "name": "Burning", + "type": "text", + "_id": "Zplq6oUkBLZjuLuC", + "title": { + "show": true, + "level": 1 + }, + "image": {}, + "text": { + "format": 1, + "content": "

Take [[/roll 1d6 # 1d6]] Sync damage at the start of your turn.

" + }, + "video": { + "controls": true, + "volume": 0.5 + }, + "src": null, + "system": {}, + "ownership": { + "default": -1, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1707675739369, + "modifiedTime": 1707675744761, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!journal.pages!yAgJW3Aa4cXf8OpM.Zplq6oUkBLZjuLuC" + }, + { + "sort": 500000, + "name": "Charmed", + "type": "text", + "_id": "uPKEeeK5O8gILptc", + "title": { + "show": true, + "level": 1 + }, + "image": {}, + "text": { + "format": 1, + "content": "

Act on the charmer's turn and add your dice to their damage.

" + }, + "video": { + "controls": true, + "volume": 0.5 + }, + "src": null, + "system": {}, + "ownership": { + "default": -1, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1707675928934, + "modifiedTime": 1707675933413, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!journal.pages!yAgJW3Aa4cXf8OpM.uPKEeeK5O8gILptc" + }, + { + "sort": 600000, + "name": "Confused", + "type": "text", + "_id": "INB0gL4EznwVg0yn", + "title": { + "show": true, + "level": 1 + }, + "image": {}, + "text": { + "format": 1, + "content": "

All failed rolls are considered fumbles

" + }, + "video": { + "controls": true, + "volume": 0.5 + }, + "src": null, + "system": {}, + "ownership": { + "default": -1, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1707675943152, + "modifiedTime": 1707675944882, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!journal.pages!yAgJW3Aa4cXf8OpM.INB0gL4EznwVg0yn" + }, + { + "sort": 700000, + "name": "Deafened", + "type": "text", + "_id": "NOvC9fH492bYFqXb", + "title": { + "show": true, + "level": 1 + }, + "image": {}, + "text": { + "format": 1, + "content": "

You can't hear any voice communications or game sounds. You may need to cover your ears, leave the room, or deafen your headset.

" + }, + "video": { + "controls": true, + "volume": 0.5 + }, + "src": null, + "system": {}, + "ownership": { + "default": -1, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1707675968679, + "modifiedTime": 1707675971640, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!journal.pages!yAgJW3Aa4cXf8OpM.NOvC9fH492bYFqXb" + }, + { + "sort": 800000, + "name": "Drowning", + "type": "text", + "_id": "eBdIFH0ZWvcFFcTX", + "title": { + "show": true, + "level": 1 + }, + "image": {}, + "text": { + "format": 1, + "content": "

You've failed your swimming roll and are now drowning. Roll Tilt at the start of your turn until someone saves you or you are forced to respawn.

" + }, + "video": { + "controls": true, + "volume": 0.5 + }, + "src": null, + "system": {}, + "ownership": { + "default": -1, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1707675981287, + "modifiedTime": 1707675986654, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!journal.pages!yAgJW3Aa4cXf8OpM.eBdIFH0ZWvcFFcTX" + }, + { + "sort": 900000, + "name": "Frightened", + "type": "text", + "_id": "lsCvBn9TMtQl7Hgw", + "title": { + "show": true, + "level": 1 + }, + "image": {}, + "text": { + "format": 1, + "content": "

You must move as far as possible from the thing that frightened you. You can't take actions that harm the thing that frightened you.

" + }, + "video": { + "controls": true, + "volume": 0.5 + }, + "src": null, + "system": {}, + "ownership": { + "default": -1, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1707675997068, + "modifiedTime": 1707675999184, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!journal.pages!yAgJW3Aa4cXf8OpM.lsCvBn9TMtQl7Hgw" + }, + { + "sort": 1000000, + "name": "Grappled", + "type": "text", + "_id": "FErkgaSZlHPLdtd7", + "title": { + "show": true, + "level": 1 + }, + "image": {}, + "text": { + "format": 1, + "content": "

You can't move but you can still take your action.

" + }, + "video": { + "controls": true, + "volume": 0.5 + }, + "src": null, + "system": {}, + "ownership": { + "default": -1, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1707676010259, + "modifiedTime": 1707676012998, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!journal.pages!yAgJW3Aa4cXf8OpM.FErkgaSZlHPLdtd7" + }, + { + "sort": 1100000, + "name": "Invisible", + "type": "text", + "_id": "VEGpoa1DPtSheaEk", + "title": { + "show": true, + "level": 1 + }, + "image": {}, + "text": { + "format": 1, + "content": "

Make an RNG roll to be able to spot an invisible target. All combat rolls must be made using RNG as well. Hitting an invisible target makes them visible.

" + }, + "video": { + "controls": true, + "volume": 0.5 + }, + "src": null, + "system": {}, + "ownership": { + "default": -1, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1707676021625, + "modifiedTime": 1707676024296, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!journal.pages!yAgJW3Aa4cXf8OpM.VEGpoa1DPtSheaEk" + }, + { + "sort": 1200000, + "name": "Lost", + "type": "text", + "_id": "9pAaAElhaowUbXqS", + "title": { + "show": true, + "level": 1 + }, + "image": {}, + "text": { + "format": 1, + "content": "

If you are afflicted with this status, all navigation requires a successful RNG roll. Failure means the Server chooses where you go.

" + }, + "video": { + "controls": true, + "volume": 0.5 + }, + "src": null, + "system": {}, + "ownership": { + "default": -1, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1707676039784, + "modifiedTime": 1707676045882, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!journal.pages!yAgJW3Aa4cXf8OpM.9pAaAElhaowUbXqS" + }, + { + "sort": 1300000, + "name": "Paralyzed", + "type": "text", + "_id": "Tmeq77W6rtFhtR8M", + "title": { + "show": true, + "level": 1 + }, + "image": {}, + "text": { + "format": 1, + "content": "

You can't move or take an action on your turn. All damage you take is doubled.

" + }, + "video": { + "controls": true, + "volume": 0.5 + }, + "src": null, + "system": {}, + "ownership": { + "default": -1, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1707676104227, + "modifiedTime": 1707676107220, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!journal.pages!yAgJW3Aa4cXf8OpM.Tmeq77W6rtFhtR8M" + }, + { + "sort": 1400000, + "name": "Poisoned", + "type": "text", + "_id": "RSKucW2LOCbR6iwb", + "title": { + "show": true, + "level": 1 + }, + "image": {}, + "text": { + "format": 1, + "content": "

You make all rolls using your Tilt dice. Failure marks a respawn. Respawning removes this status.

" + }, + "video": { + "controls": true, + "volume": 0.5 + }, + "src": null, + "system": {}, + "ownership": { + "default": -1, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1707676117414, + "modifiedTime": 1707676125744, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!journal.pages!yAgJW3Aa4cXf8OpM.RSKucW2LOCbR6iwb" + }, + { + "sort": 1500000, + "name": "Rage", + "type": "text", + "_id": "5G1Gj8eeKrmTLk2v", + "title": { + "show": true, + "level": 1 + }, + "image": {}, + "text": { + "format": 1, + "content": "

You must attack the person or creature nearest to you.

" + }, + "video": { + "controls": true, + "volume": 0.5 + }, + "src": null, + "system": {}, + "ownership": { + "default": -1, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1707676138008, + "modifiedTime": 1707676140388, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!journal.pages!yAgJW3Aa4cXf8OpM.5G1Gj8eeKrmTLk2v" + }, + { + "sort": 1600000, + "name": "Sickened", + "type": "text", + "_id": "nUy4CjnVZgL1bsdn", + "title": { + "show": true, + "level": 1 + }, + "image": {}, + "text": { + "format": 1, + "content": "

You make all rolls with -1

" + }, + "video": { + "controls": true, + "volume": 0.5 + }, + "src": null, + "system": {}, + "ownership": { + "default": -1, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1707676151943, + "modifiedTime": 1707676154116, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!journal.pages!yAgJW3Aa4cXf8OpM.nUy4CjnVZgL1bsdn" + }, + { + "sort": 1700000, + "name": "Silenced", + "type": "text", + "_id": "xuAqJEceowRBS6EK", + "title": { + "show": true, + "level": 1 + }, + "image": {}, + "text": { + "format": 1, + "content": "

Cannot cast songs and voice communication doesn't work. Cover your mouth or mute your microphone.

" + }, + "video": { + "controls": true, + "volume": 0.5 + }, + "src": null, + "system": {}, + "ownership": { + "default": -1, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1707676173514, + "modifiedTime": 1707676176637, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!journal.pages!yAgJW3Aa4cXf8OpM.xuAqJEceowRBS6EK" + }, + { + "sort": 1800000, + "name": "Sleeping", + "type": "text", + "_id": "vrAbVZkCvwsjRb0i", + "title": { + "show": true, + "level": 1 + }, + "image": {}, + "text": { + "format": 1, + "content": "

All attacks auto-hit and deal twice as much damage. If attacking a sleeping mob, the attack removes two dice.

" + }, + "video": { + "controls": true, + "volume": 0.5 + }, + "src": null, + "system": {}, + "ownership": { + "default": -1, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1707676185470, + "modifiedTime": 1707676188134, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!journal.pages!yAgJW3Aa4cXf8OpM.vrAbVZkCvwsjRb0i" + }, + { + "sort": 1900000, + "name": "Slowed", + "type": "text", + "_id": "lkpQ9j1NLhnkfZvI", + "title": { + "show": true, + "level": 1 + }, + "image": {}, + "text": { + "format": 1, + "content": "

You go at the end of initiative and can only more or take an action on your turn, not both.

" + }, + "video": { + "controls": true, + "volume": 0.5 + }, + "src": null, + "system": {}, + "ownership": { + "default": -1, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1707676197864, + "modifiedTime": 1707676201034, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!journal.pages!yAgJW3Aa4cXf8OpM.lkpQ9j1NLhnkfZvI" + }, + { + "sort": 2000000, + "name": "Stunned", + "type": "text", + "_id": "aTVNF7ftndKpRO3C", + "title": { + "show": true, + "level": 1 + }, + "image": {}, + "text": { + "format": 1, + "content": "

Your turn is skilled this round of initiative. Then remove this status.

" + }, + "video": { + "controls": true, + "volume": 0.5 + }, + "src": null, + "system": {}, + "ownership": { + "default": -1, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1707676211147, + "modifiedTime": 1707676215921, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!journal.pages!yAgJW3Aa4cXf8OpM.aTVNF7ftndKpRO3C" + }, + { + "sort": 2100000, + "name": "Tripped", + "type": "text", + "_id": "S4ogXF1XCORqPqz3", + "title": { + "show": true, + "level": 1 + }, + "image": {}, + "text": { + "format": 1, + "content": "

Melee attacks against you count as crits if they hit. Ranged attacks treat you as behind partial cover. You can end this status at the end of your turn, no roll is needed.

" + }, + "video": { + "controls": true, + "volume": 0.5 + }, + "src": null, + "system": {}, + "ownership": { + "default": -1, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1707676224313, + "modifiedTime": 1707676227775, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!journal.pages!yAgJW3Aa4cXf8OpM.S4ogXF1XCORqPqz3" + } + ], + "folder": null, + "sort": 0, + "ownership": { + "default": 0, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1707675666312, + "modifiedTime": 1707676227775, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!journal!yAgJW3Aa4cXf8OpM" +} diff --git a/packs/spells/_source/Angel_s_Fury_eyTlmHFZTOF79byJ.json b/packs/spells/_source/Angel_s_Fury_eyTlmHFZTOF79byJ.json new file mode 100644 index 0000000..690fccb --- /dev/null +++ b/packs/spells/_source/Angel_s_Fury_eyTlmHFZTOF79byJ.json @@ -0,0 +1,33 @@ +{ + "folder": null, + "name": "Angel's Fury", + "type": "spell", + "img": "icons/svg/item-bag.svg", + "system": { + "description": "You enshrine a party member's head with a fiery crown, allowing them to auto-destroy a single mob up to a d10.", + "skill": "magic", + "quantity": 1, + "buy": null, + "usage_cost": null, + "tier": "simple" + }, + "effects": [], + "ownership": { + "default": 0, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": { + "core": {} + }, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1704947604011, + "modifiedTime": 1707674953656, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_id": "eyTlmHFZTOF79byJ", + "sort": 1700000, + "_key": "!items!eyTlmHFZTOF79byJ" +} diff --git a/packs/spells/_source/Black_Pool_WDKHWifnRseeOWPM.json b/packs/spells/_source/Black_Pool_WDKHWifnRseeOWPM.json new file mode 100644 index 0000000..875fd4c --- /dev/null +++ b/packs/spells/_source/Black_Pool_WDKHWifnRseeOWPM.json @@ -0,0 +1,33 @@ +{ + "folder": null, + "name": "Black Pool", + "type": "spell", + "img": "icons/svg/item-bag.svg", + "system": { + "description": "You cast unholy light onto the bottom of the dungeon steps, flooding it with magical, black ichor. From its murky depths, Darkroot, the Skeleton Prince, crawls forth to answer a single question about the dungeon you're in.", + "skill": "dreams", + "quantity": 1, + "buy": null, + "usage_cost": null, + "tier": "simple" + }, + "effects": [], + "ownership": { + "default": 0, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": { + "core": {} + }, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1704947661164, + "modifiedTime": 1707674953656, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_id": "WDKHWifnRseeOWPM", + "sort": 1800000, + "_key": "!items!WDKHWifnRseeOWPM" +} diff --git a/packs/spells/_source/Bubble_8ryHC0LXO5V7t8Kz.json b/packs/spells/_source/Bubble_8ryHC0LXO5V7t8Kz.json new file mode 100644 index 0000000..020583f --- /dev/null +++ b/packs/spells/_source/Bubble_8ryHC0LXO5V7t8Kz.json @@ -0,0 +1,33 @@ +{ + "folder": null, + "name": "Bubble", + "type": "spell", + "img": "icons/svg/item-bag.svg", + "system": { + "description": "Create a circle with your hand to encase one target in a bubble that floats above the battlefield. The target may roll Tilt to end this effect. Any damage to the bubble pops it. A falling target is stunned until their next turn.", + "skill": "magic", + "quantity": 1, + "buy": null, + "usage_cost": null, + "tier": "simple" + }, + "effects": [], + "ownership": { + "default": 0, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": { + "core": {} + }, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1704947765627, + "modifiedTime": 1707674953656, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_id": "8ryHC0LXO5V7t8Kz", + "sort": 1600000, + "_key": "!items!8ryHC0LXO5V7t8Kz" +} diff --git a/packs/spells/_source/Buff_IfNKFvoGeMXEVMNz.json b/packs/spells/_source/Buff_IfNKFvoGeMXEVMNz.json new file mode 100644 index 0000000..280abd6 --- /dev/null +++ b/packs/spells/_source/Buff_IfNKFvoGeMXEVMNz.json @@ -0,0 +1,33 @@ +{ + "folder": null, + "name": "Buff", + "type": "spell", + "img": "icons/svg/item-bag.svg", + "system": { + "description": "The magic of the elk hand sign grants each party member a d8 to add to their next roll.", + "skill": "dreams", + "quantity": 1, + "buy": null, + "usage_cost": null, + "tier": "simple" + }, + "effects": [], + "ownership": { + "default": 0, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": { + "core": {} + }, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1704947859875, + "modifiedTime": 1707674953656, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_id": "IfNKFvoGeMXEVMNz", + "sort": 1400000, + "_key": "!items!IfNKFvoGeMXEVMNz" +} diff --git a/packs/spells/_source/Cellar_Door_ywIywJEL6r84ETx6.json b/packs/spells/_source/Cellar_Door_ywIywJEL6r84ETx6.json new file mode 100644 index 0000000..e1a1cec --- /dev/null +++ b/packs/spells/_source/Cellar_Door_ywIywJEL6r84ETx6.json @@ -0,0 +1,33 @@ +{ + "folder": null, + "name": "Cellar Door", + "type": "spell", + "img": "icons/svg/item-bag.svg", + "system": { + "description": "A surface becomes an entrance into a liminal space big enough to hold five party members. If the door is shut from the inside, the space is impenetrable. It lasts for up to thirty minutes.", + "skill": "magic", + "quantity": 1, + "buy": null, + "usage_cost": null, + "tier": "simple" + }, + "effects": [], + "ownership": { + "default": 0, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": { + "core": {} + }, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1704947905093, + "modifiedTime": 1707674953656, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_id": "ywIywJEL6r84ETx6", + "sort": 1500000, + "_key": "!items!ywIywJEL6r84ETx6" +} diff --git a/packs/spells/_source/Circle_of_Protection_G74mZfS2L2Ph6POM.json b/packs/spells/_source/Circle_of_Protection_G74mZfS2L2Ph6POM.json new file mode 100644 index 0000000..e4e71d8 --- /dev/null +++ b/packs/spells/_source/Circle_of_Protection_G74mZfS2L2Ph6POM.json @@ -0,0 +1,33 @@ +{ + "folder": null, + "name": "Circle of Protection", + "type": "spell", + "img": "icons/svg/item-bag.svg", + "system": { + "description": "Draw a space around two targets. For one encounter, all damage inside or from outside the circle is voided. Entities inside can’t attack anyone outside without breaking the circle.", + "skill": "magic", + "quantity": 1, + "buy": null, + "usage_cost": null, + "tier": "simple" + }, + "effects": [], + "ownership": { + "default": 0, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": { + "core": {} + }, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1704947987314, + "modifiedTime": 1707674953656, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_id": "G74mZfS2L2Ph6POM", + "sort": 1300000, + "_key": "!items!G74mZfS2L2Ph6POM" +} diff --git a/packs/spells/_source/Dark_Tendril_WhowfuX05z9o4fyD.json b/packs/spells/_source/Dark_Tendril_WhowfuX05z9o4fyD.json new file mode 100644 index 0000000..12c1d04 --- /dev/null +++ b/packs/spells/_source/Dark_Tendril_WhowfuX05z9o4fyD.json @@ -0,0 +1,33 @@ +{ + "folder": null, + "name": "Dark Tendril", + "type": "spell", + "img": "icons/svg/item-bag.svg", + "system": { + "description": "Shadow tentacles rise from the surrounding surfaces, grappling up to four entities of d6 dice or lower", + "skill": "magic", + "quantity": 1, + "buy": null, + "usage_cost": null, + "tier": "simple" + }, + "effects": [], + "ownership": { + "default": 0, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": { + "core": {} + }, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1704948047394, + "modifiedTime": 1707674953656, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_id": "WhowfuX05z9o4fyD", + "sort": 900000, + "_key": "!items!WhowfuX05z9o4fyD" +} diff --git a/packs/spells/_source/Disguise_Self_ESwYY87mZbzVT1lW.json b/packs/spells/_source/Disguise_Self_ESwYY87mZbzVT1lW.json new file mode 100644 index 0000000..c26804e --- /dev/null +++ b/packs/spells/_source/Disguise_Self_ESwYY87mZbzVT1lW.json @@ -0,0 +1,33 @@ +{ + "folder": null, + "name": "Disguise Self", + "type": "spell", + "img": "icons/svg/item-bag.svg", + "system": { + "description": "Transform your avatar into an entity matching the environment, like a vase, chest, or plant. This illusion stays until you are attacked or make an attack.", + "skill": "magic", + "quantity": 1, + "buy": null, + "usage_cost": null, + "tier": "simple" + }, + "effects": [], + "ownership": { + "default": 0, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": { + "core": {} + }, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1704948095237, + "modifiedTime": 1707674953656, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_id": "ESwYY87mZbzVT1lW", + "sort": 1000000, + "_key": "!items!ESwYY87mZbzVT1lW" +} diff --git a/packs/spells/_source/Heal_WhDuH34bhhDHK3LV.json b/packs/spells/_source/Heal_WhDuH34bhhDHK3LV.json new file mode 100644 index 0000000..f7f8880 --- /dev/null +++ b/packs/spells/_source/Heal_WhDuH34bhhDHK3LV.json @@ -0,0 +1,33 @@ +{ + "folder": null, + "name": "Heal", + "type": "spell", + "img": "icons/svg/item-bag.svg", + "system": { + "description": "Heal wounds, young and old, by soothing the energy around you. Regain d10 Sync.", + "skill": "magic", + "quantity": 1, + "buy": null, + "usage_cost": null, + "tier": "simple" + }, + "effects": [], + "ownership": { + "default": 0, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": { + "core": {} + }, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1704948272274, + "modifiedTime": 1707674953656, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_id": "WhDuH34bhhDHK3LV", + "sort": 1100000, + "_key": "!items!WhDuH34bhhDHK3LV" +} diff --git a/packs/spells/_source/Liminalis_pkrgJoP3noq3EY4o.json b/packs/spells/_source/Liminalis_pkrgJoP3noq3EY4o.json new file mode 100644 index 0000000..65d0239 --- /dev/null +++ b/packs/spells/_source/Liminalis_pkrgJoP3noq3EY4o.json @@ -0,0 +1,33 @@ +{ + "folder": null, + "name": "Liminalis", + "type": "spell", + "img": "icons/svg/item-bag.svg", + "system": { + "description": "Draw the essence of another door upon the door in front of you. They are now linked forever for you and your party members.", + "skill": "dreams", + "quantity": 1, + "buy": null, + "usage_cost": null, + "tier": "simple" + }, + "effects": [], + "ownership": { + "default": 0, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": { + "core": {} + }, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1704948322259, + "modifiedTime": 1707674953656, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_id": "pkrgJoP3noq3EY4o", + "sort": 1200000, + "_key": "!items!pkrgJoP3noq3EY4o" +} diff --git a/packs/spells/_source/Meteor_YifLvJsoCKAy8mBS.json b/packs/spells/_source/Meteor_YifLvJsoCKAy8mBS.json new file mode 100644 index 0000000..b0306c0 --- /dev/null +++ b/packs/spells/_source/Meteor_YifLvJsoCKAy8mBS.json @@ -0,0 +1,33 @@ +{ + "folder": null, + "name": "Meteor", + "type": "spell", + "img": "icons/svg/item-bag.svg", + "system": { + "description": "Reach into the Detritus and pull something free. Choose two entities in addition to yourself to survive. Destroy all other entities in the area.", + "skill": "magic", + "quantity": 1, + "buy": null, + "usage_cost": null, + "tier": "simple" + }, + "effects": [], + "ownership": { + "default": 0, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": { + "core": {} + }, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1704948377846, + "modifiedTime": 1707674953656, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_id": "YifLvJsoCKAy8mBS", + "sort": 800000, + "_key": "!items!YifLvJsoCKAy8mBS" +} diff --git a/packs/spells/_source/Mimicry_YKqNvgTIblW9f95K.json b/packs/spells/_source/Mimicry_YKqNvgTIblW9f95K.json new file mode 100644 index 0000000..e1732c0 --- /dev/null +++ b/packs/spells/_source/Mimicry_YKqNvgTIblW9f95K.json @@ -0,0 +1,33 @@ +{ + "folder": null, + "name": "Mimicry", + "type": "spell", + "img": "icons/svg/item-bag.svg", + "system": { + "description": "Split a copy off of yourself. You can puppet them through hand signs, but they still move and act when you aren't controlling them.", + "skill": "magic", + "quantity": 1, + "buy": null, + "usage_cost": null, + "tier": "simple" + }, + "effects": [], + "ownership": { + "default": 0, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": { + "core": {} + }, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1704948442337, + "modifiedTime": 1707674953656, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_id": "YKqNvgTIblW9f95K", + "sort": 200000, + "_key": "!items!YKqNvgTIblW9f95K" +} diff --git a/packs/spells/_source/Obliterate_M5aiwm9enP3G2nYG.json b/packs/spells/_source/Obliterate_M5aiwm9enP3G2nYG.json new file mode 100644 index 0000000..88d2d64 --- /dev/null +++ b/packs/spells/_source/Obliterate_M5aiwm9enP3G2nYG.json @@ -0,0 +1,33 @@ +{ + "folder": null, + "name": "Obliterate", + "type": "spell", + "img": "icons/svg/item-bag.svg", + "system": { + "description": "Encase a hostile entity in your hand and permanently remove it from the field of battle.", + "skill": "magic", + "quantity": 1, + "buy": null, + "usage_cost": null, + "tier": "simple" + }, + "effects": [], + "ownership": { + "default": 0, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": { + "core": {} + }, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1704948695404, + "modifiedTime": 1707674953656, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_id": "M5aiwm9enP3G2nYG", + "sort": 300000, + "_key": "!items!M5aiwm9enP3G2nYG" +} diff --git a/packs/spells/_source/Omega_7wHFyHwO0rTAR9tm.json b/packs/spells/_source/Omega_7wHFyHwO0rTAR9tm.json new file mode 100644 index 0000000..10f31f1 --- /dev/null +++ b/packs/spells/_source/Omega_7wHFyHwO0rTAR9tm.json @@ -0,0 +1,33 @@ +{ + "folder": null, + "name": "Omega", + "type": "spell", + "img": "icons/svg/item-bag.svg", + "system": { + "description": "Can only be case when you reach 0 Sync. Sacrifice your avatar and your influence over Annwn to give the party 20 Sync", + "skill": "magic", + "quantity": 1, + "buy": null, + "usage_cost": null, + "tier": "simple" + }, + "effects": [], + "ownership": { + "default": 0, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": { + "core": {} + }, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1704948748118, + "modifiedTime": 1707674953656, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_id": "7wHFyHwO0rTAR9tm", + "sort": 400000, + "_key": "!items!7wHFyHwO0rTAR9tm" +} diff --git a/packs/spells/_source/Polymorph_rOw26GbtObv8XNcO.json b/packs/spells/_source/Polymorph_rOw26GbtObv8XNcO.json new file mode 100644 index 0000000..66fc522 --- /dev/null +++ b/packs/spells/_source/Polymorph_rOw26GbtObv8XNcO.json @@ -0,0 +1,33 @@ +{ + "folder": null, + "name": "Polymorph", + "type": "spell", + "img": "icons/svg/item-bag.svg", + "system": { + "description": "With a few quick motions, turn one hostile entity into a chicken. This lasts until they are attacked.", + "skill": "magic", + "quantity": 1, + "buy": null, + "usage_cost": null, + "tier": "simple" + }, + "effects": [], + "ownership": { + "default": 0, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": { + "core": {} + }, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1704948804588, + "modifiedTime": 1707674953656, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_id": "rOw26GbtObv8XNcO", + "sort": 500000, + "_key": "!items!rOw26GbtObv8XNcO" +} diff --git a/packs/spells/_source/Solar_Flare_KblpgCg4Bmmk0sJS.json b/packs/spells/_source/Solar_Flare_KblpgCg4Bmmk0sJS.json new file mode 100644 index 0000000..cfa4d65 --- /dev/null +++ b/packs/spells/_source/Solar_Flare_KblpgCg4Bmmk0sJS.json @@ -0,0 +1,33 @@ +{ + "folder": null, + "name": "Solar Flare", + "type": "spell", + "img": "icons/svg/item-bag.svg", + "system": { + "description": "Eclipse the sun with your hand. The halo of light auto-destroys d4 entities in the field of battle.", + "skill": "", + "quantity": 1, + "buy": null, + "usage_cost": null, + "tier": "simple" + }, + "effects": [], + "ownership": { + "default": 0, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": { + "core": {} + }, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1704948846489, + "modifiedTime": 1707674953656, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_id": "KblpgCg4Bmmk0sJS", + "sort": 600000, + "_key": "!items!KblpgCg4Bmmk0sJS" +} diff --git a/packs/spells/_source/Summon_Fallminx_esWqcb9nm8NExqye.json b/packs/spells/_source/Summon_Fallminx_esWqcb9nm8NExqye.json new file mode 100644 index 0000000..ba2061c --- /dev/null +++ b/packs/spells/_source/Summon_Fallminx_esWqcb9nm8NExqye.json @@ -0,0 +1,33 @@ +{ + "folder": null, + "name": "Summon Fallminx", + "type": "spell", + "img": "icons/svg/item-bag.svg", + "system": { + "description": "Create an area for Fallminx to land. Fallminx counts as a d20x2, looks like a gargantuan gargoyle, and wields a mighty scythe", + "skill": "dreams", + "quantity": 1, + "buy": null, + "usage_cost": null, + "tier": "simple" + }, + "effects": [], + "ownership": { + "default": 0, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": { + "core": {} + }, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1704948896351, + "modifiedTime": 1707674953656, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_id": "esWqcb9nm8NExqye", + "sort": 700000, + "_key": "!items!esWqcb9nm8NExqye" +} diff --git a/packs/spells/_source/Teleport_PDh4OihLyz0MgORt.json b/packs/spells/_source/Teleport_PDh4OihLyz0MgORt.json new file mode 100644 index 0000000..072cc43 --- /dev/null +++ b/packs/spells/_source/Teleport_PDh4OihLyz0MgORt.json @@ -0,0 +1,33 @@ +{ + "folder": null, + "name": "Teleport", + "type": "spell", + "img": "icons/svg/item-bag.svg", + "system": { + "description": "Escape through velvet curtains, returning to the last settlement you visited.", + "skill": "magic", + "quantity": 1, + "buy": null, + "usage_cost": null, + "tier": "simple" + }, + "effects": [], + "ownership": { + "default": 0, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": { + "core": {} + }, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1704948957420, + "modifiedTime": 1707674953656, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_id": "PDh4OihLyz0MgORt", + "sort": 100000, + "_key": "!items!PDh4OihLyz0MgORt" +} diff --git a/packs/spells/_source/Vanishment_qO7UpU2Y5v5fmuKY.json b/packs/spells/_source/Vanishment_qO7UpU2Y5v5fmuKY.json new file mode 100644 index 0000000..65ee79f --- /dev/null +++ b/packs/spells/_source/Vanishment_qO7UpU2Y5v5fmuKY.json @@ -0,0 +1,33 @@ +{ + "folder": null, + "name": "Vanishment", + "type": "spell", + "img": "icons/svg/item-bag.svg", + "system": { + "description": "Hide a single entity from all eyes.", + "skill": "magic", + "quantity": 1, + "buy": null, + "usage_cost": null, + "tier": "simple" + }, + "effects": [], + "ownership": { + "default": 0, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": { + "core": {} + }, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1704949001028, + "modifiedTime": 1707674955455, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_id": "qO7UpU2Y5v5fmuKY", + "sort": 0, + "_key": "!items!qO7UpU2Y5v5fmuKY" +} diff --git a/packs/spells/_source/Wall_5Af5TbY1z9r9ssGm.json b/packs/spells/_source/Wall_5Af5TbY1z9r9ssGm.json new file mode 100644 index 0000000..4cc11e4 --- /dev/null +++ b/packs/spells/_source/Wall_5Af5TbY1z9r9ssGm.json @@ -0,0 +1,33 @@ +{ + "folder": null, + "name": "Wall", + "type": "spell", + "img": "icons/svg/item-bag.svg", + "system": { + "description": "Add to the dungeon, change the architecture. A new wall is created from surrounding material, connecting two nearby walls of your choice.", + "skill": "dreams", + "quantity": 1, + "buy": null, + "usage_cost": null, + "tier": "simple" + }, + "effects": [], + "ownership": { + "default": 0, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": { + "core": {} + }, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1704949029619, + "modifiedTime": 1707674960427, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_id": "5Af5TbY1z9r9ssGm", + "sort": 0, + "_key": "!items!5Af5TbY1z9r9ssGm" +} diff --git a/packs/untyped/_source/Dream_Materials_Y2ngvKcIMgfTaW05.json b/packs/untyped/_source/Dream_Materials_Y2ngvKcIMgfTaW05.json new file mode 100644 index 0000000..40e992c --- /dev/null +++ b/packs/untyped/_source/Dream_Materials_Y2ngvKcIMgfTaW05.json @@ -0,0 +1,30 @@ +{ + "name": "Dream Materials", + "type": "untyped", + "_id": "Y2ngvKcIMgfTaW05", + "img": "icons/svg/item-bag.svg", + "system": { + "quantity": 1, + "buy": null, + "usage_cost": null, + "tier": "simple", + "description": "" + }, + "effects": [], + "folder": "MMUe6fSojzLZ1kEx", + "sort": 100000, + "ownership": { + "default": 0, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1707677200136, + "modifiedTime": 1708052963493, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!items!Y2ngvKcIMgfTaW05" +} diff --git a/packs/untyped/_source/Dungeon_Foil_WQSTrX0ul4z9K6wd.json b/packs/untyped/_source/Dungeon_Foil_WQSTrX0ul4z9K6wd.json new file mode 100644 index 0000000..7b9e57a --- /dev/null +++ b/packs/untyped/_source/Dungeon_Foil_WQSTrX0ul4z9K6wd.json @@ -0,0 +1,30 @@ +{ + "folder": "FpQZ612cZuEoLAEQ", + "name": "Dungeon Foil", + "type": "untyped", + "_id": "WQSTrX0ul4z9K6wd", + "img": "icons/svg/item-bag.svg", + "system": { + "quantity": 1, + "buy": 3, + "usage_cost": 0, + "tier": "simple", + "description": "Removes you and one ally from a dungeon or encounter. Tears after use." + }, + "effects": [], + "sort": 0, + "ownership": { + "default": 0, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1708053148260, + "modifiedTime": 1708053167578, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!items!WQSTrX0ul4z9K6wd" +} diff --git a/packs/untyped/_source/Fashion_Materials_7Wa6J1aOeFoOGWqo.json b/packs/untyped/_source/Fashion_Materials_7Wa6J1aOeFoOGWqo.json new file mode 100644 index 0000000..4e85d77 --- /dev/null +++ b/packs/untyped/_source/Fashion_Materials_7Wa6J1aOeFoOGWqo.json @@ -0,0 +1,30 @@ +{ + "name": "Fashion Materials", + "type": "untyped", + "_id": "7Wa6J1aOeFoOGWqo", + "img": "icons/svg/item-bag.svg", + "system": { + "quantity": 1, + "buy": null, + "usage_cost": null, + "tier": "simple", + "description": "A resource used to craft dyes and buy decorations. Fashion Materials can be acquired through rare mob drops, found in dungeon chests, or traded for at the Auction House." + }, + "effects": [], + "folder": "MMUe6fSojzLZ1kEx", + "sort": 200000, + "ownership": { + "default": 0, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1707677060852, + "modifiedTime": 1708052964560, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!items!7Wa6J1aOeFoOGWqo" +} diff --git a/packs/untyped/_source/Foils_FpQZ612cZuEoLAEQ.json b/packs/untyped/_source/Foils_FpQZ612cZuEoLAEQ.json new file mode 100644 index 0000000..3adb656 --- /dev/null +++ b/packs/untyped/_source/Foils_FpQZ612cZuEoLAEQ.json @@ -0,0 +1,19 @@ +{ + "name": "Foils", + "sorting": "a", + "folder": null, + "type": "Item", + "_id": "FpQZ612cZuEoLAEQ", + "sort": 0, + "color": null, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1708052955406, + "modifiedTime": 1708052955406, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!folders!FpQZ612cZuEoLAEQ" +} diff --git a/packs/untyped/_source/Healing_Foil_4w2ZytQagi4Cjhnm.json b/packs/untyped/_source/Healing_Foil_4w2ZytQagi4Cjhnm.json new file mode 100644 index 0000000..ec4440b --- /dev/null +++ b/packs/untyped/_source/Healing_Foil_4w2ZytQagi4Cjhnm.json @@ -0,0 +1,30 @@ +{ + "folder": "FpQZ612cZuEoLAEQ", + "name": "Healing Foil", + "type": "untyped", + "_id": "4w2ZytQagi4Cjhnm", + "img": "icons/svg/item-bag.svg", + "system": { + "quantity": 1, + "buy": 5, + "usage_cost": 0, + "tier": "simple", + "description": "Heals 1d6 Sync. Tears after use." + }, + "effects": [], + "sort": 0, + "ownership": { + "default": 0, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1708053058625, + "modifiedTime": 1708053118561, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!items!4w2ZytQagi4Cjhnm" +} diff --git a/packs/untyped/_source/Lucky_Foil_fDAWnUfvsPW7OrQz.json b/packs/untyped/_source/Lucky_Foil_fDAWnUfvsPW7OrQz.json new file mode 100644 index 0000000..55d1675 --- /dev/null +++ b/packs/untyped/_source/Lucky_Foil_fDAWnUfvsPW7OrQz.json @@ -0,0 +1,30 @@ +{ + "folder": "FpQZ612cZuEoLAEQ", + "name": "Lucky Foil", + "type": "untyped", + "_id": "fDAWnUfvsPW7OrQz", + "img": "icons/svg/item-bag.svg", + "system": { + "quantity": 1, + "buy": 4, + "usage_cost": 0, + "tier": "simple", + "description": "Reroll one RNG roll. Tears after use." + }, + "effects": [], + "sort": 0, + "ownership": { + "default": 0, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1708052984357, + "modifiedTime": 1708053080172, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!items!fDAWnUfvsPW7OrQz" +} diff --git a/packs/untyped/_source/Materials_MMUe6fSojzLZ1kEx.json b/packs/untyped/_source/Materials_MMUe6fSojzLZ1kEx.json new file mode 100644 index 0000000..a1e7640 --- /dev/null +++ b/packs/untyped/_source/Materials_MMUe6fSojzLZ1kEx.json @@ -0,0 +1,19 @@ +{ + "name": "Materials", + "sorting": "a", + "folder": null, + "type": "Item", + "_id": "MMUe6fSojzLZ1kEx", + "sort": 0, + "color": null, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1708052960887, + "modifiedTime": 1708052960887, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!folders!MMUe6fSojzLZ1kEx" +} diff --git a/packs/untyped/_source/Solar_Foil_IaDutRd6ggG5y1I9.json b/packs/untyped/_source/Solar_Foil_IaDutRd6ggG5y1I9.json new file mode 100644 index 0000000..d3bfca7 --- /dev/null +++ b/packs/untyped/_source/Solar_Foil_IaDutRd6ggG5y1I9.json @@ -0,0 +1,30 @@ +{ + "folder": "FpQZ612cZuEoLAEQ", + "name": "Solar Foil", + "type": "untyped", + "_id": "IaDutRd6ggG5y1I9", + "img": "icons/svg/item-bag.svg", + "system": { + "quantity": 1, + "buy": 4, + "usage_cost": 0, + "tier": "simple", + "description": "Insert into your weapon to imbue it with solar damage. Insert it into your armor to negate the next solar damage you take." + }, + "effects": [], + "sort": 0, + "ownership": { + "default": 0, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1708053008790, + "modifiedTime": 1708053083362, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!items!IaDutRd6ggG5y1I9" +} diff --git a/packs/untyped/_source/Traveller_s_Clothes_krrugTrTjfZthHlZ.json b/packs/untyped/_source/Traveller_s_Clothes_krrugTrTjfZthHlZ.json new file mode 100644 index 0000000..0cdfa1f --- /dev/null +++ b/packs/untyped/_source/Traveller_s_Clothes_krrugTrTjfZthHlZ.json @@ -0,0 +1,30 @@ +{ + "name": "Traveller's Clothes", + "type": "untyped", + "_id": "krrugTrTjfZthHlZ", + "img": "icons/svg/item-bag.svg", + "system": { + "quantity": 1, + "buy": null, + "usage_cost": null, + "tier": "simple", + "description": "A basic set of clothes that are used for adventuring." + }, + "effects": [], + "folder": null, + "sort": 0, + "ownership": { + "default": 0, + "SXF6LgHA8oYfOhCm": 3 + }, + "flags": {}, + "_stats": { + "systemId": "dotdungeon", + "systemVersion": "0.0.5", + "coreVersion": "11.315", + "createdTime": 1707677259313, + "modifiedTime": 1707677284619, + "lastModifiedBy": "SXF6LgHA8oYfOhCm" + }, + "_key": "!items!krrugTrTjfZthHlZ" +} diff --git a/scripts/buildCompendia.mjs b/scripts/buildCompendia.mjs new file mode 100644 index 0000000..2b39aa3 --- /dev/null +++ b/scripts/buildCompendia.mjs @@ -0,0 +1,32 @@ +import { existsSync } from "fs"; +import { readFile } from "fs/promises"; +import { join } from "path"; +import { compilePack } from "@foundryvtt/foundryvtt-cli"; + +async function main() { + const system = JSON.parse(await readFile(`./system.json`, `utf-8`)); + + if (!system.packs || system.packs.length === 0) { + console.log(`No compendium packs defined`); + process.exit(0); + }; + + for (const compendium of system.packs) { + console.debug(`Packing ${compendium.label} (${compendium.name})`); + let src = join(process.cwd(), compendium.path, `_source`); + if (!existsSync(src)) { + console.warn(`${compendium.path} doesn't exist, skipping.`) + continue; + }; + await compilePack( + src, + join(process.cwd(), compendium.path), + { recursive: true }, + ); + console.debug(`Finished packing ${compendium.name}`); + }; + + console.log(`Finished packing compendia`) +}; + +main(); diff --git a/scripts/extractCompendia.mjs b/scripts/extractCompendia.mjs new file mode 100644 index 0000000..32935c9 --- /dev/null +++ b/scripts/extractCompendia.mjs @@ -0,0 +1,27 @@ +import { readFile } from "fs/promises"; +import { join } from "path"; +import { extractPack } from "@foundryvtt/foundryvtt-cli"; + +async function main() { + const system = JSON.parse(await readFile(`./system.json`, `utf-8`)); + + if (!system.packs || system.packs.length === 0) { + console.log(`No compendium packs defined`); + process.exit(0); + }; + + for (const compendium of system.packs) { + console.debug(`Unpacking ${compendium.label} (${compendium.name})`); + let src = join(process.cwd(), compendium.path, `_source`); + await extractPack( + join(process.cwd(), compendium.path), + src, + { recursive: true }, + ); + console.debug(`Finished packing ${compendium.name}`); + }; + + console.log(`Finished unpacking compendia`); +}; + +main(); diff --git a/scripts/macros/deleteInvalidActors.mjs b/scripts/macros/deleteInvalidActors.mjs new file mode 100644 index 0000000..c8f09e6 --- /dev/null +++ b/scripts/macros/deleteInvalidActors.mjs @@ -0,0 +1,4 @@ +const invalids = game.actors.invalidDocumentIds; +invalids.forEach(id => { + game.actors.getInvalid(id).delete(); +}); diff --git a/scripts/macros/deleteInvalidItems.mjs b/scripts/macros/deleteInvalidItems.mjs new file mode 100644 index 0000000..6fcbdc9 --- /dev/null +++ b/scripts/macros/deleteInvalidItems.mjs @@ -0,0 +1,4 @@ +const invalids = game.items.invalidDocumentIds; +invalids.forEach(id => { + game.items.getInvalid(id).delete(); +}); diff --git a/scripts/preventLocalizationCycles.mjs b/scripts/preventLocalizationCycles.mjs new file mode 100644 index 0000000..0271122 --- /dev/null +++ b/scripts/preventLocalizationCycles.mjs @@ -0,0 +1,76 @@ +/* +The purpose of this script is to validate all of the language files to ensure +that there are no cycles in them to prevent infinite recursion. This must pull +the pattern to match subkeys on via the config, otherwise this could result in +inconsistencies with the localizer logic. +*/ + +import { readFile } from "fs/promises"; + +class Node { + /** @type {Array} */ + connectsTo = []; + + /** @type {boolean} */ + visited = false; + + /** @type {boolean} */ + finished = false; + + id; + constructor(data) { this.id = data; }; +}; + +/** + * @param {object | string} lang The localization object to convert into a graph + * @returns {Promise | Node>} + */ +async function createGraph(data) { + throw new Error(`createGraph not Implemented Yet`); +}; + +/** + * @param {Node} from + * @returns {Promise} + */ +async function depthFirstSearch(from) { + if (from.finished) return false; + if (from.visited) return true; + from.visited = true; + for (const neighbour of from.connectsTo) { + if (depthFirstSearch(neighbour)) return true; + }; + from.finished = true; + return false; +}; + +/** + * @param {Array} graph + */ +async function checkForCycles(graph) { + for (const node of graph) { + if (node.finished) { + console.log(`skipping node: ${node.id}`); + continue; + } + if (depthFirstSearch(node)) { + console.log(`cycle found in node: ${node.id}`); + }; + }; +}; + +async function main() { + /* + Process: + - Load the system.json to identify all lang files + - Iterate through defined languages + - Construct a graph from the language file + - Iterate through nodes checking for a cycle + */ + const lang = JSON.parse(await readFile("test.lang") ?? "{}"); + const graph = await createGraph(lang); + console.log(graph) + // await checkForCycles(graph); +}; + +main(); diff --git a/scripts/updateSystem.mjs b/scripts/updateSystem.mjs new file mode 100644 index 0000000..9670675 --- /dev/null +++ b/scripts/updateSystem.mjs @@ -0,0 +1,13 @@ +/* +Takes the system.json and updates all the release-specific properties in it to +help prevent erroneous updates from being made when using the local package for +development. + +--- + +Set the "manifest" property to: + "url" property + "/releases/latest/download/system.json" + +Set the "download" property to: + "url" property + "/releases/download/{version number}/{zip_name}.zip" +*/ diff --git a/styles/_vars.scss b/styles/_vars.scss index 690dcae..ddb9cf1 100644 --- a/styles/_vars.scss +++ b/styles/_vars.scss @@ -8,7 +8,7 @@ $background: #f2f2f2; $colour-confirm: #048A81; $text-on-confirm: white; -$colour-neutral: #007ACC; +$colour-neutral: darkblue; $text-on-neutral: white; $colour-danger: #960200; diff --git a/styles/dialog/DiceList.scss b/styles/dialog/DiceList.scss new file mode 100644 index 0000000..c7833bc --- /dev/null +++ b/styles/dialog/DiceList.scss @@ -0,0 +1,54 @@ +.dotdungeon:not(.style-v3):has(.dialog--dice-list) { + max-width: 275px; + min-width: 275px; +} + +.dotdungeon:not(.style-v3) .dialog--dice-list { + padding: 8px; + + .dice { + display: flex; + flex-direction: column; + gap: 8px; + } + + .die { + align-items: center; + border: 2px solid; + border-radius: 4px; + display: grid; + gap: 4px; + grid-template-rows: 40px; + grid-template-columns: 1fr 15px 1fr 15px 1fr 15px 1fr; + padding: 4px; + text-align: center; + + label { + font-size: 0.8125rem; + } + + .large { font-size: 1.2rem; } + + .count { grid-column: 1; } + .sides { grid-column: 3; } + .repeat { grid-column: 5; } + + button { grid-column: 7; } + + input { + height: 100%; + font-size: 1.1rem; + } + } + + .actions { + margin-top: 8px; + display: flex; + flex-direction: row; + gap: 4px; + + button { + flex-grow: 1; + } + } +} \ No newline at end of file diff --git a/styles/generic.scss b/styles/generic.scss index 0f7ecec..92cfb3e 100644 --- a/styles/generic.scss +++ b/styles/generic.scss @@ -4,18 +4,31 @@ @import url('https://fonts.googleapis.com/css2?family=Pixelify+Sans&display=swap'); -// Reset the parts of Foundry's styling which gets in the way of what I want -.dotdungeon > .window-content { - +/* +Enabling scrollbar customization on a per-sheet basis, with a relatively low +specificity to allow easier overriding without artificially increasing it. +*/ +.dotdungeon:not(.style-v3) { + --scrollbar-width: 5px; + --scrollbar-handle-color: #782e22; + --scrollbar-handle-border-color: var(--color-border-highlight); + --color-checkbox-checked: inherit; ::-webkit-scrollbar { - width: 10px; + width: var(--scrollbar-width); } ::-webkit-scrollbar-thumb { + background: var(--scrollbar-handle-color); + border-color: var(--scrollbar-handle-border-color); border-radius: 5px; } +} + +// Reset the parts of Foundry's styling which gets in the way of what I want +.dotdungeon:not(.style-v3) > .window-content { h2, h3, h4, h5, h6 { @include fvtt_reset; + color: inherit; font-family: $title-font; margin: 0; } @@ -37,8 +50,8 @@ input[type="text"], input[type="number"], textarea { + @include fvtt_reset; padding: 5px 7px; - @include input-generic; } textarea { @@ -52,10 +65,16 @@ cursor: default; } } + + hr { + border-color: black; + opacity: 25%; + width: 100%; + } } // Styling that doesn't belong to any particular part of my sheet -.dotdungeon.dotdungeon.dotdungeon.dotdungeon { +.dotdungeon.dotdungeon.dotdungeon.dotdungeon:not(.style-v3) { container-type: size; > .window-content { diff --git a/styles/global/buttons.scss b/styles/global/buttons.scss index 04e809e..77531b3 100644 --- a/styles/global/buttons.scss +++ b/styles/global/buttons.scss @@ -1,6 +1,8 @@ @use "../vars.scss" as *; -.dotdungeon.dotdungeon.dotdungeon.dotdungeon > .window-content { +@use "sass:color" as color; + +.dotdungeon.dotdungeon:not(.style-v3) > .window-content { button { border-radius: 4px; box-sizing: border-box; @@ -8,7 +10,10 @@ border-color: transparent; border-width: 2px; border-radius: 4px; - transition: 400ms; + transition: + background-color 400ms ease-in-out, + color 400ms ease-in-out, + border-color 400ms ease-in-out; padding: 4px 8px; display: inline-flex; justify-content: center; @@ -18,31 +23,52 @@ &.confirm { background: $colour-confirm; color: $text-on-confirm; - &:hover, &:focus-visible { + + &:hover:not(:disabled), + &:focus-visible { background: transparent; color: $colour-confirm; border-color: $colour-confirm; } + + &:disabled { + background: color.adjust($colour-confirm, $lightness: -10%); + color: color.adjust($text-on-confirm, $lightness: -15%); + } } &.neutral { background: $colour-neutral; color: $text-on-neutral; - &:hover, &:focus-visible { + + &:hover:not(:disabled), + &:focus-visible { background: transparent; color: $colour-neutral; border-color: $colour-neutral; } + + &:disabled { + background: color.adjust($colour-neutral, $lightness: -10%); + color: color.adjust($text-on-neutral, $lightness: -15%); + } } &.danger { background: $colour-danger; color: $text-on-danger; - &:hover, &:focus-visible { + + &:hover:not(:disabled), + &:focus-visible { background: transparent; color: $colour-danger; border-color: $colour-danger; } + + &:disabled { + background: color.adjust($colour-danger, $lightness: -10%); + color: color.adjust($text-on-danger, $lightness: -15%); + } } &.reduced-padding { diff --git a/styles/global/design-v2.scss b/styles/global/design-v2.scss new file mode 100644 index 0000000..5c22fe1 --- /dev/null +++ b/styles/global/design-v2.scss @@ -0,0 +1,64 @@ +.item--custom:not(.style-v3), +.actor--mob:not(.style-v3) { + input { + border: 2px solid black; + background: none; + box-shadow: none; + height: unset; + + &.left { text-align: left; } + &.center { text-align: center; } + &.right { text-align: right; } + + &:focus { + transform: scale(102%); + } + } + + label.masked-input.masked-input { + display: flex; + flex-direction: row; + align-items: center; + transition: all ease-in-out 50ms; + border: 2px solid black; + border-radius: 4px; + gap: 4px; + padding: 4px; + + &:focus-within { + transform: scale(102%); + } + + > input { + border: none; + background: none; + border-radius: 0; + border: none; + &:focus, &:focus-visible { + box-shadow: none; + } + } + } + + label.masked-textarea.masked-textarea { + display: flex; + flex-direction: column; + transition: all ease-in-out 50ms; + border: 2px solid black; + border-radius: 4px; + gap: 4px; + padding: 4px; + + &:focus-within { + transform: scale(102%); + } + + textarea { + flex-grow: 1; + resize: none; + border: none; + box-shadow: none; + background: none; + } + } +} \ No newline at end of file diff --git a/styles/global/icons.scss b/styles/global/icons.scss index 500ebca..3d62ada 100644 --- a/styles/global/icons.scss +++ b/styles/global/icons.scss @@ -1,6 +1,6 @@ $iconSizes: 12, 14, 16, 18, 20, 22, 24; -.dotdungeon.dotdungeon.dotdungeon.dotdungeon { +.dotdungeon.dotdungeon.dotdungeon.dotdungeon:not(.style-v3) { .icon { display: inline-flex; justify-content: center; @@ -14,4 +14,4 @@ $iconSizes: 12, 14, 16, 18, 20, 22, 24; }; } } -} \ No newline at end of file +} diff --git a/styles/mixins/_material.scss b/styles/mixins/_material.scss new file mode 100644 index 0000000..2eb9bec --- /dev/null +++ b/styles/mixins/_material.scss @@ -0,0 +1,6 @@ +@mixin elevate($height) { + background-color: var(--elevation-#{$height}dp-bg); + -webkit-box-shadow: 0px 0px #{$height * 1.75}px 0px rgba(0,0,0,0.75); + -moz-box-shadow: 0px 0px #{$height * 1.75}px 0px rgba(0,0,0,0.75); + box-shadow: 0px 0px #{$height * 1.75}px 0px rgba(0,0,0,0.75); +} \ No newline at end of file diff --git a/styles/mixins/_partials.scss b/styles/mixins/_partials.scss index e5d897b..e006802 100644 --- a/styles/mixins/_partials.scss +++ b/styles/mixins/_partials.scss @@ -1,15 +1,26 @@ @use "../vars" as *; +@use "./material" as material; @mixin input-generic { border-width: 2px; border-radius: 4px; border-style: solid; - border-color: rgba(0,0,0, 0.4); - background-color: rgba(0,0,0, 0.1); + border-color: black; font-family: $input-font; + box-shadow: none; &:focus, &:active { - border-color: rgba(0,0,0, 1); + transform: scale(103%); } -} \ No newline at end of file +} + +@mixin sub-nav($parent, $group, $display) { + nav.#{$group} { + @include material.elevate(1); + display: none !important; + } + &:has(nav.#{$parent} .active[data-tab="#{$group}"]) nav.#{$group} { + display: #{$display} !important; + } +} diff --git a/styles/root.scss b/styles/root.scss index 9d91d69..8ff6cdf 100644 --- a/styles/root.scss +++ b/styles/root.scss @@ -6,14 +6,23 @@ @use "./global/icons.scss"; @use "./global/buttons.scss"; +@use "./global/design-v2.scss"; + +@use "./dialog/DiceList.scss"; @use "./sheets/partials/stat.scss"; @use "./sheets/partials/skill.scss"; @use "./sheets/partials/panel.scss"; @use "./sheets/actor/mvp.scss"; -@use "./sheets/actor/mob.scss"; +@use "./sheets/actor/char-sheet/v2/v2.scss"; +@use "./sheets/actor/mob/mob.scss"; @use "./sheets/actor/sync/basic.scss"; +@use "./sheets/items/custom.scss"; @use "./sheets/items/aspect.scss"; @use "./sheets/items/spell.scss"; -@use "./sheets/items/pet.scss"; \ No newline at end of file +@use "./sheets/items/pet.scss"; + +/* NEW BETTER SCOPED CSS ONLY BELOW HERE */ + +@use "./v3/index.scss"; \ No newline at end of file diff --git a/styles/sheets/actor/char-sheet/v2/material/inputs.scss b/styles/sheets/actor/char-sheet/v2/material/inputs.scss new file mode 100644 index 0000000..5726307 --- /dev/null +++ b/styles/sheets/actor/char-sheet/v2/material/inputs.scss @@ -0,0 +1,15 @@ +.dotdungeon.material { + button { + padding: 8px 16px; + + &.outline { + border-style: solid; + border-color: var(--outline-button-border); + } + + &:disabled { + opacity: 37%; + cursor: default; + } + } +} \ No newline at end of file diff --git a/styles/sheets/actor/char-sheet/v2/pages/inventory.scss b/styles/sheets/actor/char-sheet/v2/pages/inventory.scss new file mode 100644 index 0000000..57626fd --- /dev/null +++ b/styles/sheets/actor/char-sheet/v2/pages/inventory.scss @@ -0,0 +1,315 @@ +@use "../../../../../mixins/material" as material; + +.dotdungeon .actor--pc-v2 .active.inventory-page { + padding-bottom: 50px; + height: 100%; +} + +.dotdungeon .actor--pc-v2 .inventory-page .active.tab[data-tab="player"] { + display: grid; + gap: 16px; + grid-template-columns: 1fr 2fr; + grid-template-rows: 1fr repeat(4, min-content); + height: 100%; + color: white; + + .item-list { + grid-row: 1 / -1; + grid-column: 2; + display: flex; + flex-direction: column; + gap: 16px; + + &__group { + @include material.elevate(1); + padding: 8px; + border-radius: 4px; + } + } + + .item-group { + &__header { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center + } + + &__create-item { + @include material.elevate(2); + color: var(--inventory-create-item-font-color); + &:hover { + @include material.elevate(4); + } + &:focus-visible, &:active { + @include material.elevate(8); + } + } + + &__list { + &--material { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 8px; + } + + &--untyped, &--aspect, &--foil, &--weapon, &--pet { + display: flex; + gap: 8px; + flex-direction: column; + } + } + } + + .panel { + padding: 8px; + border-radius: 4px; + } + + .bytes-panel { + display: grid; + grid-template-columns: 1fr auto; + gap: 8px; + align-items: center; + + label { + justify-self: left; + } + + input { + @include material.elevate(3); + border: none; + border-radius: 4px; + text-align: center; + color: white; + + &:hover { + @include material.elevate(6); + background: hsl( from currentColor / 50% ); // probably gonna need color-mix + } + + &:focus-visible, &:active { + border-width: 2px; + border-color: currentColor; + background: var(--elevation-8dp-bg); + } + + } + } + + .capacity-panel { + display: grid; + grid-template-columns: 1fr 30px min-content 30px; + + .used, .total { + text-align: center; + } + } + + .filter-panel { + display: grid; + grid-template-columns: 1fr 1fr; + grid-template-rows: repeat(6, 1fr); + gap: 8px; + + label { + display: flex; + align-items: center; + gap: 4px; + + &:nth-of-type(2n) { + flex-direction: row-reverse; + justify-content: right; + } + } + + input[type="checkbox"] { + margin: 0; + } + } + + .collapse { + &__header { + display: flex; + gap: 8px; + align-items: center; + justify-content: space-between; + + &:hover { + cursor: pointer; + } + + } + &__toggle { + color: var(--inventory-material-color); + transition: transform 200ms ease-in-out; + &:focus-visible { + box-shadow: 0 0 10px var(--color-shadow-highlight); + } + } + + &__content { display: none; visibility: hidden; } + &[open] .collapse{ + &__toggle { + transform: rotate(90deg); + } + &__content { + display: unset; + visibility: visible; + } + } + } + + .material { + @include material.elevate(1); + padding: 8px; + gap: 8px; + display: flex; + justify-content: space-between; + align-items: center; + border-radius: 4px; + + &__label { + font-family: var(--inventory-item-name-font); + font-size: var(--inventory-item-name-font-size); + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + justify-self: left; + max-width: 100%; + } + + &__button { + --size: 28px; + @include material.elevate(2); + transition: all 400ms ease-in-out; + color: var(--inventory-material-color); + border-width: 0; + width: var(--size); + height: var(--size); + + &:hover { + @include material.elevate(4); + color: var(--inventory-material-hover-color); + } + + &:focus-visible { + box-shadow: 0 0 10px var(--color-shadow-highlight); + } + } + + &__input { + @include material.elevate(2); + font-family: var(--input-font); + text-align: center; + border-radius: 4px; + transition: all 400ms ease-in-out; + color: var(--inventory-material-color); + + &:hover { + @include material.elevate(4); + color: var(--inventory-material-hover-color); + } + + &:focus { + @include material.elevate(8); + color: var(--inventory-material-focus-color); + } + } + } + + .untyped, .aspect, .weapon, .pet { + @include material.elevate(1); + padding: 8px; + border-radius: 4px; + color: var(--inventory-material-color); + + &__name { + flex-grow: 1; + cursor: inherit; + font-family: var(--inventory-item-name-font); + font-size: var(--inventory-item-name-font-size); + } + + &__content { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)) 2.25rem; + grid-template-rows: repeat(3, 2.25rem) auto; + gap: 8px; + } + + &__button { + @include material.elevate(2); + padding: 0px; + + &:hover { + @include material.elevate(4); + } + &:focus-visible { + box-shadow: 0 0 10px var(--color-shadow-highlight); + } + + &--send-to-chat { + @extend .untyped__button; + color: var(--inventory-untyped-send-to-chat-color); + } + &--edit { + @extend .untyped__button; + color: var(--inventory-untyped-edit-color); + } + &--delete { + @extend .untyped__button; + color: var(--inventory-untyped-delete-color); + background: var(--inventory-untyped-delete-bg); + &:hover { + background: var(--inventory-untyped-delete-bg-hover); + } + } + } + + &__field { + @include material.elevate(1); + color: var(--inventory-untyped-card-color); + padding: 8px; + display: flex; + align-items: center; + justify-content: space-between; + border-radius: 4px; + + > :first-child { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + + &.editable { + input[type="text"], input[type="number"] { + @include material.elevate(1); + font-family: var(--input-font); + font-size: var(--inventory-untyped-quantity-font-size); + width: 50px; + text-align: center; + border-radius: 4px; + padding: 5px 7px; + color: inherit; + } + } + } + + &__description { + @include material.elevate(1); + color: var(--inventory-untyped-description-color); + margin: 0; + grid-row: span 3; + grid-column: span 3; + padding: 8px; + border-radius: 4px; + } + } + + .weapon { + &__description { + grid-row: span 2; + } + } +} diff --git a/styles/sheets/actor/char-sheet/v2/pages/stats.scss b/styles/sheets/actor/char-sheet/v2/pages/stats.scss new file mode 100644 index 0000000..a4f9dee --- /dev/null +++ b/styles/sheets/actor/char-sheet/v2/pages/stats.scss @@ -0,0 +1,79 @@ +.dotdungeon .actor--pc-v2 .active.stats-page { + display: grid; + gap: 16px; + grid-template-columns: 1fr 1fr; + grid-template-rows: auto auto auto; + + .stat { + border-radius: 8px; + display: flex; + flex-direction: column; + + select { + height: 100%; + outline: none; + border: none; + text-align: center; + } + + &__header { + padding: 8px; + display: flex; + align-items: center; + flex-direction: row; + color: var(--stat-divider-text-color); + gap: 8px; + > h2 { + flex-grow: 1; + color: var(--stat-header-text-color); + } + .dice-select { + color: var(--stat-dice-select-text-color); + option { + background: var(--stat-dice-select-bg); + } + } + .roll-stat { + color: var(--stat-roll-button-text-color); + } + + &:not(:only-child) { + border-bottom: 1px solid var(--stat-divider-color); + } + } + + &__empty { + display: flex; + justify-content: center; + align-items: center; + flex-grow: 1; + } + + &__skills { + align-items: center; + box-sizing: border-box; + display: grid; + gap: 8px; + grid-template-columns: repeat(3, 1fr); + padding: 8px; + + .skill { + &__label { + text-align: end; + justify-self: right; + color: var(--skill-name-text-color); + } + &__training { + color: var(--skill-training-select-text-color); + option { + background: var(--skill-training-select-bg); + } + } + &__roll { + margin-right: 25%; + color: var(--skill-roll-button-text-color); + } + } + } + } +} \ No newline at end of file diff --git a/styles/sheets/actor/char-sheet/v2/themes/dark.scss b/styles/sheets/actor/char-sheet/v2/themes/dark.scss new file mode 100644 index 0000000..f32ffce --- /dev/null +++ b/styles/sheets/actor/char-sheet/v2/themes/dark.scss @@ -0,0 +1,78 @@ +@use "sass:color"; + +$t: transparent; + +$background: #0a0a0a; +$surface: #121212; +$primary: #005300; +$secondary: #6c056c; +$danger: red; +$on-background: $t; +$on-surface: white; +$on-primary: $t; +$on-secondary: $t; +$on-danger: black; + +$title-font: 'Pixelify Sans', sans-serif; +$body-font: sans-serif; + +.actor--pc-v2 { + --sheet-bg: #{$background}; + --nav-bg: #{$surface}; + --nav-button-text: #{$on-surface}; + + --tab-font: #{$title-font}; + --input-font: #{$body-font}; + + // Foundry overrides + --scrollbar-handle-color: #{$primary}; + --scrollbar-handle-border-color: color-mix(in lab, white 40%, var(--scrollbar-handle-color)); + --color-checkbox-checked: #{color.adjust($primary, $lightness: 25%)}; + + /* Elevation backgrounds to following Material design */ + --elevation-0dp-bg: #{$surface}; + --elevation-1dp-bg: color-mix(in lab, transparent, white 5%); + --elevation-2dp-bg: color-mix(in lab, transparent, white 7%); + --elevation-3dp-bg: color-mix(in lab, transparent, white 8%); + --elevation-4dp-bg: color-mix(in lab, transparent, white 9%); + --elevation-6dp-bg: color-mix(in lab, transparent, white 11%); + --elevation-8dp-bg: color-mix(in lab, transparent, white 12%); + --elevation-12dp-bg: color-mix(in lab, transparent, white 14%); + --elevation-16dp-bg: color-mix(in lab, transparent, white 15%); + --elevation-24dp-bg: color-mix(in lab, transparent, white 16%); + + --stat-header-text-color: #{$on-surface}; + --stat-dice-select-text-color: #{$on-surface}; + --stat-dice-select-bg: #{$surface}; + --stat-roll-button-text-color: #{$on-surface}; + --stat-divider-color: #{$secondary}; + --stat-not-chosen-placeholder-text-color: #{$on-surface}; + --skill-name-text-color: #{$on-surface}; + --skill-training-select-bg: #{$surface}; + --skill-training-select-text-color: #{$on-surface}; + --skill-roll-button-text-color: #{$on-surface}; + + /* General variables for inventory tab */ + --inventory-create-item-font-color: white; + + /* Common Inventory Item Variables */ + --inventory-item-name-font: #{$body-font}; + --inventory-item-name-font-size: 0.875rem; + + /* Material inventory item details */ + --inventory-material-color: white; + --inventory-material-hover-color: white; + --inventory-material-focus-color: white; + + /* Untyped (custom) inventory item details */ + --inventory-untyped-description-color: white; + --inventory-untyped-card-color: white; + --inventory-untyped-send-to-chat-color: white; + --inventory-untyped-edit-color: white; + --inventory-untyped-delete-color: white; + --inventory-untyped-delete-bg: color-mix(in lab, #{$danger} 30%, #{$surface}); + --inventory-untyped-delete-bg-hover: color-mix(in lab, #{$danger} 37%, #{$surface});; + --inventory-untyped-quantity-font-size: 14px; + --inventory-untyped-quantity-color: white; + --inventory-untyped-quantity-input-color: white; +} diff --git a/styles/sheets/actor/char-sheet/v2/themes/material-design.scss b/styles/sheets/actor/char-sheet/v2/themes/material-design.scss new file mode 100644 index 0000000..aad072a --- /dev/null +++ b/styles/sheets/actor/char-sheet/v2/themes/material-design.scss @@ -0,0 +1,167 @@ +:root { + --md-source: #005500; + /* primary */ + --md-ref-palette-primary0: #000000; + --md-ref-palette-primary10: #002200; + --md-ref-palette-primary20: #003a00; + --md-ref-palette-primary25: #004600; + --md-ref-palette-primary30: #005300; + --md-ref-palette-primary35: #12600b; + --md-ref-palette-primary40: #226d19; + --md-ref-palette-primary50: #3d8730; + --md-ref-palette-primary60: #56a248; + --md-ref-palette-primary70: #70bd5f; + --md-ref-palette-primary80: #8bd978; + --md-ref-palette-primary90: #a6f691; + --md-ref-palette-primary95: #caffb9; + --md-ref-palette-primary98: #edffe1; + --md-ref-palette-primary99: #f7ffee; + --md-ref-palette-primary100: #ffffff; + /* secondary */ + --md-ref-palette-secondary0: #000000; + --md-ref-palette-secondary10: #380038; + --md-ref-palette-secondary20: #5b005b; + --md-ref-palette-secondary25: #6c056c; + --md-ref-palette-secondary30: #7a1979; + --md-ref-palette-secondary35: #892886; + --md-ref-palette-secondary40: #973693; + --md-ref-palette-secondary50: #b450ae; + --md-ref-palette-secondary60: #d26ac9; + --md-ref-palette-secondary70: #f084e6; + --md-ref-palette-secondary80: #ffaaf3; + --md-ref-palette-secondary90: #ffd7f5; + --md-ref-palette-secondary95: #ffebf8; + --md-ref-palette-secondary98: #fff7f9; + --md-ref-palette-secondary99: #fffbff; + --md-ref-palette-secondary100: #ffffff; + /* tertiary */ + --md-ref-palette-tertiary0: #000000; + --md-ref-palette-tertiary10: #002022; + --md-ref-palette-tertiary20: #003739; + --md-ref-palette-tertiary25: #104245; + --md-ref-palette-tertiary30: #1e4d50; + --md-ref-palette-tertiary35: #2b595c; + --md-ref-palette-tertiary40: #386568; + --md-ref-palette-tertiary50: #517f82; + --md-ref-palette-tertiary60: #6b989c; + --md-ref-palette-tertiary70: #85b3b6; + --md-ref-palette-tertiary80: #a0cfd2; + --md-ref-palette-tertiary90: #bcebee; + --md-ref-palette-tertiary95: #caf9fd; + --md-ref-palette-tertiary98: #e7feff; + --md-ref-palette-tertiary99: #f3ffff; + --md-ref-palette-tertiary100: #ffffff; + /* neutral */ + --md-ref-palette-neutral0: #000000; + --md-ref-palette-neutral10: #1a1c18; + --md-ref-palette-neutral20: #2f312d; + --md-ref-palette-neutral25: #3a3c38; + --md-ref-palette-neutral30: #454743; + --md-ref-palette-neutral35: #51534e; + --md-ref-palette-neutral40: #5d5f5a; + --md-ref-palette-neutral50: #767872; + --md-ref-palette-neutral60: #90918c; + --md-ref-palette-neutral70: #abaca6; + --md-ref-palette-neutral80: #c6c7c1; + --md-ref-palette-neutral90: #e2e3dc; + --md-ref-palette-neutral95: #f1f1eb; + --md-ref-palette-neutral98: #f9faf3; + --md-ref-palette-neutral99: #fcfdf6; + --md-ref-palette-neutral100: #ffffff; + /* neutral-variant */ + --md-ref-palette-neutral-variant0: #000000; + --md-ref-palette-neutral-variant10: #181d15; + --md-ref-palette-neutral-variant20: #2c3229; + --md-ref-palette-neutral-variant25: #373d34; + --md-ref-palette-neutral-variant30: #43483f; + --md-ref-palette-neutral-variant35: #4e544a; + --md-ref-palette-neutral-variant40: #5a6056; + --md-ref-palette-neutral-variant50: #73796e; + --md-ref-palette-neutral-variant60: #8d9387; + --md-ref-palette-neutral-variant70: #a7ada1; + --md-ref-palette-neutral-variant80: #c3c8bc; + --md-ref-palette-neutral-variant90: #dfe4d7; + --md-ref-palette-neutral-variant95: #edf3e5; + --md-ref-palette-neutral-variant98: #f6fbee; + --md-ref-palette-neutral-variant99: #f9fef1; + --md-ref-palette-neutral-variant100: #ffffff; + /* error */ + --md-ref-palette-error0: #000000; + --md-ref-palette-error10: #410002; + --md-ref-palette-error20: #690005; + --md-ref-palette-error25: #7e0007; + --md-ref-palette-error30: #93000a; + --md-ref-palette-error35: #a80710; + --md-ref-palette-error40: #ba1a1a; + --md-ref-palette-error50: #de3730; + --md-ref-palette-error60: #ff5449; + --md-ref-palette-error70: #ff897d; + --md-ref-palette-error80: #ffb4ab; + --md-ref-palette-error90: #ffdad6; + --md-ref-palette-error95: #ffedea; + --md-ref-palette-error98: #fff8f7; + --md-ref-palette-error99: #fffbff; + --md-ref-palette-error100: #ffffff; + /* light */ + --md-sys-color-primary-light: #226d19; + --md-sys-color-on-primary-light: #ffffff; + --md-sys-color-primary-container-light: #a6f691; + --md-sys-color-on-primary-container-light: #002200; + --md-sys-color-secondary-light: #973693; + --md-sys-color-on-secondary-light: #ffffff; + --md-sys-color-secondary-container-light: #ffd7f5; + --md-sys-color-on-secondary-container-light: #380038; + --md-sys-color-tertiary-light: #386568; + --md-sys-color-on-tertiary-light: #ffffff; + --md-sys-color-tertiary-container-light: #bcebee; + --md-sys-color-on-tertiary-container-light: #002022; + --md-sys-color-error-light: #ba1a1a; + --md-sys-color-error-container-light: #ffdad6; + --md-sys-color-on-error-light: #ffffff; + --md-sys-color-on-error-container-light: #410002; + --md-sys-color-background-light: #fcfdf6; + --md-sys-color-on-background-light: #1a1c18; + --md-sys-color-surface-light: #fcfdf6; + --md-sys-color-on-surface-light: #1a1c18; + --md-sys-color-surface-variant-light: #dfe4d7; + --md-sys-color-on-surface-variant-light: #43483f; + --md-sys-color-outline-light: #73796e; + --md-sys-color-inverse-on-surface-light: #f1f1eb; + --md-sys-color-inverse-surface-light: #2f312d; + --md-sys-color-inverse-primary-light: #8bd978; + --md-sys-color-shadow-light: #000000; + --md-sys-color-surface-tint-light: #226d19; + --md-sys-color-outline-variant-light: #c3c8bc; + --md-sys-color-scrim-light: #000000; + /* dark */ + --md-sys-color-primary-dark: #8bd978; + --md-sys-color-on-primary-dark: #003a00; + --md-sys-color-primary-container-dark: #005300; + --md-sys-color-on-primary-container-dark: #a6f691; + --md-sys-color-secondary-dark: #ffaaf3; + --md-sys-color-on-secondary-dark: #5b005b; + --md-sys-color-secondary-container-dark: #7a1979; + --md-sys-color-on-secondary-container-dark: #ffd7f5; + --md-sys-color-tertiary-dark: #a0cfd2; + --md-sys-color-on-tertiary-dark: #003739; + --md-sys-color-tertiary-container-dark: #1e4d50; + --md-sys-color-on-tertiary-container-dark: #bcebee; + --md-sys-color-error-dark: #ffb4ab; + --md-sys-color-error-container-dark: #93000a; + --md-sys-color-on-error-dark: #690005; + --md-sys-color-on-error-container-dark: #ffdad6; + --md-sys-color-background-dark: #1a1c18; + --md-sys-color-on-background-dark: #e2e3dc; + --md-sys-color-surface-dark: #1a1c18; + --md-sys-color-on-surface-dark: #e2e3dc; + --md-sys-color-surface-variant-dark: #43483f; + --md-sys-color-on-surface-variant-dark: #c3c8bc; + --md-sys-color-outline-dark: #8d9387; + --md-sys-color-inverse-on-surface-dark: #1a1c18; + --md-sys-color-inverse-surface-dark: #e2e3dc; + --md-sys-color-inverse-primary-dark: #226d19; + --md-sys-color-shadow-dark: #000000; + --md-sys-color-surface-tint-dark: #8bd978; + --md-sys-color-outline-variant-dark: #43483f; + --md-sys-color-scrim-dark: #000000; +} \ No newline at end of file diff --git a/styles/sheets/actor/char-sheet/v2/v2.scss b/styles/sheets/actor/char-sheet/v2/v2.scss new file mode 100644 index 0000000..4f18741 --- /dev/null +++ b/styles/sheets/actor/char-sheet/v2/v2.scss @@ -0,0 +1,97 @@ +@use "./themes/dark.scss"; +@use "../../../../mixins/material" as material; +@use "../../../../mixins/partials" as partials; + +@use "./pages/stats.scss"; +@use "./pages/inventory.scss"; + +.dotdungeon .actor--pc-v2 { + background-color: var(--sheet-bg); + + .e-0dp { @include material.elevate(0); } + .e-1dp { @include material.elevate(1); } + .e-2dp { @include material.elevate(2); } + .e-3dp { @include material.elevate(3); } + .e-4dp { @include material.elevate(4); } + .e-6dp { @include material.elevate(6); } + .e-8dp { @include material.elevate(8); } + .e-12dp { @include material.elevate(12); } + .e-16dp { @include material.elevate(16); } + .e-24dp { @include material.elevate(24); } + + .scrollable { + container-type: size; + overflow: auto; + scrollbar-gutter: stable; + } + + .nav-bar { + @include material.elevate(8); + background: color-mix(in lab, var(--nav-bg), white 5%); + position: absolute; + bottom: 0; + width: calc(100% - 6px); + + nav { + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 8px; + padding: 8px; + } + + @include partials.sub-nav("page", "inventory", flex); + + button { + @include material.elevate(2); + height: 36px; + box-sizing: border-box; + color: var(--nav-button-text); + + &:focus-visible { + border-style: solid; + border-width: 2px; + border-color: currentColor; + background: var(--elevation-8dp-bg); + } + + &:hover { + @include material.elevate(6); + background: hsl( from currentColor / 50% ); // probably gonna need color-mix + } + } + } + + span.placeholder { + opacity: 60%; + } + + input[type="checkbox"] { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + border-radius: 2px; + margin: 0; + cursor: pointer; + + background: var(--elevation-8dp-bg); + position: relative; + &:checked::before { + content: ""; + background: var(--color-checkbox-checked); + border-radius: 3px; + $margin: 4px; + top: $margin; + bottom: $margin; + left: $margin; + right: $margin; + position: absolute; + } + } + + .page-content { + padding: 16px; + padding-bottom: 69px; + height: 100%; + } +} \ No newline at end of file diff --git a/styles/sheets/actor/mob.scss b/styles/sheets/actor/mob.scss deleted file mode 100644 index 79cd140..0000000 --- a/styles/sheets/actor/mob.scss +++ /dev/null @@ -1,10 +0,0 @@ -@use "../../vars" as *; - -.dotdungeon .actor--mob { - padding: 4px; - - textarea { - width: 100%; - resize: vertical; - } -} \ No newline at end of file diff --git a/styles/sheets/actor/mob/avatar.scss b/styles/sheets/actor/mob/avatar.scss new file mode 100644 index 0000000..9863e1a --- /dev/null +++ b/styles/sheets/actor/mob/avatar.scss @@ -0,0 +1,4 @@ +@mixin avatar { + grid-area: avatar; + background: yellowgreen +} \ No newline at end of file diff --git a/styles/sheets/actor/mob/mob.scss b/styles/sheets/actor/mob/mob.scss new file mode 100644 index 0000000..11634b2 --- /dev/null +++ b/styles/sheets/actor/mob/mob.scss @@ -0,0 +1,81 @@ +@use "../../../vars" as *; +@use "avatar" as *; + +.dotdungeon:not(.style-v3) .actor--mob { + --gap: 8px; + --avatar-size: 100px; + --row-height: calc((var(--avatar-size) - var(--gap)) / 2); + padding: var(--gap); + display: grid; + grid-template-columns: var(--avatar-size) repeat(4, minmax(0, 1fr)); + grid-template-rows: repeat(5, var(--row-height)) minmax(var(--row-height), 1fr); + gap: var(--gap); + grid-template-areas: + "avatar . . . ." + "avatar . . . ." + "dice . . . ." + "dice description description stunts stunts" + "dice description description stunts stunts" + "dice description description stunts stunts"; + + label, .dice { + border: 2px solid black; + border-radius: 4px; + gap: 4px; + padding: 4px; + } + + input.masked { + border: 2px solid black; + background: none; + box-shadow: none; + &:focus { + transform: scale(102%); + } + } + + .wide { + grid-column: span 2; + } + +// Area-specific stylings + + .name { + height: 100%; + font-size: 1.5rem; + input { + height: 100%; + } + } + + .dice { + grid-area: dice; + display: flex; + flex-direction: column; + + .die { + display: flex; + flex-direction: row; + width: 100%; + border: 2px solid black; + border-radius: 4px; + + .formula { + flex-grow: 1; + align-self: center; + } + } + } + + .stunts { + grid-area: stunts; + } + .description { + grid-area: description; + } + + .avatar { + @include avatar; + border-radius: 4px; + } +} \ No newline at end of file diff --git a/styles/sheets/actor/mob/v2.scss b/styles/sheets/actor/mob/v2.scss new file mode 100644 index 0000000..d7f0a5c --- /dev/null +++ b/styles/sheets/actor/mob/v2.scss @@ -0,0 +1,123 @@ +@use "../../../vars" as *; +@use "avatar" as *; + +.dotdungeon:not(.style-v3) .actor--mob2 { + --gap: 8px; + --avatar-size: 100px; + --row-height: calc((var(--avatar-size) - var(--gap)) / 2); + padding: var(--gap); + display: grid; + grid-template-columns: var(--avatar-size) repeat(2, minmax(0, 1fr)); + grid-template-rows: repeat(5, var(--row-height)) minmax(var(--row-height), 1fr); + gap: var(--gap); + grid-template-areas: + "avatar . ." + "avatar . ." + "dice tabs tabs" + "dice tabs tabs" + "dice tabs tabs" + "dice tabs tabs"; + + label, .dice { + border: 2px solid black; + border-radius: 4px; + gap: 4px; + padding: 4px; + } + + input.masked { + border: 2px solid black; + background: none; + box-shadow: none; + &:focus { + transform: scale(102%); + } + } + + label.mask-input { + display: flex; + flex-direction: row; + align-items: center; + transition: all ease-in-out 50ms; + + &:focus-within { + transform: scale(102%); + } + + input { + text-align: right; + border: none; + background: none; + border-radius: 0; + border-bottom: 1px solid black; + &:focus, &:focus-visible { + box-shadow: none; + } + } + + input.left { + text-align: left; + } + } + + .wide { + grid-column: span 2; + } + + .mask-textarea { + display: flex; + flex-direction: column; + transition: all ease-in-out 50ms; + + &:focus-within { + transform: scale(102%); + } + + textarea { + flex-grow: 1; + resize: none; + border: none; + box-shadow: none; + background: none; + } + } + .name { + height: 100%; + font-size: 1.5rem; + input { + height: 100%; + } + } + + .dice { + grid-area: dice; + display: flex; + flex-direction: column; + + .die { + display: flex; + flex-direction: row; + width: 100%; + border: 2px solid black; + border-radius: 4px; + + .formula { + flex-grow: 1; + align-self: center; + } + } + } + + .tabs { grid-area: tabs; } + .stunts { + grid-area: stunts; + } + .description { + grid-area: description; + } + + .avatar { + @include avatar; + border-radius: 4px; + } +} \ No newline at end of file diff --git a/styles/sheets/actor/mvp.scss b/styles/sheets/actor/mvp.scss index ba9d43b..4193a24 100644 --- a/styles/sheets/actor/mvp.scss +++ b/styles/sheets/actor/mvp.scss @@ -1,7 +1,7 @@ @use "../../vars.scss" as *; @use "../../mixins/breakpoints" as *; -.dotdungeon .actor--pc { +.dotdungeon:not(.style-v3) .actor--pc-mvp { display: grid; grid-template-areas: "profile stats stats" @@ -79,6 +79,12 @@ flex-direction: row; align-items: center; } + .grid-row { + display: grid; + grid-template-columns: 1fr min-content 25% min-content; + gap: 4px; + align-items: center; + } .col { display: flex; flex-direction: column; @@ -95,11 +101,15 @@ .bytes-input, .supplies-count, - .materials-count { - width: 25%; + .materials-count, + .item-quantity { text-align: center; } + .item-quantity { + width: 25% + } + textarea { resize: vertical; } @@ -260,8 +270,8 @@ @include bp-m { - .dotdungeon { - .actor--pc { + .dotdungeon:not(.style-v3) { + .actor--pc-mvp { grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-rows: repeat(15, min-content); grid-template-areas: @@ -300,8 +310,8 @@ } @include bp-s { - .dotdungeon { - .actor--pc { + .dotdungeon:not(.style-v3) { + .actor--pc-mvp { grid-template-columns: 1fr; grid-template-rows: repeat(12, min-content); grid-template-areas: diff --git a/styles/sheets/actor/sync/basic.scss b/styles/sheets/actor/sync/basic.scss index 5914f07..904d093 100644 --- a/styles/sheets/actor/sync/basic.scss +++ b/styles/sheets/actor/sync/basic.scss @@ -1,6 +1,6 @@ @use "../../../mixins/breakpoints" as *; -.dotdungeon { +:not(.style-v3).dotdungeon { .actor--basic-sync { display: flex; justify-content: center; @@ -23,6 +23,22 @@ width: 60%; text-align: center; } + + .rest-container { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 4px; + + .button-row { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 4px; + align-items: center; + text-align: center; + } + } } &--sync-sheet { @@ -36,7 +52,7 @@ } @include bp-xs { - .dotdungeon { + :not(.style-v3).dotdungeon { &--sync-sheet { header { .configure-sheet { diff --git a/styles/sheets/items/aspect.scss b/styles/sheets/items/aspect.scss index 2461b9b..8f355fe 100644 --- a/styles/sheets/items/aspect.scss +++ b/styles/sheets/items/aspect.scss @@ -1,6 +1,6 @@ @use "../../vars" as *; -.dotdungeon .item--aspect { +.dotdungeon:not(.style-v3) .item--aspect { padding: 4px; input[type=text] { diff --git a/styles/sheets/items/custom.scss b/styles/sheets/items/custom.scss new file mode 100644 index 0000000..9c5bc96 --- /dev/null +++ b/styles/sheets/items/custom.scss @@ -0,0 +1,16 @@ +.item--custom:not(.style-v3) { + padding: 8px; + display: grid; + grid-template-columns: 1fr 1fr; + grid-template-rows: repeat(3, min-content) 1fr; + grid-template-areas: + "name name" + ". ." + "usage usage" + "description description"; + gap: 8px; + + .name { grid-area: name; } + .usage { grid-area: usage; white-space: nowrap; } + .description { grid-area: description; } +} \ No newline at end of file diff --git a/styles/sheets/items/pet.scss b/styles/sheets/items/pet.scss index 8cfc8e5..10e4d51 100644 --- a/styles/sheets/items/pet.scss +++ b/styles/sheets/items/pet.scss @@ -1,6 +1,6 @@ @use "../../vars" as *; -.dotdungeon .item--pet { +.dotdungeon:not(.style-v3) .item--pet { padding: 4px; input[type=text] { diff --git a/styles/sheets/items/spell.scss b/styles/sheets/items/spell.scss index 21ba81b..8d4405f 100644 --- a/styles/sheets/items/spell.scss +++ b/styles/sheets/items/spell.scss @@ -1,6 +1,6 @@ @use "../../vars" as *; -.dotdungeon .item--spell { +.dotdungeon:not(.style-v3) .item--spell { padding: 4px; input[type=text] { diff --git a/styles/sheets/partials/panel.scss b/styles/sheets/partials/panel.scss index 7d0b910..2ebf74c 100644 --- a/styles/sheets/partials/panel.scss +++ b/styles/sheets/partials/panel.scss @@ -2,7 +2,7 @@ @use "../../mixins/foundry" as *; @use "../../vars" as *; -.dotdungeon .panel { +.dotdungeon:not(.style-v3) .actor--pc-mvp .panel { display: grid; grid-template-rows: min-content 1fr; @@ -39,7 +39,7 @@ } @include bp-s { - .dotdungeon .panel__header .icon { + .dotdungeon:not(.style-v3) .panel__header .icon { display: none; visibility: hidden; } diff --git a/styles/sheets/partials/skill.scss b/styles/sheets/partials/skill.scss index 40b43de..6034e86 100644 --- a/styles/sheets/partials/skill.scss +++ b/styles/sheets/partials/skill.scss @@ -1,4 +1,4 @@ -.dotdungeon .skill { +.dotdungeon:not(.style-v3) .skill { display: flex; flex-direction: row; justify-content: center; diff --git a/styles/sheets/partials/stat.scss b/styles/sheets/partials/stat.scss index 1875cf3..0cfbe76 100644 --- a/styles/sheets/partials/stat.scss +++ b/styles/sheets/partials/stat.scss @@ -1,4 +1,4 @@ -.dotdungeon .stat { +.dotdungeon:not(.style-v3) .actor--pc-mvp .stat { display: flex; flex-direction: column; align-items: center; diff --git a/styles/v3/components/common.scss b/styles/v3/components/common.scss new file mode 100644 index 0000000..59f812d --- /dev/null +++ b/styles/v3/components/common.scss @@ -0,0 +1,7 @@ +// Disclaimer: This CSS is used by a custom web component and is scoped to JUST +// the corresponding web component. This should only be imported by web component +// style files. + +:host { + display: inline-block; +} diff --git a/styles/v3/components/icon.scss b/styles/v3/components/icon.scss new file mode 100644 index 0000000..012593f --- /dev/null +++ b/styles/v3/components/icon.scss @@ -0,0 +1,23 @@ +/* +Disclaimer: This CSS is used by a custom web component and is scoped to JUST +the corresponding web component. Importing this into other files is forbidden +*/ + +$default-size: 1rem; + +@use "./common.scss"; + +div { + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; +} + +svg { + width: var(--size, $default-size); + height: var(--size, $default-size); + fill: var(--fill); + stroke: var(--stroke); +} diff --git a/styles/v3/components/incrementer.scss b/styles/v3/components/incrementer.scss new file mode 100644 index 0000000..abe9478 --- /dev/null +++ b/styles/v3/components/incrementer.scss @@ -0,0 +1,63 @@ +/* +Disclaimer: This CSS is used by a custom web component and is scoped to JUST +the corresponding web component. Importing this into other files is forbidden +*/ + +$default-border-radius: 4px; +$default-height: 1.5rem; + +@use "../mixins/material"; +@use "./common.scss"; + +div { + display: grid; + grid-template-columns: var(--height, $default-height) var(--width, 50px) var(--height, $default-height); + grid-template-rows: var(--height, 1fr); + border-radius: var(--border-radius, $default-border-radius); + @include material.elevate(2); + + &:hover { + @include material.elevate(4); + } + + &:focus-within { + @include material.elevate(6); + } +} + +span, input { + border: none; + outline: none; + background: none; + color: inherit; +} + +input { + font-family: var(--font-family, inherit); + text-align: center; + font-size: var(--font-size, inherit); + padding: 2px 4px; + + &::-webkit-inner-spin-button, &::-webkit-outer-spin-button { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + margin: 0 + } +} + +.increment, .decrement { + aspect-ratio: 1 / 1; + padding: 0; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; +} + +.increment { + border-radius: 0 var(--border-radius, $default-border-radius) var(--border-radius, 4px) 0; +} +.decrement { + border-radius: var(--border-radius, $default-border-radius) 0 0 var(--border-radius, $default-border-radius); +} diff --git a/styles/v3/elements/button.scss b/styles/v3/elements/button.scss new file mode 100644 index 0000000..33ad097 --- /dev/null +++ b/styles/v3/elements/button.scss @@ -0,0 +1,46 @@ +@use "../mixins/material" as material; + +.dotdungeon.style-v3 > .window-content { + button, a.button { + @include material.elevate(2); + align-items: center; + border-radius: 4px; + border: none; + box-sizing: border-box; + color: inherit; + cursor: pointer; + display: inline-flex; + font-family: sans-serif; + gap: 4px; + justify-content: center; + margin: 0; + outline: none; + padding: 4px 8px; + transition: all 200ms ease-in-out; + width: initial; + + &:hover, &:focus-visible { + @include material.elevate(4); + text-shadow: none; + } + &:active { + @include material.elevate(6); + } + + &:disabled { + opacity: 50%; + cursor: default; + } + + /* Icon buttons don't use Material styling */ + &.icon { + @include material.undo; + padding: 4px; + + &:focus-visible { + @include material.undo; + // TODO : Accessible focus state + } + } + } +} diff --git a/styles/v3/elements/checkbox.scss b/styles/v3/elements/checkbox.scss new file mode 100644 index 0000000..d3bae5f --- /dev/null +++ b/styles/v3/elements/checkbox.scss @@ -0,0 +1,24 @@ +.dotdungeon.style-v3 { + input[type="checkbox"] { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + border-radius: 2px; + margin: 0; + cursor: pointer; + + background: var(--elevation-8dp-bg); + position: relative; + &:checked::before { + content: ""; + background: var(--checkbox-checked); + border-radius: 3px; + $margin: 4px; + top: $margin; + bottom: $margin; + left: $margin; + right: $margin; + position: absolute; + } + } +} diff --git a/styles/v3/elements/headers.scss b/styles/v3/elements/headers.scss new file mode 100644 index 0000000..0174901 --- /dev/null +++ b/styles/v3/elements/headers.scss @@ -0,0 +1,10 @@ +.dotdungeon.style-v3 > .window-content { + h1, h2, h3, h4, h5, h6 { + border: none; + font-size: 1rem; + margin: 0; + } + h1, .h1 { + font-size: 1.5rem; + } +} diff --git a/styles/v3/elements/hr.scss b/styles/v3/elements/hr.scss new file mode 100644 index 0000000..3f23a68 --- /dev/null +++ b/styles/v3/elements/hr.scss @@ -0,0 +1,6 @@ +.dotdungeon.style-v3 > .window-content hr { + border-color: black; + opacity: 25%; + width: 100%; + grid-column: 1 / -1; +} diff --git a/styles/v3/elements/icons.scss b/styles/v3/elements/icons.scss new file mode 100644 index 0000000..0a44997 --- /dev/null +++ b/styles/v3/elements/icons.scss @@ -0,0 +1,17 @@ +$iconSizes: 12, 14, 16, 18, 20, 22, 24; + +.dotdungeon.style-v3 > .window-content { + .icon { + display: inline-flex; + justify-content: center; + align-items: center; + + // The various icon sizes + @each $size in $iconSizes { + &--#{$size} { + height: #{$size}px; + width: #{$size}px; + }; + } + } +} diff --git a/styles/v3/elements/label.scss b/styles/v3/elements/label.scss new file mode 100644 index 0000000..12e3afb --- /dev/null +++ b/styles/v3/elements/label.scss @@ -0,0 +1,9 @@ +@use "../mixins/material"; + +.dotdungeon.style-v3 > .window-content label { + cursor: pointer; + + &.justify-start { + justify-self: start; + } +} diff --git a/styles/v3/elements/nav.scss b/styles/v3/elements/nav.scss new file mode 100644 index 0000000..6046e41 --- /dev/null +++ b/styles/v3/elements/nav.scss @@ -0,0 +1,11 @@ +@use "../mixins/material" as material; + +.dotdungeon.style-v3 > .window-content { + nav:not(#context-menu) { + display: flex; + flex-direction: row; + overflow-x: auto; + gap: 8px; + padding: 8px; + } +} diff --git a/styles/v3/elements/number-input.scss b/styles/v3/elements/number-input.scss new file mode 100644 index 0000000..ad9ce74 --- /dev/null +++ b/styles/v3/elements/number-input.scss @@ -0,0 +1,20 @@ +@use "../mixins/material"; + +.dotdungeon.style-v3 > .window-content input[type=number] { + outline: none; + border: none; + @include material.elevate(3); + padding: 4px 8px; + color: white; + transition: all 200ms ease-in-out; + text-align: center; + line-height: 1rem; + + &:hover { + @include material.elevate(4); + } + + &:focus-visible { + @include material.elevate(6); + } +} diff --git a/styles/v3/elements/panel.scss b/styles/v3/elements/panel.scss new file mode 100644 index 0000000..de7cf9f --- /dev/null +++ b/styles/v3/elements/panel.scss @@ -0,0 +1,21 @@ +@use "../mixins/material" as material; + +.dotdungeon.style-v3 > .window-content { + .panel { + @include material.elevate(2); + border-radius: 4px; + padding: var(--gap); + + &--row { + @extend .panel; + display: flex; + flex-direction: row; + gap: var(--gap); + align-items: center; + + &.space-between { + justify-content: space-between; + } + } + } +} diff --git a/styles/v3/elements/select.scss b/styles/v3/elements/select.scss new file mode 100644 index 0000000..55125ac --- /dev/null +++ b/styles/v3/elements/select.scss @@ -0,0 +1,26 @@ +@use "../mixins/material"; + +.dotdungeon.style-v3 > .window-content { + select { + outline: none; + border: none; + @include material.elevate(3); + padding: 4px 8px; + color: white; + transition: all 200ms ease-in-out; + width: 6.25rem; + + &:hover { + @include material.elevate(4); + } + + &:focus-visible { + @include material.elevate(6); + } + } + + option { + background: var(--surface); + color: var(--on-surface); + } +} diff --git a/styles/v3/elements/text-input.scss b/styles/v3/elements/text-input.scss new file mode 100644 index 0000000..4c066ed --- /dev/null +++ b/styles/v3/elements/text-input.scss @@ -0,0 +1,20 @@ +@use "../mixins/material"; + +.dotdungeon.style-v3 > .window-content input[type=text] { + outline: none; + border: none; + @include material.elevate(3); + padding: 4px 8px; + color: white; + transition: all 200ms ease-in-out; + width: auto; + height: auto; + + &:hover { + @include material.elevate(4); + } + + &:focus-visible { + @include material.elevate(6); + } +} diff --git a/styles/v3/elements/textarea.scss b/styles/v3/elements/textarea.scss new file mode 100644 index 0000000..bc8adcd --- /dev/null +++ b/styles/v3/elements/textarea.scss @@ -0,0 +1,24 @@ +@use "../mixins/material" as material; + +.dotdungeon.style-v3 > .window-content textarea { + outline: none; + border: none; + @include material.elevate(3); + color: white; + transition: all 200ms ease-in-out; + font-family: inherit; + font-size: 0.9rem; + + &:hover { + @include material.elevate(4); + } + + &:focus-visible { + @include material.elevate(6); + } + + &.no-resize { + @extend textarea; + resize: none; + } +} diff --git a/styles/v3/elements/utilities.scss b/styles/v3/elements/utilities.scss new file mode 100644 index 0000000..32cc792 --- /dev/null +++ b/styles/v3/elements/utilities.scss @@ -0,0 +1,12 @@ +.dotdungeon.style-v3 > .window-content { + .grow { + flex-grow: 1; + } + + .text-center { + text-align: center; + } + .text-right { + text-align: right; + } +} diff --git a/styles/v3/index.scss b/styles/v3/index.scss new file mode 100644 index 0000000..85ec4bf --- /dev/null +++ b/styles/v3/index.scss @@ -0,0 +1,52 @@ +/* Themes */ +@use "./themes/material.css"; +@use "./themes/dark.css"; + +/* Element-Styling */ +@use "./elements/utilities.scss"; +@use "./elements/button.scss"; +@use "./elements/checkbox.scss"; +@use "./elements/select.scss"; +@use "./elements/text-input.scss"; +@use "./elements/number-input.scss"; +@use "./elements/textarea.scss"; +@use "./elements/headers.scss"; +@use "./elements/hr.scss"; +@use "./elements/icons.scss"; +@use "./elements/nav.scss"; +@use "./elements/panel.scss"; +@use "./elements/label.scss"; + +/* Sheet Layouts */ +@use "./layouts/datasheet.scss"; +@use "./layouts/items/untyped/v2.scss"; + +/* Sheet Options */ +.dotdungeon.style-v3 { + --scrollbar-width: 5px; + --scrollbar-handle-color: #782e22; + --scrollbar-handle-border-color: var(--color-border-highlight); + --color-checkbox-checked: inherit; + ::-webkit-scrollbar { + width: var(--scrollbar-width); + } + ::-webkit-scrollbar-thumb { + background: var(--scrollbar-handle-color); + border-color: var(--scrollbar-handle-border-color); + border-radius: 5px; + } + + container-type: size; + + > .window-content { + padding: 0; + background: var(--sheet); + color: var(--on-sheet); + + .debug-data { + opacity: 60%; + font-family: sans-serif; + word-break: break-all; + } + } +} diff --git a/styles/v3/layouts/datasheet.scss b/styles/v3/layouts/datasheet.scss new file mode 100644 index 0000000..aa91e4d --- /dev/null +++ b/styles/v3/layouts/datasheet.scss @@ -0,0 +1,6 @@ +.dotdungeon.style-v3 .datasheet { + display: flex; + flex-direction: column; + gap: 8px; + margin: 8px; +} diff --git a/styles/v3/layouts/items/untyped/v2.scss b/styles/v3/layouts/items/untyped/v2.scss new file mode 100644 index 0000000..d94bf49 --- /dev/null +++ b/styles/v3/layouts/items/untyped/v2.scss @@ -0,0 +1,81 @@ +@use "../../../mixins/material"; +@use "../../../mixins/utils"; + +.dotdungeon.style-v3 .item--untyped { + --gap: 8px; + + .nav-bar { + @include material.elevate(8); + position: absolute; + bottom: 0; + left: 0; + right: 6px; + + nav { + display: flex; + flex-direction: row; + gap: var(--gap); + padding: var(--gap); + } + } + + .page-content { + padding: calc(var(--gap) * 1); + padding-bottom: 60px; + height: 100%; + + > .tab { + height: 100%; + gap: var(--gap); + } + } + + @include utils.tab("general") { + display: grid; + --height: 50px; + grid-template-columns: var(--height) 1fr; + grid-template-rows: var(--height) 1fr; + + .description { + grid-column: 1 / -1; + } + } + + %flex-col { + display: flex; + flex-direction: column; + gap: 8px; + } + + @include utils.tab("details") { + @extend %flex-col; + + .number { + display: grid; + grid-template-columns: 1fr 55px; + align-items: center; + } + } + + @include utils.tab("effects") { + @extend %flex-col; + } + + @include utils.tab("settings") { + @extend %flex-col; + + .capacity-usage, .quantity-capacity, .combat-relevant { + display: flex; + align-items: center; + } + + .capacity { + &--calculated { + display: flex; + flex-direction: row; + align-items: center; + gap: 8px; + } + } + } +} diff --git a/styles/v3/mixins/_material.scss b/styles/v3/mixins/_material.scss new file mode 100644 index 0000000..a869640 --- /dev/null +++ b/styles/v3/mixins/_material.scss @@ -0,0 +1,13 @@ +@mixin elevate($height) { + background-color: var(--elevation-#{$height}dp-bg); + -webkit-box-shadow: 0px 0px #{$height * 1.75}px 0px rgba(0,0,0,0.75); + -moz-box-shadow: 0px 0px #{$height * 1.75}px 0px rgba(0,0,0,0.75); + box-shadow: 0px 0px #{$height * 1.75}px 0px rgba(0,0,0,0.75); +} + +@mixin undo { + background-color: transparent; + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; +} diff --git a/styles/v3/mixins/_utils.scss b/styles/v3/mixins/_utils.scss new file mode 100644 index 0000000..02818c0 --- /dev/null +++ b/styles/v3/mixins/_utils.scss @@ -0,0 +1,5 @@ +@mixin tab($name) { + .tab.active[data-tab="#{$name}"] { + @content; + } +} diff --git a/styles/v3/themes/dark.css b/styles/v3/themes/dark.css new file mode 100644 index 0000000..374056f --- /dev/null +++ b/styles/v3/themes/dark.css @@ -0,0 +1,9 @@ +.dotdungeon.style-v3 { + --sheet: #0a0a0a; + --on-sheet: white; + + --surface: #121212; + --on-surface: white; + + --checkbox-checked: #00d300; +} diff --git a/styles/v3/themes/material.css b/styles/v3/themes/material.css new file mode 100644 index 0000000..7c934dc --- /dev/null +++ b/styles/v3/themes/material.css @@ -0,0 +1,27 @@ +/* +These CSS variables are all used in order to try following Material design as +best as I can. +*/ + +.dotdungeon.style-v3 { + --elevation-0dp-bg: var(--surface); + --elevation-0dp-text: var(--on-surface); + --elevation-1dp-bg: color-mix(in lab, transparent, white 5%); + --elevation-1dp-text: white; + --elevation-2dp-bg: color-mix(in lab, transparent, white 7%); + --elevation-2dp-text: white; + --elevation-3dp-bg: color-mix(in lab, transparent, white 8%); + --elevation-3dp-text: white; + --elevation-4dp-bg: color-mix(in lab, transparent, white 9%); + --elevation-4dp-text: white; + --elevation-6dp-bg: color-mix(in lab, transparent, white 11%); + --elevation-6dp-text: white; + --elevation-8dp-bg: color-mix(in lab, transparent, white 12%); + --elevation-8dp-text: white; + --elevation-12dp-bg: color-mix(in lab, transparent, white 14%); + --elevation-12dp-text: white; + --elevation-16dp-bg: color-mix(in lab, transparent, white 15%); + --elevation-16dp-text: white; + --elevation-24dp-bg: color-mix(in lab, transparent, white 16%); + --elevation-24dp-text: white; +} \ No newline at end of file diff --git a/system.json b/system.json index dbd4e82..5d3387c 100644 --- a/system.json +++ b/system.json @@ -2,8 +2,8 @@ "id": "dotdungeon", "title": ".dungeon", "description": "", - "version": "0.0.2", - "download": "https://github.com/Oliver-Akins/foundry.dungeon/releases/download/v0.0.1/dotdungeon.zip", + "version": "0.0.9", + "download": "https://github.com/Oliver-Akins/foundry.dungeon/releases/latest/download/dotdungeon.zip", "manifest": "https://github.com/Oliver-Akins/foundry.dungeon/releases/latest/download/system.json", "url": "https://github.com/Oliver-Akins/foundry.dungeon", "compatibility": { @@ -27,13 +27,58 @@ { "lang": "en", "name": "English (Canadian)", - "path": "langs/en-ca.json" + "path": "langs/en-ca.2.json" + } + ], + "packs": [ + { + "name": "adventures", + "label": "Adventures", + "system": "dotdungeon", + "type": "Adventure", + "path": "packs/adventures" + }, + { + "name": "untyped", + "label": "Generic Items", + "system": "dotdungeon", + "type": "Item", + "path": "packs/untyped" + }, + { + "name": "spells", + "label": "Core Spells", + "system": "dotdungeon", + "type": "Item", + "path": "packs/spells" + }, + { + "name": "aspects", + "label": "Core Aspects", + "system": "dotdungeon", + "type": "Item", + "path": "packs/aspects" + }, + { + "name": "beastiary", + "label": "Core Beastiary", + "system": "dotdungeon", + "type": "Actor", + "path": "packs/beastiary" + }, + { + "name": "rules", + "label": "Core Rules", + "system": "dotdungeon", + "type": "JournalEntry", + "path": "packs/rules" } ], "flags": { "hotReload": { - "extensions": ["css", "hbs", "json", "js", "mjs"], - "paths": ["templates", "langs", ".styles", "module"] + "extensions": ["css", "hbs", "json", "js", "mjs", "svg"], + "paths": ["templates", "langs", ".styles", "module", "assets"] } - } + }, + "initiative": "@initiative" } \ No newline at end of file diff --git a/template.json b/template.json index d385103..2954976 100644 --- a/template.json +++ b/template.json @@ -8,13 +8,14 @@ }, "Item": { "types": [ + "untyped", + "material", "aspect", "weapon", "armour", "equipment", "foil", "pet", - "transportation", "structure", "service", "legendaryItem", diff --git a/templates/actors/char-sheet-mvp/panels/aspect.pc.hbs b/templates/actors/char-sheet-mvp/panels/aspect.pc.hbs index fcaeaff..370ba5c 100644 --- a/templates/actors/char-sheet-mvp/panels/aspect.pc.hbs +++ b/templates/actors/char-sheet-mvp/panels/aspect.pc.hbs @@ -21,6 +21,7 @@ class="aspect__used--input" {{checked aspect.system.used}} data-embedded-update="system.used" + data-embedded-update-on="change" data-embedded-id="{{aspect.uuid}}" >