mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-06-06 21:04:16 +02:00
Compare commits
296 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4428fd5be | ||
|
|
6312a171e2 | ||
|
|
3527fd7959 | ||
|
|
f0a7539018 | ||
|
|
5be79f4ab8 | ||
|
|
2fc5b01f09 | ||
|
|
52b81de11f | ||
|
|
c0c9095847 | ||
|
|
5ac4fc3b9c | ||
|
|
6747be49b2 | ||
|
|
77c5cfcbb7 | ||
|
|
5dbcd94480 | ||
|
|
d98a7c951e | ||
|
|
3c36c5747d | ||
|
|
bcf274f1d0 | ||
|
|
df4a2c5d57 | ||
|
|
646ebc8bdf | ||
|
|
6448666579 | ||
|
|
d0c29ede56 | ||
|
|
98ce49b928 | ||
|
|
318d00b47d | ||
|
|
c8d0df87c8 | ||
|
|
983f48b415 | ||
|
|
bfd483698b | ||
|
|
3eb33a71af | ||
|
|
3fbc1e97c6 | ||
|
|
729e8bca42 | ||
|
|
53f15a7fde | ||
|
|
c23ac61ee5 | ||
|
|
d3141059ac | ||
|
|
61db7ca371 | ||
|
|
2bc1c04c93 | ||
|
|
493998cc95 | ||
|
|
251d7e4e13 | ||
|
|
a209b035c8 | ||
|
|
9487b07e43 | ||
|
|
f1a530f57f | ||
|
|
ddf4747310 | ||
|
|
ac72012387 | ||
|
|
1ab8170d2f | ||
|
|
48f9ffc318 | ||
|
|
fa6f9d56b8 | ||
|
|
c2f8b34ef2 | ||
|
|
de0ab9d047 | ||
|
|
b9416ead5a | ||
|
|
ccc4186e42 | ||
|
|
e529dd0f88 | ||
|
|
0e8c3dc74a | ||
|
|
58824d5bbf | ||
|
|
e095587305 | ||
|
|
f7f1bdce2b | ||
|
|
e4a3f105dc | ||
|
|
2931377d53 | ||
|
|
53e8da77c6 | ||
|
|
bae9006f64 | ||
|
|
273f666784 | ||
|
|
d782b25254 | ||
|
|
f4c21a6a1b | ||
|
|
da06381748 | ||
|
|
ed53d9ed4c | ||
|
|
b631525b6e | ||
|
|
b145f515d0 | ||
|
|
b23095cb2f | ||
|
|
2f589c1b8e | ||
|
|
10a608a1a5 | ||
|
|
6a2d09caac | ||
|
|
4504379fcf | ||
|
|
d152bfc906 | ||
|
|
b91d943dd1 | ||
|
|
ac5f84fff7 | ||
|
|
d78c6b1183 | ||
|
|
98049bd76b | ||
|
|
ab412367f9 | ||
|
|
0492507bd1 | ||
|
|
47960fdd61 | ||
|
|
d372f3df9b | ||
|
|
e6c27926d0 | ||
|
|
88e64531b4 | ||
|
|
dd2aa10871 | ||
|
|
855f4549ec | ||
|
|
6b4de71a0a | ||
|
|
46e552eb3d | ||
|
|
bc3c09fa2e | ||
|
|
829a6161ff | ||
|
|
24993970da | ||
|
|
d86ab2053c | ||
|
|
24813e7e4f | ||
|
|
abd7824c96 | ||
|
|
80e314ca84 | ||
|
|
4064701c16 | ||
|
|
e8828b70db | ||
|
|
9ef4929693 | ||
|
|
40804f3339 | ||
|
|
b7bc452bf5 | ||
|
|
fb5e3672dc | ||
|
|
cca468e8af | ||
|
|
94852cec21 | ||
|
|
0128106de6 | ||
|
|
2ffe678503 | ||
|
|
e95ea3c281 | ||
|
|
c91d53b4d4 | ||
|
|
85ca7efc6d | ||
|
|
edbf5aa55f | ||
|
|
54d1b2bdc0 | ||
|
|
516928f92b | ||
|
|
94e93222a4 | ||
|
|
4685ec3c77 | ||
|
|
4558fbdcf6 | ||
|
|
c7159eff11 | ||
|
|
d0c2c783f1 | ||
|
|
905d1f7e88 | ||
|
|
b22ce9697d | ||
|
|
404640a0a3 | ||
|
|
20056cd950 | ||
|
|
118c52a996 | ||
|
|
ca32aa5d35 | ||
|
|
1cece731ee | ||
|
|
b186f22cc7 | ||
|
|
047e77154a | ||
|
|
53f77972e3 | ||
|
|
cc822856e5 | ||
|
|
6d09c5504d | ||
|
|
c82bcbeb01 | ||
|
|
d0afee59d8 | ||
|
|
4d17a7d9bf | ||
|
|
b8e00b2807 | ||
|
|
e5ae56f45c | ||
|
|
ccb0073cef | ||
|
|
4a60c56462 | ||
|
|
84afec31a7 | ||
|
|
da11510e02 | ||
|
|
f45b1210c7 | ||
|
|
41829bc9d5 | ||
|
|
d73760fc39 | ||
|
|
276aee4747 | ||
|
|
fae05c24a8 | ||
|
|
e6d5a2f7d3 | ||
|
|
545934aa60 | ||
|
|
7a4f9d7bc8 | ||
|
|
3eda3c4c05 | ||
|
|
646e0debbd | ||
|
|
3cbc18f42b | ||
|
|
f850cbda76 | ||
|
|
f2ec5ef458 | ||
|
|
c683bc4352 | ||
|
|
fa04c9920f | ||
|
|
03110377e1 | ||
|
|
1fea8438ba | ||
|
|
4944722139 | ||
|
|
4b92001f97 | ||
|
|
2fde61a1d5 | ||
|
|
d9b322406d | ||
|
|
16c07d23bb | ||
|
|
91aff8b10d | ||
|
|
7e9385bc39 | ||
|
|
aa8771bf0d | ||
|
|
7d5cdeb09d | ||
|
|
8808e4646d | ||
|
|
a77d2088a0 | ||
|
|
c6335980ba | ||
|
|
1176328f62 | ||
| a62d28cd96 | |||
|
|
8d8dea81fe | ||
|
|
fb07938e54 | ||
|
|
c337338c8b | ||
|
|
f900011510 | ||
|
|
56a6613a73 | ||
|
|
e003db3ec1 | ||
|
|
66c90d69e3 | ||
|
|
a839ca0066 | ||
|
|
6ed975f5b7 | ||
|
|
e2c97a7b61 | ||
|
|
3ec013ff50 | ||
|
|
94f1fbdd9b | ||
|
|
f22b67367b | ||
|
|
8a0b1b8e22 | ||
|
|
1a57b55723 | ||
|
|
f910cf9795 | ||
|
|
6804bfe047 | ||
|
|
28d9254883 | ||
|
|
b076c2481b | ||
|
|
bdfc97bb3b | ||
|
|
e111f7c2ae | ||
|
|
bb179db758 | ||
|
|
bbc1781d01 | ||
|
|
a897037dc4 | ||
|
|
97636fa134 | ||
|
|
e7be2a7d2b | ||
|
|
9bea8d6a97 | ||
|
|
7ca420ae0e | ||
|
|
ae480157d1 | ||
|
|
b505e15eb2 | ||
|
|
087e69694c | ||
|
|
fad830580c | ||
|
|
4c2d31b2f4 | ||
|
|
67d142df3d | ||
|
|
fdfd8c5a8d | ||
|
|
dbcef140a2 | ||
|
|
90f4339898 | ||
|
|
0d7469801e | ||
|
|
70e21f34db | ||
|
|
7057504a9e | ||
|
|
331f1ebf75 | ||
|
|
f91c140d34 | ||
|
|
3a117ef117 | ||
|
|
01619ef067 | ||
|
|
622b38ac08 | ||
|
|
02cca277da | ||
|
|
3265613767 | ||
|
|
36ffe8a23a | ||
|
|
96eba49dc1 | ||
|
|
f92f9f7132 | ||
|
|
0747994686 | ||
|
|
56dc9afe8f | ||
|
|
0f1ac406df | ||
|
|
e8ac3012ad | ||
|
|
582a15be77 | ||
|
|
7fa03c58e0 | ||
|
|
1df248925e | ||
|
|
2a55e317fc | ||
|
|
2b8e4cb2fa | ||
|
|
e3b433cce9 | ||
|
|
29734c5fb5 | ||
|
|
25264c26e9 | ||
|
|
d284bd7398 | ||
|
|
259b66236c | ||
|
|
f156b12d79 | ||
|
|
dbd5ef8bb0 | ||
|
|
e2b13d6717 | ||
|
|
e8f052faf3 | ||
|
|
740216ada2 | ||
|
|
24d22dde59 | ||
|
|
2a294684d4 | ||
|
|
c730cc3d4d | ||
|
|
e3c4f1ce9f | ||
|
|
7f8e3fee6e | ||
|
|
d79c236cfe | ||
|
|
d7ce388cad | ||
|
|
8d8fa983ef | ||
|
|
394d1d338d | ||
|
|
4319fbabb9 | ||
|
|
e3e1395de6 | ||
|
|
931217577a | ||
|
|
aa1d117c43 | ||
|
|
64ce615116 | ||
|
|
f119daff07 | ||
|
|
7a7940aa04 | ||
|
|
3d25ceeb4a | ||
|
|
2de0b490e9 | ||
|
|
de801924e6 | ||
|
|
ef53a7c561 | ||
|
|
a3f515cf6d | ||
|
|
461247b285 | ||
|
|
b3e9c3fd9f | ||
|
|
15fc879f9b | ||
|
|
d5244eedbf | ||
|
|
ad8caabf71 | ||
|
|
3031531b14 | ||
|
|
a7eda31aec | ||
|
|
e77b927a75 | ||
|
|
37b088fe7d | ||
|
|
7c0ab25e10 | ||
|
|
1160f51347 | ||
|
|
92bcaf4962 | ||
|
|
fb4ddf227c | ||
|
|
b87e630a0a | ||
|
|
ee3bbaec53 | ||
|
|
5c8d16e100 | ||
|
|
fcadf119b7 | ||
|
|
5a4bbc91f5 | ||
|
|
d3ebd30e59 | ||
|
|
c2807e0676 | ||
|
|
876e496d24 | ||
|
|
1ca866f87b | ||
|
|
37c53ad74e | ||
|
|
bcb30a6ff7 | ||
|
|
4aab5d315a | ||
|
|
e2eb31c12e | ||
|
|
9b63371f4a | ||
|
|
063ff3d999 | ||
|
|
593105b163 | ||
|
|
115a31423e | ||
|
|
ac998adaa6 | ||
|
|
6a0a8d8d6e | ||
|
|
c17020c2c8 | ||
|
|
578b090f08 | ||
|
|
57e51ee841 | ||
|
|
da368f3df5 | ||
|
|
307af5b990 | ||
|
|
ae91d6786f | ||
|
|
cd52aa8f9c | ||
|
|
9553f3387f | ||
|
|
4c51bb5899 | ||
|
|
2c36da8433 | ||
|
|
6e2d700945 | ||
|
|
1a928e950c |
1081 changed files with 22797 additions and 13327 deletions
|
|
@ -1,3 +1,6 @@
|
||||||
[*]
|
[*]
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
indent_style = spaces
|
indent_style = spaces
|
||||||
|
end_of_line = lf
|
||||||
|
[*.yml]
|
||||||
|
indent_size = 2
|
||||||
|
|
|
||||||
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
* text=auto eol=lf
|
||||||
|
*.json text eol=lf
|
||||||
42
.github/workflows/ci.yml
vendored
Normal file
42
.github/workflows/ci.yml
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
name: Project CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
workflow_dispatch:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node-version: [24.x]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node-version }}
|
||||||
|
|
||||||
|
- uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: 10
|
||||||
|
|
||||||
|
- name: Cache NPM Deps
|
||||||
|
id: cache-npm
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: node_modules/
|
||||||
|
key: npm-${{ hashFiles('package-lock.json') }}
|
||||||
|
|
||||||
|
- name: Install NPM Deps
|
||||||
|
if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }}
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Lint
|
||||||
|
run: npm run lint
|
||||||
3
.github/workflows/deploy.yml
vendored
3
.github/workflows/deploy.yml
vendored
|
|
@ -35,8 +35,9 @@ jobs:
|
||||||
env:
|
env:
|
||||||
version: ${{steps.get_version.outputs.version-without-v}}
|
version: ${{steps.get_version.outputs.version-without-v}}
|
||||||
url: https://github.com/${{github.repository}}
|
url: https://github.com/${{github.repository}}
|
||||||
manifest: https://github.com/${{github.repository}}/releases/latest/download/system.json
|
manifest: https://raw.githubusercontent.com/${{github.repository}}/v14/system.json
|
||||||
download: https://github.com/${{github.repository}}/releases/download/${{github.event.release.tag_name}}/system.zip
|
download: https://github.com/${{github.repository}}/releases/download/${{github.event.release.tag_name}}/system.zip
|
||||||
|
flags.hotReload: false
|
||||||
|
|
||||||
# Create a zip file with all files required by the module to add to the release
|
# Create a zip file with all files required by the module to add to the release
|
||||||
- run: zip -r ./system.zip system.json README.md LICENSE build/daggerheart.js build/tagify.css styles/daggerheart.css assets/ templates/ packs/ lang/
|
- run: zip -r ./system.zip system.json README.md LICENSE build/daggerheart.js build/tagify.css styles/daggerheart.css assets/ templates/ packs/ lang/
|
||||||
|
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -6,3 +6,4 @@ Build
|
||||||
build
|
build
|
||||||
foundry
|
foundry
|
||||||
styles/daggerheart.css
|
styles/daggerheart.css
|
||||||
|
styles/daggerheart.css.map
|
||||||
|
|
|
||||||
13
.prettierrc
13
.prettierrc
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"trailingComma": "none",
|
|
||||||
"tabWidth": 4,
|
|
||||||
"useTabs": false,
|
|
||||||
"semi": true,
|
|
||||||
"singleQuote": true,
|
|
||||||
"quoteProps": "consistent",
|
|
||||||
"bracketSpacing": true,
|
|
||||||
"arrowParens": "avoid",
|
|
||||||
"printWidth": 120,
|
|
||||||
"endOfLine": "lf",
|
|
||||||
"bracketSameLine": true
|
|
||||||
}
|
|
||||||
|
|
@ -1,78 +1,9 @@
|
||||||
# Contributing to Foundryborne
|
# Contributing to Daggerheart
|
||||||
|
|
||||||
Welcome! This is a community-driven project to bring [Daggerheart](https://www.daggerheart.com/) to [FoundryVTT](https://foundryvtt.com/) as a full system. We're excited to have you here and appreciate your interest in contributing.
|
Thank you for your interest in contributing to the Foundryborne project!
|
||||||
|
|
||||||
---
|
To ensure that all contributions align with our project goals and architectural standards, we ask that you **do not submit outside contributions without first receiving feedback from the development team.**
|
||||||
|
|
||||||
## 🤝 How to Contribute
|
If you have an idea or a fix you'd like to contribute, please start a discussion or open an issue first. We'd love to hear from you and collaborate on the best way to move forward!
|
||||||
|
|
||||||
We welcome contributions of all kinds:
|
Thank you for your understanding and support.
|
||||||
|
|
||||||
- Bug reports
|
|
||||||
- Feature suggestions
|
|
||||||
- Code contributions
|
|
||||||
- UI/UX mockups
|
|
||||||
- Documentation improvements
|
|
||||||
- Questions and discussions
|
|
||||||
|
|
||||||
Please be respectful and collaborative — we’re all here to build something great together.
|
|
||||||
|
|
||||||
### Community Translations
|
|
||||||
|
|
||||||
Please note that we are not accepting community translations in the main project. Instead, community translations should be published as a module.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧭 General Guidelines
|
|
||||||
|
|
||||||
- **Use GitHub Issues** to report bugs or propose features
|
|
||||||
- **Start a Discussion** for larger ideas or questions
|
|
||||||
- **Open a Pull Request** once you've confirmed your work aligns with project direction
|
|
||||||
- **Keep things modular and maintainable** — if you're not sure how to structure something, ask!
|
|
||||||
- **Orient your code on existing examples**, and feel free to suggest a standard if it makes things clearer
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🗂️ Project Structure
|
|
||||||
|
|
||||||
Please try to follow the general logic of the existing code when submitting PRs.
|
|
||||||
|
|
||||||
We encourage contributors to leave comments or open Discussions when proposing structural or organizational changes.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧾 Issue & PR Best Practices
|
|
||||||
|
|
||||||
**For Issues:**
|
|
||||||
|
|
||||||
- Use clear, descriptive titles
|
|
||||||
- Provide a concise explanation of the problem or idea
|
|
||||||
- Include reproduction steps or example scenarios if it's a bug
|
|
||||||
- Add screenshots or logs if helpful
|
|
||||||
|
|
||||||
**For Pull Requests:**
|
|
||||||
|
|
||||||
- Use a clear title summarizing the change
|
|
||||||
- Provide a brief description of what your code does and why
|
|
||||||
- Link to any related Issues
|
|
||||||
- Keep PRs focused — smaller is better
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔖 Labels and Boards
|
|
||||||
|
|
||||||
We use GitHub labels to help organize contributions. If your issue or PR relates to a specific category, feel free to tag it. There is also a GitHub Project Board to help track active work and priorities.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📣 Communication
|
|
||||||
|
|
||||||
Discussions are currently happening on GitHub — in Issues, PRs, and [GitHub Discussions](https://github.com/Foundryborne/daggerheart/discussions). You're welcome to use any of these, though we may consolidate to one in the future.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🤗 Thank You!
|
|
||||||
|
|
||||||
Whether you're fixing a typo or designing entire mechanics — every contribution matters. Thank you for helping bring _Daggerheart_ to life in FoundryVTT through **Foundryborne**!
|
|
||||||
|
|
||||||
🐸🛠️
|
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,11 @@ You can find the documentation here: https://github.com/Foundryborne/daggerheart
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Looking to contribute to the project? Look no further, check out our [contributing guide](contributing.md), and keep the [Code of Conduct](coc.md) in mind when working on things.
|
Looking to contribute to the project? Look no further, check out our [contributing guide](CONTRIBUTING.md), and keep the [Code of Conduct](coc.md) in mind when working on things.
|
||||||
|
|
||||||
|
## AI Policy
|
||||||
|
|
||||||
|
The Foundryborne Daggerheart system does not make use of AI (generative or otherwise) for any area of its implementation. We expect all contributors to follow this same policy when contributing with a pull request; contributions made using AI will be rejected outright.
|
||||||
|
|
||||||
## Disclaimer:
|
## Disclaimer:
|
||||||
|
|
||||||
|
|
|
||||||
1
assets/icons/documents/actors/drama-masks.svg
Normal file
1
assets/icons/documents/actors/drama-masks.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" style="height: 512px; width: 512px;"><path d="M0 0h512v512H0z" fill="#000" fill-opacity="1"></path><g class="" transform="translate(0,0)" style=""><path d="M418.813 30.625c-21.178 26.27-49.712 50.982-84.125 70.844-36.778 21.225-75.064 33.62-110.313 38.06a310.317 310.317 0 0 0 6.813 18.25c16.01.277 29.366-.434 36.406-1.5l9.47-1.53 8.436-1.28.22 10.186a307.48 307.48 0 0 1-1.095 18.72l56.625 8.843c.86-.095 1.713-.15 2.563-.157 11.188-.114 21.44 7.29 24.468 18.593.657 2.448.922 4.903.845 7.313 5.972-2.075 11.753-4.305 17.28-6.72l9.595-4.188 2.313 10.22a340.211 340.211 0 0 1 7.375 48.062C438.29 247.836 468.438 225.71 493 197.5c-3.22-36.73-16.154-78.04-39.125-117.813a290.509 290.509 0 0 0-2.22-3.78l-27.56 71.374c5.154.762 10.123 3.158 14.092 7.126 9.81 9.807 9.813 25.69 0 35.5-9.812 9.81-25.722 9.807-35.53 0-8.86-8.858-9.69-22.68-2.532-32.5l38.938-100.844a322.02 322.02 0 0 0-20.25-25.937zM51.842 118.72c-8.46 17.373-15.76 36.198-21.187 56.436-14.108 52.617-13.96 103.682-2.812 143.438 13.3-2.605 26.442-3.96 39.312-4.03 1.855-.012 3.688.02 5.53.06 20.857.48 40.98 4.332 59.97 11.5a355.064 355.064 0 0 1-1.656-34.218c0-27.8 3.135-54.377 9-78.937l2.47-10.407 9.655 4.562c29.467 13.98 66.194 23.424 106.28 25.22 5.136-20.05 8.19-39.78 9.408-58.75-35.198 4.83-75.387 2.766-116.407-8.22-38.363-10.272-72.314-26.78-99.562-46.656zm230.594 82.218c-1.535 10.452-3.615 21.03-6.218 31.687a312.754 312.754 0 0 0 46-3.97 24.98 24.98 0 0 1-1.532-21.748l-38.25-5.97zM105 201.375l4.156 18.22-21.594 4.905c8.75 5.174 13.353 15.703 10.594 26-3.32 12.394-16.045 19.758-28.437 16.438-12.394-3.32-19.76-16.075-16.44-28.47a23.235 23.235 0 0 1 3.126-6.874l-21.062 4.78-4.125-18.218 73.78-16.78zm388.594 22.813c-25.53 25.46-55.306 45.445-86.906 60.5.05 2.397.093 4.8.093 7.218 0 9.188-.354 18.232-1.03 27.125 16.635 1.33 32.045-1.7 45.344-9.374 25.925-14.962 40.608-45.694 42.5-85.47zm-338.844 3c-4.03 19.993-6.33 41.31-6.406 63.593l.125-.342c30.568 10.174 62.622 17.572 95.25 21.375l7.5.875.718 7.5 5.687 60.125-18.625 1.75-2.53-26.75a23.117 23.117 0 0 1-14.845.968c-12.393-3.32-19.76-16.042-16.438-28.436.285-1.06.647-2.08 1.063-3.063a496.627 496.627 0 0 1-57.406-14.53c2.69 49.62 16.154 94.04 36.094 126.656 22.366 36.588 52.13 57.78 83.968 57.78 31.838.003 61.602-21.19 83.97-57.78 19.536-31.96 32.846-75.244 35.905-123.656a499.132 499.132 0 0 1-48.25 11.656c1.914 4.57 2.415 9.78 1.033 14.938-3.322 12.394-16.045 19.758-28.438 16.437a23.01 23.01 0 0 1-2.125-.686l-2.5 26.47-18.594-1.752 5.688-60.125.72-7.5 7.498-.875c29.245-3.407 57.995-9.717 85.657-18.312v-1.594c0-21.573-2.27-42.23-6.064-61.75C351.132 242.653 313.092 250 272.312 250c-43.59 0-83.986-8.658-117.562-22.813zm-87.5 105.968c-10.87.102-21.995 1.22-33.375 3.313 12.695 31.62 33.117 53.07 59 60 16.9 4.523 34.896 2.536 52.813-5.25-4.382-13.89-7.874-28.606-10.344-43.97-21.115-9.623-43.934-14.32-68.094-14.094zm137.5 80.22h130.813c-40.082 44.594-92.623 42.844-130.813 0z" fill="#fff" fill-opacity="1"></path></g></svg>
|
||||||
|
After Width: | Height: | Size: 3 KiB |
|
|
@ -1,3 +1,3 @@
|
||||||
<svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M6.12012 0.5H51.8799C55.2901 0.500041 57.8779 3.57175 57.2998 6.93262L50.4639 46.6777C50.1604 48.4411 49.0179 49.9467 47.4014 50.7139L31.3584 58.3271C29.8661 59.0354 28.1339 59.0354 26.6416 58.3271L10.5986 50.7139C8.98214 49.9467 7.83959 48.4411 7.53613 46.6777L0.700195 6.93262C0.122088 3.57175 2.7099 0.500042 6.12012 0.5Z" fill="transparent" stroke="#18162e"/>
|
<path d="M 7.12 0.5 H 52.88 C 56.29 0.5 58.88 3.57 58.3 6.93 L 51.46 46.68 C 51.16 48.44 50.02 49.95 48.4 50.71 L 32.36 58.33 C 30.87 59.04 29.13 59.04 27.64 58.33 L 11.6 50.71 C 9.98 49.95 8.84 48.44 8.54 46.68 L 1.7 6.93 C 1.12 3.57 3.71 0.5 7.12 0.5 Z" fill="transparent" stroke="#18162e"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 476 B After Width: | Height: | Size: 397 B |
|
|
@ -1,3 +1,3 @@
|
||||||
<svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M6.12012 0.5H51.8799C55.2901 0.500041 57.8779 3.57175 57.2998 6.93262L50.4639 46.6777C50.1604 48.4411 49.0179 49.9467 47.4014 50.7139L31.3584 58.3271C29.8661 59.0354 28.1339 59.0354 26.6416 58.3271L10.5986 50.7139C8.98214 49.9467 7.83959 48.4411 7.53613 46.6777L0.700195 6.93262C0.122088 3.57175 2.7099 0.500042 6.12012 0.5Z" fill="#18152E" stroke="#F3C267"/>
|
<path d="M 7.12 0.5 H 52.88 C 56.29 0.5 58.88 3.57 58.3 6.93 L 51.46 46.68 C 51.16 48.44 50.02 49.95 48.4 50.71 L 32.36 58.33 C 30.87 59.04 29.13 59.04 27.64 58.33 L 11.6 50.71 C 9.98 49.95 8.84 48.44 8.54 46.68 L 1.7 6.93 C 1.12 3.57 3.71 0.5 7.12 0.5 Z" fill="#18152E" stroke="#F3C267"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 472 B After Width: | Height: | Size: 393 B |
24
daggerheart.d.ts
vendored
24
daggerheart.d.ts
vendored
|
|
@ -1,8 +1,11 @@
|
||||||
import '@client/global.mjs';
|
import '@client/global.mjs';
|
||||||
|
import '@common/global.mjs';
|
||||||
|
import '@common/primitives/global.mjs';
|
||||||
import Canvas from '@client/canvas/board.mjs';
|
import Canvas from '@client/canvas/board.mjs';
|
||||||
|
|
||||||
// Foundry's use of `Object.assign(globalThis) means many globally available objects are not read as such
|
// Foundry's use of `Object.assign(globalThis) means many globally available objects are not read as such
|
||||||
// This declare global hopefully fixes that
|
// This declare global hopefully fixes that
|
||||||
|
// Note: eslint is not aware of these, whatever is added here should go in the eslint's globals list
|
||||||
declare global {
|
declare global {
|
||||||
/**
|
/**
|
||||||
* A simple event framework used throughout Foundry Virtual Tabletop.
|
* A simple event framework used throughout Foundry Virtual Tabletop.
|
||||||
|
|
@ -12,9 +15,28 @@ declare global {
|
||||||
class Hooks extends foundry.helpers.Hooks {}
|
class Hooks extends foundry.helpers.Hooks {}
|
||||||
const fromUuid = foundry.utils.fromUuid;
|
const fromUuid = foundry.utils.fromUuid;
|
||||||
const fromUuidSync = foundry.utils.fromUuidSync;
|
const fromUuidSync = foundry.utils.fromUuidSync;
|
||||||
|
/**
|
||||||
|
* A representation of a color in hexadecimal format.
|
||||||
|
* This class provides methods for transformations and manipulations of colors.
|
||||||
|
*/
|
||||||
|
class Color extends foundry.utils.Color {}
|
||||||
/**
|
/**
|
||||||
* The singleton game canvas
|
* The singleton game canvas
|
||||||
*/
|
*/
|
||||||
const canvas: Canvas;
|
const canvas: Canvas;
|
||||||
|
|
||||||
|
const ActiveEffect: foundry.documents.ActiveEffect;
|
||||||
|
const Actor: foundry.documents.Actor;
|
||||||
|
const BaseScene: foundry.documents.BaseScene;
|
||||||
|
const ChatMessage: foundry.documents.ChatMessage;
|
||||||
|
const Combat: foundry.documents.Combat;
|
||||||
|
const Combatant: foundry.documents.Combatant;
|
||||||
|
const Item: foundry.documents.Item;
|
||||||
|
const Macro: foundry.documents.Macro;
|
||||||
|
const Scene: foundry.documents.Scene;
|
||||||
|
const TokenDocument: foundry.documents.TokenDocument;
|
||||||
|
|
||||||
|
const Collection: foundry.utils.Collection;
|
||||||
|
const FormDataExtended: foundry.applications.ux.FormDataExtended;
|
||||||
|
const TextEditor: foundry.applications.ux.TextEditor;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
140
daggerheart.mjs
140
daggerheart.mjs
|
|
@ -9,10 +9,7 @@ import * as dice from './module/dice/_module.mjs';
|
||||||
import * as fields from './module/data/fields/_module.mjs';
|
import * as fields from './module/data/fields/_module.mjs';
|
||||||
import RegisterHandlebarsHelpers from './module/helpers/handlebarsHelper.mjs';
|
import RegisterHandlebarsHelpers from './module/helpers/handlebarsHelper.mjs';
|
||||||
import { enricherConfig, enricherRenderSetup } from './module/enrichers/_module.mjs';
|
import { enricherConfig, enricherRenderSetup } from './module/enrichers/_module.mjs';
|
||||||
import { getCommandTarget, rollCommandToJSON } from './module/helpers/utils.mjs';
|
|
||||||
import { BaseRoll, DHRoll, DualityRoll, D20Roll, DamageRoll, FateRoll } from './module/dice/_module.mjs';
|
import { BaseRoll, DHRoll, DualityRoll, D20Roll, DamageRoll, FateRoll } from './module/dice/_module.mjs';
|
||||||
import { enrichedDualityRoll } from './module/enrichers/DualityRollEnricher.mjs';
|
|
||||||
import { enrichedFateRoll, getFateTypeData } from './module/enrichers/FateRollEnricher.mjs';
|
|
||||||
import {
|
import {
|
||||||
handlebarsRegistration,
|
handlebarsRegistration,
|
||||||
runMigrations,
|
runMigrations,
|
||||||
|
|
@ -21,7 +18,6 @@ import {
|
||||||
} from './module/systemRegistration/_module.mjs';
|
} from './module/systemRegistration/_module.mjs';
|
||||||
import { placeables, DhTokenLayer } from './module/canvas/_module.mjs';
|
import { placeables, DhTokenLayer } from './module/canvas/_module.mjs';
|
||||||
import './node_modules/@yaireo/tagify/dist/tagify.css';
|
import './node_modules/@yaireo/tagify/dist/tagify.css';
|
||||||
import TemplateManager from './module/documents/templateManager.mjs';
|
|
||||||
import TokenManager from './module/documents/tokenManager.mjs';
|
import TokenManager from './module/documents/tokenManager.mjs';
|
||||||
|
|
||||||
CONFIG.DH = SYSTEM;
|
CONFIG.DH = SYSTEM;
|
||||||
|
|
@ -36,6 +32,13 @@ CONFIG.Dice.daggerheart = {
|
||||||
FateRoll: FateRoll
|
FateRoll: FateRoll
|
||||||
};
|
};
|
||||||
|
|
||||||
|
CONFIG.RegionBehavior.dataModels = {
|
||||||
|
...CONFIG.RegionBehavior.dataModels,
|
||||||
|
...data.regionBehaviors
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.assign(CONFIG.Dice.termTypes, dice.diceTypes);
|
||||||
|
|
||||||
CONFIG.Actor.documentClass = documents.DhpActor;
|
CONFIG.Actor.documentClass = documents.DhpActor;
|
||||||
CONFIG.Actor.dataModels = models.actors.config;
|
CONFIG.Actor.dataModels = models.actors.config;
|
||||||
CONFIG.Actor.collection = collections.DhActorCollection;
|
CONFIG.Actor.collection = collections.DhActorCollection;
|
||||||
|
|
@ -45,6 +48,7 @@ CONFIG.Item.dataModels = models.items.config;
|
||||||
|
|
||||||
CONFIG.ActiveEffect.documentClass = documents.DhActiveEffect;
|
CONFIG.ActiveEffect.documentClass = documents.DhActiveEffect;
|
||||||
CONFIG.ActiveEffect.dataModels = models.activeEffects.config;
|
CONFIG.ActiveEffect.dataModels = models.activeEffects.config;
|
||||||
|
CONFIG.ActiveEffect.changeTypes = { ...CONFIG.ActiveEffect.changeTypes, ...models.activeEffects.changeEffects };
|
||||||
|
|
||||||
CONFIG.Combat.documentClass = documents.DhpCombat;
|
CONFIG.Combat.documentClass = documents.DhpCombat;
|
||||||
CONFIG.Combat.dataModels = { base: models.DhCombat };
|
CONFIG.Combat.dataModels = { base: models.DhCombat };
|
||||||
|
|
@ -56,11 +60,13 @@ CONFIG.ChatMessage.documentClass = documents.DhChatMessage;
|
||||||
CONFIG.ChatMessage.template = 'systems/daggerheart/templates/ui/chat/chat-message.hbs';
|
CONFIG.ChatMessage.template = 'systems/daggerheart/templates/ui/chat/chat-message.hbs';
|
||||||
|
|
||||||
CONFIG.Canvas.rulerClass = placeables.DhRuler;
|
CONFIG.Canvas.rulerClass = placeables.DhRuler;
|
||||||
CONFIG.Canvas.layers.templates.layerClass = placeables.DhTemplateLayer;
|
CONFIG.Canvas.layers.regions.layerClass = placeables.DhRegionLayer;
|
||||||
CONFIG.Canvas.layers.tokens.layerClass = DhTokenLayer;
|
CONFIG.Canvas.layers.tokens.layerClass = DhTokenLayer;
|
||||||
|
|
||||||
CONFIG.MeasuredTemplate.objectClass = placeables.DhMeasuredTemplate;
|
CONFIG.MeasuredTemplate.objectClass = placeables.DhMeasuredTemplate;
|
||||||
|
|
||||||
|
CONFIG.Region.objectClass = placeables.DhRegion;
|
||||||
|
|
||||||
CONFIG.RollTable.documentClass = documents.DhRollTable;
|
CONFIG.RollTable.documentClass = documents.DhRollTable;
|
||||||
CONFIG.RollTable.resultTemplate = 'systems/daggerheart/templates/ui/chat/table-result.hbs';
|
CONFIG.RollTable.resultTemplate = 'systems/daggerheart/templates/ui/chat/table-result.hbs';
|
||||||
|
|
||||||
|
|
@ -84,7 +90,6 @@ CONFIG.ui.resources = applications.ui.DhFearTracker;
|
||||||
CONFIG.ui.countdowns = applications.ui.DhCountdowns;
|
CONFIG.ui.countdowns = applications.ui.DhCountdowns;
|
||||||
CONFIG.ux.ContextMenu = applications.ux.DHContextMenu;
|
CONFIG.ux.ContextMenu = applications.ux.DHContextMenu;
|
||||||
CONFIG.ux.TooltipManager = documents.DhTooltipManager;
|
CONFIG.ux.TooltipManager = documents.DhTooltipManager;
|
||||||
CONFIG.ux.TemplateManager = new TemplateManager();
|
|
||||||
CONFIG.ux.TokenManager = new TokenManager();
|
CONFIG.ux.TokenManager = new TokenManager();
|
||||||
CONFIG.debug.triggers = false;
|
CONFIG.debug.triggers = false;
|
||||||
|
|
||||||
|
|
@ -191,6 +196,11 @@ Hooks.once('init', () => {
|
||||||
makeDefault: true,
|
makeDefault: true,
|
||||||
label: sheetLabel('TYPES.Actor.environment')
|
label: sheetLabel('TYPES.Actor.environment')
|
||||||
});
|
});
|
||||||
|
Actors.registerSheet(SYSTEM.id, applications.sheets.actors.NPC, {
|
||||||
|
types: ['npc'],
|
||||||
|
makeDefault: true,
|
||||||
|
label: sheetLabel('TYPES.Actor.npc')
|
||||||
|
});
|
||||||
Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Party, {
|
Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Party, {
|
||||||
types: ['party'],
|
types: ['party'],
|
||||||
makeDefault: true,
|
makeDefault: true,
|
||||||
|
|
@ -213,6 +223,7 @@ Hooks.once('init', () => {
|
||||||
SYSTEM.id,
|
SYSTEM.id,
|
||||||
applications.sheetConfigs.ActiveEffectConfig,
|
applications.sheetConfigs.ActiveEffectConfig,
|
||||||
{
|
{
|
||||||
|
types: ['base', 'beastform', 'horde'],
|
||||||
makeDefault: true,
|
makeDefault: true,
|
||||||
label: sheetLabel('DOCUMENT.ActiveEffect')
|
label: sheetLabel('DOCUMENT.ActiveEffect')
|
||||||
}
|
}
|
||||||
|
|
@ -270,7 +281,6 @@ Hooks.on('setup', () => {
|
||||||
...damageThresholds,
|
...damageThresholds,
|
||||||
'proficiency',
|
'proficiency',
|
||||||
'evasion',
|
'evasion',
|
||||||
'armorScore',
|
|
||||||
'scars',
|
'scars',
|
||||||
'levelData.level.current'
|
'levelData.level.current'
|
||||||
]
|
]
|
||||||
|
|
@ -332,79 +342,33 @@ Hooks.on('renderHandlebarsApplication', (_, element) => {
|
||||||
enricherRenderSetup(element);
|
enricherRenderSetup(element);
|
||||||
});
|
});
|
||||||
|
|
||||||
Hooks.on('chatMessage', (_, message) => {
|
Hooks.on(CONFIG.DH.HOOKS.hooksConfig.tagTeamStart, async data => {
|
||||||
if (message.startsWith('/dr')) {
|
if (data.openForAllPlayers && data.partyId) {
|
||||||
const result =
|
const party = game.actors.get(data.partyId);
|
||||||
message.trim().toLowerCase() === '/dr' ? { result: {} } : rollCommandToJSON(message.replace(/\/dr\s?/, ''));
|
if (!party) return;
|
||||||
if (!result) {
|
|
||||||
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.dualityParsing'));
|
const TagTeamDialog = game.system.api.applications.dialogs.TagTeamDialog;
|
||||||
return false;
|
const dialog = foundry.applications.instances.get(`TagTeamDialog-${party.id}`) ?? new TagTeamDialog(party);
|
||||||
|
dialog.tabGroups.application = 'tagTeamRoll';
|
||||||
|
await dialog.render({ force: true });
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const { result: rollCommand, flavor } = result;
|
Hooks.on(CONFIG.DH.HOOKS.hooksConfig.groupRollStart, async data => {
|
||||||
|
if (data.openForAllPlayers && data.partyId) {
|
||||||
|
const party = game.actors.get(data.partyId);
|
||||||
|
if (!party) return;
|
||||||
|
|
||||||
const reaction = rollCommand.reaction;
|
const GroupRollDialog = game.system.api.applications.dialogs.GroupRollDialog;
|
||||||
const traitValue = rollCommand.trait?.toLowerCase();
|
const dialog = foundry.applications.instances.get(`GroupRollDialog-${party.id}`) ?? new GroupRollDialog(party);
|
||||||
const advantage = rollCommand.advantage
|
dialog.tabGroups.application = 'groupRoll';
|
||||||
? CONFIG.DH.ACTIONS.advantageState.advantage.value
|
await dialog.render({ force: true });
|
||||||
: rollCommand.disadvantage
|
|
||||||
? CONFIG.DH.ACTIONS.advantageState.disadvantage.value
|
|
||||||
: undefined;
|
|
||||||
const difficulty = rollCommand.difficulty;
|
|
||||||
const grantResources = rollCommand.grantResources;
|
|
||||||
|
|
||||||
const target = getCommandTarget({ allowNull: true });
|
|
||||||
const title =
|
|
||||||
(flavor ?? traitValue)
|
|
||||||
? game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
|
||||||
ability: game.i18n.localize(SYSTEM.ACTOR.abilities[traitValue].label)
|
|
||||||
})
|
|
||||||
: game.i18n.localize('DAGGERHEART.GENERAL.duality');
|
|
||||||
|
|
||||||
enrichedDualityRoll({
|
|
||||||
reaction,
|
|
||||||
traitValue,
|
|
||||||
target,
|
|
||||||
difficulty,
|
|
||||||
title,
|
|
||||||
label: game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll'),
|
|
||||||
actionType: null,
|
|
||||||
advantage,
|
|
||||||
grantResources
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.startsWith('/fr')) {
|
|
||||||
const result =
|
|
||||||
message.trim().toLowerCase() === '/fr' ? { result: {} } : rollCommandToJSON(message.replace(/\/fr\s?/, ''));
|
|
||||||
|
|
||||||
if (!result) {
|
|
||||||
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.fateParsing'));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { result: rollCommand, flavor } = result;
|
|
||||||
const fateTypeData = getFateTypeData(rollCommand?.type);
|
|
||||||
|
|
||||||
if (!fateTypeData)
|
|
||||||
return ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.fateTypeParsing'));
|
|
||||||
|
|
||||||
const { value: fateType, label: fateTypeLabel } = fateTypeData;
|
|
||||||
const target = getCommandTarget({ allowNull: true });
|
|
||||||
const title = flavor ?? game.i18n.localize('DAGGERHEART.GENERAL.fateRoll');
|
|
||||||
|
|
||||||
enrichedFateRoll({
|
|
||||||
target,
|
|
||||||
title,
|
|
||||||
label: fateTypeLabel,
|
|
||||||
fateType
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const updateActorsRangeDependentEffects = async token => {
|
const updateActorsRangeDependentEffects = async token => {
|
||||||
|
if (!token) return;
|
||||||
|
|
||||||
const rangeMeasurement = game.settings.get(
|
const rangeMeasurement = game.settings.get(
|
||||||
CONFIG.DH.id,
|
CONFIG.DH.id,
|
||||||
CONFIG.DH.SETTINGS.gameSettings.variantRules
|
CONFIG.DH.SETTINGS.gameSettings.variantRules
|
||||||
|
|
@ -482,3 +446,33 @@ Hooks.on('canvasTearDown', canvas => {
|
||||||
Hooks.on('canvasReady', canas => {
|
Hooks.on('canvasReady', canas => {
|
||||||
game.system.registeredTriggers.registerSceneTriggers(canvas.scene);
|
game.system.registeredTriggers.registerSceneTriggers(canvas.scene);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** Make the user to select a document type, instead of having a default doc type for them to accidentally keep */
|
||||||
|
Hooks.on('renderDialogV2', (_dialog, html) => {
|
||||||
|
if (!html.classList.contains('dialog')) return;
|
||||||
|
const cls = html.classList.contains('item-create')
|
||||||
|
? documents.DHItem.implementation
|
||||||
|
: html.classList.contains('actor-create')
|
||||||
|
? documents.DhpActor.implementation
|
||||||
|
: null;
|
||||||
|
if (!cls) return;
|
||||||
|
|
||||||
|
const form = html.querySelector('form');
|
||||||
|
const submit = html.querySelector('button[type=submit]');
|
||||||
|
const select = html.querySelector('select[name=type]');
|
||||||
|
const nameInput = html.querySelector('input[name=name]');
|
||||||
|
if (!form || !select || !submit || !nameInput) return;
|
||||||
|
|
||||||
|
nameInput.placeholder = cls.defaultName({});
|
||||||
|
const emptyOption = document.createElement('option');
|
||||||
|
emptyOption.value = '';
|
||||||
|
emptyOption.selected = true;
|
||||||
|
select.required = true;
|
||||||
|
select.prepend(emptyOption);
|
||||||
|
submit.addEventListener('click', event => {
|
||||||
|
if (!form.reportValidity()) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
||||||
101
eslint.config.mjs
Normal file
101
eslint.config.mjs
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
import globals from 'globals';
|
||||||
|
import { defineConfig, globalIgnores } from 'eslint/config';
|
||||||
|
import tseslint from 'typescript-eslint';
|
||||||
|
import js from '@eslint/js';
|
||||||
|
import stylistic from '@stylistic/eslint-plugin';
|
||||||
|
|
||||||
|
/** @type {Partial<RulesConfig>} */
|
||||||
|
export const stylisticRules = {
|
||||||
|
'@stylistic/indent': [
|
||||||
|
'error',
|
||||||
|
4,
|
||||||
|
{
|
||||||
|
SwitchCase: 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'@stylistic/max-len': ['error', {
|
||||||
|
code: 120,
|
||||||
|
ignoreComments: true,
|
||||||
|
ignoreStrings: true,
|
||||||
|
ignoreTemplateLiterals: true,
|
||||||
|
ignoreRegExpLiterals: true
|
||||||
|
}],
|
||||||
|
'@stylistic/quotes': ['error', 'single', { allowTemplateLiterals: 'always' }],
|
||||||
|
'@stylistic/arrow-parens': ['error', 'as-needed'],
|
||||||
|
'@stylistic/quote-props': ['error', 'as-needed'],
|
||||||
|
'@stylistic/array-bracket-newline': ['error', 'consistent'],
|
||||||
|
'@stylistic/key-spacing': 'error',
|
||||||
|
'@stylistic/comma-dangle': ['error', 'never'],
|
||||||
|
'@stylistic/space-in-parens': ['error', 'never'],
|
||||||
|
'@stylistic/space-infix-ops': 2,
|
||||||
|
'@stylistic/keyword-spacing': 2,
|
||||||
|
'@stylistic/semi-spacing': 2,
|
||||||
|
'@stylistic/no-multi-spaces': 2,
|
||||||
|
'@stylistic/no-extra-semi': 2,
|
||||||
|
'@stylistic/no-whitespace-before-property': 2,
|
||||||
|
'@stylistic/space-unary-ops': 2
|
||||||
|
};
|
||||||
|
|
||||||
|
export default defineConfig([
|
||||||
|
globalIgnores(['foundry/**/*', 'build/**/*']),
|
||||||
|
{
|
||||||
|
files: ['gulpfile.js', 'postcss.config.js'],
|
||||||
|
languageOptions: { globals: globals.node }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.{js,mjs,cjs}'],
|
||||||
|
plugins: {
|
||||||
|
'@stylistic': stylistic
|
||||||
|
},
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
CONFIG: 'readonly',
|
||||||
|
CONST: 'readonly',
|
||||||
|
// Global classes
|
||||||
|
Color: 'readonly',
|
||||||
|
Handlebars: 'readonly',
|
||||||
|
Hooks: 'readonly',
|
||||||
|
PIXI: 'readonly',
|
||||||
|
ProseMirror: 'readonly',
|
||||||
|
Roll: 'readonly',
|
||||||
|
// global namespaces
|
||||||
|
canvas: 'readonly',
|
||||||
|
foundry: 'readonly',
|
||||||
|
game: 'readonly',
|
||||||
|
ui: 'readonly',
|
||||||
|
// global functions
|
||||||
|
fromUuid: 'readonly',
|
||||||
|
fromUuidSync: 'readonly',
|
||||||
|
getDocumentClass: 'readonly',
|
||||||
|
_del: 'readonly',
|
||||||
|
_replace: 'readonly',
|
||||||
|
_loc: 'readonly',
|
||||||
|
// Documents
|
||||||
|
ActiveEffect: 'readonly',
|
||||||
|
Actor: 'readonly',
|
||||||
|
BaseScene: 'readonly',
|
||||||
|
ChatMessage: 'readonly',
|
||||||
|
Combat: 'readonly',
|
||||||
|
Combatant: 'readonly',
|
||||||
|
Item: 'readonly',
|
||||||
|
Macro: 'readonly',
|
||||||
|
Scene: 'readonly',
|
||||||
|
TokenDocument: 'readonly',
|
||||||
|
// Other
|
||||||
|
Collection: 'readonly',
|
||||||
|
FormDataExtended: 'readonly',
|
||||||
|
TextEditor: 'readonly'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'no-undef': 'error',
|
||||||
|
// 'no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
|
||||||
|
...stylisticRules
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.ts'],
|
||||||
|
extends: [js.configs.recommended, tseslint.configs.recommended]
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
@ -1,9 +1,15 @@
|
||||||
// Less configuration
|
// Less configuration
|
||||||
var gulp = require('gulp');
|
var gulp = require('gulp');
|
||||||
var less = require('gulp-less');
|
var less = require('gulp-less');
|
||||||
|
var sourcemaps = require('gulp-sourcemaps');
|
||||||
|
|
||||||
gulp.task('less', function (cb) {
|
gulp.task('less', function (cb) {
|
||||||
gulp.src('styles/daggerheart.less').pipe(less()).pipe(gulp.dest('styles'));
|
gulp.src('styles/daggerheart.less')
|
||||||
|
.pipe(sourcemaps.init())
|
||||||
|
.pipe(less())
|
||||||
|
.on('error', console.error.bind(console))
|
||||||
|
.pipe(sourcemaps.write('.'))
|
||||||
|
.pipe(gulp.dest('styles'));
|
||||||
cb();
|
cb();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"module": "ES6",
|
"module": "es2022",
|
||||||
"target": "ES6",
|
"target": "es2022",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@client/*": ["./foundry/client/*"],
|
"@client/*": ["./foundry/client/*"],
|
||||||
"@common/*": ["./foundry/common/*"]
|
"@common/*": ["./foundry/common/*"]
|
||||||
|
|
|
||||||
283
lang/en.json
283
lang/en.json
|
|
@ -14,13 +14,16 @@
|
||||||
"beastform": "Beastform"
|
"beastform": "Beastform"
|
||||||
},
|
},
|
||||||
"ActiveEffect": {
|
"ActiveEffect": {
|
||||||
"beastform": "Beastform"
|
"base": "Standard",
|
||||||
|
"beastform": "Beastform",
|
||||||
|
"horde": "Horde"
|
||||||
},
|
},
|
||||||
"Actor": {
|
"Actor": {
|
||||||
"character": "Character",
|
"character": "Character",
|
||||||
"companion": "Companion",
|
"companion": "Companion",
|
||||||
"adversary": "Adversary",
|
"adversary": "Adversary",
|
||||||
"environment": "Environment",
|
"environment": "Environment",
|
||||||
|
"npc": "NPC",
|
||||||
"party": "Party"
|
"party": "Party"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -53,6 +56,7 @@
|
||||||
},
|
},
|
||||||
"damage": {
|
"damage": {
|
||||||
"name": "Damage",
|
"name": "Damage",
|
||||||
|
"critical": "Damage (Critical)",
|
||||||
"tooltip": "Direct damage without a roll."
|
"tooltip": "Direct damage without a roll."
|
||||||
},
|
},
|
||||||
"effect": {
|
"effect": {
|
||||||
|
|
@ -71,9 +75,7 @@
|
||||||
"name": "Summon",
|
"name": "Summon",
|
||||||
"tooltip": "Create tokens in the scene.",
|
"tooltip": "Create tokens in the scene.",
|
||||||
"error": "You do not have permission to summon tokens or there is no active scene.",
|
"error": "You do not have permission to summon tokens or there is no active scene.",
|
||||||
"invalidDrop": "You can only drop Actor entities to summon.",
|
"invalidDrop": "You can only drop Actor entities to summon."
|
||||||
"chatMessageTitle": "Test2",
|
|
||||||
"chatMessageHeaderTitle": "Summoning"
|
|
||||||
},
|
},
|
||||||
"transform": {
|
"transform": {
|
||||||
"name": "Transform",
|
"name": "Transform",
|
||||||
|
|
@ -87,9 +89,14 @@
|
||||||
},
|
},
|
||||||
"Config": {
|
"Config": {
|
||||||
"beastform": {
|
"beastform": {
|
||||||
"exact": "Beastform Max Tier",
|
"exact": { "label": "Beastform Max Tier", "hint": "The Character's Tier is used if empty" },
|
||||||
"exactHint": "The Character's Tier is used if empty",
|
"modifications": {
|
||||||
"label": "Beastform"
|
"traitBonuses": {
|
||||||
|
"label": { "single": "Trait Bonus", "plural": "Trait Bonuses" },
|
||||||
|
"hint": "Pick bonuses you apply to freely chosen traits at the time of transforming",
|
||||||
|
"bonus": "Bonus Amount"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"countdown": {
|
"countdown": {
|
||||||
"defaultOwnership": "Default Ownership",
|
"defaultOwnership": "Default Ownership",
|
||||||
|
|
@ -103,9 +110,17 @@
|
||||||
"customFormula": "Custom Formula",
|
"customFormula": "Custom Formula",
|
||||||
"formula": "Formula"
|
"formula": "Formula"
|
||||||
},
|
},
|
||||||
|
"area": {
|
||||||
|
"sectionTitle": "Areas",
|
||||||
|
"shape": "Shape",
|
||||||
|
"size": "Size"
|
||||||
|
},
|
||||||
"displayInChat": "Display in chat",
|
"displayInChat": "Display in chat",
|
||||||
"deleteTriggerTitle": "Delete Trigger",
|
"deleteTriggerTitle": "Delete Trigger",
|
||||||
"deleteTriggerContent": "Are you sure you want to delete the {trigger} trigger?"
|
"deleteTriggerContent": "Are you sure you want to delete the {trigger} trigger?",
|
||||||
|
"advantageState": "Advantage State",
|
||||||
|
"damageOnSave": "Damage on Save",
|
||||||
|
"useDefaultItemValues": "Use default Item values"
|
||||||
},
|
},
|
||||||
"RollField": {
|
"RollField": {
|
||||||
"diceRolling": {
|
"diceRolling": {
|
||||||
|
|
@ -117,10 +132,11 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Settings": {
|
"Settings": {
|
||||||
"attackBonus": "Attack Bonus",
|
"attackModifier": "Attack Modifier",
|
||||||
"attackName": "Attack Name",
|
"attackName": "Attack Name",
|
||||||
"criticalThreshold": "Critical Threshold",
|
"criticalThreshold": "Critical Threshold",
|
||||||
"includeBase": { "label": "Include Item Damage" },
|
"includeBase": { "label": "Include Item Damage" },
|
||||||
|
"groupAttack": { "label": "Group Attack" },
|
||||||
"multiplier": "Multiplier",
|
"multiplier": "Multiplier",
|
||||||
"saveHint": "Set a default Trait to enable Reaction Roll. It can be changed later in Reaction Roll Dialog.",
|
"saveHint": "Set a default Trait to enable Reaction Roll. It can be changed later in Reaction Roll Dialog.",
|
||||||
"resultBased": {
|
"resultBased": {
|
||||||
|
|
@ -151,7 +167,9 @@
|
||||||
"Config": {
|
"Config": {
|
||||||
"rangeDependence": {
|
"rangeDependence": {
|
||||||
"title": "Range Dependence"
|
"title": "Range Dependence"
|
||||||
}
|
},
|
||||||
|
"stacking": { "title": "Stacking" },
|
||||||
|
"targetDispositions": "Affected Dispositions"
|
||||||
},
|
},
|
||||||
"RangeDependance": {
|
"RangeDependance": {
|
||||||
"hint": "Settings for an optional distance at which this effect should activate",
|
"hint": "Settings for an optional distance at which this effect should activate",
|
||||||
|
|
@ -198,7 +216,13 @@
|
||||||
"type": { "label": "Type" }
|
"type": { "label": "Type" }
|
||||||
},
|
},
|
||||||
"hordeDamage": "Horde Damage",
|
"hordeDamage": "Horde Damage",
|
||||||
"horderHp": "Horde/HP"
|
"horderHp": "Horde/HP",
|
||||||
|
"adversaryReactionRoll": {
|
||||||
|
"headerTitle": "Adversary Reaction Roll"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Base": {
|
||||||
|
"CannotAddType": "Cannot add {itemType} items to {actorType} actors."
|
||||||
},
|
},
|
||||||
"Character": {
|
"Character": {
|
||||||
"advantageSources": {
|
"advantageSources": {
|
||||||
|
|
@ -223,6 +247,8 @@
|
||||||
},
|
},
|
||||||
"defaultHopeDice": "Default Hope Dice",
|
"defaultHopeDice": "Default Hope Dice",
|
||||||
"defaultFearDice": "Default Fear Dice",
|
"defaultFearDice": "Default Fear Dice",
|
||||||
|
"defaultAdvantageDice": "Default Advantage Dice",
|
||||||
|
"defaultDisadvantageDice": "Default Disadvantage Dice",
|
||||||
"disadvantageSources": {
|
"disadvantageSources": {
|
||||||
"label": "Disadvantage Sources",
|
"label": "Disadvantage Sources",
|
||||||
"hint": "Add single words or short text as reminders and hints of what a character has disadvantage on."
|
"hint": "Add single words or short text as reminders and hints of what a character has disadvantage on."
|
||||||
|
|
@ -307,6 +333,27 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"newAdversary": "New Adversary"
|
"newAdversary": "New Adversary"
|
||||||
|
},
|
||||||
|
"NPC": {
|
||||||
|
"FIELDS": {
|
||||||
|
"motives": { "label": "Motives" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Party": {
|
||||||
|
"Subtitle": {
|
||||||
|
"character": "{community} {ancestry} | {subclass} {class}",
|
||||||
|
"companion": "Companion of {partner}"
|
||||||
|
},
|
||||||
|
"RemoveConfirmation": {
|
||||||
|
"title": "Remove member {name}",
|
||||||
|
"text": "Are you sure you want to remove {name} from the party?"
|
||||||
|
},
|
||||||
|
"Thresholds": {
|
||||||
|
"minor": "MIN",
|
||||||
|
"major": "MAJ",
|
||||||
|
"severe": "SEV"
|
||||||
|
},
|
||||||
|
"triggerRestContent": "This will trigger a dialog to players make their downtime moves. Are you sure?"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"APPLICATIONS": {
|
"APPLICATIONS": {
|
||||||
|
|
@ -342,7 +389,7 @@
|
||||||
"selectSecondaryWeapon": "Select Secondary Weapon",
|
"selectSecondaryWeapon": "Select Secondary Weapon",
|
||||||
"selectSubclass": "Select Subclass",
|
"selectSubclass": "Select Subclass",
|
||||||
"setupSkipTitle": "Skipping Character Setup",
|
"setupSkipTitle": "Skipping Character Setup",
|
||||||
"setupSkipContent": "You are skipping the Character Setup by adding this manually. The character setup is the blinking arrows in the top-right. Are you sure you want to continue?",
|
"setupSkipContent": "You are skipping the Character Setup by adding this manually. The character setup is the blinking button in the top-right. Are you sure you want to continue?",
|
||||||
"startingItems": "Starting Items",
|
"startingItems": "Starting Items",
|
||||||
"story": "Story",
|
"story": "Story",
|
||||||
"storyExplanation": "Select which background and connection prompts you want to copy into your character's background.",
|
"storyExplanation": "Select which background and connection prompts you want to copy into your character's background.",
|
||||||
|
|
@ -365,7 +412,11 @@
|
||||||
"giveSpotlight": "Give The Spotlight",
|
"giveSpotlight": "Give The Spotlight",
|
||||||
"requestingSpotlight": "Requesting The Spotlight",
|
"requestingSpotlight": "Requesting The Spotlight",
|
||||||
"requestSpotlight": "Request The Spotlight",
|
"requestSpotlight": "Request The Spotlight",
|
||||||
"openCountdowns": "Countdowns"
|
"openCountdowns": "Countdowns",
|
||||||
|
"adversaryCategories": {
|
||||||
|
"friendly": "Friendly",
|
||||||
|
"adversaries": "Adversaries"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"CompendiumBrowserSettings": {
|
"CompendiumBrowserSettings": {
|
||||||
"title": "Enable Compendiums",
|
"title": "Enable Compendiums",
|
||||||
|
|
@ -440,16 +491,21 @@
|
||||||
"defaultOwnershipTooltip": "The default player ownership of countdowns",
|
"defaultOwnershipTooltip": "The default player ownership of countdowns",
|
||||||
"hideNewCountdowns": "Hide New Countdowns"
|
"hideNewCountdowns": "Hide New Countdowns"
|
||||||
},
|
},
|
||||||
|
"CreateItemDialog": {
|
||||||
|
"createItem": "Create Item",
|
||||||
|
"browseCompendium": "Browse Compendium"
|
||||||
|
},
|
||||||
"DaggerheartMenu": {
|
"DaggerheartMenu": {
|
||||||
"title": "GM Tools",
|
"title": "GM Tools",
|
||||||
"refreshFeatures": "Refresh Features"
|
"refreshFeatures": "Refresh Features",
|
||||||
|
"fallingAndCollision": "Falling And Collision Damage"
|
||||||
},
|
},
|
||||||
"DeleteConfirmation": {
|
"DeleteConfirmation": {
|
||||||
"title": "Delete {type} - {name}",
|
"title": "Delete {type} - {name}",
|
||||||
"text": "Are you sure you want to delete {name}?"
|
"text": "Are you sure you want to delete {name}?"
|
||||||
},
|
},
|
||||||
"DamageReduction": {
|
"DamageReduction": {
|
||||||
"armorMarks": "Armor Marks",
|
"maxUseableArmor": "Useable Armor Slots",
|
||||||
"armorWithStress": "Spend 1 stress to use an extra mark",
|
"armorWithStress": "Spend 1 stress to use an extra mark",
|
||||||
"thresholdImmunities": "Threshold Immunities",
|
"thresholdImmunities": "Threshold Immunities",
|
||||||
"stress": "Stress",
|
"stress": "Stress",
|
||||||
|
|
@ -653,15 +709,15 @@
|
||||||
"noPlayers": "No players to assign ownership to",
|
"noPlayers": "No players to assign ownership to",
|
||||||
"default": "Default Ownership"
|
"default": "Default Ownership"
|
||||||
},
|
},
|
||||||
|
"PendingReactionsDialog": {
|
||||||
|
"title": "Pending Reaction Rolls Found",
|
||||||
|
"unfinishedRolls": "Some Tokens have not finished their Reaction Rolls.",
|
||||||
|
"warning": "Unfinished reaction rolls will be considered as failed.",
|
||||||
|
"confirmation": "Are you sure you want to continue?"
|
||||||
|
},
|
||||||
"ReactionRoll": {
|
"ReactionRoll": {
|
||||||
"title": "Reaction Roll: {trait}"
|
"title": "Reaction Roll: {trait}"
|
||||||
},
|
},
|
||||||
"RerollDialog": {
|
|
||||||
"title": "Reroll",
|
|
||||||
"damageTitle": "Reroll Damage",
|
|
||||||
"deselectDiceNotification": "Deselect one of the selected dice first",
|
|
||||||
"acceptCurrentRolls": "Accept Current Rolls"
|
|
||||||
},
|
|
||||||
"ResourceDice": {
|
"ResourceDice": {
|
||||||
"title": "{name} Resource",
|
"title": "{name} Resource",
|
||||||
"rerollDice": "Reroll Dice"
|
"rerollDice": "Reroll Dice"
|
||||||
|
|
@ -675,19 +731,56 @@
|
||||||
},
|
},
|
||||||
"TagTeamSelect": {
|
"TagTeamSelect": {
|
||||||
"title": "Tag Team Roll",
|
"title": "Tag Team Roll",
|
||||||
|
"FIELDS": {
|
||||||
|
"initiator": {
|
||||||
|
"memberId": { "label": "Initiating Character" },
|
||||||
|
"cost": { "label": "Hope Cost" }
|
||||||
|
}
|
||||||
|
},
|
||||||
"leaderTitle": "Initiating Character",
|
"leaderTitle": "Initiating Character",
|
||||||
"membersTitle": "Participants",
|
"membersTitle": "Participants",
|
||||||
"partyTeam": "Party Team",
|
"partyTeam": "Party Team",
|
||||||
"hopeCost": "Hope Cost",
|
"hopeCost": "Hope Cost",
|
||||||
"initiatingCharacter": "Initiating Character",
|
"initiatingCharacter": "Initiating Character",
|
||||||
|
"selectParticipants": "Select the two participants",
|
||||||
|
"startTagTeamRoll": "Start Tag Team Roll",
|
||||||
|
"openDialogForAll": "Open Dialog For All",
|
||||||
|
"rollType": "Roll Type",
|
||||||
|
"makeYourRoll": "Make your roll",
|
||||||
|
"cancelTagTeamRoll": "Cancel Tag Team Roll",
|
||||||
|
"finishTagTeamRoll": "Finish Tag Team Roll",
|
||||||
"linkMessageHint": "Make a roll from your character sheet to link it to the Tag Team Roll",
|
"linkMessageHint": "Make a roll from your character sheet to link it to the Tag Team Roll",
|
||||||
"damageNotRolled": "Damage not rolled in chat message yet",
|
"damageNotRolled": "Damage not rolled in chat message yet",
|
||||||
"insufficientHope": "The initiating character doesn't have enough hope",
|
"insufficientHope": "The initiating character doesn't have enough hope",
|
||||||
"createTagTeam": "Create TagTeam Roll",
|
"createTagTeam": "Create Tag Team Roll",
|
||||||
"chatMessageRollTitle": "Roll"
|
"chatMessageRollTitle": "Roll",
|
||||||
|
"cancelConfirmTitle": "Cancel Tag Team Roll",
|
||||||
|
"cancelConfirmText": "Are you sure you want to cancel the Tag Team Roll? This will close it for all other players too.",
|
||||||
|
"hints": {
|
||||||
|
"completeRolls": "Set up and complete the rolls for the characters",
|
||||||
|
"selectRoll": "Select which roll value to be used for the Tag Team"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"GroupRollSelect": {
|
||||||
|
"cancelConfirmText": "Are you sure you want to cancel the Group Roll? This will close it for all other players too.",
|
||||||
|
"cancelConfirmTitle": "Cancel Group Roll",
|
||||||
|
"initializationTitle": "Character Selection",
|
||||||
|
"finishGroupRoll": "Finish Group Roll",
|
||||||
|
"leader": "Leader",
|
||||||
|
"leaderRoll": "Leader Roll",
|
||||||
|
"members": "Members",
|
||||||
|
"openDialogForAll": "Open Dialog For All",
|
||||||
|
"removeRoll": "Remove Roll",
|
||||||
|
"resultsHint": "Results will appear when characters roll",
|
||||||
|
"selectLeaderHint": "Select one Character to be the leader",
|
||||||
|
"selectParticipantsHint": "Select one Character to be the leader",
|
||||||
|
"startGroupRoll": "Start Group Roll",
|
||||||
|
"title": "Group Roll"
|
||||||
},
|
},
|
||||||
"TokenConfig": {
|
"TokenConfig": {
|
||||||
"actorSizeUsed": "Actor size is set, determining the dimensions"
|
"actorSizeUsed": "Actor size is set, determining the dimensions",
|
||||||
|
"tokenSize": "Token Size",
|
||||||
|
"sizeCategory": "Size Category"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"CLASS": {
|
"CLASS": {
|
||||||
|
|
@ -697,6 +790,20 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"CONFIG": {
|
"CONFIG": {
|
||||||
|
"ActiveEffectDuration": {
|
||||||
|
"temporary": "Temporary",
|
||||||
|
"act": "Next Spotlight",
|
||||||
|
"scene": "Next Scene",
|
||||||
|
"shortRest": "Next Rest",
|
||||||
|
"longRest": "Next Long Rest",
|
||||||
|
"session": "Next Session",
|
||||||
|
"custom": "Custom"
|
||||||
|
},
|
||||||
|
"ActionAutomationChoices": {
|
||||||
|
"never": "Never",
|
||||||
|
"showDialog": "Show Dialog Only",
|
||||||
|
"always": "Always"
|
||||||
|
},
|
||||||
"AdversaryTrait": {
|
"AdversaryTrait": {
|
||||||
"relentless": {
|
"relentless": {
|
||||||
"name": "Relentless",
|
"name": "Relentless",
|
||||||
|
|
@ -764,6 +871,11 @@
|
||||||
"bruiser": "for each Bruiser adversary.",
|
"bruiser": "for each Bruiser adversary.",
|
||||||
"solo": "for each Solo adversary."
|
"solo": "for each Solo adversary."
|
||||||
},
|
},
|
||||||
|
"ArmorInteraction": {
|
||||||
|
"none": { "label": "Ignores Armor" },
|
||||||
|
"active": { "label": "Active w/ Armor" },
|
||||||
|
"inactive": { "label": "Inactive w/ Armor" }
|
||||||
|
},
|
||||||
"ArmorFeature": {
|
"ArmorFeature": {
|
||||||
"burning": {
|
"burning": {
|
||||||
"name": "Burning",
|
"name": "Burning",
|
||||||
|
|
@ -1114,6 +1226,12 @@
|
||||||
"description": ""
|
"description": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"fallAndCollision": {
|
||||||
|
"veryClose": { "label": "Very Close", "chatTitle": "Fall Damage: Very Close" },
|
||||||
|
"close": { "label": "Close", "chatTitle": "Fall Damage: Close" },
|
||||||
|
"far": { "label": "Far", "chatTitle": "Fall Damage: Far" },
|
||||||
|
"collision": { "label": "Collision", "chatTitle": "Dangerous Collision" }
|
||||||
|
},
|
||||||
"FeatureForm": {
|
"FeatureForm": {
|
||||||
"label": "Feature Form",
|
"label": "Feature Form",
|
||||||
"passive": "Passive",
|
"passive": "Passive",
|
||||||
|
|
@ -1215,10 +1333,20 @@
|
||||||
"on": "On",
|
"on": "On",
|
||||||
"onWithToggle": "On With Toggle"
|
"onWithToggle": "On With Toggle"
|
||||||
},
|
},
|
||||||
|
"SceneRangeMeasurementTypes": {
|
||||||
|
"disable": "Disable Daggerheart Range Measurement",
|
||||||
|
"default": "Default",
|
||||||
|
"custom": "Custom"
|
||||||
|
},
|
||||||
"SelectAction": {
|
"SelectAction": {
|
||||||
"selectType": "Select Action Type",
|
"selectType": "Select Action Type",
|
||||||
"selectAction": "Action Selection"
|
"selectAction": "Action Selection"
|
||||||
},
|
},
|
||||||
|
"TagTeamRollTypes": {
|
||||||
|
"trait": "Trait",
|
||||||
|
"ability": "Ability",
|
||||||
|
"damageAbility": "Damage Ability"
|
||||||
|
},
|
||||||
"TargetTypes": {
|
"TargetTypes": {
|
||||||
"any": "Any",
|
"any": "Any",
|
||||||
"friendly": "Friendly",
|
"friendly": "Friendly",
|
||||||
|
|
@ -1231,8 +1359,8 @@
|
||||||
"cone": "Cone",
|
"cone": "Cone",
|
||||||
"emanation": "Emanation",
|
"emanation": "Emanation",
|
||||||
"inFront": "In Front",
|
"inFront": "In Front",
|
||||||
"rect": "Rectangle",
|
"rectangle": "Rectangle",
|
||||||
"ray": "Ray"
|
"line": "Line"
|
||||||
},
|
},
|
||||||
"TokenSize": {
|
"TokenSize": {
|
||||||
"tiny": "Tiny",
|
"tiny": "Tiny",
|
||||||
|
|
@ -1847,6 +1975,17 @@
|
||||||
"name": "Healing Roll"
|
"name": "Healing Roll"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ChangeTypes": {
|
||||||
|
"armor": {
|
||||||
|
"newArmorEffect": "Armor Effect",
|
||||||
|
"FIELDS": {
|
||||||
|
"interaction": {
|
||||||
|
"label": "Armor Interaction",
|
||||||
|
"hint": "Does the character wearing armor suppress this effect?"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"Duration": {
|
"Duration": {
|
||||||
"passive": "Passive",
|
"passive": "Passive",
|
||||||
"temporary": "Temporary"
|
"temporary": "Temporary"
|
||||||
|
|
@ -1871,6 +2010,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"GENERAL": {
|
"GENERAL": {
|
||||||
|
"Ability": {
|
||||||
|
"single": "Ability",
|
||||||
|
"plural": "Abilities"
|
||||||
|
},
|
||||||
"Action": {
|
"Action": {
|
||||||
"single": "Action",
|
"single": "Action",
|
||||||
"plural": "Actions"
|
"plural": "Actions"
|
||||||
|
|
@ -1894,6 +2037,10 @@
|
||||||
"hint": "Multiply any damage dealt to you by this number"
|
"hint": "Multiply any damage dealt to you by this number"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Battlepoints": {
|
||||||
|
"full": "Battlepoints",
|
||||||
|
"short": "BP"
|
||||||
|
},
|
||||||
"Bonuses": {
|
"Bonuses": {
|
||||||
"rest": {
|
"rest": {
|
||||||
"downtimeAction": "Downtime Action",
|
"downtimeAction": "Downtime Action",
|
||||||
|
|
@ -2252,6 +2399,7 @@
|
||||||
"duality": "Duality",
|
"duality": "Duality",
|
||||||
"dualityDice": "Duality Dice",
|
"dualityDice": "Duality Dice",
|
||||||
"dualityRoll": "Duality Roll",
|
"dualityRoll": "Duality Roll",
|
||||||
|
"effect": "Effect",
|
||||||
"enabled": "Enabled",
|
"enabled": "Enabled",
|
||||||
"evasion": "Evasion",
|
"evasion": "Evasion",
|
||||||
"equipment": "Equipment",
|
"equipment": "Equipment",
|
||||||
|
|
@ -2300,13 +2448,17 @@
|
||||||
"single": "Miss",
|
"single": "Miss",
|
||||||
"plural": "Miss"
|
"plural": "Miss"
|
||||||
},
|
},
|
||||||
|
"missingX": "Missing {x}",
|
||||||
"maxWithThing": "Max {thing}",
|
"maxWithThing": "Max {thing}",
|
||||||
"missingDragDropThing": "Drop {thing} here",
|
"missingDragDropThing": "Drop {thing} here",
|
||||||
"multiclass": "Multiclass",
|
"multiclass": "Multiclass",
|
||||||
|
"name": "Name",
|
||||||
"newCategory": "New Category",
|
"newCategory": "New Category",
|
||||||
"newThing": "New {thing}",
|
"newThing": "New {thing}",
|
||||||
|
"next": "Next",
|
||||||
"none": "None",
|
"none": "None",
|
||||||
"noTarget": "No current target",
|
"noTarget": "No current target",
|
||||||
|
"optionalThing": "Optional {thing}",
|
||||||
"partner": "Partner",
|
"partner": "Partner",
|
||||||
"player": {
|
"player": {
|
||||||
"single": "Player",
|
"single": "Player",
|
||||||
|
|
@ -2324,14 +2476,20 @@
|
||||||
"rerolled": "Rerolled",
|
"rerolled": "Rerolled",
|
||||||
"rerollThing": "Reroll {thing}",
|
"rerollThing": "Reroll {thing}",
|
||||||
"resource": "Resource",
|
"resource": "Resource",
|
||||||
|
"result": {
|
||||||
|
"single": "Result",
|
||||||
|
"plural": "Results"
|
||||||
|
},
|
||||||
"roll": "Roll",
|
"roll": "Roll",
|
||||||
"rollAll": "Roll All",
|
"rollAll": "Roll All",
|
||||||
"rollDamage": "Roll Damage",
|
"rollDamage": "Roll Damage",
|
||||||
"rollWith": "{roll} Roll",
|
"rollWith": "{roll} Roll",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
|
"saveSettings": "Save Settings",
|
||||||
"scalable": "Scalable",
|
"scalable": "Scalable",
|
||||||
"scars": "Scars",
|
"scars": "Scars",
|
||||||
"situationalBonus": "Situational Bonus",
|
"situationalBonus": "Situational Bonus",
|
||||||
|
"searchPlaceholder": "Search...",
|
||||||
"spent": "Spent",
|
"spent": "Spent",
|
||||||
"step": "Step",
|
"step": "Step",
|
||||||
"stress": "Stress",
|
"stress": "Stress",
|
||||||
|
|
@ -2381,6 +2539,9 @@
|
||||||
"recovery": { "label": "Recovery" },
|
"recovery": { "label": "Recovery" },
|
||||||
"type": { "label": "Type" },
|
"type": { "label": "Type" },
|
||||||
"value": { "label": "Value" }
|
"value": { "label": "Value" }
|
||||||
|
},
|
||||||
|
"identifier": {
|
||||||
|
"label": "Identifier"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Ancestry": {
|
"Ancestry": {
|
||||||
|
|
@ -2406,10 +2567,11 @@
|
||||||
"tokenImg": { "label": "Token Image" },
|
"tokenImg": { "label": "Token Image" },
|
||||||
"tokenRingImg": { "label": "Subject Texture" },
|
"tokenRingImg": { "label": "Subject Texture" },
|
||||||
"tokenSize": {
|
"tokenSize": {
|
||||||
"placeholder": "Using character dimensions",
|
"placeholder": "Token Size",
|
||||||
"disabledPlaceholder": "Set by character size",
|
"disabledPlaceholder": "Token Size",
|
||||||
"height": { "label": "Height" },
|
"height": { "label": "Height" },
|
||||||
"width": { "label": "Width" },
|
"width": { "label": "Width" },
|
||||||
|
"depth": { "label": "Depth" },
|
||||||
"scale": { "label": "Token Scale" }
|
"scale": { "label": "Token Scale" }
|
||||||
},
|
},
|
||||||
"evolved": {
|
"evolved": {
|
||||||
|
|
@ -2482,15 +2644,20 @@
|
||||||
},
|
},
|
||||||
"Weapon": {
|
"Weapon": {
|
||||||
"weaponType": "Weapon Type",
|
"weaponType": "Weapon Type",
|
||||||
"primaryWeapon": "Primary Weapon",
|
"primaryWeapon": {
|
||||||
"secondaryWeapon": "Secondary Weapon"
|
"full": "Primary Weapon",
|
||||||
|
"short": "Primary"
|
||||||
|
},
|
||||||
|
"secondaryWeapon": {
|
||||||
|
"full": "Secondary Weapon",
|
||||||
|
"short": "Secondary"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"MACROS": {
|
"MACROS": {
|
||||||
"Spotlight": {
|
"Spotlight": {
|
||||||
"errors": {
|
"errors": {
|
||||||
"noActiveCombat": "There is no active encounter",
|
"noTokenSelected": "A token on the canvas must either be selected or hovered to spotlight it"
|
||||||
"noCombatantSelected": "A combatant token must be either selected or hovered to spotlight it"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -2565,6 +2732,10 @@
|
||||||
"hint": "Automatically increase the GM's fear pool on a fear duality roll result."
|
"hint": "Automatically increase the GM's fear pool on a fear duality roll result."
|
||||||
},
|
},
|
||||||
"FIELDS": {
|
"FIELDS": {
|
||||||
|
"autoExpireActiveEffects": {
|
||||||
|
"label": "Auto Expire Active Effects",
|
||||||
|
"hint": "Active Effects with set durations will automatically be removed when their durations are up"
|
||||||
|
},
|
||||||
"damageReductionRulesDefault": {
|
"damageReductionRulesDefault": {
|
||||||
"label": "Damage Reduction Rules Default",
|
"label": "Damage Reduction Rules Default",
|
||||||
"hint": "Wether using armor and reductions has rules on by default"
|
"hint": "Wether using armor and reductions has rules on by default"
|
||||||
|
|
@ -2662,6 +2833,15 @@
|
||||||
"hideObserverPermissionInChat": {
|
"hideObserverPermissionInChat": {
|
||||||
"label": "Hide Chat Info From Players",
|
"label": "Hide Chat Info From Players",
|
||||||
"hint": "Information such as hit/miss on attack rolls against adversaries will be hidden"
|
"hint": "Information such as hit/miss on attack rolls against adversaries will be hidden"
|
||||||
|
},
|
||||||
|
"hidePartyStats": {
|
||||||
|
"label": "Hide Party Stats",
|
||||||
|
"hint": "Resources and stats in the party sheet's member list will be hidden to the following users, even if the user is part of the same party",
|
||||||
|
"choices": {
|
||||||
|
"never": "Never, always show",
|
||||||
|
"players": "Hide From Players",
|
||||||
|
"always": "Hide from Everyone"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -2734,6 +2914,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Keybindings": {
|
"Keybindings": {
|
||||||
|
"partySheet": {
|
||||||
|
"name": "Toggle Party Sheet",
|
||||||
|
"hint": "Open or close the active party's sheet"
|
||||||
|
},
|
||||||
"spotlight": {
|
"spotlight": {
|
||||||
"name": "Spotlight Combatant",
|
"name": "Spotlight Combatant",
|
||||||
"hint": "Move the spotlight to a hovered or selected token that's present in an active encounter"
|
"hint": "Move the spotlight to a hovered or selected token that's present in an active encounter"
|
||||||
|
|
@ -2778,6 +2962,7 @@
|
||||||
"system": "Dice Preset",
|
"system": "Dice Preset",
|
||||||
"font": "Font",
|
"font": "Font",
|
||||||
"critical": "Duality Critical Animation",
|
"critical": "Duality Critical Animation",
|
||||||
|
"muted": "Muted",
|
||||||
"diceAppearance": "Dice Appearance",
|
"diceAppearance": "Dice Appearance",
|
||||||
"animations": "Animations",
|
"animations": "Animations",
|
||||||
"defaultAnimations": "Set Animations As Player Defaults",
|
"defaultAnimations": "Set Animations As Player Defaults",
|
||||||
|
|
@ -2886,18 +3071,6 @@
|
||||||
"immunityTo": "Immunity: {immunities}"
|
"immunityTo": "Immunity: {immunities}"
|
||||||
},
|
},
|
||||||
"featureTitle": "Class Feature",
|
"featureTitle": "Class Feature",
|
||||||
"groupRoll": {
|
|
||||||
"title": "Group Roll",
|
|
||||||
"leader": "Leader",
|
|
||||||
"partyTeam": "Party Team",
|
|
||||||
"team": "Team",
|
|
||||||
"selectLeader": "Select a Leader",
|
|
||||||
"selectMember": "Select a Member",
|
|
||||||
"rerollTitle": "Reroll Group Roll",
|
|
||||||
"rerollContent": "Are you sure you want to reroll your {trait} roll?",
|
|
||||||
"rerollTooltip": "Reroll",
|
|
||||||
"wholePartySelected": "The whole party is selected"
|
|
||||||
},
|
|
||||||
"healingRoll": {
|
"healingRoll": {
|
||||||
"title": "Heal - {damage}",
|
"title": "Heal - {damage}",
|
||||||
"heal": "Heal",
|
"heal": "Heal",
|
||||||
|
|
@ -2915,12 +3088,16 @@
|
||||||
"resourceRoll": {
|
"resourceRoll": {
|
||||||
"playerMessage": "{user} rerolled their {name}"
|
"playerMessage": "{user} rerolled their {name}"
|
||||||
},
|
},
|
||||||
|
"saveRoll": {
|
||||||
|
"reactionRollAllTargets": "Reaction Roll All Targets"
|
||||||
|
},
|
||||||
"tagTeam": {
|
"tagTeam": {
|
||||||
"title": "Tag Team",
|
"title": "Tag Team",
|
||||||
"membersTitle": "Members"
|
"membersTitle": "Members"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ChatLog": {
|
"ChatLog": {
|
||||||
|
"rerollActionRoll": "Reroll Action",
|
||||||
"rerollDamage": "Reroll Damage",
|
"rerollDamage": "Reroll Damage",
|
||||||
"assignTagRoll": "Assign as Tag Roll"
|
"assignTagRoll": "Assign as Tag Roll"
|
||||||
},
|
},
|
||||||
|
|
@ -2937,13 +3114,15 @@
|
||||||
},
|
},
|
||||||
"EffectsDisplay": {
|
"EffectsDisplay": {
|
||||||
"removeThing": "[Right Click] Remove {thing}",
|
"removeThing": "[Right Click] Remove {thing}",
|
||||||
|
"increaseStacks": "[Left Click] Increment Stacks",
|
||||||
|
"decreaseStacks": "[Right Click] Decrement Stacks",
|
||||||
"appliedBy": "Applied By: {by}"
|
"appliedBy": "Applied By: {by}"
|
||||||
},
|
},
|
||||||
"ItemBrowser": {
|
"ItemBrowser": {
|
||||||
"title": "Daggerheart Compendium Browser",
|
"title": "Daggerheart Compendium Browser",
|
||||||
|
"windowTitle": "Compendium Browser",
|
||||||
"hint": "Select a Folder in sidebar to start browsing through the compendium",
|
"hint": "Select a Folder in sidebar to start browsing through the compendium",
|
||||||
"browserSettings": "Browser Settings",
|
"browserSettings": "Browser Settings",
|
||||||
"searchPlaceholder": "Search...",
|
|
||||||
"columnName": "Name",
|
"columnName": "Name",
|
||||||
"tooltipFilters": "Filters",
|
"tooltipFilters": "Filters",
|
||||||
"tooltipErase": "Erase",
|
"tooltipErase": "Erase",
|
||||||
|
|
@ -2979,7 +3158,7 @@
|
||||||
"weapons": "Weapons",
|
"weapons": "Weapons",
|
||||||
"armors": "Armors",
|
"armors": "Armors",
|
||||||
"consumables": "Consumables",
|
"consumables": "Consumables",
|
||||||
"loots": "Loots"
|
"loots": "Loot"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Notifications": {
|
"Notifications": {
|
||||||
|
|
@ -3052,7 +3231,6 @@
|
||||||
"subclassesAlreadyPresent": "You already have a class and multiclass subclass",
|
"subclassesAlreadyPresent": "You already have a class and multiclass subclass",
|
||||||
"noDiceSystem": "Your selected dice {system} does not have a {faces} dice",
|
"noDiceSystem": "Your selected dice {system} does not have a {faces} dice",
|
||||||
"gmMenuRefresh": "You refreshed all actions and resources {types}",
|
"gmMenuRefresh": "You refreshed all actions and resources {types}",
|
||||||
"subclassAlreadyLinked": "{name} is already a subclass in the class {class}. Remove it from there if you want it to be a subclass to this class.",
|
|
||||||
"gmRequired": "This action requires an online GM",
|
"gmRequired": "This action requires an online GM",
|
||||||
"gmOnly": "This can only be accessed by the GM",
|
"gmOnly": "This can only be accessed by the GM",
|
||||||
"noActorOwnership": "You do not have permissions for this character",
|
"noActorOwnership": "You do not have permissions for this character",
|
||||||
|
|
@ -3061,7 +3239,12 @@
|
||||||
"tokenActorsMissing": "[{names}] missing Actors",
|
"tokenActorsMissing": "[{names}] missing Actors",
|
||||||
"domainTouchRequirement": "This domain card requires {nr} {domain} cards in the loadout to be used",
|
"domainTouchRequirement": "This domain card requires {nr} {domain} cards in the loadout to be used",
|
||||||
"knowTheTide": "Know The Tide gained a token",
|
"knowTheTide": "Know The Tide gained a token",
|
||||||
"lackingItemTransferPermission": "User {user} lacks owner permission needed to transfer items to {target}"
|
"lackingItemTransferPermission": "User {user} lacks owner permission needed to transfer items to {target}",
|
||||||
|
"noTokenTargeted": "No token is targeted",
|
||||||
|
"behaviorRegionRequiresGM": "Creating a Region with an attached Behavior requires an online GM"
|
||||||
|
},
|
||||||
|
"Progress": {
|
||||||
|
"migrationLabel": "Performing system migration. Please wait and do not close Foundry."
|
||||||
},
|
},
|
||||||
"Sidebar": {
|
"Sidebar": {
|
||||||
"actorDirectory": {
|
"actorDirectory": {
|
||||||
|
|
@ -3070,6 +3253,9 @@
|
||||||
"companion": "Level {level} - {partner}",
|
"companion": "Level {level} - {partner}",
|
||||||
"companionNoPartner": "No Partner",
|
"companionNoPartner": "No Partner",
|
||||||
"duplicateToNewTier": "Duplicate to New Tier",
|
"duplicateToNewTier": "Duplicate to New Tier",
|
||||||
|
"activateParty": "Make Active Party",
|
||||||
|
"partyIsActive": "Active",
|
||||||
|
"createAdversary": "Create Adversary",
|
||||||
"pickTierTitle": "Pick a new tier for this adversary"
|
"pickTierTitle": "Pick a new tier for this adversary"
|
||||||
},
|
},
|
||||||
"daggerheartMenu": {
|
"daggerheartMenu": {
|
||||||
|
|
@ -3081,6 +3267,7 @@
|
||||||
"Tooltip": {
|
"Tooltip": {
|
||||||
"disableEffect": "Disable Effect",
|
"disableEffect": "Disable Effect",
|
||||||
"enableEffect": "Enable Effect",
|
"enableEffect": "Enable Effect",
|
||||||
|
"edit": "Edit",
|
||||||
"openItemWorld": "Open Item World",
|
"openItemWorld": "Open Item World",
|
||||||
"openActorWorld": "Open Actor World",
|
"openActorWorld": "Open Actor World",
|
||||||
"sendToChat": "Send to Chat",
|
"sendToChat": "Send to Chat",
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,10 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
||||||
this.character = character;
|
this.character = character;
|
||||||
|
|
||||||
this.setup = {
|
this.setup = {
|
||||||
traits: this.character.system.traits,
|
traits: Object.keys(this.character.system.traits).reduce((acc, key) => {
|
||||||
|
acc[key] = { value: null };
|
||||||
|
return acc;
|
||||||
|
}, {}),
|
||||||
ancestryName: {
|
ancestryName: {
|
||||||
primary: '',
|
primary: '',
|
||||||
secondary: ''
|
secondary: ''
|
||||||
|
|
@ -377,8 +380,10 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
||||||
];
|
];
|
||||||
return Object.values(this.setup.traits).reduce((acc, x) => {
|
return Object.values(this.setup.traits).reduce((acc, x) => {
|
||||||
const index = traitCompareArray.indexOf(x.value);
|
const index = traitCompareArray.indexOf(x.value);
|
||||||
|
if (index === -1) return acc;
|
||||||
|
|
||||||
traitCompareArray.splice(index, 1);
|
traitCompareArray.splice(index, 1);
|
||||||
acc += index !== -1;
|
acc += 1;
|
||||||
return acc;
|
return acc;
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
@ -434,15 +439,18 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
||||||
'system.domain': { key: 'system.domain', value: this.setup.class?.system.domains ?? null }
|
'system.domain': { key: 'system.domain', value: this.setup.class?.system.domains ?? null }
|
||||||
};
|
};
|
||||||
|
|
||||||
if (type === 'subclasses')
|
if (type === 'subclasses') {
|
||||||
|
const classItem = this.setup.class;
|
||||||
|
const uuid = classItem?._stats.compendiumSource ?? classItem?.uuid;
|
||||||
presets.filter = {
|
presets.filter = {
|
||||||
'system.linkedClass.uuid': { key: 'system.linkedClass.uuid', value: this.setup.class?.uuid }
|
'system.linkedClass': { key: 'system.linkedClass', value: uuid }
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (equipment.includes(type))
|
if (equipment.includes(type))
|
||||||
presets.filter = {
|
presets.filter = {
|
||||||
'system.tier': { key: 'system.tier', value: 1 },
|
'system.tier': { key: 'system.tier', value: 1 },
|
||||||
'type': { key: 'type', value: type }
|
type: { key: 'type', value: type }
|
||||||
};
|
};
|
||||||
|
|
||||||
ui.compendiumBrowser.open(presets);
|
ui.compendiumBrowser.open(presets);
|
||||||
|
|
@ -554,7 +562,7 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
||||||
experiences: {
|
experiences: {
|
||||||
...this.setup.experiences,
|
...this.setup.experiences,
|
||||||
...Object.keys(this.character.system.experiences).reduce((acc, key) => {
|
...Object.keys(this.character.system.experiences).reduce((acc, key) => {
|
||||||
acc[`-=${key}`] = null;
|
acc[`${key}`] = _del;
|
||||||
return acc;
|
return acc;
|
||||||
}, {})
|
}, {})
|
||||||
}
|
}
|
||||||
|
|
@ -605,7 +613,8 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
||||||
[foundry.utils.randomID()]: {}
|
[foundry.utils.randomID()]: {}
|
||||||
};
|
};
|
||||||
} else if (item.type === 'subclass' && event.target.closest('.subclass-card')) {
|
} else if (item.type === 'subclass' && event.target.closest('.subclass-card')) {
|
||||||
if (this.setup.class.system.subclasses.every(subclass => subclass.uuid !== item.uuid)) {
|
const classSubclasses = await this.setup.class.system.fetchSubclasses();
|
||||||
|
if (classSubclasses.every(subclass => subclass.uuid !== item.uuid)) {
|
||||||
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.subclassNotInClass'));
|
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.subclassNotInClass'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ export default class CompendiumBrowserSettings extends HandlebarsApplicationMixi
|
||||||
const excludedSourceData = this.browserSettings.excludedSources;
|
const excludedSourceData = this.browserSettings.excludedSources;
|
||||||
const excludedPackData = this.browserSettings.excludedPacks;
|
const excludedPackData = this.browserSettings.excludedPacks;
|
||||||
context.typePackCollections = game.packs.reduce((acc, pack) => {
|
context.typePackCollections = game.packs.reduce((acc, pack) => {
|
||||||
const { type, label, packageType, packageName: basePackageName, id } = pack.metadata;
|
const { type, label, packageType, packageName: basePackageName, name, id } = pack.metadata;
|
||||||
if (!CompendiumBrowserSettings.#browserPackTypes.includes(type)) return acc;
|
if (!CompendiumBrowserSettings.#browserPackTypes.includes(type)) return acc;
|
||||||
|
|
||||||
const isWorldPack = packageType === 'world';
|
const isWorldPack = packageType === 'world';
|
||||||
|
|
@ -68,13 +68,15 @@ export default class CompendiumBrowserSettings extends HandlebarsApplicationMixi
|
||||||
if (!acc[type].sources[packageName])
|
if (!acc[type].sources[packageName])
|
||||||
acc[type].sources[packageName] = { label: sourceLabel, checked: sourceChecked, packs: [] };
|
acc[type].sources[packageName] = { label: sourceLabel, checked: sourceChecked, packs: [] };
|
||||||
|
|
||||||
const checked = !excludedPackData[id] || !excludedPackData[id].excludedDocumentTypes.includes(type);
|
const included =
|
||||||
|
!excludedPackData[packageName] ||
|
||||||
|
!excludedPackData[packageName][name]?.excludedDocumentTypes.includes(type);
|
||||||
|
|
||||||
acc[type].sources[packageName].packs.push({
|
acc[type].sources[packageName].packs.push({
|
||||||
pack: id,
|
name,
|
||||||
type,
|
type,
|
||||||
label: id === game.system.id ? game.system.title : game.i18n.localize(label),
|
label: id === game.system.id ? game.system.title : game.i18n.localize(label),
|
||||||
checked: checked
|
checked: included
|
||||||
});
|
});
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
|
|
@ -106,16 +108,16 @@ export default class CompendiumBrowserSettings extends HandlebarsApplicationMixi
|
||||||
toggleTypedPack(event) {
|
toggleTypedPack(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
const { type, pack } = event.target.dataset;
|
const { type, source, packName } = event.target.dataset;
|
||||||
const currentlyExcluded = this.browserSettings.excludedPacks[pack]
|
const currentlyExcluded = this.browserSettings.excludedPacks[source]?.[packName]
|
||||||
? this.browserSettings.excludedPacks[pack].excludedDocumentTypes.includes(type)
|
? this.browserSettings.excludedPacks[source][packName].excludedDocumentTypes.includes(type)
|
||||||
: false;
|
: false;
|
||||||
|
|
||||||
if (!this.browserSettings.excludedPacks[pack])
|
this.browserSettings.excludedPacks[source] ??= {};
|
||||||
this.browserSettings.excludedPacks[pack] = { excludedDocumentTypes: [] };
|
this.browserSettings.excludedPacks[source][packName] ??= { excludedDocumentTypes: [] };
|
||||||
this.browserSettings.excludedPacks[pack].excludedDocumentTypes = currentlyExcluded
|
this.browserSettings.excludedPacks[source][packName].excludedDocumentTypes = currentlyExcluded
|
||||||
? this.browserSettings.excludedPacks[pack].excludedDocumentTypes.filter(x => x !== type)
|
? this.browserSettings.excludedPacks[source][packName].excludedDocumentTypes.filter(x => x !== type)
|
||||||
: [...(this.browserSettings.excludedPacks[pack]?.excludedDocumentTypes ?? []), type];
|
: [...(this.browserSettings.excludedPacks[source][packName]?.excludedDocumentTypes ?? []), type];
|
||||||
|
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,9 @@ export { default as ImageSelectDialog } from './imageSelectDialog.mjs';
|
||||||
export { default as ItemTransferDialog } from './itemTransfer.mjs';
|
export { default as ItemTransferDialog } from './itemTransfer.mjs';
|
||||||
export { default as MulticlassChoiceDialog } from './multiclassChoiceDialog.mjs';
|
export { default as MulticlassChoiceDialog } from './multiclassChoiceDialog.mjs';
|
||||||
export { default as OwnershipSelection } from './ownershipSelection.mjs';
|
export { default as OwnershipSelection } from './ownershipSelection.mjs';
|
||||||
export { default as RerollDamageDialog } from './rerollDamageDialog.mjs';
|
|
||||||
export { default as ResourceDiceDialog } from './resourceDiceDialog.mjs';
|
export { default as ResourceDiceDialog } from './resourceDiceDialog.mjs';
|
||||||
export { default as ActionSelectionDialog } from './actionSelectionDialog.mjs';
|
export { default as ActionSelectionDialog } from './actionSelectionDialog.mjs';
|
||||||
export { default as GroupRollDialog } from './group-roll-dialog.mjs';
|
|
||||||
export { default as TagTeamDialog } from './tagTeamDialog.mjs';
|
export { default as TagTeamDialog } from './tagTeamDialog.mjs';
|
||||||
|
export { default as GroupRollDialog } from './groupRollDialog.mjs';
|
||||||
export { default as RiskItAllDialog } from './riskItAllDialog.mjs';
|
export { default as RiskItAllDialog } from './riskItAllDialog.mjs';
|
||||||
export { default as CompendiumBrowserSettingsDialog } from './CompendiumBrowserSettings.mjs';
|
export { default as CompendiumBrowserSettingsDialog } from './CompendiumBrowserSettings.mjs';
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ export default class ActionSelectionDialog extends HandlebarsApplicationMixin(Ap
|
||||||
|
|
||||||
static async #onChooseAction(event, button) {
|
static async #onChooseAction(event, button) {
|
||||||
const { actionId } = button.dataset;
|
const { actionId } = button.dataset;
|
||||||
this.#action = this.#item.system.actionsList.find(a => a._id === actionId);
|
this.#action = this.item.system.actionsList.find(a => a._id === actionId);
|
||||||
Object.defineProperty(this.#event, 'shiftKey', {
|
Object.defineProperty(this.#event, 'shiftKey', {
|
||||||
get() {
|
get() {
|
||||||
return event.shiftKey;
|
return event.shiftKey;
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,12 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
|
||||||
this.selected = null;
|
this.selected = null;
|
||||||
this.evolved = { form: null };
|
this.evolved = { form: null };
|
||||||
this.hybrid = { forms: {}, advantages: {}, features: {} };
|
this.hybrid = { forms: {}, advantages: {}, features: {} };
|
||||||
|
this.modifications = {
|
||||||
|
traitBonuses: configData.modifications.traitBonuses.map(x => ({
|
||||||
|
trait: null,
|
||||||
|
bonus: x.bonus
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
|
||||||
this._dragDrop = this._createDragDropHandlers();
|
this._dragDrop = this._createDragDropHandlers();
|
||||||
}
|
}
|
||||||
|
|
@ -28,6 +34,7 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
|
||||||
selectBeastform: this.selectBeastform,
|
selectBeastform: this.selectBeastform,
|
||||||
toggleHybridFeature: this.toggleHybridFeature,
|
toggleHybridFeature: this.toggleHybridFeature,
|
||||||
toggleHybridAdvantage: this.toggleHybridAdvantage,
|
toggleHybridAdvantage: this.toggleHybridAdvantage,
|
||||||
|
toggleTraitBonus: this.toggleTraitBonus,
|
||||||
submitBeastform: this.submitBeastform
|
submitBeastform: this.submitBeastform
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
|
|
@ -48,6 +55,7 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
|
||||||
tabs: { template: 'systems/daggerheart/templates/dialogs/beastform/tabs.hbs' },
|
tabs: { template: 'systems/daggerheart/templates/dialogs/beastform/tabs.hbs' },
|
||||||
beastformTier: { template: 'systems/daggerheart/templates/dialogs/beastform/beastformTier.hbs' },
|
beastformTier: { template: 'systems/daggerheart/templates/dialogs/beastform/beastformTier.hbs' },
|
||||||
advanced: { template: 'systems/daggerheart/templates/dialogs/beastform/advanced.hbs' },
|
advanced: { template: 'systems/daggerheart/templates/dialogs/beastform/advanced.hbs' },
|
||||||
|
modifications: { template: 'systems/daggerheart/templates/dialogs/beastform/modifications.hbs' },
|
||||||
footer: { template: 'systems/daggerheart/templates/dialogs/beastform/footer.hbs' }
|
footer: { template: 'systems/daggerheart/templates/dialogs/beastform/footer.hbs' }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -146,6 +154,9 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
context.modifications = this.modifications;
|
||||||
|
context.traits = CONFIG.DH.ACTOR.abilities;
|
||||||
|
|
||||||
context.tier = beastformTiers[this.tabGroups.primary];
|
context.tier = beastformTiers[this.tabGroups.primary];
|
||||||
context.tierKey = this.tabGroups.primary;
|
context.tierKey = this.tabGroups.primary;
|
||||||
|
|
||||||
|
|
@ -155,6 +166,9 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
|
||||||
}
|
}
|
||||||
|
|
||||||
canSubmit() {
|
canSubmit() {
|
||||||
|
const modificationsFinished = this.modifications.traitBonuses.every(x => x.trait);
|
||||||
|
if (!modificationsFinished) return false;
|
||||||
|
|
||||||
if (this.selected) {
|
if (this.selected) {
|
||||||
switch (this.selected.system.beastformType) {
|
switch (this.selected.system.beastformType) {
|
||||||
case 'normal':
|
case 'normal':
|
||||||
|
|
@ -261,6 +275,13 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static toggleTraitBonus(_, button) {
|
||||||
|
const { index, trait } = button.dataset;
|
||||||
|
this.modifications.traitBonuses[index].trait =
|
||||||
|
this.modifications.traitBonuses[index].trait === trait ? null : trait;
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
static async submitBeastform() {
|
static async submitBeastform() {
|
||||||
await this.close({ submitted: true });
|
await this.close({ submitted: true });
|
||||||
}
|
}
|
||||||
|
|
@ -292,6 +313,23 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const beastformEffect = selected.effects.find(x => x.type === 'beastform');
|
||||||
|
for (const traitBonus of app.modifications.traitBonuses) {
|
||||||
|
const existingChange = beastformEffect.changes.find(
|
||||||
|
x => x.key === `system.traits.${traitBonus.trait}.value`
|
||||||
|
);
|
||||||
|
if (existingChange) {
|
||||||
|
existingChange.value = Number.parseInt(existingChange.value) + traitBonus.bonus;
|
||||||
|
} else {
|
||||||
|
beastformEffect.changes.push({
|
||||||
|
key: `system.traits.${traitBonus.trait}.value`,
|
||||||
|
mode: 2,
|
||||||
|
priority: null,
|
||||||
|
value: traitBonus.bonus
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resolve({
|
resolve({
|
||||||
selected: selected,
|
selected: selected,
|
||||||
evolved: { ...app.evolved, form: evolved },
|
evolved: { ...app.evolved, form: evolved },
|
||||||
|
|
|
||||||
|
|
@ -77,8 +77,8 @@ export default class CharacterResetDialog extends HandlebarsApplicationMixin(App
|
||||||
|
|
||||||
if (!this.data.optional.portrait.keep) {
|
if (!this.data.optional.portrait.keep) {
|
||||||
foundry.utils.setProperty(update, 'img', this.actor.schema.fields.img.initial(this.actor));
|
foundry.utils.setProperty(update, 'img', this.actor.schema.fields.img.initial(this.actor));
|
||||||
foundry.utils.setProperty(update, 'prototypeToken.==texture', {});
|
foundry.utils.setProperty(update, 'prototypeToken.texture', _replace({}));
|
||||||
foundry.utils.setProperty(update, 'prototypeToken.==ring', {});
|
foundry.utils.setProperty(update, 'prototypeToken.ring', _replace({}));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.data.optional.biography.keep)
|
if (this.data.optional.biography.keep)
|
||||||
|
|
@ -89,7 +89,7 @@ export default class CharacterResetDialog extends HandlebarsApplicationMixin(App
|
||||||
const { system, ...rest } = update;
|
const { system, ...rest } = update;
|
||||||
await this.actor.update({
|
await this.actor.update({
|
||||||
...rest,
|
...rest,
|
||||||
'==system': system ?? {}
|
system: _replace(system ?? {})
|
||||||
});
|
});
|
||||||
|
|
||||||
const inventoryItemTypes = ['weapon', 'armor', 'consumable', 'loot'];
|
const inventoryItemTypes = ['weapon', 'armor', 'consumable', 'loot'];
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,6 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
updateIsAdvantage: this.updateIsAdvantage,
|
updateIsAdvantage: this.updateIsAdvantage,
|
||||||
selectExperience: this.selectExperience,
|
selectExperience: this.selectExperience,
|
||||||
toggleReaction: this.toggleReaction,
|
toggleReaction: this.toggleReaction,
|
||||||
toggleTagTeamRoll: this.toggleTagTeamRoll,
|
|
||||||
toggleSelectedEffect: this.toggleSelectedEffect,
|
toggleSelectedEffect: this.toggleSelectedEffect,
|
||||||
submitRoll: this.submitRoll
|
submitRoll: this.submitRoll
|
||||||
},
|
},
|
||||||
|
|
@ -71,8 +70,8 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
context.rollConfig = this.config;
|
context.rollConfig = this.config;
|
||||||
context.hasRoll = !!this.config.roll;
|
context.hasRoll = !!this.config.roll;
|
||||||
context.canRoll = true;
|
context.canRoll = true;
|
||||||
context.selectedRollMode = this.config.selectedRollMode ?? game.settings.get('core', 'rollMode');
|
context.selectedMessageMode = this.config.selectedMessageMode ?? game.settings.get('core', 'messageMode');
|
||||||
context.rollModes = Object.entries(CONFIG.Dice.rollModes).map(([action, { label, icon }]) => ({
|
context.rollModes = Object.entries(CONFIG.ChatMessage.modes).map(([action, { label, icon }]) => ({
|
||||||
action,
|
action,
|
||||||
label,
|
label,
|
||||||
icon
|
icon
|
||||||
|
|
@ -124,6 +123,10 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
context.advantage = this.config.roll?.advantage;
|
context.advantage = this.config.roll?.advantage;
|
||||||
context.disadvantage = this.config.roll?.disadvantage;
|
context.disadvantage = this.config.roll?.disadvantage;
|
||||||
context.diceOptions = CONFIG.DH.GENERAL.diceTypes;
|
context.diceOptions = CONFIG.DH.GENERAL.diceTypes;
|
||||||
|
context.dieFaces = CONFIG.DH.GENERAL.dieFaces.reduce((acc, face) => {
|
||||||
|
acc[face] = `d${face}`;
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
context.isLite = this.config.roll?.lite;
|
context.isLite = this.config.roll?.lite;
|
||||||
context.extraFormula = this.config.extraFormula;
|
context.extraFormula = this.config.extraFormula;
|
||||||
context.formula = this.roll.constructFormula(this.config);
|
context.formula = this.roll.constructFormula(this.config);
|
||||||
|
|
@ -133,12 +136,6 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
context.reactionOverride = this.reactionOverride;
|
context.reactionOverride = this.reactionOverride;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tagTeamSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
|
|
||||||
if (this.actor && tagTeamSetting.members[this.actor.id] && !this.config.skips?.createMessage) {
|
|
||||||
context.activeTagTeamRoll = true;
|
|
||||||
context.tagTeamSelected = this.config.tagTeamSelected;
|
|
||||||
}
|
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -149,19 +146,17 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
static updateRollConfiguration(event, _, formData) {
|
static updateRollConfiguration(_event, _, formData) {
|
||||||
const { ...rest } = foundry.utils.expandObject(formData.object);
|
const { ...rest } = foundry.utils.expandObject(formData.object);
|
||||||
|
|
||||||
this.config.selectedRollMode = rest.selectedRollMode;
|
this.config.selectedMessageMode = rest.selectedMessageMode;
|
||||||
|
|
||||||
if (this.config.costs) {
|
if (this.config.costs) {
|
||||||
this.config.costs = foundry.utils.mergeObject(this.config.costs, rest.costs);
|
this.config.costs = foundry.utils.mergeObject(this.config.costs, rest.costs);
|
||||||
}
|
}
|
||||||
if (this.config.uses) this.config.uses = foundry.utils.mergeObject(this.config.uses, rest.uses);
|
if (this.config.uses) this.config.uses = foundry.utils.mergeObject(this.config.uses, rest.uses);
|
||||||
if (rest.roll?.dice) {
|
if (rest.roll?.dice) {
|
||||||
Object.entries(rest.roll.dice).forEach(([key, value]) => {
|
this.roll = foundry.utils.mergeObject(this.roll, rest.roll.dice);
|
||||||
this.roll[key] = value;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if (rest.hasOwnProperty('trait')) {
|
if (rest.hasOwnProperty('trait')) {
|
||||||
this.config.roll.trait = rest.trait;
|
this.config.roll.trait = rest.trait;
|
||||||
|
|
@ -180,6 +175,15 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
this.disadvantage = advantage === -1;
|
this.disadvantage = advantage === -1;
|
||||||
|
|
||||||
this.config.roll.advantage = this.config.roll.advantage === advantage ? 0 : advantage;
|
this.config.roll.advantage = this.config.roll.advantage === advantage ? 0 : advantage;
|
||||||
|
if (this.config.roll.advantage === 0) return this.render();
|
||||||
|
|
||||||
|
const defaultFaces =
|
||||||
|
this.config.roll.advantage === 1
|
||||||
|
? this.config.data.rules.roll.defaultAdvantageDice
|
||||||
|
: this.config.data.rules.roll.defaultDisadvantageDice;
|
||||||
|
const faces = Number.parseInt(defaultFaces);
|
||||||
|
this.roll.advantageFaces = Number.isNaN(faces) ? this.roll.advantageFaces : faces;
|
||||||
|
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -215,11 +219,6 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static toggleTagTeamRoll() {
|
|
||||||
this.config.tagTeamSelected = !this.config.tagTeamSelected;
|
|
||||||
this.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
static toggleSelectedEffect(_event, button) {
|
static toggleSelectedEffect(_event, button) {
|
||||||
this.selectedEffects[button.dataset.key].selected = !this.selectedEffects[button.dataset.key].selected;
|
this.selectedEffects[button.dataset.key].selected = !this.selectedEffects[button.dataset.key].selected;
|
||||||
this.render();
|
this.render();
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
toggleSelectedEffect: this.toggleSelectedEffect,
|
toggleSelectedEffect: this.toggleSelectedEffect,
|
||||||
|
updateGroupAttack: this.updateGroupAttack,
|
||||||
|
toggleCritical: this.toggleCritical,
|
||||||
submitRoll: this.submitRoll
|
submitRoll: this.submitRoll
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
|
|
@ -52,8 +54,9 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
|
||||||
context.formula = this.roll.constructFormula(this.config);
|
context.formula = this.roll.constructFormula(this.config);
|
||||||
context.hasHealing = this.config.hasHealing;
|
context.hasHealing = this.config.hasHealing;
|
||||||
context.directDamage = this.config.directDamage;
|
context.directDamage = this.config.directDamage;
|
||||||
context.selectedRollMode = this.config.selectedRollMode;
|
context.selectedMessageMode = this.config.selectedMessageMode;
|
||||||
context.rollModes = Object.entries(CONFIG.Dice.rollModes).map(([action, { label, icon }]) => ({
|
context.isCritical = this.config.isCritical;
|
||||||
|
context.rollModes = Object.entries(CONFIG.ChatMessage.modes).map(([action, { label, icon }]) => ({
|
||||||
action,
|
action,
|
||||||
label,
|
label,
|
||||||
icon
|
icon
|
||||||
|
|
@ -62,15 +65,45 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
|
||||||
context.hasSelectedEffects = Boolean(Object.keys(this.selectedEffects).length);
|
context.hasSelectedEffects = Boolean(Object.keys(this.selectedEffects).length);
|
||||||
context.selectedEffects = this.selectedEffects;
|
context.selectedEffects = this.selectedEffects;
|
||||||
|
|
||||||
|
context.damageOptions = this.config.damageOptions;
|
||||||
|
context.rangeOptions = CONFIG.DH.GENERAL.groupAttackRange;
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
static updateRollConfiguration(_event, _, formData) {
|
static updateRollConfiguration(_event, _, formData) {
|
||||||
const { ...rest } = foundry.utils.expandObject(formData.object);
|
const data = foundry.utils.expandObject(formData.object);
|
||||||
foundry.utils.mergeObject(this.config.roll, rest.roll);
|
foundry.utils.mergeObject(this.config.roll, data.roll);
|
||||||
foundry.utils.mergeObject(this.config.modifiers, rest.modifiers);
|
foundry.utils.mergeObject(this.config.modifiers, data.modifiers);
|
||||||
this.config.selectedRollMode = rest.selectedRollMode;
|
this.config.selectedMessageMode = data.selectedMessageMode;
|
||||||
|
|
||||||
|
if (data.damageOptions) {
|
||||||
|
const numAttackers = data.damageOptions.groupAttack?.numAttackers;
|
||||||
|
if (typeof numAttackers !== 'number' || numAttackers % 1 !== 0) {
|
||||||
|
data.damageOptions.groupAttack.numAttackers = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
foundry.utils.mergeObject(this.config.damageOptions, data.damageOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
static updateGroupAttack() {
|
||||||
|
const targets = Array.from(game.user.targets);
|
||||||
|
if (targets.length === 0)
|
||||||
|
return ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.noTokenTargeted'));
|
||||||
|
|
||||||
|
const actorId = this.roll.data.parent.id;
|
||||||
|
const range = this.config.damageOptions.groupAttack.range;
|
||||||
|
const groupAttackTokens = game.system.api.fields.ActionFields.DamageField.getGroupAttackTokens(actorId, range);
|
||||||
|
|
||||||
|
this.config.damageOptions.groupAttack.numAttackers = groupAttackTokens.length;
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
static toggleCritical() {
|
||||||
|
this.config.isCritical = !this.config.isCritical;
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { damageKeyToNumber, getDamageLabel } from '../../helpers/utils.mjs';
|
import { damageKeyToNumber, getArmorSources, getDamageLabel } from '../../helpers/utils.mjs';
|
||||||
|
|
||||||
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||||
|
|
||||||
|
|
@ -10,6 +10,7 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
||||||
this.reject = reject;
|
this.reject = reject;
|
||||||
this.actor = actor;
|
this.actor = actor;
|
||||||
this.damage = damage;
|
this.damage = damage;
|
||||||
|
|
||||||
this.damageType = damageType;
|
this.damageType = damageType;
|
||||||
this.rulesDefault = game.settings.get(
|
this.rulesDefault = game.settings.get(
|
||||||
CONFIG.DH.id,
|
CONFIG.DH.id,
|
||||||
|
|
@ -20,14 +21,21 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
||||||
this.rulesDefault
|
this.rulesDefault
|
||||||
);
|
);
|
||||||
|
|
||||||
const canApplyArmor = damageType.every(t => actor.system.armorApplicableDamageTypes[t] === true);
|
const orderedArmorSources = getArmorSources(actor).filter(s => !s.disabled);
|
||||||
const availableArmor = actor.system.armorScore - actor.system.armor.system.marks.value;
|
const armor = orderedArmorSources.reduce((acc, { name, document }) => {
|
||||||
const maxArmorMarks = canApplyArmor ? availableArmor : 0;
|
const { current, max } = document.type === 'armor' ? document.system.armor : document.system.armorData;
|
||||||
|
acc.push({
|
||||||
const armor = [...Array(maxArmorMarks).keys()].reduce((acc, _) => {
|
name,
|
||||||
acc[foundry.utils.randomID()] = { selected: false };
|
effect: document,
|
||||||
|
marks: [...Array(max).keys()].reduce((acc, _, index) => {
|
||||||
|
const spent = index < current;
|
||||||
|
acc[foundry.utils.randomID()] = { selected: false, disabled: spent, spent };
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {})
|
||||||
|
});
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
const stress = [...Array(actor.system.rules.damageReduction.maxArmorMarked.stressExtra ?? 0).keys()].reduce(
|
const stress = [...Array(actor.system.rules.damageReduction.maxArmorMarked.stressExtra ?? 0).keys()].reduce(
|
||||||
(acc, _) => {
|
(acc, _) => {
|
||||||
acc[foundry.utils.randomID()] = { selected: false };
|
acc[foundry.utils.randomID()] = { selected: false };
|
||||||
|
|
@ -121,36 +129,42 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
||||||
context.thresholdImmunities =
|
context.thresholdImmunities =
|
||||||
Object.keys(this.thresholdImmunities).length > 0 ? this.thresholdImmunities : null;
|
Object.keys(this.thresholdImmunities).length > 0 ? this.thresholdImmunities : null;
|
||||||
|
|
||||||
const { selectedArmorMarks, selectedStressMarks, stressReductions, currentMarks, currentDamage } =
|
const { selectedStressMarks, stressReductions, currentMarks, currentDamage, maxArmorUsed, availableArmor } =
|
||||||
this.getDamageInfo();
|
this.getDamageInfo();
|
||||||
|
|
||||||
context.armorScore = this.actor.system.armorScore;
|
context.armorScore = this.actor.system.armorScore.max;
|
||||||
context.armorMarks = currentMarks;
|
context.armorMarks = currentMarks;
|
||||||
context.basicMarksUsed =
|
|
||||||
selectedArmorMarks.length === this.actor.system.rules.damageReduction.maxArmorMarked.value;
|
|
||||||
|
|
||||||
const stressReductionStress = this.availableStressReductions
|
const stressReductionStress = this.availableStressReductions
|
||||||
? stressReductions.reduce((acc, red) => acc + red.cost, 0)
|
? stressReductions.reduce((acc, red) => acc + red.cost, 0)
|
||||||
: 0;
|
: 0;
|
||||||
|
const stress = this.actor.system.resources.stress;
|
||||||
context.stress =
|
context.stress =
|
||||||
selectedStressMarks.length > 0 || this.availableStressReductions
|
selectedStressMarks.length > 0 || this.availableStressReductions
|
||||||
? {
|
? {
|
||||||
value:
|
value: stress.value + selectedStressMarks.length + stressReductionStress,
|
||||||
this.actor.system.resources.stress.value + selectedStressMarks.length + stressReductionStress,
|
max: stress.max
|
||||||
max: this.actor.system.resources.stress.max
|
|
||||||
}
|
}
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const maxArmor = this.actor.system.rules.damageReduction.maxArmorMarked.value;
|
context.maxArmorUsed = maxArmorUsed;
|
||||||
context.marks = {
|
context.availableArmor = availableArmor;
|
||||||
armor: Object.keys(this.marks.armor).reduce((acc, key, index) => {
|
context.basicMarksUsed = availableArmor === 0 || selectedStressMarks.length;
|
||||||
const mark = this.marks.armor[key];
|
|
||||||
if (!this.rulesOn || index + 1 <= maxArmor) acc[key] = mark;
|
|
||||||
|
|
||||||
return acc;
|
const armorSources = [];
|
||||||
}, {}),
|
for (const source of this.marks.armor) {
|
||||||
|
armorSources.push({
|
||||||
|
label: source.name,
|
||||||
|
uuid: source.effect.uuid,
|
||||||
|
marks: source.marks
|
||||||
|
});
|
||||||
|
}
|
||||||
|
context.marks = {
|
||||||
|
armor: armorSources,
|
||||||
stress: this.marks.stress
|
stress: this.marks.stress
|
||||||
};
|
};
|
||||||
|
|
||||||
|
context.usesStressArmor = Object.keys(context.marks.stress).length;
|
||||||
context.availableStressReductions = this.availableStressReductions;
|
context.availableStressReductions = this.availableStressReductions;
|
||||||
|
|
||||||
context.damage = getDamageLabel(this.damage);
|
context.damage = getDamageLabel(this.damage);
|
||||||
|
|
@ -167,27 +181,31 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
||||||
}
|
}
|
||||||
|
|
||||||
getDamageInfo = () => {
|
getDamageInfo = () => {
|
||||||
const selectedArmorMarks = Object.values(this.marks.armor).filter(x => x.selected);
|
const selectedArmorMarks = this.marks.armor.flatMap(x => Object.values(x.marks).filter(x => x.selected));
|
||||||
const selectedStressMarks = Object.values(this.marks.stress).filter(x => x.selected);
|
const selectedStressMarks = Object.values(this.marks.stress).filter(x => x.selected);
|
||||||
const stressReductions = this.availableStressReductions
|
const stressReductions = this.availableStressReductions
|
||||||
? Object.values(this.availableStressReductions).filter(red => red.selected)
|
? Object.values(this.availableStressReductions).filter(red => red.selected)
|
||||||
: [];
|
: [];
|
||||||
const currentMarks =
|
const currentMarks = this.actor.system.armorScore.value + selectedArmorMarks.length;
|
||||||
this.actor.system.armor.system.marks.value + selectedArmorMarks.length + selectedStressMarks.length;
|
|
||||||
|
const maxArmorUsed = this.actor.system.rules.damageReduction.maxArmorMarked.value + selectedStressMarks.length;
|
||||||
|
const availableArmor =
|
||||||
|
maxArmorUsed -
|
||||||
|
this.marks.armor.reduce((acc, source) => {
|
||||||
|
acc += Object.values(source.marks).filter(x => x.selected).length;
|
||||||
|
return acc;
|
||||||
|
}, 0);
|
||||||
|
|
||||||
const armorMarkReduction =
|
const armorMarkReduction =
|
||||||
selectedArmorMarks.length * this.actor.system.rules.damageReduction.increasePerArmorMark;
|
selectedArmorMarks.length * this.actor.system.rules.damageReduction.increasePerArmorMark;
|
||||||
let currentDamage = Math.max(
|
let currentDamage = Math.max(this.damage - armorMarkReduction - stressReductions.length, 0);
|
||||||
this.damage - armorMarkReduction - selectedStressMarks.length - stressReductions.length,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
if (this.reduceSeverity) {
|
if (this.reduceSeverity) {
|
||||||
currentDamage = Math.max(currentDamage - this.reduceSeverity, 0);
|
currentDamage = Math.max(currentDamage - this.reduceSeverity, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.thresholdImmunities[currentDamage]) currentDamage = 0;
|
if (this.thresholdImmunities[currentDamage]) currentDamage = 0;
|
||||||
|
|
||||||
return { selectedArmorMarks, selectedStressMarks, stressReductions, currentMarks, currentDamage };
|
return { selectedStressMarks, stressReductions, currentMarks, currentDamage, maxArmorUsed, availableArmor };
|
||||||
};
|
};
|
||||||
|
|
||||||
static toggleRules() {
|
static toggleRules() {
|
||||||
|
|
@ -195,13 +213,10 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
||||||
|
|
||||||
const maxArmor = this.actor.system.rules.damageReduction.maxArmorMarked.value;
|
const maxArmor = this.actor.system.rules.damageReduction.maxArmorMarked.value;
|
||||||
this.marks = {
|
this.marks = {
|
||||||
armor: Object.keys(this.marks.armor).reduce((acc, key, index) => {
|
armor: this.marks.armor.map((mark, index) => {
|
||||||
const mark = this.marks.armor[key];
|
|
||||||
const keepSelectValue = !this.rulesOn || index + 1 <= maxArmor;
|
const keepSelectValue = !this.rulesOn || index + 1 <= maxArmor;
|
||||||
acc[key] = { ...mark, selected: keepSelectValue ? mark.selected : false };
|
return { ...mark, selected: keepSelectValue ? mark.selected : false };
|
||||||
|
}),
|
||||||
return acc;
|
|
||||||
}, {}),
|
|
||||||
stress: this.marks.stress
|
stress: this.marks.stress
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -209,8 +224,8 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
||||||
}
|
}
|
||||||
|
|
||||||
static setMarks(_, target) {
|
static setMarks(_, target) {
|
||||||
const currentMark = this.marks[target.dataset.type][target.dataset.key];
|
const currentMark = foundry.utils.getProperty(this.marks, target.dataset.path);
|
||||||
const { selectedStressMarks, stressReductions, currentMarks, currentDamage } = this.getDamageInfo();
|
const { selectedStressMarks, stressReductions, currentDamage, availableArmor } = this.getDamageInfo();
|
||||||
|
|
||||||
if (!currentMark.selected && currentDamage === 0) {
|
if (!currentMark.selected && currentDamage === 0) {
|
||||||
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.damageAlreadyNone'));
|
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.damageAlreadyNone'));
|
||||||
|
|
@ -218,12 +233,18 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.rulesOn) {
|
if (this.rulesOn) {
|
||||||
if (!currentMark.selected && currentMarks === this.actor.system.armorScore) {
|
if (target.dataset.type === 'armor' && !currentMark.selected && !availableArmor) {
|
||||||
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noAvailableArmorMarks'));
|
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noAvailableArmorMarks'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const stressUsed = selectedStressMarks.length;
|
||||||
|
if (target.dataset.type === 'armor' && stressUsed) {
|
||||||
|
const updateResult = this.updateStressArmor(target.dataset.id, !currentMark.selected);
|
||||||
|
if (updateResult === false) return;
|
||||||
|
}
|
||||||
|
|
||||||
if (currentMark.selected) {
|
if (currentMark.selected) {
|
||||||
const currentDamageLabel = getDamageLabel(currentDamage);
|
const currentDamageLabel = getDamageLabel(currentDamage);
|
||||||
for (let reduction of stressReductions) {
|
for (let reduction of stressReductions) {
|
||||||
|
|
@ -232,8 +253,16 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target.dataset.type === 'armor' && selectedStressMarks.length > 0) {
|
if (target.dataset.type === 'stress' && currentMark.armorMarkId) {
|
||||||
selectedStressMarks.forEach(mark => (mark.selected = false));
|
for (const source of this.marks.armor) {
|
||||||
|
const match = Object.keys(source.marks).find(key => key === currentMark.armorMarkId);
|
||||||
|
if (match) {
|
||||||
|
source.marks[match].selected = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentMark.armorMarkId = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -241,6 +270,25 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateStressArmor(armorMarkId, select) {
|
||||||
|
let stressMarkKey = null;
|
||||||
|
if (select) {
|
||||||
|
stressMarkKey = Object.keys(this.marks.stress).find(
|
||||||
|
key => this.marks.stress[key].selected && !this.marks.stress[key].armorMarkId
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
stressMarkKey = Object.keys(this.marks.stress).find(
|
||||||
|
key => this.marks.stress[key].armorMarkId === armorMarkId
|
||||||
|
);
|
||||||
|
if (!stressMarkKey)
|
||||||
|
stressMarkKey = Object.keys(this.marks.stress).find(key => this.marks.stress[key].selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stressMarkKey) return false;
|
||||||
|
|
||||||
|
this.marks.stress[stressMarkKey].armorMarkId = select ? armorMarkId : null;
|
||||||
|
}
|
||||||
|
|
||||||
static useStressReduction(_, target) {
|
static useStressReduction(_, target) {
|
||||||
const damageValue = Number(target.dataset.reduction);
|
const damageValue = Number(target.dataset.reduction);
|
||||||
const stressReduction = this.availableStressReductions[damageValue];
|
const stressReduction = this.availableStressReductions[damageValue];
|
||||||
|
|
@ -279,11 +327,18 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
||||||
}
|
}
|
||||||
|
|
||||||
static async takeDamage() {
|
static async takeDamage() {
|
||||||
const { selectedArmorMarks, selectedStressMarks, stressReductions, currentDamage } = this.getDamageInfo();
|
const { selectedStressMarks, stressReductions, currentDamage } = this.getDamageInfo();
|
||||||
const armorSpent = selectedArmorMarks.length + selectedStressMarks.length;
|
const armorChanges = this.marks.armor.reduce((acc, source) => {
|
||||||
const stressSpent = selectedStressMarks.length + stressReductions.reduce((acc, red) => acc + red.cost, 0);
|
const amount = Object.values(source.marks).filter(x => x.selected).length;
|
||||||
|
if (amount) acc.push({ uuid: source.effect.uuid, amount });
|
||||||
|
|
||||||
this.resolve({ modifiedDamage: currentDamage, armorSpent, stressSpent });
|
return acc;
|
||||||
|
}, []);
|
||||||
|
const stressSpent =
|
||||||
|
selectedStressMarks.filter(x => x.armorMarkId).length +
|
||||||
|
stressReductions.reduce((acc, red) => acc + red.cost, 0);
|
||||||
|
|
||||||
|
this.resolve({ modifiedDamage: currentDamage, armorChanges, stressSpent });
|
||||||
await this.close(true);
|
await this.close(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@ export default class DhDeathMove extends HandlebarsApplicationMixin(ApplicationV
|
||||||
|
|
||||||
let returnMessage = game.i18n.localize('DAGGERHEART.UI.Chat.deathMove.avoidScar');
|
let returnMessage = game.i18n.localize('DAGGERHEART.UI.Chat.deathMove.avoidScar');
|
||||||
if (config.roll.fate.value <= this.actor.system.levelData.level.current) {
|
if (config.roll.fate.value <= this.actor.system.levelData.level.current) {
|
||||||
|
const maxHope = this.actor.system.resources.hope.max + this.actor.system.scars;
|
||||||
const newScarAmount = this.actor.system.scars + 1;
|
const newScarAmount = this.actor.system.scars + 1;
|
||||||
await this.actor.update({
|
await this.actor.update({
|
||||||
system: {
|
system: {
|
||||||
|
|
@ -64,7 +65,7 @@ export default class DhDeathMove extends HandlebarsApplicationMixin(ApplicationV
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (newScarAmount >= this.actor.system.resources.hope.max) {
|
if (newScarAmount >= maxHope) {
|
||||||
await this.actor.setDeathMoveDefeated(CONFIG.DH.GENERAL.defeatedConditionChoices.dead.id);
|
await this.actor.setDeathMoveDefeated(CONFIG.DH.GENERAL.defeatedConditionChoices.dead.id);
|
||||||
return game.i18n.format('DAGGERHEART.UI.Chat.deathMove.journeysEnd', { scars: newScarAmount });
|
return game.i18n.format('DAGGERHEART.UI.Chat.deathMove.journeysEnd', { scars: newScarAmount });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { refreshIsAllowed } from '../../helpers/utils.mjs';
|
import { expireActiveEffects, refreshIsAllowed } from '../../helpers/utils.mjs';
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
||||||
|
|
@ -203,7 +203,7 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
||||||
const msg = {
|
const msg = {
|
||||||
user: game.user.id,
|
user: game.user.id,
|
||||||
system: {
|
system: {
|
||||||
moves: moves,
|
moves: moves.map(move => ({ ...move, actions: Array.from(move.actions) })),
|
||||||
actor: this.actor.uuid
|
actor: this.actor.uuid
|
||||||
},
|
},
|
||||||
speaker: cls.getSpeaker(),
|
speaker: cls.getSpeaker(),
|
||||||
|
|
@ -259,11 +259,16 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
||||||
const resetValue = increasing
|
const resetValue = increasing
|
||||||
? 0
|
? 0
|
||||||
: feature.system.resource.max
|
: feature.system.resource.max
|
||||||
? Roll.replaceFormulaData(feature.system.resource.max, this.actor)
|
? new Roll(
|
||||||
|
Roll.replaceFormulaData(feature.system.resource.max, this.actor.getRollData())
|
||||||
|
).evaluateSync().total
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
await feature.update({ 'system.resource.value': resetValue });
|
await feature.update({ 'system.resource.value': resetValue });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expireActiveEffects(this.actor, [this.shortRest ? 'shortRest' : 'longRest']);
|
||||||
|
|
||||||
this.close();
|
this.close();
|
||||||
} else {
|
} else {
|
||||||
this.render();
|
this.render();
|
||||||
|
|
|
||||||
|
|
@ -1,204 +0,0 @@
|
||||||
import autocomplete from 'autocompleter';
|
|
||||||
import { abilities } from '../../config/actorConfig.mjs';
|
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
|
||||||
|
|
||||||
export default class GroupRollDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
|
||||||
constructor(actors) {
|
|
||||||
super();
|
|
||||||
this.actors = actors;
|
|
||||||
this.actorLeader = {};
|
|
||||||
this.actorsMembers = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
get title() {
|
|
||||||
return 'Group Roll';
|
|
||||||
}
|
|
||||||
|
|
||||||
static DEFAULT_OPTIONS = {
|
|
||||||
tag: 'form',
|
|
||||||
classes: ['daggerheart', 'views', 'dh-style', 'dialog', 'group-roll'],
|
|
||||||
position: { width: 'auto', height: 'auto' },
|
|
||||||
window: {
|
|
||||||
title: 'DAGGERHEART.UI.Chat.groupRoll.title'
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
roll: GroupRollDialog.#roll,
|
|
||||||
removeLeader: GroupRollDialog.#removeLeader,
|
|
||||||
removeMember: GroupRollDialog.#removeMember
|
|
||||||
},
|
|
||||||
form: { handler: this.updateData, submitOnChange: true, closeOnSubmit: false }
|
|
||||||
};
|
|
||||||
|
|
||||||
static PARTS = {
|
|
||||||
application: {
|
|
||||||
id: 'group-roll',
|
|
||||||
template: 'systems/daggerheart/templates/dialogs/group-roll/group-roll.hbs'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
_attachPartListeners(partId, htmlElement, options) {
|
|
||||||
super._attachPartListeners(partId, htmlElement, options);
|
|
||||||
const leaderChoices = this.actors.filter(x => this.actorsMembers.every(member => member.actor?.id !== x.id));
|
|
||||||
const memberChoices = this.actors.filter(
|
|
||||||
x => this.actorLeader?.actor?.id !== x.id && this.actorsMembers.every(member => member.actor?.id !== x.id)
|
|
||||||
);
|
|
||||||
|
|
||||||
htmlElement.querySelectorAll('.leader-change-input').forEach(element => {
|
|
||||||
autocomplete({
|
|
||||||
input: element,
|
|
||||||
fetch: function (text, update) {
|
|
||||||
if (!text) {
|
|
||||||
update(leaderChoices);
|
|
||||||
} else {
|
|
||||||
text = text.toLowerCase();
|
|
||||||
var suggestions = leaderChoices.filter(n => n.name.toLowerCase().includes(text));
|
|
||||||
update(suggestions);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
render: function (actor, search) {
|
|
||||||
const actorName = game.i18n.localize(actor.name);
|
|
||||||
const matchIndex = actorName.toLowerCase().indexOf(search);
|
|
||||||
|
|
||||||
const beforeText = actorName.slice(0, matchIndex);
|
|
||||||
const matchText = actorName.slice(matchIndex, matchIndex + search.length);
|
|
||||||
const after = actorName.slice(matchIndex + search.length, actorName.length);
|
|
||||||
const img = document.createElement('img');
|
|
||||||
img.src = actor.img;
|
|
||||||
|
|
||||||
const element = document.createElement('li');
|
|
||||||
element.appendChild(img);
|
|
||||||
|
|
||||||
const label = document.createElement('span');
|
|
||||||
label.innerHTML =
|
|
||||||
`${beforeText}${matchText ? `<strong>${matchText}</strong>` : ''}${after}`.replaceAll(
|
|
||||||
' ',
|
|
||||||
' '
|
|
||||||
);
|
|
||||||
element.appendChild(label);
|
|
||||||
|
|
||||||
return element;
|
|
||||||
},
|
|
||||||
renderGroup: function (label) {
|
|
||||||
const itemElement = document.createElement('div');
|
|
||||||
itemElement.textContent = game.i18n.localize(label);
|
|
||||||
return itemElement;
|
|
||||||
},
|
|
||||||
onSelect: actor => {
|
|
||||||
element.value = actor.uuid;
|
|
||||||
this.actorLeader = { actor: actor, trait: 'agility', difficulty: 0 };
|
|
||||||
this.render();
|
|
||||||
},
|
|
||||||
click: e => e.fetch(),
|
|
||||||
customize: function (_input, _inputRect, container) {
|
|
||||||
container.style.zIndex = foundry.applications.api.ApplicationV2._maxZ;
|
|
||||||
},
|
|
||||||
minLength: 0
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
htmlElement.querySelectorAll('.team-push-input').forEach(element => {
|
|
||||||
autocomplete({
|
|
||||||
input: element,
|
|
||||||
fetch: function (text, update) {
|
|
||||||
if (!text) {
|
|
||||||
update(memberChoices);
|
|
||||||
} else {
|
|
||||||
text = text.toLowerCase();
|
|
||||||
var suggestions = memberChoices.filter(n => n.name.toLowerCase().includes(text));
|
|
||||||
update(suggestions);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
render: function (actor, search) {
|
|
||||||
const actorName = game.i18n.localize(actor.name);
|
|
||||||
const matchIndex = actorName.toLowerCase().indexOf(search);
|
|
||||||
|
|
||||||
const beforeText = actorName.slice(0, matchIndex);
|
|
||||||
const matchText = actorName.slice(matchIndex, matchIndex + search.length);
|
|
||||||
const after = actorName.slice(matchIndex + search.length, actorName.length);
|
|
||||||
const img = document.createElement('img');
|
|
||||||
img.src = actor.img;
|
|
||||||
|
|
||||||
const element = document.createElement('li');
|
|
||||||
element.appendChild(img);
|
|
||||||
|
|
||||||
const label = document.createElement('span');
|
|
||||||
label.innerHTML =
|
|
||||||
`${beforeText}${matchText ? `<strong>${matchText}</strong>` : ''}${after}`.replaceAll(
|
|
||||||
' ',
|
|
||||||
' '
|
|
||||||
);
|
|
||||||
element.appendChild(label);
|
|
||||||
|
|
||||||
return element;
|
|
||||||
},
|
|
||||||
renderGroup: function (label) {
|
|
||||||
const itemElement = document.createElement('div');
|
|
||||||
itemElement.textContent = game.i18n.localize(label);
|
|
||||||
return itemElement;
|
|
||||||
},
|
|
||||||
onSelect: actor => {
|
|
||||||
element.value = actor.uuid;
|
|
||||||
this.actorsMembers.push({ actor: actor, trait: 'agility', difficulty: 0 });
|
|
||||||
this.render({ force: true });
|
|
||||||
},
|
|
||||||
click: e => e.fetch(),
|
|
||||||
customize: function (_input, _inputRect, container) {
|
|
||||||
container.style.zIndex = foundry.applications.api.ApplicationV2._maxZ;
|
|
||||||
},
|
|
||||||
minLength: 0
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async _prepareContext(_options) {
|
|
||||||
const context = await super._prepareContext(_options);
|
|
||||||
context.leader = this.actorLeader;
|
|
||||||
context.members = this.actorsMembers;
|
|
||||||
context.traitList = abilities;
|
|
||||||
|
|
||||||
context.allSelected = this.actorsMembers.length + (this.actorLeader?.actor ? 1 : 0) === this.actors.length;
|
|
||||||
context.rollDisabled = context.members.length === 0 || !this.actorLeader?.actor;
|
|
||||||
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
static updateData(event, _, formData) {
|
|
||||||
const { actorLeader, actorsMembers } = foundry.utils.expandObject(formData.object);
|
|
||||||
this.actorLeader = foundry.utils.mergeObject(this.actorLeader, actorLeader);
|
|
||||||
this.actorsMembers = foundry.utils.mergeObject(this.actorsMembers, actorsMembers);
|
|
||||||
this.render(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
static async #removeLeader(_, button) {
|
|
||||||
this.actorLeader = null;
|
|
||||||
this.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
static async #removeMember(_, button) {
|
|
||||||
this.actorsMembers = this.actorsMembers.filter(m => m.actor.uuid !== button.dataset.memberUuid);
|
|
||||||
this.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
static async #roll() {
|
|
||||||
const cls = getDocumentClass('ChatMessage');
|
|
||||||
const systemData = {
|
|
||||||
leader: this.actorLeader,
|
|
||||||
members: this.actorsMembers
|
|
||||||
};
|
|
||||||
const msg = {
|
|
||||||
type: 'groupRoll',
|
|
||||||
user: game.user.id,
|
|
||||||
speaker: cls.getSpeaker(),
|
|
||||||
title: game.i18n.localize('DAGGERHEART.UI.Chat.groupRoll.title'),
|
|
||||||
system: systemData,
|
|
||||||
content: await foundry.applications.handlebars.renderTemplate(
|
|
||||||
'systems/daggerheart/templates/ui/chat/groupRoll.hbs',
|
|
||||||
{ system: systemData }
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
cls.create(msg);
|
|
||||||
this.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
500
module/applications/dialogs/groupRollDialog.mjs
Normal file
500
module/applications/dialogs/groupRollDialog.mjs
Normal file
|
|
@ -0,0 +1,500 @@
|
||||||
|
import { ResourceUpdateMap } from '../../data/action/baseAction.mjs';
|
||||||
|
import { emitGMUpdate, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||||
|
import Party from '../sheets/actors/party.mjs';
|
||||||
|
|
||||||
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
||||||
|
export default class GroupRollDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
|
constructor(party) {
|
||||||
|
super({ id: `GroupRollDialog-${party.id}` });
|
||||||
|
|
||||||
|
this.party = party;
|
||||||
|
this.partyMembers = party.system.partyMembers
|
||||||
|
.filter(x => Party.DICE_ROLL_ACTOR_TYPES.includes(x.type))
|
||||||
|
.map(member => ({
|
||||||
|
...member.toObject(),
|
||||||
|
uuid: member.uuid,
|
||||||
|
id: member.id,
|
||||||
|
selected: true,
|
||||||
|
owned: member.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER)
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.leader = null;
|
||||||
|
this.openForAllPlayers = true;
|
||||||
|
|
||||||
|
this.tabGroups.application = Object.keys(party.system.groupRoll.participants).length
|
||||||
|
? 'groupRoll'
|
||||||
|
: 'initialization';
|
||||||
|
|
||||||
|
Hooks.on(socketEvent.Refresh, this.groupRollRefresh.bind());
|
||||||
|
}
|
||||||
|
|
||||||
|
get title() {
|
||||||
|
return game.i18n.localize('DAGGERHEART.APPLICATIONS.GroupRollSelect.title');
|
||||||
|
}
|
||||||
|
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
tag: 'form',
|
||||||
|
classes: ['daggerheart', 'views', 'dh-style', 'dialog', 'group-roll-dialog'],
|
||||||
|
position: { width: 390, height: 'auto' },
|
||||||
|
window: {
|
||||||
|
icon: 'fa-solid fa-users'
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
toggleSelectMember: this.#toggleSelectMember,
|
||||||
|
startGroupRoll: this.#startGroupRoll,
|
||||||
|
makeRoll: this.#makeRoll,
|
||||||
|
removeRoll: this.#removeRoll,
|
||||||
|
rerollDice: this.#rerollDice,
|
||||||
|
markSuccessful: this.#markSuccessful,
|
||||||
|
cancelRoll: this.#onCancelRoll,
|
||||||
|
finishRoll: this.#finishRoll
|
||||||
|
},
|
||||||
|
form: { handler: this.updateData, submitOnChange: true, closeOnSubmit: false }
|
||||||
|
};
|
||||||
|
|
||||||
|
static PARTS = {
|
||||||
|
initialization: {
|
||||||
|
id: 'initialization',
|
||||||
|
template: 'systems/daggerheart/templates/dialogs/groupRollDialog/initialization.hbs'
|
||||||
|
},
|
||||||
|
main: {
|
||||||
|
id: 'main',
|
||||||
|
template: 'systems/daggerheart/templates/dialogs/groupRollDialog/main.hbs'
|
||||||
|
},
|
||||||
|
leader: {
|
||||||
|
id: 'leader',
|
||||||
|
template: 'systems/daggerheart/templates/dialogs/groupRollDialog/parts/member.hbs'
|
||||||
|
},
|
||||||
|
result: {
|
||||||
|
id: 'result',
|
||||||
|
template: 'systems/daggerheart/templates/dialogs/groupRollDialog/parts/result.hbs'
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
id: 'footer',
|
||||||
|
template: 'systems/daggerheart/templates/dialogs/groupRollDialog/parts/footer.hbs'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
static TABS = {
|
||||||
|
application: {
|
||||||
|
tabs: [{ id: 'initialization' }, { id: 'groupRoll' }]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_attachPartListeners(partId, htmlElement, options) {
|
||||||
|
super._attachPartListeners(partId, htmlElement, options);
|
||||||
|
|
||||||
|
htmlElement
|
||||||
|
.querySelector('.main-character-field')
|
||||||
|
?.addEventListener('input', this.updateLeaderField.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
_configureRenderParts(options) {
|
||||||
|
const parts = super._configureRenderParts(options);
|
||||||
|
for (const memberKey of Object.keys(this.party.system.groupRoll.aidingCharacters)) {
|
||||||
|
parts[memberKey] = {
|
||||||
|
id: memberKey,
|
||||||
|
template: 'systems/daggerheart/templates/dialogs/groupRollDialog/parts/member.hbs'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return parts;
|
||||||
|
}
|
||||||
|
|
||||||
|
async _prepareContext(_options) {
|
||||||
|
const context = await super._prepareContext(_options);
|
||||||
|
|
||||||
|
context.isGM = game.user.isGM;
|
||||||
|
context.isEditable =
|
||||||
|
game.user.isGM ||
|
||||||
|
this.party.system.partyMembers.some(actor => {
|
||||||
|
const selected = Boolean(this.party.system.groupRoll.participants[actor.id]);
|
||||||
|
return selected && actor.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER);
|
||||||
|
});
|
||||||
|
context.fields = this.party.system.schema.fields.groupRoll.fields;
|
||||||
|
context.data = this.party.system.groupRoll;
|
||||||
|
context.traitOptions = CONFIG.DH.ACTOR.abilities;
|
||||||
|
context.members = {};
|
||||||
|
context.aidKeys = Object.keys(this.party.system.groupRoll.aidingCharacters);
|
||||||
|
context.allHaveRolled = Object.keys(context.data.participants).every(key => {
|
||||||
|
const data = context.data.participants[key];
|
||||||
|
return Boolean(data.rollData);
|
||||||
|
});
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
async _preparePartContext(partId, context, options) {
|
||||||
|
const partContext = await super._preparePartContext(partId, context, options);
|
||||||
|
partContext.partId = partId;
|
||||||
|
partContext.leader = this.getRollCharacterData(this.party.system.groupRoll.leader);
|
||||||
|
|
||||||
|
switch (partId) {
|
||||||
|
case 'initialization':
|
||||||
|
partContext.groupRollFields = this.party.system.schema.fields.groupRoll.fields;
|
||||||
|
partContext.memberSelection = this.partyMembers;
|
||||||
|
|
||||||
|
const selectedMembers = partContext.memberSelection.filter(x => x.selected);
|
||||||
|
|
||||||
|
partContext.selectedLeader = this.leader;
|
||||||
|
partContext.selectedLeaderOptions = selectedMembers
|
||||||
|
.filter(actor => actor.owned)
|
||||||
|
.map(x => ({ value: x.id, label: x.name }));
|
||||||
|
partContext.selectedLeaderDisabled = !selectedMembers.length;
|
||||||
|
|
||||||
|
partContext.canStartGroupRoll = selectedMembers.length > 1 && this.leader?.memberId;
|
||||||
|
partContext.openForAllPlayers = this.openForAllPlayers;
|
||||||
|
break;
|
||||||
|
case 'result':
|
||||||
|
const leader = this.party.system.groupRoll.leader;
|
||||||
|
partContext.hasRolled =
|
||||||
|
leader?.rollData ||
|
||||||
|
Object.values(this.party.system.groupRoll?.aidingCharacters ?? {}).some(x => x.successful !== null);
|
||||||
|
const { modifierTotal, modifiers } = Object.values(this.party.system.groupRoll.aidingCharacters).reduce(
|
||||||
|
(acc, curr) => {
|
||||||
|
const modifier = curr.successful === true ? 1 : curr.successful === false ? -1 : null;
|
||||||
|
if (modifier) {
|
||||||
|
acc.modifierTotal += modifier;
|
||||||
|
acc.modifiers.push(modifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{ modifierTotal: 0, modifiers: [] }
|
||||||
|
);
|
||||||
|
const leaderTotal = leader?.rollData ? leader.roll.total : null;
|
||||||
|
partContext.groupRoll = {
|
||||||
|
totalLabel: leader?.rollData
|
||||||
|
? game.i18n.format('DAGGERHEART.GENERAL.withThing', {
|
||||||
|
thing: leader.roll.totalLabel
|
||||||
|
})
|
||||||
|
: null,
|
||||||
|
totalDualityClass: leader?.roll?.isCritical ? 'critical' : leader?.roll?.withHope ? 'hope' : 'fear',
|
||||||
|
total: leaderTotal + modifierTotal,
|
||||||
|
leaderTotal: leaderTotal,
|
||||||
|
modifiers
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 'footer':
|
||||||
|
partContext.canFinishRoll =
|
||||||
|
Boolean(this.party.system.groupRoll.leader?.rollData) &&
|
||||||
|
Object.values(this.party.system.groupRoll.aidingCharacters).every(x => x.successful !== null);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(this.party.system.groupRoll.aidingCharacters).includes(partId)) {
|
||||||
|
const characterData = this.party.system.groupRoll.aidingCharacters[partId];
|
||||||
|
partContext.members[partId] = this.getRollCharacterData(characterData, partId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return partContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
getRollCharacterData(data, partId) {
|
||||||
|
if (!data) return {};
|
||||||
|
|
||||||
|
const actor = game.actors.get(data.id);
|
||||||
|
const isLeader = data === this.party.system.groupRoll.leader;
|
||||||
|
|
||||||
|
const roll = data.roll;
|
||||||
|
const withTypeSuffix = !roll ? null : roll.isCritical ? 'criticalShort' : roll.withHope ? 'hope' : 'fear';
|
||||||
|
const thing = withTypeSuffix ? _loc(`DAGGERHEART.GENERAL.${withTypeSuffix}`) : null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...data,
|
||||||
|
type: isLeader ? 'leader' : 'aid',
|
||||||
|
basePath: isLeader ? 'system.groupRoll.leader' : `system.groupRoll.aidingCharacters.${data.id}`,
|
||||||
|
rollChoiceLabel: _loc(CONFIG.DH.ACTOR.abilities[data.rollChoice]?.label),
|
||||||
|
roll: data.roll,
|
||||||
|
isEditable: actor?.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER),
|
||||||
|
key: partId,
|
||||||
|
readyToRoll: Boolean(data.rollChoice),
|
||||||
|
hasRolled: Boolean(data.rollData),
|
||||||
|
modifier: data.successful ? 1 : data.successful === false ? -1 : 0,
|
||||||
|
withLabelShort: thing ? _loc('DAGGERHEART.GENERAL.withThing', { thing }) : null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#getCharacterDataById(id) {
|
||||||
|
if (!id) return null;
|
||||||
|
|
||||||
|
const groupRoll = this.party.system.groupRoll;
|
||||||
|
if (id === 'leader' || id === groupRoll.leader?.id) {
|
||||||
|
return { data: groupRoll.leader, basePath: 'system.groupRoll.leader' };
|
||||||
|
} else if (id in groupRoll.aidingCharacters) {
|
||||||
|
return { data: groupRoll.aidingCharacters[id], basePath: `system.groupRoll.aidingCharacters.${id}` };
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async updateData(event, _, formData) {
|
||||||
|
const partyData = foundry.utils.expandObject(formData.object);
|
||||||
|
this.updatePartyData(partyData, this.getUpdatingParts(event.target));
|
||||||
|
}
|
||||||
|
|
||||||
|
async updatePartyData(update, updatingParts, options = { render: true }) {
|
||||||
|
if (!game.users.activeGM)
|
||||||
|
return ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.gmRequired'));
|
||||||
|
|
||||||
|
const gmUpdate = async update => {
|
||||||
|
await this.party.update(update);
|
||||||
|
this.render({ parts: updatingParts });
|
||||||
|
game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||||
|
action: socketEvent.Refresh,
|
||||||
|
data: { refreshType: RefreshType.GroupRoll, action: 'refresh', parts: updatingParts }
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
await emitGMUpdate(
|
||||||
|
GMUpdateEvent.UpdateDocument,
|
||||||
|
gmUpdate,
|
||||||
|
update,
|
||||||
|
this.party.uuid,
|
||||||
|
options.render ? { refreshType: RefreshType.GroupRoll, action: 'refresh', parts: updatingParts } : undefined
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getUpdatingParts(target) {
|
||||||
|
const { initialization, leader, result, footer } = this.constructor.PARTS;
|
||||||
|
const isInitialization = this.tabGroups.application === initialization.id;
|
||||||
|
const updatingMember = target.closest('.member-roll-container.aid')?.dataset?.memberKey;
|
||||||
|
const updatingLeader = target.closest('.member-roll-container.leader');
|
||||||
|
|
||||||
|
return [
|
||||||
|
...(isInitialization ? [initialization.id] : []),
|
||||||
|
...(updatingMember ? [updatingMember] : []),
|
||||||
|
...(updatingLeader ? [leader.id] : []),
|
||||||
|
...(!isInitialization ? [result.id, footer.id] : [])
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
groupRollRefresh = ({ refreshType, action, parts }) => {
|
||||||
|
if (refreshType !== RefreshType.GroupRoll) return;
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case 'startGroupRoll':
|
||||||
|
this.tabGroups.application = 'groupRoll';
|
||||||
|
break;
|
||||||
|
case 'refresh':
|
||||||
|
this.render({ parts });
|
||||||
|
break;
|
||||||
|
case 'close':
|
||||||
|
this.close();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async close(options = {}) {
|
||||||
|
/* Opt out of Foundry's standard behavior of closing all application windows marked as UI when Escape is pressed */
|
||||||
|
if (options.closeKey) return;
|
||||||
|
|
||||||
|
Hooks.off(socketEvent.Refresh, this.groupRollRefresh);
|
||||||
|
return super.close(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
//#region Initialization
|
||||||
|
static #toggleSelectMember(_, button) {
|
||||||
|
const member = this.partyMembers.find(x => x.id === button.dataset.id);
|
||||||
|
member.selected = !member.selected;
|
||||||
|
if (this.leader?.memberId === member.id) {
|
||||||
|
this.leader = null;
|
||||||
|
}
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateLeaderField(event) {
|
||||||
|
if (!this.leader) this.leader = {};
|
||||||
|
this.leader.memberId = event.target.value;
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #startGroupRoll() {
|
||||||
|
const leader = this.partyMembers.find(x => x.id === this.leader.memberId);
|
||||||
|
const aidingCharacters = this.partyMembers.reduce((acc, curr) => {
|
||||||
|
if (curr.selected && curr.id !== this.leader.memberId)
|
||||||
|
acc[curr.id] = { id: curr.id, name: curr.name, img: curr.img };
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
await this.party.update({
|
||||||
|
'system.groupRoll': _replace(
|
||||||
|
new game.system.api.data.GroupRollData({
|
||||||
|
...this.party.system.groupRoll.toObject(),
|
||||||
|
leader: { id: leader.id, name: leader.name, img: leader.img },
|
||||||
|
aidingCharacters
|
||||||
|
})
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
const hookData = { openForAllPlayers: this.openForAllPlayers, partyId: this.party.id };
|
||||||
|
Hooks.callAll(CONFIG.DH.HOOKS.hooksConfig.groupRollStart, hookData);
|
||||||
|
game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||||
|
action: socketEvent.GroupRollStart,
|
||||||
|
data: hookData
|
||||||
|
});
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
/** @this GroupRollDialog */
|
||||||
|
static async #makeRoll(_event, button) {
|
||||||
|
const member = button.closest('[data-member-key]').dataset.memberKey;
|
||||||
|
const { data, basePath } = this.#getCharacterDataById(member);
|
||||||
|
const actor = game.actors.find(x => x.id === data.id);
|
||||||
|
if (!actor) return;
|
||||||
|
|
||||||
|
const result = await actor.rollTrait(data.rollChoice, {
|
||||||
|
skips: {
|
||||||
|
createMessage: true,
|
||||||
|
resources: true,
|
||||||
|
triggers: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result) return;
|
||||||
|
|
||||||
|
const rollData = result.messageRoll.toJSON();
|
||||||
|
delete rollData.options.messageRoll;
|
||||||
|
this.updatePartyData(
|
||||||
|
{
|
||||||
|
[basePath]: { rollData, successful: null }
|
||||||
|
},
|
||||||
|
this.getUpdatingParts(button)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @this GroupRollDialog */
|
||||||
|
static async #removeRoll(_event, button) {
|
||||||
|
const member = button.closest('[data-member-key]').dataset.memberKey;
|
||||||
|
const { basePath } = this.#getCharacterDataById(member);
|
||||||
|
this.updatePartyData(
|
||||||
|
{
|
||||||
|
[basePath]: {
|
||||||
|
rollData: null,
|
||||||
|
rollChoice: null,
|
||||||
|
selected: false,
|
||||||
|
successful: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
this.getUpdatingParts(button)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @this GroupRollDialog */
|
||||||
|
static async #rerollDice(_, button) {
|
||||||
|
const { diceType } = button.dataset;
|
||||||
|
const { data, basePath } = this.#getCharacterDataById(button.dataset.member);
|
||||||
|
|
||||||
|
const dieIndex = diceType === 'hope' ? 0 : diceType === 'fear' ? 1 : 2;
|
||||||
|
const newRoll = game.system.api.dice.DualityRoll.fromData(data.rollData);
|
||||||
|
const dice = newRoll.dice[dieIndex];
|
||||||
|
await dice.reroll(`/r1=${dice.total}`, {
|
||||||
|
liveRoll: {
|
||||||
|
roll: newRoll,
|
||||||
|
isReaction: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const rollData = newRoll.toJSON();
|
||||||
|
this.updatePartyData(
|
||||||
|
{
|
||||||
|
[`${basePath}.rollData`]: rollData
|
||||||
|
},
|
||||||
|
this.getUpdatingParts(button)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static #markSuccessful(_event, button) {
|
||||||
|
const memberKey = button.closest('[data-member-key]').dataset.memberKey;
|
||||||
|
const previousValue = this.party.system.groupRoll.aidingCharacters[memberKey].successful;
|
||||||
|
const newValue = Boolean(button.dataset.success === 'true');
|
||||||
|
this.updatePartyData(
|
||||||
|
{
|
||||||
|
[`system.groupRoll.aidingCharacters.${memberKey}.successful`]:
|
||||||
|
previousValue === newValue ? null : newValue
|
||||||
|
},
|
||||||
|
this.getUpdatingParts(button)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onCancelRoll(_event, _button, options = { confirm: true }) {
|
||||||
|
this.cancelRoll(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
async cancelRoll(options = { confirm: true }) {
|
||||||
|
if (options.confirm) {
|
||||||
|
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||||
|
window: {
|
||||||
|
title: game.i18n.localize('DAGGERHEART.APPLICATIONS.GroupRollSelect.cancelConfirmTitle')
|
||||||
|
},
|
||||||
|
content: game.i18n.localize('DAGGERHEART.APPLICATIONS.GroupRollSelect.cancelConfirmText')
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!confirmed) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.updatePartyData(
|
||||||
|
{
|
||||||
|
'system.groupRoll': {
|
||||||
|
leader: null,
|
||||||
|
aidingCharacters: _replace({})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
{ render: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
this.close();
|
||||||
|
game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||||
|
action: socketEvent.Refresh,
|
||||||
|
data: { refreshType: RefreshType.GroupRoll, action: 'close' }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #finishRoll() {
|
||||||
|
const totalRoll = this.party.system.groupRoll.leader.roll;
|
||||||
|
for (const character of Object.values(this.party.system.groupRoll.aidingCharacters)) {
|
||||||
|
totalRoll.terms.push(new foundry.dice.terms.OperatorTerm({ operator: character.successful ? '+' : '-' }));
|
||||||
|
totalRoll.terms.push(new foundry.dice.terms.NumericTerm({ number: 1 }));
|
||||||
|
}
|
||||||
|
|
||||||
|
await totalRoll._evaluate();
|
||||||
|
|
||||||
|
const systemData = totalRoll.options;
|
||||||
|
const actor = game.actors.get(this.party.system.groupRoll.leader.id);
|
||||||
|
|
||||||
|
const cls = getDocumentClass('ChatMessage'),
|
||||||
|
msgData = {
|
||||||
|
type: 'dualityRoll',
|
||||||
|
user: game.user.id,
|
||||||
|
title: game.i18n.localize('DAGGERHEART.APPLICATIONS.GroupRollSelect.title'),
|
||||||
|
speaker: cls.getSpeaker({ actor }),
|
||||||
|
system: systemData,
|
||||||
|
rolls: [JSON.stringify(totalRoll)],
|
||||||
|
sound: null,
|
||||||
|
flags: { core: { RollTable: true } }
|
||||||
|
};
|
||||||
|
|
||||||
|
await cls.create(msgData);
|
||||||
|
|
||||||
|
const resourceMap = new ResourceUpdateMap(actor);
|
||||||
|
if (totalRoll.isCritical) {
|
||||||
|
resourceMap.addResources([
|
||||||
|
{ key: 'stress', value: -1, total: 1 },
|
||||||
|
{ key: 'hope', value: 1, total: 1 }
|
||||||
|
]);
|
||||||
|
} else if (totalRoll.withHope) {
|
||||||
|
resourceMap.addResources([{ key: 'hope', value: 1, total: 1 }]);
|
||||||
|
} else {
|
||||||
|
resourceMap.addResources([{ key: 'fear', value: 1, total: 1 }]);
|
||||||
|
}
|
||||||
|
|
||||||
|
resourceMap.updateResources();
|
||||||
|
|
||||||
|
/* Fin */
|
||||||
|
this.cancelRoll({ confirm: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -38,13 +38,15 @@ export default class ItemTransferDialog extends HandlebarsApplicationMixin(Appli
|
||||||
originActor ??= item?.actor;
|
originActor ??= item?.actor;
|
||||||
const homebrewKey = CONFIG.DH.SETTINGS.gameSettings.Homebrew;
|
const homebrewKey = CONFIG.DH.SETTINGS.gameSettings.Homebrew;
|
||||||
const currencySetting = game.settings.get(CONFIG.DH.id, homebrewKey).currency?.[currency] ?? null;
|
const currencySetting = game.settings.get(CONFIG.DH.id, homebrewKey).currency?.[currency] ?? null;
|
||||||
|
const max = item?.system.quantity ?? originActor.system.gold[currency] ?? 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
originActor,
|
originActor,
|
||||||
targetActor,
|
targetActor,
|
||||||
itemImage: item?.img,
|
itemImage: item?.img,
|
||||||
currencyIcon: currencySetting?.icon,
|
currencyIcon: currencySetting?.icon,
|
||||||
max: item?.system.quantity ?? originActor.system.gold[currency] ?? 0,
|
max,
|
||||||
|
initial: targetActor.system.metadata.quantifiable?.includes(item.type) ? max : 1,
|
||||||
title: item?.name ?? currencySetting?.label
|
title: item?.name ?? currencySetting?.label
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,290 +0,0 @@
|
||||||
import { RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
|
||||||
|
|
||||||
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
|
||||||
|
|
||||||
export default class RerollDamageDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
|
||||||
constructor(message, options = {}) {
|
|
||||||
super(options);
|
|
||||||
|
|
||||||
this.message = message;
|
|
||||||
this.damage = Object.keys(message.system.damage).reduce((acc, typeKey) => {
|
|
||||||
const type = message.system.damage[typeKey];
|
|
||||||
acc[typeKey] = Object.keys(type.parts).reduce((acc, partKey) => {
|
|
||||||
const part = type.parts[partKey];
|
|
||||||
acc[partKey] = Object.keys(part.dice).reduce((acc, diceKey) => {
|
|
||||||
const dice = part.dice[diceKey];
|
|
||||||
const activeResults = dice.results.filter(x => x.active);
|
|
||||||
acc[diceKey] = {
|
|
||||||
dice: dice.dice,
|
|
||||||
selectedResults: activeResults.length,
|
|
||||||
maxSelected: activeResults.length,
|
|
||||||
results: activeResults.map(x => ({ ...x, selected: true }))
|
|
||||||
};
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
}
|
|
||||||
|
|
||||||
static DEFAULT_OPTIONS = {
|
|
||||||
id: 'reroll-dialog',
|
|
||||||
classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'reroll-dialog'],
|
|
||||||
window: {
|
|
||||||
icon: 'fa-solid fa-dice'
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
toggleResult: RerollDamageDialog.#toggleResult,
|
|
||||||
selectRoll: RerollDamageDialog.#selectRoll,
|
|
||||||
doReroll: RerollDamageDialog.#doReroll,
|
|
||||||
save: RerollDamageDialog.#save
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
static PARTS = {
|
|
||||||
main: {
|
|
||||||
id: 'main',
|
|
||||||
template: 'systems/daggerheart/templates/dialogs/rerollDialog/damage/main.hbs'
|
|
||||||
},
|
|
||||||
footer: {
|
|
||||||
id: 'footer',
|
|
||||||
template: 'systems/daggerheart/templates/dialogs/rerollDialog/footer.hbs'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
get title() {
|
|
||||||
return game.i18n.localize('DAGGERHEART.APPLICATIONS.RerollDialog.damageTitle');
|
|
||||||
}
|
|
||||||
|
|
||||||
_attachPartListeners(partId, htmlElement, options) {
|
|
||||||
super._attachPartListeners(partId, htmlElement, options);
|
|
||||||
|
|
||||||
htmlElement.querySelectorAll('.to-reroll-input').forEach(element => {
|
|
||||||
element.addEventListener('change', this.toggleDice.bind(this));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async _prepareContext(_options) {
|
|
||||||
const context = await super._prepareContext(_options);
|
|
||||||
context.damage = this.damage;
|
|
||||||
context.disabledReroll = !this.getRerollDice().length;
|
|
||||||
context.saveDisabled = !this.isSelectionDone();
|
|
||||||
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
static async #save() {
|
|
||||||
const update = {
|
|
||||||
'system.damage': Object.keys(this.damage).reduce((acc, typeKey) => {
|
|
||||||
const type = this.damage[typeKey];
|
|
||||||
let typeTotal = 0;
|
|
||||||
const messageType = this.message.system.damage[typeKey];
|
|
||||||
const parts = Object.keys(type).map(partKey => {
|
|
||||||
const part = type[partKey];
|
|
||||||
const messagePart = messageType.parts[partKey];
|
|
||||||
let partTotal = messagePart.modifierTotal;
|
|
||||||
const dice = Object.keys(part).map(diceKey => {
|
|
||||||
const dice = part[diceKey];
|
|
||||||
const total = dice.results.reduce((acc, result) => {
|
|
||||||
if (result.active) acc += result.result;
|
|
||||||
return acc;
|
|
||||||
}, 0);
|
|
||||||
partTotal += total;
|
|
||||||
const messageDice = messagePart.dice[diceKey];
|
|
||||||
return {
|
|
||||||
...messageDice,
|
|
||||||
total: total,
|
|
||||||
results: dice.results.map(x => ({
|
|
||||||
...x,
|
|
||||||
hasRerolls: dice.results.length > 1
|
|
||||||
}))
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
typeTotal += partTotal;
|
|
||||||
return {
|
|
||||||
...messagePart,
|
|
||||||
total: partTotal,
|
|
||||||
dice: dice
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
acc[typeKey] = {
|
|
||||||
...messageType,
|
|
||||||
total: typeTotal,
|
|
||||||
parts: parts
|
|
||||||
};
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, {})
|
|
||||||
};
|
|
||||||
await this.message.update(update);
|
|
||||||
|
|
||||||
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.TagTeamRoll });
|
|
||||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
|
||||||
action: socketEvent.Refresh,
|
|
||||||
data: {
|
|
||||||
refreshType: RefreshType.TagTeamRoll
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
getRerollDice() {
|
|
||||||
const rerollDice = [];
|
|
||||||
Object.keys(this.damage).forEach(typeKey => {
|
|
||||||
const type = this.damage[typeKey];
|
|
||||||
Object.keys(type).forEach(partKey => {
|
|
||||||
const part = type[partKey];
|
|
||||||
Object.keys(part).forEach(diceKey => {
|
|
||||||
const dice = part[diceKey];
|
|
||||||
Object.keys(dice.results).forEach(resultKey => {
|
|
||||||
const result = dice.results[resultKey];
|
|
||||||
if (result.toReroll) {
|
|
||||||
rerollDice.push({
|
|
||||||
...result,
|
|
||||||
dice: dice.dice,
|
|
||||||
type: typeKey,
|
|
||||||
part: partKey,
|
|
||||||
dice: diceKey,
|
|
||||||
result: resultKey
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return rerollDice;
|
|
||||||
}
|
|
||||||
|
|
||||||
isSelectionDone() {
|
|
||||||
const diceFinishedData = [];
|
|
||||||
Object.keys(this.damage).forEach(typeKey => {
|
|
||||||
const type = this.damage[typeKey];
|
|
||||||
Object.keys(type).forEach(partKey => {
|
|
||||||
const part = type[partKey];
|
|
||||||
Object.keys(part).forEach(diceKey => {
|
|
||||||
const dice = part[diceKey];
|
|
||||||
const selected = dice.results.reduce((acc, result) => acc + (result.active ? 1 : 0), 0);
|
|
||||||
diceFinishedData.push(selected === dice.maxSelected);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return diceFinishedData.every(x => x);
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleDice(event) {
|
|
||||||
const target = event.target;
|
|
||||||
const { type, part, dice } = target.dataset;
|
|
||||||
const toggleDice = this.damage[type][part][dice];
|
|
||||||
|
|
||||||
const existingDiceRerolls = this.getRerollDice().filter(
|
|
||||||
x => x.type === type && x.part === part && x.dice === dice
|
|
||||||
);
|
|
||||||
|
|
||||||
const allRerolled = existingDiceRerolls.length === toggleDice.results.filter(x => x.active).length;
|
|
||||||
|
|
||||||
toggleDice.toReroll = !allRerolled;
|
|
||||||
toggleDice.results.forEach(result => {
|
|
||||||
if (result.active) {
|
|
||||||
result.toReroll = !allRerolled;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
static #toggleResult(event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
|
|
||||||
const target = event.target.closest('.to-reroll-result');
|
|
||||||
const { type, part, dice, result } = target.dataset;
|
|
||||||
const toggleDice = this.damage[type][part][dice];
|
|
||||||
const toggleResult = toggleDice.results[result];
|
|
||||||
toggleResult.toReroll = !toggleResult.toReroll;
|
|
||||||
|
|
||||||
const existingDiceRerolls = this.getRerollDice().filter(
|
|
||||||
x => x.type === type && x.part === part && x.dice === dice
|
|
||||||
);
|
|
||||||
|
|
||||||
const allToReroll = existingDiceRerolls.length === toggleDice.results.filter(x => x.active).length;
|
|
||||||
toggleDice.toReroll = allToReroll;
|
|
||||||
|
|
||||||
this.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
static async #selectRoll(_, button) {
|
|
||||||
const { type, part, dice, result } = button.dataset;
|
|
||||||
|
|
||||||
const diceVal = this.damage[type][part][dice];
|
|
||||||
const diceResult = diceVal.results[result];
|
|
||||||
if (!diceResult.active && diceVal.results.filter(x => x.active).length === diceVal.maxSelected) {
|
|
||||||
return ui.notifications.warn(
|
|
||||||
game.i18n.localize('DAGGERHEART.APPLICATIONS.RerollDialog.deselectDiceNotification')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (diceResult.active) {
|
|
||||||
diceVal.toReroll = false;
|
|
||||||
diceResult.toReroll = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
diceVal.selectedResults += diceResult.active ? -1 : 1;
|
|
||||||
diceResult.active = !diceResult.active;
|
|
||||||
|
|
||||||
this.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
static async #doReroll() {
|
|
||||||
const toReroll = this.getRerollDice().map(x => {
|
|
||||||
const { type, part, dice, result } = x;
|
|
||||||
const diceData = this.damage[type][part][dice].results[result];
|
|
||||||
return {
|
|
||||||
...diceData,
|
|
||||||
dice: this.damage[type][part][dice].dice,
|
|
||||||
typeKey: type,
|
|
||||||
partKey: part,
|
|
||||||
diceKey: dice,
|
|
||||||
resultsIndex: result
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const roll = await new Roll(toReroll.map(x => `1${x.dice}`).join(' + ')).evaluate();
|
|
||||||
|
|
||||||
if (game.modules.get('dice-so-nice')?.active) {
|
|
||||||
const diceSoNiceRoll = {
|
|
||||||
_evaluated: true,
|
|
||||||
dice: roll.dice,
|
|
||||||
options: { appearance: {} }
|
|
||||||
};
|
|
||||||
|
|
||||||
await game.dice3d.showForRoll(diceSoNiceRoll, game.user, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
toReroll.forEach((data, index) => {
|
|
||||||
const { typeKey, partKey, diceKey, resultsIndex } = data;
|
|
||||||
const rerolledDice = roll.dice[index];
|
|
||||||
|
|
||||||
const dice = this.damage[typeKey][partKey][diceKey];
|
|
||||||
dice.toReroll = false;
|
|
||||||
dice.results[resultsIndex].active = false;
|
|
||||||
dice.results[resultsIndex].discarded = true;
|
|
||||||
dice.results[resultsIndex].toReroll = false;
|
|
||||||
dice.results.splice(dice.results.length, 0, {
|
|
||||||
...rerolledDice.results[0],
|
|
||||||
toReroll: false,
|
|
||||||
selected: true
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.render();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,279 +0,0 @@
|
||||||
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
|
||||||
|
|
||||||
export default class RerollDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
|
||||||
constructor(message, options = {}) {
|
|
||||||
super(options);
|
|
||||||
|
|
||||||
this.message = message;
|
|
||||||
this.damage = Object.keys(message.system.damage).reduce((acc, typeKey) => {
|
|
||||||
const type = message.system.damage[typeKey];
|
|
||||||
acc[typeKey] = Object.keys(type.parts).reduce((acc, partKey) => {
|
|
||||||
const part = type.parts[partKey];
|
|
||||||
acc[partKey] = Object.keys(part.dice).reduce((acc, diceKey) => {
|
|
||||||
const dice = part.dice[diceKey];
|
|
||||||
const activeResults = dice.results.filter(x => x.active);
|
|
||||||
acc[diceKey] = {
|
|
||||||
dice: dice.dice,
|
|
||||||
selectedResults: activeResults.length,
|
|
||||||
maxSelected: activeResults.length,
|
|
||||||
results: activeResults.map(x => ({ ...x, selected: true }))
|
|
||||||
};
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
}
|
|
||||||
|
|
||||||
static DEFAULT_OPTIONS = {
|
|
||||||
id: 'reroll-dialog',
|
|
||||||
classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'reroll-dialog'],
|
|
||||||
window: {
|
|
||||||
icon: 'fa-solid fa-dice'
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
toggleResult: RerollDialog.#toggleResult,
|
|
||||||
selectRoll: RerollDialog.#selectRoll,
|
|
||||||
doReroll: RerollDialog.#doReroll,
|
|
||||||
save: RerollDialog.#save
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
static PARTS = {
|
|
||||||
main: {
|
|
||||||
id: 'main',
|
|
||||||
template: 'systems/daggerheart/templates/dialogs/rerollDialog/main.hbs'
|
|
||||||
},
|
|
||||||
footer: {
|
|
||||||
id: 'footer',
|
|
||||||
template: 'systems/daggerheart/templates/dialogs/rerollDialog/footer.hbs'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
get title() {
|
|
||||||
return game.i18n.localize('DAGGERHEART.APPLICATIONS.RerollDialog.title');
|
|
||||||
}
|
|
||||||
|
|
||||||
_attachPartListeners(partId, htmlElement, options) {
|
|
||||||
super._attachPartListeners(partId, htmlElement, options);
|
|
||||||
|
|
||||||
htmlElement.querySelectorAll('.to-reroll-input').forEach(element => {
|
|
||||||
element.addEventListener('change', this.toggleDice.bind(this));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async _prepareContext(_options) {
|
|
||||||
const context = await super._prepareContext(_options);
|
|
||||||
context.damage = this.damage;
|
|
||||||
context.disabledReroll = !this.getRerollDice().length;
|
|
||||||
context.saveDisabled = !this.isSelectionDone();
|
|
||||||
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
static async #save() {
|
|
||||||
const update = {
|
|
||||||
'system.damage': Object.keys(this.damage).reduce((acc, typeKey) => {
|
|
||||||
const type = this.damage[typeKey];
|
|
||||||
let typeTotal = 0;
|
|
||||||
const messageType = this.message.system.damage[typeKey];
|
|
||||||
const parts = Object.keys(type).map(partKey => {
|
|
||||||
const part = type[partKey];
|
|
||||||
const messagePart = messageType.parts[partKey];
|
|
||||||
let partTotal = messagePart.modifierTotal;
|
|
||||||
const dice = Object.keys(part).map(diceKey => {
|
|
||||||
const dice = part[diceKey];
|
|
||||||
const total = dice.results.reduce((acc, result) => {
|
|
||||||
if (result.active) acc += result.result;
|
|
||||||
return acc;
|
|
||||||
}, 0);
|
|
||||||
partTotal += total;
|
|
||||||
const messageDice = messagePart.dice[diceKey];
|
|
||||||
return {
|
|
||||||
...messageDice,
|
|
||||||
total: total,
|
|
||||||
results: dice.results.map(x => ({
|
|
||||||
...x,
|
|
||||||
hasRerolls: dice.results.length > 1
|
|
||||||
}))
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
typeTotal += partTotal;
|
|
||||||
return {
|
|
||||||
...messagePart,
|
|
||||||
total: partTotal,
|
|
||||||
dice: dice
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
acc[typeKey] = {
|
|
||||||
...messageType,
|
|
||||||
total: typeTotal,
|
|
||||||
parts: parts
|
|
||||||
};
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, {})
|
|
||||||
};
|
|
||||||
await this.message.update(update);
|
|
||||||
await this.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
getRerollDice() {
|
|
||||||
const rerollDice = [];
|
|
||||||
Object.keys(this.damage).forEach(typeKey => {
|
|
||||||
const type = this.damage[typeKey];
|
|
||||||
Object.keys(type).forEach(partKey => {
|
|
||||||
const part = type[partKey];
|
|
||||||
Object.keys(part).forEach(diceKey => {
|
|
||||||
const dice = part[diceKey];
|
|
||||||
Object.keys(dice.results).forEach(resultKey => {
|
|
||||||
const result = dice.results[resultKey];
|
|
||||||
if (result.toReroll) {
|
|
||||||
rerollDice.push({
|
|
||||||
...result,
|
|
||||||
dice: dice.dice,
|
|
||||||
type: typeKey,
|
|
||||||
part: partKey,
|
|
||||||
dice: diceKey,
|
|
||||||
result: resultKey
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return rerollDice;
|
|
||||||
}
|
|
||||||
|
|
||||||
isSelectionDone() {
|
|
||||||
const diceFinishedData = [];
|
|
||||||
Object.keys(this.damage).forEach(typeKey => {
|
|
||||||
const type = this.damage[typeKey];
|
|
||||||
Object.keys(type).forEach(partKey => {
|
|
||||||
const part = type[partKey];
|
|
||||||
Object.keys(part).forEach(diceKey => {
|
|
||||||
const dice = part[diceKey];
|
|
||||||
const selected = dice.results.reduce((acc, result) => acc + (result.active ? 1 : 0), 0);
|
|
||||||
diceFinishedData.push(selected === dice.maxSelected);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return diceFinishedData.every(x => x);
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleDice(event) {
|
|
||||||
const target = event.target;
|
|
||||||
const { type, part, dice } = target.dataset;
|
|
||||||
const toggleDice = this.damage[type][part][dice];
|
|
||||||
|
|
||||||
const existingDiceRerolls = this.getRerollDice().filter(
|
|
||||||
x => x.type === type && x.part === part && x.dice === dice
|
|
||||||
);
|
|
||||||
|
|
||||||
const allRerolled = existingDiceRerolls.length === toggleDice.results.filter(x => x.active).length;
|
|
||||||
|
|
||||||
toggleDice.toReroll = !allRerolled;
|
|
||||||
toggleDice.results.forEach(result => {
|
|
||||||
if (result.active) {
|
|
||||||
result.toReroll = !allRerolled;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
static #toggleResult(event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
|
|
||||||
const target = event.target.closest('.to-reroll-result');
|
|
||||||
const { type, part, dice, result } = target.dataset;
|
|
||||||
const toggleDice = this.damage[type][part][dice];
|
|
||||||
const toggleResult = toggleDice.results[result];
|
|
||||||
toggleResult.toReroll = !toggleResult.toReroll;
|
|
||||||
|
|
||||||
const existingDiceRerolls = this.getRerollDice().filter(
|
|
||||||
x => x.type === type && x.part === part && x.dice === dice
|
|
||||||
);
|
|
||||||
|
|
||||||
const allToReroll = existingDiceRerolls.length === toggleDice.results.length;
|
|
||||||
toggleDice.toReroll = allToReroll;
|
|
||||||
|
|
||||||
this.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
static async #selectRoll(_, button) {
|
|
||||||
const { type, part, dice, result } = button.dataset;
|
|
||||||
|
|
||||||
const diceVal = this.damage[type][part][dice];
|
|
||||||
const diceResult = diceVal.results[result];
|
|
||||||
if (!diceResult.active && diceVal.results.filter(x => x.active).length === diceVal.maxSelected) {
|
|
||||||
return ui.notifications.warn(
|
|
||||||
game.i18n.localize('DAGGERHEART.APPLICATIONS.RerollDialog.deselectDiceNotification')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (diceResult.active) {
|
|
||||||
diceVal.toReroll = false;
|
|
||||||
diceResult.toReroll = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
diceVal.selectedResults += diceResult.active ? -1 : 1;
|
|
||||||
diceResult.active = !diceResult.active;
|
|
||||||
|
|
||||||
this.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
static async #doReroll() {
|
|
||||||
const toReroll = this.getRerollDice().map(x => {
|
|
||||||
const { type, part, dice, result } = x;
|
|
||||||
const diceData = this.damage[type][part][dice].results[result];
|
|
||||||
return {
|
|
||||||
...diceData,
|
|
||||||
dice: this.damage[type][part][dice].dice,
|
|
||||||
typeKey: type,
|
|
||||||
partKey: part,
|
|
||||||
diceKey: dice,
|
|
||||||
resultsIndex: result
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const roll = await new Roll(toReroll.map(x => `1${x.dice}`).join(' + ')).evaluate();
|
|
||||||
|
|
||||||
if (game.modules.get('dice-so-nice')?.active) {
|
|
||||||
const diceSoNiceRoll = {
|
|
||||||
_evaluated: true,
|
|
||||||
dice: roll.dice,
|
|
||||||
options: { appearance: {} }
|
|
||||||
};
|
|
||||||
|
|
||||||
await game.dice3d.showForRoll(diceSoNiceRoll, game.user, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
toReroll.forEach((data, index) => {
|
|
||||||
const { typeKey, partKey, diceKey, resultsIndex } = data;
|
|
||||||
const rerolledDice = roll.dice[index];
|
|
||||||
|
|
||||||
const dice = this.damage[typeKey][partKey][diceKey];
|
|
||||||
dice.toReroll = false;
|
|
||||||
dice.results[resultsIndex].active = false;
|
|
||||||
dice.results[resultsIndex].discarded = true;
|
|
||||||
dice.results[resultsIndex].toReroll = false;
|
|
||||||
dice.results.splice(dice.results.length, 0, {
|
|
||||||
...rerolledDice.results[0],
|
|
||||||
toReroll: false,
|
|
||||||
selected: true
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.render();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { itemAbleRollParse } from '../../helpers/utils.mjs';
|
import { itemAbleRollParse, triggerChatRollFx } from '../../helpers/utils.mjs';
|
||||||
|
|
||||||
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||||
|
|
||||||
|
|
@ -69,7 +69,7 @@ export default class ResourceDiceDialog extends HandlebarsApplicationMixin(Appli
|
||||||
const max = itemAbleRollParse(this.item.system.resource.max, this.actor, this.item);
|
const max = itemAbleRollParse(this.item.system.resource.max, this.actor, this.item);
|
||||||
const diceFormula = `${max}${this.item.system.resource.dieFaces}`;
|
const diceFormula = `${max}${this.item.system.resource.dieFaces}`;
|
||||||
const roll = await new Roll(diceFormula).evaluate();
|
const roll = await new Roll(diceFormula).evaluate();
|
||||||
if (game.modules.get('dice-so-nice')?.active) await game.dice3d.showForRoll(roll, game.user, true);
|
await triggerChatRollFx([roll]);
|
||||||
this.rollValues = roll.terms[0].results.map(x => ({ value: x.result, used: false }));
|
this.rollValues = roll.terms[0].results.map(x => ({ value: x.result, used: false }));
|
||||||
this.resetUsed = true;
|
this.resetUsed = true;
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -122,15 +122,16 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
||||||
|
|
||||||
async toggleClowncar(actors) {
|
async toggleClowncar(actors) {
|
||||||
const animationDuration = 500;
|
const animationDuration = 500;
|
||||||
const activeTokens = actors.flatMap(member => member.getActiveTokens());
|
const scene = game.scenes.get(game.user.viewedScene);
|
||||||
|
/* getDependentTokens returns already removed tokens with id = null. Need to filter that until it's potentially fixed from Foundry */
|
||||||
|
const activeTokens = actors.flatMap(member =>
|
||||||
|
member.getDependentTokens({ scenes: scene }).filter(x => x._id && !x._destroyed)
|
||||||
|
);
|
||||||
const { x: actorX, y: actorY } = this.document;
|
const { x: actorX, y: actorY } = this.document;
|
||||||
if (activeTokens.length > 0) {
|
if (activeTokens.length > 0) {
|
||||||
for (let token of activeTokens) {
|
for (let token of activeTokens) {
|
||||||
await token.document.update(
|
await token.update({ x: actorX, y: actorY, alpha: 0 }, { animation: { duration: animationDuration } });
|
||||||
{ x: actorX, y: actorY, alpha: 0 },
|
setTimeout(() => token.delete(), animationDuration);
|
||||||
{ animation: { duration: animationDuration } }
|
|
||||||
);
|
|
||||||
setTimeout(() => token.document.delete(), animationDuration);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const activeScene = game.scenes.find(x => x.id === game.user.viewedScene);
|
const activeScene = game.scenes.find(x => x.id === game.user.viewedScene);
|
||||||
|
|
@ -140,11 +141,16 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
||||||
tokenData.push(data.toObject());
|
tokenData.push(data.toObject());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const viewedLevel = game.scenes.get(game.user.viewedScene).levels.get(game.user.viewedLevel);
|
||||||
|
const elevation = this.actor.token?.elevation ?? viewedLevel.elevation.bottom;
|
||||||
|
|
||||||
const newTokens = await activeScene.createEmbeddedDocuments(
|
const newTokens = await activeScene.createEmbeddedDocuments(
|
||||||
'Token',
|
'Token',
|
||||||
tokenData.map(tokenData => ({
|
tokenData.map(tokenData => ({
|
||||||
...tokenData,
|
...tokenData,
|
||||||
alpha: 0,
|
alpha: 0,
|
||||||
|
level: viewedLevel,
|
||||||
|
elevation: elevation,
|
||||||
x: actorX,
|
x: actorX,
|
||||||
y: actorY
|
y: actorY
|
||||||
}))
|
}))
|
||||||
|
|
|
||||||
|
|
@ -156,6 +156,7 @@ export default class DhCharacterLevelUp extends LevelUpBase {
|
||||||
if (multiclasses?.[0]) {
|
if (multiclasses?.[0]) {
|
||||||
const data = multiclasses[0];
|
const data = multiclasses[0];
|
||||||
const multiclass = data.data.length > 0 ? await foundry.utils.fromUuid(data.data[0]) : {};
|
const multiclass = data.data.length > 0 ? await foundry.utils.fromUuid(data.data[0]) : {};
|
||||||
|
const subclasses = (await multiclass?.system?.fetchSubclasses()) ?? [];
|
||||||
|
|
||||||
context.multiclass = {
|
context.multiclass = {
|
||||||
...data,
|
...data,
|
||||||
|
|
@ -175,13 +176,12 @@ export default class DhCharacterLevelUp extends LevelUpBase {
|
||||||
alreadySelected
|
alreadySelected
|
||||||
};
|
};
|
||||||
}) ?? [],
|
}) ?? [],
|
||||||
subclasses:
|
subclasses: subclasses.map(subclass => ({
|
||||||
multiclass?.system?.subclasses.map(subclass => ({
|
|
||||||
...subclass,
|
...subclass,
|
||||||
uuid: subclass.uuid,
|
uuid: subclass.uuid,
|
||||||
selected: data.secondaryData.subclass === subclass.uuid,
|
selected: data.secondaryData.subclass === subclass.uuid,
|
||||||
disabled: data.secondaryData.subclass && data.secondaryData.subclass !== subclass.uuid
|
disabled: data.secondaryData.subclass && data.secondaryData.subclass !== subclass.uuid
|
||||||
})) ?? [],
|
})),
|
||||||
compendium: 'classes',
|
compendium: 'classes',
|
||||||
limit: 1
|
limit: 1
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ export default class DhCompanionLevelUp extends BaseLevelUp {
|
||||||
break;
|
break;
|
||||||
case 'summary':
|
case 'summary':
|
||||||
const levelKeys = Object.keys(this.levelup.levels);
|
const levelKeys = Object.keys(this.levelup.levels);
|
||||||
const actorDamageDice = this.actor.system.attack.damage.parts[0].value.dice;
|
const actorDamageDice = this.actor.system.attack.damage.parts.hitPoints.value.dice;
|
||||||
const actorRange = this.actor.system.attack.range;
|
const actorRange = this.actor.system.attack.range;
|
||||||
|
|
||||||
let achievementExperiences = [];
|
let achievementExperiences = [];
|
||||||
|
|
|
||||||
|
|
@ -135,192 +135,6 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
context.tabs.advancements.progress = { selected: selections, max: currentLevel.maxSelections };
|
context.tabs.advancements.progress = { selected: selections, max: currentLevel.maxSelections };
|
||||||
context.showTabs = this.tabGroups.primary !== 'summary';
|
context.showTabs = this.tabGroups.primary !== 'summary';
|
||||||
break;
|
break;
|
||||||
|
|
||||||
const actorArmor = this.actor.system.armor;
|
|
||||||
const levelKeys = Object.keys(this.levelup.levels);
|
|
||||||
let achivementProficiency = 0;
|
|
||||||
const achievementCards = [];
|
|
||||||
let achievementExperiences = [];
|
|
||||||
for (var levelKey of levelKeys) {
|
|
||||||
const level = this.levelup.levels[levelKey];
|
|
||||||
if (Number(levelKey) < this.levelup.startLevel) continue;
|
|
||||||
|
|
||||||
achivementProficiency += level.achievements.proficiency ?? 0;
|
|
||||||
const cards = level.achievements.domainCards ? Object.values(level.achievements.domainCards) : null;
|
|
||||||
if (cards) {
|
|
||||||
for (var card of cards) {
|
|
||||||
const itemCard = await foundry.utils.fromUuid(card.uuid);
|
|
||||||
achievementCards.push(itemCard);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
achievementExperiences = level.achievements.experiences
|
|
||||||
? Object.values(level.achievements.experiences).reduce((acc, experience) => {
|
|
||||||
if (experience.name) acc.push(experience);
|
|
||||||
return acc;
|
|
||||||
}, [])
|
|
||||||
: [];
|
|
||||||
}
|
|
||||||
|
|
||||||
context.achievements = {
|
|
||||||
proficiency: {
|
|
||||||
old: this.actor.system.proficiency,
|
|
||||||
new: this.actor.system.proficiency + achivementProficiency,
|
|
||||||
shown: achivementProficiency > 0
|
|
||||||
},
|
|
||||||
damageThresholds: {
|
|
||||||
major: {
|
|
||||||
old: this.actor.system.damageThresholds.major,
|
|
||||||
new: this.actor.system.damageThresholds.major + changedActorLevel - currentActorLevel
|
|
||||||
},
|
|
||||||
severe: {
|
|
||||||
old: this.actor.system.damageThresholds.severe,
|
|
||||||
new:
|
|
||||||
this.actor.system.damageThresholds.severe +
|
|
||||||
(actorArmor
|
|
||||||
? changedActorLevel - currentActorLevel
|
|
||||||
: (changedActorLevel - currentActorLevel) * 2)
|
|
||||||
},
|
|
||||||
unarmored: !actorArmor
|
|
||||||
},
|
|
||||||
domainCards: {
|
|
||||||
values: achievementCards,
|
|
||||||
shown: achievementCards.length > 0
|
|
||||||
},
|
|
||||||
experiences: {
|
|
||||||
values: achievementExperiences
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const advancement = {};
|
|
||||||
for (var levelKey of levelKeys) {
|
|
||||||
const level = this.levelup.levels[levelKey];
|
|
||||||
if (Number(levelKey) < this.levelup.startLevel) continue;
|
|
||||||
|
|
||||||
for (var choiceKey of Object.keys(level.choices)) {
|
|
||||||
const choice = level.choices[choiceKey];
|
|
||||||
for (var checkbox of Object.values(choice)) {
|
|
||||||
switch (choiceKey) {
|
|
||||||
case 'proficiency':
|
|
||||||
case 'hitPoint':
|
|
||||||
case 'stress':
|
|
||||||
case 'evasion':
|
|
||||||
advancement[choiceKey] = advancement[choiceKey]
|
|
||||||
? advancement[choiceKey] + Number(checkbox.value)
|
|
||||||
: Number(checkbox.value);
|
|
||||||
break;
|
|
||||||
case 'trait':
|
|
||||||
if (!advancement[choiceKey]) advancement[choiceKey] = {};
|
|
||||||
for (var traitKey of checkbox.data) {
|
|
||||||
if (!advancement[choiceKey][traitKey]) advancement[choiceKey][traitKey] = 0;
|
|
||||||
advancement[choiceKey][traitKey] += 1;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'domainCard':
|
|
||||||
if (!advancement[choiceKey]) advancement[choiceKey] = [];
|
|
||||||
if (checkbox.data.length === 1) {
|
|
||||||
const choiceItem = await foundry.utils.fromUuid(checkbox.data[0]);
|
|
||||||
advancement[choiceKey].push(choiceItem.toObject());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'experience':
|
|
||||||
if (!advancement[choiceKey]) advancement[choiceKey] = [];
|
|
||||||
const data = checkbox.data.map(data => {
|
|
||||||
const experience = Object.keys(this.actor.system.experiences).find(
|
|
||||||
x => x === data
|
|
||||||
);
|
|
||||||
return this.actor.system.experiences[experience]?.description ?? '';
|
|
||||||
});
|
|
||||||
advancement[choiceKey].push({ data: data, value: checkbox.value });
|
|
||||||
break;
|
|
||||||
case 'subclass':
|
|
||||||
if (checkbox.data[0]) {
|
|
||||||
const subclassItem = await foundry.utils.fromUuid(checkbox.data[0]);
|
|
||||||
if (!advancement[choiceKey]) advancement[choiceKey] = [];
|
|
||||||
advancement[choiceKey].push({
|
|
||||||
...subclassItem.toObject(),
|
|
||||||
featureLabel: game.i18n.localize(
|
|
||||||
subclassFeatureLabels[Number(checkbox.secondaryData.featureState)]
|
|
||||||
)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'multiclass':
|
|
||||||
const multiclassItem = await foundry.utils.fromUuid(checkbox.data[0]);
|
|
||||||
const subclass = multiclassItem
|
|
||||||
? await foundry.utils.fromUuid(checkbox.secondaryData.subclass)
|
|
||||||
: null;
|
|
||||||
advancement[choiceKey] = multiclassItem
|
|
||||||
? {
|
|
||||||
...multiclassItem.toObject(),
|
|
||||||
domain: checkbox.secondaryData.domain
|
|
||||||
? game.i18n.localize(
|
|
||||||
CONFIG.DH.DOMAIN.allDomains()[checkbox.secondaryData.domain]
|
|
||||||
.label
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
subclass: subclass ? subclass.name : null
|
|
||||||
}
|
|
||||||
: {};
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
context.advancements = {
|
|
||||||
statistics: {
|
|
||||||
proficiency: {
|
|
||||||
old: context.achievements.proficiency.new,
|
|
||||||
new: context.achievements.proficiency.new + (advancement.proficiency ?? 0)
|
|
||||||
},
|
|
||||||
hitPoints: {
|
|
||||||
old: this.actor.system.resources.hitPoints.max,
|
|
||||||
new: this.actor.system.resources.hitPoints.max + (advancement.hitPoint ?? 0)
|
|
||||||
},
|
|
||||||
stress: {
|
|
||||||
old: this.actor.system.resources.stress.max,
|
|
||||||
new: this.actor.system.resources.stress.max + (advancement.stress ?? 0)
|
|
||||||
},
|
|
||||||
evasion: {
|
|
||||||
old: this.actor.system.evasion,
|
|
||||||
new: this.actor.system.evasion + (advancement.evasion ?? 0)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
traits: Object.keys(this.actor.system.traits).reduce((acc, traitKey) => {
|
|
||||||
if (advancement.trait?.[traitKey]) {
|
|
||||||
if (!acc) acc = {};
|
|
||||||
acc[traitKey] = {
|
|
||||||
label: game.i18n.localize(abilities[traitKey].label),
|
|
||||||
old: this.actor.system.traits[traitKey].value,
|
|
||||||
new: this.actor.system.traits[traitKey].value + advancement.trait[traitKey]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, null),
|
|
||||||
domainCards: advancement.domainCard ?? [],
|
|
||||||
experiences:
|
|
||||||
advancement.experience?.flatMap(x => x.data.map(data => ({ name: data, modifier: x.value }))) ??
|
|
||||||
[],
|
|
||||||
multiclass: advancement.multiclass,
|
|
||||||
subclass: advancement.subclass
|
|
||||||
};
|
|
||||||
|
|
||||||
context.advancements.statistics.proficiency.shown =
|
|
||||||
context.advancements.statistics.proficiency.new > context.advancements.statistics.proficiency.old;
|
|
||||||
context.advancements.statistics.hitPoints.shown =
|
|
||||||
context.advancements.statistics.hitPoints.new > context.advancements.statistics.hitPoints.old;
|
|
||||||
context.advancements.statistics.stress.shown =
|
|
||||||
context.advancements.statistics.stress.new > context.advancements.statistics.stress.old;
|
|
||||||
context.advancements.statistics.evasion.shown =
|
|
||||||
context.advancements.statistics.evasion.new > context.advancements.statistics.evasion.old;
|
|
||||||
context.advancements.statistics.shown =
|
|
||||||
context.advancements.statistics.proficiency.shown ||
|
|
||||||
context.advancements.statistics.hitPoints.shown ||
|
|
||||||
context.advancements.statistics.stress.shown ||
|
|
||||||
context.advancements.statistics.evasion.shown;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
|
|
@ -358,14 +172,14 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
const experienceIncreaseTagify = htmlElement.querySelector('.levelup-experience-increases');
|
const experienceIncreaseTagify = htmlElement.querySelector('.levelup-experience-increases');
|
||||||
if (experienceIncreaseTagify) {
|
if (experienceIncreaseTagify) {
|
||||||
const allExperiences = {
|
const allExperiences = {
|
||||||
...this.actor.system.experiences,
|
|
||||||
...Object.values(this.levelup.levels).reduce((acc, level) => {
|
...Object.values(this.levelup.levels).reduce((acc, level) => {
|
||||||
for (const key of Object.keys(level.achievements.experiences)) {
|
for (const key of Object.keys(level.achievements.experiences)) {
|
||||||
acc[key] = level.achievements.experiences[key];
|
acc[key] = level.achievements.experiences[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {})
|
}, {}),
|
||||||
|
...this.actor.system.experiences
|
||||||
};
|
};
|
||||||
tagifyElement(
|
tagifyElement(
|
||||||
experienceIncreaseTagify,
|
experienceIncreaseTagify,
|
||||||
|
|
@ -384,9 +198,7 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
this._dragDrop.forEach(d => d.bind(htmlElement));
|
this._dragDrop.forEach(d => d.bind(htmlElement));
|
||||||
}
|
}
|
||||||
|
|
||||||
tagifyUpdate =
|
tagifyUpdate = type => async (_, { option, removed }) => {
|
||||||
type =>
|
|
||||||
async (_, { option, removed }) => {
|
|
||||||
const updatePath = Object.keys(this.levelup.levels[this.levelup.currentLevel].choices).reduce(
|
const updatePath = Object.keys(this.levelup.levels[this.levelup.currentLevel].choices).reduce(
|
||||||
(acc, choiceKey) => {
|
(acc, choiceKey) => {
|
||||||
const choice = this.levelup.levels[this.levelup.currentLevel].choices[choiceKey];
|
const choice = this.levelup.levels[this.levelup.currentLevel].choices[choiceKey];
|
||||||
|
|
@ -477,7 +289,7 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
const secondaryData = Object.keys(
|
const secondaryData = Object.keys(
|
||||||
foundry.utils.getProperty(this.levelup, `${target.dataset.path}.secondaryData`)
|
foundry.utils.getProperty(this.levelup, `${target.dataset.path}.secondaryData`)
|
||||||
).reduce((acc, key) => {
|
).reduce((acc, key) => {
|
||||||
acc[`-=${key}`] = null;
|
acc[key] = _del;
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
await this.levelup.updateSource({
|
await this.levelup.updateSource({
|
||||||
|
|
@ -511,9 +323,9 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
const current = foundry.utils.getProperty(this.levelup, `${basePath}.${button.dataset.option}`);
|
const current = foundry.utils.getProperty(this.levelup, `${basePath}.${button.dataset.option}`);
|
||||||
if (Number(button.dataset.cost) > 1 || Object.keys(current).length === 1) {
|
if (Number(button.dataset.cost) > 1 || Object.keys(current).length === 1) {
|
||||||
// Simple handling that doesn't cover potential Custom LevelTiers.
|
// Simple handling that doesn't cover potential Custom LevelTiers.
|
||||||
update[`${basePath}.-=${button.dataset.option}`] = null;
|
update[`${basePath}.${button.dataset.option}`] = _del;
|
||||||
} else {
|
} else {
|
||||||
update[`${basePath}.${button.dataset.option}.-=${button.dataset.checkboxNr}`] = null;
|
update[`${basePath}.${button.dataset.option}.${button.dataset.checkboxNr}`] = _del;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (this.levelup.levels[this.levelup.currentLevel].nrSelections.available < Number(button.dataset.cost)) {
|
if (this.levelup.levels[this.levelup.currentLevel].nrSelections.available < Number(button.dataset.cost)) {
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,15 @@ export default class DhSceneConfigSettings extends foundry.applications.sheets.S
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onDrop(event) {
|
async _onDrop(event) {
|
||||||
|
event.stopPropagation();
|
||||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||||
|
if (data.type === 'Level') {
|
||||||
|
const level = await foundry.documents.Level.fromDropData(data);
|
||||||
|
if (level?.parent === this.document) return this._onSortLevel(event, level);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const item = await foundry.utils.fromUuid(data.uuid);
|
const item = await foundry.utils.fromUuid(data.uuid);
|
||||||
if (item instanceof game.system.api.documents.DhpActor && item.type === 'environment') {
|
if (item instanceof game.system.api.documents.DhpActor && item.type === 'environment') {
|
||||||
let sceneUuid = data.uuid;
|
let sceneUuid = data.uuid;
|
||||||
|
|
@ -112,12 +120,6 @@ export default class DhSceneConfigSettings extends foundry.applications.sheets.S
|
||||||
foundry.utils.fromUuidSync(x)
|
foundry.utils.fromUuidSync(x)
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const key of Object.keys(this.document._source.flags.daggerheart?.sceneEnvironments ?? {})) {
|
|
||||||
if (!submitData.flags.daggerheart.sceneEnvironments[key]) {
|
|
||||||
submitData.flags.daggerheart.sceneEnvironments[`-=${key}`] = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
super._processSubmitData(event, form, submitData, options);
|
super._processSubmitData(event, form, submitData, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -118,8 +118,13 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App
|
||||||
break;
|
break;
|
||||||
case 'footer':
|
case 'footer':
|
||||||
partContext.buttons = [
|
partContext.buttons = [
|
||||||
{ type: 'button', action: 'reset', icon: 'fa-solid fa-arrow-rotate-left', label: 'Reset' },
|
{
|
||||||
{ type: 'submit', icon: 'fa-solid fa-floppy-disk', label: 'Save Changes' }
|
type: 'button',
|
||||||
|
action: 'reset',
|
||||||
|
icon: 'fa-solid fa-arrow-rotate-left',
|
||||||
|
label: game.i18n.localize('SETTINGS.UI.ACTIONS.Reset')
|
||||||
|
},
|
||||||
|
{ type: 'submit', icon: 'fa-solid fa-floppy-disk', label: game.i18n.localize('EDITOR.Save') }
|
||||||
];
|
];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { DhHomebrew } from '../../data/settings/_module.mjs';
|
import { DhHomebrew } from '../../data/settings/_module.mjs';
|
||||||
import { Resource } from '../../data/settings/Homebrew.mjs';
|
import { Resource } from '../../data/settings/Homebrew.mjs';
|
||||||
import { slugify } from '../../helpers/utils.mjs';
|
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
||||||
|
|
@ -112,7 +111,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
||||||
|
|
||||||
switch (partId) {
|
switch (partId) {
|
||||||
case 'domains':
|
case 'domains':
|
||||||
const selectedDomain = this.selected.domain ? this.settings.domains[this.selected.domain] : null;
|
const selectedDomain = this.settings.domains[this.selected.domain] ?? null;
|
||||||
const enrichedDescription = selectedDomain
|
const enrichedDescription = selectedDomain
|
||||||
? await foundry.applications.ux.TextEditor.implementation.enrichHTML(selectedDomain.description)
|
? await foundry.applications.ux.TextEditor.implementation.enrichHTML(selectedDomain.description)
|
||||||
: null;
|
: null;
|
||||||
|
|
@ -298,7 +297,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
||||||
const isDowntime = ['shortRest', 'longRest'].includes(type);
|
const isDowntime = ['shortRest', 'longRest'].includes(type);
|
||||||
const path = isDowntime ? `restMoves.${type}.moves` : `itemFeatures.${type}`;
|
const path = isDowntime ? `restMoves.${type}.moves` : `itemFeatures.${type}`;
|
||||||
await this.settings.updateSource({
|
await this.settings.updateSource({
|
||||||
[`${path}.-=${id}`]: null
|
[`${path}.${id}`]: _del
|
||||||
});
|
});
|
||||||
|
|
||||||
game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, this.settings.toObject());
|
game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, this.settings.toObject());
|
||||||
|
|
@ -322,7 +321,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
||||||
const fields = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).schema.fields;
|
const fields = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).schema.fields;
|
||||||
|
|
||||||
const removeUpdate = Object.keys(this.settings.restMoves[target.dataset.type].moves).reduce((acc, key) => {
|
const removeUpdate = Object.keys(this.settings.restMoves[target.dataset.type].moves).reduce((acc, key) => {
|
||||||
acc[`-=${key}`] = null;
|
acc[key] = _del;
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
@ -382,7 +381,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
||||||
[`itemFeatures.${target.dataset.type}`]: Object.keys(
|
[`itemFeatures.${target.dataset.type}`]: Object.keys(
|
||||||
this.settings.itemFeatures[target.dataset.type]
|
this.settings.itemFeatures[target.dataset.type]
|
||||||
).reduce((acc, key) => {
|
).reduce((acc, key) => {
|
||||||
acc[`-=${key}`] = null;
|
acc[key] = _del;
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {})
|
}, {})
|
||||||
|
|
@ -403,12 +402,12 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
||||||
const domainName = button.form.elements.domainName.value;
|
const domainName = button.form.elements.domainName.value;
|
||||||
if (!domainName) return;
|
if (!domainName) return;
|
||||||
|
|
||||||
const newSlug = slugify(domainName);
|
const newSlug = domainName.slugify();
|
||||||
const existingDomains = [
|
const existingDomains = [
|
||||||
...Object.values(this.settings.domains),
|
...Object.values(this.settings.domains),
|
||||||
...Object.values(CONFIG.DH.DOMAIN.domains)
|
...Object.values(CONFIG.DH.DOMAIN.domains)
|
||||||
];
|
];
|
||||||
if (existingDomains.find(x => slugify(game.i18n.localize(x.label)) === newSlug)) {
|
if (existingDomains.find(x => x.id === newSlug)) {
|
||||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.domains.duplicateDomain'));
|
ui.notifications.warn(game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.domains.duplicateDomain'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -455,12 +454,12 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
||||||
if (!confirmed) return;
|
if (!confirmed) return;
|
||||||
|
|
||||||
await this.settings.updateSource({
|
await this.settings.updateSource({
|
||||||
[`domains.-=${this.selected.domain}`]: null
|
[`domains.${this.selected.domain}`]: _del
|
||||||
});
|
});
|
||||||
|
|
||||||
const currentSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew);
|
const currentSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew);
|
||||||
if (currentSettings.domains[this.selected.domain]) {
|
if (currentSettings.domains[this.selected.domain]) {
|
||||||
await currentSettings.updateSource({ [`domains.-=${this.selected.domain}`]: null });
|
await currentSettings.updateSource({ [`domains.${this.selected.domain}`]: _del });
|
||||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, currentSettings);
|
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, currentSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -507,7 +506,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
||||||
|
|
||||||
static async deleteAdversaryType(_, target) {
|
static async deleteAdversaryType(_, target) {
|
||||||
const { key } = target.dataset;
|
const { key } = target.dataset;
|
||||||
await this.settings.updateSource({ [`adversaryTypes.-=${key}`]: null });
|
await this.settings.updateSource({ [`adversaryTypes.${key}`]: _del });
|
||||||
|
|
||||||
this.selected.adversaryType = this.selected.adversaryType === key ? null : this.selected.adversaryType;
|
this.selected.adversaryType = this.selected.adversaryType === key ? null : this.selected.adversaryType;
|
||||||
this.render();
|
this.render();
|
||||||
|
|
@ -529,7 +528,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
||||||
const identifier = button.form.elements.identifier.value;
|
const identifier = button.form.elements.identifier.value;
|
||||||
if (!identifier) return;
|
if (!identifier) return;
|
||||||
|
|
||||||
const sluggedIdentifier = slugify(identifier);
|
const sluggedIdentifier = identifier.slugify();
|
||||||
|
|
||||||
await this.settings.updateSource({
|
await this.settings.updateSource({
|
||||||
[`resources.${actorType}.resources.${sluggedIdentifier}`]: Resource.getDefaultResourceData(identifier)
|
[`resources.${actorType}.resources.${sluggedIdentifier}`]: Resource.getDefaultResourceData(identifier)
|
||||||
|
|
@ -563,7 +562,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
||||||
|
|
||||||
const { actorType, resourceKey } = target.dataset;
|
const { actorType, resourceKey } = target.dataset;
|
||||||
await this.settings.updateSource({
|
await this.settings.updateSource({
|
||||||
[`resources.${actorType}.resources.-=${resourceKey}`]: null
|
[`resources.${actorType}.resources.${resourceKey}`]: _del
|
||||||
});
|
});
|
||||||
|
|
||||||
game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, this.settings.toObject());
|
game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, this.settings.toObject());
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ export { default as ActionConfig } from './action-config.mjs';
|
||||||
export { default as ActionSettingsConfig } from './action-settings-config.mjs';
|
export { default as ActionSettingsConfig } from './action-settings-config.mjs';
|
||||||
export { default as CharacterSettings } from './character-settings.mjs';
|
export { default as CharacterSettings } from './character-settings.mjs';
|
||||||
export { default as AdversarySettings } from './adversary-settings.mjs';
|
export { default as AdversarySettings } from './adversary-settings.mjs';
|
||||||
|
export { default as NPCSettings } from './npc-settings.mjs';
|
||||||
export { default as CompanionSettings } from './companion-settings.mjs';
|
export { default as CompanionSettings } from './companion-settings.mjs';
|
||||||
export { default as SettingActiveEffectConfig } from './setting-active-effect-config.mjs';
|
|
||||||
export { default as SettingFeatureConfig } from './setting-feature-config.mjs';
|
export { default as SettingFeatureConfig } from './setting-feature-config.mjs';
|
||||||
export { default as EnvironmentSettings } from './environment-settings.mjs';
|
export { default as EnvironmentSettings } from './environment-settings.mjs';
|
||||||
export { default as ActiveEffectConfig } from './activeEffectConfig.mjs';
|
export { default as ActiveEffectConfig } from './activeEffectConfig.mjs';
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { getUnusedDamageTypes } from '../../helpers/utils.mjs';
|
||||||
import DaggerheartSheet from '../sheets/daggerheart-sheet.mjs';
|
import DaggerheartSheet from '../sheets/daggerheart-sheet.mjs';
|
||||||
|
|
||||||
const { ApplicationV2 } = foundry.applications.api;
|
const { ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
@ -35,7 +36,9 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
||||||
editDoc: this.editDoc,
|
editDoc: this.editDoc,
|
||||||
addTrigger: this.addTrigger,
|
addTrigger: this.addTrigger,
|
||||||
removeTrigger: this.removeTrigger,
|
removeTrigger: this.removeTrigger,
|
||||||
expandTrigger: this.expandTrigger
|
expandTrigger: this.expandTrigger,
|
||||||
|
addBeastformTraitBonus: this.addBeastformTraitBonus,
|
||||||
|
removeBeastformTraitBonus: this.removeBeastformTraitBonus
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
handler: this.updateForm,
|
handler: this.updateForm,
|
||||||
|
|
@ -104,7 +107,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static CLEAN_ARRAYS = ['damage.parts', 'cost', 'effects', 'summon'];
|
static CLEAN_ARRAYS = ['cost', 'effects', 'summon'];
|
||||||
|
|
||||||
_getTabs(tabs) {
|
_getTabs(tabs) {
|
||||||
for (const v of Object.values(tabs)) {
|
for (const v of Object.values(tabs)) {
|
||||||
|
|
@ -153,8 +156,13 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
||||||
context.openSection = this.openSection;
|
context.openSection = this.openSection;
|
||||||
context.tabs = this._getTabs(this.constructor.TABS);
|
context.tabs = this._getTabs(this.constructor.TABS);
|
||||||
context.config = CONFIG.DH;
|
context.config = CONFIG.DH;
|
||||||
if (this.action.damage?.hasOwnProperty('includeBase') && this.action.type === 'attack')
|
if (this.action.damage) {
|
||||||
|
context.allDamageTypesUsed = !getUnusedDamageTypes(this.action.damage.parts).length;
|
||||||
|
|
||||||
|
if (this.action.damage.hasOwnProperty('includeBase') && this.action.type === 'attack')
|
||||||
context.hasBaseDamage = !!this.action.parent.attack;
|
context.hasBaseDamage = !!this.action.parent.attack;
|
||||||
|
}
|
||||||
|
|
||||||
context.costOptions = this.getCostOptions();
|
context.costOptions = this.getCostOptions();
|
||||||
context.getRollTypeOptions = this.getRollTypeOptions();
|
context.getRollTypeOptions = this.getRollTypeOptions();
|
||||||
context.disableOption = this.disableOption.bind(this);
|
context.disableOption = this.disableOption.bind(this);
|
||||||
|
|
@ -196,7 +204,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.action.parent.metadata?.isQuantifiable) {
|
if (this.action.parent.metadata?.isInventoryItem) {
|
||||||
options.quantity = {
|
options.quantity = {
|
||||||
label: 'DAGGERHEART.GENERAL.itemQuantity',
|
label: 'DAGGERHEART.GENERAL.itemQuantity',
|
||||||
group: 'Global'
|
group: 'Global'
|
||||||
|
|
@ -256,7 +264,9 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
||||||
key = event.target.closest('[data-key]').dataset.key;
|
key = event.target.closest('[data-key]').dataset.key;
|
||||||
if (!this.action[key]) return;
|
if (!this.action[key]) return;
|
||||||
|
|
||||||
data[key].push(this.action.defaultValues[key] ?? {});
|
const value = key === 'areas' ? { name: this.action.item.name } : {};
|
||||||
|
|
||||||
|
data[key].push(this.action.defaultValues[key] ?? value);
|
||||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -291,18 +301,64 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
||||||
|
|
||||||
static addDamage(_event) {
|
static addDamage(_event) {
|
||||||
if (!this.action.damage.parts) return;
|
if (!this.action.damage.parts) return;
|
||||||
const data = this.action.toObject(),
|
|
||||||
part = {};
|
const choices = getUnusedDamageTypes(this.action._source.damage.parts);
|
||||||
if (this.action.actor?.isNPC) part.value = { multiplier: 'flat' };
|
const content = new foundry.data.fields.StringField({
|
||||||
data.damage.parts.push(part);
|
label: game.i18n.localize('Damage Type'),
|
||||||
|
choices,
|
||||||
|
required: true
|
||||||
|
}).toFormGroup(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
name: 'type',
|
||||||
|
localize: true,
|
||||||
|
nameAttr: 'value',
|
||||||
|
labelAttr: 'label'
|
||||||
|
}
|
||||||
|
).outerHTML;
|
||||||
|
|
||||||
|
const callback = (_, button) => {
|
||||||
|
const data = this.action.toObject();
|
||||||
|
const type = choices[button.form.elements.type.value].value;
|
||||||
|
const part = this.action.schema.fields.damage.fields.parts.element.getInitialValue();
|
||||||
|
part.applyTo = type;
|
||||||
|
if (type === CONFIG.DH.GENERAL.healingTypes.hitPoints.id)
|
||||||
|
part.type = this.action.schema.fields.damage.fields.parts.element.fields.type.element.initial;
|
||||||
|
|
||||||
|
data.damage.parts[type] = part;
|
||||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
|
};
|
||||||
|
|
||||||
|
const typeDialog = new foundry.applications.api.DialogV2({
|
||||||
|
buttons: [
|
||||||
|
foundry.utils.mergeObject(
|
||||||
|
{
|
||||||
|
action: 'ok',
|
||||||
|
label: 'Confirm',
|
||||||
|
icon: 'fas fa-check',
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
{ callback: callback }
|
||||||
|
)
|
||||||
|
],
|
||||||
|
content: content,
|
||||||
|
rejectClose: false,
|
||||||
|
modal: false,
|
||||||
|
window: {
|
||||||
|
title: game.i18n.localize('Add Damage')
|
||||||
|
},
|
||||||
|
position: { width: 300 }
|
||||||
|
});
|
||||||
|
|
||||||
|
typeDialog.render(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static removeDamage(_event, button) {
|
static removeDamage(_event, button) {
|
||||||
if (!this.action.damage.parts) return;
|
if (!this.action.damage.parts) return;
|
||||||
const data = this.action.toObject(),
|
const data = this.action.toObject();
|
||||||
index = button.dataset.index;
|
const key = button.dataset.key;
|
||||||
data.damage.parts.splice(index, 1);
|
delete data.damage.parts[key];
|
||||||
|
data.damage.parts[`${key}`] = _del;
|
||||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -360,6 +416,21 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async addBeastformTraitBonus() {
|
||||||
|
const data = this.action.toObject();
|
||||||
|
data.beastform.modifications.traitBonuses = [
|
||||||
|
...data.beastform.modifications.traitBonuses,
|
||||||
|
this.action.schema.fields.beastform.fields.modifications.fields.traitBonuses.element.getInitialValue()
|
||||||
|
];
|
||||||
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
|
}
|
||||||
|
|
||||||
|
static async removeBeastformTraitBonus(_event, button) {
|
||||||
|
const data = this.action.toObject();
|
||||||
|
data.beastform.modifications.traitBonuses.splice(button.dataset.index, 1);
|
||||||
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
|
}
|
||||||
|
|
||||||
updateSummonCount(event) {
|
updateSummonCount(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const wrapper = event.target.closest('.summon-count-wrapper');
|
const wrapper = event.target.closest('.summon-count-wrapper');
|
||||||
|
|
|
||||||
|
|
@ -19,17 +19,19 @@ export default class DHActionConfig extends DHActionBaseConfig {
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async addEffect(_event) {
|
static async addEffect(event) {
|
||||||
|
const { areaIndex } = event.target.dataset;
|
||||||
if (!this.action.effects) return;
|
if (!this.action.effects) return;
|
||||||
const effectData = this._addEffectData.bind(this)();
|
|
||||||
const data = this.action.toObject();
|
const data = this.action.toObject();
|
||||||
|
|
||||||
const [created] = await this.action.item.createEmbeddedDocuments('ActiveEffect', [effectData], {
|
const created = await this.action.item.createEmbeddedDocuments('ActiveEffect', [
|
||||||
render: false
|
game.system.api.data.activeEffects.BaseEffect.getDefaultObject({ transfer: false })
|
||||||
});
|
]);
|
||||||
data.effects.push({ _id: created._id });
|
|
||||||
|
if (areaIndex !== undefined) data.areas[areaIndex].effects.push(created[0]._id);
|
||||||
|
else data.effects.push({ _id: created[0]._id });
|
||||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
this.action.item.effects.get(created._id).sheet.render(true);
|
this.action.item.effects.get(created[0]._id).sheet.render(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -52,9 +54,19 @@ export default class DHActionConfig extends DHActionBaseConfig {
|
||||||
|
|
||||||
static removeEffect(event, button) {
|
static removeEffect(event, button) {
|
||||||
if (!this.action.effects) return;
|
if (!this.action.effects) return;
|
||||||
const index = button.dataset.index,
|
|
||||||
|
const { areaIndex, index } = button.dataset;
|
||||||
|
let effectId = null;
|
||||||
|
if (areaIndex !== undefined) {
|
||||||
|
effectId = this.action.areas[areaIndex].effects[index];
|
||||||
|
const data = this.action.toObject();
|
||||||
|
data.areas[areaIndex].effects.splice(index, 1);
|
||||||
|
this.constructor.updateForm.call(this, null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
|
} else {
|
||||||
effectId = this.action.effects[index]._id;
|
effectId = this.action.effects[index]._id;
|
||||||
this.constructor.removeElement.bind(this)(event, button);
|
this.constructor.removeElement.call(this, event, button);
|
||||||
|
}
|
||||||
|
|
||||||
this.action.item.deleteEmbeddedDocuments('ActiveEffect', [effectId]);
|
this.action.item.deleteEmbeddedDocuments('ActiveEffect', [effectId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,21 +31,35 @@ export default class DHActionSettingsConfig extends DHActionBaseConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
static async addEffect(_event) {
|
static async addEffect(_event) {
|
||||||
|
const { areaIndex } = event.target.dataset;
|
||||||
if (!this.action.effects) return;
|
if (!this.action.effects) return;
|
||||||
const effectData = game.system.api.data.activeEffects.BaseEffect.getDefaultObject();
|
|
||||||
|
const effectData = game.system.api.data.activeEffects.BaseEffect.getDefaultObject({ transfer: false });
|
||||||
const data = this.action.toObject();
|
const data = this.action.toObject();
|
||||||
|
|
||||||
this.sheetUpdate(data, effectData);
|
this.sheetUpdate(data, effectData);
|
||||||
this.effects = [...this.effects, effectData];
|
this.effects = [...this.effects, effectData];
|
||||||
data.effects.push({ _id: effectData.id });
|
|
||||||
|
if (areaIndex !== undefined) data.areas[areaIndex].effects.push(effectData.id);
|
||||||
|
else data.effects.push({ _id: effectData.id });
|
||||||
|
|
||||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
}
|
}
|
||||||
|
|
||||||
static removeEffect(event, button) {
|
static removeEffect(event, button) {
|
||||||
if (!this.action.effects) return;
|
if (!this.action.effects) return;
|
||||||
const index = button.dataset.index,
|
const { areaIndex, index } = button.dataset;
|
||||||
|
let effectId = null;
|
||||||
|
if (areaIndex !== undefined) {
|
||||||
|
effectId = this.action.areas[areaIndex].effects[index];
|
||||||
|
const data = this.action.toObject();
|
||||||
|
data.areas[areaIndex].effects.splice(index, 1);
|
||||||
|
this.constructor.updateForm.call(this, null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
|
} else {
|
||||||
effectId = this.action.effects[index]._id;
|
effectId = this.action.effects[index]._id;
|
||||||
this.constructor.removeElement.bind(this)(event, button);
|
this.constructor.removeElement.call(this, event, button);
|
||||||
|
}
|
||||||
|
|
||||||
this.sheetUpdate(
|
this.sheetUpdate(
|
||||||
this.action.toObject(),
|
this.action.toObject(),
|
||||||
this.effects.find(x => x.id === effectId),
|
this.effects.find(x => x.id === effectId),
|
||||||
|
|
@ -55,7 +69,7 @@ export default class DHActionSettingsConfig extends DHActionBaseConfig {
|
||||||
|
|
||||||
static async editEffect(event) {
|
static async editEffect(event) {
|
||||||
const id = event.target.closest('[data-effect-id]')?.dataset?.effectId;
|
const id = event.target.closest('[data-effect-id]')?.dataset?.effectId;
|
||||||
const updatedEffect = await game.system.api.applications.sheetConfigs.SettingActiveEffectConfig.configure(
|
const updatedEffect = await game.system.api.applications.sheetConfigs.ActiveEffectConfig.configureSetting(
|
||||||
this.getEffectDetails(id)
|
this.getEffectDetails(id)
|
||||||
);
|
);
|
||||||
if (!updatedEffect) return;
|
if (!updatedEffect) return;
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
|
||||||
settings: { template: 'systems/daggerheart/templates/sheets/activeEffect/settings.hbs' },
|
settings: { template: 'systems/daggerheart/templates/sheets/activeEffect/settings.hbs' },
|
||||||
changes: {
|
changes: {
|
||||||
template: 'systems/daggerheart/templates/sheets/activeEffect/changes.hbs',
|
template: 'systems/daggerheart/templates/sheets/activeEffect/changes.hbs',
|
||||||
|
templates: ['systems/daggerheart/templates/sheets/activeEffect/change.hbs'],
|
||||||
scrollable: ['ol[data-changes]']
|
scrollable: ['ol[data-changes]']
|
||||||
},
|
},
|
||||||
footer: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-form-footer.hbs' }
|
footer: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-form-footer.hbs' }
|
||||||
|
|
@ -40,7 +41,7 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
|
||||||
* @returns {ChangeChoice { value: string, label: string, hint: string, group: string }[]}
|
* @returns {ChangeChoice { value: string, label: string, hint: string, group: string }[]}
|
||||||
*/
|
*/
|
||||||
static getChangeChoices() {
|
static getChangeChoices() {
|
||||||
const ignoredActorKeys = ['config', 'DhEnvironment', 'DhParty'];
|
const ignoredActorKeys = ['config', 'DhEnvironment', 'DhParty', 'DhNPC'];
|
||||||
|
|
||||||
const getAllLeaves = (root, group, parentPath = '') => {
|
const getAllLeaves = (root, group, parentPath = '') => {
|
||||||
const leaves = [];
|
const leaves = [];
|
||||||
|
|
@ -149,6 +150,18 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
|
||||||
minLength: 0
|
minLength: 0
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
htmlElement
|
||||||
|
.querySelector('.stacking-change-checkbox')
|
||||||
|
?.addEventListener('change', this.stackingChangeToggle.bind(this));
|
||||||
|
|
||||||
|
htmlElement
|
||||||
|
.querySelector('.armor-change-checkbox')
|
||||||
|
?.addEventListener('change', this.armorChangeToggle.bind(this));
|
||||||
|
|
||||||
|
htmlElement
|
||||||
|
.querySelector('.armor-damage-thresholds-checkbox')
|
||||||
|
?.addEventListener('change', this.armorDamageThresholdToggle.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
async _prepareContext(options) {
|
async _prepareContext(options) {
|
||||||
|
|
@ -162,6 +175,7 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
|
||||||
const partContext = await super._preparePartContext(partId, context);
|
const partContext = await super._preparePartContext(partId, context);
|
||||||
switch (partId) {
|
switch (partId) {
|
||||||
case 'details':
|
case 'details':
|
||||||
|
partContext.isItemEffect = partContext.isItemEffect || this.options.isSetting;
|
||||||
const useGeneric = game.settings.get(
|
const useGeneric = game.settings.get(
|
||||||
CONFIG.DH.id,
|
CONFIG.DH.id,
|
||||||
CONFIG.DH.SETTINGS.gameSettings.appearance
|
CONFIG.DH.SETTINGS.gameSettings.appearance
|
||||||
|
|
@ -173,8 +187,166 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'settings':
|
||||||
|
const groups = {
|
||||||
|
time: _loc('EFFECT.DURATION.UNITS.GROUPS.time'),
|
||||||
|
combat: _loc('EFFECT.DURATION.UNITS.GROUPS.combat')
|
||||||
|
};
|
||||||
|
partContext.durationUnits = CONST.ACTIVE_EFFECT_DURATION_UNITS.map(value => ({
|
||||||
|
value,
|
||||||
|
label: _loc(`EFFECT.DURATION.UNITS.${value}`),
|
||||||
|
group: CONST.ACTIVE_EFFECT_TIME_DURATION_UNITS.includes(value) ? groups.time : groups.combat
|
||||||
|
}));
|
||||||
|
break;
|
||||||
|
case 'changes':
|
||||||
|
const singleTypes = ['armor'];
|
||||||
|
const typedChanges = context.source.changes.reduce((acc, change, index) => {
|
||||||
|
if (singleTypes.includes(change.type)) {
|
||||||
|
acc[change.type] = { ...change, index };
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
partContext.changes = partContext.changes.filter(c => !!c);
|
||||||
|
partContext.typedChanges = typedChanges;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return partContext;
|
return partContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stackingChangeToggle(event) {
|
||||||
|
const stackingFields = this.document.system.schema.fields.stacking.fields;
|
||||||
|
const systemData = {
|
||||||
|
stacking: event.target.checked
|
||||||
|
? { value: stackingFields.value.initial, max: stackingFields.max.initial }
|
||||||
|
: null
|
||||||
|
};
|
||||||
|
return this.submit({ updateData: { system: systemData } });
|
||||||
|
}
|
||||||
|
|
||||||
|
armorChangeToggle(event) {
|
||||||
|
if (event.target.checked) {
|
||||||
|
this.addArmorChange();
|
||||||
|
} else {
|
||||||
|
this.removeTypedChange(event.target.dataset.index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Could be generalised if needed later */
|
||||||
|
addArmorChange() {
|
||||||
|
const submitData = this._processFormData(null, this.form, new FormDataExtended(this.form));
|
||||||
|
const changes = Object.values(submitData.system?.changes ?? {});
|
||||||
|
changes.push(game.system.api.data.activeEffects.changeTypes.armor.getInitialValue());
|
||||||
|
return this.submit({ updateData: { system: { changes } } });
|
||||||
|
}
|
||||||
|
|
||||||
|
removeTypedChange(indexString) {
|
||||||
|
const submitData = this._processFormData(null, this.form, new FormDataExtended(this.form));
|
||||||
|
const changes = Object.values(submitData.system.changes);
|
||||||
|
const index = Number(indexString);
|
||||||
|
changes.splice(index, 1);
|
||||||
|
return this.submit({ updateData: { system: { changes } } });
|
||||||
|
}
|
||||||
|
|
||||||
|
armorDamageThresholdToggle(event) {
|
||||||
|
const submitData = this._processFormData(null, this.form, new FormDataExtended(this.form));
|
||||||
|
const changes = Object.values(submitData.system?.changes ?? {});
|
||||||
|
const index = Number(event.target.dataset.index);
|
||||||
|
if (event.target.checked) {
|
||||||
|
changes[index].value.damageThresholds = { major: 0, severe: 0 };
|
||||||
|
} else {
|
||||||
|
changes[index].value.damageThresholds = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.submit({ updateData: { system: { changes } } });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
_renderChange(context) {
|
||||||
|
const { change, index, defaultPriority } = context;
|
||||||
|
if (!(change.type in CONFIG.DH.GENERAL.baseActiveEffectModes)) return null;
|
||||||
|
|
||||||
|
const changeTypesSchema = this.document.system.schema.fields.changes.element.types;
|
||||||
|
const fields = context.fields ?? (changeTypesSchema[change.type] ?? changeTypesSchema.add).fields;
|
||||||
|
if (typeof change.value !== 'string') change.value = JSON.stringify(change.value);
|
||||||
|
Object.assign(
|
||||||
|
change,
|
||||||
|
['key', 'type', 'value', 'priority'].reduce((paths, fieldName) => {
|
||||||
|
paths[`${fieldName}Path`] = `system.changes.${index}.${fieldName}`;
|
||||||
|
return paths;
|
||||||
|
}, {})
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
game.system.api.documents.DhActiveEffect.CHANGE_TYPES[change.type].render?.(
|
||||||
|
change,
|
||||||
|
index,
|
||||||
|
defaultPriority
|
||||||
|
) ??
|
||||||
|
foundry.applications.handlebars.renderTemplate(
|
||||||
|
'systems/daggerheart/templates/sheets/activeEffect/change.hbs',
|
||||||
|
{
|
||||||
|
change,
|
||||||
|
index,
|
||||||
|
defaultPriority,
|
||||||
|
fields,
|
||||||
|
types: Object.keys(CONFIG.DH.GENERAL.baseActiveEffectModes).reduce((r, key) => {
|
||||||
|
r[key] = CONFIG.DH.GENERAL.baseActiveEffectModes[key].label;
|
||||||
|
return r;
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
_onChangeForm(_formConfig, event) {
|
||||||
|
if (foundry.utils.isElementInstanceOf(event.target, 'select') && event.target.name === 'system.duration.type') {
|
||||||
|
const durationSection = this.element.querySelector('.custom-duration-section');
|
||||||
|
if (event.target.value === 'custom') durationSection.classList.add('visible');
|
||||||
|
else durationSection.classList.remove('visible');
|
||||||
|
|
||||||
|
const durationDescription = this.element.querySelector('.duration-description');
|
||||||
|
if (event.target.value === 'temporary') durationDescription.classList.add('visible');
|
||||||
|
else durationDescription.classList.remove('visible');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
_processFormData(event, form, formData) {
|
||||||
|
const submitData = super._processFormData(event, form, formData);
|
||||||
|
if (submitData.start && !submitData.start.time) submitData.start.time = '0';
|
||||||
|
else if (!submitData) submitData.start = null;
|
||||||
|
|
||||||
|
return submitData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
_processSubmitData(event, form, submitData, options) {
|
||||||
|
if (this.options.isSetting) {
|
||||||
|
// Settings should update source instead
|
||||||
|
this.document.updateSource(submitData);
|
||||||
|
this.render();
|
||||||
|
} else {
|
||||||
|
return super._processSubmitData(event, form, submitData, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Creates an active effect config for a setting */
|
||||||
|
static async configureSetting(effect, options = {}) {
|
||||||
|
const document = new CONFIG.ActiveEffect.documentClass({ ...foundry.utils.duplicate(effect), _id: effect.id });
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const app = new this({ document, ...options, isSetting: true });
|
||||||
|
app.addEventListener(
|
||||||
|
'close',
|
||||||
|
() => {
|
||||||
|
const newEffect = app.document.toObject(true);
|
||||||
|
newEffect.id = newEffect._id;
|
||||||
|
delete newEffect._id;
|
||||||
|
resolve(newEffect);
|
||||||
|
},
|
||||||
|
{ once: true }
|
||||||
|
);
|
||||||
|
app.render({ force: true });
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@ export default class DHAdversarySettings extends DHBaseActorSettings {
|
||||||
});
|
});
|
||||||
if (!confirmed) return;
|
if (!confirmed) return;
|
||||||
|
|
||||||
await this.actor.update({ [`system.experiences.-=${target.dataset.experience}`]: null });
|
await this.actor.update({ [`system.experiences.${target.dataset.experience}`]: _del });
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onDragStart(event) {
|
async _onDragStart(event) {
|
||||||
|
|
@ -110,6 +110,7 @@ export default class DHAdversarySettings extends DHBaseActorSettings {
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onDrop(event) {
|
async _onDrop(event) {
|
||||||
|
event.stopPropagation();
|
||||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||||
|
|
||||||
const item = await fromUuid(data.uuid);
|
const item = await fromUuid(data.uuid);
|
||||||
|
|
|
||||||
|
|
@ -101,8 +101,8 @@ export default class DHCharacterSettings extends DHBaseActorSettings {
|
||||||
|
|
||||||
if (relinkAchievementData.length > 0) {
|
if (relinkAchievementData.length > 0) {
|
||||||
relinkAchievementData.forEach(data => {
|
relinkAchievementData.forEach(data => {
|
||||||
updates[`system.levelData.levelups.${data.levelKey}.achievements.experiences.-=${data.experience}`] =
|
updates[`system.levelData.levelups.${data.levelKey}.achievements.experiences.${data.experience}`] =
|
||||||
null;
|
_del;
|
||||||
});
|
});
|
||||||
} else if (relinkSelectionData.length > 0) {
|
} else if (relinkSelectionData.length > 0) {
|
||||||
relinkSelectionData.forEach(data => {
|
relinkSelectionData.forEach(data => {
|
||||||
|
|
@ -137,7 +137,7 @@ export default class DHCharacterSettings extends DHBaseActorSettings {
|
||||||
|
|
||||||
await this.actor.update({
|
await this.actor.update({
|
||||||
...updates,
|
...updates,
|
||||||
[`system.experiences.-=${target.dataset.experience}`]: null
|
[`system.experiences.${target.dataset.experience}`]: _del
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,6 @@ export default class DHCompanionSettings extends DHBaseActorSettings {
|
||||||
});
|
});
|
||||||
if (!confirmed) return;
|
if (!confirmed) return;
|
||||||
|
|
||||||
await this.actor.update({ [`system.experiences.-=${target.dataset.experience}`]: null });
|
await this.actor.update({ [`system.experiences.${target.dataset.experience}`]: _del });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,9 +68,9 @@ export default class DHEnvironmentSettings extends DHBaseActorSettings {
|
||||||
*/
|
*/
|
||||||
static async #addCategory() {
|
static async #addCategory() {
|
||||||
await this.actor.update({
|
await this.actor.update({
|
||||||
[`system.potentialAdversaries.${foundry.utils.randomID()}.label`]: game.i18n.localize(
|
[`system.potentialAdversaries.${foundry.utils.randomID()}`]: {
|
||||||
'DAGGERHEART.ACTORS.Environment.newAdversary'
|
label: game.i18n.localize('DAGGERHEART.ACTORS.Environment.newAdversary')
|
||||||
)
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,7 +79,7 @@ export default class DHEnvironmentSettings extends DHBaseActorSettings {
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
*/
|
*/
|
||||||
static async #removeCategory(_, target) {
|
static async #removeCategory(_, target) {
|
||||||
await this.actor.update({ [`system.potentialAdversaries.-=${target.dataset.categoryId}`]: null });
|
await this.actor.update({ [`system.potentialAdversaries.${target.dataset.categoryId}`]: _del });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -121,6 +121,7 @@ export default class DHEnvironmentSettings extends DHBaseActorSettings {
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onDrop(event) {
|
async _onDrop(event) {
|
||||||
|
event.stopPropagation();
|
||||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||||
const item = await fromUuid(data.uuid);
|
const item = await fromUuid(data.uuid);
|
||||||
if (data.fromInternal && item?.parent?.uuid === this.actor.uuid) return;
|
if (data.fromInternal && item?.parent?.uuid === this.actor.uuid) return;
|
||||||
|
|
|
||||||
85
module/applications/sheets-configs/npc-settings.mjs
Normal file
85
module/applications/sheets-configs/npc-settings.mjs
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
import DHBaseActorSettings from '../sheets/api/actor-setting.mjs';
|
||||||
|
|
||||||
|
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
|
||||||
|
|
||||||
|
export default class DHNPCSettings extends DHBaseActorSettings {
|
||||||
|
/**@inheritdoc */
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
classes: ['npc-settings'],
|
||||||
|
position: { width: 455, height: 'auto' },
|
||||||
|
actions: {},
|
||||||
|
dragDrop: [
|
||||||
|
{ dragSelector: null, dropSelector: '.tab.features' },
|
||||||
|
{ dragSelector: '.feature-item', dropSelector: null }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
/**@override */
|
||||||
|
static PARTS = {
|
||||||
|
header: {
|
||||||
|
id: 'header',
|
||||||
|
template: 'systems/daggerheart/templates/sheets-settings/npc-settings/header.hbs'
|
||||||
|
},
|
||||||
|
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
|
||||||
|
details: {
|
||||||
|
id: 'details',
|
||||||
|
template: 'systems/daggerheart/templates/sheets-settings/npc-settings/details.hbs'
|
||||||
|
},
|
||||||
|
features: {
|
||||||
|
id: 'features',
|
||||||
|
template: 'systems/daggerheart/templates/sheets-settings/npc-settings/features.hbs'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static TABS = {
|
||||||
|
primary: {
|
||||||
|
tabs: [{ id: 'details' }, { id: 'features' }],
|
||||||
|
initial: 'details',
|
||||||
|
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async _prepareContext(options) {
|
||||||
|
const context = await super._prepareContext(options);
|
||||||
|
|
||||||
|
const featureForms = ['passive', 'action', 'reaction'];
|
||||||
|
context.features = context.document.system.features.sort((a, b) =>
|
||||||
|
a.system.featureForm !== b.system.featureForm
|
||||||
|
? featureForms.indexOf(a.system.featureForm) - featureForms.indexOf(b.system.featureForm)
|
||||||
|
: a.sort - b.sort
|
||||||
|
);
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
async _onDragStart(event) {
|
||||||
|
const featureItem = event.currentTarget.closest('.feature-item');
|
||||||
|
|
||||||
|
if (featureItem) {
|
||||||
|
const feature = this.actor.items.get(featureItem.id);
|
||||||
|
const featureData = { type: 'Item', uuid: feature.uuid, fromInternal: true };
|
||||||
|
event.dataTransfer.setData('text/plain', JSON.stringify(featureData));
|
||||||
|
event.dataTransfer.setDragImage(featureItem.querySelector('img'), 60, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async _onDrop(event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||||
|
|
||||||
|
const item = await fromUuid(data.uuid);
|
||||||
|
if (item?.type === 'feature') {
|
||||||
|
if (data.fromInternal && item.parent?.uuid === this.actor.uuid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const itemData = item.toObject();
|
||||||
|
delete itemData._id;
|
||||||
|
|
||||||
|
await this.actor.createEmbeddedDocuments('Item', [itemData]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,223 +0,0 @@
|
||||||
import autocomplete from 'autocompleter';
|
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
|
||||||
|
|
||||||
export default class SettingActiveEffectConfig extends HandlebarsApplicationMixin(ApplicationV2) {
|
|
||||||
constructor(effect) {
|
|
||||||
super({});
|
|
||||||
|
|
||||||
this.effect = foundry.utils.deepClone(effect);
|
|
||||||
this.changeChoices = game.system.api.applications.sheetConfigs.ActiveEffectConfig.getChangeChoices();
|
|
||||||
}
|
|
||||||
|
|
||||||
static DEFAULT_OPTIONS = {
|
|
||||||
classes: ['daggerheart', 'sheet', 'dh-style', 'active-effect-config', 'standard-form'],
|
|
||||||
tag: 'form',
|
|
||||||
position: {
|
|
||||||
width: 560
|
|
||||||
},
|
|
||||||
form: {
|
|
||||||
submitOnChange: false,
|
|
||||||
closeOnSubmit: false,
|
|
||||||
handler: SettingActiveEffectConfig.#onSubmit
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
editImage: SettingActiveEffectConfig.#editImage,
|
|
||||||
addChange: SettingActiveEffectConfig.#addChange,
|
|
||||||
deleteChange: SettingActiveEffectConfig.#deleteChange
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static PARTS = {
|
|
||||||
header: { template: 'systems/daggerheart/templates/sheets/activeEffect/header.hbs' },
|
|
||||||
tabs: { template: 'templates/generic/tab-navigation.hbs' },
|
|
||||||
details: { template: 'systems/daggerheart/templates/sheets/activeEffect/details.hbs', scrollable: [''] },
|
|
||||||
settings: { template: 'systems/daggerheart/templates/sheets/activeEffect/settings.hbs' },
|
|
||||||
changes: {
|
|
||||||
template: 'systems/daggerheart/templates/sheets/activeEffect/changes.hbs',
|
|
||||||
scrollable: ['ol[data-changes]']
|
|
||||||
},
|
|
||||||
footer: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-form-footer.hbs' }
|
|
||||||
};
|
|
||||||
|
|
||||||
static TABS = {
|
|
||||||
sheet: {
|
|
||||||
tabs: [
|
|
||||||
{ id: 'details', icon: 'fa-solid fa-book' },
|
|
||||||
{ id: 'settings', icon: 'fa-solid fa-bars', label: 'DAGGERHEART.GENERAL.Tabs.settings' },
|
|
||||||
{ id: 'changes', icon: 'fa-solid fa-gears' }
|
|
||||||
],
|
|
||||||
initial: 'details',
|
|
||||||
labelPrefix: 'EFFECT.TABS'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**@inheritdoc */
|
|
||||||
async _onFirstRender(context, options) {
|
|
||||||
await super._onFirstRender(context, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
async _prepareContext(_options) {
|
|
||||||
const context = await super._prepareContext(_options);
|
|
||||||
context.source = this.effect;
|
|
||||||
context.fields = game.system.api.documents.DhActiveEffect.schema.fields;
|
|
||||||
context.systemFields = game.system.api.data.activeEffects.BaseEffect._schema.fields;
|
|
||||||
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
_attachPartListeners(partId, htmlElement, options) {
|
|
||||||
super._attachPartListeners(partId, htmlElement, options);
|
|
||||||
const changeChoices = this.changeChoices;
|
|
||||||
|
|
||||||
htmlElement.querySelectorAll('.effect-change-input').forEach(element => {
|
|
||||||
autocomplete({
|
|
||||||
input: element,
|
|
||||||
fetch: function (text, update) {
|
|
||||||
if (!text) {
|
|
||||||
update(changeChoices);
|
|
||||||
} else {
|
|
||||||
text = text.toLowerCase();
|
|
||||||
var suggestions = changeChoices.filter(n => n.label.toLowerCase().includes(text));
|
|
||||||
update(suggestions);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
render: function (item, search) {
|
|
||||||
const label = game.i18n.localize(item.label);
|
|
||||||
const matchIndex = label.toLowerCase().indexOf(search);
|
|
||||||
|
|
||||||
const beforeText = label.slice(0, matchIndex);
|
|
||||||
const matchText = label.slice(matchIndex, matchIndex + search.length);
|
|
||||||
const after = label.slice(matchIndex + search.length, label.length);
|
|
||||||
|
|
||||||
const element = document.createElement('li');
|
|
||||||
element.innerHTML =
|
|
||||||
`${beforeText}${matchText ? `<strong>${matchText}</strong>` : ''}${after}`.replaceAll(
|
|
||||||
' ',
|
|
||||||
' '
|
|
||||||
);
|
|
||||||
if (item.hint) {
|
|
||||||
element.dataset.tooltip = game.i18n.localize(item.hint);
|
|
||||||
}
|
|
||||||
|
|
||||||
return element;
|
|
||||||
},
|
|
||||||
renderGroup: function (label) {
|
|
||||||
const itemElement = document.createElement('div');
|
|
||||||
itemElement.textContent = game.i18n.localize(label);
|
|
||||||
return itemElement;
|
|
||||||
},
|
|
||||||
onSelect: function (item) {
|
|
||||||
element.value = `system.${item.value}`;
|
|
||||||
},
|
|
||||||
click: e => e.fetch(),
|
|
||||||
customize: function (_input, _inputRect, container) {
|
|
||||||
container.style.zIndex = foundry.applications.api.ApplicationV2._maxZ;
|
|
||||||
},
|
|
||||||
minLength: 0
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async _preparePartContext(partId, context) {
|
|
||||||
if (partId in context.tabs) context.tab = context.tabs[partId];
|
|
||||||
switch (partId) {
|
|
||||||
case 'details':
|
|
||||||
context.statuses = CONFIG.statusEffects.map(s => ({ value: s.id, label: game.i18n.localize(s.name) }));
|
|
||||||
context.isActorEffect = false;
|
|
||||||
context.isItemEffect = true;
|
|
||||||
const useGeneric = game.settings.get(
|
|
||||||
CONFIG.DH.id,
|
|
||||||
CONFIG.DH.SETTINGS.gameSettings.appearance
|
|
||||||
).showGenericStatusEffects;
|
|
||||||
if (!useGeneric) {
|
|
||||||
context.statuses = [
|
|
||||||
...context.statuses,
|
|
||||||
Object.values(CONFIG.DH.GENERAL.conditions).map(status => ({
|
|
||||||
value: status.id,
|
|
||||||
label: game.i18n.localize(status.name)
|
|
||||||
}))
|
|
||||||
];
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'changes':
|
|
||||||
context.modes = Object.entries(CONST.ACTIVE_EFFECT_MODES).reduce((modes, [key, value]) => {
|
|
||||||
modes[value] = game.i18n.localize(`EFFECT.MODE_${key}`);
|
|
||||||
return modes;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
context.priorities = ActiveEffectConfig.DEFAULT_PRIORITIES;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
static async #onSubmit(_event, _form, formData) {
|
|
||||||
this.data = foundry.utils.expandObject(formData.object);
|
|
||||||
this.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Edit a Document image.
|
|
||||||
* @this {DocumentSheetV2}
|
|
||||||
* @type {ApplicationClickAction}
|
|
||||||
*/
|
|
||||||
static async #editImage(_event, target) {
|
|
||||||
if (target.nodeName !== 'IMG') {
|
|
||||||
throw new Error('The editImage action is available only for IMG elements.');
|
|
||||||
}
|
|
||||||
|
|
||||||
const attr = target.dataset.edit;
|
|
||||||
const current = foundry.utils.getProperty(this.effect, attr);
|
|
||||||
const fp = new FilePicker.implementation({
|
|
||||||
current,
|
|
||||||
type: 'image',
|
|
||||||
callback: path => (target.src = path),
|
|
||||||
position: {
|
|
||||||
top: this.position.top + 40,
|
|
||||||
left: this.position.left + 10
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await fp.browse();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a new change to the effect's changes array.
|
|
||||||
* @this {ActiveEffectConfig}
|
|
||||||
* @type {ApplicationClickAction}
|
|
||||||
*/
|
|
||||||
static async #addChange() {
|
|
||||||
const { changes, ...rest } = foundry.utils.expandObject(new FormDataExtended(this.form).object);
|
|
||||||
const updatedChanges = Object.values(changes ?? {});
|
|
||||||
updatedChanges.push({});
|
|
||||||
|
|
||||||
this.effect = { ...rest, changes: updatedChanges };
|
|
||||||
this.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a change from the effect's changes array.
|
|
||||||
* @this {ActiveEffectConfig}
|
|
||||||
* @type {ApplicationClickAction}
|
|
||||||
*/
|
|
||||||
static async #deleteChange(event) {
|
|
||||||
const submitData = foundry.utils.expandObject(new FormDataExtended(this.form).object);
|
|
||||||
const updatedChanges = Object.values(submitData.changes);
|
|
||||||
const row = event.target.closest('li');
|
|
||||||
const index = Number(row.dataset.index) || 0;
|
|
||||||
updatedChanges.splice(index, 1);
|
|
||||||
|
|
||||||
this.effect = { ...submitData, changes: updatedChanges };
|
|
||||||
this.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
static async configure(effect, options = {}) {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
const app = new this(effect, options);
|
|
||||||
app.addEventListener('close', () => resolve(app.data), { once: true });
|
|
||||||
app.render({ force: true });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -147,7 +147,7 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App
|
||||||
const effectIndex = this.move.effects.findIndex(x => x.id === id);
|
const effectIndex = this.move.effects.findIndex(x => x.id === id);
|
||||||
const effect = this.move.effects[effectIndex];
|
const effect = this.move.effects[effectIndex];
|
||||||
const updatedEffect =
|
const updatedEffect =
|
||||||
await game.system.api.applications.sheetConfigs.SettingActiveEffectConfig.configure(effect);
|
await game.system.api.applications.sheetConfigs.ActiveEffectConfig.configureSetting(effect);
|
||||||
if (!updatedEffect) return;
|
if (!updatedEffect) return;
|
||||||
|
|
||||||
await this.updateMove({
|
await this.updateMove({
|
||||||
|
|
@ -188,8 +188,9 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App
|
||||||
if (type === 'effect') {
|
if (type === 'effect') {
|
||||||
const move = foundry.utils.getProperty(this.settings, this.movePath);
|
const move = foundry.utils.getProperty(this.settings, this.movePath);
|
||||||
for (const action of move.actions) {
|
for (const action of move.actions) {
|
||||||
const remainingEffects = action.effects.filter(x => x._id !== id);
|
const actionEffects = action.effects ?? [];
|
||||||
if (action.effects.length !== remainingEffects.length) {
|
const remainingEffects = actionEffects.filter(x => x._id !== id);
|
||||||
|
if (actionEffects.length !== remainingEffects.length) {
|
||||||
await action.update({
|
await action.update({
|
||||||
effects: remainingEffects.map(x => {
|
effects: remainingEffects.map(x => {
|
||||||
const { _id, ...rest } = x;
|
const { _id, ...rest } = x;
|
||||||
|
|
@ -205,7 +206,7 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await this.updateMove({ [`${this.actionsPath}.-=${target.dataset.id}`]: null });
|
await this.updateMove({ [`${this.actionsPath}.${target.dataset.id}`]: _del });
|
||||||
}
|
}
|
||||||
|
|
||||||
this.render();
|
this.render();
|
||||||
|
|
|
||||||
|
|
@ -67,9 +67,9 @@ export default function DHTokenConfigMixin(Base) {
|
||||||
changes.height = tokenSize;
|
changes.height = tokenSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
const deletions = { '-=actorId': null, '-=actorLink': null };
|
// const deletions = { actorId: _del };
|
||||||
const mergeOptions = { inplace: false, performDeletions: true };
|
// const mergeOptions = { inplace: false, performDeletions: true, actorLink: false };
|
||||||
this._preview.updateSource(mergeObject(changes, deletions, mergeOptions));
|
this._preview.updateSource(changes);
|
||||||
|
|
||||||
if (this._preview?.object?.destroyed === false) {
|
if (this._preview?.object?.destroyed === false) {
|
||||||
this._preview.object.initializeSources();
|
this._preview.object.initializeSources();
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,5 @@ export { default as Adversary } from './adversary.mjs';
|
||||||
export { default as Character } from './character.mjs';
|
export { default as Character } from './character.mjs';
|
||||||
export { default as Companion } from './companion.mjs';
|
export { default as Companion } from './companion.mjs';
|
||||||
export { default as Environment } from './environment.mjs';
|
export { default as Environment } from './environment.mjs';
|
||||||
|
export { default as NPC } from './npc.mjs';
|
||||||
export { default as Party } from './party.mjs';
|
export { default as Party } from './party.mjs';
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,16 @@ export default class AdversarySheet extends DHBaseActorSheet {
|
||||||
dragSelector: '[data-item-id][draggable="true"], [data-item-id] [draggable="true"]',
|
dragSelector: '[data-item-id][draggable="true"], [data-item-id] [draggable="true"]',
|
||||||
dropSelector: null
|
dropSelector: null
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
contextMenus: [
|
||||||
|
{
|
||||||
|
handler: DHBaseActorSheet.getBaseAttackContextOptions,
|
||||||
|
selector: '[data-item-uuid][data-type="attack"]',
|
||||||
|
options: {
|
||||||
|
parentClassHooks: false,
|
||||||
|
fixed: true
|
||||||
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -217,8 +227,8 @@ export default class AdversarySheet extends DHBaseActorSheet {
|
||||||
static #reactionRoll(event) {
|
static #reactionRoll(event) {
|
||||||
const config = {
|
const config = {
|
||||||
event,
|
event,
|
||||||
title: `Reaction Roll: ${this.actor.name}`,
|
title: game.i18n.localize('DAGGERHEART.GENERAL.reactionRoll'),
|
||||||
headerTitle: 'Adversary Reaction Roll',
|
headerTitle: game.i18n.localize('DAGGERHEART.ACTORS.Adversary.adversaryReactionRoll.headerTitle'),
|
||||||
roll: {
|
roll: {
|
||||||
type: 'trait'
|
type: 'trait'
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
import DHBaseActorSheet from '../api/base-actor.mjs';
|
import DHBaseActorSheet from '../api/base-actor.mjs';
|
||||||
import DhDeathMove from '../../dialogs/deathMove.mjs';
|
import DhDeathMove from '../../dialogs/deathMove.mjs';
|
||||||
import { abilities } from '../../../config/actorConfig.mjs';
|
|
||||||
import { CharacterLevelup, LevelupViewMode } from '../../levelup/_module.mjs';
|
import { CharacterLevelup, LevelupViewMode } from '../../levelup/_module.mjs';
|
||||||
import DhCharacterCreation from '../../characterCreation/characterCreation.mjs';
|
import DhCharacterCreation from '../../characterCreation/characterCreation.mjs';
|
||||||
import FilterMenu from '../../ux/filter-menu.mjs';
|
import FilterMenu from '../../ux/filter-menu.mjs';
|
||||||
import { getDocFromElement, getDocFromElementSync } from '../../../helpers/utils.mjs';
|
import { getArmorSources, getDocFromElement, getDocFromElementSync, sortBy } from '../../../helpers/utils.mjs';
|
||||||
|
|
||||||
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
|
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
|
||||||
|
|
||||||
|
|
@ -13,8 +12,6 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
classes: ['character'],
|
classes: ['character'],
|
||||||
position: { width: 850, height: 800 },
|
position: { width: 850, height: 800 },
|
||||||
/* Foundry adds disabled to all buttons and inputs if editPermission is missing. This is not desired. */
|
|
||||||
editPermission: CONST.DOCUMENT_OWNERSHIP_LEVELS.OBSERVER,
|
|
||||||
actions: {
|
actions: {
|
||||||
toggleVault: CharacterSheet.#toggleVault,
|
toggleVault: CharacterSheet.#toggleVault,
|
||||||
rollAttribute: CharacterSheet.#rollAttribute,
|
rollAttribute: CharacterSheet.#rollAttribute,
|
||||||
|
|
@ -35,7 +32,8 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
cancelBeastform: CharacterSheet.#cancelBeastform,
|
cancelBeastform: CharacterSheet.#cancelBeastform,
|
||||||
toggleResourceManagement: CharacterSheet.#toggleResourceManagement,
|
toggleResourceManagement: CharacterSheet.#toggleResourceManagement,
|
||||||
useDowntime: this.useDowntime,
|
useDowntime: this.useDowntime,
|
||||||
viewParty: CharacterSheet.#viewParty
|
viewParty: CharacterSheet.#viewParty,
|
||||||
|
toggleArmorMangement: CharacterSheet.#toggleArmorManagement
|
||||||
},
|
},
|
||||||
window: {
|
window: {
|
||||||
resizable: true,
|
resizable: true,
|
||||||
|
|
@ -59,6 +57,22 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
contextMenus: [
|
contextMenus: [
|
||||||
|
{
|
||||||
|
handler: CharacterSheet.#getCreationMainContextOptions,
|
||||||
|
selector: '.character-details [data-action="editDoc"]',
|
||||||
|
options: {
|
||||||
|
parentClassHooks: false,
|
||||||
|
fixed: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
handler: DHBaseActorSheet.getBaseAttackContextOptions,
|
||||||
|
selector: '[data-item-uuid][data-type="attack"]',
|
||||||
|
options: {
|
||||||
|
parentClassHooks: false,
|
||||||
|
fixed: true
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
handler: CharacterSheet.#getDomainCardContextOptions,
|
handler: CharacterSheet.#getDomainCardContextOptions,
|
||||||
selector: '[data-item-uuid][data-type="domainCard"]',
|
selector: '[data-item-uuid][data-type="domainCard"]',
|
||||||
|
|
@ -68,7 +82,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
handler: CharacterSheet.#getEquipamentContextOptions,
|
handler: CharacterSheet.#getEquipmentContextOptions,
|
||||||
selector: '[data-item-uuid][data-type="armor"], [data-item-uuid][data-type="weapon"]',
|
selector: '[data-item-uuid][data-type="armor"], [data-item-uuid][data-type="weapon"]',
|
||||||
options: {
|
options: {
|
||||||
parentClassHooks: false,
|
parentClassHooks: false,
|
||||||
|
|
@ -170,6 +184,19 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
return applicationOptions;
|
return applicationOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
_toggleDisabled(disabled) {
|
||||||
|
// Overriden to only disable text inputs by default.
|
||||||
|
// Everything else is done by checking @root.editable in the sheet
|
||||||
|
const form = this.form;
|
||||||
|
for (const input of form.querySelectorAll('input:not([type=search]), .editor.prosemirror')) {
|
||||||
|
input.disabled = disabled;
|
||||||
|
}
|
||||||
|
for (const element of form.querySelectorAll('.input[contenteditable]')) {
|
||||||
|
element.classList.toggle('disabled', disabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
async _onRender(context, options) {
|
async _onRender(context, options) {
|
||||||
await super._onRender(context, options);
|
await super._onRender(context, options);
|
||||||
|
|
@ -201,8 +228,9 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
context.attributes = Object.keys(this.document.system.traits).reduce((acc, key) => {
|
context.attributes = Object.keys(this.document.system.traits).reduce((acc, key) => {
|
||||||
acc[key] = {
|
acc[key] = {
|
||||||
...this.document.system.traits[key],
|
...this.document.system.traits[key],
|
||||||
name: game.i18n.localize(CONFIG.DH.ACTOR.abilities[key].name),
|
label: _loc(CONFIG.DH.ACTOR.abilities[key].label),
|
||||||
verbs: CONFIG.DH.ACTOR.abilities[key].verbs.map(x => game.i18n.localize(x))
|
verbs: CONFIG.DH.ACTOR.abilities[key].verbs.map(x => game.i18n.localize(x)),
|
||||||
|
isSpellcasting: this.document.system.spellcastModifierTrait?.key === key
|
||||||
};
|
};
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
|
|
@ -218,6 +246,11 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
context.resources.stress.emptyPips =
|
context.resources.stress.emptyPips =
|
||||||
context.resources.stress.max < maxResource ? maxResource - context.resources.stress.max : 0;
|
context.resources.stress.max < maxResource ? maxResource - context.resources.stress.max : 0;
|
||||||
|
|
||||||
|
context.equippedItems = sortBy(
|
||||||
|
this.document.items.filter(i => i.system.equipped && (i.type === 'weapon' || i.usable)),
|
||||||
|
i => (i.type === 'weapon' ? (i.system.secondary ? 1 : 0) : 2)
|
||||||
|
);
|
||||||
|
|
||||||
context.beastformActive = this.document.effects.find(x => x.type === 'beastform');
|
context.beastformActive = this.document.effects.find(x => x.type === 'beastform');
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
|
|
@ -305,6 +338,56 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
/* Context Menu */
|
/* Context Menu */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
static #getCreationMainContextOptions() {
|
||||||
|
/** Returns true if the item is managed by the level up wizard. Such items shouldn't allow things like manual removal */
|
||||||
|
function isItemWizardManaged(item) {
|
||||||
|
const actor = item?.actor;
|
||||||
|
if (!actor) return false;
|
||||||
|
|
||||||
|
// If levelup automation is off in general or for this character, all items are unmanaged
|
||||||
|
// This is disabled until we have proper granted feature removal, for now this feature is to correct errors
|
||||||
|
// const levelupAuto = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).levelupAuto;
|
||||||
|
// if (!levelupAuto) return false;
|
||||||
|
|
||||||
|
// Core items aren't part of levelup data. TODO: add some way to flag a specific character as no auto leveling
|
||||||
|
const classPair = actor.system.class;
|
||||||
|
const coreItems = [actor.system.ancestry, actor.system.community, classPair?.value, classPair?.subclass];
|
||||||
|
if (coreItems.includes(item)) return true;
|
||||||
|
|
||||||
|
const levelups = Object.values(actor.system.levelData?.levelups) ?? [];
|
||||||
|
const uuid = item.uuid;
|
||||||
|
const sourceUuid = item._stats.compendiumSource; // on older characters this may be missing
|
||||||
|
return levelups.some(data => {
|
||||||
|
if (item.type === 'subclass') {
|
||||||
|
const selectedSubclasses = data.selections.map(s => s.secondaryData?.subclass).filter(s => !!s);
|
||||||
|
return sourceUuid
|
||||||
|
? selectedSubclasses.includes(sourceUuid)
|
||||||
|
: selectedSubclasses.length && item.system.isMulticlass;
|
||||||
|
}
|
||||||
|
|
||||||
|
const matchesCard = data.achievements.domainCards.some(i => i.itemUuid === uuid);
|
||||||
|
const matchesSelection = data.selections.some(s => s.itemUuid === uuid);
|
||||||
|
return matchesCard || matchesSelection;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: 'CONTROLS.CommonDelete',
|
||||||
|
icon: 'fa-solid fa-trash',
|
||||||
|
visible: target => {
|
||||||
|
const doc = getDocFromElementSync(target);
|
||||||
|
return doc?.isOwner && !isItemWizardManaged(doc);
|
||||||
|
},
|
||||||
|
onClick: async (event, target) => {
|
||||||
|
const doc = await getDocFromElement(target);
|
||||||
|
if (event.shiftKey) return doc.delete();
|
||||||
|
else return doc.deleteDialog();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the set of ContextMenu options for DomainCards.
|
* Get the set of ContextMenu options for DomainCards.
|
||||||
* @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - The Array of context options passed to the ContextMenu instance
|
* @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - The Array of context options passed to the ContextMenu instance
|
||||||
|
|
@ -315,13 +398,13 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
/**@type {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} */
|
/**@type {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} */
|
||||||
const options = [
|
const options = [
|
||||||
{
|
{
|
||||||
name: 'toLoadout',
|
label: 'toLoadout',
|
||||||
icon: 'fa-solid fa-arrow-up',
|
icon: 'fa-solid fa-arrow-up',
|
||||||
condition: target => {
|
visible: target => {
|
||||||
const doc = getDocFromElementSync(target);
|
const doc = getDocFromElementSync(target);
|
||||||
return doc && doc.system.inVault;
|
return doc?.isOwner && doc.system.inVault;
|
||||||
},
|
},
|
||||||
callback: async target => {
|
onClick: async (_, target) => {
|
||||||
const doc = await getDocFromElement(target);
|
const doc = await getDocFromElement(target);
|
||||||
const actorLoadout = doc.actor.system.loadoutSlot;
|
const actorLoadout = doc.actor.system.loadoutSlot;
|
||||||
if (actorLoadout.available) return doc.update({ 'system.inVault': false });
|
if (actorLoadout.available) return doc.update({ 'system.inVault': false });
|
||||||
|
|
@ -329,13 +412,13 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'recall',
|
label: 'recall',
|
||||||
icon: 'fa-solid fa-bolt-lightning',
|
icon: 'fa-solid fa-bolt-lightning',
|
||||||
condition: target => {
|
visible: target => {
|
||||||
const doc = getDocFromElementSync(target);
|
const doc = getDocFromElementSync(target);
|
||||||
return doc && doc.system.inVault;
|
return doc?.isOwner && doc.system.inVault;
|
||||||
},
|
},
|
||||||
callback: async (target, event) => {
|
onClick: async (event, target) => {
|
||||||
const doc = await getDocFromElement(target);
|
const doc = await getDocFromElement(target);
|
||||||
const actorLoadout = doc.actor.system.loadoutSlot;
|
const actorLoadout = doc.actor.system.loadoutSlot;
|
||||||
if (!actorLoadout.available) {
|
if (!actorLoadout.available) {
|
||||||
|
|
@ -368,17 +451,17 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'toVault',
|
label: 'toVault',
|
||||||
icon: 'fa-solid fa-arrow-down',
|
icon: 'fa-solid fa-arrow-down',
|
||||||
condition: target => {
|
visible: target => {
|
||||||
const doc = getDocFromElementSync(target);
|
const doc = getDocFromElementSync(target);
|
||||||
return doc && !doc.system.inVault;
|
return doc?.isOwner && !doc.system.inVault;
|
||||||
},
|
},
|
||||||
callback: async target => (await getDocFromElement(target)).update({ 'system.inVault': true })
|
onClick: async (_, target) => (await getDocFromElement(target)).update({ 'system.inVault': true })
|
||||||
}
|
}
|
||||||
].map(option => ({
|
].map(option => ({
|
||||||
...option,
|
...option,
|
||||||
name: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.name}`,
|
label: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.label}`,
|
||||||
icon: `<i class="${option.icon}"></i>`
|
icon: `<i class="${option.icon}"></i>`
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
@ -391,29 +474,29 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
* @this {CharacterSheet}
|
* @this {CharacterSheet}
|
||||||
* @protected
|
* @protected
|
||||||
*/
|
*/
|
||||||
static #getEquipamentContextOptions() {
|
static #getEquipmentContextOptions() {
|
||||||
const options = [
|
const options = [
|
||||||
{
|
{
|
||||||
name: 'equip',
|
label: 'equip',
|
||||||
icon: 'fa-solid fa-hands',
|
icon: 'fa-solid fa-hands',
|
||||||
condition: target => {
|
visible: target => {
|
||||||
const doc = getDocFromElementSync(target);
|
const doc = getDocFromElementSync(target);
|
||||||
return doc && !doc.system.equipped;
|
return doc.isOwner && doc && !doc.system.equipped;
|
||||||
},
|
},
|
||||||
callback: (target, event) => CharacterSheet.#toggleEquipItem.call(this, event, target)
|
onClick: (event, target) => CharacterSheet.#toggleEquipItem.call(this, event, target)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'unequip',
|
label: 'unequip',
|
||||||
icon: 'fa-solid fa-hands',
|
icon: 'fa-solid fa-hands',
|
||||||
condition: target => {
|
visible: target => {
|
||||||
const doc = getDocFromElementSync(target);
|
const doc = getDocFromElementSync(target);
|
||||||
return doc && doc.system.equipped;
|
return doc.isOwner && doc && doc.system.equipped;
|
||||||
},
|
},
|
||||||
callback: (target, event) => CharacterSheet.#toggleEquipItem.call(this, event, target)
|
onClick: (event, target) => CharacterSheet.#toggleEquipItem.call(this, event, target)
|
||||||
}
|
}
|
||||||
].map(option => ({
|
].map(option => ({
|
||||||
...option,
|
...option,
|
||||||
name: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.name}`,
|
label: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.label}`,
|
||||||
icon: `<i class="${option.icon}"></i>`
|
icon: `<i class="${option.icon}"></i>`
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
@ -639,12 +722,12 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateArmorMarks(event) {
|
async updateArmorMarks(event) {
|
||||||
const armor = this.document.system.armor;
|
const inputValue = Number(event.currentTarget.value);
|
||||||
if (!armor) return;
|
const { value, max } = this.document.system.armorScore;
|
||||||
|
const changeValue = Math.min(inputValue - value, max - value);
|
||||||
|
|
||||||
const maxMarks = this.document.system.armorScore;
|
event.currentTarget.value = inputValue < 0 ? 0 : value + changeValue;
|
||||||
const value = Math.min(Math.max(Number(event.currentTarget.value), 0), maxMarks);
|
this.document.system.updateArmorValue({ value: changeValue });
|
||||||
await armor.update({ 'system.marks.value': value });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
@ -704,7 +787,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
? {
|
? {
|
||||||
'system.linkedClass.uuid': {
|
'system.linkedClass.uuid': {
|
||||||
key: 'system.linkedClass.uuid',
|
key: 'system.linkedClass.uuid',
|
||||||
value: this.document.system.class.value._stats.compendiumSource
|
value: this.document.system.class.value?._stats.compendiumSource
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
|
|
@ -720,35 +803,16 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
* Rolls an attribute check based on the clicked button's dataset attribute.
|
* Rolls an attribute check based on the clicked button's dataset attribute.
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
*/
|
*/
|
||||||
static async #rollAttribute(event, button) {
|
static async #rollAttribute(_event, button) {
|
||||||
const abilityLabel = game.i18n.localize(abilities[button.dataset.attribute].label);
|
const result = await this.document.rollTrait(button.dataset.attribute);
|
||||||
const config = {
|
|
||||||
event: event,
|
|
||||||
title: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${this.actor.name}`,
|
|
||||||
headerTitle: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
|
||||||
ability: abilityLabel
|
|
||||||
}),
|
|
||||||
effects: await game.system.api.data.actions.actionsTypes.base.getEffects(this.document),
|
|
||||||
roll: {
|
|
||||||
trait: button.dataset.attribute,
|
|
||||||
type: 'trait'
|
|
||||||
},
|
|
||||||
hasRoll: true,
|
|
||||||
actionType: 'action',
|
|
||||||
headerTitle: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${this.actor.name}`,
|
|
||||||
title: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
|
||||||
ability: abilityLabel
|
|
||||||
})
|
|
||||||
};
|
|
||||||
const result = await this.document.diceRoll(config);
|
|
||||||
if (!result) return;
|
if (!result) return;
|
||||||
|
|
||||||
/* This could be avoided by baking config.costs into config.resourceUpdates. Didn't feel like messing with it at the time */
|
/* This could be avoided by baking config.costs into config.resourceUpdates. Didn't feel like messing with it at the time */
|
||||||
const costResources =
|
const costResources =
|
||||||
result.costs?.filter(x => x.enabled).map(cost => ({ ...cost, value: -cost.value, total: -cost.total })) ||
|
result.costs?.filter(x => x.enabled).map(cost => ({ ...cost, value: -cost.value, total: -cost.total })) ||
|
||||||
{};
|
{};
|
||||||
config.resourceUpdates.addResources(costResources);
|
result.resourceUpdates.addResources(costResources);
|
||||||
await config.resourceUpdates.updateResources();
|
await result.resourceUpdates.updateResources();
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: redo toggleEquipItem method
|
//TODO: redo toggleEquipItem method
|
||||||
|
|
@ -823,10 +887,13 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
* Toggles ArmorScore resource value.
|
* Toggles ArmorScore resource value.
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
*/
|
*/
|
||||||
static async #toggleArmor(_, button, element) {
|
static async #toggleArmor(_, button, _element) {
|
||||||
const ArmorValue = Number.parseInt(button.dataset.value);
|
const { value, max } = this.document.system.armorScore;
|
||||||
const newValue = this.document.system.armor.system.marks.value >= ArmorValue ? ArmorValue - 1 : ArmorValue;
|
const inputValue = Number.parseInt(button.dataset.value);
|
||||||
await this.document.system.armor.update({ 'system.marks.value': newValue });
|
const newValue = value >= inputValue ? inputValue - 1 : inputValue;
|
||||||
|
const changeValue = Math.min(newValue - value, max - value);
|
||||||
|
|
||||||
|
this.document.system.updateArmorValue({ value: changeValue });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -952,6 +1019,99 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async #toggleArmorManagement(_event, target) {
|
||||||
|
const existingTooltip = document.body.querySelector('.locked-tooltip .armor-management-container');
|
||||||
|
if (existingTooltip) {
|
||||||
|
game.tooltip.dismissLockedTooltips();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const armorSources = getArmorSources(this.document)
|
||||||
|
.filter(s => !s.disabled)
|
||||||
|
.toReversed()
|
||||||
|
.map(({ name, document, data }) => ({
|
||||||
|
...data,
|
||||||
|
uuid: document.uuid,
|
||||||
|
name
|
||||||
|
}));
|
||||||
|
if (!armorSources.length) return;
|
||||||
|
|
||||||
|
const useResourcePips = game.settings.get(
|
||||||
|
CONFIG.DH.id,
|
||||||
|
CONFIG.DH.SETTINGS.gameSettings.appearance
|
||||||
|
).useResourcePips;
|
||||||
|
const html = document.createElement('div');
|
||||||
|
html.innerHTML = await foundry.applications.handlebars.renderTemplate(
|
||||||
|
`systems/daggerheart/templates/ui/tooltip/armorManagement.hbs`,
|
||||||
|
{
|
||||||
|
sources: armorSources,
|
||||||
|
useResourcePips
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
game.tooltip.dismissLockedTooltips();
|
||||||
|
game.tooltip.activate(target, {
|
||||||
|
html,
|
||||||
|
locked: true,
|
||||||
|
cssClass: 'bordered-tooltip dh-style',
|
||||||
|
direction: 'DOWN'
|
||||||
|
});
|
||||||
|
|
||||||
|
html.querySelectorAll('.armor-slot').forEach(element => {
|
||||||
|
element.addEventListener('click', CharacterSheet.armorSourcePipUpdate);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static async armorSourceInput(event) {
|
||||||
|
const effect = await foundry.utils.fromUuid(event.target.dataset.uuid);
|
||||||
|
const value = Math.max(Math.min(Number.parseInt(event.target.value), effect.system.armorData.max), 0);
|
||||||
|
event.target.value = value;
|
||||||
|
const progressBar = event.target.closest('.status-bar.armor-slots').querySelector('progress');
|
||||||
|
progressBar.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Update specific armor source */
|
||||||
|
static async armorSourcePipUpdate(event) {
|
||||||
|
const target = event.target.closest('.armor-slot');
|
||||||
|
const { uuid, value } = target.dataset;
|
||||||
|
const document = await foundry.utils.fromUuid(uuid);
|
||||||
|
|
||||||
|
let inputValue = Number.parseInt(value);
|
||||||
|
let decreasing = false;
|
||||||
|
let newCurrent = 0;
|
||||||
|
|
||||||
|
if (document.type === 'armor') {
|
||||||
|
decreasing = document.system.armor.current >= inputValue;
|
||||||
|
newCurrent = decreasing ? inputValue - 1 : inputValue;
|
||||||
|
await document.update({ 'system.armor.current': newCurrent });
|
||||||
|
} else if (document.system.armorData) {
|
||||||
|
const { current } = document.system.armorData;
|
||||||
|
decreasing = current >= inputValue;
|
||||||
|
newCurrent = decreasing ? inputValue - 1 : inputValue;
|
||||||
|
|
||||||
|
const newChanges = document.system.changes.map(change => ({
|
||||||
|
...change,
|
||||||
|
value: change.type === 'armor' ? { ...change.value, current: newCurrent } : change.value
|
||||||
|
}));
|
||||||
|
|
||||||
|
await document.update({ 'system.changes': newChanges });
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const container = target.closest('.slot-bar');
|
||||||
|
for (const armorSlot of container.querySelectorAll('.armor-slot i')) {
|
||||||
|
const index = Number.parseInt(armorSlot.dataset.index);
|
||||||
|
if (decreasing && index >= newCurrent) {
|
||||||
|
armorSlot.classList.remove('fa-shield');
|
||||||
|
armorSlot.classList.add('fa-shield-halved');
|
||||||
|
} else if (!decreasing && index < newCurrent) {
|
||||||
|
armorSlot.classList.add('fa-shield');
|
||||||
|
armorSlot.classList.remove('fa-shield-halved');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static async #toggleResourceManagement(event, button) {
|
static async #toggleResourceManagement(event, button) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const existingTooltip = document.body.querySelector('.locked-tooltip .resource-management-container');
|
const existingTooltip = document.body.querySelector('.locked-tooltip .resource-management-container');
|
||||||
|
|
@ -985,12 +1145,11 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
);
|
);
|
||||||
|
|
||||||
const target = button.closest('.resource-section');
|
const target = button.closest('.resource-section');
|
||||||
|
|
||||||
game.tooltip.dismissLockedTooltips();
|
game.tooltip.dismissLockedTooltips();
|
||||||
game.tooltip.activate(target, {
|
game.tooltip.activate(target, {
|
||||||
html,
|
html,
|
||||||
locked: true,
|
locked: true,
|
||||||
cssClass: 'bordered-tooltip',
|
cssClass: 'bordered-tooltip dh-style',
|
||||||
direction: 'DOWN',
|
direction: 'DOWN',
|
||||||
noOffset: true
|
noOffset: true
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,17 @@ export default class DhCompanionSheet extends DHBaseActorSheet {
|
||||||
toggleStress: DhCompanionSheet.#toggleStress,
|
toggleStress: DhCompanionSheet.#toggleStress,
|
||||||
actionRoll: DhCompanionSheet.#actionRoll,
|
actionRoll: DhCompanionSheet.#actionRoll,
|
||||||
levelManagement: DhCompanionSheet.#levelManagement
|
levelManagement: DhCompanionSheet.#levelManagement
|
||||||
|
},
|
||||||
|
contextMenus: [
|
||||||
|
{
|
||||||
|
handler: DHBaseActorSheet.getBaseAttackContextOptions,
|
||||||
|
selector: '[data-item-uuid][data-type="attack"]',
|
||||||
|
options: {
|
||||||
|
parentClassHooks: false,
|
||||||
|
fixed: true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
static PARTS = {
|
static PARTS = {
|
||||||
|
|
|
||||||
136
module/applications/sheets/actors/npc.mjs
Normal file
136
module/applications/sheets/actors/npc.mjs
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
import DHBaseActorSheet from '../api/base-actor.mjs';
|
||||||
|
|
||||||
|
export default class NPCSheet extends DHBaseActorSheet {
|
||||||
|
/** @inheritDoc */
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
classes: ['npc'],
|
||||||
|
position: { width: 660, height: 600 },
|
||||||
|
window: { resizable: true },
|
||||||
|
actions: {},
|
||||||
|
window: {
|
||||||
|
resizable: true,
|
||||||
|
controls: [
|
||||||
|
{
|
||||||
|
icon: 'fa-solid fa-signature',
|
||||||
|
label: 'DAGGERHEART.UI.Tooltip.configureAttribution',
|
||||||
|
action: 'editAttribution'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
dragDrop: [
|
||||||
|
{
|
||||||
|
dragSelector: '[data-item-id][draggable="true"], [data-item-id] [draggable="true"]',
|
||||||
|
dropSelector: null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
static PARTS = {
|
||||||
|
header: { template: 'systems/daggerheart/templates/sheets/actors/npc/header.hbs' },
|
||||||
|
tabs: { template: 'systems/daggerheart/templates/sheets/actors/npc/navigation.hbs' },
|
||||||
|
features: {
|
||||||
|
template: 'systems/daggerheart/templates/sheets/actors/npc/features.hbs',
|
||||||
|
scrollable: ['.feature-section']
|
||||||
|
},
|
||||||
|
notes: {
|
||||||
|
template: 'systems/daggerheart/templates/sheets/actors/npc/notes.hbs'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
static TABS = {
|
||||||
|
primary: {
|
||||||
|
tabs: [{ id: 'notes' }, { id: 'features' }],
|
||||||
|
initial: 'notes',
|
||||||
|
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
_prepareTabs(group) {
|
||||||
|
const result = super._prepareTabs(group);
|
||||||
|
if (group === 'primary') {
|
||||||
|
result.features.empty = this.document.system.features.length === 0;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
async _preparePartContext(partId, context, options) {
|
||||||
|
context = await super._preparePartContext(partId, context, options);
|
||||||
|
switch (partId) {
|
||||||
|
case 'header':
|
||||||
|
await this._prepareHeaderContext(context, options);
|
||||||
|
break;
|
||||||
|
case 'features':
|
||||||
|
await this._prepareFeaturesContext(context, options);
|
||||||
|
break;
|
||||||
|
case 'notes':
|
||||||
|
await this._prepareNotesContext(context, options);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare render context for the Header part.
|
||||||
|
* @param {ApplicationRenderContext} context
|
||||||
|
* @param {ApplicationRenderOptions} options
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
async _prepareHeaderContext(context, _options) {
|
||||||
|
const { system } = this.document;
|
||||||
|
const { TextEditor } = foundry.applications.ux;
|
||||||
|
|
||||||
|
context.description = await TextEditor.implementation.enrichHTML(system.description, {
|
||||||
|
secrets: this.document.isOwner,
|
||||||
|
relativeTo: this.document
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare render context for the Features part.
|
||||||
|
* @param {ApplicationRenderContext} context
|
||||||
|
* @param {ApplicationRenderOptions} options
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
async _prepareFeaturesContext(context, _options) {
|
||||||
|
const featureForms = ['passive', 'action', 'reaction'];
|
||||||
|
context.features = this.document.system.features.sort((a, b) =>
|
||||||
|
a.system.featureForm !== b.system.featureForm
|
||||||
|
? featureForms.indexOf(a.system.featureForm) - featureForms.indexOf(b.system.featureForm)
|
||||||
|
: a.sort - b.sort
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare render context for the Biography part.
|
||||||
|
* @param {ApplicationRenderContext} context
|
||||||
|
* @param {ApplicationRenderOptions} options
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
async _prepareNotesContext(context, _options) {
|
||||||
|
const { system } = this.document;
|
||||||
|
const { TextEditor } = foundry.applications.ux;
|
||||||
|
|
||||||
|
const paths = {
|
||||||
|
notes: 'notes'
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const [key, path] of Object.entries(paths)) {
|
||||||
|
const value = foundry.utils.getProperty(system, path);
|
||||||
|
context[key] = {
|
||||||
|
field: system.schema.getField(path),
|
||||||
|
value,
|
||||||
|
enriched: await TextEditor.implementation.enrichHTML(value, {
|
||||||
|
secrets: this.document.isOwner,
|
||||||
|
relativeTo: this.document
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
import DHBaseActorSheet from '../api/base-actor.mjs';
|
import DHBaseActorSheet from '../api/base-actor.mjs';
|
||||||
import { getDocFromElement } from '../../../helpers/utils.mjs';
|
import { getDocFromElement, sortBy } from '../../../helpers/utils.mjs';
|
||||||
import { ItemBrowser } from '../../ui/itemBrowser.mjs';
|
import { ItemBrowser } from '../../ui/itemBrowser.mjs';
|
||||||
import FilterMenu from '../../ux/filter-menu.mjs';
|
import FilterMenu from '../../ux/filter-menu.mjs';
|
||||||
import DaggerheartMenu from '../../sidebar/tabs/daggerheartMenu.mjs';
|
import DaggerheartMenu from '../../sidebar/tabs/daggerheartMenu.mjs';
|
||||||
import { socketEvent } from '../../../systemRegistration/socket.mjs';
|
import { socketEvent } from '../../../systemRegistration/socket.mjs';
|
||||||
import GroupRollDialog from '../../dialogs/group-roll-dialog.mjs';
|
|
||||||
import DhpActor from '../../../documents/actor.mjs';
|
import DhpActor from '../../../documents/actor.mjs';
|
||||||
|
|
||||||
export default class Party extends DHBaseActorSheet {
|
export default class Party extends DHBaseActorSheet {
|
||||||
|
|
@ -18,15 +17,15 @@ export default class Party extends DHBaseActorSheet {
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
classes: ['party'],
|
classes: ['party'],
|
||||||
position: {
|
position: {
|
||||||
width: 550,
|
width: 600,
|
||||||
height: 900
|
height: 900
|
||||||
},
|
},
|
||||||
window: {
|
window: {
|
||||||
resizable: true
|
resizable: true
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
|
openDocument: Party.#openDocument,
|
||||||
deletePartyMember: Party.#deletePartyMember,
|
deletePartyMember: Party.#deletePartyMember,
|
||||||
deleteItem: Party.#deleteItem,
|
|
||||||
toggleHope: Party.#toggleHope,
|
toggleHope: Party.#toggleHope,
|
||||||
toggleHitPoints: Party.#toggleHitPoints,
|
toggleHitPoints: Party.#toggleHitPoints,
|
||||||
toggleStress: Party.#toggleStress,
|
toggleStress: Party.#toggleStress,
|
||||||
|
|
@ -35,9 +34,7 @@ export default class Party extends DHBaseActorSheet {
|
||||||
refeshActions: Party.#refeshActions,
|
refeshActions: Party.#refeshActions,
|
||||||
triggerRest: Party.#triggerRest,
|
triggerRest: Party.#triggerRest,
|
||||||
tagTeamRoll: Party.#tagTeamRoll,
|
tagTeamRoll: Party.#tagTeamRoll,
|
||||||
groupRoll: Party.#groupRoll,
|
groupRoll: Party.#groupRoll
|
||||||
selectRefreshable: DaggerheartMenu.selectRefreshable,
|
|
||||||
refreshActors: DaggerheartMenu.refreshActors
|
|
||||||
},
|
},
|
||||||
dragDrop: [{ dragSelector: '[data-item-id]', dropSelector: null }]
|
dragDrop: [{ dragSelector: '[data-item-id]', dropSelector: null }]
|
||||||
};
|
};
|
||||||
|
|
@ -46,16 +43,10 @@ export default class Party extends DHBaseActorSheet {
|
||||||
static PARTS = {
|
static PARTS = {
|
||||||
header: { template: 'systems/daggerheart/templates/sheets/actors/party/header.hbs' },
|
header: { template: 'systems/daggerheart/templates/sheets/actors/party/header.hbs' },
|
||||||
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
|
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
|
||||||
partyMembers: { template: 'systems/daggerheart/templates/sheets/actors/party/party-members.hbs' },
|
partyMembers: {
|
||||||
resources: {
|
template: 'systems/daggerheart/templates/sheets/actors/party/party-members.hbs',
|
||||||
template: 'systems/daggerheart/templates/sheets/actors/party/resources.hbs',
|
|
||||||
scrollable: ['']
|
scrollable: ['']
|
||||||
},
|
},
|
||||||
/* NOT YET IMPLEMENTED */
|
|
||||||
// projects: {
|
|
||||||
// template: 'systems/daggerheart/templates/sheets/actors/party/projects.hbs',
|
|
||||||
// scrollable: ['']
|
|
||||||
// },
|
|
||||||
inventory: {
|
inventory: {
|
||||||
template: 'systems/daggerheart/templates/sheets/actors/party/inventory.hbs',
|
template: 'systems/daggerheart/templates/sheets/actors/party/inventory.hbs',
|
||||||
scrollable: ['.tab.inventory .items-section']
|
scrollable: ['.tab.inventory .items-section']
|
||||||
|
|
@ -66,20 +57,13 @@ export default class Party extends DHBaseActorSheet {
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
static TABS = {
|
static TABS = {
|
||||||
primary: {
|
primary: {
|
||||||
tabs: [
|
tabs: [{ id: 'partyMembers' }, { id: 'inventory' }, { id: 'notes' }],
|
||||||
{ id: 'partyMembers' },
|
|
||||||
{ id: 'resources' },
|
|
||||||
/* NOT YET IMPLEMENTED */
|
|
||||||
// { id: 'projects' },
|
|
||||||
{ id: 'inventory' },
|
|
||||||
{ id: 'notes' }
|
|
||||||
],
|
|
||||||
initial: 'partyMembers',
|
initial: 'partyMembers',
|
||||||
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static ALLOWED_ACTOR_TYPES = ['character', 'companion', 'adversary'];
|
static ALLOWED_ACTOR_TYPES = ['character', 'companion', 'adversary', 'npc'];
|
||||||
static DICE_ROLL_ACTOR_TYPES = ['character'];
|
static DICE_ROLL_ACTOR_TYPES = ['character'];
|
||||||
|
|
||||||
async _onRender(context, options) {
|
async _onRender(context, options) {
|
||||||
|
|
@ -92,12 +76,22 @@ export default class Party extends DHBaseActorSheet {
|
||||||
/* Prepare Context */
|
/* Prepare Context */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
async _prepareContext(options) {
|
||||||
|
const context = await super._prepareContext(options);
|
||||||
|
const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Metagaming);
|
||||||
|
context.showStats =
|
||||||
|
settings.hidePartyStats === 'never' || (settings.hidePartyStats === 'players' && game.user.isGM);
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
async _preparePartContext(partId, context, options) {
|
async _preparePartContext(partId, context, options) {
|
||||||
context = await super._preparePartContext(partId, context, options);
|
context = await super._preparePartContext(partId, context, options);
|
||||||
switch (partId) {
|
switch (partId) {
|
||||||
case 'header':
|
case 'header':
|
||||||
await this._prepareHeaderContext(context, options);
|
await this._prepareHeaderContext(context, options);
|
||||||
break;
|
break;
|
||||||
|
case 'partyMembers':
|
||||||
|
await this._prepareMembersContext(context, options);
|
||||||
case 'notes':
|
case 'notes':
|
||||||
await this._prepareNotesContext(context, options);
|
await this._prepareNotesContext(context, options);
|
||||||
break;
|
break;
|
||||||
|
|
@ -120,6 +114,61 @@ export default class Party extends DHBaseActorSheet {
|
||||||
secrets: this.document.isOwner,
|
secrets: this.document.isOwner,
|
||||||
relativeTo: this.document
|
relativeTo: this.document
|
||||||
});
|
});
|
||||||
|
context.tagTeamActive = Boolean(this.document.system.tagTeam.initiator);
|
||||||
|
context.groupRollActive = Boolean(this.document.system.groupRoll.leader);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _prepareMembersContext(context, _options) {
|
||||||
|
context.partyMembers = [];
|
||||||
|
const traits = ['agility', 'strength', 'finesse', 'instinct', 'presence', 'knowledge'];
|
||||||
|
for (const actor of this.document.system.partyMembers) {
|
||||||
|
const weapons = [];
|
||||||
|
if (actor.type === 'character') {
|
||||||
|
if (actor.system.usedUnarmed) {
|
||||||
|
weapons.push(actor.system.usedUnarmed);
|
||||||
|
}
|
||||||
|
const equipped = actor.items.filter(i => i.system.equipped && i.type === 'weapon');
|
||||||
|
weapons.push(...sortBy(equipped, i => (i.system.secondary ? 1 : 0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
context.partyMembers.push({
|
||||||
|
uuid: actor.uuid,
|
||||||
|
img: actor.img,
|
||||||
|
name: actor.name,
|
||||||
|
subtitle: (() => {
|
||||||
|
if (!['character', 'companion'].includes(actor.type)) {
|
||||||
|
return game.i18n.format(`TYPES.Actor.${actor.type}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { value: classItem, subclass } = actor.system.class ?? {};
|
||||||
|
const partner = actor.system.partner;
|
||||||
|
const ancestry = actor.system.ancestry;
|
||||||
|
const community = actor.system.community;
|
||||||
|
if (partner || (classItem && subclass && ancestry && community)) {
|
||||||
|
return game.i18n.format(`DAGGERHEART.ACTORS.Party.Subtitle.${actor.type}`, {
|
||||||
|
class: classItem?.name,
|
||||||
|
subclass: subclass?.name,
|
||||||
|
partner: partner?.name,
|
||||||
|
ancestry: ancestry?.name,
|
||||||
|
community: community?.name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})(),
|
||||||
|
type: actor.type,
|
||||||
|
resources: actor.system.resources,
|
||||||
|
armorScore: actor.system.armorScore,
|
||||||
|
damageThresholds: actor.system.damageThresholds,
|
||||||
|
evasion: actor.system.evasion,
|
||||||
|
difficulty: actor.system.difficulty,
|
||||||
|
traits: actor.system.traits
|
||||||
|
? traits.map(t => ({
|
||||||
|
label: game.i18n.localize(`DAGGERHEART.CONFIG.Traits.${t}.short`),
|
||||||
|
value: actor.system.traits[t].value
|
||||||
|
}))
|
||||||
|
: null,
|
||||||
|
weapons
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -150,6 +199,12 @@ export default class Party extends DHBaseActorSheet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async #openDocument(_, target) {
|
||||||
|
const uuid = target.dataset.uuid;
|
||||||
|
const document = await foundry.utils.fromUuid(uuid);
|
||||||
|
document?.sheet?.render(true);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggles a hope resource value.
|
* Toggles a hope resource value.
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
|
|
@ -190,11 +245,14 @@ export default class Party extends DHBaseActorSheet {
|
||||||
* Toggles a armor slot resource value.
|
* Toggles a armor slot resource value.
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
*/
|
*/
|
||||||
static async #toggleArmorSlot(_, target, element) {
|
static async #toggleArmorSlot(_, target) {
|
||||||
const armorItem = await foundry.utils.fromUuid(target.dataset.itemUuid);
|
const actor = await foundry.utils.fromUuid(target.dataset.actorId);
|
||||||
const armorValue = Number.parseInt(target.dataset.value);
|
const { value, max } = actor.system.armorScore;
|
||||||
const newValue = armorItem.system.marks.value >= armorValue ? armorValue - 1 : armorValue;
|
const inputValue = Number.parseInt(target.dataset.value);
|
||||||
await armorItem.update({ 'system.marks.value': newValue });
|
const newValue = value >= inputValue ? inputValue - 1 : inputValue;
|
||||||
|
const changeValue = Math.min(newValue - value, max - value);
|
||||||
|
|
||||||
|
await actor.system.updateArmorValue({ value: changeValue });
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -229,7 +287,7 @@ export default class Party extends DHBaseActorSheet {
|
||||||
title: game.i18n.localize(`DAGGERHEART.APPLICATIONS.Downtime.${button.dataset.type}.title`),
|
title: game.i18n.localize(`DAGGERHEART.APPLICATIONS.Downtime.${button.dataset.type}.title`),
|
||||||
icon: button.dataset.type === 'shortRest' ? 'fa-solid fa-utensils' : 'fa-solid fa-bed'
|
icon: button.dataset.type === 'shortRest' ? 'fa-solid fa-utensils' : 'fa-solid fa-bed'
|
||||||
},
|
},
|
||||||
content: 'This will trigger a dialog to players make their downtime moves, are you sure?',
|
content: game.i18n.localize('DAGGERHEART.ACTORS.Party.triggerRestContent'),
|
||||||
classes: ['daggerheart', 'dialog', 'dh-style']
|
classes: ['daggerheart', 'dialog', 'dh-style']
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -248,24 +306,18 @@ export default class Party extends DHBaseActorSheet {
|
||||||
|
|
||||||
static async downtimeMoveQuery({ actorId, downtimeType }) {
|
static async downtimeMoveQuery({ actorId, downtimeType }) {
|
||||||
const actor = await foundry.utils.fromUuid(actorId);
|
const actor = await foundry.utils.fromUuid(actorId);
|
||||||
if (!actor || !actor?.isOwner) reject();
|
if (!actor || !actor?.isOwner) return;
|
||||||
new game.system.api.applications.dialogs.Downtime(actor, downtimeType === 'shortRest').render({
|
new game.system.api.applications.dialogs.Downtime(actor, downtimeType === 'shortRest').render({
|
||||||
force: true
|
force: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #tagTeamRoll() {
|
static async #tagTeamRoll() {
|
||||||
new game.system.api.applications.dialogs.TagTeamDialog(
|
new game.system.api.applications.dialogs.TagTeamDialog(this.document).render({ force: true });
|
||||||
this.document.system.partyMembers.filter(x => Party.DICE_ROLL_ACTOR_TYPES.includes(x.type))
|
|
||||||
).render({
|
|
||||||
force: true
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #groupRoll(_params) {
|
static async #groupRoll(_params) {
|
||||||
new GroupRollDialog(
|
new game.system.api.applications.dialogs.GroupRollDialog(this.document).render({ force: true });
|
||||||
this.document.system.partyMembers.filter(x => Party.DICE_ROLL_ACTOR_TYPES.includes(x.type))
|
|
||||||
).render({ force: true });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
@ -427,43 +479,22 @@ export default class Party extends DHBaseActorSheet {
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #deletePartyMember(event, target) {
|
static async #deletePartyMember(event, target) {
|
||||||
const doc = await getDocFromElement(target.closest('.inventory-item'));
|
const doc = await foundry.utils.fromUuid(target.closest('[data-uuid]')?.dataset.uuid);
|
||||||
|
|
||||||
if (!event.shiftKey) {
|
if (!event.shiftKey) {
|
||||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||||
window: {
|
window: {
|
||||||
title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', {
|
title: game.i18n.format('DAGGERHEART.ACTORS.Party.RemoveConfirmation.title', {
|
||||||
type: game.i18n.localize('TYPES.Actor.adversary'),
|
|
||||||
name: doc.name
|
name: doc.name
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: doc.name })
|
content: game.i18n.format('DAGGERHEART.ACTORS.Party.RemoveConfirmation.text', { name: doc.name })
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!confirmed) return;
|
if (!confirmed) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentMembers = this.document.system.partyMembers.map(x => x.uuid);
|
const currentMembers = this.document.system.partyMembers.map(x => x.uuid);
|
||||||
const newMemberdList = currentMembers.filter(uuid => uuid !== doc.uuid);
|
const newMembersList = currentMembers.filter(uuid => uuid !== doc.uuid);
|
||||||
await this.document.update({ 'system.partyMembers': newMemberdList });
|
await this.document.update({ 'system.partyMembers': newMembersList });
|
||||||
}
|
|
||||||
|
|
||||||
static async #deleteItem(event, target) {
|
|
||||||
const doc = await getDocFromElement(target.closest('.inventory-item'));
|
|
||||||
if (!event.shiftKey) {
|
|
||||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
|
||||||
window: {
|
|
||||||
title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', {
|
|
||||||
type: game.i18n.localize('TYPES.Actor.party'),
|
|
||||||
name: doc.name
|
|
||||||
})
|
|
||||||
},
|
|
||||||
content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: doc.name })
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!confirmed) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.document.deleteEmbeddedDocuments('Item', [doc.id]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -72,20 +72,15 @@ const typeSettingsMap = {
|
||||||
*/
|
*/
|
||||||
export default function DHApplicationMixin(Base) {
|
export default function DHApplicationMixin(Base) {
|
||||||
class DHSheetV2 extends HandlebarsApplicationMixin(Base) {
|
class DHSheetV2 extends HandlebarsApplicationMixin(Base) {
|
||||||
|
#nonHeaderAttribution = ['environment', 'ancestry', 'community', 'domainCard'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {DHSheetV2Configuration} [options={}]
|
* @param {DHSheetV2Configuration} [options={}]
|
||||||
*/
|
*/
|
||||||
constructor(options = {}) {
|
constructor(options = {}) {
|
||||||
super(options);
|
super(options);
|
||||||
/**
|
|
||||||
* @type {foundry.applications.ux.DragDrop[]}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
this._dragDrop = this._createDragDropHandlers();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#nonHeaderAttribution = ['environment', 'ancestry', 'community', 'domainCard'];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default options for the sheet.
|
* The default options for the sheet.
|
||||||
* @type {DHSheetV2Configuration}
|
* @type {DHSheetV2Configuration}
|
||||||
|
|
@ -94,7 +89,7 @@ export default function DHApplicationMixin(Base) {
|
||||||
classes: ['daggerheart', 'sheet', 'dh-style'],
|
classes: ['daggerheart', 'sheet', 'dh-style'],
|
||||||
actions: {
|
actions: {
|
||||||
triggerContextMenu: DHSheetV2.#triggerContextMenu,
|
triggerContextMenu: DHSheetV2.#triggerContextMenu,
|
||||||
createDoc: DHSheetV2.#createDoc,
|
createDoc: DHSheetV2.#onCreateDoc,
|
||||||
editDoc: DHSheetV2.#editDoc,
|
editDoc: DHSheetV2.#editDoc,
|
||||||
deleteDoc: DHSheetV2.#deleteDoc,
|
deleteDoc: DHSheetV2.#deleteDoc,
|
||||||
toChat: DHSheetV2.#toChat,
|
toChat: DHSheetV2.#toChat,
|
||||||
|
|
@ -102,8 +97,8 @@ export default function DHApplicationMixin(Base) {
|
||||||
viewItem: DHSheetV2.#viewItem,
|
viewItem: DHSheetV2.#viewItem,
|
||||||
toggleEffect: DHSheetV2.#toggleEffect,
|
toggleEffect: DHSheetV2.#toggleEffect,
|
||||||
toggleExtended: DHSheetV2.#toggleExtended,
|
toggleExtended: DHSheetV2.#toggleExtended,
|
||||||
addNewItem: DHSheetV2.#addNewItem,
|
addNewItem: DHSheetV2.#onAddNewItem,
|
||||||
browseItem: DHSheetV2.#browseItem,
|
browseItem: DHSheetV2.#onBrowseItem,
|
||||||
editAttribution: DHSheetV2.#editAttribution
|
editAttribution: DHSheetV2.#editAttribution
|
||||||
},
|
},
|
||||||
contextMenus: [
|
contextMenus: [
|
||||||
|
|
@ -177,7 +172,6 @@ export default function DHApplicationMixin(Base) {
|
||||||
/**@inheritdoc */
|
/**@inheritdoc */
|
||||||
_attachPartListeners(partId, htmlElement, options) {
|
_attachPartListeners(partId, htmlElement, options) {
|
||||||
super._attachPartListeners(partId, htmlElement, options);
|
super._attachPartListeners(partId, htmlElement, options);
|
||||||
this._dragDrop.forEach(d => d.bind(htmlElement));
|
|
||||||
|
|
||||||
// Handle delta inputs
|
// Handle delta inputs
|
||||||
for (const deltaInput of htmlElement.querySelectorAll('input[data-allow-delta]')) {
|
for (const deltaInput of htmlElement.querySelectorAll('input[data-allow-delta]')) {
|
||||||
|
|
@ -290,6 +284,16 @@ export default function DHApplicationMixin(Base) {
|
||||||
async _onRender(context, options) {
|
async _onRender(context, options) {
|
||||||
await super._onRender(context, options);
|
await super._onRender(context, options);
|
||||||
this._createTagifyElements(this.options.tagifyConfigs);
|
this._createTagifyElements(this.options.tagifyConfigs);
|
||||||
|
|
||||||
|
for (const d of this.options.dragDrop) {
|
||||||
|
new foundry.applications.ux.DragDrop.implementation({
|
||||||
|
...d,
|
||||||
|
callbacks: {
|
||||||
|
dragstart: this._onDragStart.bind(this),
|
||||||
|
drop: this._onDrop.bind(this)
|
||||||
|
}
|
||||||
|
}).bind(this.element);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
@ -350,21 +354,6 @@ export default function DHApplicationMixin(Base) {
|
||||||
/* Drag and Drop */
|
/* Drag and Drop */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates drag-drop handlers from the configured options.
|
|
||||||
* @returns {foundry.applications.ux.DragDrop[]}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_createDragDropHandlers() {
|
|
||||||
return this.options.dragDrop.map(d => {
|
|
||||||
d.callbacks = {
|
|
||||||
dragstart: this._onDragStart.bind(this),
|
|
||||||
drop: this._onDrop.bind(this)
|
|
||||||
};
|
|
||||||
return new foundry.applications.ux.DragDrop.implementation(d);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle dragStart event.
|
* Handle dragStart event.
|
||||||
* @param {DragEvent} event
|
* @param {DragEvent} event
|
||||||
|
|
@ -429,26 +418,26 @@ export default function DHApplicationMixin(Base) {
|
||||||
/**@type {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} */
|
/**@type {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} */
|
||||||
const options = [
|
const options = [
|
||||||
{
|
{
|
||||||
name: 'disableEffect',
|
label: 'disableEffect',
|
||||||
icon: 'fa-solid fa-lightbulb',
|
icon: 'fa-solid fa-lightbulb',
|
||||||
condition: element => {
|
visible: element => {
|
||||||
const target = element.closest('[data-item-uuid]');
|
const target = element.closest('[data-item-uuid]');
|
||||||
return !target.dataset.disabled && target.dataset.itemType !== 'beastform';
|
return !target.dataset.disabled && target.dataset.itemType !== 'beastform';
|
||||||
},
|
},
|
||||||
callback: async target => (await getDocFromElement(target)).update({ disabled: true })
|
onClick: async (_, target) => (await getDocFromElement(target)).update({ disabled: true })
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'enableEffect',
|
label: 'enableEffect',
|
||||||
icon: 'fa-regular fa-lightbulb',
|
icon: 'fa-regular fa-lightbulb',
|
||||||
condition: element => {
|
visible: element => {
|
||||||
const target = element.closest('[data-item-uuid]');
|
const target = element.closest('[data-item-uuid]');
|
||||||
return target.dataset.disabled && target.dataset.itemType !== 'beastform';
|
return target.dataset.disabled && target.dataset.itemType !== 'beastform';
|
||||||
},
|
},
|
||||||
callback: async target => (await getDocFromElement(target)).update({ disabled: false })
|
onClick: async (_, target) => (await getDocFromElement(target)).update({ disabled: false })
|
||||||
}
|
}
|
||||||
].map(option => ({
|
].map(option => ({
|
||||||
...option,
|
...option,
|
||||||
name: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.name}`,
|
label: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.label}`,
|
||||||
icon: `<i class="${option.icon}"></i>`
|
icon: `<i class="${option.icon}"></i>`
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
@ -479,29 +468,34 @@ export default function DHApplicationMixin(Base) {
|
||||||
_getContextMenuCommonOptions({ usable = false, toChat = false, deletable = true }) {
|
_getContextMenuCommonOptions({ usable = false, toChat = false, deletable = true }) {
|
||||||
const options = [
|
const options = [
|
||||||
{
|
{
|
||||||
name: 'CONTROLS.CommonEdit',
|
label: 'CONTROLS.CommonEdit',
|
||||||
icon: 'fa-solid fa-pen-to-square',
|
icon: 'fa-solid fa-pen-to-square',
|
||||||
condition: target => {
|
visible: target => {
|
||||||
const { dataset } = target.closest('[data-item-uuid]');
|
const { dataset } = target.closest('[data-item-uuid]');
|
||||||
const doc = getDocFromElementSync(target);
|
const doc = getDocFromElementSync(target);
|
||||||
return (
|
return (
|
||||||
(!dataset.noCompendiumEdit && !doc) ||
|
(!dataset.noCompendiumEdit && !doc) ||
|
||||||
(doc && (!doc?.hasOwnProperty('systemPath') || doc?.inCollection))
|
(doc?.isOwner && (!doc?.hasOwnProperty('systemPath') || doc?.inCollection))
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
callback: async target => (await getDocFromElement(target)).sheet.render({ force: true })
|
onClick: async (_, target) => {
|
||||||
|
return (await getDocFromElement(target)).sheet.render({ force: true });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
if (usable) {
|
if (usable) {
|
||||||
options.unshift({
|
options.unshift({
|
||||||
name: 'DAGGERHEART.GENERAL.damage',
|
label: 'DAGGERHEART.GENERAL.damage',
|
||||||
icon: 'fa-solid fa-explosion',
|
icon: 'fa-solid fa-explosion',
|
||||||
condition: target => {
|
visible: target => {
|
||||||
const doc = getDocFromElementSync(target);
|
const doc = getDocFromElementSync(target);
|
||||||
return doc?.system?.attack?.damage.parts.length || doc?.damage?.parts.length;
|
const hasDamage =
|
||||||
|
!foundry.utils.isEmpty(doc?.system?.attack?.damage.parts) ||
|
||||||
|
!foundry.utils.isEmpty(doc?.damage?.parts);
|
||||||
|
return doc?.isOwner && hasDamage;
|
||||||
},
|
},
|
||||||
callback: async (target, event) => {
|
onClick: async (event, target) => {
|
||||||
const doc = await getDocFromElement(target),
|
const doc = await getDocFromElement(target),
|
||||||
action = doc?.system?.attack ?? doc;
|
action = doc?.system?.attack ?? doc;
|
||||||
const config = action.prepareConfig(event);
|
const config = action.prepareConfig(event);
|
||||||
|
|
@ -515,32 +509,33 @@ export default function DHApplicationMixin(Base) {
|
||||||
});
|
});
|
||||||
|
|
||||||
options.unshift({
|
options.unshift({
|
||||||
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.useItem',
|
label: 'DAGGERHEART.APPLICATIONS.ContextMenu.useItem',
|
||||||
icon: 'fa-solid fa-burst',
|
icon: 'fa-solid fa-burst',
|
||||||
condition: target => {
|
visible: target => {
|
||||||
const doc = getDocFromElementSync(target);
|
const doc = getDocFromElementSync(target);
|
||||||
return doc && !(doc.type === 'domainCard' && doc.system.inVault);
|
return doc?.isOwner && !(doc.type === 'domainCard' && doc.system.inVault);
|
||||||
},
|
},
|
||||||
callback: async (target, event) => (await getDocFromElement(target)).use(event)
|
onClick: async (event, target) => (await getDocFromElement(target)).use(event)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toChat)
|
if (toChat)
|
||||||
options.push({
|
options.push({
|
||||||
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.sendToChat',
|
label: 'DAGGERHEART.APPLICATIONS.ContextMenu.sendToChat',
|
||||||
icon: 'fa-solid fa-message',
|
icon: 'fa-solid fa-message',
|
||||||
callback: async target => (await getDocFromElement(target)).toChat(this.document.uuid)
|
onClick: async (_, target) => (await getDocFromElement(target)).toChat(this.document.uuid)
|
||||||
});
|
});
|
||||||
|
|
||||||
if (deletable)
|
if (deletable)
|
||||||
options.push({
|
options.push({
|
||||||
name: 'CONTROLS.CommonDelete',
|
label: 'CONTROLS.CommonDelete',
|
||||||
icon: 'fa-solid fa-trash',
|
icon: 'fa-solid fa-trash',
|
||||||
condition: element => {
|
visible: element => {
|
||||||
const target = element.closest('[data-item-uuid]');
|
const target = element.closest('[data-item-uuid]');
|
||||||
return target.dataset.itemType !== 'beastform';
|
const doc = getDocFromElementSync(target);
|
||||||
|
return doc?.isOwner !== false && target.dataset.itemType !== 'beastform';
|
||||||
},
|
},
|
||||||
callback: async (target, event) => {
|
onClick: async (event, target) => {
|
||||||
const doc = await getDocFromElement(target);
|
const doc = await getDocFromElement(target);
|
||||||
if (event.shiftKey) return doc.delete();
|
if (event.shiftKey) return doc.delete();
|
||||||
else return doc.deleteDialog();
|
else return doc.deleteDialog();
|
||||||
|
|
@ -646,18 +641,18 @@ export default function DHApplicationMixin(Base) {
|
||||||
/* Application Clicks Actions */
|
/* Application Clicks Actions */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
static async #addNewItem(event, target) {
|
static async #onAddNewItem(event, target) {
|
||||||
const createChoice = await foundry.applications.api.DialogV2.wait({
|
const createChoice = await foundry.applications.api.DialogV2.wait({
|
||||||
classes: ['dh-style', 'two-big-buttons'],
|
classes: ['dh-style', 'two-big-buttons'],
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
action: 'create',
|
action: 'create',
|
||||||
label: 'Create Item',
|
label: game.i18n.localize('DAGGERHEART.APPLICATIONS.CreateItemDialog.createItem'),
|
||||||
icon: 'fa-solid fa-plus'
|
icon: 'fa-solid fa-plus'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: 'browse',
|
action: 'browse',
|
||||||
label: 'Browse Compendium',
|
label: game.i18n.localize('DAGGERHEART.APPLICATIONS.CreateItemDialog.browseCompendium'),
|
||||||
icon: 'fa-solid fa-book'
|
icon: 'fa-solid fa-book'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -665,11 +660,11 @@ export default function DHApplicationMixin(Base) {
|
||||||
|
|
||||||
if (!createChoice) return;
|
if (!createChoice) return;
|
||||||
|
|
||||||
if (createChoice === 'browse') return DHSheetV2.#browseItem.call(this, event, target);
|
if (createChoice === 'browse') return DHSheetV2.#onBrowseItem.call(this, event, target);
|
||||||
else return DHSheetV2.#createDoc.call(this, event, target);
|
else return DHSheetV2.#onCreateDoc.call(this, event, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #browseItem(event, target) {
|
static async #onBrowseItem(_event, target) {
|
||||||
const type = target.dataset.compendium ?? target.dataset.type;
|
const type = target.dataset.compendium ?? target.dataset.type;
|
||||||
|
|
||||||
const presets = {
|
const presets = {
|
||||||
|
|
@ -720,7 +715,7 @@ export default function DHApplicationMixin(Base) {
|
||||||
* Create an embedded document.
|
* Create an embedded document.
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
*/
|
*/
|
||||||
static async #createDoc(event, target) {
|
static async #onCreateDoc(event, target) {
|
||||||
const { documentClass, type, inVault, disabled } = target.dataset;
|
const { documentClass, type, inVault, disabled } = target.dataset;
|
||||||
const parentIsItem = this.document.documentName === 'Item';
|
const parentIsItem = this.document.documentName === 'Item';
|
||||||
const featureOnCharacter = this.document.parent?.type === 'character' && type === 'feature';
|
const featureOnCharacter = this.document.parent?.type === 'character' && type === 'feature';
|
||||||
|
|
@ -742,15 +737,21 @@ export default function DHApplicationMixin(Base) {
|
||||||
|
|
||||||
const cls =
|
const cls =
|
||||||
type === 'action' ? game.system.api.models.actions.actionsTypes.base : getDocumentClass(documentClass);
|
type === 'action' ? game.system.api.models.actions.actionsTypes.base : getDocumentClass(documentClass);
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
name: cls.defaultName({ type, parent }),
|
name: cls.defaultName({ type, parent }),
|
||||||
type,
|
type,
|
||||||
system: systemData
|
system: systemData
|
||||||
};
|
};
|
||||||
if (inVault) data['system.inVault'] = true;
|
|
||||||
if (disabled) data.disabled = true;
|
if (disabled) data.disabled = true;
|
||||||
if (type === 'domainCard' && parent?.system.domains?.length) {
|
|
||||||
data.system.domain = parent.system.domains[0];
|
if (type === 'domainCard') {
|
||||||
|
if (parent?.system.domains?.length) data.system.domain = parent.system.domains[0];
|
||||||
|
if (inVault) data.system.inVault = true;
|
||||||
|
} else if (type === 'weapon') {
|
||||||
|
// Passing an empty system object to weapon causes validation failure due to attack action initialization
|
||||||
|
// todo: determine why, fix it at its source, then remove this fallback
|
||||||
|
delete data.system;
|
||||||
}
|
}
|
||||||
|
|
||||||
const doc = await cls.create(data, { parent, renderSheet: !event.shiftKey });
|
const doc = await cls.create(data, { parent, renderSheet: !event.shiftKey });
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
||||||
.hideAttribution;
|
.hideAttribution;
|
||||||
|
|
||||||
// Prepare inventory data
|
// Prepare inventory data
|
||||||
if (['party', 'character'].includes(this.document.type)) {
|
if (this.document.system.metadata.hasInventory) {
|
||||||
context.inventory = {
|
context.inventory = {
|
||||||
currencies: {},
|
currencies: {},
|
||||||
weapons: this.document.itemTypes.weapon.sort((a, b) => a.sort - b.sort),
|
weapons: this.document.itemTypes.weapon.sort((a, b) => a.sort - b.sort),
|
||||||
|
|
@ -160,12 +160,21 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
||||||
inactives: []
|
inactives: []
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const effect of this.actor.allApplicableEffects()) {
|
for (const effect of this.actor.allApplicableEffects({ noTransferArmor: true })) {
|
||||||
const list = effect.active ? context.effects.actives : context.effects.inactives;
|
const list = effect.active ? context.effects.actives : context.effects.inactives;
|
||||||
list.push(effect);
|
list.push(effect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Add support for input content editables */
|
||||||
|
_toggleDisabled(disabled) {
|
||||||
|
super._toggleDisabled(disabled);
|
||||||
|
const form = this.form;
|
||||||
|
for (const element of form.querySelectorAll('.input[contenteditable]')) {
|
||||||
|
element.classList.toggle('disabled', disabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
/* Context Menu */
|
/* Context Menu */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
@ -180,6 +189,43 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
||||||
return this._getContextMenuCommonOptions.call(this, { usable: true, toChat: true });
|
return this._getContextMenuCommonOptions.call(this, { usable: true, toChat: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the set of ContextMenu options for the base attack.
|
||||||
|
* @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - The Array of context options passed to the ContextMenu instance
|
||||||
|
* @this {CharacterSheet}
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
static getBaseAttackContextOptions() {
|
||||||
|
/**@type {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} */
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: 'DAGGERHEART.CONFIG.RollTypes.attack.name',
|
||||||
|
icon: 'fa-solid fa-burst',
|
||||||
|
onClick: async (event, target) => (await getDocFromElement(target)).use(event)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'DAGGERHEART.GENERAL.damage',
|
||||||
|
icon: 'fa-solid fa-explosion',
|
||||||
|
onClick: async (event, target) => {
|
||||||
|
const doc = await getDocFromElement(target),
|
||||||
|
action = doc?.system?.attack ?? doc;
|
||||||
|
const config = action.prepareConfig(event);
|
||||||
|
config.effects = await game.system.api.data.actions.actionsTypes.base.getEffects(
|
||||||
|
this.document,
|
||||||
|
doc
|
||||||
|
);
|
||||||
|
config.hasRoll = false;
|
||||||
|
return action && action.workflow.get('damage').execute(config, null, true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'DAGGERHEART.APPLICATIONS.ContextMenu.sendToChat',
|
||||||
|
icon: 'fa-solid fa-message',
|
||||||
|
onClick: async (_, target) => (await getDocFromElement(target)).toChat(this.document.uuid)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
/* Application Listener Actions */
|
/* Application Listener Actions */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
@ -228,7 +274,6 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
||||||
'systems/daggerheart/templates/ui/chat/action.hbs',
|
'systems/daggerheart/templates/ui/chat/action.hbs',
|
||||||
systemData
|
systemData
|
||||||
),
|
),
|
||||||
title: game.i18n.localize('DAGGERHEART.ACTIONS.Config.displayInChat'),
|
|
||||||
speaker: cls.getSpeaker(),
|
speaker: cls.getSpeaker(),
|
||||||
flags: {
|
flags: {
|
||||||
daggerheart: {
|
daggerheart: {
|
||||||
|
|
@ -284,11 +329,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
||||||
async _onDropItem(event, item) {
|
async _onDropItem(event, item) {
|
||||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||||
const originActor = item.actor;
|
const originActor = item.actor;
|
||||||
if (
|
if (!originActor || originActor.uuid === this.document.uuid || !this.document.system.metadata.hasInventory) {
|
||||||
item.actor?.uuid === this.document.uuid ||
|
|
||||||
!originActor ||
|
|
||||||
!['character', 'party'].includes(this.document.type)
|
|
||||||
) {
|
|
||||||
return super._onDropItem(event, item);
|
return super._onDropItem(event, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -303,45 +344,77 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.system.metadata.isQuantifiable) {
|
// Perform the actual transfer, showing a dialog when doing it
|
||||||
const actorItem = originActor.items.get(data.originId);
|
const availableQuantity = Math.max(1, item.system.quantity);
|
||||||
const quantityTransfered = await game.system.api.applications.dialogs.ItemTransferDialog.configure({
|
const actorItem = originActor.items.get(data.originId) ?? item;
|
||||||
|
if (availableQuantity > 1) {
|
||||||
|
const quantityTransferred = await game.system.api.applications.dialogs.ItemTransferDialog.configure({
|
||||||
item,
|
item,
|
||||||
targetActor: this.document
|
targetActor: this.document
|
||||||
});
|
});
|
||||||
|
return this.#transferItem(actorItem, quantityTransferred);
|
||||||
if (quantityTransfered) {
|
|
||||||
const existingItem = this.document.items.find(x => itemIsIdentical(x, item));
|
|
||||||
if (existingItem) {
|
|
||||||
await existingItem.update({
|
|
||||||
'system.quantity': existingItem.system.quantity + quantityTransfered
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
const createData = item.toObject();
|
return this.#transferItem(actorItem, availableQuantity);
|
||||||
await this.document.createEmbeddedDocuments('Item', [
|
|
||||||
{
|
|
||||||
...createData,
|
|
||||||
system: {
|
|
||||||
...createData.system,
|
|
||||||
quantity: quantityTransfered
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (quantityTransfered === actorItem.system.quantity) {
|
/**
|
||||||
await originActor.deleteEmbeddedDocuments('Item', [data.originId]);
|
* Helper to perform the actual transfer of an item to this actor, including stack/unstack logic based on target quantifiability.
|
||||||
|
* Make sure item is the actor item before calling this method or there will be issues
|
||||||
|
*/
|
||||||
|
async #transferItem(item, quantity) {
|
||||||
|
const originActor = item.actor;
|
||||||
|
const targetActor = this.document;
|
||||||
|
const allowStacking = targetActor.system.metadata.quantifiable?.includes(item.type);
|
||||||
|
|
||||||
|
const batch = [];
|
||||||
|
|
||||||
|
// First add/update the item to the target actor
|
||||||
|
const existing = allowStacking ? targetActor.items.find(x => itemIsIdentical(x, item)) : null;
|
||||||
|
if (existing) {
|
||||||
|
batch.push({
|
||||||
|
action: 'update',
|
||||||
|
documentName: 'Item',
|
||||||
|
parent: targetActor,
|
||||||
|
updates: [{ _id: existing.id, 'system.quantity': existing.system.quantity + quantity }]
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
await actorItem.update({
|
const itemsToCreate = [];
|
||||||
'system.quantity': actorItem.system.quantity - quantityTransfered
|
if (allowStacking) {
|
||||||
|
itemsToCreate.push(foundry.utils.mergeObject(item.toObject(true), { system: { quantity } }));
|
||||||
|
} else {
|
||||||
|
const createData = new Array(Math.max(1, quantity))
|
||||||
|
.fill(0)
|
||||||
|
.map(() => foundry.utils.mergeObject(item.toObject(), { system: { quantity: 1 } }));
|
||||||
|
itemsToCreate.push(...createData);
|
||||||
|
}
|
||||||
|
batch.push({
|
||||||
|
action: 'create',
|
||||||
|
documentName: 'Item',
|
||||||
|
parent: targetActor,
|
||||||
|
data: itemsToCreate
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// Remove the item from the original actor (by either deleting it, or updating its quantity)
|
||||||
|
if (quantity >= item.system.quantity) {
|
||||||
|
batch.push({
|
||||||
|
action: 'delete',
|
||||||
|
documentName: 'Item',
|
||||||
|
parent: originActor,
|
||||||
|
ids: [item.id]
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
await this.document.createEmbeddedDocuments('Item', [item.toObject()]);
|
batch.push({
|
||||||
await originActor.deleteEmbeddedDocuments('Item', [data.originId]);
|
action: 'update',
|
||||||
}
|
documentName: 'Item',
|
||||||
|
parent: originActor,
|
||||||
|
updates: [{ _id: item.id, 'system.quantity': item.system.quantity - quantity }]
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return foundry.documents.modifyBatch(batch);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,7 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
||||||
options.push({
|
options.push({
|
||||||
name: 'CONTROLS.CommonDelete',
|
name: 'CONTROLS.CommonDelete',
|
||||||
icon: '<i class="fa-solid fa-trash"></i>',
|
icon: '<i class="fa-solid fa-trash"></i>',
|
||||||
callback: async target => {
|
onClick: async (_, target) => {
|
||||||
const feature = await getDocFromElement(target);
|
const feature = await getDocFromElement(target);
|
||||||
if (!feature) return;
|
if (!feature) return;
|
||||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||||
|
|
|
||||||
|
|
@ -29,16 +29,6 @@ export default function ItemAttachmentSheet(Base) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
async _preparePartContext(partId, context) {
|
|
||||||
await super._preparePartContext(partId, context);
|
|
||||||
|
|
||||||
if (partId === 'attachments') {
|
|
||||||
context.attachedItems = await prepareAttachmentContext(this.document);
|
|
||||||
}
|
|
||||||
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
async _onDrop(event) {
|
async _onDrop(event) {
|
||||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,11 +31,12 @@ export default class AncestrySheet extends DHHeritageSheet {
|
||||||
if (data.type === 'ActiveEffect') return super._onDrop(event);
|
if (data.type === 'ActiveEffect') return super._onDrop(event);
|
||||||
|
|
||||||
const target = event.target.closest('fieldset.drop-section');
|
const target = event.target.closest('fieldset.drop-section');
|
||||||
|
if (target) {
|
||||||
const typeField =
|
const typeField =
|
||||||
this.document.system[target.dataset.type === 'primary' ? 'primaryFeature' : 'secondaryFeature'];
|
this.document.system[target.dataset.type === 'primary' ? 'primaryFeature' : 'secondaryFeature'];
|
||||||
|
|
||||||
if (!typeField) {
|
if (!typeField) {
|
||||||
super._onDrop(event);
|
super._onDrop(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,15 @@ export default class ArmorSheet extends ItemAttachmentSheet(DHBaseItemSheet) {
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateArmorEffect(event) {
|
||||||
|
const value = Number.parseInt(event.target.value);
|
||||||
|
const armorEffect = this.document.system.armorEffect;
|
||||||
|
if (Number.isNaN(value) || !armorEffect) return;
|
||||||
|
|
||||||
|
await armorEffect.system.armorChange.updateArmorMax(value);
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback function used by `tagifyElement`.
|
* Callback function used by `tagifyElement`.
|
||||||
* @param {Array<Object>} selectedOptions - The currently selected tag objects.
|
* @param {Array<Object>} selectedOptions - The currently selected tag objects.
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,7 @@ export default class BeastformSheet extends DHBaseItemSheet {
|
||||||
|
|
||||||
async advantageOnRemove(event) {
|
async advantageOnRemove(event) {
|
||||||
await this.document.update({
|
await this.document.update({
|
||||||
[`system.advantageOn.-=${event.detail.data.value}`]: null
|
[`system.advantageOn.${event.detail.data.value}`]: _del
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -104,9 +104,10 @@ export default class ClassSheet extends DHBaseItemSheet {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**@inheritdoc */
|
/**@inheritdoc */
|
||||||
async _prepareContext(_options) {
|
async _prepareContext(options) {
|
||||||
const context = await super._prepareContext(_options);
|
const context = await super._prepareContext(options);
|
||||||
context.domains = this.document.system.domains;
|
context.domains = this.document.system.domains;
|
||||||
|
context.subclasses = await this.document.system.fetchSubclasses();
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -128,20 +129,8 @@ export default class ClassSheet extends DHBaseItemSheet {
|
||||||
const item = await fromUuid(data.uuid);
|
const item = await fromUuid(data.uuid);
|
||||||
const itemType = data.type === 'ActiveEffect' ? data.type : item.type;
|
const itemType = data.type === 'ActiveEffect' ? data.type : item.type;
|
||||||
const target = event.target.closest('fieldset.drop-section');
|
const target = event.target.closest('fieldset.drop-section');
|
||||||
if (itemType === 'subclass') {
|
|
||||||
if (item.system.linkedClass) {
|
if (['feature', 'ActiveEffect'].includes(itemType)) {
|
||||||
return ui.notifications.warn(
|
|
||||||
game.i18n.format('DAGGERHEART.UI.Notifications.subclassAlreadyLinked', {
|
|
||||||
name: item.name,
|
|
||||||
class: this.document.name
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
await item.update({ 'system.linkedClass': this.document.uuid });
|
|
||||||
await this.document.update({
|
|
||||||
'system.subclasses': [...this.document.system.subclasses.map(x => x.uuid), item.uuid]
|
|
||||||
});
|
|
||||||
} else if (['feature', 'ActiveEffect'].includes(itemType)) {
|
|
||||||
super._onDrop(event);
|
super._onDrop(event);
|
||||||
} else if (this.document.parent?.type !== 'character') {
|
} else if (this.document.parent?.type !== 'character') {
|
||||||
if (itemType === 'weapon') {
|
if (itemType === 'weapon') {
|
||||||
|
|
@ -200,12 +189,6 @@ export default class ClassSheet extends DHBaseItemSheet {
|
||||||
static async #removeItemFromCollection(_event, element) {
|
static async #removeItemFromCollection(_event, element) {
|
||||||
const { uuid, target } = element.dataset;
|
const { uuid, target } = element.dataset;
|
||||||
const prop = foundry.utils.getProperty(this.document.system, target);
|
const prop = foundry.utils.getProperty(this.document.system, target);
|
||||||
|
|
||||||
if (target === 'subclasses') {
|
|
||||||
const subclass = await foundry.utils.fromUuid(uuid);
|
|
||||||
await subclass?.update({ 'system.linkedClass': null });
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.document.update({ [`system.${target}`]: prop.filter(i => i && i.uuid !== uuid).map(x => x.uuid) });
|
await this.document.update({ [`system.${target}`]: prop.filter(i => i && i.uuid !== uuid).map(x => x.uuid) });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ export default class FeatureSheet extends DHBaseItemSheet {
|
||||||
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
//Might be wrong location but testing out if here is okay.
|
//Might be wrong location but testing out if here is okay.
|
||||||
/**@override */
|
/**@override */
|
||||||
async _prepareContext(options) {
|
async _prepareContext(options) {
|
||||||
const context = await super._prepareContext(options);
|
const context = await super._prepareContext(options);
|
||||||
|
|
|
||||||
|
|
@ -40,4 +40,36 @@ export default class SubclassSheet extends DHBaseItemSheet {
|
||||||
get relatedDocs() {
|
get relatedDocs() {
|
||||||
return this.document.system.features.map(x => x.item);
|
return this.document.system.features.map(x => x.item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _prepareContext(options) {
|
||||||
|
const context = await super._prepareContext(options);
|
||||||
|
if (this.document.system.linkedClass) {
|
||||||
|
const classData = await fromUuid(this.document.system.linkedClass);
|
||||||
|
context.class = classData ?? {
|
||||||
|
name: _loc('DAGGERHEART.GENERAL.missingX', { x: _loc('TYPES.Item.class') }),
|
||||||
|
missing: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
async _onDrop(event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
const data = TextEditor.getDragEventData(event);
|
||||||
|
const item = await fromUuid(data.uuid);
|
||||||
|
const itemType = data.type === 'ActiveEffect' ? data.type : item.type;
|
||||||
|
if (itemType === 'class') {
|
||||||
|
const uuid = item._stats.compendiumSource ?? item.uuid;
|
||||||
|
if (this.document.system.linkedClass !== uuid) {
|
||||||
|
await this.document.update({ 'system.linkedClass': uuid });
|
||||||
|
// Re-render all class sheets for instant feedback
|
||||||
|
for (const app of foundry.applications.instances.values()) {
|
||||||
|
if (app.document?.type === 'class') app.render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super._onDrop(event);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -108,14 +108,15 @@ export default class DhRollTableSheet extends foundry.applications.sheets.RollTa
|
||||||
getSystemFlagUpdate() {
|
getSystemFlagUpdate() {
|
||||||
const deleteUpdate = Object.keys(this.document._source.flags.daggerheart?.altFormula ?? {}).reduce(
|
const deleteUpdate = Object.keys(this.document._source.flags.daggerheart?.altFormula ?? {}).reduce(
|
||||||
(acc, formulaKey) => {
|
(acc, formulaKey) => {
|
||||||
if (!this.daggerheartFlag.altFormula[formulaKey]) acc.altFormula[`-=${formulaKey}`] = null;
|
if (!this.daggerheartFlag.altFormula[formulaKey]) acc.altFormula[formulaKey] = _del;
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
},
|
},
|
||||||
{ altFormula: {} }
|
{ altFormula: {} }
|
||||||
);
|
);
|
||||||
|
|
||||||
return { ['flags.daggerheart']: foundry.utils.mergeObject(this.daggerheartFlag.toObject(), deleteUpdate) };
|
const flagData = this.daggerheartFlag.toObject();
|
||||||
|
return { ...flagData, altFormula: { ...flagData.altFormula, ...deleteUpdate.altFormula } };
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #addFormula() {
|
static async #addFormula() {
|
||||||
|
|
@ -127,7 +128,7 @@ export default class DhRollTableSheet extends foundry.applications.sheets.RollTa
|
||||||
|
|
||||||
static async #removeFormula(_event, target) {
|
static async #removeFormula(_event, target) {
|
||||||
await this.daggerheartFlag.updateSource({
|
await this.daggerheartFlag.updateSource({
|
||||||
[`altFormula.-=${target.dataset.key}`]: null
|
[`altFormula.${target.dataset.key}`]: _del
|
||||||
});
|
});
|
||||||
this.render({ internalRefresh: true });
|
this.render({ internalRefresh: true });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,52 +1,19 @@
|
||||||
export default class DhSidebar extends foundry.applications.sidebar.Sidebar {
|
export default class DhSidebar extends foundry.applications.sidebar.Sidebar {
|
||||||
/** @override */
|
static buildTabs() {
|
||||||
static TABS = {
|
const { settings, ...tabs } = super.TABS;
|
||||||
chat: {
|
return {
|
||||||
documentName: 'ChatMessage'
|
...tabs,
|
||||||
},
|
|
||||||
combat: {
|
|
||||||
documentName: 'Combat'
|
|
||||||
},
|
|
||||||
scenes: {
|
|
||||||
documentName: 'Scene',
|
|
||||||
gmOnly: true
|
|
||||||
},
|
|
||||||
actors: {
|
|
||||||
documentName: 'Actor'
|
|
||||||
},
|
|
||||||
items: {
|
|
||||||
documentName: 'Item'
|
|
||||||
},
|
|
||||||
journal: {
|
|
||||||
documentName: 'JournalEntry',
|
|
||||||
tooltip: 'SIDEBAR.TabJournal'
|
|
||||||
},
|
|
||||||
tables: {
|
|
||||||
documentName: 'RollTable'
|
|
||||||
},
|
|
||||||
cards: {
|
|
||||||
documentName: 'Cards'
|
|
||||||
},
|
|
||||||
macros: {
|
|
||||||
documentName: 'Macro'
|
|
||||||
},
|
|
||||||
playlists: {
|
|
||||||
documentName: 'Playlist'
|
|
||||||
},
|
|
||||||
compendium: {
|
|
||||||
tooltip: 'SIDEBAR.TabCompendium',
|
|
||||||
icon: 'fa-solid fa-book-atlas'
|
|
||||||
},
|
|
||||||
daggerheartMenu: {
|
daggerheartMenu: {
|
||||||
tooltip: 'DAGGERHEART.UI.Sidebar.daggerheartMenu.title',
|
tooltip: 'DAGGERHEART.UI.Sidebar.daggerheartMenu.title',
|
||||||
img: 'systems/daggerheart/assets/logos/FoundryBorneLogoWhite.svg',
|
img: 'systems/daggerheart/assets/logos/FoundryBorneLogoWhite.svg',
|
||||||
gmOnly: true
|
gmOnly: true
|
||||||
},
|
},
|
||||||
settings: {
|
settings
|
||||||
tooltip: 'SIDEBAR.TabSettings',
|
|
||||||
icon: 'fa-solid fa-gears'
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static TABS = DhSidebar.buildTabs();
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
static PARTS = {
|
static PARTS = {
|
||||||
|
|
|
||||||
|
|
@ -46,10 +46,11 @@ export default class DhActorDirectory extends foundry.applications.sidebar.tabs.
|
||||||
|
|
||||||
_getEntryContextOptions() {
|
_getEntryContextOptions() {
|
||||||
const options = super._getEntryContextOptions();
|
const options = super._getEntryContextOptions();
|
||||||
options.push({
|
options.push(
|
||||||
name: 'DAGGERHEART.UI.Sidebar.actorDirectory.duplicateToNewTier',
|
{
|
||||||
|
label: 'DAGGERHEART.UI.Sidebar.actorDirectory.duplicateToNewTier',
|
||||||
icon: `<i class="fa-solid fa-arrow-trend-up" inert></i>`,
|
icon: `<i class="fa-solid fa-arrow-trend-up" inert></i>`,
|
||||||
condition: li => {
|
visible: li => {
|
||||||
const actor = game.actors.get(li.dataset.entryId);
|
const actor = game.actors.get(li.dataset.entryId);
|
||||||
return actor?.type === 'adversary' && actor.system.type !== 'social';
|
return actor?.type === 'adversary' && actor.system.type !== 'social';
|
||||||
},
|
},
|
||||||
|
|
@ -76,7 +77,7 @@ export default class DhActorDirectory extends foundry.applications.sidebar.tabs.
|
||||||
window: { title: 'DAGGERHEART.UI.Sidebar.actorDirectory.pickTierTitle' },
|
window: { title: 'DAGGERHEART.UI.Sidebar.actorDirectory.pickTierTitle' },
|
||||||
content,
|
content,
|
||||||
ok: {
|
ok: {
|
||||||
label: 'Create Adversary',
|
label: 'DAGGERHEART.UI.Sidebar.actorDirectory.createAdversary',
|
||||||
callback: (event, button, dialog) => Number(button.form.elements.tier.value)
|
callback: (event, button, dialog) => Number(button.form.elements.tier.value)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -89,7 +90,23 @@ export default class DhActorDirectory extends foundry.applications.sidebar.tabs.
|
||||||
ui.notifications.info(`Tier ${tier} ${actor.name} created`);
|
ui.notifications.info(`Tier ${tier} ${actor.name} created`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
{
|
||||||
|
label: 'DAGGERHEART.UI.Sidebar.actorDirectory.activateParty',
|
||||||
|
icon: `<i class="fa-regular fa-square"></i>`,
|
||||||
|
visible: li => {
|
||||||
|
const actor = game.actors.get(li.dataset.entryId);
|
||||||
|
return actor && actor.type === 'party' && !actor.system.active;
|
||||||
|
},
|
||||||
|
callback: async li => {
|
||||||
|
const actor = game.actors.get(li.dataset.entryId);
|
||||||
|
if (!actor) throw new Error('Unexpected missing actor');
|
||||||
|
|
||||||
|
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.ActiveParty, actor.id);
|
||||||
|
ui.actors.render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,8 @@ export default class DaggerheartMenu extends HandlebarsApplicationMixin(Abstract
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
selectRefreshable: DaggerheartMenu.#selectRefreshable,
|
selectRefreshable: DaggerheartMenu.#selectRefreshable,
|
||||||
refreshActors: DaggerheartMenu.#refreshActors
|
refreshActors: DaggerheartMenu.#refreshActors,
|
||||||
|
createFallCollisionDamage: DaggerheartMenu.#createFallCollisionDamage
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -50,6 +51,7 @@ export default class DaggerheartMenu extends HandlebarsApplicationMixin(Abstract
|
||||||
const context = await super._prepareContext(options);
|
const context = await super._prepareContext(options);
|
||||||
context.refreshables = this.refreshSelections;
|
context.refreshables = this.refreshSelections;
|
||||||
context.disableRefresh = Object.values(this.refreshSelections).every(x => !x.selected);
|
context.disableRefresh = Object.values(this.refreshSelections).every(x => !x.selected);
|
||||||
|
context.fallAndCollision = CONFIG.DH.GENERAL.fallAndCollisionDamage;
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
@ -71,4 +73,22 @@ export default class DaggerheartMenu extends HandlebarsApplicationMixin(Abstract
|
||||||
this.refreshSelections = DaggerheartMenu.defaultRefreshSelections();
|
this.refreshSelections = DaggerheartMenu.defaultRefreshSelections();
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async #createFallCollisionDamage(_event, button) {
|
||||||
|
const data = CONFIG.DH.GENERAL.fallAndCollisionDamage[button.dataset.key];
|
||||||
|
const roll = new Roll(data.damageFormula);
|
||||||
|
await roll.evaluate();
|
||||||
|
|
||||||
|
/* class BaseRoll needed to get rendered by foundryRoll.hbs */
|
||||||
|
const rollJSON = roll.toJSON();
|
||||||
|
rollJSON.class = 'BaseRoll';
|
||||||
|
|
||||||
|
foundry.documents.ChatMessage.implementation.create({
|
||||||
|
title: game.i18n.localize(data.chatTitle),
|
||||||
|
author: game.user.id,
|
||||||
|
speaker: foundry.documents.ChatMessage.implementation.getSpeaker(),
|
||||||
|
rolls: [rollJSON],
|
||||||
|
sound: CONFIG.sounds.dice
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,3 +7,4 @@ export { default as DhFearTracker } from './fearTracker.mjs';
|
||||||
export { default as DhHotbar } from './hotbar.mjs';
|
export { default as DhHotbar } from './hotbar.mjs';
|
||||||
export { default as DhSceneNavigation } from './sceneNavigation.mjs';
|
export { default as DhSceneNavigation } from './sceneNavigation.mjs';
|
||||||
export { ItemBrowser } from './itemBrowser.mjs';
|
export { ItemBrowser } from './itemBrowser.mjs';
|
||||||
|
export { default as DhProgress } from './progress.mjs';
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { abilities } from '../../config/actorConfig.mjs';
|
import { enrichedDualityRoll } from '../../enrichers/DualityRollEnricher.mjs';
|
||||||
import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
import { enrichedFateRoll, getFateTypeData } from '../../enrichers/FateRollEnricher.mjs';
|
||||||
|
import { getCommandTarget, rollCommandToJSON } from '../../helpers/utils.mjs';
|
||||||
|
|
||||||
export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLog {
|
export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLog {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
|
|
@ -21,35 +22,114 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
classes: ['daggerheart']
|
classes: ['daggerheart']
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static CHAT_COMMANDS = {
|
||||||
|
...super.CHAT_COMMANDS,
|
||||||
|
dr: {
|
||||||
|
rgx: /^(?:\/dr)((?:\s)[^]*)?/,
|
||||||
|
fn: (_, match) => {
|
||||||
|
const argString = match[1]?.trim();
|
||||||
|
const result = argString ? rollCommandToJSON(argString) : { result: {} };
|
||||||
|
if (!result) {
|
||||||
|
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.dualityParsing'));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { result: rollCommand, flavor } = result;
|
||||||
|
|
||||||
|
const reaction = rollCommand.reaction;
|
||||||
|
const traitValue = rollCommand.trait?.toLowerCase();
|
||||||
|
const advantage = rollCommand.advantage
|
||||||
|
? CONFIG.DH.ACTIONS.advantageState.advantage.value
|
||||||
|
: rollCommand.disadvantage
|
||||||
|
? CONFIG.DH.ACTIONS.advantageState.disadvantage.value
|
||||||
|
: undefined;
|
||||||
|
const difficulty = rollCommand.difficulty;
|
||||||
|
const grantResources = rollCommand.grantResources;
|
||||||
|
|
||||||
|
const target = getCommandTarget({ allowNull: true });
|
||||||
|
const title =
|
||||||
|
(flavor ?? traitValue)
|
||||||
|
? game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
||||||
|
ability: game.i18n.localize(CONFIG.DH.ACTOR.abilities[traitValue].label)
|
||||||
|
})
|
||||||
|
: game.i18n.localize('DAGGERHEART.GENERAL.duality');
|
||||||
|
|
||||||
|
enrichedDualityRoll({
|
||||||
|
reaction,
|
||||||
|
traitValue,
|
||||||
|
target,
|
||||||
|
difficulty,
|
||||||
|
title,
|
||||||
|
label: game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll'),
|
||||||
|
actionType: null,
|
||||||
|
advantage,
|
||||||
|
grantResources
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fr: {
|
||||||
|
rgx: /^(?:\/fr)((?:\s)[^]*)?/,
|
||||||
|
fn: (_, match) => {
|
||||||
|
const argString = match[1]?.trim();
|
||||||
|
const result = argString ? rollCommandToJSON(argString) : { result: {} };
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.fateParsing'));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { result: rollCommand, flavor } = result;
|
||||||
|
const fateTypeData = getFateTypeData(rollCommand?.type);
|
||||||
|
|
||||||
|
if (!fateTypeData)
|
||||||
|
return ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.fateTypeParsing'));
|
||||||
|
|
||||||
|
const { value: fateType, label: fateTypeLabel } = fateTypeData;
|
||||||
|
const target = getCommandTarget({ allowNull: true });
|
||||||
|
const title = flavor ?? game.i18n.localize('DAGGERHEART.GENERAL.fateRoll');
|
||||||
|
|
||||||
|
enrichedFateRoll({
|
||||||
|
target,
|
||||||
|
title,
|
||||||
|
label: fateTypeLabel,
|
||||||
|
fateType
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
_getEntryContextOptions() {
|
_getEntryContextOptions() {
|
||||||
return [
|
return [
|
||||||
...super._getEntryContextOptions(),
|
...super._getEntryContextOptions(),
|
||||||
// {
|
|
||||||
// name: 'Reroll',
|
|
||||||
// icon: '<i class="fa-solid fa-dice"></i>',
|
|
||||||
// condition: li => {
|
|
||||||
// const message = game.messages.get(li.dataset.messageId);
|
|
||||||
|
|
||||||
// return (game.user.isGM || message.isAuthor) && message.rolls.length > 0;
|
|
||||||
// },
|
|
||||||
// callback: li => {
|
|
||||||
// const message = game.messages.get(li.dataset.messageId);
|
|
||||||
// new game.system.api.applications.dialogs.RerollDialog(message).render({ force: true });
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
name: game.i18n.localize('DAGGERHEART.UI.ChatLog.rerollDamage'),
|
label: 'DAGGERHEART.UI.ChatLog.rerollActionRoll',
|
||||||
icon: '<i class="fa-solid fa-dice"></i>',
|
icon: '<i class="fa-solid fa-dice"></i>',
|
||||||
condition: li => {
|
visible: li => {
|
||||||
|
const message = game.messages.get(li.dataset.messageId);
|
||||||
|
return message.system.hasRoll && (game.user.isGM || message.isAuthor);
|
||||||
|
},
|
||||||
|
callback: async li => {
|
||||||
|
const message = game.messages.get(li.dataset.messageId);
|
||||||
|
const reroll = await message.rolls[0].reroll({ liveRoll: true });
|
||||||
|
message.update({ rolls: [reroll] });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'DAGGERHEART.UI.ChatLog.rerollDamage',
|
||||||
|
icon: '<i class="fa-solid fa-dice"></i>',
|
||||||
|
visible: li => {
|
||||||
const message = game.messages.get(li.dataset.messageId);
|
const message = game.messages.get(li.dataset.messageId);
|
||||||
const hasRolledDamage = message.system.hasDamage
|
const hasRolledDamage = message.system.hasDamage
|
||||||
? Object.keys(message.system.damage).length > 0
|
? Object.keys(message.system.damage).length > 0
|
||||||
: false;
|
: false;
|
||||||
return (game.user.isGM || message.isAuthor) && hasRolledDamage;
|
return (game.user.isGM || message.isAuthor) && hasRolledDamage;
|
||||||
},
|
},
|
||||||
callback: li => {
|
callback: async li => {
|
||||||
const message = game.messages.get(li.dataset.messageId);
|
const message = game.messages.get(li.dataset.messageId);
|
||||||
new game.system.api.applications.dialogs.RerollDamageDialog(message).render({ force: true });
|
const update = await message.system.getRerolledDamage();
|
||||||
|
message.update(update);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
@ -69,18 +149,6 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
html.querySelectorAll('.reroll-button').forEach(element =>
|
html.querySelectorAll('.reroll-button').forEach(element =>
|
||||||
element.addEventListener('click', event => this.rerollEvent(event, message))
|
element.addEventListener('click', event => this.rerollEvent(event, message))
|
||||||
);
|
);
|
||||||
html.querySelectorAll('.group-roll-button').forEach(element =>
|
|
||||||
element.addEventListener('click', event => this.groupRollButton(event, message))
|
|
||||||
);
|
|
||||||
html.querySelectorAll('.group-roll-reroll').forEach(element =>
|
|
||||||
element.addEventListener('click', event => this.groupRollReroll(event, message))
|
|
||||||
);
|
|
||||||
html.querySelectorAll('.group-roll-success').forEach(element =>
|
|
||||||
element.addEventListener('click', event => this.groupRollSuccessEvent(event, message))
|
|
||||||
);
|
|
||||||
html.querySelectorAll('.group-roll-header-expand-section').forEach(element =>
|
|
||||||
element.addEventListener('click', this.groupRollExpandSection)
|
|
||||||
);
|
|
||||||
html.querySelectorAll('.risk-it-all-button').forEach(element =>
|
html.querySelectorAll('.risk-it-all-button').forEach(element =>
|
||||||
element.addEventListener('click', event => this.riskItAllClearStressAndHitPoints(event, data))
|
element.addEventListener('click', event => this.riskItAllClearStressAndHitPoints(event, data))
|
||||||
);
|
);
|
||||||
|
|
@ -175,7 +243,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
action.use(event);
|
action.use(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
async rerollEvent(event, message) {
|
async rerollEvent(event, messageData) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
if (!event.shiftKey) {
|
if (!event.shiftKey) {
|
||||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||||
|
|
@ -187,206 +255,41 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
if (!confirmed) return;
|
if (!confirmed) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const message = game.messages.get(messageData._id);
|
||||||
const target = event.target.closest('[data-die-index]');
|
const target = event.target.closest('[data-die-index]');
|
||||||
|
|
||||||
if (target.dataset.type === 'damage') {
|
if (target.dataset.type === 'damage') {
|
||||||
game.system.api.dice.DamageRoll.reroll(target, message);
|
const { damageType, part, dice, result } = target.dataset;
|
||||||
|
const damagePart = message.system.damage[damageType].parts[part];
|
||||||
|
const { parsedRoll, rerolledDice } = await game.system.api.dice.DamageRoll.reroll(damagePart, dice, result);
|
||||||
|
const damageParts = message.system.damage[damageType].parts.map((damagePart, index) => {
|
||||||
|
if (index !== Number(part)) return damagePart;
|
||||||
|
return {
|
||||||
|
...damagePart,
|
||||||
|
total: parsedRoll.total,
|
||||||
|
dice: rerolledDice
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const updateMessage = game.messages.get(message._id);
|
||||||
|
await updateMessage.update({
|
||||||
|
[`system.damage.${damageType}`]: {
|
||||||
|
total: parsedRoll.total,
|
||||||
|
parts: damageParts
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
let originalRoll_parsed = message.rolls.map(roll => JSON.parse(roll))[0];
|
const rerollDice = message.system.roll.dice[target.dataset.dieIndex];
|
||||||
const rollClass =
|
await rerollDice.reroll(`/r1=${rerollDice.total}`, {
|
||||||
game.system.api.dice[
|
liveRoll: {
|
||||||
message.type === 'dualityRoll'
|
roll: message.system.roll,
|
||||||
? 'DualityRoll'
|
actor: message.system.actionActor,
|
||||||
: target.dataset.type === 'damage'
|
isReaction: message.system.roll.options.actionType === 'reaction'
|
||||||
? 'DHRoll'
|
|
||||||
: 'D20Roll'
|
|
||||||
];
|
|
||||||
|
|
||||||
if (!game.modules.get('dice-so-nice')?.active) foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice });
|
|
||||||
|
|
||||||
const { newRoll, parsedRoll } = await rollClass.reroll(originalRoll_parsed, target, message);
|
|
||||||
|
|
||||||
await game.messages.get(message._id).update({
|
|
||||||
'system.roll': newRoll,
|
|
||||||
'rolls': [parsedRoll]
|
|
||||||
});
|
|
||||||
|
|
||||||
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.TagTeamRoll });
|
|
||||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
|
||||||
action: socketEvent.Refresh,
|
|
||||||
data: {
|
|
||||||
refreshType: RefreshType.TagTeamRoll
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
await message.update({
|
||||||
}
|
rolls: [message.system.roll.toJSON()]
|
||||||
|
|
||||||
async groupRollButton(event, message) {
|
|
||||||
const path = event.currentTarget.dataset.path;
|
|
||||||
const isLeader = path === 'leader';
|
|
||||||
const { actor: actorData, trait } = foundry.utils.getProperty(message.system, path);
|
|
||||||
const actor = game.actors.get(actorData._id);
|
|
||||||
|
|
||||||
if (!actor) {
|
|
||||||
return ui.notifications.error(
|
|
||||||
game.i18n.format('DAGGERHEART.UI.Notifications.documentIsMissing', {
|
|
||||||
documentType: game.i18n.localize('TYPES.Actor.character')
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!actor.testUserPermission(game.user, 'OWNER')) {
|
|
||||||
return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.noActorOwnership'));
|
|
||||||
}
|
|
||||||
|
|
||||||
const traitLabel = game.i18n.localize(abilities[trait].label);
|
|
||||||
const config = {
|
|
||||||
event: event,
|
|
||||||
title: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${actor.name}`,
|
|
||||||
headerTitle: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
|
||||||
ability: traitLabel
|
|
||||||
}),
|
|
||||||
roll: {
|
|
||||||
trait: trait,
|
|
||||||
advantage: 0,
|
|
||||||
modifiers: [{ label: traitLabel, value: actor.system.traits[trait].value }]
|
|
||||||
},
|
|
||||||
hasRoll: true,
|
|
||||||
skips: {
|
|
||||||
createMessage: true,
|
|
||||||
resources: !isLeader,
|
|
||||||
updateCountdowns: !isLeader
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const result = await actor.diceRoll({
|
|
||||||
...config,
|
|
||||||
headerTitle: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${actor.name}`,
|
|
||||||
title: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
|
||||||
ability: traitLabel
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!result) return;
|
|
||||||
|
|
||||||
const newMessageData = foundry.utils.deepClone(message.system);
|
|
||||||
foundry.utils.setProperty(newMessageData, `${path}.result`, result.roll);
|
|
||||||
const renderData = { system: new game.system.api.models.chatMessages.config.groupRoll(newMessageData) };
|
|
||||||
|
|
||||||
const updatedContent = await foundry.applications.handlebars.renderTemplate(
|
|
||||||
'systems/daggerheart/templates/ui/chat/groupRoll.hbs',
|
|
||||||
{ ...renderData, user: game.user }
|
|
||||||
);
|
|
||||||
const mess = game.messages.get(message._id);
|
|
||||||
|
|
||||||
await emitAsGM(
|
|
||||||
GMUpdateEvent.UpdateDocument,
|
|
||||||
mess.update.bind(mess),
|
|
||||||
{
|
|
||||||
...renderData,
|
|
||||||
content: updatedContent
|
|
||||||
},
|
|
||||||
mess.uuid
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async groupRollReroll(event, message) {
|
|
||||||
const path = event.currentTarget.dataset.path;
|
|
||||||
const { actor: actorData, trait } = foundry.utils.getProperty(message.system, path);
|
|
||||||
const actor = game.actors.get(actorData._id);
|
|
||||||
|
|
||||||
if (!actor.testUserPermission(game.user, 'OWNER')) {
|
|
||||||
return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.noActorOwnership'));
|
|
||||||
}
|
|
||||||
|
|
||||||
const traitLabel = game.i18n.localize(abilities[trait].label);
|
|
||||||
|
|
||||||
const config = {
|
|
||||||
event: event,
|
|
||||||
title: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${actor.name}`,
|
|
||||||
headerTitle: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
|
||||||
ability: traitLabel
|
|
||||||
}),
|
|
||||||
roll: {
|
|
||||||
trait: trait,
|
|
||||||
advantage: 0,
|
|
||||||
modifiers: [{ label: traitLabel, value: actor.system.traits[trait].value }]
|
|
||||||
},
|
|
||||||
hasRoll: true,
|
|
||||||
skips: {
|
|
||||||
createMessage: true,
|
|
||||||
updateCountdowns: true
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const result = await actor.diceRoll({
|
|
||||||
...config,
|
|
||||||
headerTitle: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${actor.name}`,
|
|
||||||
title: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
|
||||||
ability: traitLabel
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
const newMessageData = foundry.utils.deepClone(message.system);
|
|
||||||
foundry.utils.setProperty(newMessageData, `${path}.result`, { ...result.roll, rerolled: true });
|
|
||||||
const renderData = { system: new game.system.api.models.chatMessages.config.groupRoll(newMessageData) };
|
|
||||||
|
|
||||||
const updatedContent = await foundry.applications.handlebars.renderTemplate(
|
|
||||||
'systems/daggerheart/templates/ui/chat/groupRoll.hbs',
|
|
||||||
{ ...renderData, user: game.user }
|
|
||||||
);
|
|
||||||
const mess = game.messages.get(message._id);
|
|
||||||
await emitAsGM(
|
|
||||||
GMUpdateEvent.UpdateDocument,
|
|
||||||
mess.update.bind(mess),
|
|
||||||
{
|
|
||||||
...renderData,
|
|
||||||
content: updatedContent
|
|
||||||
},
|
|
||||||
mess.uuid
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async groupRollSuccessEvent(event, message) {
|
|
||||||
if (!game.user.isGM) {
|
|
||||||
return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.gmOnly'));
|
|
||||||
}
|
|
||||||
|
|
||||||
const { path, success } = event.currentTarget.dataset;
|
|
||||||
const { actor: actorData } = foundry.utils.getProperty(message.system, path);
|
|
||||||
const actor = game.actors.get(actorData._id);
|
|
||||||
|
|
||||||
if (!actor.testUserPermission(game.user, 'OWNER')) {
|
|
||||||
return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.noActorOwnership'));
|
|
||||||
}
|
|
||||||
|
|
||||||
const newMessageData = foundry.utils.deepClone(message.system);
|
|
||||||
foundry.utils.setProperty(newMessageData, `${path}.manualSuccess`, Boolean(success));
|
|
||||||
const renderData = { system: new game.system.api.models.chatMessages.config.groupRoll(newMessageData) };
|
|
||||||
|
|
||||||
const updatedContent = await foundry.applications.handlebars.renderTemplate(
|
|
||||||
'systems/daggerheart/templates/ui/chat/groupRoll.hbs',
|
|
||||||
{ ...renderData, user: game.user }
|
|
||||||
);
|
|
||||||
const mess = game.messages.get(message._id);
|
|
||||||
await emitAsGM(
|
|
||||||
GMUpdateEvent.UpdateDocument,
|
|
||||||
mess.update.bind(mess),
|
|
||||||
{
|
|
||||||
...renderData,
|
|
||||||
content: updatedContent
|
|
||||||
},
|
|
||||||
mess.uuid
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async groupRollExpandSection(event) {
|
|
||||||
event.target
|
|
||||||
.closest('.group-roll-header-expand-section')
|
|
||||||
.querySelectorAll('i')
|
|
||||||
.forEach(element => {
|
|
||||||
element.classList.toggle('fa-angle-up');
|
|
||||||
element.classList.toggle('fa-angle-down');
|
|
||||||
});
|
|
||||||
event.target.closest('.group-roll-section').querySelector('.group-roll-content').classList.toggle('closed');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async riskItAllClearStressAndHitPoints(event, data) {
|
async riskItAllClearStressAndHitPoints(event, data) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
import { AdversaryBPPerEncounter } from '../../config/encounterConfig.mjs';
|
import { AdversaryBPPerEncounter } from '../../config/encounterConfig.mjs';
|
||||||
|
import { expireActiveEffects } from '../../helpers/utils.mjs';
|
||||||
|
import { clearPreviousSpotlight } from '../../macros/spotlightCombatant.mjs';
|
||||||
|
|
||||||
export default class DhCombatTracker extends foundry.applications.sidebar.tabs.CombatTracker {
|
export default class DhCombatTracker extends foundry.applications.sidebar.tabs.CombatTracker {
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
|
|
@ -54,7 +56,9 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
||||||
async _prepareTrackerContext(context, options) {
|
async _prepareTrackerContext(context, options) {
|
||||||
await super._prepareTrackerContext(context, options);
|
await super._prepareTrackerContext(context, options);
|
||||||
|
|
||||||
const adversaries = context.turns?.filter(x => x.isNPC) ?? [];
|
const npcs = context.turns?.filter(x => x.isNPC) ?? [];
|
||||||
|
const adversaries = npcs.filter(x => x.disposition !== CONST.TOKEN_DISPOSITIONS.FRIENDLY);
|
||||||
|
const friendlies = npcs.filter(x => x.disposition === CONST.TOKEN_DISPOSITIONS.FRIENDLY);
|
||||||
const characters = context.turns?.filter(x => !x.isNPC) ?? [];
|
const characters = context.turns?.filter(x => !x.isNPC) ?? [];
|
||||||
const spotlightQueueEnabled = game.settings.get(
|
const spotlightQueueEnabled = game.settings.get(
|
||||||
CONFIG.DH.id,
|
CONFIG.DH.id,
|
||||||
|
|
@ -73,25 +77,56 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
||||||
Object.assign(context, {
|
Object.assign(context, {
|
||||||
actionTokens: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules).actionTokens,
|
actionTokens: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules).actionTokens,
|
||||||
adversaries,
|
adversaries,
|
||||||
|
friendlies,
|
||||||
allCharacters: characters,
|
allCharacters: characters,
|
||||||
characters: characters.filter(x => !spotlightQueueEnabled || x.system.spotlight.requestOrderIndex == 0),
|
characters: characters.filter(x => !spotlightQueueEnabled || x.system.spotlight.requestOrderIndex == 0),
|
||||||
spotlightRequests
|
spotlightRequests
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open the dialog used to edit the name of the currently viewed Combat encounter.
|
||||||
|
* @this {CombatTracker}
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
static async #onEditName() {
|
||||||
|
const combat = this.viewed;
|
||||||
|
if (!combat || !game.user.isGM) return null;
|
||||||
|
const field = combat.schema.fields.name;
|
||||||
|
const inputHTML = field.toFormGroup({}, { name: 'name', value: combat.name, autofocus: true }).outerHTML;
|
||||||
|
const formData = await foundry.applications.api.DialogV2.input({
|
||||||
|
window: { icon: 'fa-solid fa-tag', title: 'COMBAT.ACTIONS.EditNameTitle' },
|
||||||
|
position: { width: 480 },
|
||||||
|
content: inputHTML
|
||||||
|
});
|
||||||
|
await combat.update({ name: formData.name || '' });
|
||||||
|
}
|
||||||
|
|
||||||
_getCombatContextOptions() {
|
_getCombatContextOptions() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
name: 'COMBAT.ClearMovementHistories',
|
label: 'COMBAT.ACTIONS.EditName',
|
||||||
icon: '<i class="fa-solid fa-shoe-prints"></i>',
|
icon: 'fa-solid fa-tag',
|
||||||
condition: () => game.user.isGM && this.viewed?.combatants.size > 0,
|
visible: () => game.user.isGM && !!this.viewed,
|
||||||
callback: () => this.viewed.clearMovementHistories()
|
onClick: () => DhCombatTracker.#onEditName.call(this)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'COMBAT.Delete',
|
label: 'COMBAT.ACTIONS.LinkToScene',
|
||||||
icon: '<i class="fa-solid fa-trash"></i>',
|
icon: '<i class="fa-solid fa-link"></i>',
|
||||||
condition: () => game.user.isGM && !!this.viewed,
|
visible: () => game.user.isGM && !this.scene,
|
||||||
callback: () => this.viewed.endCombat()
|
onClick: () => this.viewed.toggleSceneLink()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'COMBAT.ACTIONS.UnlinkFromScene',
|
||||||
|
icon: '<i class="fa-solid fa-unlink"></i>',
|
||||||
|
visible: () => game.user.isGM && !!this.scene,
|
||||||
|
onClick: () => this.viewed.toggleSceneLink()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'COMBAT.End',
|
||||||
|
icon: 'fa-solid fa-xmark',
|
||||||
|
visible: () => game.user.isGM && !!this.viewed,
|
||||||
|
onClick: () => this.viewed.endCombat()
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
@ -127,7 +162,8 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
||||||
active: index === combat.turn,
|
active: index === combat.turn,
|
||||||
canPing: combatant.sceneId === canvas.scene?.id && game.user.hasPermission('PING_CANVAS'),
|
canPing: combatant.sceneId === canvas.scene?.id && game.user.hasPermission('PING_CANVAS'),
|
||||||
type: combatant.actor?.system?.type,
|
type: combatant.actor?.system?.type,
|
||||||
img: await this._getCombatantThumbnail(combatant)
|
img: await this._getCombatantThumbnail(combatant),
|
||||||
|
disposition: combatant.token?.disposition
|
||||||
};
|
};
|
||||||
|
|
||||||
turn.css = [turn.active ? 'active' : null, hidden ? 'hide' : null, isDefeated ? 'defeated' : null].filterJoin(
|
turn.css = [turn.active ? 'active' : null, hidden ? 'hide' : null, isDefeated ? 'defeated' : null].filterJoin(
|
||||||
|
|
@ -149,13 +185,13 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
||||||
}
|
}
|
||||||
|
|
||||||
async setCombatantSpotlight(combatantId) {
|
async setCombatantSpotlight(combatantId) {
|
||||||
|
const combatant = this.viewed.combatants.get(combatantId);
|
||||||
const update = {
|
const update = {
|
||||||
system: {
|
system: {
|
||||||
'spotlight.requesting': false,
|
'spotlight.requesting': false,
|
||||||
'spotlight.requestOrderIndex': 0
|
'spotlight.requestOrderIndex': 0
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const combatant = this.viewed.combatants.get(combatantId);
|
|
||||||
|
|
||||||
const toggleTurn = this.viewed.combatants.contents
|
const toggleTurn = this.viewed.combatants.contents
|
||||||
.sort(this.viewed._sortCombatants)
|
.sort(this.viewed._sortCombatants)
|
||||||
|
|
@ -177,6 +213,8 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
||||||
if (autoPoints) {
|
if (autoPoints) {
|
||||||
update.system.actionTokens = Math.max(combatant.system.actionTokens - 1, 0);
|
update.system.actionTokens = Math.max(combatant.system.actionTokens - 1, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (combatant.actor) expireActiveEffects(combatant.actor, [CONFIG.DH.GENERAL.activeEffectDurations.act.id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.viewed.update({
|
await this.viewed.update({
|
||||||
|
|
@ -184,6 +222,14 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
||||||
round: this.viewed.round + 1
|
round: this.viewed.round + 1
|
||||||
});
|
});
|
||||||
await combatant.update(update);
|
await combatant.update(update);
|
||||||
|
if (combatant.token) clearPreviousSpotlight();
|
||||||
|
}
|
||||||
|
|
||||||
|
async clearTurn() {
|
||||||
|
await this.viewed.update({
|
||||||
|
turn: null,
|
||||||
|
round: this.viewed.round + 1
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static async requestSpotlight(_, target) {
|
static async requestSpotlight(_, target) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { DhCountdown } from '../../data/countdowns.mjs';
|
import { DhCountdown } from '../../data/countdowns.mjs';
|
||||||
import { waitForDiceSoNice } from '../../helpers/utils.mjs';
|
import { waitForDiceSoNice } from '../../helpers/utils.mjs';
|
||||||
import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
import { emitGMUpdate, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
||||||
|
|
@ -114,7 +114,7 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.data.updateSource(update);
|
await this.data.updateSource(update);
|
||||||
await emitAsGM(GMUpdateEvent.UpdateCountdowns, this.gmSetSetting.bind(this.data), this.data, null, {
|
await emitGMUpdate(GMUpdateEvent.UpdateCountdowns, this.gmSetSetting.bind(this.data), this.data, null, {
|
||||||
refreshType: RefreshType.Countdown
|
refreshType: RefreshType.Countdown
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -148,7 +148,7 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio
|
||||||
}
|
}
|
||||||
|
|
||||||
async gmSetSetting(data) {
|
async gmSetSetting(data) {
|
||||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, data),
|
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, data);
|
||||||
game.socket.emit(`system.${CONFIG.DH.id}`, {
|
game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||||
action: socketEvent.Refresh,
|
action: socketEvent.Refresh,
|
||||||
data: { refreshType: RefreshType.Countdown }
|
data: { refreshType: RefreshType.Countdown }
|
||||||
|
|
@ -233,6 +233,6 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.editingCountdowns.has(countdownId)) this.editingCountdowns.delete(countdownId);
|
if (this.editingCountdowns.has(countdownId)) this.editingCountdowns.delete(countdownId);
|
||||||
this.updateSetting({ [`countdowns.-=${countdownId}`]: null });
|
this.updateSetting({ [`countdowns.${countdownId}`]: _del });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { waitForDiceSoNice } from '../../helpers/utils.mjs';
|
import { waitForDiceSoNice } from '../../helpers/utils.mjs';
|
||||||
import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
import { emitGMUpdate, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
||||||
|
|
@ -21,19 +21,19 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
id: 'countdowns',
|
id: 'countdowns',
|
||||||
tag: 'div',
|
tag: 'div',
|
||||||
classes: ['daggerheart', 'dh-style', 'countdowns', 'faded-ui'],
|
classes: ['daggerheart', 'dh-style', 'countdowns'],
|
||||||
window: {
|
window: {
|
||||||
icon: 'fa-solid fa-clock-rotate-left',
|
icon: 'fa-solid fa-clock-rotate-left',
|
||||||
frame: true,
|
frame: false,
|
||||||
title: 'DAGGERHEART.UI.Countdowns.title',
|
title: 'DAGGERHEART.UI.Countdowns.title',
|
||||||
positioned: false,
|
positioned: false,
|
||||||
resizable: false,
|
resizable: false,
|
||||||
minimizable: false
|
minimizable: false
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
toggleViewMode: DhCountdowns.#toggleViewMode,
|
toggleViewMode: DhCountdowns.#onToggleViewMode,
|
||||||
editCountdowns: DhCountdowns.#editCountdowns,
|
editCountdowns: DhCountdowns.#onEditCountdowns,
|
||||||
loopCountdown: DhCountdowns.#loopCountdown,
|
loopCountdown: DhCountdowns.#onLoopCountdown,
|
||||||
decreaseCountdown: (_, target) => this.editCountdown(false, target),
|
decreaseCountdown: (_, target) => this.editCountdown(false, target),
|
||||||
increaseCountdown: (_, target) => this.editCountdown(true, target)
|
increaseCountdown: (_, target) => this.editCountdown(true, target)
|
||||||
},
|
},
|
||||||
|
|
@ -52,10 +52,6 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
get element() {
|
|
||||||
return document.body.querySelector('.daggerheart.dh-style.countdowns');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**@inheritdoc */
|
/**@inheritdoc */
|
||||||
async _renderFrame(options) {
|
async _renderFrame(options) {
|
||||||
const frame = await super._renderFrame(options);
|
const frame = await super._renderFrame(options);
|
||||||
|
|
@ -66,19 +62,6 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
||||||
if (iconOnly) frame.classList.add('icon-only');
|
if (iconOnly) frame.classList.add('icon-only');
|
||||||
else frame.classList.remove('icon-only');
|
else frame.classList.remove('icon-only');
|
||||||
|
|
||||||
const header = frame.querySelector('.window-header');
|
|
||||||
header.querySelector('button[data-action="close"]').remove();
|
|
||||||
|
|
||||||
if (game.user.isGM) {
|
|
||||||
const editTooltip = game.i18n.localize('DAGGERHEART.APPLICATIONS.CountdownEdit.editTitle');
|
|
||||||
const editButton = `<a style="margin-right: 8px;" class="header-control" data-tooltip="${editTooltip}" aria-label="${editTooltip}" data-action="editCountdowns"><i class="fa-solid fa-wrench"></i></a>`;
|
|
||||||
header.insertAdjacentHTML('beforeEnd', editButton);
|
|
||||||
}
|
|
||||||
|
|
||||||
const minimizeTooltip = game.i18n.localize('DAGGERHEART.UI.Countdowns.toggleIconMode');
|
|
||||||
const minimizeButton = `<a class="header-control" data-tooltip="${minimizeTooltip}" aria-label="${minimizeTooltip}" data-action="toggleViewMode"><i class="fa-solid fa-down-left-and-up-right-to-center"></i></a>`;
|
|
||||||
header.insertAdjacentHTML('beforeEnd', minimizeButton);
|
|
||||||
|
|
||||||
return frame;
|
return frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -140,6 +123,8 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
||||||
}
|
}
|
||||||
|
|
||||||
static #getPlayerOwnership(user, setting, countdown) {
|
static #getPlayerOwnership(user, setting, countdown) {
|
||||||
|
if (user.isGM) return CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER;
|
||||||
|
|
||||||
const playerOwnership = countdown.ownership[user.id];
|
const playerOwnership = countdown.ownership[user.id];
|
||||||
return playerOwnership === undefined || playerOwnership === CONST.DOCUMENT_OWNERSHIP_LEVELS.INHERIT
|
return playerOwnership === undefined || playerOwnership === CONST.DOCUMENT_OWNERSHIP_LEVELS.INHERIT
|
||||||
? setting.defaultOwnership
|
? setting.defaultOwnership
|
||||||
|
|
@ -162,7 +147,7 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #toggleViewMode() {
|
static async #onToggleViewMode() {
|
||||||
const currentMode = game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.countdownMode);
|
const currentMode = game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.countdownMode);
|
||||||
const appMode = CONFIG.DH.GENERAL.countdownAppMode;
|
const appMode = CONFIG.DH.GENERAL.countdownAppMode;
|
||||||
const newMode = currentMode === appMode.textIcon ? appMode.iconOnly : appMode.textIcon;
|
const newMode = currentMode === appMode.textIcon ? appMode.iconOnly : appMode.textIcon;
|
||||||
|
|
@ -173,15 +158,16 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #editCountdowns() {
|
static async #onEditCountdowns() {
|
||||||
new game.system.api.applications.ui.CountdownEdit().render(true);
|
new game.system.api.applications.ui.CountdownEdit().render(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #loopCountdown(_, target) {
|
static async #onLoopCountdown(_, target) {
|
||||||
if (!DhCountdowns.canPerformEdit()) return;
|
if (!DhCountdowns.canPerformEdit()) return;
|
||||||
|
|
||||||
const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||||
const countdown = settings.countdowns[target.id];
|
const countdownId = target.closest('[data-countdown]').dataset.countdown;
|
||||||
|
const countdown = settings.countdowns[countdownId];
|
||||||
|
|
||||||
let progressMax = countdown.progress.start;
|
let progressMax = countdown.progress.start;
|
||||||
let message = null;
|
let message = null;
|
||||||
|
|
@ -200,12 +186,12 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
||||||
|
|
||||||
await waitForDiceSoNice(message);
|
await waitForDiceSoNice(message);
|
||||||
await settings.updateSource({
|
await settings.updateSource({
|
||||||
[`countdowns.${target.id}.progress`]: {
|
[`countdowns.${countdownId}.progress`]: {
|
||||||
current: newMax,
|
current: newMax,
|
||||||
start: newMax
|
start: newMax
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
await emitAsGM(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, {
|
await emitGMUpdate(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, {
|
||||||
refreshType: RefreshType.Countdown
|
refreshType: RefreshType.Countdown
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -214,18 +200,19 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
||||||
if (!DhCountdowns.canPerformEdit()) return;
|
if (!DhCountdowns.canPerformEdit()) return;
|
||||||
|
|
||||||
const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||||
const countdown = settings.countdowns[target.id];
|
const countdownId = target.closest('[data-countdown]').dataset.countdown;
|
||||||
|
const countdown = settings.countdowns[countdownId];
|
||||||
const newCurrent = increase
|
const newCurrent = increase
|
||||||
? Math.min(countdown.progress.current + 1, countdown.progress.start)
|
? Math.min(countdown.progress.current + 1, countdown.progress.start)
|
||||||
: Math.max(countdown.progress.current - 1, 0);
|
: Math.max(countdown.progress.current - 1, 0);
|
||||||
await settings.updateSource({ [`countdowns.${target.id}.progress.current`]: newCurrent });
|
await settings.updateSource({ [`countdowns.${countdownId}.progress.current`]: newCurrent });
|
||||||
await emitAsGM(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, {
|
await emitGMUpdate(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, {
|
||||||
refreshType: RefreshType.Countdown
|
refreshType: RefreshType.Countdown
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static async gmSetSetting(data) {
|
static async gmSetSetting(data) {
|
||||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, data),
|
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, data);
|
||||||
game.socket.emit(`system.${CONFIG.DH.id}`, {
|
game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||||
action: socketEvent.Refresh,
|
action: socketEvent.Refresh,
|
||||||
data: { refreshType: RefreshType.Countdown }
|
data: { refreshType: RefreshType.Countdown }
|
||||||
|
|
@ -278,9 +265,7 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
||||||
return acc;
|
return acc;
|
||||||
}, {})
|
}, {})
|
||||||
};
|
};
|
||||||
await emitAsGM(GMUpdateEvent.UpdateCountdowns,
|
await emitGMUpdate(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, {
|
||||||
DhCountdowns.gmSetSetting.bind(settings),
|
|
||||||
settings, null, {
|
|
||||||
refreshType: RefreshType.Countdown
|
refreshType: RefreshType.Countdown
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { getIconVisibleActiveEffects } from '../../helpers/utils.mjs';
|
||||||
import { RefreshType } from '../../systemRegistration/socket.mjs';
|
import { RefreshType } from '../../systemRegistration/socket.mjs';
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
@ -48,11 +49,9 @@ export default class DhEffectsDisplay extends HandlebarsApplicationMixin(Applica
|
||||||
|
|
||||||
_attachPartListeners(partId, htmlElement, options) {
|
_attachPartListeners(partId, htmlElement, options) {
|
||||||
super._attachPartListeners(partId, htmlElement, options);
|
super._attachPartListeners(partId, htmlElement, options);
|
||||||
|
for (const element of this.element?.querySelectorAll('.effect-container a') ?? []) {
|
||||||
if (this.element) {
|
element.addEventListener('click', e => this.#onClickEffect(e));
|
||||||
this.element.querySelectorAll('.effect-container a').forEach(element => {
|
element.addEventListener('contextmenu', e => this.#onClickEffect(e, -1));
|
||||||
element.addEventListener('contextmenu', this.removeEffect.bind(this));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -72,7 +71,7 @@ export default class DhEffectsDisplay extends HandlebarsApplicationMixin(Applica
|
||||||
? game.user.character
|
? game.user.character
|
||||||
: null
|
: null
|
||||||
: canvas.tokens.controlled[0].actor;
|
: canvas.tokens.controlled[0].actor;
|
||||||
return actor?.getActiveEffects() ?? [];
|
return getIconVisibleActiveEffects(actor?.getActiveEffects() ?? []);
|
||||||
};
|
};
|
||||||
|
|
||||||
toggleHidden(token, focused) {
|
toggleHidden(token, focused) {
|
||||||
|
|
@ -86,12 +85,23 @@ export default class DhEffectsDisplay extends HandlebarsApplicationMixin(Applica
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeEffect(event) {
|
async #onClickEffect(event, delta = 1) {
|
||||||
const element = event.target.closest('.effect-container');
|
const element = event.target.closest('.effect-container');
|
||||||
const effects = DhEffectsDisplay.getTokenEffects();
|
const effects = DhEffectsDisplay.getTokenEffects();
|
||||||
const effect = effects.find(x => x.id === element.dataset.effectId);
|
const effect = effects.find(x => x.id === element.dataset.effectId);
|
||||||
|
if (!effect || (delta >= 0 && !effect.system.stacking)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxValue = effect.system.stacking?.max ?? Infinity;
|
||||||
|
const newValue = Math.clamp((effect.system.stacking?.value ?? 1) + delta, 0, maxValue);
|
||||||
|
if (newValue > 0) {
|
||||||
|
await effect.update({ 'system.stacking.value': newValue });
|
||||||
|
} else {
|
||||||
await effect.delete();
|
await effect.delete();
|
||||||
}
|
}
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
setupHooks() {
|
setupHooks() {
|
||||||
Hooks.on('controlToken', this.toggleHidden.bind(this));
|
Hooks.on('controlToken', this.toggleHidden.bind(this));
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { emitAsGM, GMUpdateEvent } from '../../systemRegistration/socket.mjs';
|
import { emitGMUpdate, GMUpdateEvent } from '../../systemRegistration/socket.mjs';
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
||||||
|
|
@ -22,7 +22,7 @@ export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV
|
||||||
tag: 'div',
|
tag: 'div',
|
||||||
window: {
|
window: {
|
||||||
frame: true,
|
frame: true,
|
||||||
title: 'Fear',
|
title: 'DAGGERHEART.GENERAL.fear',
|
||||||
positioned: true,
|
positioned: true,
|
||||||
resizable: true,
|
resizable: true,
|
||||||
minimizable: false
|
minimizable: false
|
||||||
|
|
@ -104,7 +104,7 @@ export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateFear(value) {
|
async updateFear(value) {
|
||||||
return emitAsGM(
|
return emitGMUpdate(
|
||||||
GMUpdateEvent.UpdateFear,
|
GMUpdateEvent.UpdateFear,
|
||||||
game.settings.set.bind(game.settings, CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear),
|
game.settings.set.bind(game.settings, CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear),
|
||||||
value
|
value
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { getDocFromElement } from '../../helpers/utils.mjs';
|
||||||
import { RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
import { RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
@ -37,7 +38,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
tag: 'div',
|
tag: 'div',
|
||||||
window: {
|
window: {
|
||||||
frame: true,
|
frame: true,
|
||||||
title: 'Compendium Browser',
|
title: 'DAGGERHEART.UI.ItemBrowser.windowTitle',
|
||||||
icon: 'fa-solid fa-book-atlas',
|
icon: 'fa-solid fa-book-atlas',
|
||||||
positioned: true,
|
positioned: true,
|
||||||
resizable: true
|
resizable: true
|
||||||
|
|
@ -47,7 +48,8 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
expandContent: this.expandContent,
|
expandContent: this.expandContent,
|
||||||
resetFilters: this.resetFilters,
|
resetFilters: this.resetFilters,
|
||||||
sortList: this.sortList,
|
sortList: this.sortList,
|
||||||
openSettings: this.openSettings
|
openSettings: this.openSettings,
|
||||||
|
viewSheet: this.#onViewSheet
|
||||||
},
|
},
|
||||||
position: {
|
position: {
|
||||||
left: 100,
|
left: 100,
|
||||||
|
|
@ -109,8 +111,8 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
CONFIG.DH.id,
|
CONFIG.DH.id,
|
||||||
CONFIG.DH.FLAGS[`${this.compendiumBrowserTypeKey}`].position
|
CONFIG.DH.FLAGS[`${this.compendiumBrowserTypeKey}`].position
|
||||||
);
|
);
|
||||||
|
|
||||||
options.position = userPresetPosition ?? ItemBrowser.DEFAULT_OPTIONS.position;
|
options.position = userPresetPosition ?? ItemBrowser.DEFAULT_OPTIONS.position;
|
||||||
|
delete options.position.zIndex;
|
||||||
|
|
||||||
if (!userPresetPosition) {
|
if (!userPresetPosition) {
|
||||||
const width = noFolder === true || lite === true ? 600 : 850;
|
const width = noFolder === true || lite === true ? 600 : 850;
|
||||||
|
|
@ -207,8 +209,23 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
label: game.i18n.localize(col.label)
|
label: game.i18n.localize(col.label)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const splitPath = folderId?.split('.') ?? [];
|
||||||
|
const { pathLabels } = splitPath.reduce(
|
||||||
|
(acc, curr) => {
|
||||||
|
acc.currentPath = !acc.currentPath ? curr : [acc.currentPath, curr].join('.');
|
||||||
|
if (curr === 'folder') return acc;
|
||||||
|
|
||||||
|
const label = foundry.utils.getProperty(this.config, acc.currentPath)?.label;
|
||||||
|
if (label) acc.pathLabels.push(game.i18n.localize(label));
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{ pathLabels: [], currentPath: '' }
|
||||||
|
);
|
||||||
|
|
||||||
this.selectedMenu = {
|
this.selectedMenu = {
|
||||||
path: folderId?.split('.') ?? [],
|
path: splitPath,
|
||||||
|
pathLabels: pathLabels,
|
||||||
data: {
|
data: {
|
||||||
...folderData,
|
...folderData,
|
||||||
columns: columns
|
columns: columns
|
||||||
|
|
@ -262,7 +279,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
(await foundry.applications.ux.TextEditor.implementation.enrichHTML(item.description));
|
(await foundry.applications.ux.TextEditor.implementation.enrichHTML(item.description));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.fieldFilter = this._createFieldFilter();
|
this.fieldFilter = await this._createFieldFilter();
|
||||||
|
|
||||||
if (this.presets?.filter) {
|
if (this.presets?.filter) {
|
||||||
Object.entries(this.presets.filter).forEach(([k, v]) => {
|
Object.entries(this.presets.filter).forEach(([k, v]) => {
|
||||||
|
|
@ -291,7 +308,8 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
{
|
{
|
||||||
items: this.items,
|
items: this.items,
|
||||||
menu: this.selectedMenu,
|
menu: this.selectedMenu,
|
||||||
formatLabel: this.formatLabel
|
formatLabel: this.formatLabel,
|
||||||
|
viewSheet: this.items[0] instanceof Actor
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -340,12 +358,12 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_createFieldFilter() {
|
async _createFieldFilter() {
|
||||||
const filters = ItemBrowser.getFolderConfig(this.selectedMenu.data, 'filters');
|
const filters = ItemBrowser.getFolderConfig(this.selectedMenu.data, 'filters');
|
||||||
filters.forEach(f => {
|
for (const f of filters) {
|
||||||
if (typeof f.field === 'string') f.field = foundry.utils.getProperty(game, f.field);
|
if (typeof f.field === 'string') f.field = foundry.utils.getProperty(game, f.field);
|
||||||
else if (typeof f.choices === 'function') {
|
else if (typeof f.choices === 'function') {
|
||||||
f.choices = f.choices(this.items);
|
f.choices = await f.choices(this.items);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear field label so template uses our custom label parameter
|
// Clear field label so template uses our custom label parameter
|
||||||
|
|
@ -355,7 +373,8 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
|
|
||||||
f.name ??= f.key;
|
f.name ??= f.key;
|
||||||
f.value = this.presets?.filter?.[f.name]?.value ?? null;
|
f.value = this.presets?.filter?.[f.name]?.value ?? null;
|
||||||
});
|
}
|
||||||
|
|
||||||
return filters;
|
return filters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -552,6 +571,11 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async #onViewSheet(_, target) {
|
||||||
|
const document = await getDocFromElement(target);
|
||||||
|
document?.sheet?.render(true);
|
||||||
|
}
|
||||||
|
|
||||||
_createDragProcess() {
|
_createDragProcess() {
|
||||||
new foundry.applications.ux.DragDrop.implementation({
|
new foundry.applications.ux.DragDrop.implementation({
|
||||||
dragSelector: '.item-container',
|
dragSelector: '.item-container',
|
||||||
|
|
@ -568,7 +592,9 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
const { itemUuid } = event.target.closest('[data-item-uuid]').dataset,
|
const { itemUuid } = event.target.closest('[data-item-uuid]').dataset,
|
||||||
item = await foundry.utils.fromUuid(itemUuid),
|
item = await foundry.utils.fromUuid(itemUuid),
|
||||||
dragData = item.toDragData();
|
dragData = item.toDragData();
|
||||||
|
|
||||||
event.dataTransfer.setData('text/plain', JSON.stringify(dragData));
|
event.dataTransfer.setData('text/plain', JSON.stringify(dragData));
|
||||||
|
event.dataTransfer.setDragImage(event.target.querySelector('img'), 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
_canDragStart() {
|
_canDragStart() {
|
||||||
|
|
@ -588,7 +614,16 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
items: {
|
items: {
|
||||||
folder: 'equipments',
|
folder: 'equipments',
|
||||||
render: {
|
render: {
|
||||||
noFolder: true
|
folders: [
|
||||||
|
'equipments',
|
||||||
|
'ancestries',
|
||||||
|
'classes',
|
||||||
|
'subclasses',
|
||||||
|
'domains',
|
||||||
|
'communities',
|
||||||
|
'beastforms'
|
||||||
|
// excluded: features
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
compendium: {}
|
compendium: {}
|
||||||
|
|
|
||||||
27
module/applications/ui/progress.mjs
Normal file
27
module/applications/ui/progress.mjs
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
export default class DhProgress {
|
||||||
|
#notification;
|
||||||
|
|
||||||
|
constructor({ max, label = '' }) {
|
||||||
|
this.max = max;
|
||||||
|
this.label = label;
|
||||||
|
this.#notification = ui.notifications.info(this.label, { progress: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
updateMax(newMax) {
|
||||||
|
this.max = newMax;
|
||||||
|
}
|
||||||
|
|
||||||
|
advance({ by = 1, label = this.label } = {}) {
|
||||||
|
if (this.value === this.max) return;
|
||||||
|
this.value = (this.value ?? 0) + Math.abs(by);
|
||||||
|
this.#notification.update({ message: label, pct: this.value / this.max });
|
||||||
|
}
|
||||||
|
|
||||||
|
close({ label = '' } = {}) {
|
||||||
|
this.#notification.update({ message: label, pct: 1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
static createMigrationProgress(max = 0) {
|
||||||
|
return new DhProgress({ max, label: game.i18n.localize('DAGGERHEART.UI.Progress.migrationLabel') });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { emitAsGM, GMUpdateEvent } from '../../systemRegistration/socket.mjs';
|
import { emitGMUpdate, GMUpdateEvent } from '../../systemRegistration/socket.mjs';
|
||||||
|
|
||||||
export default class DhSceneNavigation extends foundry.applications.ui.SceneNavigation {
|
export default class DhSceneNavigation extends foundry.applications.ui.SceneNavigation {
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
|
|
@ -31,7 +31,7 @@ export default class DhSceneNavigation extends foundry.applications.ui.SceneNavi
|
||||||
const environments = daggerheartInfo.sceneEnvironments.filter(
|
const environments = daggerheartInfo.sceneEnvironments.filter(
|
||||||
x => x && x.testUserPermission(game.user, 'LIMITED')
|
x => x && x.testUserPermission(game.user, 'LIMITED')
|
||||||
);
|
);
|
||||||
const hasEnvironments = environments.length > 0 && x.isView;
|
const hasEnvironments = environments.length > 0 && x.active;
|
||||||
return {
|
return {
|
||||||
...x,
|
...x,
|
||||||
hasEnvironments,
|
hasEnvironments,
|
||||||
|
|
@ -39,9 +39,10 @@ export default class DhSceneNavigation extends foundry.applications.ui.SceneNavi
|
||||||
environments: environments
|
environments: environments
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
context.scenes.active = extendScenes(context.scenes.active);
|
context.scenes.active = extendScenes(context.scenes.active);
|
||||||
context.scenes.inactive = extendScenes(context.scenes.inactive);
|
context.scenes.inactive = extendScenes(context.scenes.inactive);
|
||||||
|
context.scenes.viewed = context.scenes.viewed ? extendScenes([context.scenes.viewed])[0] : null;
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -67,7 +68,7 @@ export default class DhSceneNavigation extends foundry.applications.ui.SceneNavi
|
||||||
1
|
1
|
||||||
)[0];
|
)[0];
|
||||||
newEnvironments.unshift(newFirst);
|
newEnvironments.unshift(newFirst);
|
||||||
emitAsGM(
|
emitGMUpdate(
|
||||||
GMUpdateEvent.UpdateDocument,
|
GMUpdateEvent.UpdateDocument,
|
||||||
scene.update.bind(scene),
|
scene.update.bind(scene),
|
||||||
{ 'flags.daggerheart.sceneEnvironments': newEnvironments },
|
{ 'flags.daggerheart.sceneEnvironments': newEnvironments },
|
||||||
|
|
|
||||||
|
|
@ -1,97 +1,4 @@
|
||||||
/**
|
|
||||||
* @typedef ContextMenuEntry
|
|
||||||
* @property {string} name The context menu label. Can be localized.
|
|
||||||
* @property {string} [icon] A string containing an HTML icon element for the menu item.
|
|
||||||
* @property {string} [classes] Additional CSS classes to apply to this menu item.
|
|
||||||
* @property {string} [group] An identifier for a group this entry belongs to.
|
|
||||||
* @property {ContextMenuJQueryCallback} callback The function to call when the menu item is clicked.
|
|
||||||
* @property {ContextMenuCondition|boolean} [condition] A function to call or boolean value to determine if this entry
|
|
||||||
* appears in the menu.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @callback ContextMenuCondition
|
|
||||||
* @param {jQuery|HTMLElement} html The element of the context menu entry.
|
|
||||||
* @returns {boolean} Whether the entry should be rendered in the context menu.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @callback ContextMenuCallback
|
|
||||||
* @param {HTMLElement} target The element that the context menu has been triggered for.
|
|
||||||
* @returns {unknown}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @callback ContextMenuJQueryCallback
|
|
||||||
* @param {HTMLElement|jQuery} target The element that the context menu has been triggered for. Will
|
|
||||||
* either be a jQuery object or an HTMLElement instance, depending
|
|
||||||
* on how the ContextMenu was configured.
|
|
||||||
* @returns {unknown}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef ContextMenuOptions
|
|
||||||
* @property {string} [eventName="contextmenu"] Optionally override the triggering event which can spawn the menu. If
|
|
||||||
* the menu is using fixed positioning, this event must be a MouseEvent.
|
|
||||||
* @property {ContextMenuCallback} [onOpen] A function to call when the context menu is opened.
|
|
||||||
* @property {ContextMenuCallback} [onClose] A function to call when the context menu is closed.
|
|
||||||
* @property {boolean} [fixed=false] If true, the context menu is given a fixed position rather than being
|
|
||||||
* injected into the target.
|
|
||||||
* @property {boolean} [jQuery=true] If true, callbacks will be passed jQuery objects instead of HTMLElement
|
|
||||||
* instances.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef ContextMenuRenderOptions
|
|
||||||
* @property {Event} [event] The event that triggered the context menu opening.
|
|
||||||
* @property {boolean} [animate=true] Animate the context menu opening.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A subclass of ContextMenu.
|
|
||||||
* @extends {foundry.applications.ux.ContextMenu}
|
|
||||||
*/
|
|
||||||
export default class DHContextMenu extends foundry.applications.ux.ContextMenu {
|
export default class DHContextMenu extends foundry.applications.ux.ContextMenu {
|
||||||
/**
|
|
||||||
* @param {HTMLElement|jQuery} container - The HTML element that contains the context menu targets.
|
|
||||||
* @param {string} selector - A CSS selector which activates the context menu.
|
|
||||||
* @param {ContextMenuEntry[]} menuItems - An Array of entries to display in the menu
|
|
||||||
* @param {ContextMenuOptions} [options] - Additional options to configure the context menu.
|
|
||||||
*/
|
|
||||||
constructor(container, selector, menuItems, options) {
|
|
||||||
super(container, selector, menuItems, options);
|
|
||||||
|
|
||||||
/** @deprecated since v13 until v15 */
|
|
||||||
this.#jQuery = options.jQuery;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether to pass jQuery objects or HTMLElement instances to callback.
|
|
||||||
* @type {boolean}
|
|
||||||
*/
|
|
||||||
#jQuery;
|
|
||||||
|
|
||||||
/**@inheritdoc */
|
|
||||||
activateListeners(menu) {
|
|
||||||
menu.addEventListener('click', this.#onClickItem.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle click events on context menu items.
|
|
||||||
* @param {PointerEvent} event The click event
|
|
||||||
*/
|
|
||||||
#onClickItem(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
const element = event.target.closest('.context-item');
|
|
||||||
if (!element) return;
|
|
||||||
const item = this.menuItems.find(i => i.element === element);
|
|
||||||
item?.callback(this.#jQuery ? $(this.target) : this.target, event);
|
|
||||||
this.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Trigger a context menu event in response to a normal click on a additional options button.
|
* Trigger a context menu event in response to a normal click on a additional options button.
|
||||||
* @param {PointerEvent} event
|
* @param {PointerEvent} event
|
||||||
|
|
@ -99,8 +6,11 @@ export default class DHContextMenu extends foundry.applications.ux.ContextMenu {
|
||||||
static triggerContextMenu(event, altSelector) {
|
static triggerContextMenu(event, altSelector) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const { clientX, clientY } = event;
|
|
||||||
const selector = altSelector ?? '[data-item-uuid]';
|
const selector = altSelector ?? '[data-item-uuid]';
|
||||||
|
if (ui.context?.selector === selector) return;
|
||||||
|
|
||||||
|
const { clientX, clientY } = event;
|
||||||
const target = event.target.closest(selector) ?? event.currentTarget.closest(selector);
|
const target = event.target.closest(selector) ?? event.currentTarget.closest(selector);
|
||||||
target?.dispatchEvent(
|
target?.dispatchEvent(
|
||||||
new PointerEvent('contextmenu', {
|
new PointerEvent('contextmenu', {
|
||||||
|
|
|
||||||
|
|
@ -188,7 +188,7 @@ export default class FilterMenu extends foundry.applications.ux.ContextMenu {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const damageTypeFilter = Object.values(CONFIG.DH.GENERAL.damageTypes).map(({ id, abbreviation }) => ({
|
const damageTypeFilter = Object.values(CONFIG.DH.GENERAL.damageTypes).map(({ id, abbreviation }) => ({
|
||||||
group: 'Damage Type', //TODO localize
|
group: game.i18n.localize('DAGGERHEART.GENERAL.damageType'),
|
||||||
name: game.i18n.localize(abbreviation),
|
name: game.i18n.localize(abbreviation),
|
||||||
filter: {
|
filter: {
|
||||||
field: 'system.damage.type',
|
field: 'system.damage.type',
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
export { default as DhMeasuredTemplate } from './measuredTemplate.mjs';
|
export { default as DhMeasuredTemplate } from './measuredTemplate.mjs';
|
||||||
export { default as DhRuler } from './ruler.mjs';
|
export { default as DhRuler } from './ruler.mjs';
|
||||||
export { default as DhTemplateLayer } from './templateLayer.mjs';
|
export { default as DhRegion } from './region.mjs';
|
||||||
|
export { default as DhRegionLayer } from './regionLayer.mjs';
|
||||||
export { default as DhTokenPlaceable } from './token.mjs';
|
export { default as DhTokenPlaceable } from './token.mjs';
|
||||||
export { default as DhTokenRuler } from './tokenRuler.mjs';
|
export { default as DhTokenRuler } from './tokenRuler.mjs';
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ export default class DhMeasuredTemplate extends foundry.canvas.placeables.Measur
|
||||||
|
|
||||||
static getRangeLabels(distanceValue, settings) {
|
static getRangeLabels(distanceValue, settings) {
|
||||||
let result = { distance: distanceValue, units: '' };
|
let result = { distance: distanceValue, units: '' };
|
||||||
if (!settings.enabled) return result;
|
if (!settings.enabled || !canvas.scene) return result;
|
||||||
|
|
||||||
const sceneRangeMeasurement = canvas.scene.flags.daggerheart?.rangeMeasurement;
|
const sceneRangeMeasurement = canvas.scene.flags.daggerheart?.rangeMeasurement;
|
||||||
const { disable, custom } = CONFIG.DH.GENERAL.sceneRangeMeasurementSetting;
|
const { disable, custom } = CONFIG.DH.GENERAL.sceneRangeMeasurementSetting;
|
||||||
|
|
|
||||||
12
module/canvas/placeables/region.mjs
Normal file
12
module/canvas/placeables/region.mjs
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import DhMeasuredTemplate from './measuredTemplate.mjs';
|
||||||
|
|
||||||
|
export default class DhRegion extends foundry.canvas.placeables.Region {
|
||||||
|
/**@inheritdoc */
|
||||||
|
_formatMeasuredDistance(distance) {
|
||||||
|
const range = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules).rangeMeasurement;
|
||||||
|
if (!range.enabled) return super._formatMeasuredDistance(distance);
|
||||||
|
|
||||||
|
const { distance: resultDistance, units } = DhMeasuredTemplate.getRangeLabels(distance, range);
|
||||||
|
return `${resultDistance} ${units}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
155
module/canvas/placeables/regionLayer.mjs
Normal file
155
module/canvas/placeables/regionLayer.mjs
Normal file
|
|
@ -0,0 +1,155 @@
|
||||||
|
export default class DhRegionLayer extends foundry.canvas.layers.RegionLayer {
|
||||||
|
static prepareSceneControls() {
|
||||||
|
const sc = foundry.applications.ui.SceneControls;
|
||||||
|
const { tools, ...rest } = super.prepareSceneControls();
|
||||||
|
|
||||||
|
return {
|
||||||
|
...rest,
|
||||||
|
tools: {
|
||||||
|
select: tools.select,
|
||||||
|
templateMode: tools.templateMode,
|
||||||
|
rectangle: tools.rectangle,
|
||||||
|
circle: tools.circle,
|
||||||
|
ellipse: tools.ellipse,
|
||||||
|
cone: tools.cone,
|
||||||
|
inFront: {
|
||||||
|
name: 'inFront',
|
||||||
|
order: 7,
|
||||||
|
title: 'CONTROLS.inFront',
|
||||||
|
icon: 'fa-solid fa-eye',
|
||||||
|
toolclip: {
|
||||||
|
src: 'toolclips/tools/measure-cone.webm',
|
||||||
|
heading: 'CONTROLS.inFront',
|
||||||
|
items: sc.buildToolclipItems(['create', 'move', 'edit', 'hide', 'delete', 'rotate'])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ring: { ...tools.ring, order: 8 },
|
||||||
|
line: { ...tools.line, order: 9 },
|
||||||
|
emanation: { ...tools.emanation, order: 10 },
|
||||||
|
polygon: { ...tools.polygon, order: 11 },
|
||||||
|
hole: { ...tools.hole, order: 12 },
|
||||||
|
snap: { ...tools.snap, order: 13 },
|
||||||
|
clear: { ...tools.clear, order: 14 }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
_isCreationToolActive() {
|
||||||
|
return this.active && (game.activeTool === 'inFront' || game.activeTool in foundry.data.BaseShapeData.TYPES);
|
||||||
|
}
|
||||||
|
|
||||||
|
_createDragShapeData(event) {
|
||||||
|
const hole = ui.controls.controls[this.options.name].tools.hole?.active ?? false;
|
||||||
|
if (game.activeTool === 'inFront') return { type: 'cone', x: 0, y: 0, radius: 0, angle: 180, hole };
|
||||||
|
|
||||||
|
const shape = super._createDragShapeData(event);
|
||||||
|
const token =
|
||||||
|
shape?.type === 'emanation' && shape.base?.type === 'token'
|
||||||
|
? this.#findTokenInBounds(event.interactionData.origin)
|
||||||
|
: null;
|
||||||
|
if (token) {
|
||||||
|
shape.base.width = token.width;
|
||||||
|
shape.base.height = token.height;
|
||||||
|
event.interactionData.origin = token.getCenterPoint();
|
||||||
|
}
|
||||||
|
return shape;
|
||||||
|
}
|
||||||
|
|
||||||
|
async placeRegion(data, options = {}) {
|
||||||
|
const preConfirm = data => {
|
||||||
|
const shape = data.document.shapes[0];
|
||||||
|
const isEmanation = shape.type === 'emanation';
|
||||||
|
if (isEmanation) {
|
||||||
|
const token = this.#findTokenInBounds(shape.base.origin);
|
||||||
|
if (!token) return options.preConfirm?.(data) ?? true;
|
||||||
|
const shapeData = shape.toObject();
|
||||||
|
data.document.updateSource({
|
||||||
|
shapes: [
|
||||||
|
{
|
||||||
|
...shapeData,
|
||||||
|
base: {
|
||||||
|
...shapeData.base,
|
||||||
|
height: token.height,
|
||||||
|
width: token.width,
|
||||||
|
x: token.x,
|
||||||
|
y: token.y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return options?.preConfirm?.(data) ?? true;
|
||||||
|
};
|
||||||
|
|
||||||
|
return await super.placeRegion(data, { ...options, preConfirm });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Searches for token at origin point, returning null if there are no tokens or multiple overlapping tokens */
|
||||||
|
#findTokenInBounds(origin) {
|
||||||
|
const { x, y } = origin;
|
||||||
|
const gridSize = canvas.grid.size;
|
||||||
|
const inBounds = canvas.scene.tokens.filter(t => {
|
||||||
|
return x.between(t.x, t.x + t.width * gridSize) && y.between(t.y, t.y + t.height * gridSize);
|
||||||
|
});
|
||||||
|
return inBounds.length === 1 ? inBounds[0] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static getTemplateShape({ type, angle, range, direction } = {}) {
|
||||||
|
const { line, rectangle, inFront, cone, circle, emanation } = CONFIG.DH.GENERAL.templateTypes;
|
||||||
|
|
||||||
|
/* Length calculation */
|
||||||
|
const { grid, distance } = CONFIG.Scene.documentClass.schema.fields.grid.fields;
|
||||||
|
const sceneGridSize = canvas.scene?.grid.size ?? grid.size.initial;
|
||||||
|
const sceneGridDistance = canvas.scene?.grid.distance ?? distance.getInitialValue();
|
||||||
|
const dimensionConstant = sceneGridSize / sceneGridDistance;
|
||||||
|
|
||||||
|
const settings = canvas.scene?.rangeSettings;
|
||||||
|
const rangeNumber = Number(range);
|
||||||
|
const length = (!Number.isNaN(rangeNumber) ? rangeNumber : settings ? settings[range] : 0) * dimensionConstant;
|
||||||
|
/*----*/
|
||||||
|
|
||||||
|
const shapeData = {
|
||||||
|
...canvas.mousePosition,
|
||||||
|
type: type,
|
||||||
|
direction: direction ?? 0
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case rectangle.id:
|
||||||
|
shapeData.width = length;
|
||||||
|
shapeData.height = length;
|
||||||
|
break;
|
||||||
|
case line.id:
|
||||||
|
shapeData.length = length;
|
||||||
|
shapeData.width = 5 * dimensionConstant;
|
||||||
|
break;
|
||||||
|
case cone.id:
|
||||||
|
shapeData.angle = angle ?? CONFIG.MeasuredTemplate.defaults.angle;
|
||||||
|
shapeData.radius = length;
|
||||||
|
break;
|
||||||
|
case inFront.id:
|
||||||
|
shapeData.angle = '180';
|
||||||
|
shapeData.radius = length;
|
||||||
|
shapeData.type = cone.id;
|
||||||
|
break;
|
||||||
|
case circle.id:
|
||||||
|
shapeData.radius = length;
|
||||||
|
break;
|
||||||
|
case emanation.id:
|
||||||
|
shapeData.radius = length;
|
||||||
|
shapeData.base = {
|
||||||
|
type: 'token',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 1,
|
||||||
|
height: 1,
|
||||||
|
shape: game.canvas.grid.isHexagonal ? CONST.TOKEN_SHAPES.ELLIPSE_1 : CONST.TOKEN_SHAPES.RECTANGLE_1
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return shapeData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,116 +0,0 @@
|
||||||
export default class DhTemplateLayer extends foundry.canvas.layers.TemplateLayer {
|
|
||||||
static prepareSceneControls() {
|
|
||||||
const sc = foundry.applications.ui.SceneControls;
|
|
||||||
return {
|
|
||||||
name: 'templates',
|
|
||||||
order: 2,
|
|
||||||
title: 'CONTROLS.GroupMeasure',
|
|
||||||
icon: 'fa-solid fa-ruler-combined',
|
|
||||||
visible: game.user.can('TEMPLATE_CREATE'),
|
|
||||||
onChange: (event, active) => {
|
|
||||||
if (active) canvas.templates.activate();
|
|
||||||
},
|
|
||||||
onToolChange: () => canvas.templates.setAllRenderFlags({ refreshState: true }),
|
|
||||||
tools: {
|
|
||||||
circle: {
|
|
||||||
name: 'circle',
|
|
||||||
order: 1,
|
|
||||||
title: 'CONTROLS.MeasureCircle',
|
|
||||||
icon: 'fa-regular fa-circle',
|
|
||||||
toolclip: {
|
|
||||||
src: 'toolclips/tools/measure-circle.webm',
|
|
||||||
heading: 'CONTROLS.MeasureCircle',
|
|
||||||
items: sc.buildToolclipItems(['create', 'move', 'edit', 'hide', 'delete'])
|
|
||||||
}
|
|
||||||
},
|
|
||||||
cone: {
|
|
||||||
name: 'cone',
|
|
||||||
order: 2,
|
|
||||||
title: 'CONTROLS.MeasureCone',
|
|
||||||
icon: 'fa-solid fa-angle-left',
|
|
||||||
toolclip: {
|
|
||||||
src: 'toolclips/tools/measure-cone.webm',
|
|
||||||
heading: 'CONTROLS.MeasureCone',
|
|
||||||
items: sc.buildToolclipItems(['create', 'move', 'edit', 'hide', 'delete', 'rotate'])
|
|
||||||
}
|
|
||||||
},
|
|
||||||
inFront: {
|
|
||||||
name: 'inFront',
|
|
||||||
order: 3,
|
|
||||||
title: 'CONTROLS.inFront',
|
|
||||||
icon: 'fa-solid fa-eye',
|
|
||||||
toolclip: {
|
|
||||||
src: 'toolclips/tools/measure-cone.webm',
|
|
||||||
heading: 'CONTROLS.inFront',
|
|
||||||
items: sc.buildToolclipItems(['create', 'move', 'edit', 'hide', 'delete', 'rotate'])
|
|
||||||
}
|
|
||||||
},
|
|
||||||
rect: {
|
|
||||||
name: 'rect',
|
|
||||||
order: 4,
|
|
||||||
title: 'CONTROLS.MeasureRect',
|
|
||||||
icon: 'fa-regular fa-square',
|
|
||||||
toolclip: {
|
|
||||||
src: 'toolclips/tools/measure-rect.webm',
|
|
||||||
heading: 'CONTROLS.MeasureRect',
|
|
||||||
items: sc.buildToolclipItems(['create', 'move', 'edit', 'hide', 'delete', 'rotate'])
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ray: {
|
|
||||||
name: 'ray',
|
|
||||||
order: 5,
|
|
||||||
title: 'CONTROLS.MeasureRay',
|
|
||||||
icon: 'fa-solid fa-up-down',
|
|
||||||
toolclip: {
|
|
||||||
src: 'toolclips/tools/measure-ray.webm',
|
|
||||||
heading: 'CONTROLS.MeasureRay',
|
|
||||||
items: sc.buildToolclipItems(['create', 'move', 'edit', 'hide', 'delete', 'rotate'])
|
|
||||||
}
|
|
||||||
},
|
|
||||||
clear: {
|
|
||||||
name: 'clear',
|
|
||||||
order: 6,
|
|
||||||
title: 'CONTROLS.MeasureClear',
|
|
||||||
icon: 'fa-solid fa-trash',
|
|
||||||
visible: game.user.isGM,
|
|
||||||
onChange: () => canvas.templates.deleteAll(),
|
|
||||||
button: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
activeTool: 'circle'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
_onDragLeftStart(event) {
|
|
||||||
const interaction = event.interactionData;
|
|
||||||
|
|
||||||
// Snap the origin to the grid
|
|
||||||
if (!event.shiftKey) interaction.origin = this.getSnappedPoint(interaction.origin);
|
|
||||||
|
|
||||||
// Create a pending MeasuredTemplateDocument
|
|
||||||
const tool = game.activeTool === 'inFront' ? 'cone' : game.activeTool;
|
|
||||||
const previewData = {
|
|
||||||
user: game.user.id,
|
|
||||||
t: tool,
|
|
||||||
x: interaction.origin.x,
|
|
||||||
y: interaction.origin.y,
|
|
||||||
sort: Math.max(this.getMaxSort() + 1, 0),
|
|
||||||
distance: 1,
|
|
||||||
direction: 0,
|
|
||||||
fillColor: game.user.color || '#FF0000',
|
|
||||||
hidden: event.altKey
|
|
||||||
};
|
|
||||||
const defaults = CONFIG.MeasuredTemplate.defaults;
|
|
||||||
if (game.activeTool === 'cone') previewData.angle = defaults.angle;
|
|
||||||
else if (game.activeTool === 'inFront') previewData.angle = 180;
|
|
||||||
else if (game.activeTool === 'ray') previewData.width = defaults.width * canvas.dimensions.distance;
|
|
||||||
const cls = foundry.utils.getDocumentClass('MeasuredTemplate');
|
|
||||||
const doc = new cls(previewData, { parent: canvas.scene });
|
|
||||||
|
|
||||||
// Create a preview MeasuredTemplate object
|
|
||||||
const template = new this.constructor.placeableClass(doc);
|
|
||||||
doc._object = template;
|
|
||||||
interaction.preview = this.preview.addChild(template);
|
|
||||||
template.draw();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { getIconVisibleActiveEffects } from '../../helpers/utils.mjs';
|
||||||
import DhMeasuredTemplate from './measuredTemplate.mjs';
|
import DhMeasuredTemplate from './measuredTemplate.mjs';
|
||||||
|
|
||||||
export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
|
export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
|
||||||
|
|
@ -9,6 +10,36 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
|
||||||
this.previewHelp ||= this.addChild(this.#drawPreviewHelp());
|
this.previewHelp ||= this.addChild(this.#drawPreviewHelp());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**@inheritdoc */
|
||||||
|
_refreshTurnMarker() {
|
||||||
|
// Should a Turn Marker be active?
|
||||||
|
const { turnMarker } = this.document;
|
||||||
|
const markersEnabled =
|
||||||
|
CONFIG.Combat.settings.turnMarker.enabled && turnMarker.mode !== CONST.TOKEN_TURN_MARKER_MODES.DISABLED;
|
||||||
|
const spotlighted = game.settings
|
||||||
|
.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.SpotlightTracker)
|
||||||
|
.spotlightedTokens.has(this.document.uuid);
|
||||||
|
|
||||||
|
const turnIsSet = typeof game.combat?.turn === 'number';
|
||||||
|
const isTurn = game.combat?.combatant?.tokenId === this.id;
|
||||||
|
const markerActive = markersEnabled && turnIsSet ? isTurn : spotlighted;
|
||||||
|
|
||||||
|
// Activate a Turn Marker
|
||||||
|
if (markerActive) {
|
||||||
|
if (!this.turnMarker)
|
||||||
|
this.turnMarker = this.addChildAt(new foundry.canvas.placeables.tokens.TokenTurnMarker(this), 0);
|
||||||
|
canvas.tokens.turnMarkers.add(this);
|
||||||
|
this.turnMarker.draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove a Turn Marker
|
||||||
|
else if (this.turnMarker) {
|
||||||
|
canvas.tokens.turnMarkers.delete(this);
|
||||||
|
this.turnMarker.destroy();
|
||||||
|
this.turnMarker = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
async _drawEffects() {
|
async _drawEffects() {
|
||||||
this.effects.renderable = false;
|
this.effects.renderable = false;
|
||||||
|
|
@ -20,7 +51,7 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
|
||||||
this.effects.overlay = null;
|
this.effects.overlay = null;
|
||||||
|
|
||||||
// Categorize effects
|
// Categorize effects
|
||||||
const activeEffects = this.actor?.getActiveEffects() ?? [];
|
const activeEffects = getIconVisibleActiveEffects(Array.from(this.actor?.allApplicableEffects() ?? []));
|
||||||
const overlayEffect = activeEffects.findLast(e => e.img && e.getFlag?.('core', 'overlay'));
|
const overlayEffect = activeEffects.findLast(e => e.img && e.getFlag?.('core', 'overlay'));
|
||||||
|
|
||||||
// Draw effects
|
// Draw effects
|
||||||
|
|
@ -29,8 +60,8 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
|
||||||
if (!effect.img) continue;
|
if (!effect.img) continue;
|
||||||
const promise =
|
const promise =
|
||||||
effect === overlayEffect
|
effect === overlayEffect
|
||||||
? this._drawOverlay(effect.img, effect.tint)
|
? this._drawOverlay(effect.img, effect.tint, effect)
|
||||||
: this._drawEffect(effect.img, effect.tint);
|
: this._drawEffect(effect.img, effect.tint, effect);
|
||||||
promises.push(
|
promises.push(
|
||||||
promise.then(e => {
|
promise.then(e => {
|
||||||
if (e) e.zIndex = i;
|
if (e) e.zIndex = i;
|
||||||
|
|
@ -44,6 +75,39 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
|
||||||
this.renderFlags.set({ refreshEffects: true });
|
this.renderFlags.set({ refreshEffects: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**@inheritdoc */
|
||||||
|
async _drawEffect(src, tint, effect) {
|
||||||
|
if (!src) return;
|
||||||
|
const tex = await foundry.canvas.loadTexture(src, { fallback: 'icons/svg/hazard.svg' });
|
||||||
|
const icon = new PIXI.Sprite(tex);
|
||||||
|
icon.tint = tint ?? 0xffffff;
|
||||||
|
|
||||||
|
if (effect.system.stacking?.value > 1) {
|
||||||
|
const stackOverlay = new PIXI.Text(effect.system.stacking.value, {
|
||||||
|
fill: '#f3c267',
|
||||||
|
stroke: '#000000',
|
||||||
|
fontSize: 96,
|
||||||
|
strokeThickness: 4
|
||||||
|
});
|
||||||
|
const nrDigits = Math.floor(Math.log10(effect.system.stacking.value)) + 1;
|
||||||
|
stackOverlay.y = -8;
|
||||||
|
/* This does not account for 1:s being much less wide than other digits. I don't think it's desired however as it makes it look jumpy */
|
||||||
|
stackOverlay.x = icon.width - 8 - nrDigits * 56;
|
||||||
|
stackOverlay.anchor.set(0, 0);
|
||||||
|
|
||||||
|
icon.addChild(stackOverlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.effects.addChild(icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _drawOverlay(src, tint, effect) {
|
||||||
|
const icon = await this._drawEffect(src, tint, effect);
|
||||||
|
if (icon) icon.alpha = 0.8;
|
||||||
|
this.effects.overlay = icon ?? null;
|
||||||
|
return icon;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the distance from this token to another token object.
|
* Returns the distance from this token to another token object.
|
||||||
* This value is corrected to handle alternate token sizes and other grid types
|
* This value is corrected to handle alternate token sizes and other grid types
|
||||||
|
|
@ -185,9 +249,6 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
|
||||||
|
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
_drawBar(number, bar, data) {
|
_drawBar(number, bar, data) {
|
||||||
const val = Number(data.value);
|
|
||||||
const pct = Math.clamp(val, 0, data.max) / data.max;
|
|
||||||
|
|
||||||
// Determine sizing
|
// Determine sizing
|
||||||
const { width, height } = this.document.getSize();
|
const { width, height } = this.document.getSize();
|
||||||
const s = canvas.dimensions.uiScale;
|
const s = canvas.dimensions.uiScale;
|
||||||
|
|
@ -195,17 +256,19 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
|
||||||
const bh = 8 * (this.document.height >= 2 ? 1.5 : 1) * s;
|
const bh = 8 * (this.document.height >= 2 ? 1.5 : 1) * s;
|
||||||
|
|
||||||
// Determine the color to use
|
// Determine the color to use
|
||||||
const fillColor =
|
const Color = foundry.utils.Color;
|
||||||
number === 0 ? foundry.utils.Color.fromRGB([1, 0, 0]) : foundry.utils.Color.fromString('#0032b1');
|
const fillColor = number === 0 ? Color.fromRGB([1, 0, 0]) : Color.fromString('#0032b1');
|
||||||
|
const emptyColor = Color.fromRGB([0, 0, 0]);
|
||||||
|
|
||||||
// Draw the bar
|
// Draw the bar (accounting floating point numbers from bar animations)
|
||||||
const widthUnit = bw / data.max;
|
const widthUnit = bw / Math.ceil(data.max);
|
||||||
bar.clear().lineStyle(s, 0x000000, 1.0);
|
bar.clear().lineStyle(s, 0x000000, 1.0);
|
||||||
const sections = [...Array(data.max).keys()];
|
const sections = [...Array(Math.ceil(data.max)).keys()];
|
||||||
for (let mark of sections) {
|
for (const mark of sections) {
|
||||||
const x = mark * widthUnit;
|
const x = mark * widthUnit;
|
||||||
const marked = mark + 1 <= data.value;
|
const marked = mark < Math.ceil(data.value);
|
||||||
const color = marked ? fillColor : foundry.utils.Color.fromRGB([0, 0, 0]);
|
const remainder = mark === Math.ceil(data.value) - 1 ? data.value % 1 : 0;
|
||||||
|
const color = !marked ? emptyColor : remainder ? emptyColor.mix(fillColor, remainder) : fillColor;
|
||||||
if (mark === 0 || mark === sections.length - 1) {
|
if (mark === 0 || mark === sections.length - 1) {
|
||||||
bar.beginFill(color, marked ? 1.0 : 0.5).drawRect(x, 0, widthUnit, bh, 2 * s); // Would like drawRoundedRect, but it's very troublsome with the corners. Leaving for now.
|
bar.beginFill(color, marked ? 1.0 : 0.5).drawRect(x, 0, widthUnit, bh, 2 * s); // Would like drawRoundedRect, but it's very troublsome with the corners. Leaving for now.
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1 @@
|
||||||
export default class DhTokenLayer extends foundry.canvas.layers.TokenLayer {
|
export default class DhTokenLayer extends foundry.canvas.layers.TokenLayer {}
|
||||||
async _createPreview(createData, options) {
|
|
||||||
if (options.actor) {
|
|
||||||
const tokenSizes = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes;
|
|
||||||
if (options.actor?.system.metadata.usesSize) {
|
|
||||||
const tokenSize = tokenSizes[options.actor.system.size];
|
|
||||||
if (tokenSize && options.actor.system.size !== CONFIG.DH.ACTOR.tokenSize.custom.id) {
|
|
||||||
createData.width = tokenSize;
|
|
||||||
createData.height = tokenSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return super._createPreview(createData, options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -115,3 +115,10 @@ export const advantageState = {
|
||||||
value: 1
|
value: 1
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const areaTypes = {
|
||||||
|
placed: {
|
||||||
|
id: 'placed',
|
||||||
|
label: 'Placed Area'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -70,10 +70,40 @@ export const range = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const groupAttackRange = {
|
||||||
|
melee: range.melee,
|
||||||
|
veryClose: range.veryClose,
|
||||||
|
close: range.close,
|
||||||
|
far: range.far,
|
||||||
|
veryFar: range.veryFar
|
||||||
|
};
|
||||||
|
|
||||||
|
/* circle|cone|rect|ray used to be CONST.MEASURED_TEMPLATE_TYPES. Hardcoded for now */
|
||||||
export const templateTypes = {
|
export const templateTypes = {
|
||||||
...CONST.MEASURED_TEMPLATE_TYPES,
|
circle: {
|
||||||
EMANATION: 'emanation',
|
id: 'circle',
|
||||||
INFRONT: 'inFront'
|
label: 'Circle'
|
||||||
|
},
|
||||||
|
cone: {
|
||||||
|
id: 'cone',
|
||||||
|
label: 'Cone'
|
||||||
|
},
|
||||||
|
rectangle: {
|
||||||
|
id: 'rectangle',
|
||||||
|
label: 'Rectangle'
|
||||||
|
},
|
||||||
|
line: {
|
||||||
|
id: 'line',
|
||||||
|
label: 'Line'
|
||||||
|
},
|
||||||
|
emanation: {
|
||||||
|
id: 'emanation',
|
||||||
|
label: 'Emanation'
|
||||||
|
},
|
||||||
|
inFront: {
|
||||||
|
id: 'inFront',
|
||||||
|
label: 'In Front'
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const rangeInclusion = {
|
export const rangeInclusion = {
|
||||||
|
|
@ -241,8 +271,8 @@ export const defaultRestOptions = {
|
||||||
type: 'friendly'
|
type: 'friendly'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: [
|
parts: {
|
||||||
{
|
hitPoints: {
|
||||||
applyTo: healingTypes.hitPoints.id,
|
applyTo: healingTypes.hitPoints.id,
|
||||||
value: {
|
value: {
|
||||||
custom: {
|
custom: {
|
||||||
|
|
@ -251,7 +281,7 @@ export const defaultRestOptions = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -275,8 +305,8 @@ export const defaultRestOptions = {
|
||||||
type: 'self'
|
type: 'self'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: [
|
parts: {
|
||||||
{
|
stress: {
|
||||||
applyTo: healingTypes.stress.id,
|
applyTo: healingTypes.stress.id,
|
||||||
value: {
|
value: {
|
||||||
custom: {
|
custom: {
|
||||||
|
|
@ -285,7 +315,7 @@ export const defaultRestOptions = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -310,8 +340,8 @@ export const defaultRestOptions = {
|
||||||
type: 'friendly'
|
type: 'friendly'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: [
|
parts: {
|
||||||
{
|
armor: {
|
||||||
applyTo: healingTypes.armor.id,
|
applyTo: healingTypes.armor.id,
|
||||||
value: {
|
value: {
|
||||||
custom: {
|
custom: {
|
||||||
|
|
@ -320,7 +350,7 @@ export const defaultRestOptions = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -344,8 +374,8 @@ export const defaultRestOptions = {
|
||||||
type: 'self'
|
type: 'self'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: [
|
parts: {
|
||||||
{
|
hope: {
|
||||||
applyTo: healingTypes.hope.id,
|
applyTo: healingTypes.hope.id,
|
||||||
value: {
|
value: {
|
||||||
custom: {
|
custom: {
|
||||||
|
|
@ -354,7 +384,7 @@ export const defaultRestOptions = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
prepareWithFriends: {
|
prepareWithFriends: {
|
||||||
|
|
@ -368,8 +398,8 @@ export const defaultRestOptions = {
|
||||||
type: 'self'
|
type: 'self'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: [
|
parts: {
|
||||||
{
|
hope: {
|
||||||
applyTo: healingTypes.hope.id,
|
applyTo: healingTypes.hope.id,
|
||||||
value: {
|
value: {
|
||||||
custom: {
|
custom: {
|
||||||
|
|
@ -378,7 +408,7 @@ export const defaultRestOptions = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -405,8 +435,8 @@ export const defaultRestOptions = {
|
||||||
type: 'friendly'
|
type: 'friendly'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: [
|
parts: {
|
||||||
{
|
hitPoints: {
|
||||||
applyTo: healingTypes.hitPoints.id,
|
applyTo: healingTypes.hitPoints.id,
|
||||||
value: {
|
value: {
|
||||||
custom: {
|
custom: {
|
||||||
|
|
@ -415,7 +445,7 @@ export const defaultRestOptions = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -439,8 +469,8 @@ export const defaultRestOptions = {
|
||||||
type: 'self'
|
type: 'self'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: [
|
parts: {
|
||||||
{
|
stress: {
|
||||||
applyTo: healingTypes.stress.id,
|
applyTo: healingTypes.stress.id,
|
||||||
value: {
|
value: {
|
||||||
custom: {
|
custom: {
|
||||||
|
|
@ -449,7 +479,7 @@ export const defaultRestOptions = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -474,17 +504,17 @@ export const defaultRestOptions = {
|
||||||
type: 'friendly'
|
type: 'friendly'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: [
|
parts: {
|
||||||
{
|
armor: {
|
||||||
applyTo: healingTypes.armor.id,
|
applyTo: healingTypes.armor.id,
|
||||||
value: {
|
value: {
|
||||||
custom: {
|
custom: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
formula: '@system.armorScore'
|
formula: '@system.armorScore.max'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -508,8 +538,8 @@ export const defaultRestOptions = {
|
||||||
type: 'self'
|
type: 'self'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: [
|
parts: {
|
||||||
{
|
hope: {
|
||||||
applyTo: healingTypes.hope.id,
|
applyTo: healingTypes.hope.id,
|
||||||
value: {
|
value: {
|
||||||
custom: {
|
custom: {
|
||||||
|
|
@ -518,7 +548,7 @@ export const defaultRestOptions = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
prepareWithFriends: {
|
prepareWithFriends: {
|
||||||
|
|
@ -532,8 +562,8 @@ export const defaultRestOptions = {
|
||||||
type: 'self'
|
type: 'self'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: [
|
parts: {
|
||||||
{
|
hope: {
|
||||||
applyTo: healingTypes.hope.id,
|
applyTo: healingTypes.hope.id,
|
||||||
value: {
|
value: {
|
||||||
custom: {
|
custom: {
|
||||||
|
|
@ -542,7 +572,7 @@ export const defaultRestOptions = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -704,14 +734,14 @@ const getDiceSoNiceSFX = sfxOptions => {
|
||||||
if (sfxOptions.critical && criticalAnimationData.class) {
|
if (sfxOptions.critical && criticalAnimationData.class) {
|
||||||
return {
|
return {
|
||||||
specialEffect: criticalAnimationData.class,
|
specialEffect: criticalAnimationData.class,
|
||||||
options: {}
|
options: { ...criticalAnimationData.options }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sfxOptions.higher && sfxOptions.data.higher) {
|
if (sfxOptions.higher && sfxOptions.data.higher) {
|
||||||
return {
|
return {
|
||||||
specialEffect: sfxOptions.data.higher.class,
|
specialEffect: sfxOptions.data.higher.class,
|
||||||
options: {}
|
options: { ...sfxOptions.data.higher.options }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -943,14 +973,155 @@ export const countdownAppMode = {
|
||||||
export const sceneRangeMeasurementSetting = {
|
export const sceneRangeMeasurementSetting = {
|
||||||
disable: {
|
disable: {
|
||||||
id: 'disable',
|
id: 'disable',
|
||||||
label: 'Disable Daggerheart Range Measurement'
|
label: 'DAGGERHEART.CONFIG.SceneRangeMeasurementTypes.disable'
|
||||||
},
|
},
|
||||||
default: {
|
default: {
|
||||||
id: 'default',
|
id: 'default',
|
||||||
label: 'Default'
|
label: 'DAGGERHEART.CONFIG.SceneRangeMeasurementTypes.default'
|
||||||
},
|
},
|
||||||
custom: {
|
custom: {
|
||||||
id: 'custom',
|
id: 'custom',
|
||||||
label: 'Custom'
|
label: 'DAGGERHEART.CONFIG.SceneRangeMeasurementTypes.custom'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const tagTeamRollTypes = {
|
||||||
|
trait: {
|
||||||
|
id: 'trait',
|
||||||
|
label: 'DAGGERHEART.CONFIG.TagTeamRollTypes.trait'
|
||||||
|
},
|
||||||
|
ability: {
|
||||||
|
id: 'ability',
|
||||||
|
label: 'DAGGERHEART.CONFIG.TagTeamRollTypes.ability'
|
||||||
|
},
|
||||||
|
damageAbility: {
|
||||||
|
id: 'damageAbility',
|
||||||
|
label: 'DAGGERHEART.CONFIG.TagTeamRollTypes.damageAbility'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const baseActiveEffectModes = {
|
||||||
|
custom: {
|
||||||
|
id: 'custom',
|
||||||
|
priority: 0,
|
||||||
|
label: 'EFFECT.CHANGES.TYPES.custom'
|
||||||
|
},
|
||||||
|
multiply: {
|
||||||
|
id: 'multiply',
|
||||||
|
priority: 10,
|
||||||
|
label: 'EFFECT.CHANGES.TYPES.multiply'
|
||||||
|
},
|
||||||
|
add: {
|
||||||
|
id: 'add',
|
||||||
|
priority: 20,
|
||||||
|
label: 'EFFECT.CHANGES.TYPES.add'
|
||||||
|
},
|
||||||
|
subtract: {
|
||||||
|
id: 'subtract',
|
||||||
|
priority: 20,
|
||||||
|
label: 'EFFECT.CHANGES.TYPES.subtract'
|
||||||
|
},
|
||||||
|
downgrade: {
|
||||||
|
id: 'downgrade',
|
||||||
|
priority: 30,
|
||||||
|
label: 'EFFECT.CHANGES.TYPES.downgrade'
|
||||||
|
},
|
||||||
|
upgrade: {
|
||||||
|
id: 'upgrade',
|
||||||
|
priority: 40,
|
||||||
|
label: 'EFFECT.CHANGES.TYPES.upgrade'
|
||||||
|
},
|
||||||
|
override: {
|
||||||
|
id: 'override',
|
||||||
|
priority: 50,
|
||||||
|
label: 'EFFECT.CHANGES.TYPES.override'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const activeEffectModes = {
|
||||||
|
armor: {
|
||||||
|
id: 'armor',
|
||||||
|
priority: 20,
|
||||||
|
label: 'TYPES.ActiveEffect.armor'
|
||||||
|
},
|
||||||
|
...baseActiveEffectModes
|
||||||
|
};
|
||||||
|
|
||||||
|
export const activeEffectArmorInteraction = {
|
||||||
|
none: { id: 'none', label: 'DAGGERHEART.CONFIG.ArmorInteraction.none.label' },
|
||||||
|
active: { id: 'active', label: 'DAGGERHEART.CONFIG.ArmorInteraction.active.label' },
|
||||||
|
inactive: { id: 'inactive', label: 'DAGGERHEART.CONFIG.ArmorInteraction.inactive.label' }
|
||||||
|
};
|
||||||
|
|
||||||
|
export const activeEffectDurations = {
|
||||||
|
temporary: {
|
||||||
|
id: 'temporary',
|
||||||
|
label: 'DAGGERHEART.CONFIG.ActiveEffectDuration.temporary'
|
||||||
|
},
|
||||||
|
act: {
|
||||||
|
id: 'act',
|
||||||
|
label: 'DAGGERHEART.CONFIG.ActiveEffectDuration.act'
|
||||||
|
},
|
||||||
|
scene: {
|
||||||
|
id: 'scene',
|
||||||
|
label: 'DAGGERHEART.CONFIG.ActiveEffectDuration.scene'
|
||||||
|
},
|
||||||
|
shortRest: {
|
||||||
|
id: 'shortRest',
|
||||||
|
label: 'DAGGERHEART.CONFIG.ActiveEffectDuration.shortRest'
|
||||||
|
},
|
||||||
|
longRest: {
|
||||||
|
id: 'longRest',
|
||||||
|
label: 'DAGGERHEART.CONFIG.ActiveEffectDuration.longRest'
|
||||||
|
},
|
||||||
|
session: {
|
||||||
|
id: 'session',
|
||||||
|
label: 'DAGGERHEART.CONFIG.ActiveEffectDuration.session'
|
||||||
|
},
|
||||||
|
custom: {
|
||||||
|
id: 'custom',
|
||||||
|
label: 'DAGGERHEART.CONFIG.ActiveEffectDuration.custom'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fallAndCollisionDamage = {
|
||||||
|
veryClose: {
|
||||||
|
id: 'veryClose',
|
||||||
|
label: 'DAGGERHEART.CONFIG.fallAndCollision.veryClose.label',
|
||||||
|
chatTitle: 'DAGGERHEART.CONFIG.fallAndCollision.veryClose.chatTitle',
|
||||||
|
damageFormula: '1d10 + 3'
|
||||||
|
},
|
||||||
|
close: {
|
||||||
|
id: 'veryClose',
|
||||||
|
label: 'DAGGERHEART.CONFIG.fallAndCollision.close.label',
|
||||||
|
chatTitle: 'DAGGERHEART.CONFIG.fallAndCollision.close.chatTitle',
|
||||||
|
damageFormula: '1d20 + 5'
|
||||||
|
},
|
||||||
|
far: {
|
||||||
|
id: 'veryClose',
|
||||||
|
label: 'DAGGERHEART.CONFIG.fallAndCollision.far.label',
|
||||||
|
chatTitle: 'DAGGERHEART.CONFIG.fallAndCollision.far.chatTitle',
|
||||||
|
damageFormula: '1d100 + 15'
|
||||||
|
},
|
||||||
|
collision: {
|
||||||
|
id: 'veryClose',
|
||||||
|
label: 'DAGGERHEART.CONFIG.fallAndCollision.collision.label',
|
||||||
|
chatTitle: 'DAGGERHEART.CONFIG.fallAndCollision.collision.chatTitle',
|
||||||
|
damageFormula: '1d20 + 5'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const simpleDispositions = {
|
||||||
|
[-1]: {
|
||||||
|
id: -1,
|
||||||
|
label: 'TOKEN.DISPOSITION.HOSTILE'
|
||||||
|
},
|
||||||
|
[0]: {
|
||||||
|
id: 0,
|
||||||
|
label: 'TOKEN.DISPOSITION.NEUTRAL'
|
||||||
|
},
|
||||||
|
[1]: {
|
||||||
|
id: 1,
|
||||||
|
label: 'TOKEN.DISPOSITION.FRIENDLY'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
export const hooksConfig = {
|
export const hooksConfig = {
|
||||||
effectDisplayToggle: 'DHEffectDisplayToggle',
|
effectDisplayToggle: 'DHEffectDisplayToggle',
|
||||||
lockedTooltipDismissed: 'DHLockedTooltipDismissed'
|
lockedTooltipDismissed: 'DHLockedTooltipDismissed',
|
||||||
|
tagTeamStart: 'DHTagTeamRollStart',
|
||||||
|
groupRollStart: 'DHGroupRollStart'
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,12 @@ export const typeConfig = {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'system.type',
|
key: 'system.type',
|
||||||
label: 'DAGGERHEART.GENERAL.type'
|
label: 'DAGGERHEART.GENERAL.type',
|
||||||
|
format: type => {
|
||||||
|
if (!type) return '-';
|
||||||
|
|
||||||
|
return CONFIG.DH.ACTOR.allAdversaryTypes()[type].label;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
filters: [
|
filters: [
|
||||||
|
|
@ -65,16 +70,65 @@ export const typeConfig = {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
environments: {
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
key: 'system.tier',
|
||||||
|
label: 'DAGGERHEART.GENERAL.Tiers.singular'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'system.type',
|
||||||
|
label: 'DAGGERHEART.GENERAL.type',
|
||||||
|
format: type => {
|
||||||
|
if (!type) return '-';
|
||||||
|
|
||||||
|
return CONFIG.DH.ACTOR.environmentTypes[type].label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
key: 'system.tier',
|
||||||
|
label: 'DAGGERHEART.GENERAL.Tiers.singular',
|
||||||
|
field: 'system.api.models.actors.DhEnvironment.schema.fields.tier'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'system.type',
|
||||||
|
label: 'DAGGERHEART.GENERAL.type',
|
||||||
|
field: 'system.api.models.actors.DhEnvironment.schema.fields.type'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'system.difficulty',
|
||||||
|
name: 'difficulty.min',
|
||||||
|
label: 'DAGGERHEART.UI.ItemBrowser.difficultyMin',
|
||||||
|
field: 'system.api.models.actors.DhEnvironment.schema.fields.difficulty',
|
||||||
|
operator: 'gte'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'system.difficulty',
|
||||||
|
name: 'difficulty.max',
|
||||||
|
label: 'DAGGERHEART.UI.ItemBrowser.difficultyMax',
|
||||||
|
field: 'system.api.models.actors.DhEnvironment.schema.fields.difficulty',
|
||||||
|
operator: 'lte'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
items: {
|
items: {
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
key: 'type',
|
key: 'type',
|
||||||
label: 'DAGGERHEART.GENERAL.type'
|
label: 'DAGGERHEART.GENERAL.type',
|
||||||
|
format: type => (type ? `TYPES.Item.${type}` : '-')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'system.secondary',
|
key: 'system.secondary',
|
||||||
label: 'DAGGERHEART.UI.ItemBrowser.subtype',
|
label: 'DAGGERHEART.UI.ItemBrowser.subtype',
|
||||||
format: isSecondary => (isSecondary ? 'secondary' : isSecondary === false ? 'primary' : '-')
|
format: isSecondary =>
|
||||||
|
isSecondary
|
||||||
|
? 'DAGGERHEART.ITEMS.Weapon.secondaryWeapon.short'
|
||||||
|
: isSecondary === false
|
||||||
|
? 'DAGGERHEART.ITEMS.Weapon.primaryWeapon.short'
|
||||||
|
: '-'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'system.tier',
|
key: 'system.tier',
|
||||||
|
|
@ -94,8 +148,8 @@ export const typeConfig = {
|
||||||
key: 'system.secondary',
|
key: 'system.secondary',
|
||||||
label: 'DAGGERHEART.UI.ItemBrowser.subtype',
|
label: 'DAGGERHEART.UI.ItemBrowser.subtype',
|
||||||
choices: [
|
choices: [
|
||||||
{ value: false, label: 'DAGGERHEART.ITEMS.Weapon.primaryWeapon' },
|
{ value: false, label: 'DAGGERHEART.ITEMS.Weapon.primaryWeapon.full' },
|
||||||
{ value: true, label: 'DAGGERHEART.ITEMS.Weapon.secondaryWeapon' }
|
{ value: true, label: 'DAGGERHEART.ITEMS.Weapon.secondaryWeapon.full' }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -166,8 +220,8 @@ export const typeConfig = {
|
||||||
key: 'system.secondary',
|
key: 'system.secondary',
|
||||||
label: 'DAGGERHEART.UI.ItemBrowser.subtype',
|
label: 'DAGGERHEART.UI.ItemBrowser.subtype',
|
||||||
choices: [
|
choices: [
|
||||||
{ value: false, label: 'DAGGERHEART.ITEMS.Weapon.primaryWeapon' },
|
{ value: false, label: 'DAGGERHEART.ITEMS.Weapon.primaryWeapon.full' },
|
||||||
{ value: true, label: 'DAGGERHEART.ITEMS.Weapon.secondaryWeapon' }
|
{ value: true, label: 'DAGGERHEART.ITEMS.Weapon.secondaryWeapon.full' }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -253,11 +307,13 @@ export const typeConfig = {
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
key: 'system.type',
|
key: 'system.type',
|
||||||
label: 'DAGGERHEART.GENERAL.type'
|
label: 'DAGGERHEART.GENERAL.type',
|
||||||
|
format: type => (type ? `DAGGERHEART.CONFIG.DomainCardTypes.${type}` : '-')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'system.domain',
|
key: 'system.domain',
|
||||||
label: 'DAGGERHEART.GENERAL.Domain.single'
|
label: 'DAGGERHEART.GENERAL.Domain.single',
|
||||||
|
format: domain => (domain ? CONFIG.DH.DOMAIN.allDomains()[domain].label : '-')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'system.level',
|
key: 'system.level',
|
||||||
|
|
@ -318,7 +374,14 @@ export const typeConfig = {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'system.domains',
|
key: 'system.domains',
|
||||||
label: 'DAGGERHEART.GENERAL.Domain.plural'
|
label: 'DAGGERHEART.GENERAL.Domain.plural',
|
||||||
|
format: domains => {
|
||||||
|
const config = CONFIG.DH.DOMAIN.allDomains();
|
||||||
|
return domains
|
||||||
|
.map(x => (x ? game.i18n.localize(config[x].label) : null))
|
||||||
|
.filter(x => x)
|
||||||
|
.join(', ');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
filters: [
|
filters: [
|
||||||
|
|
@ -362,25 +425,32 @@ export const typeConfig = {
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
key: 'system.linkedClass',
|
key: 'system.linkedClass',
|
||||||
label: 'Class',
|
label: 'TYPES.Item.class',
|
||||||
format: linkedClass => linkedClass?.name ?? 'DAGGERHEART.UI.ItemBrowser.missing'
|
format: linkedClass =>
|
||||||
|
foundry.utils.fromUuidSync(linkedClass)?.name ?? 'DAGGERHEART.UI.ItemBrowser.missing'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'system.spellcastingTrait',
|
key: 'system.spellcastingTrait',
|
||||||
label: 'DAGGERHEART.ITEMS.Subclass.spellcastingTrait'
|
label: 'DAGGERHEART.ITEMS.Subclass.spellcastingTrait',
|
||||||
|
format: trait => (trait ? `DAGGERHEART.CONFIG.Traits.${trait}.name` : '-')
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
filters: [
|
filters: [
|
||||||
{
|
{
|
||||||
key: 'system.linkedClass.uuid',
|
key: 'system.linkedClass',
|
||||||
label: 'Class',
|
label: 'TYPES.Item.class',
|
||||||
choices: items => {
|
choices: async items => {
|
||||||
const list = items
|
const list = [];
|
||||||
.filter(item => item.system.linkedClass)
|
for (const item of items.filter(item => item.system.linkedClass)) {
|
||||||
.map(item => ({
|
const linkedClass = await foundry.utils.fromUuid(item.system.linkedClass);
|
||||||
value: item.system.linkedClass.uuid,
|
if (linkedClass) {
|
||||||
label: item.system.linkedClass.name
|
list.push({
|
||||||
}));
|
value: linkedClass.uuid,
|
||||||
|
label: linkedClass.name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return list.reduce((a, c) => {
|
return list.reduce((a, c) => {
|
||||||
if (!a.find(i => i.value === c.value)) a.push(c);
|
if (!a.find(i => i.value === c.value)) a.push(c);
|
||||||
return a;
|
return a;
|
||||||
|
|
@ -397,7 +467,8 @@ export const typeConfig = {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'system.mainTrait',
|
key: 'system.mainTrait',
|
||||||
label: 'DAGGERHEART.GENERAL.Trait.single'
|
label: 'DAGGERHEART.GENERAL.Trait.single',
|
||||||
|
format: trait => (trait ? `DAGGERHEART.CONFIG.Traits.${trait}.name` : '-')
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
filters: [
|
filters: [
|
||||||
|
|
@ -533,7 +604,8 @@ export const compendiumConfig = {
|
||||||
id: 'environments',
|
id: 'environments',
|
||||||
keys: ['environments'],
|
keys: ['environments'],
|
||||||
label: 'DAGGERHEART.UI.ItemBrowser.folders.environments',
|
label: 'DAGGERHEART.UI.ItemBrowser.folders.environments',
|
||||||
type: ['environment']
|
type: ['environment'],
|
||||||
|
listType: 'environments'
|
||||||
},
|
},
|
||||||
beastforms: {
|
beastforms: {
|
||||||
id: 'beastforms',
|
id: 'beastforms',
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,8 @@ export const armorFeatures = {
|
||||||
type: 'hostile'
|
type: 'hostile'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: [
|
parts: {
|
||||||
{
|
stress: {
|
||||||
applyTo: 'stress',
|
applyTo: 'stress',
|
||||||
value: {
|
value: {
|
||||||
custom: {
|
custom: {
|
||||||
|
|
@ -24,7 +24,7 @@ export const armorFeatures = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -453,7 +453,7 @@ export const allArmorFeatures = () => {
|
||||||
const feature = homebrewFeatures[key];
|
const feature = homebrewFeatures[key];
|
||||||
const actions = feature.actions.map(action => ({
|
const actions = feature.actions.map(action => ({
|
||||||
...action,
|
...action,
|
||||||
effects: action.effects.map(effect => feature.effects.find(x => x.id === effect._id)),
|
effects: action.effects?.map(effect => feature.effects.find(x => x.id === effect._id)) ?? [],
|
||||||
type: action.type
|
type: action.type
|
||||||
}));
|
}));
|
||||||
const actionEffects = actions.flatMap(a => a.effects);
|
const actionEffects = actions.flatMap(a => a.effects);
|
||||||
|
|
@ -489,15 +489,18 @@ export const weaponFeatures = {
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.barrier.effects.barrier.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.barrier.effects.barrier.description',
|
||||||
img: 'icons/skills/melee/shield-block-bash-blue.webp',
|
img: 'icons/skills/melee/shield-block-bash-blue.webp',
|
||||||
changes: [
|
changes: [
|
||||||
{
|
|
||||||
key: 'system.armorScore',
|
|
||||||
mode: 2,
|
|
||||||
value: 'ITEM.@system.tier + 1'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: 'system.evasion',
|
key: 'system.evasion',
|
||||||
mode: 2,
|
mode: 2,
|
||||||
value: '-1'
|
value: '-1'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'Armor',
|
||||||
|
type: 'armor',
|
||||||
|
typeData: {
|
||||||
|
type: 'armor',
|
||||||
|
max: 'ITEM.@system.tier + 1'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -732,8 +735,8 @@ export const weaponFeatures = {
|
||||||
type: 'hostile'
|
type: 'hostile'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: [
|
parts: {
|
||||||
{
|
stress: {
|
||||||
applyTo: 'stress',
|
applyTo: 'stress',
|
||||||
value: {
|
value: {
|
||||||
custom: {
|
custom: {
|
||||||
|
|
@ -742,7 +745,7 @@ export const weaponFeatures = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -789,11 +792,6 @@ export const weaponFeatures = {
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.doubleDuty.effects.doubleDuty.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.doubleDuty.effects.doubleDuty.description',
|
||||||
img: 'icons/skills/melee/sword-shield-stylized-white.webp',
|
img: 'icons/skills/melee/sword-shield-stylized-white.webp',
|
||||||
changes: [
|
changes: [
|
||||||
{
|
|
||||||
key: 'system.armorScore',
|
|
||||||
mode: 2,
|
|
||||||
value: '1'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: 'system.bonuses.damage.primaryWeapon.bonus',
|
key: 'system.bonuses.damage.primaryWeapon.bonus',
|
||||||
mode: 2,
|
mode: 2,
|
||||||
|
|
@ -808,6 +806,22 @@ export const weaponFeatures = {
|
||||||
type: 'withinRange'
|
type: 'withinRange'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.doubleDuty.effects.doubleDuty.name',
|
||||||
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.doubleDuty.effects.doubleDuty.description',
|
||||||
|
img: 'icons/skills/melee/sword-shield-stylized-white.webp',
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
key: 'Armor',
|
||||||
|
type: 'armor',
|
||||||
|
value: 0,
|
||||||
|
typeData: {
|
||||||
|
type: 'armor',
|
||||||
|
max: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
@ -914,8 +928,8 @@ export const weaponFeatures = {
|
||||||
type: 'self'
|
type: 'self'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: [
|
parts: {
|
||||||
{
|
hitPoints: {
|
||||||
applyTo: 'hitPoints',
|
applyTo: 'hitPoints',
|
||||||
value: {
|
value: {
|
||||||
custom: {
|
custom: {
|
||||||
|
|
@ -924,7 +938,7 @@ export const weaponFeatures = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -1191,9 +1205,13 @@ export const weaponFeatures = {
|
||||||
img: 'icons/skills/melee/shield-block-gray-orange.webp',
|
img: 'icons/skills/melee/shield-block-gray-orange.webp',
|
||||||
changes: [
|
changes: [
|
||||||
{
|
{
|
||||||
key: 'system.armorScore',
|
key: 'Armor',
|
||||||
mode: 2,
|
type: 'armor',
|
||||||
value: 'ITEM.@system.tier'
|
value: 0,
|
||||||
|
typeData: {
|
||||||
|
type: 'armor',
|
||||||
|
max: 'ITEM.@system.tier'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -1389,7 +1407,7 @@ export const allWeaponFeatures = () => {
|
||||||
|
|
||||||
const actions = feature.actions.map(action => ({
|
const actions = feature.actions.map(action => ({
|
||||||
...action,
|
...action,
|
||||||
effects: action.effects.map(effect => feature.effects.find(x => x.id === effect._id)),
|
effects: action.effects?.map(effect => feature.effects.find(x => x.id === effect._id)) ?? [],
|
||||||
type: action.type
|
type: action.type
|
||||||
}));
|
}));
|
||||||
const actionEffects = actions.flatMap(a => a.effects);
|
const actionEffects = actions.flatMap(a => a.effects);
|
||||||
|
|
|
||||||
|
|
@ -57,15 +57,9 @@ const companionBaseResources = Object.freeze({
|
||||||
stress: {
|
stress: {
|
||||||
id: 'stress',
|
id: 'stress',
|
||||||
initial: 0,
|
initial: 0,
|
||||||
max: 0,
|
max: 3,
|
||||||
reverse: true,
|
reverse: true,
|
||||||
label: 'DAGGERHEART.GENERAL.stress'
|
label: 'DAGGERHEART.GENERAL.stress'
|
||||||
},
|
|
||||||
hope: {
|
|
||||||
id: 'hope',
|
|
||||||
initial: 0,
|
|
||||||
reverse: false,
|
|
||||||
label: 'DAGGERHEART.GENERAL.hope'
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue