Compare commits

..

5 commits

Author SHA1 Message Date
Oliver-Akins
245dc1f7e1 Rename folder 2025-03-07 19:42:07 -07:00
Oliver-Akins
9336e3465c Get the deltas stacking properly 2025-03-06 21:22:14 -07:00
Oliver-Akins
85d820a9de Merge remote-tracking branch 'origin/dev' into feature/region-behaviours 2025-03-06 18:50:50 -07:00
Oliver-Akins
f74c22c510 Fix import order 2025-03-02 21:21:44 -07:00
Oliver-Akins
690eff8e46 Add the most basic form of a difficulty delta region behaviour 2025-03-02 21:19:12 -07:00
176 changed files with 712 additions and 5734 deletions

View file

@ -1,2 +0,0 @@
# The absolute path to the Foundry installation to create symlinks to
FOUNDRY_ROOT=""

View file

@ -29,22 +29,20 @@ jobs:
if: ${{ steps.check-tag.outputs.exists == 'true' }}
run: exit 1
- name: "Building compendia"
run: "npm run data:build"
- name: Ensure there are specific files to release
if: ${{ vars.files_to_release == '' }}
run: exit 1
- name: "Removing compendium source"
run: "rm -rf packs/**/_source"
- name: Move system.json to a temp file
id: manifest-move
run: mv system.json module.temp.json
- name: Update the manifest with the relevant properties
- name: Update the download property in the manifest
id: manifest-update
uses: microsoft/variable-substitution@v1
with:
files: "system.json"
env:
download: "https://github.com/${{ github.repository }}/releases/download/v${{ steps.version.outputs.version }}/release.zip"
run: cat module.temp.json | jq -r --tab '.download = "https://github.com/${{ github.repository }}/releases/download/v${{ steps.version.outputs.version }}/release.zip"' > system.json
- name: Create the zip
run: zip -r release.zip system.json packs module langs assets templates README.md
run: zip -r release.zip ${{ vars.files_to_release }}
- name: Create the draft release
uses: ncipollo/release-action@v1
@ -52,6 +50,5 @@ jobs:
tag: "v${{ steps.version.outputs.version }}"
commit: ${{ github.ref }}
draft: true
body: <img aria-hidden="true" src="https://img.shields.io/github/downloads/${{ github.repository }}/v${{ steps.version.outputs.version }}/release.zip?style=flat-square&color=%2300aa00">
generateReleaseNotes: true
artifacts: "release.zip,system.json"

9
.gitignore vendored
View file

@ -1,7 +1,5 @@
dist/
*.link
*.txt
/foundry
# Dependency directories
node_modules/
@ -13,10 +11,3 @@ jspm_packages/
.env.test.local
.env.production.local
.env.local
# 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

View file

@ -1,10 +1,9 @@
{
"files.exclude": {
"**/node_modules": true,
"foundry": true
"**/node_modules": true
},
"search.exclude": {
"foundry": true
"foundry.*.link": true
},
"html.customData": [
"./.vscode/foundry.html-data.json",
@ -12,6 +11,5 @@
],
"workbench.editorAssociations": {
"*.svg": "default",
},
"git.branchProtection": []
}
}

View file

@ -1,11 +1,6 @@
Eldritch-Oliver:
Oliver Akins:
- geist-silhouette.v2.svg : All rights reserved.
- caster-silhouette.v1.svg : All rights reserved.
- icons/star-empty.svg : Modified from https://thenounproject.com/icon/star-7711815/ by Llisole
- icons/star.svg : Modified from https://thenounproject.com/icon/star-7711815/ by Llisole
- icons/shield/checked.v1.svg : Modified from https://thenounproject.com/icon/shield-5565751/ by Corner Pixel
- icons/shield/crossed.v1.svg : Modified from https://thenounproject.com/icon/shield-5565751/ by Corner Pixel
- icons/shield/solid.v1.svg : Modified from https://thenounproject.com/icon/shield-5565751/ by Corner Pixel
Kýnan Antos (Gritsilk Games):
- hero-silhouette.svg : Licensed to Distribute and Modify within the bounds of the "Foundry-RipCrypt" system.
@ -16,9 +11,6 @@ ARISO:
Abdulloh Fauzan:
- icons/info-circle.svg (https://thenounproject.com/icon/information-4176576/) : Rights Purchased
hanifmuhammad:
- icons/plus.svg (https://thenounproject.com/icon/plus-7363257/) : Rights Purchased
QOLBIN SALIIM:
- icons/arrow-left.svg (https://thenounproject.com/icon/arrow-1933583/) : Rights Purchased
- icons/arrow-right.svg (https://thenounproject.com/icon/arrow-1933581/) : Rights Purchased

View file

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 124 45"><path d="M57.684.377c-.627-.016-1.63.187-2.633 1.111-1.003.925-.169 1.578-.485 1.569-.549-.016-1.913 1.268-3.511 2.992s-1.222 4.388-1.05 5.14c.173.753.987 1.317.987 1.317s-.218-.972-.297-1.897c-.078-.924.392-1.833 1.27-3.322.877-1.489 1.746-1.92 1.746-1.92s-.367 1.026-.531 1.41-.448 2.813.242 3.409.407.572.258 1.16c-.15.587 0 .79.525 1.465.525.673.47 1.582.611 2.632s.323.948 1.67.885c1.348-.063 1.159.714.446 1.043-.713.33-.508.49-.065 1.377s.654 1.287-1.418 1.242c-6.249-.133-21.563 9.817-25.441 9.684s-9.995-2.105-10.86-2.504c-.864-.399-2.66-1.062-3.435-1.129-.776-.066-1.662-.532-2.77-1.086s-4.084-.338-4.084-.338-.232-.399-.398-.715-.322-.476-.787-.338c-.466.139.021 1.053.021 1.053s-.537.082-.78-.056c-.245-.139-.333-.45-.644-.893-.31-.443-.287-.73-.93-.441-.642.288.2 1.373.2 1.373s-.2.022-.775.244c-.577.221-.178.974-.178.974s-.865-.665-.998-1.086c-.133-.42-.421-.953-1.086-.554s.665 1.884.576 1.884-.53-.154-1.062-.597-.866-.644-1.309-.422.31 1.041 1.219 1.861c.908.82 1.64 1.197 3.258 1.397 1.617.2 2.24 1.196 3.28 1.152 1.042-.044 2.06-.62 4.698-.265 2.637.354 8.51 3.9 8.953 4.232s.466.643.488 1.02c.023.376-.377 1.862-.421 2.216-.045.355-.975 1.153-1.75 1.907-.776.753-1.33 1.86-1.33 2.28 0 .422-.887 1.663-.887 1.663s23.27.489 24.156.467c.887-.023 3.325-2.194 4.522-3.258 1.196-1.064 2.989-3.073 3.283-3.7.294-.626.33-.364.58-.05.25.313.345.203.69.234.344.032.218.032.25.502.03.47.753 1.52 1.505 2.256.752.737 1.88 2.852 2.334 4.106.455 1.253 21.03.753 20.592.22s-1.207-2.978-1.285-3.605c-.079-.627.25-2.036.25-2.506s-.313-1.553-.094-1.568c.22-.016 4.922 6.66 6.176 7.914 1.253 1.253 18.396.392 19.117.377.72-.016.627.313.58-1.223s-1.034-3.574-1.394-4.373-.064-.861.265-1.002c.33-.141 2.273-.847 3.244-1.254s3.87-1.347 5.438-1.582c.863-.13 1.983-.43 3.12-.78.559-.15 1.682-.535 4.089-1.574 3.071-1.325 5.214-3.823 6.363-4.906.265-.22.818-.84 1.746-2.033s.442-.949-.154-.97c-.597-.023-2.498 1.81-2.498 1.81s.044-1.326-.22-2.873c-.266-1.547-.73-.617-.84-.264-.111.354-.067 1.768-.067 1.768s-.774-1.68-.973-1.68-.508.75-.154 1.436c.353.685.441 1.79.441 1.79s-.44-.332-1.015-1.591c-.575-1.26-.795-.132-.729.398s.883 1.613.883 2.188c0 .574-.486.95-1.502.927s-.796-.619-.553-1.26c.243-.64.73-1.944.752-2.519.022-.574-.509-1.28-.885-1.258-.375.022-.309.574-.574 2.01s-2.365 2.475-3.426 3.094c-.827.483-.957.818-1.257 1.152-.578.456-1.311.876-1.684.793-.564-.125-.565.095-.91.408-.345.314-1.221.532-3.133.94 0 0-2.79.345-3.824.408s-2.07-.156-2.916-.752c-.846-.595-1.88-1.004-4.543-.283-2.664.72-4.17.564-4.17.564s-.39-1.512-1.785-3.306-2.586-2.186-2.586-2.186.54-.07 1.55-.125 1.898.548 2.446.877c.548.33.728 1.417.814 1.723.087.305-.148.855-.015.87s.478-1.12.478-1.41-.345-1.286-.455-1.513-.729-.744-.588-.877.698.063 1.207.51c.51.446.791.775.862 2.1.07 1.324-.893 2.09-.698 2.287.196.195 1.584-1.535 1.569-3s-1.05-2.703-1.912-3.495c-.862-.791-2.328-1.066-2.313-1.207.016-.14.385-.556.377-1.425-.008-.87-.745-1.968-1.027-2.336-.282-.369-1.41-1.018-1.15-1.057.258-.04.837.188 1.746.705.908.517 1.396 1.762 1.56 1.691s-.165-1.05-.635-1.92-1.81-1.308-1.81-1.308.312-.163.94-1.096c.626-.932.046-1.7-.071-1.912-.118-.211-.822-.572-.328.04.493.61-.337 1.268-1.434 2.052-1.097.783-3.918.156-3.918.156s-.125.024 0-.916c.126-.94-.596-2.539-2.445-3.949-1.85-1.41-3.448-.282-6.645-.094s-3.95-2.757-5.861-6.174c-1.912-3.416-5.093-3.996-6.049-3.996S58.31.393 57.684.377m21.06 12.541a3.3 3.3 0 0 1 1.844.582c1.208.79 1.01 2.57 1.01 2.57s-1.325-.392-2.547-1.841c-.833-.988-2.155-1.223-2.155-1.223s.568-.069 1.202-.086c.227-.006.44-.01.646-.002m2.465 5.38c.124-.012.623.257 1.277.507.698.266 1.774.277 2.627.343.854.067 1.386.111 2.416 1.086s.287 2.371.287 2.371l-1.33-.056s.312-.388.534-.71c.221-.32.375-1.428-.467-1.76s-.366-.056-.3.52c.067.577-.73 1.075-1.405 1.075-.676 0-1.496-.598-2.205-1.24-1.197-.998-1.531-2.072-1.454-2.127a.04.04 0 0 1 .02-.008" style="paint-order:markers stroke fill"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 124 45"><path d="M57.684.377c-.627-.016-1.63.187-2.633 1.111-1.003.925-.169 1.578-.485 1.569-.549-.016-1.913 1.268-3.511 2.992s-1.222 4.388-1.05 5.14c.173.753.987 1.317.987 1.317s-.218-.972-.297-1.897c-.078-.924.392-1.833 1.27-3.322.877-1.489 1.746-1.92 1.746-1.92s-.367 1.026-.531 1.41-.448 2.813.242 3.409.407.572.258 1.16c-.15.587 0 .79.525 1.465.525.673.47 1.582.611 2.632s.323.948 1.67.885c1.348-.063 1.159.714.446 1.043-.713.33-.508.49-.065 1.377s.654 1.287-1.418 1.242c-6.249-.133-21.563 9.817-25.441 9.684s-9.995-2.105-10.86-2.504c-.864-.399-2.66-1.062-3.435-1.129-.776-.066-1.662-.532-2.77-1.086s-4.084-.338-4.084-.338-.232-.399-.398-.715-.322-.476-.787-.338c-.466.139.021 1.053.021 1.053s-.537.082-.78-.056c-.245-.139-.333-.45-.644-.893-.31-.443-.287-.73-.93-.441-.642.288.2 1.373.2 1.373s-.2.022-.775.244c-.577.221-.178.974-.178.974s-.865-.665-.998-1.086c-.133-.42-.421-.953-1.086-.554s.665 1.884.576 1.884-.53-.154-1.062-.597-.866-.644-1.309-.422.31 1.041 1.219 1.861c.908.82 1.64 1.197 3.258 1.397 1.617.2 2.24 1.196 3.28 1.152 1.042-.044 2.06-.62 4.698-.265 2.637.354 8.51 3.9 8.953 4.232s.466.643.488 1.02c.023.376-.377 1.862-.421 2.216-.045.355-.975 1.153-1.75 1.907-.776.753-1.33 1.86-1.33 2.28 0 .422-.887 1.663-.887 1.663s23.27.489 24.156.467c.887-.023 3.325-2.194 4.522-3.258 1.196-1.064 2.989-3.073 3.283-3.7.294-.626.33-.364.58-.05.25.313.345.203.69.234.344.032.218.032.25.502.03.47.753 1.52 1.505 2.256.752.737 1.88 2.852 2.334 4.106.455 1.253 21.03.753 20.592.22s-1.207-2.978-1.285-3.605c-.079-.627.25-2.036.25-2.506s-.313-1.553-.094-1.568c.22-.016 4.922 6.66 6.176 7.914 1.253 1.253 18.396.392 19.117.377.72-.016.627.313.58-1.223s-1.034-3.574-1.394-4.373-.064-.861.265-1.002c.33-.141 2.273-.847 3.244-1.254s3.87-1.347 5.438-1.582c.863-.13 1.983-.43 3.12-.78.559-.15 1.682-.535 4.089-1.574 3.071-1.325 5.214-3.823 6.363-4.906.265-.22.818-.84 1.746-2.033s.442-.949-.154-.97c-.597-.023-2.498 1.81-2.498 1.81s.044-1.326-.22-2.873c-.266-1.547-.73-.617-.84-.264-.111.354-.067 1.768-.067 1.768s-.774-1.68-.973-1.68-.508.75-.154 1.436c.353.685.441 1.79.441 1.79s-.44-.332-1.015-1.591c-.575-1.26-.795-.132-.729.398s.883 1.613.883 2.188c0 .574-.486.95-1.502.927s-.796-.619-.553-1.26c.243-.64.73-1.944.752-2.519.022-.574-.509-1.28-.885-1.258-.375.022-.309.574-.574 2.01s-2.365 2.475-3.426 3.094c-.827.483-.957.818-1.257 1.152-.578.456-1.311.876-1.684.793-.564-.125-.565.095-.91.408-.345.314-1.221.532-3.133.94 0 0-2.79.345-3.824.408s-2.07-.156-2.916-.752c-.846-.595-1.88-1.004-4.543-.283-2.664.72-4.17.564-4.17.564s-.39-1.512-1.785-3.306-2.586-2.186-2.586-2.186.54-.07 1.55-.125 1.898.548 2.446.877c.548.33.728 1.417.814 1.723.087.305-.148.855-.015.87s.478-1.12.478-1.41-.345-1.286-.455-1.513-.729-.744-.588-.877.698.063 1.207.51c.51.446.791.775.862 2.1.07 1.324-.893 2.09-.698 2.287.196.195 1.584-1.535 1.569-3s-1.05-2.703-1.912-3.495c-.862-.791-2.328-1.066-2.313-1.207.016-.14.385-.556.377-1.425-.008-.87-.745-1.968-1.027-2.336-.282-.369-1.41-1.018-1.15-1.057.258-.04.837.188 1.746.705.908.517 1.396 1.762 1.56 1.691s-.165-1.05-.635-1.92-1.81-1.308-1.81-1.308.312-.163.94-1.096c.626-.932.046-1.7-.071-1.912-.118-.211-.822-.572-.328.04.493.61-.337 1.268-1.434 2.052-1.097.783-3.918.156-3.918.156s-.125.024 0-.916c.126-.94-.596-2.539-2.445-3.949-1.85-1.41-3.448-.282-6.645-.094s-3.95-2.757-5.861-6.174c-1.912-3.416-5.093-3.996-6.049-3.996S58.31.393 57.684.377m21.06 12.541a3.3 3.3 0 0 1 1.844.582c1.208.79 1.01 2.57 1.01 2.57s-1.325-.392-2.547-1.841c-.833-.988-2.155-1.223-2.155-1.223s.568-.069 1.202-.086c.227-.006.44-.01.646-.002m2.465 5.38c.124-.012.623.257 1.277.507.698.266 1.774.277 2.627.343.854.067 1.386.111 2.416 1.086s.287 2.371.287 2.371l-1.33-.056s.312-.388.534-.71c.221-.32.375-1.428-.467-1.76s-.366-.056-.3.52c.067.577-.73 1.075-1.405 1.075-.676 0-1.496-.598-2.205-1.24-1.197-.998-1.531-2.072-1.454-2.127a.04.04 0 0 1 .02-.008" style="display:inline;fill:#000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;paint-order:markers stroke fill"/></svg>

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 4 KiB

Before After
Before After

View file

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="100pt" height="100pt" version="1.1" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<path d="m92.719 4.8594s-4.6914 7.8398-17.219 13.52c-7.1094-5.3203-15.941-8.4688-25.5-8.4688s-18.391 3.1484-25.5 8.4688c-12.531-5.6797-17.219-13.52-17.219-13.52s-6.9609 17.73 3.4297 31.109c-2.1289 5.0781-3.3203 10.691-3.3203 16.551 0 23.539 19.078 42.621 42.621 42.621 23.539 0 42.621-19.078 42.621-42.621 0-5.8594-1.1914-11.469-3.3203-16.551 10.379-13.379 3.4297-31.109 3.4297-31.109zm-56.719 57.762c-2.4609 1.1211-5.3086 1.2383-8.0391 0.32812-2.7305-0.91016-4.9414-2.7109-6.2305-5.0781-1.25-2.2891-1.5508-4.9805-0.89844-7.8516l20.602 6.8711c-1.1992 2.6914-3.0508 4.6602-5.4297 5.7383zm42.27-4.7617c-1.2891 2.3711-3.5117 4.1797-6.2305 5.0781s-5.5781 0.78906-8.0391-0.32812c-2.3789-1.0781-4.2305-3.0508-5.4297-5.7383l20.602-6.8711c0.66016 2.8711 0.35156 5.5586-0.89844 7.8516z"/>
</svg>

Before

Width:  |  Height:  |  Size: 933 B

View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="100pt" height="100pt" version="1.1" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<path d="m39.898 56.199-7.6992-10.801c-1.8984-2.6016-2-6.3008-0.19922-9 0.19922-0.30078 2.3984-3.5 6.8008-6.6992-16.5 7.3984-34.5 21.398-36 23.801-2.5 4 11.5 5.6016 14 19.199 2.1016 11.898 18 17.602 20.398 11.898 2.3008-5.6016 5-10.801 7.8984-15.398l0.80078-9.6992c-2.5-0.19922-4.5977-1.3984-6-3.3008z"/>
<path d="m93.102 26.602h-33.102c-17 0-24.801 11.301-25.102 11.801-1 1.5-1 3.5 0.10156 5l7.6992 10.801c0.89844 1.1992 2.1992 1.8008 3.6016 1.8008 0.89844 0 1.8008-0.30078 2.5-0.80078 0.19922-0.19922 0.39844-0.39844 0.60156-0.60156v3.1016l-2.6992 32.602c-0.19922 2.6992 1.8008 5 4.5 5.3008 2.6992 0.19922 5-1.8008 5.3008-4.5l2.6016-31.301 1.8945-0.003907 2.6016 31.199c0.19922 2.5 2.3008 4.5 4.8984 4.5h0.39844c2.6992-0.19922 4.6992-2.6016 4.5-5.3008l-2.6992-32.602-0.19922-22.301h22.699c2.3984 0 4.3984-2 4.3984-4.3984-0.097656-2.3984-2.0977-4.2969-4.4961-4.2969zm-43.602 22-5.3008-7.3984c1.1992-1.1016 3-2.3984 5.3984-3.6016z"/>
<path d="m69.801 14.199c0 5.4141-4.3867 9.8008-9.8008 9.8008s-9.8008-4.3867-9.8008-9.8008c0-5.4102 4.3867-9.8008 9.8008-9.8008s9.8008 4.3906 9.8008 9.8008z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -1,3 +0,0 @@
<svg version="1.1" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<path d="m83.59 16.41c-18.547-18.547-48.633-18.547-67.18 0s-18.547 48.633 0 67.18 48.633 18.547 67.18 0 18.547-48.633 0-67.18zm-6.3438 39.922h-20.914v20.91c0 3.5039-2.8398 6.332-6.332 6.332s-6.332-2.8281-6.332-6.332v-20.91h-20.914c-3.5039 0-6.332-2.8398-6.332-6.332s2.8281-6.332 6.332-6.332h20.914v-20.914c0-3.5039 2.8398-6.332 6.332-6.332s6.332 2.8281 6.332 6.332v20.91h20.91c3.5039 0 6.332 2.8398 6.332 6.332 0.003906 3.4961-2.8242 6.3359-6.3281 6.3359z"/>
</svg>

Before

Width:  |  Height:  |  Size: 544 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><path d="M50 6.9q-2.1 0-3.9 1.4a47 47 0 0 1-24.8 10c-3.2.3-5.7 3-5.7 6.2v31c0 13 7.3 24.8 19 30.7l12.6 6.2a6 6 0 0 0 5.6 0l12.6-6.2a34 34 0 0 0 19-30.8V24.5c0-3.2-2.5-5.9-5.7-6.2a47 47 0 0 1-24.8-10A6 6 0 0 0 50 6.9m20.4 25.6a5 5 0 0 1 4 8.2L52.2 69a5 5 0 0 1-7.2.8L28.2 55.9a5 5 0 0 1 6.4-7.7l12.8 10.5 19.1-24.2a5 5 0 0 1 3.9-2"/></svg>

Before

Width:  |  Height:  |  Size: 401 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><path d="M50 6.9q-2.1 0-3.9 1.4a47 47 0 0 1-24.8 10c-3.2.3-5.7 3-5.7 6.2v31c0 13 7.3 24.8 19 30.7l12.6 6.2a6 6 0 0 0 5.6 0l12.6-6.2a34 34 0 0 0 19-30.8V24.5c0-3.2-2.5-5.9-5.7-6.2a47 47 0 0 1-24.8-10A6 6 0 0 0 50 6.9M36.7 27.6a6 6 0 0 1 4.6 2.3l9 11.2L59 30a6 6 0 0 1 8.4-1l.4.3a6 6 0 0 1 1 8.4L58.2 51.2l10.5 13.3a6 6 0 0 1-1 8.4l-.4.3a6 6 0 0 1-8.4-1l-8.7-11-8.7 11.1a6 6 0 0 1-8.4 1l-.3-.3a6 6 0 0 1-1-8.4l10.5-13.4-10.8-13.6a6 6 0 0 1 1-8.4l.4-.3a6 6 0 0 1 3.8-1.3"/></svg>

Before

Width:  |  Height:  |  Size: 539 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><path d="M49.996 6.9a6.2 6.2 0 0 0-3.869 1.362 46.9 46.9 0 0 1-24.828 10.035c-3.238.308-5.68 2.984-5.68 6.219V55.41c0 13.102 7.281 24.883 19.004 30.746l12.578 6.29a6.32 6.32 0 0 0 5.59 0l12.578-6.29c11.72-5.863 19.004-17.645 19.004-30.746l.004-30.894c0-3.239-2.441-5.91-5.68-6.22h-.002a46.9 46.9 0 0 1-24.83-10.034A6.2 6.2 0 0 0 49.996 6.9"/></svg>

Before

Width:  |  Height:  |  Size: 411 B

View file

@ -1 +0,0 @@
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path d="M93.824 44.383 80.058 61.379a6.3 6.3 0 0 0-1.398 4.3l1.144 21.84c.2 3.802-3.578 6.548-7.133 5.18l-20.418-7.84a6.3 6.3 0 0 0-4.52 0L27.317 92.7c-3.551 1.364-7.332-1.382-7.133-5.18l1.145-21.84a6.3 6.3 0 0 0-1.399-4.3L6.163 44.383C3.77 41.426 5.21 36.985 8.886 36l21.125-5.66a6.3 6.3 0 0 0 3.656-2.656L45.577 9.34c2.07-3.192 6.742-3.192 8.816 0l11.91 18.344a6.3 6.3 0 0 0 3.657 2.656L91.085 36c3.675.985 5.128 5.43 2.734 8.387z" style="stroke-width: 8px; stroke: black; fill: transparent;"/></svg>

Before

Width:  |  Height:  |  Size: 565 B

View file

@ -1 +0,0 @@
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path d="M93.824 44.383 80.058 61.379a6.3 6.3 0 0 0-1.398 4.3l1.144 21.84c.2 3.802-3.578 6.548-7.133 5.18l-20.418-7.84a6.3 6.3 0 0 0-4.52 0L27.317 92.7c-3.551 1.364-7.332-1.382-7.133-5.18l1.145-21.84a6.3 6.3 0 0 0-1.399-4.3L6.163 44.383C3.77 41.426 5.21 36.985 8.886 36l21.125-5.66a6.3 6.3 0 0 0 3.656-2.656L45.577 9.34c2.07-3.192 6.742-3.192 8.816 0l11.91 18.344a6.3 6.3 0 0 0 3.657 2.656L91.085 36c3.675.985 5.128 5.43 2.734 8.387z"/></svg>

Before

Width:  |  Height:  |  Size: 504 B

14
augments.d.ts vendored
View file

@ -1,14 +0,0 @@
declare global {
class Hooks extends foundry.helpers.Hooks {};
const fromUuid = foundry.utils.fromUuid;
}
interface Actor {
/** The system-specific data */
system: any;
};
interface Item {
/** The system-specific data */
system: any;
};

View file

@ -4,7 +4,7 @@ import stylistic from "@stylistic/eslint-plugin";
export default [
// Tell eslint to ignore files that I don't mind being formatted slightly differently
{ ignores: [ `scripts/`, `foundry/` ] },
{ ignores: [ `scripts/` ] },
{
languageOptions: {
globals: globals.browser,
@ -22,7 +22,9 @@ export default [
Hooks: `readonly`,
ui: `readonly`,
Actor: `readonly`,
Actors: `readonly`,
Item: `readonly`,
Items: `readonly`,
foundry: `readonly`,
ChatMessage: `readonly`,
ActiveEffect: `readonly`,
@ -34,7 +36,6 @@ export default [
Combatant: `readonly`,
canvas: `readonly`,
Token: `readonly`,
Tour: `readonly`,
},
},
},
@ -75,7 +76,7 @@ export default [
"@stylistic/eol-last": `warn`,
"@stylistic/operator-linebreak": [`warn`, `before`],
"@stylistic/indent": [`warn`, `tab`],
"@stylistic/brace-style": [`warn`, `stroustrup`, { "allowSingleLine": true }],
"@stylistic/brace-style": [`warn`, `1tbs`, { "allowSingleLine": true }],
"@stylistic/quotes": [`warn`, `backtick`, { "avoidEscape": true }],
"@stylistic/comma-dangle": [`warn`, { arrays: `always-multiline`, objects: `always-multiline`, imports: `always-multiline`, exports: `always-multiline`, functions: `always-multiline` }],
"@stylistic/comma-style": [`warn`, `last`],

View file

@ -1,19 +1,11 @@
{
"compilerOptions": {
"module": "es2022",
"target": "es2022",
"types": [
"./augments.d.ts"
],
"paths": {
"@client/*": ["./foundry/client/*"],
"@common/*": ["./foundry/common/*"],
}
"module": "ES2020",
"target": "ES2020"
},
"include": [
"module/**/*",
"foundry/client/client.mjs",
"foundry/client/global.d.mts",
"foundry/common/primitives/global.d.mts"
]
"exclude": ["node_modules", "**/node_modules/*"],
"include": ["module/**/*", "foundry.v13.link/client/**/*.js", "foundry.v13.link/**/*.mjs"],
"typeAcquisition": {
"include": ["jquery"]
}
}

View file

@ -1,8 +1,7 @@
{
"TYPES": {
"Actor": {
"hero": "Hero",
"geist": "Geist"
"hero": "Hero"
},
"Item": {
"ammo": "Ammo",
@ -12,19 +11,17 @@
"shield": "Shield",
"skill": "Skill",
"weapon": "Weapon"
},
"RegionBehavior": {
"difficultyDelta": "Condition Delta"
}
},
"RipCrypt": {
"sheet-names": {
"AllItemsSheetV1": "RipCrypt Item Sheet",
"ArmourSheet": "Armour Sheet",
"CombinedHeroSheet": "Hero Sheet",
"StatsCardV1": "Hero Stat Card",
"CraftCardV1": "Hero Craft Card",
"SkillsCardV1": "Hero Skill Card"
},
"app-titles": {
"AmmoTracker": "Ammo Tracker"
"HeroSummaryCardV1": "Hero Stat Card",
"HeroSkillsCardV1": "Hero Skill Card"
},
"common": {
"abilities": {
@ -57,7 +54,6 @@
"fract": "Fract",
"focus": "Focus"
},
"aura": "Aura",
"cost": "Cost",
"currency": {
"gold": "Gold",
@ -67,7 +63,6 @@
"damage": "Damage",
"delete": "Delete",
"description": "Description",
"details": "Details",
"difficulties": {
"easy": "Easy",
"normal": "Normal",
@ -83,7 +78,6 @@
"equipped": "Equipped",
"fate": "Fate",
"gear": "Gear",
"glimcraft": "Glimcraft",
"glory": "Glory",
"guts": "Guts",
"location": "Location",
@ -155,15 +149,11 @@
"both": "Notification and Pause Game",
"nothing": "Do Nothing"
}
},
"allowUpdateSandsSocket": {
"name": "Player Haste Updates the Sands of Fate",
"hint": "This setting determines if when a player makes a haste check that the result will automatically be applied to the global Sands of Fate. Disabling this is good if you want to let players roll without needing to worry about automation messing anything up while they spam rolls."
}
},
"Apps": {
"damage-reduction": "@RipCrypt.common.damage reduction",
"traits-range": "@RipCrypt.common.traits & @RipCrypt.common.range",
"move-run": "@RipCrypt.common.move • @RipCrypt.common.run",
"traits-range": "@RipCrypt.common.traits @RipCrypt.common.range",
"grit-skills": "@RipCrypt.common.abilities.grit Skills",
"gait-skills": "@RipCrypt.common.abilities.gait Skills",
"grip-skills": "@RipCrypt.common.abilities.grip Skills",
@ -182,26 +172,12 @@
"numberOfDice": "# of Dice",
"rollTarget": "Target",
"difficulty": "(DC: {dc})",
"RichEditor-no-collaborative": "Warning: This editor is not collaborative, that means that if you and someone else are editing it at the same time, you won't see that someone else is making changes until they save, and then your changes will be lost.",
"starred-ammo-placeholder": "Starred Ammo Slot",
"AmmoTracker": {
"no-ammo": "You don't have any ammo!",
"star-button": "Add {name} as a starred ammo",
"star-button-tooltip": "Add Star",
"unstar-button": "Remove {name} as a starred ammo",
"unstar-button-tooltip": "Remove Star"
},
"protects-the-location": "Protects your {part}"
"RichEditor-no-collaborative": "Warning: This editor is not collaborative, that means that if you and someone else are editing it at the same time, you won't see that someone else is making changes until they save, and then your changes will be lost."
},
"notifs": {
"error": {
"cannot-equip": "Cannot equip the {itemType}, see console for more details.",
"invalid-delta": "The delta for \"{name}\" is not a number, cannot finish processing the action.",
"at-favourite-limit": "Cannot favourite more than three items, unfavourite one to make space.",
"invalid-socket": "Invalid socket data received, this means a module or system bug is present.",
"unknown-socket-event": "An unknown socket event was received: {event}",
"no-active-gm": "No active @USER.GM is logged in, you must wait for a @USER.GM to be active before you can do that.",
"malformed-socket-payload": "Socket event \"{event}\" received with malformed payload. Details: {details}"
"invalid-delta": "The delta for \"{name}\" is not a number, cannot finish processing the action."
},
"warn": {
"cannot-go-negative": "\"{name}\" is unable to be a negative number."
@ -214,16 +190,18 @@
"shield-bonus": "Shield Bonus: {value}",
"set-fate-to": "Set Fate to {ordinal}",
"current-tour": "Current Delve Tour",
"create-new-item": "Create new item",
"next-tour": "Next Delve Tour",
"prev-tour": "Previous Delve Tour",
"auras": {
"normal": "The distance of your aura normally",
"heavy": "The distance of your aura when using Heavycraft"
"prev-tour": "Previous Delve Tour"
},
"region": {
"difficultyDelta": {
"FIELDS": {
"delta": {
"label": "Delta",
"hint": "How much should actors in this area have their difficulty changed from the global value."
}
}
}
}
},
"USER": {
"GM": "Keeper"
}
}

View file

@ -1,8 +1,9 @@
import { CraftCardV1 } from "./CraftCardV1.mjs";
import { filePath } from "../../consts.mjs";
import { GenericAppMixin } from "../GenericApp.mjs";
import { SkillsCardV1 } from "./SkillsCardV1.mjs";
import { StatsCardV1 } from "./StatsCardV1.mjs";
import { HeroCraftCardV1 } from "./HeroCraftCardV1.mjs";
import { HeroSkillsCardV1 } from "./HeroSkillsCardV1.mjs";
import { HeroSummaryCardV1 } from "./HeroSummaryCardV1.mjs";
import { Logger } from "../../utils/Logger.mjs";
const { HandlebarsApplicationMixin } = foundry.applications.api;
const { ActorSheetV2 } = foundry.applications.sheets;
@ -22,9 +23,7 @@ export class CombinedHeroSheet extends GenericAppMixin(HandlebarsApplicationMixi
window: {
resizable: false,
},
actions: {
...StatsCardV1.DEFAULT_OPTIONS.actions,
},
actions: {},
form: {
submitOnChange: true,
closeOnSubmit: false,
@ -33,10 +32,10 @@ export class CombinedHeroSheet extends GenericAppMixin(HandlebarsApplicationMixi
static PARTS = {
summary: {
template: filePath(`templates/Apps/StatsCardV1/content.hbs`),
template: filePath(`templates/Apps/HeroSummaryCardV1/content.hbs`),
},
skills: {
template: filePath(`templates/Apps/SkillsCardV1/content.hbs`),
template: filePath(`templates/Apps/HeroSkillsCardV1/content.hbs`),
},
craft: {
template: filePath(`templates/Apps/CombinedHeroSheet/crafts.hbs`),
@ -48,8 +47,8 @@ export class CombinedHeroSheet extends GenericAppMixin(HandlebarsApplicationMixi
async _onRender(context, options) {
await super._onRender(context, options);
const summaryElement = this.element.querySelector(`.StatsCardV1`);
StatsCardV1._onRender(
const summaryElement = this.element.querySelector(`.HeroSummaryCardV1`);
HeroSummaryCardV1._onRender(
context,
{
...options,
@ -58,9 +57,8 @@ export class CombinedHeroSheet extends GenericAppMixin(HandlebarsApplicationMixi
},
);
const skillsElement = this.element.querySelector(`.SkillsCardV1`);
SkillsCardV1._createPopoverListeners.bind(this)();
SkillsCardV1._onRender.bind(this)(
const skillsElement = this.element.querySelector(`.HeroSkillsCardV1`);
HeroSkillsCardV1._onRender.bind(this)(
context,
{
...options,
@ -70,7 +68,7 @@ export class CombinedHeroSheet extends GenericAppMixin(HandlebarsApplicationMixi
);
const craftsElement = this.element.querySelector(`.crafts-summary`);
CraftCardV1._onRender.bind(this)(
HeroCraftCardV1._onRender.bind(this)(
context,
{
...options,
@ -86,27 +84,28 @@ export class CombinedHeroSheet extends GenericAppMixin(HandlebarsApplicationMixi
switch (partId) {
case `summary`: {
ctx = await StatsCardV1.prepareGuts(ctx);
ctx = await StatsCardV1.prepareWeapons(ctx);
ctx = await StatsCardV1.prepareArmor(ctx);
ctx = await StatsCardV1.prepareFatePath(ctx);
ctx = await StatsCardV1.prepareAbilityRow(ctx);
ctx = await StatsCardV1.prepareSpeed(ctx);
ctx = await StatsCardV1.prepareLevelData(ctx);
ctx = await HeroSummaryCardV1.prepareGuts(ctx);
ctx = await HeroSummaryCardV1.prepareWeapons(ctx);
ctx = await HeroSummaryCardV1.prepareArmor(ctx);
ctx = await HeroSummaryCardV1.prepareFatePath(ctx);
ctx = await HeroSummaryCardV1.prepareAbilityRow(ctx);
ctx = await HeroSummaryCardV1.prepareSpeed(ctx);
ctx = await HeroSummaryCardV1.prepareLevelData(ctx);
break;
};
case `skills`: {
ctx = await SkillsCardV1.prepareGear(ctx);
ctx = await SkillsCardV1.prepareAmmo(ctx);
ctx = await SkillsCardV1.prepareSkills(ctx);
ctx = await HeroSkillsCardV1.prepareGear(ctx);
ctx = await HeroSkillsCardV1.prepareAmmo(ctx);
ctx = await HeroSkillsCardV1.prepareSkills(ctx);
break;
};
case `craft`: {
ctx = await CraftCardV1.prepareCraft(ctx);
ctx = await HeroCraftCardV1.prepareCraft(ctx);
break;
};
};
Logger.debug(`Context keys:`, Object.keys(ctx));
return ctx;
};
// #endregion

View file

@ -7,16 +7,15 @@ import { Logger } from "../../utils/Logger.mjs";
const { HandlebarsApplicationMixin } = foundry.applications.api;
const { ActorSheetV2 } = foundry.applications.sheets;
const { ContextMenu } = foundry.applications.ux;
const { deepClone } = foundry.utils;
const { ContextMenu } = foundry.applications.ui;
export class CraftCardV1 extends GenericAppMixin(HandlebarsApplicationMixin(ActorSheetV2)) {
export class HeroCraftCardV1 extends GenericAppMixin(HandlebarsApplicationMixin(ActorSheetV2)) {
// #region Options
static DEFAULT_OPTIONS = {
classes: [
`ripcrypt--actor`,
`ripcrypt--CraftCardV1`,
`ripcrypt--HeroCraftCardV1`,
],
position: {
width: `auto`,
@ -35,7 +34,7 @@ export class CraftCardV1 extends GenericAppMixin(HandlebarsApplicationMixin(Acto
static PARTS = {
content: {
template: filePath(`templates/Apps/CraftCardV1/content.hbs`),
template: filePath(`templates/Apps/HeroCraftCardV1/content.hbs`),
},
};
// #endregion
@ -43,7 +42,7 @@ export class CraftCardV1 extends GenericAppMixin(HandlebarsApplicationMixin(Acto
// #region Lifecycle
async _onRender(context, options) {
await super._onRender(context, options);
CraftCardV1._onRender.bind(this)(context, options);
HeroCraftCardV1._onRender.bind(this)(context, options);
};
static async _onRender(_context, options) {
@ -80,18 +79,12 @@ export class CraftCardV1 extends GenericAppMixin(HandlebarsApplicationMixin(Acto
ctx = await super._preparePartContext(partId, ctx, opts);
ctx.actor = this.document;
ctx = await CraftCardV1.prepareAura(ctx);
ctx = await CraftCardV1.prepareCraft(ctx);
ctx = await HeroCraftCardV1.prepareCraft(ctx);
Logger.debug(`Context:`, ctx);
return ctx;
};
static async prepareAura(ctx) {
ctx.aura = deepClone(ctx.actor.system.aura);
return ctx;
};
static async prepareCraft(ctx) {
ctx.craft = {};
const aspects = Object.values(gameTerms.Aspects);
@ -115,8 +108,7 @@ export class CraftCardV1 extends GenericAppMixin(HandlebarsApplicationMixin(Acto
const length = crafts.length;
if (length >= limit) {
crafts = crafts.slice(0, limit);
}
else {
} else {
crafts = crafts
.concat(Array(limit - length).fill(null))
.slice(0, limit);

View file

@ -1,25 +1,21 @@
import { deleteItemFromElement, editItemFromElement } from "../utils.mjs";
import { documentSorter, filePath } from "../../consts.mjs";
import { AmmoTracker } from "../popovers/AmmoTracker.mjs";
import { gameTerms } from "../../gameTerms.mjs";
import { GenericAppMixin } from "../GenericApp.mjs";
import { ItemFlags } from "../../flags/item.mjs";
import { localizer } from "../../utils/Localizer.mjs";
import { Logger } from "../../utils/Logger.mjs";
import { PopoverEventManager } from "../../utils/PopoverEventManager.mjs";
const { HandlebarsApplicationMixin } = foundry.applications.api;
const { ActorSheetV2 } = foundry.applications.sheets;
const { ContextMenu } = foundry.applications.ux;
const { deepClone } = foundry.utils;
const { ContextMenu } = foundry.applications.ui;
export class SkillsCardV1 extends GenericAppMixin(HandlebarsApplicationMixin(ActorSheetV2)) {
export class HeroSkillsCardV1 extends GenericAppMixin(HandlebarsApplicationMixin(ActorSheetV2)) {
// #region Options
static DEFAULT_OPTIONS = {
classes: [
`ripcrypt--actor`,
`ripcrypt--SkillsCardV1`,
`ripcrypt--HeroSkillsCardV1`,
],
position: {
width: `auto`,
@ -38,7 +34,7 @@ export class SkillsCardV1 extends GenericAppMixin(HandlebarsApplicationMixin(Act
static PARTS = {
content: {
template: filePath(`templates/Apps/SkillsCardV1/content.hbs`),
template: filePath(`templates/Apps/HeroSkillsCardV1/content.hbs`),
},
};
// #endregion
@ -46,8 +42,7 @@ export class SkillsCardV1 extends GenericAppMixin(HandlebarsApplicationMixin(Act
// #region Lifecycle
async _onRender(context, options) {
await super._onRender(context, options);
SkillsCardV1._onRender.bind(this)(context, options);
SkillsCardV1._createPopoverListeners.bind(this)();
HeroSkillsCardV1._onRender.bind(this)(context, options);
};
static async _onRender(_context, options) {
@ -80,27 +75,13 @@ export class SkillsCardV1 extends GenericAppMixin(HandlebarsApplicationMixin(Act
);
};
/** @this {SkillsCardV1} */
static async _createPopoverListeners() {
const ammoInfoIcon = this.element.querySelector(`.ammo-info-icon`);
const idPrefix = this.actor.uuid;
const manager = new PopoverEventManager(`${idPrefix}.ammo-info-icon`, ammoInfoIcon, AmmoTracker);
this._popoverManagers.set(`.ammo-info-icon`, manager);
this._hookIDs.set(Hooks.on(`prepare${manager.id}Context`, (ctx) => {
ctx.ammos = this.actor.itemTypes.ammo;
}), `prepare${manager.id}Context`);
};
async _preparePartContext(partId, ctx, opts) {
ctx = await super._preparePartContext(partId, ctx, opts);
ctx.actor = this.document;
ctx = await SkillsCardV1.prepareGear(ctx);
ctx = await SkillsCardV1.prepareAmmo(ctx);
ctx = await SkillsCardV1.prepareSkills(ctx);
ctx.aura = deepClone(ctx.actor.system.aura);
ctx = await HeroSkillsCardV1.prepareGear(ctx);
ctx = await HeroSkillsCardV1.prepareAmmo(ctx);
ctx = await HeroSkillsCardV1.prepareSkills(ctx);
Logger.debug(`Context:`, ctx);
return ctx;
@ -125,7 +106,7 @@ export class SkillsCardV1 extends GenericAppMixin(HandlebarsApplicationMixin(Act
};
if (ctx.gear.length < limit) {
for (let i = ctx.gear.length; i < limit; i++) {
for (let i = ctx.gear.length - 1; i <= limit; i++) {
ctx.gear.push({
index: ctx.gear.length,
uuid: ``,
@ -139,24 +120,7 @@ export class SkillsCardV1 extends GenericAppMixin(HandlebarsApplicationMixin(Act
};
static async prepareAmmo(ctx) {
let total = 0;
let favouriteCount = 0;
ctx.favouriteAmmo = new Array(3).fill(null);
for (const ammo of ctx.actor.itemTypes.ammo) {
total += ammo.system.quantity;
if (favouriteCount < 3 && ammo.getFlag(game.system.id, ItemFlags.FAVOURITE)) {
ctx.favouriteAmmo[favouriteCount] = {
uuid: ammo.uuid,
name: ammo.name,
quantity: ammo.system.quantity,
};
favouriteCount++;
};
};
ctx.ammo = total;
ctx.ammo = 0;
return ctx;
};
@ -194,8 +158,7 @@ export class SkillsCardV1 extends GenericAppMixin(HandlebarsApplicationMixin(Act
const length = ctx.skills[ability].length;
if (length >= limit) {
ctx.skills[ability] = ctx.skills[ability].slice(0, limit);
}
else {
} else {
ctx.skills[ability] = ctx.skills[ability]
.concat(Array(limit - length).fill(null))
.slice(0, limit);

View file

@ -1,5 +1,4 @@
import { deleteItemFromElement, editItemFromElement } from "../utils.mjs";
import { DelveDiceHUD } from "../DelveDiceHUD.mjs";
import { filePath } from "../../consts.mjs";
import { gameTerms } from "../../gameTerms.mjs";
import { GenericAppMixin } from "../GenericApp.mjs";
@ -8,15 +7,15 @@ import { Logger } from "../../utils/Logger.mjs";
const { HandlebarsApplicationMixin } = foundry.applications.api;
const { ActorSheetV2 } = foundry.applications.sheets;
const { ContextMenu } = foundry.applications.ux;
const { ContextMenu } = foundry.applications.ui;
export class StatsCardV1 extends GenericAppMixin(HandlebarsApplicationMixin(ActorSheetV2)) {
export class HeroSummaryCardV1 extends GenericAppMixin(HandlebarsApplicationMixin(ActorSheetV2)) {
// #region Options
static DEFAULT_OPTIONS = {
classes: [
`ripcrypt--actor`,
`ripcrypt--StatsCardV1`,
`ripcrypt--HeroSummaryCardV1`,
],
position: {
width: `auto`,
@ -26,7 +25,6 @@ export class StatsCardV1 extends GenericAppMixin(HandlebarsApplicationMixin(Acto
resizable: false,
},
actions: {
rollForHaste: DelveDiceHUD.rollForHaste,
},
form: {
submitOnChange: true,
@ -36,7 +34,7 @@ export class StatsCardV1 extends GenericAppMixin(HandlebarsApplicationMixin(Acto
static PARTS = {
content: {
template: filePath(`templates/Apps/StatsCardV1/content.hbs`),
template: filePath(`templates/Apps/HeroSummaryCardV1/content.hbs`),
},
};
// #endregion
@ -44,7 +42,7 @@ export class StatsCardV1 extends GenericAppMixin(HandlebarsApplicationMixin(Acto
// #region Lifecycle
async _onRender(context, options) {
await super._onRender(context, options);
StatsCardV1._onRender.bind(this)(context, options);
HeroSummaryCardV1._onRender.bind(this)(context, options);
};
static async _onRender(context, options) {
@ -81,13 +79,13 @@ export class StatsCardV1 extends GenericAppMixin(HandlebarsApplicationMixin(Acto
ctx = await super._preparePartContext(partId, ctx, opts);
ctx.actor = this.document;
ctx = await StatsCardV1.prepareGuts(ctx);
ctx = await StatsCardV1.prepareWeapons(ctx);
ctx = await StatsCardV1.prepareArmor(ctx);
ctx = await StatsCardV1.prepareFatePath(ctx);
ctx = await StatsCardV1.prepareAbilityRow(ctx);
ctx = await StatsCardV1.prepareSpeed(ctx);
ctx = await StatsCardV1.prepareLevelData(ctx);
ctx = await HeroSummaryCardV1.prepareGuts(ctx);
ctx = await HeroSummaryCardV1.prepareWeapons(ctx);
ctx = await HeroSummaryCardV1.prepareArmor(ctx);
ctx = await HeroSummaryCardV1.prepareFatePath(ctx);
ctx = await HeroSummaryCardV1.prepareAbilityRow(ctx);
ctx = await HeroSummaryCardV1.prepareSpeed(ctx);
ctx = await HeroSummaryCardV1.prepareLevelData(ctx);
Logger.debug(`Context:`, ctx);
return ctx;

View file

@ -5,8 +5,7 @@ import { localizer } from "../utils/Localizer.mjs";
import { Logger } from "../utils/Logger.mjs";
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
const { ContextMenu } = foundry.applications.ux;
const { Roll } = foundry.dice;
const { ContextMenu } = foundry.applications.ui;
const { FatePath } = gameTerms;
const CompassRotations = {
@ -88,8 +87,7 @@ export class DelveDiceHUD extends HandlebarsApplicationMixin(ApplicationV2) {
const existing = document.getElementById(element.id);
if (existing) {
existing.replaceWith(element);
}
else {
} else {
const parent = document.getElementById(`ui-top`);
parent.prepend(element);
};
@ -191,7 +189,18 @@ export class DelveDiceHUD extends HandlebarsApplicationMixin(ApplicationV2) {
/** @this {DelveDiceHUD} */
static async #tourDelta(_event, element) {
const delta = parseInt(element.dataset.delta);
await this.sandsOfFateDelta(delta);
const initial = game.settings.get(`ripcrypt`, `sandsOfFateInitial`);
let newSands = this._sandsOfFate + delta;
if (newSands > initial) {
Logger.info(`Cannot go to a previous Delve Tour above the initial value`);
return;
};
if (newSands === 0) {
newSands = initial;
await this.alertCrypticEvent();
};
switch (Math.sign(delta)) {
case -1: {
@ -203,6 +212,9 @@ export class DelveDiceHUD extends HandlebarsApplicationMixin(ApplicationV2) {
break;
}
};
this.#animateSandsTo(newSands);
game.settings.set(`ripcrypt`, `sandsOfFate`, newSands);
};
/** @this {DelveDiceHUD} */
@ -229,77 +241,11 @@ export class DelveDiceHUD extends HandlebarsApplicationMixin(ApplicationV2) {
localizer(`RipCrypt.notifs.info.cryptic-event-alert`),
{ console: false },
);
game.socket.emit(`system.ripcrypt`, {
event: `notify`,
payload: {
message: `RipCrypt.notifs.info.cryptic-event-alert`,
type: `info`,
},
});
};
if ([`both`, `pause`].includes(alertType) && game.user.isGM) {
game.togglePause(true, { broadcast: true });
};
};
/**
* Changes the current Sands of Fate by an amount provided, animating the
* @param {number} delta The amount of change
*/
async sandsOfFateDelta(delta) {
const initial = game.settings.get(`ripcrypt`, `sandsOfFateInitial`);
let newSands = this._sandsOfFate + delta;
if (newSands > initial) {
Logger.info(`Cannot increase the Sands of Fate to a value about the initial`);
return;
};
if (newSands === 0) {
newSands = initial;
await this.alertCrypticEvent();
};
this.#animateSandsTo(newSands);
game.settings.set(`ripcrypt`, `sandsOfFate`, newSands);
};
/**
* A helper method that rolls the dice required for hasty turns while delving
* and adjusts the Sands of Fate accordingly
*/
static async rollForHaste() {
const shouldUpdateSands = game.settings.get(`ripcrypt`, `allowUpdateSandsSocket`);
if (shouldUpdateSands && game.users.activeGM == null) {
ui.notifications.error(localizer(`RipCrypt.notifs.error.no-active-gm`));
return;
};
const roll = new Roll(`1d8xo=1`);
await roll.evaluate();
let delta = 0;
if (roll.dice[0].results[0].exploded) {
delta = -1;
if (roll.dice[0].results[1].result === 1) {
delta = -2;
};
};
roll.toMessage({ flavor: `Haste Check` });
// Change the Sands of Fate setting if required
if (delta === 0 || !shouldUpdateSands) { return };
if (game.user.isActiveGM) {
ui.delveDice.sandsOfFateDelta(delta);
}
else {
game.socket.emit(`system.ripcrypt`, {
event: `updateSands`,
payload: { delta },
});
};
};
// #endregion
};

View file

@ -1,11 +1,10 @@
import { createItemFromElement, deleteItemFromElement, editItemFromElement, updateForeignDocumentFromEvent } from "./utils.mjs";
import { createItemFromElement, deleteItemFromElement, editItemFromElement } from "./utils.mjs";
import { DicePool } from "./DicePool.mjs";
import { RichEditor } from "./RichEditor.mjs";
import { toBoolean } from "../consts.mjs";
/**
* A mixin that takes the class from HandlebarsApplicationMixin and combines it
* with utility functions / data that is used across all RipCrypt applications
* A mixin that takes the class from HandlebarsApplicationMixin and
*/
export function GenericAppMixin(HandlebarsApp) {
class GenericRipCryptApp extends HandlebarsApp {
@ -32,13 +31,6 @@ export function GenericAppMixin(HandlebarsApp) {
};
// #endregion
// #region Instance Data
/** @type {Map<string, PopoverEventManager>} */
_popoverManagers = new Map();
/** @type {Map<number, string>} */
_hookIDs = new Map();
// #endregion
// #region Lifecycle
/**
* @override
@ -46,43 +38,13 @@ export function GenericAppMixin(HandlebarsApp) {
* top after being re-rendered as normal
*/
async render(options = {}, _options = {}) {
await super.render(options, _options);
super.render(options, _options);
const instance = foundry.applications.instances.get(this.id);
if (instance !== undefined && options.orBringToFront) {
instance.bringToFront();
};
};
/** @override */
async _onRender(...args) {
await super._onRender(...args);
/*
Rendering each of the popover managers associated with this app allows us
to have them be dynamic and update when their parent application is rerendered,
this could eventually be something we can move into the Document's apps
collection so Foundry auto-rerenders it, but because it isn't actually
associated with the Document (as it's dependendant on the Application), I
decided that it would be best to do my own handling for it.
*/
for (const manager of this._popoverManagers.values()) {
manager.render();
};
/*
Foreign update listeners so that we can easily update items that may not
be this document itself, but are useful to be able to be edited from this
sheet. Primarily useful for editing the Actors' Item collection, or an Items'
ActiveEffect collection.
*/
this.element.querySelectorAll(`input[data-foreign-update-on]`).forEach(el => {
const events = el.dataset.foreignUpdateOn.split(`,`);
for (const event of events) {
el.addEventListener(event, updateForeignDocumentFromEvent);
};
});
};
async _preparePartContext(partId, ctx, opts) {
ctx = await super._preparePartContext(partId, ctx, opts);
delete ctx.document;
@ -92,29 +54,12 @@ export function GenericAppMixin(HandlebarsApp) {
ctx.meta.idp = this.document?.uuid ?? this.id;
if (this.document) {
ctx.meta.limited = this.document.limited;
ctx.meta.editable = this.isEditable || game.user.isGM;
ctx.meta.embedded = this.document.isEmbedded;
};
ctx.meta.editable = ctx.editable;
}
delete ctx.editable;
return ctx;
};
_tearDown(options) {
// Clear all popovers associated with the app
for (const manager of this._popoverManagers.values()) {
manager.destroy();
};
this._popoverManagers.clear();
// Remove any hooks added for this app
for (const [id, hook] of this._hookIDs.entries()) {
Hooks.off(hook, id);
};
this._hookIDs.clear();
super._tearDown(options);
};
// #endregion
// #region Actions

View file

@ -61,7 +61,7 @@ export class AllItemSheetV1 extends GenericAppMixin(HandlebarsApplicationMixin(I
await super._processSubmitData(...args);
if (this.document.system.forceRerender) {
await this.render();
await this.render(false);
};
};
// #endregion

View file

@ -1,135 +0,0 @@
import { filePath } from "../../consts.mjs";
import { gameTerms } from "../../gameTerms.mjs";
import { GenericAppMixin } from "../GenericApp.mjs";
const { HandlebarsApplicationMixin } = foundry.applications.api;
const { ItemSheetV2 } = foundry.applications.sheets;
const { getProperty, hasProperty, setProperty } = foundry.utils;
export class ArmourSheet extends GenericAppMixin(HandlebarsApplicationMixin(ItemSheetV2)) {
// #region Options
static DEFAULT_OPTIONS = {
classes: [
`ripcrypt--item`,
`ArmourSheet`,
],
position: {
width: `auto`,
height: `auto`,
},
window: {
resizable: false,
},
form: {
submitOnChange: true,
closeOnSubmit: false,
},
};
static PARTS = {
header: {
template: filePath(`templates/Apps/partials/item-header.hbs`),
},
content: {
template: filePath(`templates/Apps/ArmourSheet/content.hbs`),
},
};
// #endregion
// #region Lifecycle
async _onRender() {
// remove the flag if it exists when we render the sheet
delete this.document?.system?.forceRerender;
};
/**
* Used to make it so that items that don't get updated because of the
* _preUpdate hook removing/changing the data submitted, can still get
* re-rendered when the diff is empty. If the document does get updated,
* this rerendering does not happen.
*
* @override
*/
async _processSubmitData(...args) {
await super._processSubmitData(...args);
if (this.document.system.forceRerender) {
await this.render();
};
};
/**
* Customize how form data is extracted into an expanded object.
* @param {SubmitEvent|null} event The originating form submission event
* @param {HTMLFormElement} form The form element that was submitted
* @param {FormDataExtended} formData Processed data for the submitted form
* @returns {object} An expanded object of processed form data
* @throws {Error} Subclasses may throw validation errors here to prevent form submission
* @protected
*/
_processFormData(event, form, formData) {
const data = super._processFormData(event, form, formData);
if (hasProperty(data, `system.location`)) {
let locations = getProperty(data, `system.location`);
locations = locations.filter(value => value != null);
setProperty(data, `system.location`, locations);
};
return data;
};
// #endregion
// #region Data Prep
async _preparePartContext(partId, _, opts) {
const ctx = await super._preparePartContext(partId, {}, opts);
ctx.item = this.document;
ctx.system = this.document.system;
switch (partId) {
case `content`: {
this._prepareContentContext(ctx, opts);
break;
};
};
return ctx;
};
async _prepareContentContext(ctx) {
ctx.weights = [
{
label: `RipCrypt.common.empty`,
value: null,
},
...Object.values(gameTerms.WeightRatings).map(opt => ({
label: `RipCrypt.common.weightRatings.${opt}`,
value: opt,
})),
];
ctx.accesses = [
{
label: `RipCrypt.common.empty`,
value: ``,
},
...gameTerms.Access.map(opt => ({
label: `RipCrypt.common.accessLevels.${opt}`,
value: opt,
})),
];
ctx.protects = {
head: this.document.system.location.has(gameTerms.Anatomy.HEAD),
body: this.document.system.location.has(gameTerms.Anatomy.BODY),
arms: this.document.system.location.has(gameTerms.Anatomy.ARMS),
legs: this.document.system.location.has(gameTerms.Anatomy.LEGS),
};
};
// #endregion
// #region Actions
// #endregion
};

View file

@ -1,56 +0,0 @@
import { filePath } from "../../consts.mjs";
import { StyledShadowElement } from "./mixins/StyledShadowElement.mjs";
const { renderTemplate } = foundry.applications.handlebars;
export class ArmourSummary extends StyledShadowElement(HTMLElement) {
static elementName = `armour-summary`;
static formAssociated = false;
/* Stuff for the mixin to use */
static _stylePath = `css/components/armour-summary.css`;
#container;
get type() {
return this.getAttribute(`type`) ?? `hero`;
};
set type(newValue) {
this.setAttribute(`type`, newValue);
};
_mounted = false;
async connectedCallback() {
super.connectedCallback();
if (this._mounted) { return };
/*
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);
};
};
this.#container = document.createElement(`div`);
this.#container.classList = `person`;
this.#container.innerHTML = await renderTemplate(
filePath(`templates/components/armour-summary.hbs`),
{ type: this.type },
);
this._shadow.appendChild(this.#container);
this._mounted = true;
};
disconnectedCallback() {
super.disconnectedCallback();
if (!this._mounted) { return };
this._mounted = false;
};
};

View file

@ -1,11 +1,9 @@
import { ArmourSummary } from "./ArmourSummary.mjs";
import { Logger } from "../../utils/Logger.mjs";
import { RipCryptBorder } from "./RipCryptBorder.mjs";
import { RipCryptIcon } from "./Icon.mjs";
import { RipCryptSVGLoader } from "./svgLoader.mjs";
const components = [
ArmourSummary,
RipCryptIcon,
RipCryptSVGLoader,
RipCryptBorder,

View file

@ -51,8 +51,7 @@ export function StyledShadowElement(Base) {
const stylePath = this.constructor._stylePath;
if (this.constructor._styles.has(stylePath)) {
this._style.innerHTML = this.constructor._styles.get(stylePath);
}
else {
} else {
fetch(`./systems/${game.system.id}/templates/${stylePath}`)
.then(r => r.text())
.then(t => {

View file

@ -1,96 +0,0 @@
import { filePath } from "../../consts.mjs";
import { GenericPopoverMixin } from "./GenericPopoverMixin.mjs";
import { ItemFlags } from "../../flags/item.mjs";
import { localizer } from "../../utils/Localizer.mjs";
import { Logger } from "../../utils/Logger.mjs";
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
export class AmmoTracker extends GenericPopoverMixin(HandlebarsApplicationMixin(ApplicationV2)) {
// #region Options
static DEFAULT_OPTIONS = {
classes: [
`ripcrypt`,
],
window: {
title: `RipCrypt.app-titles.AmmoTracker`,
contentClasses: [
`ripcrypt--AmmoTracker`,
],
},
actions: {
favourite: this.#favourite,
unfavourite: this.#unfavourite,
},
};
static PARTS = {
ammoList: {
template: filePath(`templates/Apps/popovers/AmmoTracker/ammoList.hbs`),
},
};
// #endregion
// #region Instance Data
_favouriteCount = 0;
// #endregion
// #region Lifecycle
async _preparePartContext(partId, data) {
const ctx = {
meta: { idp: this.id },
partId,
};
let favouriteCount = 0;
ctx.ammos = data.ammos.map(ammo => {
const favourite = ammo.getFlag(game.system.id, ItemFlags.FAVOURITE) ?? false;
if (favourite) { favouriteCount++ };
return {
ammo,
favourite,
};
});
this._favouriteCount = favouriteCount;
ctx.atFavouriteLimit = favouriteCount >= 3;
return ctx;
};
// #endregion
// #region Actions
static async #favourite(_, el) {
const targetEl = el.closest(`[data-item-id]`);
if (!targetEl) {
Logger.warn(`Cannot find a parent element with data-item-id`);
return;
};
if (this._favouriteCount > 3) {
ui.notifications.error(localizer(`RipCrypt.notifs.error.at-favourite-limit`));
return;
};
const data = targetEl.dataset;
const item = await fromUuid(data.itemId);
if (!item) { return };
item.setFlag(game.system.id, ItemFlags.FAVOURITE, true);
};
static async #unfavourite(_, el) {
const targetEl = el.closest(`[data-item-id]`);
if (!targetEl) {
Logger.warn(`Cannot find a parent element with data-item-id`);
return;
};
const data = targetEl.dataset;
const item = await fromUuid(data.itemId);
if (!item) { return };
item.unsetFlag(game.system.id, ItemFlags.FAVOURITE);
};
// #endregion
};

View file

@ -1,188 +0,0 @@
import { updateForeignDocumentFromEvent } from "../utils.mjs";
const { ApplicationV2 } = foundry.applications.api;
/**
* This mixin provides the ability to designate an Application as a "popover",
* which means that it will spawn near the x/y coordinates provided it won't
* overflow the bounds of the screen. This also implements a _preparePartContext
* in order to allow the parent application passing new data into the popover
* whenever it rerenders; how the popover handles this data is up to the
* specific implementation.
*/
export function GenericPopoverMixin(HandlebarsApp) {
class GenericRipCryptPopover extends HandlebarsApp {
static DEFAULT_OPTIONS = {
id: `popover-{id}`,
classes: [
`popover`,
],
window: {
frame: false,
positioned: true,
resizable: false,
minimizable: false,
},
actions: {},
};
popover = {};
constructor({ popover, ...options}) {
// For when the caller doesn't provide anything, we want this to behave
// like a normal Application instance.
popover.framed ??= true;
popover.locked ??= false;
if (popover.framed) {
options.window ??= {};
options.window.frame = true;
options.window.minimizable = true;
}
options.classes ??= [];
options.classes.push(popover.framed ? `framed` : `frameless`);
super(options);
this.popover = popover;
};
toggleLock() {
this.popover.locked = !this.popover.locked;
this.classList.toggle(`locked`, this.popover.locked);
};
/**
* This render utility is intended in order to make the popovers able to be
* used in both framed and frameless mode, making sure that the content classes
* from the framed mode get shunted onto the frameless Application's root
* element.
*/
async _onFirstRender(...args) {
await super._onFirstRender(...args);
const hasContentClasses = this.options?.window?.contentClasses?.length > 0;
if (!this.popover.framed && hasContentClasses) {
this.classList.add(...this.options.window.contentClasses);
};
};
async _onRender(...args) {
await super._onRender(...args);
/*
Foreign update listeners so that we can easily update items that may not
be this document itself, but are useful to be able to be edited from this
sheet. Primarily useful for editing the Actors' Item collection, or an Items'
ActiveEffect collection.
*/
this.element.querySelectorAll(`input[data-foreign-update-on]`).forEach(el => {
const events = el.dataset.foreignUpdateOn.split(`,`);
for (const event of events) {
el.addEventListener(event, updateForeignDocumentFromEvent);
};
});
};
async close(options = {}) {
// prevent locked popovers from being closed
if (this.popover.locked && !options.force) { return };
if (!this.popover.framed) {
options.animate = false;
};
return super.close(options);
};
/**
* @override
* Custom implementation in order to make it show up approximately where I
* want it to when being created.
*
* Most of this implementation is identical to the ApplicationV2
* implementation, the biggest difference is how targetLeft and targetTop
* are calculated.
*/
_updatePosition(position) {
if (!this.element) { return position };
if (this.popover.framed) { return super._updatePosition(position) };
const el = this.element;
let {width, height, left, top, scale} = position;
scale ??= 1.0;
const computedStyle = getComputedStyle(el);
let minWidth = ApplicationV2.parseCSSDimension(computedStyle.minWidth, el.parentElement.offsetWidth) || 0;
let maxWidth = ApplicationV2.parseCSSDimension(computedStyle.maxWidth, el.parentElement.offsetWidth) || Infinity;
let minHeight = ApplicationV2.parseCSSDimension(computedStyle.minHeight, el.parentElement.offsetHeight) || 0;
let maxHeight = ApplicationV2.parseCSSDimension(computedStyle.maxHeight, el.parentElement.offsetHeight) || Infinity;
let bounds = el.getBoundingClientRect();
const {clientWidth, clientHeight} = document.documentElement;
// Explicit width
const autoWidth = width === `auto`;
if ( !autoWidth ) {
const targetWidth = Number(width || bounds.width);
minWidth = parseInt(minWidth) || 0;
maxWidth = parseInt(maxWidth) || (clientWidth / scale);
width = Math.clamp(targetWidth, minWidth, maxWidth);
}
// Explicit height
const autoHeight = height === `auto`;
if ( !autoHeight ) {
const targetHeight = Number(height || bounds.height);
minHeight = parseInt(minHeight) || 0;
maxHeight = parseInt(maxHeight) || (clientHeight / scale);
height = Math.clamp(targetHeight, minHeight, maxHeight);
}
// Implicit height
if ( autoHeight ) {
Object.assign(el.style, {width: `${width}px`, height: ``});
bounds = el.getBoundingClientRect();
height = bounds.height;
}
// Implicit width
if ( autoWidth ) {
Object.assign(el.style, {height: `${height}px`, width: ``});
bounds = el.getBoundingClientRect();
width = bounds.width;
}
// Left Offset
const scaledWidth = width * scale;
const targetLeft = left ?? (this.popover.x - Math.floor( scaledWidth / 2 ));
const maxLeft = Math.max(clientWidth - scaledWidth, 0);
left = Math.clamp(targetLeft, 0, maxLeft);
// Top Offset
const scaledHeight = height * scale;
const targetTop = top ?? (this.popover.y - scaledHeight);
const maxTop = Math.max(clientHeight - scaledHeight, 0);
top = Math.clamp(targetTop, 0, maxTop);
// Scale
scale ??= 1.0;
return {
width: autoWidth ? `auto` : width,
height: autoHeight ? `auto` : height,
left,
top,
scale,
};
};
/**
* This is here in order allow things that are not this Application
* to provide / augment the context data for the lifecycle of the app.
*/
async _prepareContext(_partId, _context, options) {
const context = {};
Hooks.callAll(`prepare${this.constructor.name}Context`, context, options);
Hooks.callAll(`prepare${this.popover.managerId}Context`, context, options);
return context;
};
};
return GenericRipCryptPopover;
};

View file

@ -1,38 +1,6 @@
const { CombatTracker } = foundry.applications.sidebar.tabs;
function createButtonInnerHTML() {
const whoFirst = game.settings.get(`ripcrypt`, `whoFirst`);
let icon = `evil`;
let ariaLabel = `Geists go first, click to make heroes go first`;
if (whoFirst === `friendly`) {
icon = `hero`;
ariaLabel = `Heroes go first, click to make geists go first`;
};
return `<rc-icon
name="icons/${icon}"
var:fill="currentColor"
aria-label="${ariaLabel}"
></rc-icon>`;
};
function createButtonTooltip() {
const whoFirst = game.settings.get(`ripcrypt`, `whoFirst`);
if (whoFirst === `friendly`) {
return `Heroes currently go first`;
};
return `Geists currently go first`;
};
export class RipCryptCombatTracker extends CombatTracker {
static DEFAULT_OPTIONS = {
actions: {
toggleFirst: this.#toggleFirst,
},
};
/**
* Changes the way the combat tracker renders combatant rows to account for
* multiple combatants being in the same combat "group", thus all going at the
@ -60,30 +28,10 @@ export class RipCryptCombatTracker extends CombatTracker {
async _onRender(...args) {
await super._onRender(...args);
const spacer = document.createElement(`div`);
spacer.classList.add(`spacer`);
const button = document.createElement(`button`);
button.classList.add(`inline-control`, `combat-control`, `icon`);
button.type = `button`;
button.dataset.tooltip = createButtonTooltip();
button.dataset.action = `toggleFirst`;
button.innerHTML = createButtonInnerHTML();
button.disabled = !game.user.isGM;
// Purge the combat controls that I don't want to exist because they don't
// make sense in the system.
this.element?.querySelector(`[data-action="rollNPC"]`)?.replaceWith(spacer.cloneNode(true));
this.element?.querySelector(`[data-action="rollAll"]`)?.replaceWith(button.cloneNode(true));
};
static async #toggleFirst(_event, element) {
game.tooltip.deactivate();
const whoFirst = game.settings.get(`ripcrypt`, `whoFirst`);
const otherFirst = whoFirst === `friendly` ? `hostile` : `friendly`;
await game.settings.set(`ripcrypt`, `whoFirst`, otherFirst);
element.innerHTML = createButtonInnerHTML();
element.dataset.tooltip = createButtonTooltip();
game.tooltip.activate(element);
this.element?.querySelector(`[data-action="resetAll"]`)?.remove();
this.element?.querySelector(`[data-action="rollNPC"]`)?.remove();
this.element?.querySelector(`[data-action="rollAll"]`)?.remove();
};
};

View file

@ -11,7 +11,7 @@ export async function createItemFromElement(target, { parent } = {}) {
const type = data.defaultItemType;
await Item.createDialog(
{ type },
{ parent, showEquipPrompt: false },
{ parent },
{
types,
folders: [],
@ -40,26 +40,5 @@ export async function deleteItemFromElement(target) {
const itemId = itemEl.dataset.itemId;
if (!itemId) { return };
const item = await fromUuid(itemId);
item.deleteDialog();
};
/**
* Updates a document using the UUID, expects there to be the following
* dataset attributes:
* - "data-foreign-uuid" : The UUID of the document to update
* - "data-foreign-name" : The dot-separated path of the value to update
*
* @param {Event} event
*/
export async function updateForeignDocumentFromEvent(event) {
const target = event.currentTarget;
const data = target.dataset;
const document = await fromUuid(data.foreignUuid);
let value = target.value;
switch (target.type) {
case `checkbox`: value = target.checked; break;
};
await document?.update({ [data.foreignName]: value });
item.delete();
};

View file

@ -1,18 +1,13 @@
// App imports
import { AmmoTracker } from "./Apps/popovers/AmmoTracker.mjs";
import { CombinedHeroSheet } from "./Apps/ActorSheets/CombinedHeroSheet.mjs";
import { DicePool } from "./Apps/DicePool.mjs";
import { HeroSkillsCardV1 } from "./Apps/ActorSheets/HeroSkillsCardV1.mjs";
import { HeroSummaryCardV1 } from "./Apps/ActorSheets/HeroSummaryCardV1.mjs";
import { RichEditor } from "./Apps/RichEditor.mjs";
import { SkillsCardV1 } from "./Apps/ActorSheets/SkillsCardV1.mjs";
import { StatsCardV1 } from "./Apps/ActorSheets/StatsCardV1.mjs";
// Util imports
import { distanceBetweenFates, nextFate, previousFate } from "./utils/fates.mjs";
import { documentSorter } from "./consts.mjs";
import { rankToInteger } from "./utils/rank.mjs";
// Misc Imports
import { ItemFlags } from "./flags/item.mjs";
const { deepFreeze } = foundry.utils;
@ -22,11 +17,10 @@ Object.defineProperty(
{
value: deepFreeze({
Apps: {
AmmoTracker,
DicePool,
CombinedHeroSheet,
StatsCardV1,
SkillsCardV1,
HeroSummaryCardV1,
HeroSkillsCardV1,
RichEditor,
},
utils: {
@ -34,9 +28,7 @@ Object.defineProperty(
distanceBetweenFates,
nextFate,
previousFate,
rankToInteger,
},
ItemFlags,
}),
writable: false,
},

View file

@ -42,11 +42,9 @@ export function toBoolean(val) {
export function documentSorter(a, b) {
if (!a && !b) {
return 0;
}
else if (!a) {
} else if (!a) {
return 1;
}
else if (!b) {
} else if (!b) {
return -1;
};
@ -56,14 +54,3 @@ export function documentSorter(a, b) {
};
return Math.sign(a.name.localeCompare(b.name));
};
// MARK: getTooltipDelay
/**
* Retrieves the configured minimum delay between the user hovering an element
* and a tooltip showing up. Used for the pseudo-tooltip Applications that I use.
*
* @returns The number of milliseconds for the timeout
*/
export function getTooltipDelay() {
return game.tooltip.constructor.TOOLTIP_ACTIVATION_MS;
};

View file

@ -1,201 +0,0 @@
import { derivedMaximumBar } from "../helpers.mjs";
import { gameTerms } from "../../gameTerms.mjs";
import { rankToInteger } from "../../utils/rank.mjs";
import { sumReduce } from "../../utils/sumReduce.mjs";
const { fields } = foundry.data;
export class EntityData extends foundry.abstract.TypeDataModel {
// MARK: Token Attrs
static get trackableAttributes() {
return {
bar: [
`guts`,
],
value: [
`ability.grit`,
`ability.gait`,
`ability.grip`,
`ability.glim`,
`level.glory`,
`level.step`,
`level.rank`,
`coin.gold`,
`coin.silver`,
`coin.copper`,
],
};
};
// MARK: Schema
static defineSchema() {
return {
ability: new fields.SchemaField({
grit: new fields.NumberField({
min: 0,
initial: 1,
integer: true,
required: true,
nullable: false,
}),
gait: new fields.NumberField({
min: 0,
initial: 1,
integer: true,
required: true,
nullable: false,
}),
grip: new fields.NumberField({
min: 0,
initial: 1,
integer: true,
required: true,
nullable: false,
}),
glim: new fields.NumberField({
min: 0,
initial: 1,
integer: true,
required: true,
nullable: false,
}),
}),
guts: derivedMaximumBar(0, 5),
coin: new fields.SchemaField({
gold: new fields.NumberField({
initial: 5,
integer: true,
required: true,
nullable: false,
}),
silver: new fields.NumberField({
initial: 0,
integer: true,
required: true,
nullable: false,
}),
copper: new fields.NumberField({
initial: 0,
integer: true,
required: true,
nullable: false,
}),
}),
fate: new fields.StringField({
initial: ``,
blank: true,
trim: true,
nullable: false,
choices: () => {
return Object.values(gameTerms.FatePath).concat(``);
},
}),
level: new fields.SchemaField({
glory: new fields.NumberField({
min: 0,
initial: 0,
integer: true,
required: true,
nullable: false,
}),
step: new fields.NumberField({
min: 1,
initial: 1,
max: 3,
integer: true,
required: true,
nullable: false,
}),
rank: new fields.StringField({
initial: gameTerms.Rank.NOVICE,
required: true,
nullable: false,
blank: false,
trim: true,
choices: Object.values(gameTerms.Rank),
}),
}),
};
};
// MARK: Base Data
prepareBaseData() {
super.prepareBaseData();
// Calculate the person's base Crafting aura
const rank = rankToInteger(this.level.rank);
this.aura = {
normal: ( rank + 1 ) * 2,
heavy: ( rank + 2 ) * 2,
};
this.guts.max = 0;
// The limitations imposed on things like inventory spaces and equipped
// weapon count
this.limit = {
weapons: 4,
equipment: 12,
skills: 4,
};
};
// MARK: Derived Data
prepareDerivedData() {
super.prepareDerivedData();
this.guts.max += Object.values(this.ability).reduce(sumReduce);
// Movement speeds
this.speed = {
move: this.ability.gait + 3,
run: (this.ability.gait + 3) * 2,
};
};
// #region Getters
get equippedWeapons() {
const weapons = this.parent.itemTypes.weapon;
return weapons.filter(w => w.system.equipped);
};
get equippedArmour() {
const armours = this.parent.itemTypes.armour;
const slots = Object.fromEntries(
Object.values(gameTerms.Anatomy).map(v => [v, null]),
);
for (const armour of armours) {
if (!armour.system.equipped) { continue };
for (const locationTag of [...armour.system.location.values()]) {
const location = locationTag.toLowerCase();
slots[location] = armour;
};
};
return slots;
};
get equippedShield() {
const shields = this.parent.itemTypes.shield;
return shields.find(item => item.system.equipped);
};
get defense() {
const defenses = {};
const armour = this.equippedArmour;
for (const slot in armour) {
defenses[slot] = armour[slot]?.system.protection ?? 0;
};
const shield = this.equippedShield;
if (shield) {
for (const location of [...shield.system.location.values()]) {
const slot = location.toLowerCase();
defenses[slot] += shield.system.protection;
};
};
return defenses;
};
// #endregion
};

View file

@ -1,3 +0,0 @@
import { EntityData } from "./Entity.mjs";
export class GeistData extends EntityData {};

View file

@ -1,3 +1,188 @@
import { EntityData } from "./Entity.mjs";
import { derivedMaximumBar } from "../helpers.mjs";
import { gameTerms } from "../../gameTerms.mjs";
import { sumReduce } from "../../utils/sumReduce.mjs";
export class HeroData extends EntityData {};
const { fields } = foundry.data;
export class HeroData extends foundry.abstract.TypeDataModel {
// MARK: Token Attrs
static get trackableAttributes() {
return {
bar: [
`guts`,
],
value: [
`ability.grit`,
`ability.gait`,
`ability.grip`,
`ability.glim`,
`level.glory`,
`level.step`,
`level.rank`,
`coin.gold`,
`coin.silver`,
`coin.copper`,
],
};
};
// MARK: Schema
static defineSchema() {
return {
ability: new fields.SchemaField({
grit: new fields.NumberField({
min: 0,
initial: 1,
integer: true,
required: true,
nullable: false,
}),
gait: new fields.NumberField({
min: 0,
initial: 1,
integer: true,
required: true,
nullable: false,
}),
grip: new fields.NumberField({
min: 0,
initial: 1,
integer: true,
required: true,
nullable: false,
}),
glim: new fields.NumberField({
min: 0,
initial: 1,
integer: true,
required: true,
nullable: false,
}),
}),
guts: derivedMaximumBar(0, 5),
coin: new fields.SchemaField({
gold: new fields.NumberField({
initial: 5,
integer: true,
required: true,
nullable: false,
}),
silver: new fields.NumberField({
initial: 0,
integer: true,
required: true,
nullable: false,
}),
copper: new fields.NumberField({
initial: 0,
integer: true,
required: true,
nullable: false,
}),
}),
fate: new fields.StringField({
initial: ``,
blank: true,
trim: true,
nullable: false,
choices: () => {
return Object.values(gameTerms.FatePath).concat(``);
},
}),
level: new fields.SchemaField({
glory: new fields.NumberField({
min: 0,
initial: 0,
integer: true,
required: true,
nullable: false,
}),
step: new fields.NumberField({
min: 1,
initial: 1,
max: 3,
integer: true,
required: true,
nullable: false,
}),
rank: new fields.StringField({
initial: gameTerms.Rank.NOVICE,
required: true,
nullable: false,
blank: false,
trim: true,
choices: Object.values(gameTerms.Rank),
}),
}),
};
};
// MARK: Base Data
prepareBaseData() {
super.prepareBaseData();
this.guts.max = 0;
// The limitations imposed on things like inventory spaces and equipped
// weapon count
this.limit = {
weapons: 4,
equipment: 12,
skills: 4,
};
};
// MARK: Derived Data
prepareDerivedData() {
super.prepareDerivedData();
this.guts.max += Object.values(this.ability).reduce(sumReduce);
// Movement speeds
this.speed = {
move: this.ability.gait + 3,
run: (this.ability.gait + 3) * 2,
};
};
// #region Getters
get equippedArmour() {
const armours = this.parent.itemTypes.armour;
const slots = Object.fromEntries(
Object.values(gameTerms.Anatomy).map(v => [v, null]),
);
for (const armour of armours) {
if (!armour.system.equipped) { continue };
for (const locationTag of [...armour.system.location.values()]) {
const location = locationTag.toLowerCase();
slots[location] = armour;
};
};
return slots;
};
get equippedShield() {
const shields = this.parent.itemTypes.shield;
return shields.find(item => item.system.equipped);
};
get defense() {
const defenses = {};
const armour = this.equippedArmour;
for (const slot in armour) {
defenses[slot] = armour[slot]?.system.protection ?? 0;
};
const shield = this.equippedShield;
if (shield) {
for (const location of [...shield.system.location.values()]) {
const slot = location.toLowerCase();
defenses[slot] += shield.system.protection;
};
};
return defenses;
};
// #endregion
};

View file

@ -0,0 +1,71 @@
import { Logger } from "../../utils/Logger.mjs";
const { fields } = foundry.data;
const { RegionBehaviorType } = foundry.data.regionBehaviors;
export class DifficultyDeltaBehaviorData extends RegionBehaviorType {
static LOCALIZATION_PREFIXES = [`RipCrypt.region.difficultyDelta`];
static defineSchema() {
return {
delta: new fields.NumberField({
required: true,
initial: 1,
}),
};
};
static events = {
[CONST.REGION_EVENTS.TOKEN_ENTER]: this.#onTokenEnter,
[CONST.REGION_EVENTS.TOKEN_EXIT]: this.#onTokenExit,
};
static async #onTokenEnter(event) {
Logger.debug(`token enter`, event, this);
const actor = event.data.token.actor;
// const token = event.data.token.object;
// Logger.debug(token.center, token.h)
if (!actor) { return };
let delta = actor.getFlag(game.system.id, `dcDelta`) ?? 0;
delta += this.delta;
actor.setFlag(game.system.id, `dcDelta`, delta);
ui.notifications.info(`Updated delta to: ${delta}`);
Logger.debug(`Updated delta to:`, delta);
// await canvas.interface.createScrollingText(
// token.center,
// delta,
// {
// distance: 2 * token.h,
// fontSize: 40,
// fill: `#aa0000`,
// }
// );
};
static async #onTokenExit(event) {
Logger.debug(`token exit`, event, this);
const actor = event.data.token.actor;
// const token = event.data.token.object;
if (!actor) { return };
let delta = actor.getFlag(game.system.id, `dcDelta`) ?? 0;
delta -= this.delta;
if (delta === 0) {
actor.unsetFlag(game.system.id, `dcDelta`);
} else {
actor.setFlag(game.system.id, `dcDelta`, delta);
};
ui.notifications.info(`Updated delta to: ${delta}`);
Logger.debug(`Updated delta to:`, delta);
// await canvas.interface.createScrollingText(
// token.center,
// delta,
// {
// distance: 2 * token.h,
// fontSize: 40,
// fill: `#00aa00`,
// }
// );
};
};

View file

@ -26,6 +26,14 @@ export class AmmoData extends CommonItemData {
value: this.quantity,
min: 0,
},
{
id: `cost`,
type: `cost`,
label: `RipCrypt.common.cost`,
gold: this.cost.gold,
silver: this.cost.silver,
copper: this.cost.copper,
},
{
id: `access`,
type: `dropdown`,
@ -44,14 +52,6 @@ export class AmmoData extends CommonItemData {
})),
],
},
{
id: `cost`,
type: `cost`,
label: `RipCrypt.common.cost`,
gold: this.cost.gold,
silver: this.cost.silver,
copper: this.cost.copper,
},
];
return fields;
};

View file

@ -4,13 +4,12 @@ import { localizer } from "../../utils/Localizer.mjs";
import { Logger } from "../../utils/Logger.mjs";
import { requiredInteger } from "../helpers.mjs";
const { diffObject, getProperty, setProperty } = foundry.utils;
const { DialogV2 } = foundry.applications.api;
const { fields } = foundry.data;
const { hasProperty, diffObject, mergeObject } = foundry.utils;
/** Used for Armour and Shields */
export class ArmourData extends CommonItemData {
// #region Schema
// MARK: Schema
static defineSchema() {
return {
...super.defineSchema(),
@ -20,16 +19,16 @@ export class ArmourData extends CommonItemData {
blank: false,
trim: true,
nullable: false,
required: true,
options: Object.values(gameTerms.Anatomy),
}),
{
nullable: false,
initial: [],
required: true,
},
),
equipped: new fields.BooleanField({
initial: false,
required: true,
nullable: false,
}),
weight: new fields.StringField({
@ -40,30 +39,27 @@ export class ArmourData extends CommonItemData {
}),
};
};
// #endregion Schema
// #region Lifecycle
async _preCreate(item, options) {
const showEquipPrompt = options.showEquipPrompt ?? true;
if (showEquipPrompt && this.parent.isEmbedded && this._canEquip()) {
const shouldEquip = await DialogV2.confirm({
window: { title: `Equip Item?` },
content: `Do you want to equip ${item.name}?`,
});
if (shouldEquip) {
this.updateSource({ "equipped": true });
};
};
// MARK: Base Data
prepareBaseData() {
super.prepareBaseData();
};
// MARK: Derived Data
prepareDerivedData() {
super.prepareDerivedData();
};
// #region Lifecycle
async _preUpdate(changes, options, user) {
// return false
if (options.force && game.settings.get(`ripcrypt`, `devMode`)) { return };
// Ensure changes is a diffed object
const diff = diffObject(this.parent._source, changes);
let valid = await super._preUpdate(changes, options, user);
if (getProperty(diff, `system.equipped`) && !this._canEquip()) {
if (hasProperty(diff, `system.equipped`) && !this._canEquip()) {
ui.notifications.error(
localizer(
`RipCrypt.notifs.error.cannot-equip`,
@ -73,7 +69,9 @@ export class ArmourData extends CommonItemData {
);
// Don't stop the update, but don't allow changing the equipped status
setProperty(changes, `system.equipped`, false);
mergeObject(changes, {
"system.equipped": false,
});
// Set a flag so that we can tell the sheet that it needs to rerender
this.forceRerender = true;
@ -81,13 +79,8 @@ export class ArmourData extends CommonItemData {
return valid;
};
// #endregion Lifecycle
// #region Helpers
/**
* Used to tell the preUpdate logic whether or not to prevent the item from
* being equipped or not.
*/
/** Used to tell the preUpdate logic whether or not to prevent the */
_canEquip() {
const parent = this.parent;
if (!parent.isEmbedded || !(parent.parent instanceof Actor)) {
@ -101,7 +94,7 @@ export class ArmourData extends CommonItemData {
};
const slots = parent.parent.system.equippedArmour ?? {};
Logger.debug(`slots`, slots);
for (const locationTag of this.location) {
if (slots[locationTag.toLowerCase()] != null) {
Logger.error(`Unable to equip multiple items in the same slot`);
@ -110,9 +103,89 @@ export class ArmourData extends CommonItemData {
};
return true;
};
// #endregion
// #region Getters
get locationString() {
return [...this.location].join(`, `);
};
// #endregion Helpers
// #endregion
// #region Sheet Data
getFormFields(_ctx) {
const fields = [
{
id: `quantity`,
type: `integer`,
label: `RipCrypt.common.quantity`,
path: `system.quantity`,
value: this.quantity,
min: 0,
},
{
id: `access`,
type: `dropdown`,
label: `RipCrypt.common.access`,
path: `system.access`,
value: this.access,
limited: false,
options: [
{
label: `RipCrypt.common.empty`,
value: ``,
},
...gameTerms.Access.map(opt => ({
label: `RipCrypt.common.accessLevels.${opt}`,
value: opt,
})),
],
},
{
id: `weight`,
type: `dropdown`,
label: `RipCrypt.common.weightRating`,
path: `system.weight`,
value: this.weight,
options: [
{
label: `RipCrypt.common.empty`,
value: null,
},
...Object.values(gameTerms.WeightRatings).map(opt => ({
label: `RipCrypt.common.weightRatings.${opt}`,
value: opt,
})),
],
},
{
id: `location`,
type: `string-set`,
label: `RipCrypt.common.location`,
placeholder: `RipCrypt.Apps.location-placeholder`,
path: `system.location`,
value: this.locationString,
},
{
id: `protection`,
type: `integer`,
label: `RipCrypt.common.protection`,
value: this.protection,
path: `system.protection`,
min: 0,
},
];
if (this.parent.isEmbedded) {
fields.push({
id: `equipped`,
type: `boolean`,
label: `RipCrypt.common.equipped`,
value: this.equipped,
path: `system.equipped`,
});
};
return fields;
};
// #endregion
};

View file

@ -59,14 +59,6 @@ export class GoodData extends CommonItemData {
})),
],
},
{
id: `cost`,
type: `cost`,
label: `RipCrypt.common.cost`,
gold: this.cost.gold,
silver: this.cost.silver,
copper: this.cost.copper,
},
{
id: `description`,
type: `prosemirror`,

View file

@ -2,14 +2,12 @@ import { barAttribute, optionalInteger, requiredInteger } from "../helpers.mjs";
import { CommonItemData } from "./Common.mjs";
import { gameTerms } from "../../gameTerms.mjs";
import { localizer } from "../../utils/Localizer.mjs";
import { Logger } from "../../utils/Logger.mjs";
const { diffObject, getProperty, setProperty } = foundry.utils;
const { DialogV2 } = foundry.applications.api;
const { fields } = foundry.data;
const { hasProperty, mergeObject } = foundry.utils;
export class WeaponData extends CommonItemData {
// #region Schema
// MARK: Schema
static defineSchema() {
return {
...super.defineSchema(),
@ -43,69 +41,39 @@ export class WeaponData extends CommonItemData {
}),
};
};
// #endregion Schema
// #region Lifecycle
async _preCreate(item, options) {
const showEquipPrompt = options.showEquipPrompt ?? true;
if (showEquipPrompt && this.parent.isEmbedded && this._canEquip()) {
const shouldEquip = await DialogV2.confirm({
window: { title: `Equip Item?` },
content: `Do you want to equip ${item.name}?`,
});
if (shouldEquip) {
this.updateSource({ "equipped": true });
};
};
// MARK: Base Data
prepareBaseData() {
super.prepareBaseData();
};
/**
*
* @param {*} changes The expanded object that was used for the update
* @param {*} options
* @param {*} user
* @returns
*/
// MARK: Derived Data
prepareDerivedData() {
super.prepareDerivedData();
};
// #region Lifecycle
async _preUpdate(changes, options, user) {
if (options.force && game.settings.get(`ripcrypt`, `devMode`)) { return };
const diff = diffObject(this.parent._source, changes);
let valid = super._preUpdate(changes, options, user);
if (getProperty(diff, `system.equipped`) && !this._canEquip()) {
if (hasProperty(changes, `system.equipped`) && !this.parent.isEmbedded) {
ui.notifications.error(localizer(
`RipCrypt.notifs.error.cannot-equip`,
`RipCrypt.notifs.error.cannot-equip-not-embedded`,
{ itemType: `@TYPES.Item.${this.parent.type}` },
));
// Don't stop the update, but don't allow changing the equipped status
setProperty(changes, `system.equipped`, false);
// Set a flag so that we can tell the sheet that it needs to rerender
this.forceRerender = true;
mergeObject(
changes,
{ "-=system.equipped": null },
{ inplace: true, performDeletions: true },
);
return false;
};
return valid;
};
// #endregion Lifecycle
// #region Helpers
/**
* Used to tell the preUpdate logic whether or not to prevent the item from
* being equipped or not.
*/
_canEquip() {
const parent = this.parent;
if (!parent.isEmbedded || !(parent.parent instanceof Actor)) {
Logger.error(`Unable to equip item when it's not embedded`);
return false;
};
const actor = this.parent.parent.system;
if (actor.equippedWeapons?.length >= actor.limit.weapons) {
return false;
};
return true;
};
// #endregion
// #region Getters
get traitString() {
return [...this.traits].join(`, `);
};
@ -116,7 +84,7 @@ export class WeaponData extends CommonItemData {
};
return String(this.range.short ?? this.range.long ?? ``);
};
// #endregion Helpers
// #endregion
// #region Sheet Data
async getFormFields(_ctx) {
@ -147,14 +115,6 @@ export class WeaponData extends CommonItemData {
})),
],
},
{
id: `cost`,
type: `cost`,
label: `RipCrypt.common.cost`,
gold: this.cost.gold,
silver: this.cost.silver,
copper: this.cost.copper,
},
{
id: `weight`,
type: `dropdown`,
@ -199,8 +159,7 @@ export class WeaponData extends CommonItemData {
value: this.range.long,
},
});
}
else {
} else {
fields.push({
id: `short-range`,
type: `integer`,
@ -258,5 +217,5 @@ export class WeaponData extends CommonItemData {
return fields;
};
// #endregion Sheet Data
// #endregion
};

View file

@ -38,8 +38,7 @@ export class CryptDie extends Die {
if (almostCrypted) {
this.ripCryptState = `crypted`;
break;
}
else {
} else {
almostCrypted = true;
}
}

View file

@ -16,8 +16,7 @@ export class RipCryptCombat extends Combat {
if (groups.has(groupKey)) {
groups.get(groupKey).push(combatant);
}
else {
} else {
groups.set(groupKey, [combatant]);
};
};
@ -130,8 +129,7 @@ export class RipCryptCombat extends Combat {
const currentToken = this.combatant?.token?._object;
if (!tokenGroup && currentToken) {
currentToken.renderFlags.set({refreshTurnMarker: true});
}
else {
} else {
const group = this.customGroups.get(tokenGroup) ?? [];
for (const combatant of group) {
combatant.token?._object?.renderFlags.set({ refreshTurnMarker: true });

View file

@ -28,8 +28,7 @@ export class RipCryptCombatant extends Combatant {
const disposition = this.disposition;
if (disposition === `unknown`) {
total += 0.25;
}
else if (whoFirst !== disposition) {
} else if (whoFirst !== disposition) {
total += 0.5;
};
};

View file

@ -26,8 +26,7 @@ export class RipCryptToken extends Token {
};
canvas.tokens.turnMarkers.add(this);
this.turnMarker.draw();
}
else if (this.turnMarker) {
} else if (this.turnMarker) {
canvas.tokens.turnMarkers.delete(this);
this.turnMarker.destroy();
this.turnMarker = null;

View file

@ -1,4 +0,0 @@
export const ItemFlags = Object.freeze({
/** The boolean value to indicate if an item is considered favourited/starred or not */
FAVOURITE: `favourited`,
});

View file

@ -37,7 +37,6 @@ export const gameTerms = Object.preventExtensions({
}),
/** The types of items that contribute to the gear limit */
gearItemTypes: new Set([
`ammo`,
`armour`,
`weapon`,
`shield`,

View file

@ -1,18 +1,16 @@
// Applications
import { AllItemSheetV1 } from "../Apps/ItemSheets/AllItemSheetV1.mjs";
import { ArmourSheet } from "../Apps/ItemSheets/ArmourSheet.mjs";
import { CombinedHeroSheet } from "../Apps/ActorSheets/CombinedHeroSheet.mjs";
import { CraftCardV1 } from "../Apps/ActorSheets/CraftCardV1.mjs";
import { DelveDiceHUD } from "../Apps/DelveDiceHUD.mjs";
import { HeroSkillsCardV1 } from "../Apps/ActorSheets/HeroSkillsCardV1.mjs";
import { HeroSummaryCardV1 } from "../Apps/ActorSheets/HeroSummaryCardV1.mjs";
import { RipCryptCombatTracker } from "../Apps/sidebar/CombatTracker.mjs";
import { SkillsCardV1 } from "../Apps/ActorSheets/SkillsCardV1.mjs";
import { StatsCardV1 } from "../Apps/ActorSheets/StatsCardV1.mjs";
// Data Models
import { AmmoData } from "../data/Item/Ammo.mjs";
import { ArmourData } from "../data/Item/Armour.mjs";
import { CraftData } from "../data/Item/Craft.mjs";
import { GeistData } from "../data/Actor/Geist.mjs";
import { DifficultyDeltaBehaviorData } from "../data/Behavior/DifficultyDelta.mjs";
import { GoodData } from "../data/Item/Good.mjs";
import { HeroData } from "../data/Actor/Hero.mjs";
import { ShieldData } from "../data/Item/Shield.mjs";
@ -31,15 +29,12 @@ import { RipCryptToken } from "../documents/token.mjs";
// Misc
import helpers from "../handlebarHelpers/_index.mjs";
import { Logger } from "../utils/Logger.mjs";
import { registerCustomComponents } from "../Apps/components/_index.mjs";
import { registerCustomComponents } from "../Apps/elements/_index.mjs";
import { registerDevSettings } from "../settings/devSettings.mjs";
import { registerMetaSettings } from "../settings/metaSettings.mjs";
import { registerSockets } from "../sockets/_index.mjs";
import { registerUserSettings } from "../settings/userSettings.mjs";
import { registerWorldSettings } from "../settings/worldSettings.mjs";
const { Items, Actors } = foundry.documents.collections;
Hooks.once(`init`, () => {
Logger.log(`Initializing`);
@ -55,14 +50,14 @@ Hooks.once(`init`, () => {
// #region Datamodels
CONFIG.Actor.dataModels.hero = HeroData;
CONFIG.Actor.dataModels.geist = GeistData;
CONFIG.Item.dataModels.ammo = AmmoData;
CONFIG.Item.dataModels.ammo = AmmoData,
CONFIG.Item.dataModels.armour = ArmourData;
CONFIG.Item.dataModels.craft = CraftData;
CONFIG.Item.dataModels.good = GoodData;
CONFIG.Item.dataModels.shield = ShieldData;
CONFIG.Item.dataModels.skill = SkillData;
CONFIG.Item.dataModels.weapon = WeaponData;
CONFIG.RegionBehavior.dataModels.difficultyDelta = DifficultyDeltaBehaviorData;
// #endregion
// #region Class Changes
@ -75,6 +70,12 @@ Hooks.once(`init`, () => {
// #endregion
// #region Sheets
// Unregister core sheets
/* eslint-disable no-undef */
Items.unregisterSheet(`core`, ItemSheet);
Actors.unregisterSheet(`core`, ActorSheet);
/* eslint-enabled no-undef */
// #region Actors
Actors.registerSheet(game.system.id, CombinedHeroSheet, {
makeDefault: true,
@ -82,26 +83,15 @@ Hooks.once(`init`, () => {
label: `RipCrypt.sheet-names.CombinedHeroSheet`,
themes: CombinedHeroSheet.themes,
});
Actors.registerSheet(game.system.id, StatsCardV1, {
Actors.registerSheet(game.system.id, HeroSummaryCardV1, {
types: [`hero`],
label: `RipCrypt.sheet-names.StatsCardV1`,
themes: StatsCardV1.themes,
label: `RipCrypt.sheet-names.HeroSummaryCardV1`,
themes: HeroSummaryCardV1.themes,
});
Actors.registerSheet(game.system.id, StatsCardV1, {
makeDefault: true,
types: [`geist`],
label: `RipCrypt.sheet-names.StatsCardV1`,
themes: StatsCardV1.themes,
});
Actors.registerSheet(game.system.id, SkillsCardV1, {
types: [`hero`, `geist`],
label: `RipCrypt.sheet-names.SkillsCardV1`,
themes: SkillsCardV1.themes,
});
Actors.registerSheet(game.system.id, CraftCardV1, {
types: [`hero`, `geist`],
label: `RipCrypt.sheet-names.CraftCardV1`,
themes: CraftCardV1.themes,
Actors.registerSheet(game.system.id, HeroSkillsCardV1, {
types: [`hero`],
label: `RipCrypt.sheet-names.HeroSkillsCardV1`,
themes: HeroSkillsCardV1.themes,
});
// #endregion
@ -111,16 +101,6 @@ Hooks.once(`init`, () => {
label: `RipCrypt.sheet-names.AllItemsSheetV1`,
themes: AllItemSheetV1.themes,
});
Items.registerSheet(game.system.id, ArmourSheet, {
makeDefault: true,
types: [`armour`, `shield`],
label: `RipCrypt.sheet-names.ArmourSheet`,
themes: ArmourSheet.themes,
});
Items.unregisterSheet(game.system.id, AllItemSheetV1, {
types: [`armour`, `shield`],
});
// #endregion
// #endregion
@ -128,7 +108,6 @@ Hooks.once(`init`, () => {
CONFIG.Actor.trackableAttributes.hero = HeroData.trackableAttributes;
// #endregion
registerSockets();
registerCustomComponents();
Handlebars.registerHelper(helpers);
});

View file

@ -6,18 +6,17 @@ Hooks.once(`ready`, () => {
let defaultTab = game.settings.get(`ripcrypt`, `defaultTab`);
if (defaultTab) {
try {
if (!ui.sidebar?.TABS?.[defaultTab]) {
Logger.error(`Couldn't find a sidebar tab with ID:`, defaultTab);
} else {
Logger.debug(`Switching sidebar tab to:`, defaultTab);
ui.sidebar.changeTab(defaultTab, `primary`);
}
catch {
Logger.error(`Failed to change to sidebar tab:`, defaultTab);
ui.sidebar.activateTab(defaultTab);
};
};
if (game.settings.get(`ripcrypt`, `devMode`)) {
ui.sidebar.expand();
if (game.paused) { game.togglePause(false, { broadcast: true }) };
if (game.paused) { game.togglePause() };
};
ui.delveDice.render({ force: true });
@ -29,7 +28,7 @@ Hooks.once(`ready`, () => {
combatConfig.turnMarker.src = filePath(`assets/turn-marker.png`);
combatConfig.turnMarker.animation = `spinPulse`;
game.settings.set(`core`, `combatTrackerConfig`, combatConfig);
};
}
game.settings.set(`ripcrypt`, `firstLoadFinished`, true);
});

View file

@ -7,7 +7,6 @@ export function registerMetaSettings() {
game.settings.register(`ripcrypt`, `dc`, {
scope: `world`,
type: Number,
default: 5,
config: false,
requiresReload: false,
onChange: () => {
@ -18,7 +17,7 @@ export function registerMetaSettings() {
game.settings.register(`ripcrypt`, `sandsOfFate`, {
scope: `world`,
type: Number,
default: 8,
initial: 8,
config: false,
requiresReload: false,
onChange: async () => {
@ -45,9 +44,9 @@ export function registerMetaSettings() {
type: String,
config: false,
requiresReload: false,
default: `friendly`,
initial: `friendly`,
onChange: async () => {
await game.combat?.setupTurns();
await game.combat.setupTurns();
await ui.combat.render({ parts: [ `tracker` ] });
},
});
@ -55,7 +54,7 @@ export function registerMetaSettings() {
game.settings.register(`ripcrypt`, `firstLoadFinished`, {
scope: `world`,
type: Boolean,
default: false,
initial: false,
requiresReload: false,
});
};

View file

@ -1,5 +1,4 @@
export function registerUserSettings() {
/* ! Non-Functional
game.settings.register(`ripcrypt`, `abbrAccess`, {
name: `RipCrypt.setting.abbrAccess.name`,
hint: `RipCrypt.setting.abbrAccess.hint`,
@ -9,7 +8,6 @@ export function registerUserSettings() {
default: false,
requiresReload: false,
});
*/
game.settings.register(`ripcrypt`, `condensedRange`, {
name: `RipCrypt.setting.condensedRange.name`,

View file

@ -39,14 +39,4 @@ export function registerWorldSettings() {
},
}),
});
game.settings.register(`ripcrypt`, `allowUpdateSandsSocket`, {
name: `RipCrypt.setting.allowUpdateSandsSocket.name`,
hint: `RipCrypt.setting.allowUpdateSandsSocket.hint`,
scope: `world`,
config: true,
requiresReload: false,
type: Boolean,
default: true,
});
};

View file

@ -1,29 +0,0 @@
import { localizer } from "../utils/Localizer.mjs";
import { Logger } from "../utils/Logger.mjs";
import { notify } from "./notify.mjs";
import { updateSands } from "./updateSands.mjs";
const events = {
notify,
updateSands,
};
export function registerSockets() {
Logger.info(`Setting up socket listener`);
game.socket.on(`system.ripcrypt`, (data, userID) => {
const { event, payload } = data ?? {};
if (event == null || payload === undefined) {
ui.notifications.error(localizer(`RipCrypt.notifs.error.invalid-socket`));
return;
};
if (events[event] == null) {
ui.notifications.error(localizer(`RipCrypt.notifs.error.unknown-socket-event`, { event }));
return;
};
const user = game.users.get(userID);
events[event](payload, user);
});
};

View file

@ -1,56 +0,0 @@
import { localizer } from "../utils/Localizer.mjs";
export function notify(payload) {
// #region Payload Validity
const {
message,
users = [],
type = `info`,
permanent = false,
} = payload;
if (!message) {
ui.notifications.error(localizer(
`RipCrypt.notifs.error.malformed-socket-payload`,
{
event: `notify`,
details: `A message must be provided`,
},
));
return;
};
if (users && !Array.isArray(users)) {
ui.notifications.error(localizer(
`RipCrypt.notifs.error.malformed-socket-payload`,
{
event: `notify`,
details: `"users" must be an array of user IDs`,
},
));
return;
};
if (![`info`, `error`, `success`].includes(type)) {
ui.notifications.error(localizer(
`RipCrypt.notifs.error.malformed-socket-payload`,
{
event: `notify`,
details: `An invalid notification type was provided.`,
},
));
return;
}
// #endregion Payload Validity
// Act
if (users.length === 0 || users.includes(game.user.id)) {
ui.notifications[type]?.(
localizer(message),
{
console: false,
permanent,
},
);
};
};

View file

@ -1,38 +0,0 @@
import { clamp } from "../utils/clamp.mjs";
import { localizer } from "../utils/Localizer.mjs";
export function updateSands(payload) {
if (!game.user.isActiveGM) { return };
if (!game.settings.get(game.system.id, `allowUpdateSandsSocket`)) { return };
// Assert payload validity
const { value, delta } = payload;
if (value == null && delta == null) {
ui.notifications.error(localizer(
`RipCrypt.notifs.error.malformed-socket-payload`,
{
event: `updateSands`,
details: `Either value or delta must be provided`,
},
));
return;
};
// Take action
if (value != null) {
const initial = game.settings.get(game.system.id, `sandsOfFateInitial`);
let sands = clamp(0, value, initial);
if (sands === 0) {
ui.delveDice.alertCrypticEvent();
sands = initial;
};
game.settings.set(
game.system.id,
`sandsOfFate`,
sands,
);
}
else if (delta != null) {
ui.delveDice.sandsOfFateDelta(delta);
};
};

View file

@ -1,184 +0,0 @@
import { getTooltipDelay } from "../consts.mjs";
import { Logger } from "./Logger.mjs";
export class PopoverEventManager {
#options;
#id;
get id() {
return this.#id;
};
/** @type {Map<string, PopoverEventManager>} */
static #existing = new Map();
/**
* @param {HTMLElement} element The element to attach the listeners to.
* @param {GenericPopoverMixin} popoverClass The class reference that represents the popover app
*/
constructor(id, element, popoverClass, options = {}) {
id = `${id}-${popoverClass.name}`;
this.#id = id;
if (PopoverEventManager.#existing.has(id)) {
const manager = PopoverEventManager.#existing.get(id);
manager.#addListeners(element);
return manager;
};
options.managerId = id;
options.locked ??= false;
options.lockable ??= true;
this.#options = options;
this.#element = element;
this.#class = popoverClass;
this.#addListeners(element);
PopoverEventManager.#existing.set(id, this);
};
/**
* @param {HTMLElement} element
*/
#addListeners(element) {
element.addEventListener(`pointerenter`, this.#pointerEnterHandler.bind(this));
element.addEventListener(`pointerout`, this.#pointerOutHandler.bind(this));
element.addEventListener(`click`, this.#clickHandler.bind(this));
if (this.#options.lockable) {
element.addEventListener(`pointerup`, this.#pointerUpHandler.bind(this));
};
};
destroy() {
this.close();
this.#element.removeEventListener(`pointerenter`, this.#pointerEnterHandler);
this.#element.removeEventListener(`pointerout`, this.#pointerOutHandler);
this.#element.removeEventListener(`click`, this.#clickHandler);
if (this.#options.lockable) {
this.#element.removeEventListener(`pointerup`, this.#pointerUpHandler);
};
this.#stopOpen();
this.#stopClose();
};
close() {
this.#frameless?.close({ force: true });
this.#framed?.close({ force: true });
};
#stopOpen() {
if (this.#openTimeout != null) {
clearTimeout(this.#openTimeout);
this.#openTimeout = null;
};
};
#stopClose() {
if (this.#closeTimeout != null) {
clearTimeout(this.#closeTimeout);
this.#closeTimeout = null;
}
};
get rendered() {
return Boolean(this.#frameless?.rendered || this.#framed?.rendered);
};
render(options) {
if (this.#framed?.rendered) {
this.#framed.render(options);
};
if (this.#frameless?.rendered) {
this.#frameless.render(options);
};
};
#element;
#class;
#openTimeout = null;
#closeTimeout = null;
#frameless;
#framed;
#construct(options) {
options.popover ??= {};
options.popover.managerId = this.#id;
return new this.#class(options);
};
#clickHandler() {
Logger.debug(`click event handler`);
// Cleanup for the frameless lifecycle
this.#stopOpen();
this.#stopClose();
this.#frameless?.close({ force: true });
if (!this.#framed) {
this.#framed = this.#construct({ popover: { ...this.#options, framed: true } });
}
this.#framed?.render({ force: true });
};
#pointerEnterHandler(event) {
this.#stopClose();
const pos = event.target.getBoundingClientRect();
const x = pos.x + Math.floor(pos.width / 2);
const y = pos.y;
this.#openTimeout = setTimeout(
() => {
this.#openTimeout = null;
// When we have the framed version rendered, we might as well just focus
// it instead of rendering a new application
if (this.#framed?.rendered) {
this.#framed.bringToFront();
return;
};
// When the frameless is already rendered, we should just move it to the
// new location instead of spawning a new one
if (this.#frameless?.rendered) {
const { width, height } = this.#frameless.element.getBoundingClientRect();
const top = y - height;
const left = x - Math.floor(width / 2);
this.#frameless.setPosition({ left, top });
return;
}
this.#frameless = this.#construct({
popover: {
...this.#options,
framed: false,
x, y,
},
});
this.#frameless?.render({ force: true });
},
getTooltipDelay(),
);
};
#pointerOutHandler() {
this.#stopOpen();
this.#closeTimeout = setTimeout(
() => {
this.#closeTimeout = null;
this.#frameless?.close();
},
getTooltipDelay(),
);
};
#pointerUpHandler(event) {
if (event.button !== 1 || !this.#frameless?.rendered || Tour.tourInProgress) { return };
event.preventDefault();
this.#frameless.toggleLock();
};
};

View file

@ -1,3 +0,0 @@
export function clamp(min, ideal, max) {
return Math.max(min, Math.min(ideal, max));
};

View file

@ -1,13 +0,0 @@
import { gameTerms } from "../gameTerms.mjs";
/**
* Converts a rank's name into an integer form for use in mathematical calculations
* that rely on rank.
*
* @param {Novice|Adept|Expert|Master} rankName The rank to convert into an integer
* @returns An integer between 1 and 4
*/
export function rankToInteger(rankName) {
return Object.values(gameTerms.Rank)
.findIndex(r => r === rankName) + 1;
};

14
package-lock.json generated
View file

@ -8,7 +8,6 @@
"@eslint/js": "^9.16.0",
"@foundryvtt/foundryvtt-cli": "^1.0.3",
"@stylistic/eslint-plugin": "^2.12.0",
"dotenv": "^17.2.3",
"eslint": "^9.16.0"
}
},
@ -776,19 +775,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/dotenv": {
"version": "17.2.3",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz",
"integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/dunder-proto": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.0.tgz",

View file

@ -3,13 +3,9 @@
"@eslint/js": "^9.16.0",
"@foundryvtt/foundryvtt-cli": "^1.0.3",
"@stylistic/eslint-plugin": "^2.12.0",
"dotenv": "^17.2.3",
"eslint": "^9.16.0"
},
"scripts": {
"data:build": "node scripts/buildCompendia.mjs",
"data:extract": "node scripts/extractCompendia.mjs",
"link": "node scripts/linkFoundry.mjs",
"lint": "eslint --fix",
"lint:nofix": "eslint"
}

View file

@ -1,23 +0,0 @@
{
"type": "Item",
"folder": null,
"name": "Armour",
"color": "#04262a",
"sorting": "m",
"_id": "pZxc6QLgVWfnZlf7",
"description": "",
"sort": 0,
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759994081362,
"modifiedTime": 1759994081362,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!folders!pZxc6QLgVWfnZlf7"
}

View file

@ -1,41 +0,0 @@
{
"folder": "BsNUpCnwmlhOWBhZ",
"name": "Breastplate",
"type": "armour",
"_id": "KQ6uyTPUOHuMTxDF",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 90,
"copper": null
},
"protection": 3,
"location": [
"body"
],
"equipped": false,
"weight": "heavy",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759994459142,
"modifiedTime": 1759994468351,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!KQ6uyTPUOHuMTxDF"
}

View file

@ -1,21 +0,0 @@
{
"type": "Item",
"folder": "pZxc6QLgVWfnZlf7",
"name": "Heavy",
"color": "#06393f",
"sorting": "a",
"_id": "BsNUpCnwmlhOWBhZ",
"description": "",
"sort": 0,
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"lastModifiedBy": null
},
"_key": "!folders!BsNUpCnwmlhOWBhZ"
}

View file

@ -1,44 +0,0 @@
{
"folder": "RXPJBkzVxFnoT3Tm",
"name": "Heavy Shields",
"type": "shield",
"_id": "uUrCwjxV6Ihisb6V",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 50,
"copper": null
},
"protection": 1,
"location": [
"head",
"body",
"arms",
"legs"
],
"equipped": false,
"weight": "heavy",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759994801184,
"modifiedTime": 1759994810086,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!uUrCwjxV6Ihisb6V"
}

View file

@ -1,41 +0,0 @@
{
"folder": "HRwiz1c1ZcQyPu4z",
"name": "Leather Cap",
"type": "armour",
"_id": "JMkV8kMnCXhW5KDh",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 10,
"copper": null
},
"protection": 1,
"location": [
"head"
],
"equipped": false,
"weight": "light",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759994118194,
"modifiedTime": 1759994130845,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!JMkV8kMnCXhW5KDh"
}

View file

@ -1,41 +0,0 @@
{
"folder": "HRwiz1c1ZcQyPu4z",
"name": "Leather, Hide Bracers",
"type": "armour",
"_id": "nz4DXXR4iU9CeMRA",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 10,
"copper": null
},
"protection": 1,
"location": [
"arms"
],
"equipped": false,
"weight": "light",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759994170968,
"modifiedTime": 1759994180395,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!nz4DXXR4iU9CeMRA"
}

View file

@ -1,41 +0,0 @@
{
"folder": "HRwiz1c1ZcQyPu4z",
"name": "Leather, Hide Jacket",
"type": "armour",
"_id": "zMyxSJ6VpaH3ddOO",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 20,
"copper": null
},
"protection": 1,
"location": [
"body"
],
"equipped": false,
"weight": "light",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759994151324,
"modifiedTime": 1759994160761,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!zMyxSJ6VpaH3ddOO"
}

View file

@ -1,41 +0,0 @@
{
"folder": "HRwiz1c1ZcQyPu4z",
"name": "Leather, Hide Leggings",
"type": "armour",
"_id": "14Omu9q2sMxW8GWB",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 20,
"copper": null
},
"protection": 1,
"location": [
"legs"
],
"equipped": false,
"weight": "light",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759994190989,
"modifiedTime": 1759994198011,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!14Omu9q2sMxW8GWB"
}

View file

@ -1,21 +0,0 @@
{
"type": "Item",
"folder": "pZxc6QLgVWfnZlf7",
"name": "Light",
"color": "#06393f",
"sorting": "a",
"_id": "HRwiz1c1ZcQyPu4z",
"description": "",
"sort": 0,
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"lastModifiedBy": null
},
"_key": "!folders!HRwiz1c1ZcQyPu4z"
}

View file

@ -1,42 +0,0 @@
{
"folder": "RXPJBkzVxFnoT3Tm",
"name": "Light Shields",
"type": "shield",
"_id": "a6vPAa25z8L9t79K",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 20,
"copper": null
},
"protection": 1,
"location": [
"head",
"arms"
],
"equipped": false,
"weight": "light",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759994549164,
"modifiedTime": 1759994761998,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!a6vPAa25z8L9t79K"
}

View file

@ -1,42 +0,0 @@
{
"folder": "cKN149ZGLqfyt0oi",
"name": "Mail, Link, Scale Coat",
"type": "armour",
"_id": "Sr40RFsPr2M0bTKK",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 180,
"copper": null
},
"protection": 2,
"location": [
"body",
"arms"
],
"equipped": false,
"weight": "modest",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759994257751,
"modifiedTime": 1759994294312,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!Sr40RFsPr2M0bTKK"
}

View file

@ -1,41 +0,0 @@
{
"folder": "cKN149ZGLqfyt0oi",
"name": "Mail, Link, Scale Coif",
"type": "armour",
"_id": "HfG5Doxf7576Jgbt",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 180,
"copper": null
},
"protection": 2,
"location": [
"head"
],
"equipped": false,
"weight": "modest",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759994210701,
"modifiedTime": 1759994221462,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!HfG5Doxf7576Jgbt"
}

View file

@ -1,41 +0,0 @@
{
"folder": "cKN149ZGLqfyt0oi",
"name": "Mail, Link, Scale Leggings",
"type": "armour",
"_id": "YBpElIVQ534pm3Mf",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 200,
"copper": null
},
"protection": 2,
"location": [
"legs"
],
"equipped": false,
"weight": null,
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759994280754,
"modifiedTime": 1759994424980,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!YBpElIVQ534pm3Mf"
}

View file

@ -1,41 +0,0 @@
{
"folder": "cKN149ZGLqfyt0oi",
"name": "Mail, Link, Scale Shirt",
"type": "armour",
"_id": "wab6Bo8ngar4mBCN",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 90,
"copper": null
},
"protection": 2,
"location": [
"body"
],
"equipped": false,
"weight": "modest",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759994235204,
"modifiedTime": 1759994246578,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!wab6Bo8ngar4mBCN"
}

View file

@ -1,43 +0,0 @@
{
"folder": "RXPJBkzVxFnoT3Tm",
"name": "Modest Shields",
"type": "shield",
"_id": "fyL8LZ8jpEQbjpM2",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 30,
"copper": null
},
"protection": 1,
"location": [
"head",
"body",
"arms"
],
"equipped": false,
"weight": "modest",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759994777609,
"modifiedTime": 1759994784898,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!fyL8LZ8jpEQbjpM2"
}

View file

@ -1,21 +0,0 @@
{
"type": "Item",
"folder": "pZxc6QLgVWfnZlf7",
"name": "Modest",
"color": "#06393f",
"sorting": "a",
"_id": "cKN149ZGLqfyt0oi",
"description": "",
"sort": 0,
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"lastModifiedBy": null
},
"_key": "!folders!cKN149ZGLqfyt0oi"
}

View file

@ -1,42 +0,0 @@
{
"folder": "BsNUpCnwmlhOWBhZ",
"name": "Plate Bracers",
"type": "armour",
"_id": "e8JRJn5Blw3UrvnW",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 180,
"copper": null
},
"protection": 1,
"location": [
"body",
"arms"
],
"equipped": false,
"weight": "heavy",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759994478040,
"modifiedTime": 1759994486947,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!e8JRJn5Blw3UrvnW"
}

View file

@ -1,41 +0,0 @@
{
"folder": "BsNUpCnwmlhOWBhZ",
"name": "Plate Leggings",
"type": "armour",
"_id": "v1y4RKGad2IXOu5e",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 200,
"copper": null
},
"protection": 3,
"location": [
"legs"
],
"equipped": false,
"weight": "heavy",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759994497119,
"modifiedTime": 1759994506514,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!v1y4RKGad2IXOu5e"
}

View file

@ -1,41 +0,0 @@
{
"folder": "BsNUpCnwmlhOWBhZ",
"name": "Ring Coif, Helm",
"type": "armour",
"_id": "Z4NTsrX63JNjjZ8Z",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 440,
"copper": null
},
"protection": 3,
"location": [
"head"
],
"equipped": false,
"weight": "heavy",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759994438779,
"modifiedTime": 1759994448846,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!Z4NTsrX63JNjjZ8Z"
}

View file

@ -1,23 +0,0 @@
{
"type": "Item",
"folder": null,
"name": "Shields",
"color": "#04262a",
"sorting": "m",
"_id": "RXPJBkzVxFnoT3Tm",
"description": "",
"sort": 0,
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759994538745,
"modifiedTime": 1759994538745,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!folders!RXPJBkzVxFnoT3Tm"
}

View file

@ -1,23 +0,0 @@
{
"type": "Item",
"folder": null,
"name": "Ammo",
"color": "#04262a",
"sorting": "a",
"_id": "gvNPXXRBx2eGIzcU",
"description": "",
"sort": 0,
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759993925940,
"modifiedTime": 1759993925940,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!folders!gvNPXXRBx2eGIzcU"
}

View file

@ -1,47 +0,0 @@
{
"folder": "dBAI76CApXH8qqjx",
"name": "Arming Sword",
"type": "weapon",
"_id": "xXUItaoHTQ2QiaX4",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 80,
"copper": null
},
"traits": [],
"range": {
"short": null,
"long": null
},
"damage": 2,
"wear": {
"value": 4,
"max": 4
},
"equipped": false,
"weight": "modest",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759992689272,
"modifiedTime": 1759992708712,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!xXUItaoHTQ2QiaX4"
}

View file

@ -1,35 +0,0 @@
{
"folder": "gvNPXXRBx2eGIzcU",
"name": "Arrow",
"type": "ammo",
"_id": "gN9JbmouUI7eOrSj",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 12,
"cost": {
"gold": null,
"silver": 3,
"copper": null
},
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759993937035,
"modifiedTime": 1759993944077,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!gN9JbmouUI7eOrSj"
}

View file

@ -1,49 +0,0 @@
{
"folder": "mmd8siMKSLyOeILo",
"name": "Axe, Hammer, Pick",
"type": "weapon",
"_id": "cr35WzuPGDojuOuJ",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 10,
"copper": null
},
"traits": [
"Thrown"
],
"range": {
"short": null,
"long": null
},
"damage": 0,
"wear": {
"value": 2,
"max": 2
},
"equipped": false,
"weight": "light",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759991980652,
"modifiedTime": 1759992328546,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!cr35WzuPGDojuOuJ"
}

View file

@ -1,47 +0,0 @@
{
"folder": "dBAI76CApXH8qqjx",
"name": "Battleaxe, Warhammer",
"type": "weapon",
"_id": "otIFI9TIDPWnT3cq",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 40,
"copper": null
},
"traits": [],
"range": {
"short": null,
"long": null
},
"damage": 3,
"wear": {
"value": 3,
"max": 3
},
"equipped": false,
"weight": "modest",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759992730036,
"modifiedTime": 1759992748112,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!otIFI9TIDPWnT3cq"
}

View file

@ -1,35 +0,0 @@
{
"folder": "gvNPXXRBx2eGIzcU",
"name": "Black Powder",
"type": "ammo",
"_id": "c86ht86Z9vOEBtNH",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 12,
"cost": {
"gold": null,
"silver": 40,
"copper": null
},
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759994005587,
"modifiedTime": 1759994010244,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!c86ht86Z9vOEBtNH"
}

View file

@ -1,35 +0,0 @@
{
"folder": "gvNPXXRBx2eGIzcU",
"name": "Blowgun Darts",
"type": "ammo",
"_id": "FvtiEaQhJumPsCwb",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 12,
"cost": {
"gold": null,
"silver": 2,
"copper": null
},
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759993963869,
"modifiedTime": 1759993969644,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!FvtiEaQhJumPsCwb"
}

View file

@ -1,49 +0,0 @@
{
"folder": "vPyj2cK1j66Zyrul",
"name": "Blowgun, Sling",
"type": "weapon",
"_id": "VrG2xer1quhjwUag",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 10,
"copper": null
},
"traits": [
"Ammo"
],
"range": {
"short": 5,
"long": 10
},
"damage": 1,
"wear": {
"value": 1,
"max": 1
},
"equipped": false,
"weight": "light",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759993449825,
"modifiedTime": 1759993468278,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!VrG2xer1quhjwUag"
}

View file

@ -1,47 +0,0 @@
{
"folder": "dBAI76CApXH8qqjx",
"name": "Broadsword",
"type": "weapon",
"_id": "I9QaJTU6O2E9WzUS",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 100,
"copper": null
},
"traits": [],
"range": {
"short": null,
"long": null
},
"damage": 3,
"wear": {
"value": 3,
"max": 3
},
"equipped": false,
"weight": "modest",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759992797603,
"modifiedTime": 1759992815028,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!I9QaJTU6O2E9WzUS"
}

View file

@ -1,47 +0,0 @@
{
"folder": "mmd8siMKSLyOeILo",
"name": "Club",
"type": "weapon",
"_id": "NlDJVbXeXRfoCZWp",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 2,
"copper": null
},
"traits": [],
"range": {
"short": null,
"long": null
},
"damage": 1,
"wear": {
"value": 2,
"max": 2
},
"equipped": false,
"weight": "light",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759992392474,
"modifiedTime": 1759992619109,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!NlDJVbXeXRfoCZWp"
}

View file

@ -1,49 +0,0 @@
{
"folder": "8NNF9jBjpmPpmw1B",
"name": "Crossbow",
"type": "weapon",
"_id": "BNoYUrlpDk6oBeJt",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 90,
"copper": null
},
"traits": [
"Reload"
],
"range": {
"short": 10,
"long": 25
},
"damage": 3,
"wear": {
"value": 3,
"max": 3
},
"equipped": false,
"weight": "modest",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759993621847,
"modifiedTime": 1759993694978,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!BNoYUrlpDk6oBeJt"
}

View file

@ -1,35 +0,0 @@
{
"folder": "gvNPXXRBx2eGIzcU",
"name": "Crossbow Bolts",
"type": "ammo",
"_id": "7cmLLV6o2pPAyyAg",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 12,
"cost": {
"gold": null,
"silver": 3,
"copper": null
},
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759993977460,
"modifiedTime": 1759993982911,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!7cmLLV6o2pPAyyAg"
}

View file

@ -1,49 +0,0 @@
{
"folder": "dBAI76CApXH8qqjx",
"name": "Cutlass, Saber, Scimitar",
"type": "weapon",
"_id": "kSWrbdKdYIRxkWka",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 120,
"copper": null
},
"traits": [
"Agile"
],
"range": {
"short": null,
"long": null
},
"damage": 2,
"wear": {
"value": 2,
"max": 2
},
"equipped": false,
"weight": "modest",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759992861725,
"modifiedTime": 1759992882913,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!kSWrbdKdYIRxkWka"
}

View file

@ -1,50 +0,0 @@
{
"folder": "mmd8siMKSLyOeILo",
"name": "Dagger",
"type": "weapon",
"_id": "q8z2HptFaPmeHU9n",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 1,
"cost": {
"gold": null,
"silver": 20,
"copper": null
},
"traits": [
"Agile",
"Thrown"
],
"range": {
"short": null,
"long": null
},
"damage": 1,
"wear": {
"value": 1,
"max": 1
},
"equipped": false,
"weight": "light",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759992606052,
"modifiedTime": 1759992639628,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!q8z2HptFaPmeHU9n"
}

View file

@ -1,49 +0,0 @@
{
"folder": "vPyj2cK1j66Zyrul",
"name": "Darts",
"type": "weapon",
"_id": "rDxS6EJzg2zvSpxR",
"img": "icons/svg/item-bag.svg",
"system": {
"quantity": 6,
"cost": {
"gold": null,
"silver": 30,
"copper": null
},
"traits": [
"Thrown"
],
"range": {
"short": 3,
"long": 6
},
"damage": 1,
"wear": {
"value": 1,
"max": 1
},
"equipped": false,
"weight": "light",
"access": ""
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"9x9FgB0YTeCJJUDK": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.350",
"systemId": "ripcrypt",
"systemVersion": "0.2.0",
"createdTime": 1759993477954,
"modifiedTime": 1759993498878,
"lastModifiedBy": "9x9FgB0YTeCJJUDK"
},
"_key": "!items!rDxS6EJzg2zvSpxR"
}

Some files were not shown because too many files have changed in this diff Show more