mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-12 19:51:08 +01:00
Compare commits
324 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b343c9f52 | ||
|
|
e6973fabd0 | ||
|
|
4e18ed8270 | ||
|
|
e7cf6594b6 | ||
|
|
bbe8fb953e | ||
|
|
6cebccd958 | ||
|
|
248f7b41e7 | ||
|
|
c6bdc846ab | ||
|
|
6deadea437 | ||
|
|
9564edb244 | ||
|
|
bca7e0d3c9 | ||
|
|
3b7b6258a1 | ||
|
|
e8c541c002 | ||
|
|
f6bd1430e3 | ||
|
|
c070c6cc2d | ||
|
|
d0e55aeb8d | ||
|
|
f11b018bd7 | ||
|
|
c83fe25a47 | ||
|
|
3405b53900 | ||
|
|
5f001a9f83 | ||
|
|
87dfebec2f | ||
|
|
ab7ea03d84 | ||
|
|
09aafd0999 | ||
|
|
52b32a4d12 | ||
|
|
fa21baf8bf | ||
|
|
50a307b271 | ||
|
|
c63ba3b41d | ||
|
|
2104549617 | ||
|
|
92b31b71a7 | ||
|
|
f8b003b304 | ||
|
|
0806c2d1ac | ||
|
|
f184db1f93 | ||
|
|
7e2b144bf4 | ||
|
|
1b7893324a | ||
|
|
0f5f866b22 | ||
|
|
a168d8de65 | ||
|
|
51eadc499f | ||
|
|
f0531d3587 | ||
|
|
790a5b4938 | ||
|
|
8178fa5738 | ||
|
|
7926c614e3 | ||
|
|
16f6fa98a6 | ||
|
|
659f73116a | ||
|
|
e8dd38fbfa | ||
|
|
99d0eab5bd | ||
|
|
f786ee5f06 | ||
|
|
b8e08fccd1 | ||
|
|
fe80b4d0f8 | ||
|
|
148c9c019a | ||
|
|
9cfa206adc | ||
|
|
0508bf4188 | ||
|
|
605a23ab58 | ||
|
|
7d1e70f66f | ||
|
|
474cf28a53 | ||
|
|
5f6d08d8c2 | ||
|
|
a8b15c8252 | ||
|
|
27fe83d906 | ||
|
|
0936b46926 | ||
|
|
05dec9fcea | ||
|
|
e74ce7726a | ||
|
|
9b4249b100 | ||
|
|
f60792f714 | ||
|
|
d5b8431f88 | ||
|
|
315f1ef8e0 | ||
|
|
6cb635901f | ||
|
|
a8c120be8e | ||
|
|
7a50d77952 | ||
|
|
64caff6fb2 | ||
|
|
46a9aea029 | ||
|
|
360b903437 | ||
|
|
f4dd9dc5c1 | ||
|
|
00e9436fe0 | ||
|
|
7f7536ee06 | ||
|
|
8eae1c0763 | ||
|
|
2b1535333a | ||
|
|
09141053c9 | ||
|
|
5356f10b2a | ||
|
|
91d916a28d | ||
|
|
b307d65d18 | ||
|
|
ccdd413933 | ||
|
|
f680ade1da | ||
|
|
d6b1c7a36c | ||
|
|
28976bb4b8 | ||
|
|
14ac8977af | ||
|
|
2a622a7363 | ||
|
|
6ae00e15bd | ||
|
|
c846c5bc85 | ||
|
|
451bef4c92 | ||
|
|
c3cb9121af | ||
|
|
6d8d773a26 | ||
|
|
b57e98071f | ||
|
|
2171c1b433 | ||
|
|
1fbce2507a | ||
|
|
e3f244d8d7 | ||
|
|
9fa4627b19 | ||
|
|
cb10b18e06 | ||
|
|
5163bf9788 | ||
|
|
82d39a3d70 | ||
|
|
01a91724ed | ||
|
|
8f917c3640 | ||
|
|
e57e7327d6 | ||
|
|
165068a9ee | ||
|
|
f41f0b20b7 | ||
|
|
f29198e81f | ||
|
|
630ba5ab7d | ||
|
|
b4c2034789 | ||
|
|
cd5b8e9c75 | ||
|
|
44b805d0df | ||
|
|
d616ddc113 | ||
|
|
4510deae96 | ||
|
|
d137e33c3d | ||
|
|
9e0bc3cff1 | ||
|
|
f78f8e32b6 | ||
|
|
d5b501cb98 | ||
|
|
4b76223e45 | ||
|
|
b9508e19e8 | ||
|
|
e6a242ba43 | ||
|
|
81e7f24d22 | ||
|
|
6398ba15f2 | ||
|
|
ebf98a6ae5 | ||
|
|
2920ead81a | ||
|
|
9f7554cdff | ||
|
|
87643dc662 | ||
|
|
e77f538ab7 | ||
|
|
207220ff7b | ||
|
|
0233979a9f | ||
|
|
844c683c7d | ||
|
|
72944698c5 | ||
|
|
28401989d0 | ||
|
|
d1cbaa7809 | ||
|
|
a02015df57 | ||
|
|
8e785a5890 | ||
|
|
336995b748 | ||
|
|
a146132171 | ||
|
|
5c52a33496 | ||
|
|
b9d67e44da | ||
|
|
fe8e98ef35 | ||
|
|
c3660ffa34 | ||
|
|
97f408c185 | ||
|
|
023e17d47d | ||
|
|
481ce46edf | ||
|
|
7df43d71e0 | ||
|
|
fcb9c032ef | ||
|
|
91b1b92d19 | ||
|
|
369559f935 | ||
|
|
97ddf651a0 | ||
|
|
bdbca0d35a | ||
|
|
ef0290ae8a | ||
|
|
f730bb762c | ||
|
|
fbf1ee2046 | ||
|
|
249f27ed5b | ||
|
|
63e66bd0d3 | ||
|
|
977715d22a | ||
|
|
72bf0171b3 | ||
|
|
d1caded970 | ||
|
|
54109bf655 | ||
|
|
c4b227de41 | ||
|
|
6408528319 | ||
|
|
7c4200b431 | ||
|
|
84ef1063d8 | ||
|
|
074a9486ae | ||
|
|
261a3a68b0 | ||
|
|
f53252a369 | ||
|
|
052b6baefe | ||
|
|
7055591a76 | ||
|
|
2d6390248f | ||
|
|
66961d7fea | ||
|
|
321b7c2da3 | ||
|
|
f52c3e840e | ||
|
|
a7bc8db55f | ||
|
|
a7d035bcdb | ||
|
|
3af90dc0f1 | ||
|
|
bccedffbca | ||
|
|
3aaae26ba1 | ||
|
|
ba84a75bd0 | ||
|
|
3c1f646b61 | ||
|
|
ce3e2a804c | ||
|
|
659670c403 | ||
|
|
ff79dd19bf | ||
|
|
e2508501a5 | ||
|
|
906c7ac853 | ||
|
|
07cdcf2d78 | ||
|
|
d4f80b6fa1 | ||
|
|
45b9b52314 | ||
|
|
dc073aa9cc | ||
|
|
25fb2ee570 | ||
|
|
a80789f1f1 | ||
|
|
e15d210e20 | ||
|
|
6f2ffaf2f8 | ||
|
|
b3a72d6b1d | ||
|
|
86eeba0648 | ||
|
|
952779000d | ||
|
|
cc6ef0b7bf | ||
|
|
b406ec6c96 | ||
|
|
e655954890 | ||
|
|
f1b5c80a53 | ||
|
|
4379741681 | ||
|
|
55586c93c8 | ||
|
|
f25387e964 | ||
|
|
a477c9b852 | ||
|
|
2748e91aa6 | ||
|
|
a76486a86c | ||
|
|
513464b01b | ||
|
|
a57d154d45 | ||
|
|
58f039ce96 | ||
|
|
2c6aabb97a | ||
|
|
2176038ec6 | ||
|
|
f1b6d3851d | ||
|
|
fd92540792 | ||
|
|
e258d9c5f6 | ||
|
|
d4a98d3f66 | ||
|
|
ec54da4e23 | ||
|
|
eefb28c312 | ||
|
|
2d92576121 | ||
|
|
f04619f73b | ||
|
|
0bd423ef52 | ||
|
|
3f08741a34 | ||
|
|
3c893df175 | ||
|
|
1b9defe4ad | ||
|
|
179dd3e6c3 | ||
|
|
31238113c9 | ||
|
|
ef4d37d725 | ||
|
|
3f1e7f4f4a | ||
|
|
c6741b1c7a | ||
|
|
8fd63d5963 | ||
|
|
9dd773001d | ||
|
|
9f2c2f1bed | ||
|
|
18687b6131 | ||
|
|
1eb3ff11c0 | ||
|
|
8e5dd22370 | ||
|
|
7a764e39ae | ||
|
|
dc54fdc096 | ||
| ff396cd2f0 | |||
|
|
aaf6c689fc | ||
|
|
3386a9d61d | ||
|
|
76d753cd88 | ||
|
|
ff65a85458 | ||
|
|
0a5828c8fa | ||
|
|
2aeb255033 | ||
|
|
9aba4dc66d | ||
|
|
990c73987e | ||
|
|
f480027b95 | ||
|
|
7a1d259e82 | ||
|
|
d24fef7494 | ||
|
|
d686ac1e0c | ||
|
|
5a38e28a84 | ||
|
|
afdffb672a | ||
|
|
2a0d748b5e | ||
|
|
661808b5f1 | ||
|
|
16173363d4 | ||
|
|
fbeff1b908 | ||
|
|
46ea4addd0 | ||
|
|
0b2694b007 | ||
|
|
a72d4583cd | ||
|
|
0c3ebd6e11 | ||
|
|
936c96a1be | ||
|
|
471cbd55df | ||
|
|
f76515eac1 | ||
|
|
2d20fb0df4 | ||
|
|
b83adbf09b | ||
|
|
d5f7e17339 | ||
|
|
7016f71da7 | ||
|
|
86d451f0d7 | ||
|
|
ee786544c7 | ||
|
|
888cf9172b | ||
|
|
e9f7c0c16b | ||
|
|
523ecb506b | ||
|
|
60b55619e1 | ||
|
|
7a6bbe3488 | ||
|
|
218f180fa0 | ||
|
|
af250d7a61 | ||
|
|
774b6dbdcc | ||
|
|
85111648aa | ||
|
|
fa8bd63614 | ||
|
|
a415ab9955 | ||
|
|
b3062bf5b9 | ||
|
|
540ee49f50 | ||
|
|
4038c44f9a | ||
|
|
f19548ef4f | ||
|
|
bc5b01bdcf | ||
|
|
bd8cfe0297 | ||
|
|
f69e5704e4 | ||
|
|
5cd5de31aa | ||
|
|
8c84edddad | ||
|
|
495575fba4 | ||
|
|
649b4d64bc | ||
|
|
577ed5f491 | ||
|
|
233e9eddbe | ||
|
|
18b6194afe | ||
|
|
e692f3814d | ||
|
|
505ee6d3cf | ||
|
|
6100f3cf9e | ||
|
|
667edda50a | ||
|
|
16591cd327 | ||
|
|
03e6570d68 | ||
|
|
96d26a1e5b | ||
|
|
264ba44a14 | ||
|
|
2820c96259 | ||
|
|
b241b36653 | ||
|
|
ab76d9e91d | ||
|
|
0e43bf197d | ||
|
|
88be4c953d | ||
|
|
a07819611e | ||
|
|
16e931179f | ||
|
|
ba42892eed | ||
|
|
841819ee89 | ||
|
|
b3c0344b91 | ||
|
|
006f9dde1f | ||
|
|
3d05c7fe0a | ||
|
|
633600f271 | ||
|
|
16299a9865 | ||
|
|
47754e78cd | ||
|
|
207d0d6335 | ||
|
|
89e4e43026 | ||
|
|
6942ec7d14 | ||
|
|
ecd33caa7b | ||
|
|
94484bd024 | ||
|
|
94c235bc08 | ||
|
|
24114fbdf6 | ||
|
|
24f5cb5a5c | ||
|
|
5e56c7ec2e | ||
|
|
06184773b9 | ||
|
|
6e747e67ee | ||
|
|
4ffa690aec |
1634 changed files with 37836 additions and 30650 deletions
2
.env.example
Normal file
2
.env.example
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
FOUNDRY_MAIN_PATH=/path/to/foundry/resources/app/main.js
|
||||||
|
FOUNDRY_DATA_PATH=/path/to/foundry/data
|
||||||
10
.github/ISSUE_TEMPLATE/bug_report.md
vendored
10
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
|
@ -1,10 +1,10 @@
|
||||||
---
|
---
|
||||||
name: Bug report
|
name: Bug report
|
||||||
about: Create a report to help us improve
|
about: Create a bug report to help us identify issues and resolve them
|
||||||
title: "[BUG] - "
|
title: "[Bug] <Insert Title here> "
|
||||||
labels: bug
|
labels: bug
|
||||||
|
type: bug
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Describe the bug**
|
**Describe the bug**
|
||||||
|
|
@ -24,10 +24,10 @@ A clear and concise description of what you expected to happen.
|
||||||
If applicable, add screenshots to help explain your problem.
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
**Setup Information:**
|
**Setup Information:**
|
||||||
- OS: [e.g. iOS]
|
- OS: [e.g. iOS, Windows]
|
||||||
- Browser [e.g. chrome, safari]
|
- Browser [e.g. chrome, safari]
|
||||||
- Foundry Version [e.g. v13 b342]
|
- Foundry Version [e.g. v13 b342]
|
||||||
- System Version [e.g. main-3593f44]
|
- System Version [e.g. v.1.0, v.1.0.1]
|
||||||
|
|
||||||
|
|
||||||
**Additional context**
|
**Additional context**
|
||||||
|
|
|
||||||
14
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
14
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Create a feature request for suggestions on improving the system
|
||||||
|
title: "[Feature] <Insert Title here> "
|
||||||
|
labels: enhancement, discussion, maybe
|
||||||
|
type: feature
|
||||||
|
assignees: ''
|
||||||
|
---
|
||||||
|
|
||||||
|
**Description**
|
||||||
|
A clear and concise description of what feature needs to be implemented.
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
If applicable, add screenshots to help explain the feature that needs to be implemented.
|
||||||
9
.github/ISSUE_TEMPLATE/typo_report.md
vendored
Normal file
9
.github/ISSUE_TEMPLATE/typo_report.md
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
name: Typo report
|
||||||
|
about: Create a new issue to report a compendium typo
|
||||||
|
title: "[TYPO] - "
|
||||||
|
labels: compendium, typo
|
||||||
|
type: bug
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
55
.github/PULL_REQUEST_TEMPLATE/community_pull_request_template.md
vendored
Normal file
55
.github/PULL_REQUEST_TEMPLATE/community_pull_request_template.md
vendored
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
---
|
||||||
|
name: Pull Request
|
||||||
|
about: Create a new pull request
|
||||||
|
title: "[Community PR] <Insert Title here>"
|
||||||
|
labels: community pr
|
||||||
|
assignees: ''
|
||||||
|
---
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Please include a summary of the change and which issue is fixed (if applicable). Also include relevant context or motivation for the change.
|
||||||
|
|
||||||
|
- Fixes #(issue)
|
||||||
|
- Closes #(issue)
|
||||||
|
|
||||||
|
## Type of Change
|
||||||
|
|
||||||
|
Please check the relevant options:
|
||||||
|
|
||||||
|
- [ ] Bug fix
|
||||||
|
- [ ] New feature
|
||||||
|
- [ ] Code cleanup/refactor
|
||||||
|
- [ ] Documentation update
|
||||||
|
- [ ] Test coverage
|
||||||
|
- [ ] Dependency update
|
||||||
|
- [ ] Configuration change
|
||||||
|
- [ ] Other (please describe):
|
||||||
|
|
||||||
|
## How Has This Been Tested?
|
||||||
|
|
||||||
|
Please describe the tests you ran to verify your changes:
|
||||||
|
|
||||||
|
- [ ] Manual testing
|
||||||
|
- [ ] Other:
|
||||||
|
|
||||||
|
## Screenshots (if applicable)
|
||||||
|
|
||||||
|
Include screenshots or GIFs to help explain your changes visually.
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
|
||||||
|
- [ ] My code follows the project style guidelines
|
||||||
|
- [ ] I have performed a self-review of my code
|
||||||
|
- [ ] I have commented my code where necessary
|
||||||
|
- [ ] I have made corresponding changes to the documentation
|
||||||
|
- [ ] My changes generate no new warnings or errors
|
||||||
|
- [ ] I have added tests that prove my fix or feature works
|
||||||
|
- [ ] New and existing tests pass locally with my changes
|
||||||
|
|
||||||
|
## Additional Comments
|
||||||
|
|
||||||
|
Add any other context or questions here.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
> Thank you for your contribution! 🎉
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,4 +1,5 @@
|
||||||
.vscode
|
.vscode
|
||||||
|
.env
|
||||||
node_modules
|
node_modules
|
||||||
/packs
|
/packs
|
||||||
Build
|
Build
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,10 @@ We welcome contributions of all kinds:
|
||||||
|
|
||||||
Please be respectful and collaborative — we’re all here to build something great together.
|
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
|
## 🧭 General Guidelines
|
||||||
|
|
@ -40,12 +44,14 @@ We encourage contributors to leave comments or open Discussions when proposing s
|
||||||
## 🧾 Issue & PR Best Practices
|
## 🧾 Issue & PR Best Practices
|
||||||
|
|
||||||
**For Issues:**
|
**For Issues:**
|
||||||
|
|
||||||
- Use clear, descriptive titles
|
- Use clear, descriptive titles
|
||||||
- Provide a concise explanation of the problem or idea
|
- Provide a concise explanation of the problem or idea
|
||||||
- Include reproduction steps or example scenarios if it's a bug
|
- Include reproduction steps or example scenarios if it's a bug
|
||||||
- Add screenshots or logs if helpful
|
- Add screenshots or logs if helpful
|
||||||
|
|
||||||
**For Pull Requests:**
|
**For Pull Requests:**
|
||||||
|
|
||||||
- Use a clear title summarizing the change
|
- Use a clear title summarizing the change
|
||||||
- Provide a brief description of what your code does and why
|
- Provide a brief description of what your code does and why
|
||||||
- Link to any related Issues
|
- Link to any related Issues
|
||||||
|
|
@ -67,6 +73,6 @@ Discussions are currently happening on GitHub — in Issues, PRs, and [GitHub Di
|
||||||
|
|
||||||
## 🤗 Thank You!
|
## 🤗 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**!
|
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**!
|
||||||
|
|
||||||
🐸🛠️
|
🐸🛠️
|
||||||
|
|
|
||||||
43
README.md
43
README.md
|
|
@ -24,23 +24,40 @@ You can find the documentation here: https://github.com/Foundryborne/daggerheart
|
||||||
|
|
||||||
## Development Setup
|
## Development Setup
|
||||||
|
|
||||||
- Open a terminal in the directory with the repo `cd <path>/<to>/<repo>`
|
1. **Navigate to the repo directory:**
|
||||||
- NOTE: The repo should be placed in the system files are or somewhere else and a link (if on linux) is placed in the system directory
|
|
||||||
- NOTE: Linux link can be made using `ln -snf <path to development folder> daggerheart` inside the system folder
|
|
||||||
- Install npm `npm install`
|
|
||||||
- Update package.json to match your profile
|
|
||||||
|
|
||||||
```
|
|
||||||
"start": "concurrently \"rollup -c --watch\" \"node C:/FoundryDev/resources/app/main.js --dataPath=C:/FoundryDevFiles --noupnp\" \"gulp\"",
|
|
||||||
"start-test": "node C:/FoundryDev/resources/app/main.js --dataPath=C:/FoundryDevFiles && rollup -c --watch && gulp",
|
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd <path>/<to>/<repo>
|
||||||
```
|
```
|
||||||
|
|
||||||
- Replace `C:/FoundryDev/resources/app/main.js` with `<your>/<path>/<to>/<foundry>/<main.js>`
|
2. **Install dependencies:**
|
||||||
- The main is likely in `<Foundry Install Location>/resouces/app/main.js`
|
|
||||||
- Replace `--dataPath=C:/FoundryDevFiles` with `<your>/<path>/<to>/<foundry>/<data>`
|
|
||||||
|
|
||||||
Now you should be able to build the app using `npm start`
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Configure your Foundry paths:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run setup:dev -- --foundry-path="/path/to/foundry/main.js" --data-path="/path/to/data"
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Start developing:**
|
||||||
|
```bash
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
### Available Scripts
|
||||||
|
|
||||||
|
- `npm start` - Start development with file watching and Foundry launching
|
||||||
|
- `npm run build` - One-time build
|
||||||
|
- `npm run setup:dev -- --foundry-path="<path>" --data-path="<path>"` - Configure development environment
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
|
||||||
|
- The repo should be placed in your Foundry `Data/systems/` directory or symlinked there
|
||||||
|
- Linux symlink can be made using `ln -snf <path to development folder> daggerheart` inside the systems folder
|
||||||
|
- Your `.env` file is ignored by git, so each developer can have their own configuration
|
||||||
[Foundry VTT Website][1]
|
[Foundry VTT Website][1]
|
||||||
|
|
||||||
[1]: https://foundryvtt.com/
|
[1]: https://foundryvtt.com/
|
||||||
|
|
|
||||||
BIN
assets/icons/arrow-dunk.png
Normal file
BIN
assets/icons/arrow-dunk.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
BIN
assets/icons/dice/duality/DualityBW.png
Normal file
BIN
assets/icons/dice/duality/DualityBW.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.7 KiB |
59
assets/icons/dice/duality/DualityBW.svg
Normal file
59
assets/icons/dice/duality/DualityBW.svg
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
|
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="42.0746mm" height="50.8071mm"
|
||||||
|
viewBox="0 0 159 192">
|
||||||
|
<path id="Fear"
|
||||||
|
fill="#808080" stroke="black" stroke-width="1"
|
||||||
|
d="M 107.14,62.84
|
||||||
|
C 107.14,62.84 52.70,137.55 52.70,137.55
|
||||||
|
52.70,137.55 110.33,137.55 110.33,137.55
|
||||||
|
110.33,137.55 127.99,79.63 127.99,79.63
|
||||||
|
127.99,79.63 107.14,62.84 107.14,62.84 Z
|
||||||
|
M 48.07,144.21
|
||||||
|
C 48.07,144.21 13.61,192.28 13.61,192.28
|
||||||
|
14.01,191.90 30.68,177.55 47.81,176.06
|
||||||
|
47.81,176.06 64.48,176.30 64.48,176.30
|
||||||
|
64.69,176.22 79.31,180.77 79.31,180.77
|
||||||
|
79.31,180.77 126.04,165.15 126.04,165.15
|
||||||
|
126.04,165.15 110.91,143.92 110.91,143.92
|
||||||
|
110.91,143.92 48.07,144.21 48.07,144.21 Z
|
||||||
|
M 134.24,81.37
|
||||||
|
C 134.24,81.37 115.74,140.33 115.74,140.33
|
||||||
|
115.74,140.33 131.41,161.00 131.41,161.00
|
||||||
|
131.41,161.00 158.98,122.82 158.98,122.82
|
||||||
|
158.98,122.82 158.91,72.83 158.91,72.83
|
||||||
|
158.91,72.83 134.24,81.37 134.24,81.37 Z
|
||||||
|
M 130.43,30.45
|
||||||
|
C 130.43,30.45 110.41,59.27 110.41,59.27
|
||||||
|
110.41,59.27 131.77,75.21 131.77,75.21
|
||||||
|
131.77,75.21 157.51,67.23 157.51,67.23
|
||||||
|
157.25,67.32 130.42,30.45 130.43,30.45 Z" />
|
||||||
|
<path id="Hope"
|
||||||
|
fill="white" stroke="black" stroke-width="1"
|
||||||
|
d="M 143.84,1.80
|
||||||
|
C 143.84,1.80 105.59,54.92 105.59,54.92
|
||||||
|
105.59,54.92 83.04,39.72 83.04,39.72
|
||||||
|
83.04,39.72 82.87,13.08 82.87,13.08
|
||||||
|
82.23,14.32 111.40,18.59 112.13,17.16
|
||||||
|
113.68,18.32 145.11,2.74 143.84,1.80 Z
|
||||||
|
M 76.01,13.40
|
||||||
|
C 76.01,13.40 76.01,40.05 76.01,40.05
|
||||||
|
76.01,40.05 25.99,75.35 25.99,75.35
|
||||||
|
25.99,75.35 1.96,68.16 1.96,68.16
|
||||||
|
1.96,68.16 30.08,27.62 30.08,27.62
|
||||||
|
30.08,27.62 76.01,13.40 76.01,13.40 Z
|
||||||
|
M 79.11,44.62
|
||||||
|
C 79.11,44.62 101.34,60.81 101.34,60.81
|
||||||
|
101.34,60.81 48.38,133.54 48.38,133.54
|
||||||
|
48.38,133.54 30.57,80.09 30.57,80.09
|
||||||
|
30.57,80.09 79.11,44.62 79.11,44.62 Z
|
||||||
|
M 24.36,81.07
|
||||||
|
C 24.36,81.07 43.64,139.59 43.64,139.59
|
||||||
|
43.64,139.59 28.11,162.31 28.11,162.31
|
||||||
|
28.11,162.31 0.33,123.90 0.33,123.90
|
||||||
|
0.33,123.90 0.33,74.05 0.33,74.05
|
||||||
|
0.33,74.05 24.36,81.07 24.36,81.07 Z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.6 KiB |
1
assets/icons/documents/actors/dark-squad.svg
Normal file
1
assets/icons/documents/actors/dark-squad.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="M369.1 21.22c-19.2 0-36.2 10.63-47.9 26.47-11.7 15.84-18.6 37.03-18.6 60.31 0 21.1 5.7 40.5 15.5 55.7-5.7 1.6-11 3.9-15.9 6.6-10.2-8.5-22.6-13.6-35.9-13.6-19.3 0-36.3 10.6-48 26.4-4.7 6.4-8.6 13.6-11.6 21.5-4.8-2.4-9.9-4.3-15.5-5.6 9.4-15.1 14.8-34.1 14.8-54.7 0-23.2-6.9-44.43-18.6-60.27-11.7-15.84-28.7-26.5-47.9-26.5s-36.2 10.66-47.94 26.5C79.87 99.87 73 121.1 73 144.3c0 21.1 5.69 40.5 15.47 55.8-32.07 9.1-50.29 37.1-59.44 70-9.79 35.2-10.87 77.3-10.87 115.6v9.4h45.5l6.78 99.3h18.75l-7.28-106.5-4.1-80-18.65 1 3.47 67.5H36.97c.24-35.2 1.97-72.1 10.09-101.2 8.78-31.6 23.32-52.8 51.25-58.2l4.69-.1c10.3 8.8 22.9 14.2 36.5 14.2 14.1 0 26.9-5.7 37.4-15h4.6c7.8 1.2 14.4 3.5 20.1 6.7-1.2 6.6-1.9 13.5-1.9 20.6 0 21.1 5.7 40.5 15.5 55.8-32.1 9.1-50.3 37.2-59.4 70-9.8 35.2-10.9 77.3-10.9 115.6v9.4c21.7-.3 42.8.2 64.3.2l-.5-7.3-4.1-80-18.7.9 3.4 67.5h-25.6c.3-35.2 2-72.1 10.1-101.2 8.7-31.6 23.3-52.7 51.1-58.2l4.9-.1c10.3 8.8 22.8 14.2 36.4 14.2 14.1 0 27-5.7 37.5-15h4.4c15.4 2.4 26.1 8.9 34.5 18.6 8.5 9.7 14.5 23.2 18.5 39.2 7.3 29.5 7.7 66.9 7.7 102.5h-23.4l3.5-67.5-18.7-.9-4.2 82-.3 5.3c20.8 0 43.3-.3 61.9-.2v-9.4c0-38.1.5-80.6-8.4-116.3-4.4-17.8-11.3-34.1-22.4-47-9.7-11.1-22.7-19.4-38.8-23.4 9.4-15.1 14.7-34.1 14.7-54.7 0-22.5-6.4-43.2-17.5-58.8 3.9-1.8 8.1-3.1 12.7-4h4.7c10.3 8.8 22.9 14.2 36.5 14.2 14.1 0 27-5.8 37.4-15l4.6-.1c15.4 2.5 26 8.9 34.4 18.6 8.5 9.8 14.5 23.3 18.5 39.3 7.3 29.4 7.7 66.8 7.7 102.4h-23.4l3.5-67.4-18.7-1-4.1 79.7-8.6 143.1h18.7l8.2-135.7h43.1v-9.3c0-38.2.6-80.7-8.3-116.3-4.5-17.9-11.4-34.2-22.5-47-9.6-11.2-22.6-19.5-38.8-23.5 9.4-15.1 14.8-34 14.8-54.6 0-23.28-6.9-44.47-18.6-60.31-11.6-15.29-31.5-26.13-47.9-26.47zm0 18.69c12.4 0 23.9 6.69 32.9 18.87 9 12.19 14.9 29.67 14.9 49.22 0 19.5-5.9 37-14.9 49.2-9 12.2-20.5 18.9-32.9 18.9-12.3 0-23.9-6.7-32.9-18.9s-14.9-29.7-14.9-49.2c0-19.55 5.9-37.03 14.9-49.22 9-12.18 20.6-18.87 32.9-18.87zM139.5 76.22c12.4 0 23.9 6.72 32.9 18.9s14.9 29.68 14.9 49.18-5.9 37-14.9 49.2c-9 12.2-20.5 18.9-32.9 18.9-12.4 0-23.9-6.7-32.9-18.9-8.97-12.2-14.91-29.7-14.91-49.2 0-19.5 5.94-37 14.91-49.17 9-12.19 20.5-18.91 32.9-18.91zm197.8 22.34v18.64h22.5V98.56h-22.5zm41.1 0v18.64h22.5V98.56h-22.5zM107.7 134.9v18.7h22.5v-18.7h-22.5zm41.1 0v18.7h22.5v-18.7h-22.5zm117.5 40.4c12.3 0 23.8 6.7 32.8 18.9 9 12.2 15 29.7 15 49.2 0 19.6-6 37-15 49.2-9 12.2-20.5 18.9-32.8 18.9-12.4 0-24-6.7-33-18.9-8.9-12.2-14.9-29.6-14.9-49.2 0-19.5 6-37 14.9-49.2 9-12.2 20.6-18.9 33-18.9zM234.5 234v18.7h22.4V234zm41.1 0v18.7H298V234h-22.4z" fill="#fff" fill-opacity="1"></path></g></svg>
|
||||||
|
After Width: | Height: | Size: 2.7 KiB |
364
daggerheart.mjs
364
daggerheart.mjs
|
|
@ -1,5 +1,6 @@
|
||||||
import { SYSTEM } from './module/config/system.mjs';
|
import { SYSTEM } from './module/config/system.mjs';
|
||||||
import * as applications from './module/applications/_module.mjs';
|
import * as applications from './module/applications/_module.mjs';
|
||||||
|
import * as data from './module/data/_module.mjs';
|
||||||
import * as models from './module/data/_module.mjs';
|
import * as models from './module/data/_module.mjs';
|
||||||
import * as documents from './module/documents/_module.mjs';
|
import * as documents from './module/documents/_module.mjs';
|
||||||
import * as dice from './module/dice/_module.mjs';
|
import * as dice from './module/dice/_module.mjs';
|
||||||
|
|
@ -7,41 +8,22 @@ 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 { getCommandTarget, rollCommandToJSON } from './module/helpers/utils.mjs';
|
||||||
import { NarrativeCountdowns } from './module/applications/ui/countdowns.mjs';
|
import { BaseRoll, DHRoll, DualityRoll, D20Roll, DamageRoll } from './module/dice/_module.mjs';
|
||||||
import { DHRoll, DualityRoll, D20Roll, DamageRoll } from './module/dice/_module.mjs';
|
|
||||||
import { enrichedDualityRoll } from './module/enrichers/DualityRollEnricher.mjs';
|
import { enrichedDualityRoll } from './module/enrichers/DualityRollEnricher.mjs';
|
||||||
import { registerCountdownHooks } from './module/data/countdowns.mjs';
|
|
||||||
import {
|
import {
|
||||||
handlebarsRegistration,
|
handlebarsRegistration,
|
||||||
|
runMigrations,
|
||||||
settingsRegistration,
|
settingsRegistration,
|
||||||
socketRegistration
|
socketRegistration
|
||||||
} from './module/systemRegistration/_module.mjs';
|
} from './module/systemRegistration/_module.mjs';
|
||||||
import { placeables } from './module/canvas/_module.mjs';
|
import { placeables } from './module/canvas/_module.mjs';
|
||||||
import { registerRollDiceHooks } from './module/dice/dhRoll.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 TemplateManager from './module/documents/templateManager.mjs';
|
||||||
|
|
||||||
Hooks.once('init', () => {
|
|
||||||
CONFIG.DH = SYSTEM;
|
CONFIG.DH = SYSTEM;
|
||||||
game.system.api = {
|
|
||||||
applications,
|
|
||||||
models,
|
|
||||||
documents,
|
|
||||||
dice,
|
|
||||||
fields
|
|
||||||
};
|
|
||||||
|
|
||||||
CONFIG.TextEditor.enrichers.push(...enricherConfig);
|
CONFIG.TextEditor.enrichers.push(...enricherConfig);
|
||||||
|
|
||||||
CONFIG.statusEffects = [
|
CONFIG.Dice.rolls = [BaseRoll, DHRoll, DualityRoll, D20Roll, DamageRoll];
|
||||||
...CONFIG.statusEffects.filter(x => !['dead', 'unconscious'].includes(x.id)),
|
|
||||||
...Object.values(SYSTEM.GENERAL.conditions).map(x => ({
|
|
||||||
...x,
|
|
||||||
name: game.i18n.localize(x.name),
|
|
||||||
systemEffect: true
|
|
||||||
}))
|
|
||||||
];
|
|
||||||
|
|
||||||
CONFIG.Dice.daggerheart = {
|
CONFIG.Dice.daggerheart = {
|
||||||
DHRoll: DHRoll,
|
DHRoll: DHRoll,
|
||||||
DualityRoll: DualityRoll,
|
DualityRoll: DualityRoll,
|
||||||
|
|
@ -49,55 +31,154 @@ Hooks.once('init', () => {
|
||||||
DamageRoll: DamageRoll
|
DamageRoll: DamageRoll
|
||||||
};
|
};
|
||||||
|
|
||||||
CONFIG.Dice.rolls = [...CONFIG.Dice.rolls, DHRoll, DualityRoll, D20Roll, DamageRoll];
|
CONFIG.Actor.documentClass = documents.DhpActor;
|
||||||
Roll.CHAT_TEMPLATE = 'systems/daggerheart/templates/ui/chat/foundryRoll.hbs';
|
CONFIG.Actor.dataModels = models.actors.config;
|
||||||
Roll.TOOLTIP_TEMPLATE = 'systems/daggerheart/templates/ui/chat/foundryRollTooltip.hbs';
|
|
||||||
|
CONFIG.Item.documentClass = documents.DHItem;
|
||||||
|
CONFIG.Item.dataModels = models.items.config;
|
||||||
|
|
||||||
|
CONFIG.ActiveEffect.documentClass = documents.DhActiveEffect;
|
||||||
|
CONFIG.ActiveEffect.dataModels = models.activeEffects.config;
|
||||||
|
|
||||||
|
CONFIG.Combat.documentClass = documents.DhpCombat;
|
||||||
|
CONFIG.Combat.dataModels = { base: models.DhCombat };
|
||||||
|
CONFIG.Combatant.documentClass = documents.DHCombatant;
|
||||||
|
CONFIG.Combatant.dataModels = { base: models.DhCombatant };
|
||||||
|
|
||||||
|
CONFIG.ChatMessage.dataModels = models.chatMessages.config;
|
||||||
|
CONFIG.ChatMessage.documentClass = documents.DhChatMessage;
|
||||||
|
CONFIG.ChatMessage.template = 'systems/daggerheart/templates/ui/chat/chat-message.hbs';
|
||||||
|
|
||||||
|
CONFIG.Canvas.rulerClass = placeables.DhRuler;
|
||||||
|
CONFIG.Canvas.layers.templates.layerClass = placeables.DhTemplateLayer;
|
||||||
CONFIG.MeasuredTemplate.objectClass = placeables.DhMeasuredTemplate;
|
CONFIG.MeasuredTemplate.objectClass = placeables.DhMeasuredTemplate;
|
||||||
|
|
||||||
const { DocumentSheetConfig } = foundry.applications.apps;
|
CONFIG.Scene.documentClass = documents.DhScene;
|
||||||
|
|
||||||
CONFIG.Token.documentClass = documents.DhToken;
|
CONFIG.Token.documentClass = documents.DhToken;
|
||||||
CONFIG.Token.prototypeSheetClass = applications.sheetConfigs.DhPrototypeTokenConfig;
|
CONFIG.Token.prototypeSheetClass = applications.sheetConfigs.DhPrototypeTokenConfig;
|
||||||
|
CONFIG.Token.objectClass = placeables.DhTokenPlaceable;
|
||||||
|
CONFIG.Token.rulerClass = placeables.DhTokenRuler;
|
||||||
|
CONFIG.Token.hudClass = applications.hud.DHTokenHUD;
|
||||||
|
|
||||||
|
CONFIG.ui.combat = applications.ui.DhCombatTracker;
|
||||||
|
CONFIG.ui.chat = applications.ui.DhChatLog;
|
||||||
|
CONFIG.ui.effectsDisplay = applications.ui.DhEffectsDisplay;
|
||||||
|
CONFIG.ui.hotbar = applications.ui.DhHotbar;
|
||||||
|
CONFIG.ui.sidebar = applications.sidebar.DhSidebar;
|
||||||
|
CONFIG.ui.actors = applications.sidebar.DhActorDirectory;
|
||||||
|
CONFIG.ui.daggerheartMenu = applications.sidebar.DaggerheartMenu;
|
||||||
|
CONFIG.ui.resources = applications.ui.DhFearTracker;
|
||||||
|
CONFIG.ui.countdowns = applications.ui.DhCountdowns;
|
||||||
|
CONFIG.ux.ContextMenu = applications.ux.DHContextMenu;
|
||||||
|
CONFIG.ux.TooltipManager = documents.DhTooltipManager;
|
||||||
|
CONFIG.ux.TemplateManager = new TemplateManager();
|
||||||
|
|
||||||
|
Hooks.once('init', () => {
|
||||||
|
game.system.api = {
|
||||||
|
applications,
|
||||||
|
data,
|
||||||
|
models,
|
||||||
|
documents,
|
||||||
|
dice,
|
||||||
|
fields
|
||||||
|
};
|
||||||
|
|
||||||
|
const { DocumentSheetConfig } = foundry.applications.apps;
|
||||||
DocumentSheetConfig.unregisterSheet(TokenDocument, 'core', foundry.applications.sheets.TokenConfig);
|
DocumentSheetConfig.unregisterSheet(TokenDocument, 'core', foundry.applications.sheets.TokenConfig);
|
||||||
DocumentSheetConfig.registerSheet(TokenDocument, SYSTEM.id, applications.sheetConfigs.DhTokenConfig, {
|
DocumentSheetConfig.registerSheet(TokenDocument, SYSTEM.id, applications.sheetConfigs.DhTokenConfig, {
|
||||||
makeDefault: true
|
makeDefault: true
|
||||||
});
|
});
|
||||||
|
|
||||||
CONFIG.Item.documentClass = documents.DHItem;
|
const sheetLabel = typePath => () =>
|
||||||
|
game.i18n.format('DAGGERHEART.GENERAL.typeSheet', {
|
||||||
//Registering the Item DataModel
|
type: game.i18n.localize(typePath)
|
||||||
CONFIG.Item.dataModels = models.items.config;
|
});
|
||||||
|
|
||||||
const { Items, Actors } = foundry.documents.collections;
|
const { Items, Actors } = foundry.documents.collections;
|
||||||
Items.unregisterSheet('core', foundry.applications.sheets.ItemSheetV2);
|
Items.unregisterSheet('core', foundry.applications.sheets.ItemSheetV2);
|
||||||
Items.registerSheet(SYSTEM.id, applications.sheets.items.Ancestry, { types: ['ancestry'], makeDefault: true });
|
Items.registerSheet(SYSTEM.id, applications.sheets.items.Ancestry, {
|
||||||
Items.registerSheet(SYSTEM.id, applications.sheets.items.Community, { types: ['community'], makeDefault: true });
|
types: ['ancestry'],
|
||||||
Items.registerSheet(SYSTEM.id, applications.sheets.items.Class, { types: ['class'], makeDefault: true });
|
makeDefault: true,
|
||||||
Items.registerSheet(SYSTEM.id, applications.sheets.items.Subclass, { types: ['subclass'], makeDefault: true });
|
label: sheetLabel('TYPES.Item.ancestry')
|
||||||
Items.registerSheet(SYSTEM.id, applications.sheets.items.Feature, { types: ['feature'], makeDefault: true });
|
});
|
||||||
Items.registerSheet(SYSTEM.id, applications.sheets.items.DomainCard, { types: ['domainCard'], makeDefault: true });
|
Items.registerSheet(SYSTEM.id, applications.sheets.items.Community, {
|
||||||
|
types: ['community'],
|
||||||
|
makeDefault: true,
|
||||||
|
label: sheetLabel('TYPES.Item.community')
|
||||||
|
});
|
||||||
|
Items.registerSheet(SYSTEM.id, applications.sheets.items.Class, {
|
||||||
|
types: ['class'],
|
||||||
|
makeDefault: true,
|
||||||
|
label: sheetLabel('TYPES.Item.class')
|
||||||
|
});
|
||||||
|
Items.registerSheet(SYSTEM.id, applications.sheets.items.Subclass, {
|
||||||
|
types: ['subclass'],
|
||||||
|
makeDefault: true,
|
||||||
|
label: sheetLabel('TYPES.Item.subclass')
|
||||||
|
});
|
||||||
|
Items.registerSheet(SYSTEM.id, applications.sheets.items.Feature, {
|
||||||
|
types: ['feature'],
|
||||||
|
makeDefault: true,
|
||||||
|
label: sheetLabel('TYPES.Item.feature')
|
||||||
|
});
|
||||||
|
Items.registerSheet(SYSTEM.id, applications.sheets.items.DomainCard, {
|
||||||
|
types: ['domainCard'],
|
||||||
|
makeDefault: true,
|
||||||
|
label: sheetLabel('TYPES.Item.domainCard')
|
||||||
|
});
|
||||||
Items.registerSheet(SYSTEM.id, applications.sheets.items.Loot, {
|
Items.registerSheet(SYSTEM.id, applications.sheets.items.Loot, {
|
||||||
types: ['loot'],
|
types: ['loot'],
|
||||||
makeDefault: true
|
makeDefault: true,
|
||||||
|
label: sheetLabel('TYPES.Item.loot')
|
||||||
|
});
|
||||||
|
Items.registerSheet(SYSTEM.id, applications.sheets.items.Consumable, {
|
||||||
|
types: ['consumable'],
|
||||||
|
makeDefault: true,
|
||||||
|
label: sheetLabel('TYPES.Item.consumable')
|
||||||
|
});
|
||||||
|
Items.registerSheet(SYSTEM.id, applications.sheets.items.Weapon, {
|
||||||
|
types: ['weapon'],
|
||||||
|
makeDefault: true,
|
||||||
|
label: sheetLabel('TYPES.Item.weapon')
|
||||||
|
});
|
||||||
|
Items.registerSheet(SYSTEM.id, applications.sheets.items.Armor, {
|
||||||
|
types: ['armor'],
|
||||||
|
makeDefault: true,
|
||||||
|
label: sheetLabel('TYPES.Item.armor')
|
||||||
|
});
|
||||||
|
Items.registerSheet(SYSTEM.id, applications.sheets.items.Beastform, {
|
||||||
|
types: ['beastform'],
|
||||||
|
makeDefault: true,
|
||||||
|
label: sheetLabel('TYPES.Item.beastform')
|
||||||
});
|
});
|
||||||
Items.registerSheet(SYSTEM.id, applications.sheets.items.Consumable, { types: ['consumable'], makeDefault: true });
|
|
||||||
Items.registerSheet(SYSTEM.id, applications.sheets.items.Weapon, { types: ['weapon'], makeDefault: true });
|
|
||||||
Items.registerSheet(SYSTEM.id, applications.sheets.items.Armor, { types: ['armor'], makeDefault: true });
|
|
||||||
Items.registerSheet(SYSTEM.id, applications.sheets.items.Beastform, { types: ['beastform'], makeDefault: true });
|
|
||||||
|
|
||||||
CONFIG.Actor.documentClass = documents.DhpActor;
|
|
||||||
CONFIG.Actor.dataModels = models.actors.config;
|
|
||||||
|
|
||||||
Actors.unregisterSheet('core', foundry.applications.sheets.ActorSheetV2);
|
Actors.unregisterSheet('core', foundry.applications.sheets.ActorSheetV2);
|
||||||
Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Character, { types: ['character'], makeDefault: true });
|
Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Character, {
|
||||||
Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Companion, { types: ['companion'], makeDefault: true });
|
types: ['character'],
|
||||||
Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Adversary, { types: ['adversary'], makeDefault: true });
|
makeDefault: true,
|
||||||
|
label: sheetLabel('TYPES.Actor.character')
|
||||||
|
});
|
||||||
|
Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Companion, {
|
||||||
|
types: ['companion'],
|
||||||
|
makeDefault: true,
|
||||||
|
label: sheetLabel('TYPES.Actor.companion')
|
||||||
|
});
|
||||||
|
Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Adversary, {
|
||||||
|
types: ['adversary'],
|
||||||
|
makeDefault: true,
|
||||||
|
label: sheetLabel('TYPES.Actor.adversary')
|
||||||
|
});
|
||||||
Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Environment, {
|
Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Environment, {
|
||||||
types: ['environment'],
|
types: ['environment'],
|
||||||
makeDefault: true
|
makeDefault: true,
|
||||||
|
label: sheetLabel('TYPES.Actor.environment')
|
||||||
|
});
|
||||||
|
Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Party, {
|
||||||
|
types: ['party'],
|
||||||
|
makeDefault: true,
|
||||||
|
label: sheetLabel('TYPES.Actor.party')
|
||||||
});
|
});
|
||||||
|
|
||||||
CONFIG.ActiveEffect.documentClass = documents.DhActiveEffect;
|
|
||||||
CONFIG.ActiveEffect.dataModels = models.activeEffects.config;
|
|
||||||
|
|
||||||
DocumentSheetConfig.unregisterSheet(
|
DocumentSheetConfig.unregisterSheet(
|
||||||
CONFIG.ActiveEffect.documentClass,
|
CONFIG.ActiveEffect.documentClass,
|
||||||
|
|
@ -109,58 +190,56 @@ Hooks.once('init', () => {
|
||||||
SYSTEM.id,
|
SYSTEM.id,
|
||||||
applications.sheetConfigs.ActiveEffectConfig,
|
applications.sheetConfigs.ActiveEffectConfig,
|
||||||
{
|
{
|
||||||
makeDefault: true
|
makeDefault: true,
|
||||||
|
label: sheetLabel('DOCUMENT.ActiveEffect')
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
CONFIG.Token.hudClass = applications.hud.DHTokenHUD;
|
|
||||||
|
|
||||||
CONFIG.Combat.dataModels = {
|
|
||||||
base: models.DhCombat
|
|
||||||
};
|
|
||||||
|
|
||||||
CONFIG.Combatant.dataModels = {
|
|
||||||
base: models.DhCombatant
|
|
||||||
};
|
|
||||||
|
|
||||||
CONFIG.ChatMessage.dataModels = models.chatMessages.config;
|
|
||||||
CONFIG.ChatMessage.documentClass = documents.DhChatMessage;
|
|
||||||
CONFIG.ChatMessage.template = 'systems/daggerheart/templates/ui/chat/chat-message.hbs';
|
|
||||||
|
|
||||||
CONFIG.Canvas.rulerClass = placeables.DhRuler;
|
|
||||||
CONFIG.Canvas.layers.templates.layerClass = placeables.DhTemplateLayer;
|
|
||||||
CONFIG.Token.objectClass = placeables.DhTokenPlaceable;
|
|
||||||
CONFIG.Combat.documentClass = documents.DhpCombat;
|
|
||||||
CONFIG.ui.combat = applications.ui.DhCombatTracker;
|
|
||||||
CONFIG.ui.chat = applications.ui.DhChatLog;
|
|
||||||
CONFIG.ui.hotbar = applications.ui.DhHotbar;
|
|
||||||
CONFIG.Token.rulerClass = placeables.DhTokenRuler;
|
|
||||||
|
|
||||||
CONFIG.ui.resources = applications.ui.DhFearTracker;
|
|
||||||
CONFIG.ux.ContextMenu = applications.ux.DHContextMenu;
|
|
||||||
CONFIG.ux.TooltipManager = documents.DhTooltipManager;
|
|
||||||
|
|
||||||
CONFIG.ux.TemplateManager = new TemplateManager();
|
|
||||||
|
|
||||||
game.socket.on(`system.${SYSTEM.id}`, socketRegistration.handleSocketEvent);
|
game.socket.on(`system.${SYSTEM.id}`, socketRegistration.handleSocketEvent);
|
||||||
|
|
||||||
// Make Compendium Dialog resizable
|
// Make Compendium Dialog resizable
|
||||||
foundry.applications.sidebar.apps.Compendium.DEFAULT_OPTIONS.window.resizable = true;
|
foundry.applications.sidebar.apps.Compendium.DEFAULT_OPTIONS.window.resizable = true;
|
||||||
|
|
||||||
|
DocumentSheetConfig.unregisterSheet(foundry.documents.Scene, 'core', foundry.applications.sheets.SceneConfig);
|
||||||
|
DocumentSheetConfig.registerSheet(foundry.documents.Scene, SYSTEM.id, applications.scene.DhSceneConfigSettings, {
|
||||||
|
makeDefault: true,
|
||||||
|
label: sheetLabel('DOCUMENT.Scene')
|
||||||
|
});
|
||||||
|
|
||||||
settingsRegistration.registerDHSettings();
|
settingsRegistration.registerDHSettings();
|
||||||
RegisterHandlebarsHelpers.registerHelpers();
|
RegisterHandlebarsHelpers.registerHelpers();
|
||||||
|
|
||||||
return handlebarsRegistration();
|
return handlebarsRegistration();
|
||||||
});
|
});
|
||||||
|
|
||||||
Hooks.on('ready', async () => {
|
Hooks.on('setup', () => {
|
||||||
ui.resources = new CONFIG.ui.resources();
|
CONFIG.statusEffects = [
|
||||||
if (game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.appearance).displayFear !== 'hide')
|
...CONFIG.statusEffects.filter(x => !['dead', 'unconscious'].includes(x.id)),
|
||||||
ui.resources.render({ force: true });
|
...Object.values(SYSTEM.GENERAL.conditions()).map(x => ({
|
||||||
|
...x,
|
||||||
|
name: game.i18n.localize(x.name),
|
||||||
|
systemEffect: true
|
||||||
|
}))
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
Hooks.on('ready', async () => {
|
||||||
|
const appearanceSettings = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.appearance);
|
||||||
|
ui.resources = new CONFIG.ui.resources();
|
||||||
|
if (appearanceSettings.displayFear !== 'hide') ui.resources.render({ force: true });
|
||||||
|
|
||||||
|
if (appearanceSettings.displayCountdownUI) {
|
||||||
|
ui.countdowns = new CONFIG.ui.countdowns();
|
||||||
|
ui.countdowns.render({ force: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.effectsDisplay = new CONFIG.ui.effectsDisplay();
|
||||||
|
ui.effectsDisplay.render({ force: true });
|
||||||
|
|
||||||
|
if (!(ui.compendiumBrowser instanceof applications.ui.ItemBrowser))
|
||||||
|
ui.compendiumBrowser = new applications.ui.ItemBrowser();
|
||||||
|
|
||||||
registerCountdownHooks();
|
|
||||||
socketRegistration.registerSocketHooks();
|
socketRegistration.registerSocketHooks();
|
||||||
registerRollDiceHooks();
|
|
||||||
socketRegistration.registerUserQueries();
|
socketRegistration.registerUserQueries();
|
||||||
|
|
||||||
if (!game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.welcomeMessage)) {
|
if (!game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.welcomeMessage)) {
|
||||||
|
|
@ -170,13 +249,15 @@ Hooks.on('ready', async () => {
|
||||||
game.user.setFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.welcomeMessage, true);
|
game.user.setFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.welcomeMessage, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
runMigrations();
|
||||||
});
|
});
|
||||||
|
|
||||||
Hooks.once('dicesoniceready', () => {});
|
Hooks.once('dicesoniceready', () => {});
|
||||||
|
|
||||||
Hooks.on('renderChatMessageHTML', (_, element, message) => {
|
Hooks.on('renderChatMessageHTML', (document, element) => {
|
||||||
enricherRenderSetup(element);
|
enricherRenderSetup(element);
|
||||||
const cssClass = message.message.flags?.daggerheart?.cssClass;
|
const cssClass = document.flags?.daggerheart?.cssClass;
|
||||||
if (cssClass) cssClass.split(' ').forEach(cls => element.classList.add(cls));
|
if (cssClass) cssClass.split(' ').forEach(cls => element.classList.add(cls));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -229,73 +310,72 @@ Hooks.on('chatMessage', (_, message) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Hooks.on('renderJournalDirectory', async (tab, html, _, options) => {
|
const updateActorsRangeDependentEffects = async token => {
|
||||||
if (tab.id === 'journal') {
|
|
||||||
if (options.parts && !options.parts.includes('footer')) return;
|
|
||||||
|
|
||||||
const buttons = tab.element.querySelector('.directory-footer.action-buttons');
|
|
||||||
const title = game.i18n.format('DAGGERHEART.APPLICATIONS.Countdown.title', {
|
|
||||||
type: game.i18n.localize('DAGGERHEART.APPLICATIONS.Countdown.types.narrative')
|
|
||||||
});
|
|
||||||
buttons.insertAdjacentHTML(
|
|
||||||
'afterbegin',
|
|
||||||
`
|
|
||||||
<button id="narrative-countdown-button">
|
|
||||||
<i class="fa-solid fa-stopwatch"></i>
|
|
||||||
<span style="font-weight: 400; font-family: var(--font-sans);">${title}</span>
|
|
||||||
</button>`
|
|
||||||
);
|
|
||||||
|
|
||||||
buttons.querySelector('#narrative-countdown-button').onclick = async () => {
|
|
||||||
new NarrativeCountdowns().open();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Hooks.on('moveToken', async (movedToken, data) => {
|
|
||||||
const effectsAutomation = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).effects;
|
|
||||||
if (!effectsAutomation.rangeDependent) return;
|
|
||||||
|
|
||||||
const rangeDependantEffects = movedToken.actor.effects.filter(effect => effect.system.rangeDependence?.enabled);
|
|
||||||
|
|
||||||
const updateEffects = async (disposition, token, effects, effectUpdates) => {
|
|
||||||
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
|
||||||
).rangeMeasurement;
|
).rangeMeasurement;
|
||||||
|
|
||||||
for (let effect of effects.filter(x => x.system.rangeDependence?.enabled)) {
|
for (let effect of token.actor?.allApplicableEffects() ?? []) {
|
||||||
|
if (!effect.system.rangeDependence?.enabled) continue;
|
||||||
const { target, range, type } = effect.system.rangeDependence;
|
const { target, range, type } = effect.system.rangeDependence;
|
||||||
if ((target === 'friendly' && disposition !== 1) || (target === 'hostile' && disposition !== -1))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const distanceBetween = canvas.grid.measurePath([
|
// If there are no targets, assume false. Otherwise, start with the effect enabled.
|
||||||
{ ...movedToken.toObject(), x: data.destination.x, y: data.destination.y },
|
let enabledEffect = game.user.targets.size !== 0;
|
||||||
token
|
// Expect all targets to meet the rangeDependence requirements
|
||||||
]).distance;
|
for (let userTarget of game.user.targets) {
|
||||||
const distance = rangeMeasurement[range];
|
const disposition = userTarget.document.disposition;
|
||||||
|
if ((target === 'friendly' && disposition !== 1) || (target === 'hostile' && disposition !== -1)) {
|
||||||
|
enabledEffect = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get required distance and special case 5 feet to test adjacency
|
||||||
|
const required = rangeMeasurement[range];
|
||||||
const reverse = type === CONFIG.DH.GENERAL.rangeInclusion.outsideRange.id;
|
const reverse = type === CONFIG.DH.GENERAL.rangeInclusion.outsideRange.id;
|
||||||
const newDisabled = reverse ? distanceBetween <= distance : distanceBetween > distance;
|
const inRange =
|
||||||
const oldDisabled = effectUpdates[effect.uuid] ? effectUpdates[effect.uuid].disabled : newDisabled;
|
required === 5
|
||||||
effectUpdates[effect.uuid] = {
|
? userTarget.isAdjacentWith(token.object)
|
||||||
disabled: oldDisabled || newDisabled,
|
: userTarget.distanceTo(token.object) <= required;
|
||||||
value: effect
|
if (reverse ? inRange : !inRange) {
|
||||||
};
|
enabledEffect = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await effect.update({ disabled: !enabledEffect });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const effectUpdates = {};
|
const updateAllRangeDependentEffects = async () => {
|
||||||
for (let token of game.scenes.find(x => x.active).tokens) {
|
const effectsAutomation = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).effects;
|
||||||
if (token.id !== movedToken.id) {
|
if (!effectsAutomation.rangeDependent) return;
|
||||||
await updateEffects(token.disposition, token, rangeDependantEffects, effectUpdates);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (token.actor) await updateEffects(movedToken.disposition, token, token.actor.effects, effectUpdates);
|
const tokens = canvas.scene.tokens;
|
||||||
|
if (game.user.character) {
|
||||||
|
// The character updates their character's token. There can be only one token.
|
||||||
|
const characterToken = tokens.find(x => x.actor === game.user.character);
|
||||||
|
updateActorsRangeDependentEffects(characterToken);
|
||||||
|
} else if (game.user.isActiveGM) {
|
||||||
|
// The GM is responsible for all other tokens.
|
||||||
|
const playerCharacters = game.users.players.filter(x => x.active).map(x => x.character);
|
||||||
|
for (const token of tokens.filter(x => !playerCharacters.includes(x.actor))) {
|
||||||
|
updateActorsRangeDependentEffects(token);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
for (let key in effectUpdates) {
|
const debouncedRangeEffectCall = foundry.utils.debounce(updateAllRangeDependentEffects, 50);
|
||||||
const effect = effectUpdates[key];
|
|
||||||
await effect.value.update({ disabled: effect.disabled });
|
Hooks.on('targetToken', () => {
|
||||||
|
debouncedRangeEffectCall();
|
||||||
|
});
|
||||||
|
|
||||||
|
Hooks.on('refreshToken', (_, options) => {
|
||||||
|
if (options.refreshPosition) {
|
||||||
|
debouncedRangeEffectCall();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Hooks.on('renderCompendiumDirectory', (app, html) => applications.ui.ItemBrowser.injectSidebarButton(html));
|
||||||
|
Hooks.on('renderDocumentDirectory', (app, html) => applications.ui.ItemBrowser.injectSidebarButton(html));
|
||||||
|
|
|
||||||
520
lang/en.json
520
lang/en.json
File diff suppressed because it is too large
Load diff
|
|
@ -2,8 +2,10 @@ export * as characterCreation from './characterCreation/_module.mjs';
|
||||||
export * as dialogs from './dialogs/_module.mjs';
|
export * as dialogs from './dialogs/_module.mjs';
|
||||||
export * as hud from './hud/_module.mjs';
|
export * as hud from './hud/_module.mjs';
|
||||||
export * as levelup from './levelup/_module.mjs';
|
export * as levelup from './levelup/_module.mjs';
|
||||||
|
export * as scene from './scene/_module.mjs';
|
||||||
export * as settings from './settings/_module.mjs';
|
export * as settings from './settings/_module.mjs';
|
||||||
export * as sheets from './sheets/_module.mjs';
|
export * as sheets from './sheets/_module.mjs';
|
||||||
export * as sheetConfigs from './sheets-configs/_module.mjs';
|
export * as sheetConfigs from './sheets-configs/_module.mjs';
|
||||||
|
export * as sidebar from './sidebar/_module.mjs';
|
||||||
export * as ui from './ui/_module.mjs';
|
export * as ui from './ui/_module.mjs';
|
||||||
export * as ux from './ux/_module.mjs';
|
export * as ux from './ux/_module.mjs';
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { abilities } from '../../config/actorConfig.mjs';
|
import { abilities } from '../../config/actorConfig.mjs';
|
||||||
import { burden } from '../../config/generalConfig.mjs';
|
import { burden } from '../../config/generalConfig.mjs';
|
||||||
import { ItemBrowser } from '../ui/itemBrowser.mjs';
|
|
||||||
import { createEmbeddedItemsWithEffects, createEmbeddedItemWithEffects } from '../../helpers/utils.mjs';
|
import { createEmbeddedItemsWithEffects, createEmbeddedItemWithEffects } from '../../helpers/utils.mjs';
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
@ -46,8 +45,6 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
||||||
};
|
};
|
||||||
|
|
||||||
this._dragDrop = this._createDragDropHandlers();
|
this._dragDrop = this._createDragDropHandlers();
|
||||||
|
|
||||||
this.itemBrowser = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get title() {
|
get title() {
|
||||||
|
|
@ -86,9 +83,9 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
||||||
|
|
||||||
static PARTS = {
|
static PARTS = {
|
||||||
tabs: { template: 'systems/daggerheart/templates/characterCreation/tabs.hbs' },
|
tabs: { template: 'systems/daggerheart/templates/characterCreation/tabs.hbs' },
|
||||||
|
class: { template: 'systems/daggerheart/templates/characterCreation/tabs/class.hbs' },
|
||||||
ancestry: { template: 'systems/daggerheart/templates/characterCreation/tabs/ancestry.hbs' },
|
ancestry: { template: 'systems/daggerheart/templates/characterCreation/tabs/ancestry.hbs' },
|
||||||
community: { template: 'systems/daggerheart/templates/characterCreation/tabs/community.hbs' },
|
community: { template: 'systems/daggerheart/templates/characterCreation/tabs/community.hbs' },
|
||||||
class: { template: 'systems/daggerheart/templates/characterCreation/tabs/class.hbs' },
|
|
||||||
traits: { template: 'systems/daggerheart/templates/characterCreation/tabs/traits.hbs' },
|
traits: { template: 'systems/daggerheart/templates/characterCreation/tabs/traits.hbs' },
|
||||||
experience: { template: 'systems/daggerheart/templates/characterCreation/tabs/experience.hbs' },
|
experience: { template: 'systems/daggerheart/templates/characterCreation/tabs/experience.hbs' },
|
||||||
domainCards: { template: 'systems/daggerheart/templates/characterCreation/tabs/domainCards.hbs' },
|
domainCards: { template: 'systems/daggerheart/templates/characterCreation/tabs/domainCards.hbs' },
|
||||||
|
|
@ -98,6 +95,13 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
||||||
};
|
};
|
||||||
|
|
||||||
static TABS = {
|
static TABS = {
|
||||||
|
class: {
|
||||||
|
active: false,
|
||||||
|
cssClass: '',
|
||||||
|
group: 'setup',
|
||||||
|
id: 'class',
|
||||||
|
label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.tabs.class'
|
||||||
|
},
|
||||||
ancestry: {
|
ancestry: {
|
||||||
active: true,
|
active: true,
|
||||||
cssClass: '',
|
cssClass: '',
|
||||||
|
|
@ -112,13 +116,6 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
||||||
id: 'community',
|
id: 'community',
|
||||||
label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.tabs.community'
|
label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.tabs.community'
|
||||||
},
|
},
|
||||||
class: {
|
|
||||||
active: false,
|
|
||||||
cssClass: '',
|
|
||||||
group: 'setup',
|
|
||||||
id: 'class',
|
|
||||||
label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.tabs.class'
|
|
||||||
},
|
|
||||||
traits: {
|
traits: {
|
||||||
active: false,
|
active: false,
|
||||||
cssClass: '',
|
cssClass: '',
|
||||||
|
|
@ -159,10 +156,10 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
||||||
v.cssClass = v.active ? 'active' : '';
|
v.cssClass = v.active ? 'active' : '';
|
||||||
|
|
||||||
switch (v.id) {
|
switch (v.id) {
|
||||||
case 'community':
|
case 'ancestry':
|
||||||
v.disabled = this.setup.visibility < 2;
|
v.disabled = this.setup.visibility < 2;
|
||||||
break;
|
break;
|
||||||
case 'class':
|
case 'community':
|
||||||
v.disabled = this.setup.visibility < 3;
|
v.disabled = this.setup.visibility < 3;
|
||||||
break;
|
break;
|
||||||
case 'traits':
|
case 'traits':
|
||||||
|
|
@ -195,7 +192,7 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
||||||
}
|
}
|
||||||
|
|
||||||
async _prepareContext(_options) {
|
async _prepareContext(_options) {
|
||||||
this.tabGroups.setup = this.tabGroups.setup ?? 'ancestry';
|
this.tabGroups.setup = this.tabGroups.setup ?? 'class';
|
||||||
const context = await super._prepareContext(_options);
|
const context = await super._prepareContext(_options);
|
||||||
|
|
||||||
context.tabs = this._getTabs(this.constructor.TABS);
|
context.tabs = this._getTabs(this.constructor.TABS);
|
||||||
|
|
@ -269,13 +266,13 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
||||||
context.isLastTab = this.tabGroups.setup === 'equipment';
|
context.isLastTab = this.tabGroups.setup === 'equipment';
|
||||||
switch (this.tabGroups.setup) {
|
switch (this.tabGroups.setup) {
|
||||||
case null:
|
case null:
|
||||||
case 'ancestry':
|
case 'class':
|
||||||
context.nextDisabled = this.setup.visibility === 1;
|
context.nextDisabled = this.setup.visibility === 1;
|
||||||
break;
|
break;
|
||||||
case 'community':
|
case 'ancestry':
|
||||||
context.nextDisabled = this.setup.visibility === 2;
|
context.nextDisabled = this.setup.visibility === 2;
|
||||||
break;
|
break;
|
||||||
case 'class':
|
case 'community':
|
||||||
context.nextDisabled = this.setup.visibility === 3;
|
context.nextDisabled = this.setup.visibility === 3;
|
||||||
break;
|
break;
|
||||||
case 'traits':
|
case 'traits':
|
||||||
|
|
@ -366,11 +363,11 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
||||||
case 4:
|
case 4:
|
||||||
return this.getNrSelectedTrait() === 6 ? 5 : 4;
|
return this.getNrSelectedTrait() === 6 ? 5 : 4;
|
||||||
case 3:
|
case 3:
|
||||||
return this.setup.class.uuid && this.setup.subclass.uuid ? 4 : 3;
|
return this.setup.community.uuid ? 4 : 3;
|
||||||
case 2:
|
case 2:
|
||||||
return this.setup.community.uuid ? 3 : 2;
|
return this.setup.primaryAncestry.uuid ? 3 : 2;
|
||||||
case 1:
|
case 1:
|
||||||
return this.setup.primaryAncestry.uuid ? 2 : 1;
|
return this.setup.class.uuid && this.setup.subclass.uuid ? 2 : 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -425,26 +422,30 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
||||||
equipment = ['armor', 'weapon'];
|
equipment = ['armor', 'weapon'];
|
||||||
|
|
||||||
const presets = {
|
const presets = {
|
||||||
compendium: 'daggerheart',
|
folder: equipment.includes(type) ? `equipments.folders.${type}s` : type,
|
||||||
folder: equipment.includes(type) ? 'equipments' : type,
|
|
||||||
render: {
|
render: {
|
||||||
noFolder: true
|
noFolder: true
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (type == 'domains')
|
if (type === 'domains')
|
||||||
presets.filter = {
|
presets.filter = {
|
||||||
'level.max': { key: 'level.max', value: 1 },
|
'level.max': { key: 'level.max', value: 1 },
|
||||||
'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')
|
||||||
|
presets.filter = {
|
||||||
|
'system.linkedClass.uuid': { key: 'system.linkedClass.uuid', value: this.setup.class?.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 }
|
||||||
};
|
};
|
||||||
|
|
||||||
return (this.itemBrowser = await new ItemBrowser({ presets }).render({ force: true }));
|
ui.compendiumBrowser.open(presets);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async viewItem(_, target) {
|
static async viewItem(_, target) {
|
||||||
|
|
@ -472,10 +473,10 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
||||||
static setupGoNext() {
|
static setupGoNext() {
|
||||||
switch (this.setup.visibility) {
|
switch (this.setup.visibility) {
|
||||||
case 2:
|
case 2:
|
||||||
this.tabGroups.setup = 'community';
|
this.tabGroups.setup = 'ancestry';
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
this.tabGroups.setup = 'class';
|
this.tabGroups.setup = 'community';
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
this.tabGroups.setup = 'traits';
|
this.tabGroups.setup = 'traits';
|
||||||
|
|
@ -494,7 +495,9 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
static async finish() {
|
static async finish(_, button) {
|
||||||
|
button.disabled = true;
|
||||||
|
|
||||||
const primaryAncestryFeature = this.setup.primaryAncestry.system.primaryFeature;
|
const primaryAncestryFeature = this.setup.primaryAncestry.system.primaryFeature;
|
||||||
const secondaryAncestryFeature = this.setup.secondaryAncestry?.uuid
|
const secondaryAncestryFeature = this.setup.secondaryAncestry?.uuid
|
||||||
? this.setup.secondaryAncestry.system.secondaryFeature
|
? this.setup.secondaryAncestry.system.secondaryFeature
|
||||||
|
|
@ -560,7 +563,7 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
||||||
{ overwrite: true }
|
{ overwrite: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
if (this.itemBrowser) this.itemBrowser.close();
|
if (ui.compendiumBrowser) ui.compendiumBrowser.close();
|
||||||
this.close();
|
this.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,16 @@
|
||||||
|
export { default as AttributionDialog } from './attributionDialog.mjs';
|
||||||
export { default as BeastformDialog } from './beastformDialog.mjs';
|
export { default as BeastformDialog } from './beastformDialog.mjs';
|
||||||
export { default as d20RollDialog } from './d20RollDialog.mjs';
|
export { default as d20RollDialog } from './d20RollDialog.mjs';
|
||||||
export { default as DamageDialog } from './damageDialog.mjs';
|
export { default as DamageDialog } from './damageDialog.mjs';
|
||||||
export { default as DamageReductionDialog } from './damageReductionDialog.mjs';
|
export { default as DamageReductionDialog } from './damageReductionDialog.mjs';
|
||||||
export { default as DeathMove } from './deathMove.mjs';
|
export { default as DeathMove } from './deathMove.mjs';
|
||||||
export { default as Downtime } from './downtime.mjs';
|
export { default as Downtime } from './downtime.mjs';
|
||||||
|
export { default as ImageSelectDialog } from './imageSelectDialog.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 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';
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,11 @@ export default class ActionSelectionDialog extends HandlebarsApplicationMixin(Ap
|
||||||
|
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
async _prepareContext(options) {
|
async _prepareContext(options) {
|
||||||
const actions = this.#item.system.actionsList,
|
const actions = this.#item.system.actionsList.map(action => ({
|
||||||
|
...action.toObject(),
|
||||||
|
id: action.id,
|
||||||
|
img: action.baseAction ? action.parent.parent.img : action.img
|
||||||
|
})),
|
||||||
itemName = this.#item.name;
|
itemName = this.#item.name;
|
||||||
return {
|
return {
|
||||||
...(await super._prepareContext(options)),
|
...(await super._prepareContext(options)),
|
||||||
|
|
|
||||||
93
module/applications/dialogs/attributionDialog.mjs
Normal file
93
module/applications/dialogs/attributionDialog.mjs
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
import autocomplete from 'autocompleter';
|
||||||
|
|
||||||
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
||||||
|
export default class AttributionDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
|
constructor(item) {
|
||||||
|
super({});
|
||||||
|
|
||||||
|
this.item = item;
|
||||||
|
this.sources = Object.keys(CONFIG.DH.GENERAL.attributionSources).flatMap(groupKey => {
|
||||||
|
const group = CONFIG.DH.GENERAL.attributionSources[groupKey];
|
||||||
|
return group.values.map(x => ({ group: group.label, ...x }));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get title() {
|
||||||
|
return game.i18n.localize('DAGGERHEART.APPLICATIONS.Attribution.title');
|
||||||
|
}
|
||||||
|
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
tag: 'form',
|
||||||
|
classes: ['daggerheart', 'dh-style', 'dialog', 'views', 'attribution'],
|
||||||
|
position: { width: 'auto', height: 'auto' },
|
||||||
|
window: { icon: 'fa-solid fa-signature' },
|
||||||
|
form: { handler: this.updateData, submitOnChange: false, closeOnSubmit: true }
|
||||||
|
};
|
||||||
|
|
||||||
|
static PARTS = {
|
||||||
|
main: { template: 'systems/daggerheart/templates/dialogs/attribution.hbs' }
|
||||||
|
};
|
||||||
|
|
||||||
|
_attachPartListeners(partId, htmlElement, options) {
|
||||||
|
super._attachPartListeners(partId, htmlElement, options);
|
||||||
|
const sources = this.sources;
|
||||||
|
|
||||||
|
htmlElement.querySelectorAll('.attribution-input').forEach(element => {
|
||||||
|
autocomplete({
|
||||||
|
input: element,
|
||||||
|
fetch: function (text, update) {
|
||||||
|
if (!text) {
|
||||||
|
update(sources);
|
||||||
|
} else {
|
||||||
|
text = text.toLowerCase();
|
||||||
|
var suggestions = sources.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}`;
|
||||||
|
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 = item.label;
|
||||||
|
},
|
||||||
|
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.item = this.item;
|
||||||
|
context.data = this.item.system.attribution;
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async updateData(_event, _element, formData) {
|
||||||
|
await this.item.update({ 'system.attribution': formData.object });
|
||||||
|
this.item.sheet.refreshFrame();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -276,7 +276,29 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
|
||||||
const featureItem = item;
|
const featureItem = item;
|
||||||
app.addEventListener(
|
app.addEventListener(
|
||||||
'close',
|
'close',
|
||||||
() => resolve({ selected: app.selected, evolved: app.evolved, hybrid: app.hybrid, item: featureItem }),
|
async () => {
|
||||||
|
const selected = app.selected.toObject();
|
||||||
|
const evolved = app.evolved.form ? app.evolved.form.toObject() : null;
|
||||||
|
const data = await game.system.api.data.items.DHBeastform.getWildcardImage(
|
||||||
|
app.configData.data.parent,
|
||||||
|
evolved ?? app.selected
|
||||||
|
);
|
||||||
|
if (data) {
|
||||||
|
if (!data.selectedImage) selected = null;
|
||||||
|
else {
|
||||||
|
const imageSource = evolved ?? selected;
|
||||||
|
if (imageSource.usesDynamicToken) imageSource.system.tokenRingImg = data.selectedImage;
|
||||||
|
else imageSource.system.tokenImg = data.selectedImage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve({
|
||||||
|
selected: selected,
|
||||||
|
evolved: { ...app.evolved, form: evolved },
|
||||||
|
hybrid: app.hybrid,
|
||||||
|
item: featureItem
|
||||||
|
});
|
||||||
|
},
|
||||||
{ once: true }
|
{ once: true }
|
||||||
);
|
);
|
||||||
app.render({ force: true });
|
app.render({ force: true });
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { abilities } from '../../config/actorConfig.mjs';
|
||||||
|
|
||||||
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||||
|
|
||||||
export default class D20RollDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
export default class D20RollDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
|
|
@ -7,20 +9,20 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
this.roll = roll;
|
this.roll = roll;
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.config.experiences = [];
|
this.config.experiences = [];
|
||||||
this.reactionOverride = config.roll?.type === 'reaction';
|
this.reactionOverride = config.actionType === 'reaction';
|
||||||
|
|
||||||
if (config.source?.action) {
|
if (config.source?.action) {
|
||||||
this.item = config.data.parent.items.get(config.source.item) ?? config.data.parent;
|
this.item = config.data.parent.items.get(config.source.item) ?? config.data.parent;
|
||||||
this.action =
|
this.action =
|
||||||
config.data.attack?._id == config.source.action
|
config.data.attack?._id == config.source.action
|
||||||
? config.data.attack
|
? config.data.attack
|
||||||
: this.item.system.actions.get(config.source.action);
|
: this.item.system.actionsList?.find(a => a.id === config.source.action);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
tag: 'form',
|
tag: 'form',
|
||||||
id: 'roll-selection',
|
// id: 'roll-selection',
|
||||||
classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'roll-selection'],
|
classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'roll-selection'],
|
||||||
position: {
|
position: {
|
||||||
width: 'auto'
|
width: 'auto'
|
||||||
|
|
@ -32,6 +34,7 @@ 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,
|
||||||
submitRoll: this.submitRoll
|
submitRoll: this.submitRoll
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
|
|
@ -42,7 +45,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
};
|
};
|
||||||
|
|
||||||
get title() {
|
get title() {
|
||||||
return this.config.title;
|
return `${this.config.title}${this.actor ? `: ${this.actor.name}` : ''}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
get actor() {
|
get actor() {
|
||||||
|
|
@ -66,7 +69,7 @@ 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;
|
context.selectedRollMode = this.config.selectedRollMode ?? game.settings.get('core', 'rollMode');
|
||||||
context.rollModes = Object.entries(CONFIG.Dice.rollModes).map(([action, { label, icon }]) => ({
|
context.rollModes = Object.entries(CONFIG.Dice.rollModes).map(([action, { label, icon }]) => ({
|
||||||
action,
|
action,
|
||||||
label,
|
label,
|
||||||
|
|
@ -81,7 +84,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
);
|
);
|
||||||
context.costs = updatedCosts.map(x => ({
|
context.costs = updatedCosts.map(x => ({
|
||||||
...x,
|
...x,
|
||||||
label: x.keyIsID
|
label: x.itemId
|
||||||
? this.action.parent.parent.name
|
? this.action.parent.parent.name
|
||||||
: game.i18n.localize(CONFIG.DH.GENERAL.abilityCosts[x.key].label)
|
: game.i18n.localize(CONFIG.DH.GENERAL.abilityCosts[x.key].label)
|
||||||
}));
|
}));
|
||||||
|
|
@ -101,7 +104,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
context.roll = this.roll;
|
context.roll = this.roll;
|
||||||
context.rollType = this.roll?.constructor.name;
|
context.rollType = this.roll?.constructor.name;
|
||||||
context.rallyDie = this.roll.rallyChoices;
|
context.rallyDie = this.roll.rallyChoices;
|
||||||
const experiences = this.config.data?.experiences || {};
|
const experiences = this.config.data?.system?.experiences || {};
|
||||||
context.experiences = Object.keys(experiences).map(id => ({
|
context.experiences = Object.keys(experiences).map(id => ({
|
||||||
id,
|
id,
|
||||||
...experiences[id]
|
...experiences[id]
|
||||||
|
|
@ -113,15 +116,31 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
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);
|
||||||
|
if (this.actor?.system?.traits) context.abilities = this.getTraitModifiers();
|
||||||
|
|
||||||
context.showReaction = !context.rollConfig.type && context.rollType === 'DualityRoll';
|
context.showReaction = !this.config.roll?.type && context.rollType === 'DualityRoll';
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTraitModifiers() {
|
||||||
|
return Object.values(abilities).map(a => ({
|
||||||
|
id: a.id,
|
||||||
|
label: `${game.i18n.localize(a.label)} (${this.actor.system.traits[a.id]?.value.signedString() ?? 0})`
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
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.selectedRollMode = rest.selectedRollMode;
|
||||||
|
|
||||||
if (this.config.costs) {
|
if (this.config.costs) {
|
||||||
|
|
@ -133,6 +152,12 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
this.roll[key] = value;
|
this.roll[key] = value;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (rest.hasOwnProperty('trait')) {
|
||||||
|
this.config.roll.trait = rest.trait;
|
||||||
|
this.config.title = game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
||||||
|
ability: game.i18n.localize(abilities[this.config.roll.trait]?.label)
|
||||||
|
});
|
||||||
|
}
|
||||||
this.config.extraFormula = rest.extraFormula;
|
this.config.extraFormula = rest.extraFormula;
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
@ -151,7 +176,6 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
this.config.experiences.indexOf(button.dataset.key) > -1
|
this.config.experiences.indexOf(button.dataset.key) > -1
|
||||||
? this.config.experiences.filter(x => x !== button.dataset.key)
|
? this.config.experiences.filter(x => x !== button.dataset.key)
|
||||||
: [...this.config.experiences, button.dataset.key];
|
: [...this.config.experiences, button.dataset.key];
|
||||||
if (this.config?.data?.parent?.type === 'character' || this.config?.data?.parent?.type === 'companion') {
|
|
||||||
this.config.costs =
|
this.config.costs =
|
||||||
this.config.costs.indexOf(this.config.costs.find(c => c.extKey === button.dataset.key)) > -1
|
this.config.costs.indexOf(this.config.costs.find(c => c.extKey === button.dataset.key)) > -1
|
||||||
? this.config.costs.filter(x => x.extKey !== button.dataset.key)
|
? this.config.costs.filter(x => x.extKey !== button.dataset.key)
|
||||||
|
|
@ -159,27 +183,31 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
...this.config.costs,
|
...this.config.costs,
|
||||||
{
|
{
|
||||||
extKey: button.dataset.key,
|
extKey: button.dataset.key,
|
||||||
key: 'hope',
|
key: this.config?.data?.parent?.isNPC ? 'fear' : 'hope',
|
||||||
value: 1,
|
value: 1,
|
||||||
name: this.config.data?.experiences?.[button.dataset.key]?.name
|
name: this.config.data?.system.experiences?.[button.dataset.key]?.name
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}
|
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
static toggleReaction() {
|
static toggleReaction() {
|
||||||
if (this.config.roll) {
|
if (this.config.roll) {
|
||||||
this.reactionOverride = !this.reactionOverride;
|
this.reactionOverride = !this.reactionOverride;
|
||||||
this.config.roll.type = this.reactionOverride
|
this.config.actionType = this.reactionOverride
|
||||||
? CONFIG.DH.ITEM.actionTypes.reaction.id
|
? 'reaction'
|
||||||
: this.config.roll.type === CONFIG.DH.ITEM.actionTypes.reaction.id
|
: this.config.actionType === 'reaction'
|
||||||
? null
|
? 'action'
|
||||||
: this.config.roll.type;
|
: this.config.actionType;
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static toggleTagTeamRoll() {
|
||||||
|
this.config.tagTeamSelected = !this.config.tagTeamSelected;
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
static async submitRoll() {
|
static async submitRoll() {
|
||||||
await this.close({ submitted: true });
|
await this.close({ submitted: true });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.rulesDefault = game.settings.get(
|
this.rulesDefault = game.settings.get(
|
||||||
CONFIG.DH.id,
|
CONFIG.DH.id,
|
||||||
CONFIG.DH.SETTINGS.gameSettings.Automation
|
CONFIG.DH.SETTINGS.gameSettings.Automation
|
||||||
|
|
@ -39,7 +40,7 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
||||||
this.availableStressReductions = Object.keys(actor.system.rules.damageReduction.stressDamageReduction).reduce(
|
this.availableStressReductions = Object.keys(actor.system.rules.damageReduction.stressDamageReduction).reduce(
|
||||||
(acc, key) => {
|
(acc, key) => {
|
||||||
const dr = actor.system.rules.damageReduction.stressDamageReduction[key];
|
const dr = actor.system.rules.damageReduction.stressDamageReduction[key];
|
||||||
if (dr.enabled) {
|
if (dr.cost) {
|
||||||
if (acc === null) acc = {};
|
if (acc === null) acc = {};
|
||||||
|
|
||||||
const damage = damageKeyToNumber(key);
|
const damage = damageKeyToNumber(key);
|
||||||
|
|
@ -57,6 +58,11 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.reduceSeverity = this.damageType.reduce((value, curr) => {
|
||||||
|
return Math.max(this.actor.system.rules.damageReduction.reduceSeverity[curr], value);
|
||||||
|
}, 0);
|
||||||
|
this.actor.system.rules.damageReduction.reduceSeverity[this.damageType];
|
||||||
|
|
||||||
this.thresholdImmunities = Object.keys(actor.system.rules.damageReduction.thresholdImmunities).reduce(
|
this.thresholdImmunities = Object.keys(actor.system.rules.damageReduction.thresholdImmunities).reduce(
|
||||||
(acc, key) => {
|
(acc, key) => {
|
||||||
if (actor.system.rules.damageReduction.thresholdImmunities[key])
|
if (actor.system.rules.damageReduction.thresholdImmunities[key])
|
||||||
|
|
@ -111,7 +117,9 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
||||||
CONFIG.DH.GENERAL.ruleChoice.onWithToggle.id,
|
CONFIG.DH.GENERAL.ruleChoice.onWithToggle.id,
|
||||||
CONFIG.DH.GENERAL.ruleChoice.offWithToggle.id
|
CONFIG.DH.GENERAL.ruleChoice.offWithToggle.id
|
||||||
].includes(this.rulesDefault);
|
].includes(this.rulesDefault);
|
||||||
context.thresholdImmunities = this.thresholdImmunities;
|
context.reduceSeverity = this.reduceSeverity;
|
||||||
|
context.thresholdImmunities =
|
||||||
|
Object.keys(this.thresholdImmunities).length > 0 ? this.thresholdImmunities : null;
|
||||||
|
|
||||||
const { selectedArmorMarks, selectedStressMarks, stressReductions, currentMarks, currentDamage } =
|
const { selectedArmorMarks, selectedStressMarks, stressReductions, currentMarks, currentDamage } =
|
||||||
this.getDamageInfo();
|
this.getDamageInfo();
|
||||||
|
|
@ -173,6 +181,9 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
||||||
this.damage - armorMarkReduction - selectedStressMarks.length - stressReductions.length,
|
this.damage - armorMarkReduction - selectedStressMarks.length - stressReductions.length,
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
|
if (this.reduceSeverity) {
|
||||||
|
currentDamage = Math.max(currentDamage - this.reduceSeverity, 0);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.thresholdImmunities[currentDamage]) currentDamage = 0;
|
if (this.thresholdImmunities[currentDamage]) currentDamage = 0;
|
||||||
|
|
||||||
|
|
@ -260,7 +271,7 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
||||||
const reducedDamage = currentDamage !== this.damage ? getDamageLabel(currentDamage) : null;
|
const reducedDamage = currentDamage !== this.damage ? getDamageLabel(currentDamage) : null;
|
||||||
const currentDamageLabel = reducedDamage ?? getDamageLabel(this.damage);
|
const currentDamageLabel = reducedDamage ?? getDamageLabel(this.damage);
|
||||||
|
|
||||||
if (stressReduction.from !== currentDamageLabel) return;
|
if (!stressReduction.any && stressReduction.from !== currentDamageLabel) return;
|
||||||
|
|
||||||
stressReduction.selected = true;
|
stressReduction.selected = true;
|
||||||
this.render();
|
this.render();
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { refreshIsAllowed } from '../../helpers/utils.mjs';
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
||||||
export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV2) {
|
export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
|
|
@ -91,14 +93,10 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
||||||
}
|
}
|
||||||
|
|
||||||
getRefreshables() {
|
getRefreshables() {
|
||||||
const actionItems = this.actor.items.reduce((acc, x) => {
|
const actionItems = this.actor.items.filter(x => this.actor.system.isItemAvailable(x)).reduce((acc, x) => {
|
||||||
if (x.system.actions) {
|
if (x.system.actions) {
|
||||||
const recoverable = x.system.actions.reduce((acc, action) => {
|
const recoverable = x.system.actions.reduce((acc, action) => {
|
||||||
if (
|
if (refreshIsAllowed([this.shortrest ? 'shortRest' : 'longRest'], action.uses.recovery)) {
|
||||||
action.uses.recovery &&
|
|
||||||
((action.uses.recovery === 'longRest' && !this.shortrest) ||
|
|
||||||
action.uses.recovery === 'shortRest')
|
|
||||||
) {
|
|
||||||
acc.push({
|
acc.push({
|
||||||
title: x.name,
|
title: x.name,
|
||||||
name: action.name,
|
name: action.name,
|
||||||
|
|
@ -120,8 +118,7 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
||||||
if (
|
if (
|
||||||
x.system.resource &&
|
x.system.resource &&
|
||||||
x.system.resource.type &&
|
x.system.resource.type &&
|
||||||
((x.system.resource.recovery === 'longRest') === !this.shortrest ||
|
refreshIsAllowed([this.shortrest ? 'shortRest' : 'longRest'], x.system.resource.recovery)
|
||||||
x.system.resource.recovery === 'shortRest')
|
|
||||||
) {
|
) {
|
||||||
acc.push({
|
acc.push({
|
||||||
title: game.i18n.localize(`TYPES.Item.${x.type}`),
|
title: game.i18n.localize(`TYPES.Item.${x.type}`),
|
||||||
|
|
@ -178,11 +175,23 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
||||||
}
|
}
|
||||||
|
|
||||||
static async takeDowntime() {
|
static async takeDowntime() {
|
||||||
const moves = Object.values(this.moveData).flatMap(category => {
|
const moves = Object.keys(this.moveData).flatMap(categoryKey => {
|
||||||
return Object.values(category.moves)
|
const category = this.moveData[categoryKey];
|
||||||
.filter(x => x.selected)
|
return Object.keys(category.moves)
|
||||||
.flatMap(move => [...Array(move.selected).keys()].map(_ => move));
|
.filter(x => category.moves[x].selected)
|
||||||
|
.flatMap(key => {
|
||||||
|
const move = category.moves[key];
|
||||||
|
const needsTarget = move.actions.filter(x => x.target?.type && x.target.type !== 'self').length > 0;
|
||||||
|
return [...Array(move.selected).keys()].map(_ => ({
|
||||||
|
...move,
|
||||||
|
movePath: `${categoryKey}.moves.${key}`,
|
||||||
|
needsTarget: needsTarget
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
const characters = game.actors.filter(x => x.type === 'character')
|
||||||
|
.filter(x => x.testUserPermission(game.user, 'LIMITED'))
|
||||||
|
.filter(x => x.uuid !== this.actor.uuid);
|
||||||
|
|
||||||
const cls = getDocumentClass('ChatMessage');
|
const cls = getDocumentClass('ChatMessage');
|
||||||
const msg = {
|
const msg = {
|
||||||
|
|
@ -202,7 +211,9 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
||||||
`DAGGERHEART.APPLICATIONS.Downtime.${this.shortrest ? 'shortRest' : 'longRest'}.title`
|
`DAGGERHEART.APPLICATIONS.Downtime.${this.shortrest ? 'shortRest' : 'longRest'}.title`
|
||||||
),
|
),
|
||||||
actor: { name: this.actor.name, img: this.actor.img },
|
actor: { name: this.actor.name, img: this.actor.img },
|
||||||
moves: moves
|
moves: moves,
|
||||||
|
characters: characters,
|
||||||
|
selfId: this.actor.uuid
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
flags: {
|
flags: {
|
||||||
|
|
|
||||||
196
module/applications/dialogs/group-roll-dialog.mjs
Normal file
196
module/applications/dialogs/group-roll-dialog.mjs
Normal file
|
|
@ -0,0 +1,196 @@
|
||||||
|
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}`;
|
||||||
|
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}`;
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
70
module/applications/dialogs/imageSelectDialog.mjs
Normal file
70
module/applications/dialogs/imageSelectDialog.mjs
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||||
|
|
||||||
|
export default class ImageSelectDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
|
constructor(titleName, images) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.titleName = titleName;
|
||||||
|
this.images = images;
|
||||||
|
}
|
||||||
|
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
tag: 'form',
|
||||||
|
classes: ['daggerheart', 'dialog', 'dh-style', 'image-select'],
|
||||||
|
position: {
|
||||||
|
width: 612,
|
||||||
|
height: 'auto'
|
||||||
|
},
|
||||||
|
window: {
|
||||||
|
icon: 'fa-solid fa-paw'
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
selectImage: ImageSelectDialog.#selectImage,
|
||||||
|
finishSelection: ImageSelectDialog.#finishSelection
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
get title() {
|
||||||
|
return this.titleName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static PARTS = {
|
||||||
|
main: {
|
||||||
|
template: 'systems/daggerheart/templates/dialogs/image-select/main.hbs',
|
||||||
|
scrollable: ['.images-container']
|
||||||
|
},
|
||||||
|
footer: { template: 'systems/daggerheart/templates/dialogs/image-select/footer.hbs' }
|
||||||
|
};
|
||||||
|
|
||||||
|
async _prepareContext(_options) {
|
||||||
|
const context = await super._prepareContext(_options);
|
||||||
|
context.images = this.images;
|
||||||
|
context.selectedImage = this.selectedImage;
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
static #selectImage(_event, button) {
|
||||||
|
this.selectedImage = button.dataset.image ?? button.querySelector('img').dataset.image;
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
static #finishSelection() {
|
||||||
|
this.close({ submitted: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
async close(options = {}) {
|
||||||
|
if (!options.submitted) this.selectedImage = null;
|
||||||
|
|
||||||
|
await super.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
static async configure(title, images) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const app = new this(title, images);
|
||||||
|
app.addEventListener('close', () => resolve(app.selectedImage), { once: true });
|
||||||
|
app.render({ force: true });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
62
module/applications/dialogs/itemTransfer.mjs
Normal file
62
module/applications/dialogs/itemTransfer.mjs
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
||||||
|
export default class ItemTransferDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
|
constructor(data) {
|
||||||
|
super({});
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
get title() {
|
||||||
|
return this.data.title;
|
||||||
|
}
|
||||||
|
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
tag: 'form',
|
||||||
|
classes: ['daggerheart', 'dh-style', 'dialog', 'item-transfer'],
|
||||||
|
position: { width: 400, height: 'auto' },
|
||||||
|
window: { icon: 'fa-solid fa-hand-holding-hand' },
|
||||||
|
actions: {
|
||||||
|
finish: ItemTransferDialog.#finish
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static PARTS = {
|
||||||
|
main: { template: 'systems/daggerheart/templates/dialogs/item-transfer.hbs', root: true }
|
||||||
|
};
|
||||||
|
|
||||||
|
async _prepareContext(_options) {
|
||||||
|
const context = await super._prepareContext(_options);
|
||||||
|
return foundry.utils.mergeObject(context, this.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #finish() {
|
||||||
|
this.selected = this.form.elements.quantity.valueAsNumber || null;
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
static #determineTransferOptions({ originActor, targetActor, item, currency }) {
|
||||||
|
originActor ??= item?.actor;
|
||||||
|
const homebrewKey = CONFIG.DH.SETTINGS.gameSettings.Homebrew;
|
||||||
|
const currencySetting = game.settings.get(CONFIG.DH.id, homebrewKey).currency?.[currency] ?? null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
originActor,
|
||||||
|
targetActor,
|
||||||
|
itemImage: item?.img,
|
||||||
|
currencyIcon: currencySetting?.icon,
|
||||||
|
max: item?.system.quantity ?? originActor.system.gold[currency] ?? 0,
|
||||||
|
title: item?.name ?? currencySetting?.label
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static async configure(options) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const data = this.#determineTransferOptions(options);
|
||||||
|
if (data.max <= 1) return resolve(data.max);
|
||||||
|
|
||||||
|
const app = new this(data);
|
||||||
|
app.addEventListener('close', () => resolve(app.selected), { once: true });
|
||||||
|
app.render({ force: true });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,18 +1,20 @@
|
||||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
||||||
export default class OwnershipSelection extends HandlebarsApplicationMixin(ApplicationV2) {
|
export default class OwnershipSelection extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
constructor(resolve, reject, name, ownership) {
|
constructor(name, ownership, defaultOwnership) {
|
||||||
super({});
|
super({});
|
||||||
|
|
||||||
this.resolve = resolve;
|
|
||||||
this.reject = reject;
|
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.ownership = ownership;
|
this.ownership = foundry.utils.deepClone(ownership);
|
||||||
|
this.defaultOwnership = defaultOwnership;
|
||||||
}
|
}
|
||||||
|
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
tag: 'form',
|
tag: 'form',
|
||||||
classes: ['daggerheart', 'views', 'ownership-selection'],
|
classes: ['daggerheart', 'views', 'dialog', 'dh-style', 'ownership-selection'],
|
||||||
|
window: {
|
||||||
|
icon: 'fa-solid fa-users'
|
||||||
|
},
|
||||||
position: {
|
position: {
|
||||||
width: 600,
|
width: 600,
|
||||||
height: 'auto'
|
height: 'auto'
|
||||||
|
|
@ -30,43 +32,48 @@ export default class OwnershipSelection extends HandlebarsApplicationMixin(Appli
|
||||||
return game.i18n.format('DAGGERHEART.APPLICATIONS.OwnershipSelection.title', { name: this.name });
|
return game.i18n.format('DAGGERHEART.APPLICATIONS.OwnershipSelection.title', { name: this.name });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getOwnershipData(id) {
|
||||||
|
return this.ownership[id] ?? CONST.DOCUMENT_OWNERSHIP_LEVELS.INHERIT;
|
||||||
|
}
|
||||||
|
|
||||||
async _prepareContext(_options) {
|
async _prepareContext(_options) {
|
||||||
const context = await super._prepareContext(_options);
|
const context = await super._prepareContext(_options);
|
||||||
context.ownershipOptions = Object.keys(CONST.DOCUMENT_OWNERSHIP_LEVELS).map(level => ({
|
context.ownershipOptions = CONFIG.DH.GENERAL.simpleOwnershiplevels;
|
||||||
value: CONST.DOCUMENT_OWNERSHIP_LEVELS[level],
|
context.defaultOwnership = this.defaultOwnership;
|
||||||
label: game.i18n.localize(`OWNERSHIP.${level}`)
|
context.ownership = game.users.reduce((acc, user) => {
|
||||||
}));
|
|
||||||
context.ownership = {
|
|
||||||
default: this.ownership.default,
|
|
||||||
players: Object.keys(this.ownership.players).reduce((acc, x) => {
|
|
||||||
const user = game.users.get(x);
|
|
||||||
if (!user.isGM) {
|
if (!user.isGM) {
|
||||||
acc[x] = {
|
acc[user.id] = {
|
||||||
|
...user,
|
||||||
img: user.character?.img ?? 'icons/svg/cowled.svg',
|
img: user.character?.img ?? 'icons/svg/cowled.svg',
|
||||||
name: user.name,
|
ownership: this.getOwnershipData(user.id)
|
||||||
ownership: this.ownership.players[x].value
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {})
|
}, {});
|
||||||
};
|
context.showOwnership = Boolean(Object.keys(context.ownership).length);
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async updateData(event, _, formData) {
|
static async updateData(event, _, formData) {
|
||||||
const { ownership } = foundry.utils.expandObject(formData.object);
|
const data = foundry.utils.expandObject(formData.object);
|
||||||
|
this.close(data);
|
||||||
this.resolve(ownership);
|
|
||||||
this.close(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async close(fromSave) {
|
async close(data) {
|
||||||
if (!fromSave) {
|
if (data) {
|
||||||
this.reject();
|
this.saveData = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
await super.close();
|
await super.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async configure(name, ownership, defaultOwnership) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const app = new this(name, ownership, defaultOwnership);
|
||||||
|
app.addEventListener('close', () => resolve(app.saveData), { once: true });
|
||||||
|
app.render({ force: true });
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||||
|
|
||||||
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||||
|
|
||||||
export default class RerollDamageDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
export default class RerollDamageDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
|
|
@ -122,6 +124,15 @@ export default class RerollDamageDialog extends HandlebarsApplicationMixin(Appli
|
||||||
}, {})
|
}, {})
|
||||||
};
|
};
|
||||||
await this.message.update(update);
|
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();
|
await this.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
347
module/applications/dialogs/tagTeamDialog.mjs
Normal file
347
module/applications/dialogs/tagTeamDialog.mjs
Normal file
|
|
@ -0,0 +1,347 @@
|
||||||
|
import { getCritDamageBonus } from '../../helpers/utils.mjs';
|
||||||
|
import { GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||||
|
|
||||||
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
||||||
|
export default class TagTeamDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
|
constructor(party) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.data = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
|
||||||
|
this.party = party;
|
||||||
|
|
||||||
|
this.setupHooks = Hooks.on(socketEvent.Refresh, ({ refreshType }) => {
|
||||||
|
if (refreshType === RefreshType.TagTeamRoll) {
|
||||||
|
this.data = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get title() {
|
||||||
|
return game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.title');
|
||||||
|
}
|
||||||
|
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
tag: 'form',
|
||||||
|
classes: ['daggerheart', 'views', 'dh-style', 'dialog', 'tag-team-dialog'],
|
||||||
|
position: { width: 550, height: 'auto' },
|
||||||
|
actions: {
|
||||||
|
removeMember: TagTeamDialog.#removeMember,
|
||||||
|
unlinkMessage: TagTeamDialog.#unlinkMessage,
|
||||||
|
selectMessage: TagTeamDialog.#selectMessage,
|
||||||
|
createTagTeam: TagTeamDialog.#createTagTeam
|
||||||
|
},
|
||||||
|
form: { handler: this.updateData, submitOnChange: true, closeOnSubmit: false }
|
||||||
|
};
|
||||||
|
|
||||||
|
static PARTS = {
|
||||||
|
application: {
|
||||||
|
id: 'tag-team-dialog',
|
||||||
|
template: 'systems/daggerheart/templates/dialogs/tagTeamDialog.hbs'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async _prepareContext(_options) {
|
||||||
|
const context = await super._prepareContext(_options);
|
||||||
|
context.hopeCost = this.hopeCost;
|
||||||
|
context.data = this.data;
|
||||||
|
|
||||||
|
context.memberOptions = this.party.filter(c => !this.data.members[c.id]);
|
||||||
|
context.selectedCharacterOptions = this.party.filter(c => this.data.members[c.id]);
|
||||||
|
|
||||||
|
context.members = Object.keys(this.data.members).map(id => {
|
||||||
|
const roll = this.data.members[id].messageId ? game.messages.get(this.data.members[id].messageId) : null;
|
||||||
|
|
||||||
|
context.usesDamage =
|
||||||
|
context.usesDamage === undefined
|
||||||
|
? roll?.system.hasDamage
|
||||||
|
: context.usesDamage && roll?.system.hasDamage;
|
||||||
|
return {
|
||||||
|
character: this.party.find(x => x.id === id),
|
||||||
|
selected: this.data.members[id].selected,
|
||||||
|
roll: roll,
|
||||||
|
damageValues: roll
|
||||||
|
? Object.keys(roll.system.damage).map(key => ({
|
||||||
|
key: key,
|
||||||
|
name: game.i18n.localize(CONFIG.DH.GENERAL.healingTypes[key].label),
|
||||||
|
total: roll.system.damage[key].total
|
||||||
|
}))
|
||||||
|
: null
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const initiatorChar = this.party.find(x => x.id === this.data.initiator.id);
|
||||||
|
context.initiator = {
|
||||||
|
character: initiatorChar,
|
||||||
|
cost: this.data.initiator.cost
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectedMember = Object.values(context.members).find(x => x.selected && x.roll);
|
||||||
|
const selectedIsCritical = selectedMember?.roll?.system?.isCritical;
|
||||||
|
context.selectedData = {
|
||||||
|
result: selectedMember
|
||||||
|
? `${selectedMember.roll.system.roll.total} ${selectedMember.roll.system.roll.result.label}`
|
||||||
|
: null,
|
||||||
|
damageValues: null
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const member of Object.values(context.members)) {
|
||||||
|
if (!member.roll) continue;
|
||||||
|
if (context.usesDamage) {
|
||||||
|
if (!context.selectedData.damageValues) context.selectedData.damageValues = {};
|
||||||
|
for (let damage of member.damageValues) {
|
||||||
|
const damageTotal = member.roll.system.isCritical
|
||||||
|
? damage.total
|
||||||
|
: selectedIsCritical
|
||||||
|
? damage.total + (await getCritDamageBonus(member.roll.system.damage[damage.key].formula))
|
||||||
|
: damage.total;
|
||||||
|
if (context.selectedData.damageValues[damage.key]) {
|
||||||
|
context.selectedData.damageValues[damage.key].total += damageTotal;
|
||||||
|
} else {
|
||||||
|
context.selectedData.damageValues[damage.key] = {
|
||||||
|
...foundry.utils.deepClone(damage),
|
||||||
|
total: damageTotal
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context.showResult = Object.values(context.members).reduce((enabled, member) => {
|
||||||
|
if (!member.roll) return enabled;
|
||||||
|
if (context.usesDamage) {
|
||||||
|
enabled = enabled === null ? member.damageValues.length > 0 : enabled && member.damageValues.length > 0;
|
||||||
|
} else {
|
||||||
|
enabled = enabled === null ? Boolean(member.roll) : enabled && Boolean(member.roll);
|
||||||
|
}
|
||||||
|
|
||||||
|
return enabled;
|
||||||
|
}, null);
|
||||||
|
|
||||||
|
context.createDisabled =
|
||||||
|
!context.selectedData.result ||
|
||||||
|
!this.data.initiator.id ||
|
||||||
|
Object.keys(this.data.members).length === 0 ||
|
||||||
|
Object.values(context.members).some(x =>
|
||||||
|
context.usesDamage ? !x.damageValues || x.damageValues.length === 0 : !x.roll
|
||||||
|
);
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateSource(update) {
|
||||||
|
await this.data.updateSource(update);
|
||||||
|
|
||||||
|
if (game.user.isGM) {
|
||||||
|
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll, this.data.toObject());
|
||||||
|
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.TagTeamRoll });
|
||||||
|
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||||
|
action: socketEvent.Refresh,
|
||||||
|
data: {
|
||||||
|
refreshType: RefreshType.TagTeamRoll
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||||
|
action: socketEvent.GMUpdate,
|
||||||
|
data: {
|
||||||
|
action: GMUpdateEvent.UpdateSetting,
|
||||||
|
uuid: CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll,
|
||||||
|
update: this.data.toObject(),
|
||||||
|
refresh: { refreshType: RefreshType.TagTeamRoll }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async updateData(_event, _element, formData) {
|
||||||
|
const { selectedAddMember, initiator } = foundry.utils.expandObject(formData.object);
|
||||||
|
const update = { initiator: initiator };
|
||||||
|
if (selectedAddMember) {
|
||||||
|
const member = await foundry.utils.fromUuid(selectedAddMember);
|
||||||
|
update[`members.${member.id}`] = { messageId: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.updateSource(update);
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #removeMember(_, button) {
|
||||||
|
const update = { [`members.-=${button.dataset.characterId}`]: null };
|
||||||
|
if (this.data.initiator.id === button.dataset.characterId) {
|
||||||
|
update.iniator = { id: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.updateSource(update);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #unlinkMessage(_, button) {
|
||||||
|
await this.updateSource({ [`members.${button.id}.messageId`]: null });
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #selectMessage(_, button) {
|
||||||
|
const member = this.data.members[button.id];
|
||||||
|
const currentSelected = Object.keys(this.data.members).find(key => this.data.members[key].selected);
|
||||||
|
const curretSelectedUpdate =
|
||||||
|
currentSelected && currentSelected !== button.id ? { [`${currentSelected}`]: { selected: false } } : {};
|
||||||
|
await this.updateSource({
|
||||||
|
members: {
|
||||||
|
[`${button.id}`]: { selected: !member.selected },
|
||||||
|
...curretSelectedUpdate
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #createTagTeam() {
|
||||||
|
const mainRollId = Object.keys(this.data.members).find(key => this.data.members[key].selected);
|
||||||
|
const mainRoll = game.messages.get(this.data.members[mainRollId].messageId);
|
||||||
|
|
||||||
|
if (this.data.initiator.cost) {
|
||||||
|
const initiator = this.party.find(x => x.id === this.data.initiator.id);
|
||||||
|
if (initiator.system.resources.hope.value < this.data.initiator.cost) {
|
||||||
|
return ui.notifications.warn(
|
||||||
|
game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.insufficientHope')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const secondaryRolls = Object.keys(this.data.members)
|
||||||
|
.filter(key => key !== mainRollId)
|
||||||
|
.map(key => game.messages.get(this.data.members[key].messageId));
|
||||||
|
|
||||||
|
const systemData = foundry.utils.deepClone(mainRoll).system.toObject();
|
||||||
|
const criticalRoll = systemData.roll.isCritical;
|
||||||
|
for (let roll of secondaryRolls) {
|
||||||
|
if (roll.system.hasDamage) {
|
||||||
|
for (let key in roll.system.damage) {
|
||||||
|
var damage = roll.system.damage[key];
|
||||||
|
const damageTotal =
|
||||||
|
!roll.system.isCritical && criticalRoll
|
||||||
|
? (await getCritDamageBonus(damage.formula)) + damage.total
|
||||||
|
: damage.total;
|
||||||
|
const updatedDamageParts = damage.parts;
|
||||||
|
if (systemData.damage[key]) {
|
||||||
|
if (!roll.system.isCritical && criticalRoll) {
|
||||||
|
for (let part of updatedDamageParts) {
|
||||||
|
const criticalDamage = await getCritDamageBonus(part.formula);
|
||||||
|
if (criticalDamage) {
|
||||||
|
damage.formula = `${damage.formula} + ${criticalDamage}`;
|
||||||
|
part.formula = `${part.formula} + ${criticalDamage}`;
|
||||||
|
part.modifierTotal = part.modifierTotal + criticalDamage;
|
||||||
|
part.total += criticalDamage;
|
||||||
|
part.roll = new Roll(part.formula);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
systemData.damage[key].formula = `${systemData.damage[key].formula} + ${damage.formula}`;
|
||||||
|
systemData.damage[key].total += damageTotal;
|
||||||
|
systemData.damage[key].parts = [...systemData.damage[key].parts, ...updatedDamageParts];
|
||||||
|
} else {
|
||||||
|
systemData.damage[key] = { ...damage, total: damageTotal, parts: updatedDamageParts };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
systemData.title = game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.chatMessageRollTitle');
|
||||||
|
const cls = getDocumentClass('ChatMessage'),
|
||||||
|
msgData = {
|
||||||
|
type: 'dualityRoll',
|
||||||
|
user: game.user.id,
|
||||||
|
title: game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.title'),
|
||||||
|
speaker: cls.getSpeaker({ actor: this.party.find(x => x.id === mainRollId) }),
|
||||||
|
system: systemData,
|
||||||
|
rolls: mainRoll.rolls,
|
||||||
|
sound: null,
|
||||||
|
flags: { core: { RollTable: true } }
|
||||||
|
};
|
||||||
|
|
||||||
|
await cls.create(msgData);
|
||||||
|
|
||||||
|
const fearUpdate = { key: 'fear', value: null, total: null, enabled: true };
|
||||||
|
for (let memberId of Object.keys(this.data.members)) {
|
||||||
|
const resourceUpdates = [];
|
||||||
|
const rollGivesHope = systemData.roll.isCritical || systemData.roll.result.duality === 1;
|
||||||
|
if (memberId === this.data.initiator.id) {
|
||||||
|
const value = this.data.initiator.cost
|
||||||
|
? rollGivesHope
|
||||||
|
? 1 - this.data.initiator.cost
|
||||||
|
: -this.data.initiator.cost
|
||||||
|
: 1;
|
||||||
|
resourceUpdates.push({ key: 'hope', value: value, total: -value, enabled: true });
|
||||||
|
} else if (rollGivesHope) {
|
||||||
|
resourceUpdates.push({ key: 'hope', value: 1, total: -1, enabled: true });
|
||||||
|
}
|
||||||
|
if (systemData.roll.isCritical) resourceUpdates.push({ key: 'stress', value: -1, total: 1, enabled: true });
|
||||||
|
if (systemData.roll.result.duality === -1) {
|
||||||
|
fearUpdate.value = fearUpdate.value === null ? 1 : fearUpdate.value + 1;
|
||||||
|
fearUpdate.total = fearUpdate.total === null ? -1 : fearUpdate.total - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.party.find(x => x.id === memberId).modifyResource(resourceUpdates);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fearUpdate.value) {
|
||||||
|
this.party.find(x => x.id === mainRollId).modifyResource([fearUpdate]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Improve by fetching default from schema */
|
||||||
|
const update = { members: [], initiator: { id: null, cost: 3 } };
|
||||||
|
if (game.user.isGM) {
|
||||||
|
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll, update);
|
||||||
|
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.TagTeamRoll });
|
||||||
|
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||||
|
action: socketEvent.Refresh,
|
||||||
|
data: {
|
||||||
|
refreshType: RefreshType.TagTeamRoll
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||||
|
action: socketEvent.GMUpdate,
|
||||||
|
data: {
|
||||||
|
action: GMUpdateEvent.UpdateSetting,
|
||||||
|
uuid: CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll,
|
||||||
|
update: update,
|
||||||
|
refresh: { refreshType: RefreshType.TagTeamRoll }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async assignRoll(char, message) {
|
||||||
|
const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
|
||||||
|
const character = settings.members[char.id];
|
||||||
|
if (!character) return;
|
||||||
|
|
||||||
|
await settings.updateSource({ [`members.${char.id}.messageId`]: message.id });
|
||||||
|
|
||||||
|
if (game.user.isGM) {
|
||||||
|
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll, settings);
|
||||||
|
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.TagTeamRoll });
|
||||||
|
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||||
|
action: socketEvent.Refresh,
|
||||||
|
data: {
|
||||||
|
refreshType: RefreshType.TagTeamRoll
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||||
|
action: socketEvent.GMUpdate,
|
||||||
|
data: {
|
||||||
|
action: GMUpdateEvent.UpdateSetting,
|
||||||
|
uuid: CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll,
|
||||||
|
update: settings,
|
||||||
|
refresh: { refreshType: RefreshType.TagTeamRoll }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async close(options = {}) {
|
||||||
|
Hooks.off(socketEvent.Refresh, this.setupHooks);
|
||||||
|
await super.close(options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,12 @@
|
||||||
|
import { shuffleArray } from '../../helpers/utils.mjs';
|
||||||
|
|
||||||
export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
classes: ['daggerheart']
|
classes: ['daggerheart'],
|
||||||
|
actions: {
|
||||||
|
combat: DHTokenHUD.#onToggleCombat,
|
||||||
|
togglePartyTokens: DHTokenHUD.#togglePartyTokens
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
|
|
@ -11,11 +17,28 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static #nonCombatTypes = ['environment', 'companion', 'party'];
|
||||||
|
|
||||||
async _prepareContext(options) {
|
async _prepareContext(options) {
|
||||||
const context = await super._prepareContext(options);
|
const context = await super._prepareContext(options);
|
||||||
|
if (!this.actor) return context;
|
||||||
|
|
||||||
|
context.partyOnCanvas =
|
||||||
|
this.actor.type === 'party' &&
|
||||||
|
this.actor.system.partyMembers.some(member => member.getActiveTokens().length > 0);
|
||||||
|
context.icons.toggleParty = 'systems/daggerheart/assets/icons/arrow-dunk.png';
|
||||||
|
context.actorType = this.actor.type;
|
||||||
|
context.usesEffects = this.actor.type !== 'party';
|
||||||
|
context.canToggleCombat = DHTokenHUD.#nonCombatTypes.includes(this.actor.type)
|
||||||
|
? false
|
||||||
|
: context.canToggleCombat;
|
||||||
|
|
||||||
context.systemStatusEffects = Object.keys(context.statusEffects).reduce((acc, key) => {
|
context.systemStatusEffects = Object.keys(context.statusEffects).reduce((acc, key) => {
|
||||||
const effect = context.statusEffects[key];
|
const effect = context.statusEffects[key];
|
||||||
if (effect.systemEffect) acc[key] = effect;
|
if (effect.systemEffect) {
|
||||||
|
const disabled = !effect.isActive && this.actor.system.rules?.conditionImmunities?.[key];
|
||||||
|
acc[key] = { ...effect, disabled };
|
||||||
|
}
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
@ -36,6 +59,138 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async #onToggleCombat() {
|
||||||
|
const tokensWithoutActors = canvas.tokens.controlled.filter(t => !t.actor);
|
||||||
|
const warning =
|
||||||
|
tokensWithoutActors.length === 1
|
||||||
|
? game.i18n.format('DAGGERHEART.UI.Notifications.tokenActorMissing', {
|
||||||
|
name: tokensWithoutActors[0].name
|
||||||
|
})
|
||||||
|
: game.i18n.format('DAGGERHEART.UI.Notifications.tokenActorsMissing', {
|
||||||
|
names: tokensWithoutActors.map(x => x.name).join(', ')
|
||||||
|
});
|
||||||
|
|
||||||
|
const tokens = canvas.tokens.controlled
|
||||||
|
.filter(t => t.actor && !DHTokenHUD.#nonCombatTypes.includes(t.actor.type))
|
||||||
|
.map(t => t.document);
|
||||||
|
if (!this.object.controlled && this.document.actor) tokens.push(this.document);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (this.document.inCombat) {
|
||||||
|
const tokensInCombat = tokens.filter(t => t.inCombat);
|
||||||
|
await TokenDocument.implementation.deleteCombatants([...tokensInCombat, ...tokensWithoutActors]);
|
||||||
|
} else {
|
||||||
|
if (tokensWithoutActors.length) {
|
||||||
|
ui.notifications.warn(warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokensOutOfCombat = tokens.filter(t => !t.inCombat);
|
||||||
|
await TokenDocument.implementation.createCombatants(tokensOutOfCombat);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
ui.notifications.warn(err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #togglePartyTokens(_, button) {
|
||||||
|
const icon = button.querySelector('img');
|
||||||
|
icon.classList.toggle('flipped');
|
||||||
|
button.dataset.tooltip = game.i18n.localize(
|
||||||
|
icon.classList.contains('flipped')
|
||||||
|
? 'DAGGERHEART.APPLICATIONS.HUD.tokenHUD.retrievePartyTokens'
|
||||||
|
: 'DAGGERHEART.APPLICATIONS.HUD.tokenHUD.depositPartyTokens'
|
||||||
|
);
|
||||||
|
|
||||||
|
const animationDuration = 500;
|
||||||
|
const activeTokens = this.actor.system.partyMembers.flatMap(member => member.getActiveTokens());
|
||||||
|
const { x: actorX, y: actorY } = this.document;
|
||||||
|
if (activeTokens.length > 0) {
|
||||||
|
for (let token of activeTokens) {
|
||||||
|
await token.document.update(
|
||||||
|
{ x: actorX, y: actorY, alpha: 0 },
|
||||||
|
{ animation: { duration: animationDuration } }
|
||||||
|
);
|
||||||
|
setTimeout(() => token.document.delete(), animationDuration);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const activeScene = game.scenes.find(x => x.id === game.user.viewedScene);
|
||||||
|
const partyTokenData = [];
|
||||||
|
for (let member of this.actor.system.partyMembers) {
|
||||||
|
const data = await member.getTokenDocument();
|
||||||
|
partyTokenData.push(data.toObject());
|
||||||
|
}
|
||||||
|
const newTokens = await activeScene.createEmbeddedDocuments(
|
||||||
|
'Token',
|
||||||
|
partyTokenData.map(tokenData => ({
|
||||||
|
...tokenData,
|
||||||
|
alpha: 0,
|
||||||
|
x: actorX,
|
||||||
|
y: actorY
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
const { sizeX, sizeY } = activeScene.grid;
|
||||||
|
const nrRandomPositions = Math.ceil(newTokens.length / 8) * 8;
|
||||||
|
/* This is an overcomplicated mess, but I'm stupid */
|
||||||
|
const positions = shuffleArray(
|
||||||
|
[...Array(nrRandomPositions).keys()].map((_, index) => {
|
||||||
|
const nonZeroIndex = index + 1;
|
||||||
|
const indexFloor = Math.floor(index / 8);
|
||||||
|
const distanceCoefficient = indexFloor + 1;
|
||||||
|
const side = 3 + indexFloor * 2;
|
||||||
|
const sideMiddle = Math.ceil(side / 2);
|
||||||
|
const inbetween = 1 + indexFloor * 2;
|
||||||
|
const inbetweenMiddle = Math.ceil(inbetween / 2);
|
||||||
|
|
||||||
|
if (index < side) {
|
||||||
|
const distance =
|
||||||
|
nonZeroIndex === sideMiddle
|
||||||
|
? 0
|
||||||
|
: nonZeroIndex < sideMiddle
|
||||||
|
? -nonZeroIndex
|
||||||
|
: nonZeroIndex - sideMiddle;
|
||||||
|
return { x: actorX - sizeX * distance, y: actorY - sizeY * distanceCoefficient };
|
||||||
|
} else if (index < side + inbetween) {
|
||||||
|
const inbetweenIndex = nonZeroIndex - side;
|
||||||
|
const distance =
|
||||||
|
inbetweenIndex === inbetweenMiddle
|
||||||
|
? 0
|
||||||
|
: inbetweenIndex < inbetweenMiddle
|
||||||
|
? -inbetweenIndex
|
||||||
|
: inbetweenIndex - inbetweenMiddle;
|
||||||
|
return { x: actorX + sizeX * distanceCoefficient, y: actorY + sizeY * distance };
|
||||||
|
} else if (index < 2 * side + inbetween) {
|
||||||
|
const sideIndex = nonZeroIndex - side - inbetween;
|
||||||
|
const distance =
|
||||||
|
sideIndex === sideMiddle
|
||||||
|
? 0
|
||||||
|
: sideIndex < sideMiddle
|
||||||
|
? sideIndex
|
||||||
|
: -(sideIndex - sideMiddle);
|
||||||
|
return { x: actorX + sizeX * distance, y: actorY + sizeY * distanceCoefficient };
|
||||||
|
} else {
|
||||||
|
const inbetweenIndex = nonZeroIndex - 2 * side - inbetween;
|
||||||
|
const distance =
|
||||||
|
inbetweenIndex === inbetweenMiddle
|
||||||
|
? 0
|
||||||
|
: inbetweenIndex < inbetweenMiddle
|
||||||
|
? inbetweenIndex
|
||||||
|
: -(inbetweenIndex - inbetweenMiddle);
|
||||||
|
return { x: actorX - sizeX * distanceCoefficient, y: actorY + sizeY * distance };
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
for (let token of newTokens) {
|
||||||
|
const position = positions.pop();
|
||||||
|
token.update(
|
||||||
|
{ x: position.x, y: position.y, alpha: 1 },
|
||||||
|
{ animation: { duration: animationDuration } }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_getStatusEffectChoices() {
|
_getStatusEffectChoices() {
|
||||||
// Include all HUD-enabled status effects
|
// Include all HUD-enabled status effects
|
||||||
const choices = {};
|
const choices = {};
|
||||||
|
|
@ -59,16 +214,20 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the status of effects which are active for the token actor
|
// Update the status of effects which are active for the token actor
|
||||||
const activeEffects = this.actor?.effects || [];
|
const activeEffects = this.actor?.getActiveEffects() || [];
|
||||||
for (const effect of activeEffects) {
|
for (const effect of activeEffects) {
|
||||||
for (const statusId of effect.statuses) {
|
for (const statusId of effect.statuses) {
|
||||||
const status = choices[statusId];
|
const status = choices[statusId];
|
||||||
if (!status) continue;
|
if (!status) continue;
|
||||||
|
|
||||||
|
status.instances = 1 + (status.instances ?? 0);
|
||||||
|
status.locked = status.locked || effect.condition || status.instances > 1;
|
||||||
|
if (!status) continue;
|
||||||
if (status._id) {
|
if (status._id) {
|
||||||
if (status._id !== effect.id) continue;
|
if (status._id !== effect.id) continue;
|
||||||
}
|
}
|
||||||
status.isActive = true;
|
status.isActive = true;
|
||||||
if (effect.getFlag('core', 'overlay')) status.isOverlay = true;
|
if (effect.getFlag?.('core', 'overlay')) status.isOverlay = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
export { default as CharacterLevelup } from './characterLevelup.mjs';
|
export { default as CharacterLevelup } from './characterLevelup.mjs';
|
||||||
export { default as CompanionLevelup } from './companionLevelup.mjs';
|
export { default as CompanionLevelup } from './companionLevelup.mjs';
|
||||||
export { default as Levelup } from './levelup.mjs';
|
export { default as Levelup } from './levelup.mjs';
|
||||||
|
export { default as LevelupViewMode } from './levelupViewMode.mjs';
|
||||||
|
|
|
||||||
|
|
@ -280,11 +280,19 @@ export default class DhCharacterLevelUp extends LevelUpBase {
|
||||||
break;
|
break;
|
||||||
case 'experience':
|
case 'experience':
|
||||||
if (!advancement[choiceKey]) advancement[choiceKey] = [];
|
if (!advancement[choiceKey]) advancement[choiceKey] = [];
|
||||||
|
const allExperiences = {
|
||||||
|
...this.actor.system.experiences,
|
||||||
|
...Object.values(this.levelup.levels).reduce((acc, level) => {
|
||||||
|
for (const key of Object.keys(level.achievements.experiences)) {
|
||||||
|
acc[key] = level.achievements.experiences[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {})
|
||||||
|
};
|
||||||
const data = checkbox.data.map(data => {
|
const data = checkbox.data.map(data => {
|
||||||
const experience = Object.keys(this.actor.system.experiences).find(
|
const experience = Object.keys(allExperiences).find(x => x === data);
|
||||||
x => x === data
|
return allExperiences[experience]?.name ?? '';
|
||||||
);
|
|
||||||
return this.actor.system.experiences[experience]?.name ?? '';
|
|
||||||
});
|
});
|
||||||
advancement[choiceKey].push({ data: data, value: checkbox.value });
|
advancement[choiceKey].push({ data: data, value: checkbox.value });
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { abilities, subclassFeatureLabels } from '../../config/actorConfig.mjs';
|
import { abilities, subclassFeatureLabels } from '../../config/actorConfig.mjs';
|
||||||
import { getDeleteKeys, tagifyElement } from '../../helpers/utils.mjs';
|
import { getDeleteKeys, tagifyElement } from '../../helpers/utils.mjs';
|
||||||
import { ItemBrowser } from '../ui/itemBrowser.mjs';
|
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
||||||
|
|
@ -12,8 +11,6 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
|
|
||||||
this._dragDrop = this._createDragDropHandlers();
|
this._dragDrop = this._createDragDropHandlers();
|
||||||
this.tabGroups.primary = 'advancements';
|
this.tabGroups.primary = 'advancements';
|
||||||
|
|
||||||
this.itemBrowser = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get title() {
|
get title() {
|
||||||
|
|
@ -360,11 +357,23 @@ 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 = {
|
||||||
|
...this.actor.system.experiences,
|
||||||
|
...Object.values(this.levelup.levels).reduce((acc, level) => {
|
||||||
|
for (const key of Object.keys(level.achievements.experiences)) {
|
||||||
|
acc[key] = level.achievements.experiences[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {})
|
||||||
|
};
|
||||||
tagifyElement(
|
tagifyElement(
|
||||||
experienceIncreaseTagify,
|
experienceIncreaseTagify,
|
||||||
Object.keys(this.actor.system.experiences).reduce((acc, id) => {
|
Object.keys(allExperiences).reduce((acc, id) => {
|
||||||
const experience = this.actor.system.experiences[id];
|
const experience = allExperiences[id];
|
||||||
|
if (experience.name) {
|
||||||
acc.push({ id: id, label: experience.name });
|
acc.push({ id: id, label: experience.name });
|
||||||
|
}
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, []),
|
}, []),
|
||||||
|
|
@ -540,7 +549,6 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
const type = target.dataset.compendium ?? target.dataset.type;
|
const type = target.dataset.compendium ?? target.dataset.type;
|
||||||
|
|
||||||
const presets = {
|
const presets = {
|
||||||
compendium: 'daggerheart',
|
|
||||||
folder: type,
|
folder: type,
|
||||||
render: {
|
render: {
|
||||||
noFolder: true
|
noFolder: true
|
||||||
|
|
@ -559,7 +567,7 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return (this.itemBrowser = await new ItemBrowser({ presets }).render({ force: true }));
|
ui.compendiumBrowser.open(presets);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async selectPreview(_, button) {
|
static async selectPreview(_, button) {
|
||||||
|
|
@ -650,7 +658,9 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
static async save() {
|
static async save(_, button) {
|
||||||
|
button.disabled = true;
|
||||||
|
|
||||||
const levelupData = Object.keys(this.levelup.levels).reduce((acc, level) => {
|
const levelupData = Object.keys(this.levelup.levels).reduce((acc, level) => {
|
||||||
if (level >= this.levelup.startLevel) {
|
if (level >= this.levelup.startLevel) {
|
||||||
acc[level] = this.levelup.levels[level].toObject();
|
acc[level] = this.levelup.levels[level].toObject();
|
||||||
|
|
@ -660,7 +670,8 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
await this.actor.levelUp(levelupData);
|
await this.actor.levelUp(levelupData);
|
||||||
if (this.itemBrowser) this.itemBrowser.close();
|
|
||||||
|
if (ui.compendiumBrowser) ui.compendiumBrowser.close();
|
||||||
this.close();
|
this.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
95
module/applications/levelup/levelupViewMode.mjs
Normal file
95
module/applications/levelup/levelupViewMode.mjs
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
import { chunkify } from '../../helpers/utils.mjs';
|
||||||
|
|
||||||
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
||||||
|
export default class DhlevelUpViewMode extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
|
constructor(actor) {
|
||||||
|
super({});
|
||||||
|
|
||||||
|
this.actor = actor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get title() {
|
||||||
|
return game.i18n.format('DAGGERHEART.APPLICATIONS.Levelup.viewModeTitle', { actor: this.actor.name });
|
||||||
|
}
|
||||||
|
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
classes: ['daggerheart', 'dialog', 'dh-style', 'levelup'],
|
||||||
|
position: { width: 1000, height: 'auto' },
|
||||||
|
window: {
|
||||||
|
resizable: true,
|
||||||
|
icon: 'fa-solid fa-arrow-turn-up'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static PARTS = {
|
||||||
|
main: { template: 'systems/daggerheart/templates/levelup/tabs/viewMode.hbs' }
|
||||||
|
};
|
||||||
|
|
||||||
|
async _prepareContext(_options) {
|
||||||
|
const context = await super._prepareContext(_options);
|
||||||
|
|
||||||
|
const { tiers } = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers);
|
||||||
|
const tierKeys = Object.keys(tiers);
|
||||||
|
const selections = Object.keys(this.actor.system.levelData.levelups).reduce(
|
||||||
|
(acc, key) => {
|
||||||
|
const level = this.actor.system.levelData.levelups[key];
|
||||||
|
Object.keys(level.selections).forEach(optionKey => {
|
||||||
|
const choice = level.selections[optionKey];
|
||||||
|
if (!acc[choice.tier][choice.optionKey]) acc[choice.tier][choice.optionKey] = {};
|
||||||
|
acc[choice.tier][choice.optionKey][choice.checkboxNr] = choice;
|
||||||
|
});
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
tierKeys.reduce((acc, key) => {
|
||||||
|
acc[key] = {};
|
||||||
|
return acc;
|
||||||
|
}, {})
|
||||||
|
);
|
||||||
|
|
||||||
|
context.tiers = tierKeys.map((tierKey, tierIndex) => {
|
||||||
|
const tier = tiers[tierKey];
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: tier.name,
|
||||||
|
active: true,
|
||||||
|
groups: Object.keys(tier.options).map(optionKey => {
|
||||||
|
const option = tier.options[optionKey];
|
||||||
|
|
||||||
|
const checkboxes = [...Array(option.checkboxSelections).keys()].flatMap(index => {
|
||||||
|
const checkboxNr = index + 1;
|
||||||
|
const checkboxData = selections[tierKey]?.[optionKey]?.[checkboxNr];
|
||||||
|
const checkbox = { ...option, checkboxNr, tier: tierKey, disabled: true };
|
||||||
|
|
||||||
|
if (checkboxData) {
|
||||||
|
checkbox.level = checkboxData.level;
|
||||||
|
checkbox.selected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return checkbox;
|
||||||
|
});
|
||||||
|
|
||||||
|
let label = game.i18n.localize(option.label);
|
||||||
|
return {
|
||||||
|
label: label,
|
||||||
|
checkboxGroups: chunkify(checkboxes, option.minCost, chunkedBoxes => {
|
||||||
|
const anySelected = chunkedBoxes.some(x => x.selected);
|
||||||
|
const anyDisabled = chunkedBoxes.some(x => x.disabled);
|
||||||
|
return {
|
||||||
|
multi: option.minCost > 1,
|
||||||
|
checkboxes: chunkedBoxes.map(x => ({
|
||||||
|
...x,
|
||||||
|
selected: anySelected,
|
||||||
|
disabled: anyDisabled
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
})
|
||||||
|
};
|
||||||
|
})
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
module/applications/scene/_module.mjs
Normal file
1
module/applications/scene/_module.mjs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export { default as DhSceneConfigSettings } from './sceneConfigSettings.mjs';
|
||||||
63
module/applications/scene/sceneConfigSettings.mjs
Normal file
63
module/applications/scene/sceneConfigSettings.mjs
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
export default class DhSceneConfigSettings extends foundry.applications.sheets.SceneConfig {
|
||||||
|
// static DEFAULT_OPTIONS = {
|
||||||
|
// ...super.DEFAULT_OPTIONS,
|
||||||
|
// form: {
|
||||||
|
// handler: this.updateData,
|
||||||
|
// closeOnSubmit: true
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
static buildParts() {
|
||||||
|
const { footer, tabs, ...parts } = super.PARTS;
|
||||||
|
const tmpParts = {
|
||||||
|
// tabs,
|
||||||
|
tabs: { template: 'systems/daggerheart/templates/scene/tabs.hbs' },
|
||||||
|
...parts,
|
||||||
|
dh: { template: 'systems/daggerheart/templates/scene/dh-config.hbs' },
|
||||||
|
footer
|
||||||
|
};
|
||||||
|
return tmpParts;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PARTS = DhSceneConfigSettings.buildParts();
|
||||||
|
|
||||||
|
static buildTabs() {
|
||||||
|
super.TABS.sheet.tabs.push({ id: 'dh', src: 'systems/daggerheart/assets/logos/FoundryBorneLogoWhite.svg' });
|
||||||
|
return super.TABS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static TABS = DhSceneConfigSettings.buildTabs();
|
||||||
|
|
||||||
|
_attachPartListeners(partId, htmlElement, options) {
|
||||||
|
super._attachPartListeners(partId, htmlElement, options);
|
||||||
|
switch (partId) {
|
||||||
|
case 'dh':
|
||||||
|
htmlElement.querySelector('#rangeMeasurementSetting')?.addEventListener('change', async event => {
|
||||||
|
const flagData = foundry.utils.mergeObject(this.document.flags.daggerheart, {
|
||||||
|
rangeMeasurement: { setting: event.target.value }
|
||||||
|
});
|
||||||
|
this.document.flags.daggerheart = flagData;
|
||||||
|
this.render();
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
async _preparePartContext(partId, context, options) {
|
||||||
|
context = await super._preparePartContext(partId, context, options);
|
||||||
|
switch (partId) {
|
||||||
|
case 'dh':
|
||||||
|
context.data = new game.system.api.data.scenes.DHScene(canvas.scene.flags.daggerheart);
|
||||||
|
context.variantRules = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
// static async updateData(event, _, formData) {
|
||||||
|
// const data = foundry.utils.expandObject(formData.object);
|
||||||
|
// this.close(data);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
@ -3,43 +3,48 @@ import { getDiceSoNicePreset } from '../../config/generalConfig.mjs';
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @import {ApplicationClickAction} from "@client/applications/_types.mjs"
|
||||||
|
*/
|
||||||
|
|
||||||
export default class DHAppearanceSettings extends HandlebarsApplicationMixin(ApplicationV2) {
|
export default class DHAppearanceSettings extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
constructor() {
|
/**@inheritdoc */
|
||||||
super({});
|
|
||||||
|
|
||||||
this.settings = new DhAppearance(
|
|
||||||
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance).toObject()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
get title() {
|
|
||||||
return game.i18n.localize('DAGGERHEART.SETTINGS.Menu.title');
|
|
||||||
}
|
|
||||||
|
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
tag: 'form',
|
tag: 'form',
|
||||||
id: 'daggerheart-appearance-settings',
|
id: 'daggerheart-appearance-settings',
|
||||||
classes: ['daggerheart', 'dialog', 'dh-style', 'setting'],
|
classes: ['daggerheart', 'dialog', 'dh-style', 'setting'],
|
||||||
position: { width: '600', height: 'auto' },
|
position: { width: '600', height: 'auto' },
|
||||||
window: {
|
window: {
|
||||||
|
title: 'DAGGERHEART.SETTINGS.Menu.title',
|
||||||
icon: 'fa-solid fa-gears'
|
icon: 'fa-solid fa-gears'
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
reset: this.reset,
|
reset: DHAppearanceSettings.#onReset,
|
||||||
save: this.save,
|
preview: DHAppearanceSettings.#onPreview
|
||||||
preview: this.preview
|
|
||||||
},
|
},
|
||||||
form: { handler: this.updateData, submitOnChange: true }
|
form: {
|
||||||
|
closeOnSubmit: true,
|
||||||
|
handler: DHAppearanceSettings.#onSubmit
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static PARTS = {
|
static PARTS = {
|
||||||
main: {
|
header: { template: 'systems/daggerheart/templates/settings/appearance-settings/header.hbs' },
|
||||||
template: 'systems/daggerheart/templates/settings/appearance-settings.hbs'
|
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
|
||||||
}
|
main: { template: 'systems/daggerheart/templates/settings/appearance-settings/main.hbs' },
|
||||||
|
diceSoNice: { template: 'systems/daggerheart/templates/settings/appearance-settings/diceSoNice.hbs' },
|
||||||
|
footer: { template: 'templates/generic/form-footer.hbs' }
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
static TABS = {
|
static TABS = {
|
||||||
|
general: {
|
||||||
|
tabs: [
|
||||||
|
{ id: 'main', label: 'DAGGERHEART.GENERAL.Tabs.general' },
|
||||||
|
{ id: 'diceSoNice', label: 'DAGGERHEART.SETTINGS.Menu.appearance.diceSoNice.title' }
|
||||||
|
],
|
||||||
|
initial: 'main'
|
||||||
|
},
|
||||||
diceSoNice: {
|
diceSoNice: {
|
||||||
tabs: [
|
tabs: [
|
||||||
{ id: 'hope', label: 'DAGGERHEART.GENERAL.hope' },
|
{ id: 'hope', label: 'DAGGERHEART.GENERAL.hope' },
|
||||||
|
|
@ -51,79 +56,150 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
changeTab(tab, group, options) {
|
/**@type {DhAppearance}*/
|
||||||
super.changeTab(tab, group, options);
|
setting;
|
||||||
|
|
||||||
this.render();
|
static #localized = false;
|
||||||
}
|
|
||||||
|
|
||||||
async _prepareContext(_options) {
|
/** @inheritDoc */
|
||||||
const context = await super._prepareContext(_options);
|
async _preFirstRender(_context, _options) {
|
||||||
context.settingFields = this.settings;
|
await super._preFirstRender(_context, _options);
|
||||||
|
if (!DHAppearanceSettings.#localized) {
|
||||||
context.showDiceSoNice = game.modules.get('dice-so-nice')?.active;
|
foundry.helpers.Localization.localizeDataModel(this.setting.constructor);
|
||||||
if (game.dice3d) {
|
DHAppearanceSettings.#localized = true;
|
||||||
context.diceSoNiceTextures = game.dice3d.exports.TEXTURELIST;
|
|
||||||
context.diceSoNiceColorsets = game.dice3d.exports.COLORSETS;
|
|
||||||
context.diceSoNiceMaterials = Object.keys(game.dice3d.DiceFactory.material_options).map(key => ({
|
|
||||||
key: key,
|
|
||||||
name: `DICESONICE.Material${key.capitalize()}`
|
|
||||||
}));
|
|
||||||
context.diceSoNiceSystems = [];
|
|
||||||
for (const [key, system] of game.dice3d.DiceFactory.systems.entries()) {
|
|
||||||
context.diceSoNiceSystems.push({ key, name: system.name });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context.diceTab = {
|
/** @inheritdoc */
|
||||||
key: this.tabGroups.diceSoNice,
|
_configureRenderParts(options) {
|
||||||
source: this.settings._source.diceSoNice[this.tabGroups.diceSoNice],
|
const parts = super._configureRenderParts(options);
|
||||||
fields: this.settings.schema.fields.diceSoNice.fields[this.tabGroups.diceSoNice].fields
|
if (!game.modules.get('dice-so-nice')?.active) {
|
||||||
};
|
delete parts.diceSoNice;
|
||||||
|
delete parts.tabs;
|
||||||
|
}
|
||||||
|
return parts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**@inheritdoc */
|
||||||
|
async _prepareContext(options) {
|
||||||
|
const context = await super._prepareContext(options);
|
||||||
|
if (options.isFirstRender)
|
||||||
|
this.setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance);
|
||||||
|
|
||||||
|
context.setting = this.setting;
|
||||||
|
context.fields = this.setting.schema.fields;
|
||||||
|
|
||||||
|
context.tabs = this._prepareTabs('general');
|
||||||
|
context.dsnTabs = this._prepareTabs('diceSoNice');
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async updateData(event, element, formData) {
|
/**@inheritdoc */
|
||||||
const updatedSettings = foundry.utils.expandObject(formData.object);
|
async _preparePartContext(partId, context, options) {
|
||||||
|
const partContext = await super._preparePartContext(partId, context, options);
|
||||||
await this.settings.updateSource(updatedSettings);
|
if (partId in context.tabs) partContext.tab = partContext.tabs[partId];
|
||||||
this.render();
|
switch (partId) {
|
||||||
|
case 'diceSoNice':
|
||||||
|
await this.prepareDiceSoNiceContext(partContext);
|
||||||
|
break;
|
||||||
|
case 'footer':
|
||||||
|
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' }
|
||||||
|
];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return partContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async preview() {
|
/**
|
||||||
const source = this.settings._source.diceSoNice[this.tabGroups.diceSoNice];
|
* Prepare render context for the DSN part.
|
||||||
let faces = 'd12';
|
* @param {ApplicationRenderContext} context
|
||||||
switch (this.tabGroups.diceSoNice) {
|
* @returns {Promise<void>}
|
||||||
case 'advantage':
|
* @protected
|
||||||
case 'disadvantage':
|
*/
|
||||||
faces = 'd6';
|
async prepareDiceSoNiceContext(context) {
|
||||||
|
context.diceSoNiceTextures = Object.entries(game.dice3d.exports.TEXTURELIST).reduce(
|
||||||
|
(acc, [k, v]) => ({
|
||||||
|
...acc,
|
||||||
|
[k]: v.name
|
||||||
|
}),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
context.diceSoNiceColorsets = Object.values(game.dice3d.exports.COLORSETS).reduce(
|
||||||
|
(acc, v) => ({
|
||||||
|
...acc,
|
||||||
|
[v.id]: v.description
|
||||||
|
}),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
context.diceSoNiceMaterials = Object.keys(game.dice3d.DiceFactory.material_options).reduce(
|
||||||
|
(acc, key) => ({
|
||||||
|
...acc,
|
||||||
|
[key]: `DICESONICE.Material${key.capitalize()}`
|
||||||
|
}),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
context.diceSoNiceSystems = Object.fromEntries(
|
||||||
|
[...game.dice3d.DiceFactory.systems].map(([k, v]) => [k, v.name])
|
||||||
|
);
|
||||||
|
context.diceSoNiceFonts = game.dice3d.exports.Utils.prepareFontList();
|
||||||
|
|
||||||
|
foundry.utils.mergeObject(
|
||||||
|
context.dsnTabs,
|
||||||
|
['hope', 'fear', 'advantage', 'disadvantage'].reduce(
|
||||||
|
(acc, key) => ({
|
||||||
|
...acc,
|
||||||
|
[key]: {
|
||||||
|
values: this.setting.diceSoNice[key],
|
||||||
|
fields: this.setting.schema.getField(`diceSoNice.${key}`).fields
|
||||||
}
|
}
|
||||||
const preset = await getDiceSoNicePreset(source, faces);
|
}),
|
||||||
const diceSoNiceRoll = await new Roll(`1${faces}`).evaluate();
|
{}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit the configuration form.
|
||||||
|
* @this {DHAppearanceSettings}
|
||||||
|
* @param {SubmitEvent} event
|
||||||
|
* @param {HTMLFormElement} form
|
||||||
|
* @param {foundry.applications.ux.FormDataExtended} formData
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
static async #onSubmit(event, form, formData) {
|
||||||
|
const data = this.setting.schema.clean(foundry.utils.expandObject(formData.object));
|
||||||
|
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit the configuration form.
|
||||||
|
* @this {DHAppearanceSettings}
|
||||||
|
* @type {ApplicationClickAction}
|
||||||
|
*/
|
||||||
|
static async #onPreview(_, target) {
|
||||||
|
const formData = new foundry.applications.ux.FormDataExtended(target.closest('form'));
|
||||||
|
const { diceSoNice } = foundry.utils.expandObject(formData.object);
|
||||||
|
const { key } = target.dataset;
|
||||||
|
const faces = ['advantage', 'disadvantage'].includes(key) ? 'd6' : 'd12';
|
||||||
|
const preset = await getDiceSoNicePreset(diceSoNice[key], faces);
|
||||||
|
const diceSoNiceRoll = await new foundry.dice.Roll(`1${faces}`).evaluate();
|
||||||
diceSoNiceRoll.dice[0].options.appearance = preset.appearance;
|
diceSoNiceRoll.dice[0].options.appearance = preset.appearance;
|
||||||
diceSoNiceRoll.dice[0].options.modelFile = preset.modelFile;
|
diceSoNiceRoll.dice[0].options.modelFile = preset.modelFile;
|
||||||
|
|
||||||
await game.dice3d.showForRoll(diceSoNiceRoll, game.user, false);
|
await game.dice3d.showForRoll(diceSoNiceRoll, game.user, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async reset() {
|
/**
|
||||||
this.settings = new DhAppearance();
|
* Reset the form back to default values.
|
||||||
this.render();
|
* @this {DHAppearanceSettings}
|
||||||
}
|
* @type {ApplicationClickAction}
|
||||||
|
*/
|
||||||
static async save() {
|
static async #onReset() {
|
||||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance, this.settings.toObject());
|
this.setting = new this.setting.constructor();
|
||||||
|
this.render({ force: false });
|
||||||
this.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
_getTabs(tabs) {
|
|
||||||
for (const v of Object.values(tabs)) {
|
|
||||||
v.active = this.tabGroups[v.group] ? this.tabGroups[v.group] === v.id : v.active;
|
|
||||||
v.cssClass = v.active ? 'active' : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
return tabs;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,13 +35,14 @@ export default class DhAutomationSettings extends HandlebarsApplicationMixin(App
|
||||||
header: { template: 'systems/daggerheart/templates/settings/automation-settings/header.hbs' },
|
header: { template: 'systems/daggerheart/templates/settings/automation-settings/header.hbs' },
|
||||||
general: { template: 'systems/daggerheart/templates/settings/automation-settings/general.hbs' },
|
general: { template: 'systems/daggerheart/templates/settings/automation-settings/general.hbs' },
|
||||||
rules: { template: 'systems/daggerheart/templates/settings/automation-settings/rules.hbs' },
|
rules: { template: 'systems/daggerheart/templates/settings/automation-settings/rules.hbs' },
|
||||||
|
roll: { template: 'systems/daggerheart/templates/settings/automation-settings/roll.hbs' },
|
||||||
footer: { template: 'systems/daggerheart/templates/settings/automation-settings/footer.hbs' }
|
footer: { template: 'systems/daggerheart/templates/settings/automation-settings/footer.hbs' }
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
static TABS = {
|
static TABS = {
|
||||||
main: {
|
main: {
|
||||||
tabs: [{ id: 'general' }, { id: 'rules' }],
|
tabs: [{ id: 'general' }, { id: 'rules' }, { id: 'roll' }],
|
||||||
initial: 'general',
|
initial: 'general',
|
||||||
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { DhHomebrew } from '../../data/settings/_module.mjs';
|
import { DhHomebrew } from '../../data/settings/_module.mjs';
|
||||||
import { slugify } from '../../helpers/utils.mjs';
|
import { slugify } from '../../helpers/utils.mjs';
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
||||||
export default class DhHomebrewSettings extends HandlebarsApplicationMixin(ApplicationV2) {
|
export default class DhHomebrewSettings extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
|
|
@ -10,11 +11,14 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
||||||
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).toObject()
|
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).toObject()
|
||||||
);
|
);
|
||||||
|
|
||||||
this.selected = {
|
this.selected = this.#getDefaultAdversaryType();
|
||||||
domain: null
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#getDefaultAdversaryType = () => ({
|
||||||
|
domain: null,
|
||||||
|
adversaryType: null
|
||||||
|
});
|
||||||
|
|
||||||
get title() {
|
get title() {
|
||||||
return game.i18n.localize('DAGGERHEART.SETTINGS.Menu.title');
|
return game.i18n.localize('DAGGERHEART.SETTINGS.Menu.title');
|
||||||
}
|
}
|
||||||
|
|
@ -28,6 +32,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
||||||
icon: 'fa-solid fa-gears'
|
icon: 'fa-solid fa-gears'
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
|
editCurrencyIcon: this.changeCurrencyIcon,
|
||||||
addItem: this.addItem,
|
addItem: this.addItem,
|
||||||
editItem: this.editItem,
|
editItem: this.editItem,
|
||||||
removeItem: this.removeItem,
|
removeItem: this.removeItem,
|
||||||
|
|
@ -35,7 +40,11 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
||||||
addDomain: this.addDomain,
|
addDomain: this.addDomain,
|
||||||
toggleSelectedDomain: this.toggleSelectedDomain,
|
toggleSelectedDomain: this.toggleSelectedDomain,
|
||||||
deleteDomain: this.deleteDomain,
|
deleteDomain: this.deleteDomain,
|
||||||
|
addAdversaryType: this.addAdversaryType,
|
||||||
|
deleteAdversaryType: this.deleteAdversaryType,
|
||||||
|
selectAdversaryType: this.selectAdversaryType,
|
||||||
save: this.save,
|
save: this.save,
|
||||||
|
resetTokenSizes: this.resetTokenSizes,
|
||||||
reset: this.reset
|
reset: this.reset
|
||||||
},
|
},
|
||||||
form: { handler: this.updateData, submitOnChange: true }
|
form: { handler: this.updateData, submitOnChange: true }
|
||||||
|
|
@ -45,6 +54,8 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
||||||
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
|
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
|
||||||
settings: { template: 'systems/daggerheart/templates/settings/homebrew-settings/settings.hbs' },
|
settings: { template: 'systems/daggerheart/templates/settings/homebrew-settings/settings.hbs' },
|
||||||
domains: { template: 'systems/daggerheart/templates/settings/homebrew-settings/domains.hbs' },
|
domains: { template: 'systems/daggerheart/templates/settings/homebrew-settings/domains.hbs' },
|
||||||
|
types: { template: 'systems/daggerheart/templates/settings/homebrew-settings/types.hbs' },
|
||||||
|
itemTypes: { template: 'systems/daggerheart/templates/settings/homebrew-settings/itemFeatures.hbs' },
|
||||||
downtime: { template: 'systems/daggerheart/templates/settings/homebrew-settings/downtime.hbs' },
|
downtime: { template: 'systems/daggerheart/templates/settings/homebrew-settings/downtime.hbs' },
|
||||||
footer: { template: 'systems/daggerheart/templates/settings/homebrew-settings/footer.hbs' }
|
footer: { template: 'systems/daggerheart/templates/settings/homebrew-settings/footer.hbs' }
|
||||||
};
|
};
|
||||||
|
|
@ -52,12 +63,19 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
static TABS = {
|
static TABS = {
|
||||||
main: {
|
main: {
|
||||||
tabs: [{ id: 'settings' }, { id: 'domains' }, { id: 'downtime' }],
|
tabs: [{ id: 'settings' }, { id: 'domains' }, { id: 'types' }, { id: 'itemFeatures' }, { id: 'downtime' }],
|
||||||
initial: 'settings',
|
initial: 'settings',
|
||||||
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
changeTab(tab, group, options) {
|
||||||
|
super.changeTab(tab, group, options);
|
||||||
|
this.selected = this.#getDefaultAdversaryType();
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
async _prepareContext(_options) {
|
async _prepareContext(_options) {
|
||||||
const context = await super._prepareContext(_options);
|
const context = await super._prepareContext(_options);
|
||||||
context.settingFields = this.settings;
|
context.settingFields = this.settings;
|
||||||
|
|
@ -79,6 +97,11 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
||||||
context.configDomains = CONFIG.DH.DOMAIN.domains;
|
context.configDomains = CONFIG.DH.DOMAIN.domains;
|
||||||
context.homebrewDomains = this.settings.domains;
|
context.homebrewDomains = this.settings.domains;
|
||||||
break;
|
break;
|
||||||
|
case 'types':
|
||||||
|
context.selectedAdversaryType = this.selected.adversaryType
|
||||||
|
? { id: this.selected.adversaryType, ...this.settings.adversaryTypes[this.selected.adversaryType] }
|
||||||
|
: null;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
|
|
@ -94,34 +117,100 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
static async addItem(_, target) {
|
static async changeCurrencyIcon(_, target) {
|
||||||
|
const type = target.dataset.currency;
|
||||||
|
const currentIcon = this.settings.currency[type].icon;
|
||||||
|
const icon = await foundry.applications.api.DialogV2.input({
|
||||||
|
classes: ['daggerheart', 'dh-style', 'change-currency-icon'],
|
||||||
|
content: await foundry.applications.handlebars.renderTemplate(
|
||||||
|
'systems/daggerheart/templates/settings/homebrew-settings/change-currency-icon.hbs',
|
||||||
|
{ currentIcon }
|
||||||
|
),
|
||||||
|
window: {
|
||||||
|
title: game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.currency.changeIcon'),
|
||||||
|
icon: 'fa-solid fa-coins'
|
||||||
|
},
|
||||||
|
render: (_, dialog) => {
|
||||||
|
const icon = dialog.element.querySelector('.displayed-icon i');
|
||||||
|
const input = dialog.element.querySelector('input');
|
||||||
|
const reset = dialog.element.querySelector('button[data-action=reset]');
|
||||||
|
input.addEventListener('input', () => {
|
||||||
|
icon.classList.value = input.value;
|
||||||
|
});
|
||||||
|
reset.addEventListener('click', () => {
|
||||||
|
const currencyField = DhHomebrew.schema.fields.currency.fields[type];
|
||||||
|
const initial = currencyField.fields.icon.getInitialValue();
|
||||||
|
input.value = icon.classList.value = initial;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
ok: {
|
||||||
|
callback: (_, button) => button.form.elements.icon.value
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (icon !== null) {
|
||||||
await this.settings.updateSource({
|
await this.settings.updateSource({
|
||||||
[`restMoves.${target.dataset.type}.moves.${foundry.utils.randomID()}`]: {
|
[`currency.${type}.icon`]: icon
|
||||||
|
});
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async addItem(_, target) {
|
||||||
|
const { type } = target.dataset;
|
||||||
|
if (['shortRest', 'longRest'].includes(type)) {
|
||||||
|
await this.settings.updateSource({
|
||||||
|
[`restMoves.${type}.moves.${foundry.utils.randomID()}`]: {
|
||||||
name: game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.newDowntimeMove'),
|
name: game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.newDowntimeMove'),
|
||||||
img: 'icons/magic/life/cross-worn-green.webp',
|
img: 'icons/magic/life/cross-worn-green.webp',
|
||||||
description: '',
|
description: '',
|
||||||
actions: []
|
actions: []
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else if (['armorFeatures', 'weaponFeatures'].includes(type)) {
|
||||||
|
await this.settings.updateSource({
|
||||||
|
[`itemFeatures.${type}.${foundry.utils.randomID()}`]: {
|
||||||
|
name: game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.newFeature'),
|
||||||
|
img: 'icons/magic/life/cross-worn-green.webp',
|
||||||
|
description: '',
|
||||||
|
actions: [],
|
||||||
|
effects: []
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
static async editItem(_, target) {
|
static async editItem(_, target) {
|
||||||
const move = this.settings.restMoves[target.dataset.type].moves[target.dataset.id];
|
const { type, id } = target.dataset;
|
||||||
const path = `restMoves.${target.dataset.type}.moves.${target.dataset.id}`;
|
const isDowntime = ['shortRest', 'longRest'].includes(type);
|
||||||
const editedMove = await game.system.api.applications.sheetConfigs.DowntimeConfig.configure(
|
const path = isDowntime ? `restMoves.${type}.moves.${id}` : `itemFeatures.${type}.${id}`;
|
||||||
move,
|
const featureBase = isDowntime ? this.settings.restMoves[type].moves[id] : this.settings.itemFeatures[type][id];
|
||||||
path,
|
|
||||||
this.settings
|
|
||||||
);
|
|
||||||
if (!editedMove) return;
|
|
||||||
|
|
||||||
await this.updateAction.bind(this)(editedMove, target.dataset.type, target.dataset.id);
|
const configTitle = isDowntime
|
||||||
|
? game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.downtimeMove')
|
||||||
|
: type === 'armorFeatures'
|
||||||
|
? game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.armorFeature')
|
||||||
|
: game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.weaponFeature');
|
||||||
|
|
||||||
|
const editedBase = await game.system.api.applications.sheetConfigs.SettingFeatureConfig.configure(
|
||||||
|
configTitle,
|
||||||
|
featureBase,
|
||||||
|
path,
|
||||||
|
this.settings,
|
||||||
|
{ hasIcon: isDowntime, hasEffects: !isDowntime }
|
||||||
|
);
|
||||||
|
if (!editedBase) return;
|
||||||
|
|
||||||
|
await this.updateAction.bind(this)(editedBase, target.dataset.type, target.dataset.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateAction(data, type, id) {
|
async updateAction(data, type, id) {
|
||||||
|
const isDowntime = ['shortRest', 'longRest'].includes(type);
|
||||||
|
const path = isDowntime ? `restMoves.${type}.moves` : `itemFeatures.${type}`;
|
||||||
await this.settings.updateSource({
|
await this.settings.updateSource({
|
||||||
[`restMoves.${type}.moves.${id}`]: {
|
[`${path}.${id}`]: {
|
||||||
actions: data.actions,
|
actions: data.actions,
|
||||||
name: data.name,
|
name: data.name,
|
||||||
icon: data.icon,
|
icon: data.icon,
|
||||||
|
|
@ -129,12 +218,16 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
||||||
description: data.description
|
description: data.description
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
static async removeItem(_, target) {
|
static async removeItem(_, target) {
|
||||||
|
const { type, id } = target.dataset;
|
||||||
|
const isDowntime = ['shortRest', 'longRest'].includes(type);
|
||||||
|
const path = isDowntime ? `restMoves.${type}.moves` : `itemFeatures.${type}`;
|
||||||
await this.settings.updateSource({
|
await this.settings.updateSource({
|
||||||
[`restMoves.${target.dataset.type}.moves.-=${target.dataset.id}`]: null
|
[`${path}.-=${id}`]: null
|
||||||
});
|
});
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
@ -301,11 +394,45 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async addAdversaryType(_, target) {
|
||||||
|
const newId = foundry.utils.randomID();
|
||||||
|
await this.settings.updateSource({
|
||||||
|
[`adversaryTypes.${newId}`]: {
|
||||||
|
id: newId,
|
||||||
|
label: game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.adversaryType.newType')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.selected.adversaryType = newId;
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
static async deleteAdversaryType(_, target) {
|
||||||
|
const { key } = target.dataset;
|
||||||
|
await this.settings.updateSource({ [`adversaryTypes.-=${key}`]: null });
|
||||||
|
|
||||||
|
this.selected.adversaryType = this.selected.adversaryType === key ? null : this.selected.adversaryType;
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
static async selectAdversaryType(_, target) {
|
||||||
|
this.selected.adversaryType = this.selected.adversaryType === target.dataset.type ? null : target.dataset.type;
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
static async save() {
|
static async save() {
|
||||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, this.settings.toObject());
|
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, this.settings.toObject());
|
||||||
this.close();
|
this.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async resetTokenSizes() {
|
||||||
|
await this.settings.updateSource({
|
||||||
|
tokenSizes: this.settings.schema.fields.tokenSizes.initial
|
||||||
|
});
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
static async reset() {
|
static async reset() {
|
||||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||||
window: {
|
window: {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
export { default as ActionConfig } from './action-config.mjs';
|
export { default as ActionConfig } from './action-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 CompanionSettings } from './companion-settings.mjs';
|
export { default as CompanionSettings } from './companion-settings.mjs';
|
||||||
export { default as DowntimeConfig } from './downtimeConfig.mjs';
|
export { default as SettingActiveEffectConfig } from './setting-active-effect-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';
|
||||||
export { default as DhTokenConfig } from './token-config.mjs';
|
export { default as DhTokenConfig } from './token-config.mjs';
|
||||||
|
|
|
||||||
236
module/applications/sheets-configs/action-base-config.mjs
Normal file
236
module/applications/sheets-configs/action-base-config.mjs
Normal file
|
|
@ -0,0 +1,236 @@
|
||||||
|
import DaggerheartSheet from '../sheets/daggerheart-sheet.mjs';
|
||||||
|
|
||||||
|
const { ApplicationV2 } = foundry.applications.api;
|
||||||
|
export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) {
|
||||||
|
constructor(action) {
|
||||||
|
super({});
|
||||||
|
|
||||||
|
this.action = action;
|
||||||
|
this.openSection = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
get title() {
|
||||||
|
return `${game.i18n.localize('DAGGERHEART.GENERAL.Tabs.settings')}: ${this.action.name}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
tag: 'form',
|
||||||
|
classes: ['daggerheart', 'dh-style', 'dialog', 'max-800'],
|
||||||
|
window: {
|
||||||
|
icon: 'fa-solid fa-wrench',
|
||||||
|
resizable: false
|
||||||
|
},
|
||||||
|
position: { width: 600, height: 'auto' },
|
||||||
|
actions: {
|
||||||
|
toggleSection: this.toggleSection,
|
||||||
|
addEffect: this.addEffect,
|
||||||
|
removeEffect: this.removeEffect,
|
||||||
|
addElement: this.addElement,
|
||||||
|
removeElement: this.removeElement,
|
||||||
|
editEffect: this.editEffect,
|
||||||
|
addDamage: this.addDamage,
|
||||||
|
removeDamage: this.removeDamage
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
handler: this.updateForm,
|
||||||
|
submitOnChange: true,
|
||||||
|
closeOnSubmit: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static PARTS = {
|
||||||
|
header: {
|
||||||
|
id: 'header',
|
||||||
|
template: 'systems/daggerheart/templates/sheets-settings/action-settings/header.hbs'
|
||||||
|
},
|
||||||
|
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
|
||||||
|
base: {
|
||||||
|
id: 'base',
|
||||||
|
template: 'systems/daggerheart/templates/sheets-settings/action-settings/base.hbs'
|
||||||
|
},
|
||||||
|
configuration: {
|
||||||
|
id: 'configuration',
|
||||||
|
template: 'systems/daggerheart/templates/sheets-settings/action-settings/configuration.hbs'
|
||||||
|
},
|
||||||
|
effect: {
|
||||||
|
id: 'effect',
|
||||||
|
template: 'systems/daggerheart/templates/sheets-settings/action-settings/effect.hbs'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static TABS = {
|
||||||
|
base: {
|
||||||
|
active: true,
|
||||||
|
cssClass: '',
|
||||||
|
group: 'primary',
|
||||||
|
id: 'base',
|
||||||
|
icon: null,
|
||||||
|
label: 'DAGGERHEART.GENERAL.Tabs.base'
|
||||||
|
},
|
||||||
|
config: {
|
||||||
|
active: false,
|
||||||
|
cssClass: '',
|
||||||
|
group: 'primary',
|
||||||
|
id: 'config',
|
||||||
|
icon: null,
|
||||||
|
label: 'DAGGERHEART.GENERAL.Tabs.configuration'
|
||||||
|
},
|
||||||
|
effect: {
|
||||||
|
active: false,
|
||||||
|
cssClass: '',
|
||||||
|
group: 'primary',
|
||||||
|
id: 'effect',
|
||||||
|
icon: null,
|
||||||
|
label: 'DAGGERHEART.GENERAL.Tabs.effects'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static CLEAN_ARRAYS = ['damage.parts', 'cost', 'effects'];
|
||||||
|
|
||||||
|
_getTabs(tabs) {
|
||||||
|
for (const v of Object.values(tabs)) {
|
||||||
|
v.active = this.tabGroups[v.group] ? this.tabGroups[v.group] === v.id : v.active;
|
||||||
|
v.cssClass = v.active ? 'active' : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return tabs;
|
||||||
|
}
|
||||||
|
|
||||||
|
async _prepareContext(_options) {
|
||||||
|
const context = await super._prepareContext(_options, 'action');
|
||||||
|
context.source = this.action.toObject(true);
|
||||||
|
context.openSection = this.openSection;
|
||||||
|
context.tabs = this._getTabs(this.constructor.TABS);
|
||||||
|
context.config = CONFIG.DH;
|
||||||
|
if (this.action.damage?.hasOwnProperty('includeBase') && this.action.type === 'attack')
|
||||||
|
context.hasBaseDamage = !!this.action.parent.attack;
|
||||||
|
context.costOptions = this.getCostOptions();
|
||||||
|
context.getRollTypeOptions = this.getRollTypeOptions();
|
||||||
|
context.disableOption = this.disableOption.bind(this);
|
||||||
|
context.isNPC = this.action.actor?.isNPC;
|
||||||
|
context.baseSaveDifficulty = this.action.actor?.baseSaveDifficulty;
|
||||||
|
context.baseAttackBonus = this.action.actor?.system.attack?.roll.bonus;
|
||||||
|
context.hasRoll = this.action.hasRoll;
|
||||||
|
|
||||||
|
const settingsTiers = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers).tiers;
|
||||||
|
context.tierOptions = [
|
||||||
|
{ key: 1, label: game.i18n.localize('DAGGERHEART.GENERAL.Tiers.1') },
|
||||||
|
...Object.values(settingsTiers).map(x => ({ key: x.tier, label: x.name }))
|
||||||
|
];
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
static toggleSection(_, button) {
|
||||||
|
this.openSection = button.dataset.section === this.openSection ? null : button.dataset.section;
|
||||||
|
this.render(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCostOptions() {
|
||||||
|
const options = foundry.utils.deepClone(CONFIG.DH.GENERAL.abilityCosts);
|
||||||
|
const resource = this.action.parent.resource;
|
||||||
|
if (resource) {
|
||||||
|
options.resource = {
|
||||||
|
label: 'DAGGERHEART.GENERAL.itemResource',
|
||||||
|
group: 'Global'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.action.parent.metadata?.isQuantifiable) {
|
||||||
|
options.quantity = {
|
||||||
|
label: 'DAGGERHEART.GENERAL.itemQuantity',
|
||||||
|
group: 'Global'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
getRollTypeOptions() {
|
||||||
|
const types = foundry.utils.deepClone(CONFIG.DH.GENERAL.rollTypes);
|
||||||
|
if (!this.action.actor) return types;
|
||||||
|
Object.values(types).forEach(t => {
|
||||||
|
if (this.action.actor.type !== 'character' && t.playerOnly) delete types[t.id];
|
||||||
|
});
|
||||||
|
return types;
|
||||||
|
}
|
||||||
|
|
||||||
|
disableOption(index, costOptions, choices) {
|
||||||
|
const filtered = foundry.utils.deepClone(costOptions);
|
||||||
|
Object.keys(filtered).forEach(o => {
|
||||||
|
if (choices.find((c, idx) => c.type === o && index !== idx)) filtered[o].disabled = true;
|
||||||
|
});
|
||||||
|
return filtered;
|
||||||
|
}
|
||||||
|
|
||||||
|
_prepareSubmitData(_event, formData) {
|
||||||
|
const submitData = foundry.utils.expandObject(formData.object);
|
||||||
|
|
||||||
|
const itemAbilityCostKeys = Object.keys(CONFIG.DH.GENERAL.itemAbilityCosts);
|
||||||
|
for (const keyPath of this.constructor.CLEAN_ARRAYS) {
|
||||||
|
const data = foundry.utils.getProperty(submitData, keyPath);
|
||||||
|
const dataValues = data ? Object.values(data) : [];
|
||||||
|
if (keyPath === 'cost') {
|
||||||
|
for (var value of dataValues) {
|
||||||
|
value.itemId = itemAbilityCostKeys.includes(value.key) ? this.action.parent.parent.id : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data) foundry.utils.setProperty(submitData, keyPath, dataValues);
|
||||||
|
}
|
||||||
|
return submitData;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async updateForm(event, _, formData) {
|
||||||
|
const submitData = this._prepareSubmitData(event, formData),
|
||||||
|
data = foundry.utils.mergeObject(this.action.toObject(), submitData);
|
||||||
|
this.action = await this.action.update(data);
|
||||||
|
|
||||||
|
this.sheetUpdate?.(this.action);
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
static addElement(event) {
|
||||||
|
const data = this.action.toObject(),
|
||||||
|
key = event.target.closest('[data-key]').dataset.key;
|
||||||
|
if (!this.action[key]) return;
|
||||||
|
|
||||||
|
data[key].push(this.action.defaultValues[key] ?? {});
|
||||||
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
|
}
|
||||||
|
|
||||||
|
static removeElement(event, button) {
|
||||||
|
event.stopPropagation();
|
||||||
|
const data = this.action.toObject(),
|
||||||
|
key = event.target.closest('[data-key]').dataset.key,
|
||||||
|
index = button.dataset.index;
|
||||||
|
data[key].splice(index, 1);
|
||||||
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
|
}
|
||||||
|
|
||||||
|
static addDamage(_event) {
|
||||||
|
if (!this.action.damage.parts) return;
|
||||||
|
const data = this.action.toObject(),
|
||||||
|
part = {};
|
||||||
|
if (this.action.actor?.isNPC) part.value = { multiplier: 'flat' };
|
||||||
|
data.damage.parts.push(part);
|
||||||
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
|
}
|
||||||
|
|
||||||
|
static removeDamage(_event, button) {
|
||||||
|
if (!this.action.damage.parts) return;
|
||||||
|
const data = this.action.toObject(),
|
||||||
|
index = button.dataset.index;
|
||||||
|
data.damage.parts.splice(index, 1);
|
||||||
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Specific implementation in extending classes **/
|
||||||
|
static async addEffect(_event) {}
|
||||||
|
static removeEffect(_event, _button) {}
|
||||||
|
static editEffect(_event) {}
|
||||||
|
|
||||||
|
async close(options) {
|
||||||
|
this.tabGroups.primary = 'base';
|
||||||
|
await super.close(options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,232 +1,32 @@
|
||||||
import DaggerheartSheet from '../sheets/daggerheart-sheet.mjs';
|
import DHActionBaseConfig from './action-base-config.mjs';
|
||||||
|
|
||||||
const { ApplicationV2 } = foundry.applications.api;
|
|
||||||
export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
|
|
||||||
constructor(action, sheetUpdate) {
|
|
||||||
super({});
|
|
||||||
|
|
||||||
this.action = action;
|
|
||||||
this.sheetUpdate = sheetUpdate;
|
|
||||||
this.openSection = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
get title() {
|
|
||||||
return `${game.i18n.localize('DAGGERHEART.GENERAL.Tabs.settings')}: ${this.action.name}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
export default class DHActionConfig extends DHActionBaseConfig {
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
tag: 'form',
|
...DHActionBaseConfig.DEFAULT_OPTIONS,
|
||||||
classes: ['daggerheart', 'dh-style', 'dialog', 'max-800'],
|
|
||||||
window: {
|
|
||||||
icon: 'fa-solid fa-wrench',
|
|
||||||
resizable: false
|
|
||||||
},
|
|
||||||
position: { width: 600, height: 'auto' },
|
|
||||||
actions: {
|
actions: {
|
||||||
toggleSection: this.toggleSection,
|
...DHActionBaseConfig.DEFAULT_OPTIONS.actions,
|
||||||
addEffect: this.addEffect,
|
addEffect: this.addEffect,
|
||||||
removeEffect: this.removeEffect,
|
removeEffect: this.removeEffect,
|
||||||
addElement: this.addElement,
|
editEffect: this.editEffect
|
||||||
removeElement: this.removeElement,
|
|
||||||
editEffect: this.editEffect,
|
|
||||||
addDamage: this.addDamage,
|
|
||||||
removeDamage: this.removeDamage
|
|
||||||
},
|
|
||||||
form: {
|
|
||||||
handler: this.updateForm,
|
|
||||||
submitOnChange: true,
|
|
||||||
closeOnSubmit: false
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static PARTS = {
|
async _prepareContext(options) {
|
||||||
header: {
|
const context = await super._prepareContext(options);
|
||||||
id: 'header',
|
|
||||||
template: 'systems/daggerheart/templates/sheets-settings/action-settings/header.hbs'
|
|
||||||
},
|
|
||||||
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
|
|
||||||
base: {
|
|
||||||
id: 'base',
|
|
||||||
template: 'systems/daggerheart/templates/sheets-settings/action-settings/base.hbs'
|
|
||||||
},
|
|
||||||
configuration: {
|
|
||||||
id: 'configuration',
|
|
||||||
template: 'systems/daggerheart/templates/sheets-settings/action-settings/configuration.hbs'
|
|
||||||
},
|
|
||||||
effect: {
|
|
||||||
id: 'effect',
|
|
||||||
template: 'systems/daggerheart/templates/sheets-settings/action-settings/effect.hbs'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static TABS = {
|
|
||||||
base: {
|
|
||||||
active: true,
|
|
||||||
cssClass: '',
|
|
||||||
group: 'primary',
|
|
||||||
id: 'base',
|
|
||||||
icon: null,
|
|
||||||
label: 'Base'
|
|
||||||
},
|
|
||||||
config: {
|
|
||||||
active: false,
|
|
||||||
cssClass: '',
|
|
||||||
group: 'primary',
|
|
||||||
id: 'config',
|
|
||||||
icon: null,
|
|
||||||
label: 'Configuration'
|
|
||||||
},
|
|
||||||
effect: {
|
|
||||||
active: false,
|
|
||||||
cssClass: '',
|
|
||||||
group: 'primary',
|
|
||||||
id: 'effect',
|
|
||||||
icon: null,
|
|
||||||
label: 'Effect'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static CLEAN_ARRAYS = ['damage.parts', 'cost', 'effects'];
|
|
||||||
|
|
||||||
_getTabs(tabs) {
|
|
||||||
for (const v of Object.values(tabs)) {
|
|
||||||
v.active = this.tabGroups[v.group] ? this.tabGroups[v.group] === v.id : v.active;
|
|
||||||
v.cssClass = v.active ? 'active' : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
return tabs;
|
|
||||||
}
|
|
||||||
|
|
||||||
async _prepareContext(_options) {
|
|
||||||
const context = await super._prepareContext(_options, 'action');
|
|
||||||
context.source = this.action.toObject(false);
|
|
||||||
context.openSection = this.openSection;
|
|
||||||
context.tabs = this._getTabs(this.constructor.TABS);
|
|
||||||
context.config = CONFIG.DH;
|
|
||||||
if (!!this.action.effects) context.effects = this.action.effects.map(e => this.action.item.effects.get(e._id));
|
if (!!this.action.effects) context.effects = this.action.effects.map(e => this.action.item.effects.get(e._id));
|
||||||
if (this.action.damage?.hasOwnProperty('includeBase') && this.action.type === 'attack')
|
|
||||||
context.hasBaseDamage = !!this.action.parent.attack;
|
|
||||||
context.getEffectDetails = this.getEffectDetails.bind(this);
|
context.getEffectDetails = this.getEffectDetails.bind(this);
|
||||||
context.costOptions = this.getCostOptions();
|
|
||||||
context.getRollTypeOptions = this.getRollTypeOptions();
|
|
||||||
context.disableOption = this.disableOption.bind(this);
|
|
||||||
context.isNPC = this.action.actor?.isNPC;
|
|
||||||
context.baseSaveDifficulty = this.action.actor?.baseSaveDifficulty;
|
|
||||||
context.baseAttackBonus = this.action.actor?.system.attack?.roll.bonus;
|
|
||||||
context.hasRoll = this.action.hasRoll;
|
|
||||||
|
|
||||||
const settingsTiers = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers).tiers;
|
|
||||||
context.tierOptions = [
|
|
||||||
{ key: 1, label: game.i18n.localize('DAGGERHEART.GENERAL.Tiers.1') },
|
|
||||||
...Object.values(settingsTiers).map(x => ({ key: x.tier, label: x.name }))
|
|
||||||
];
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
static toggleSection(_, button) {
|
static async addEffect(_event) {
|
||||||
this.openSection = button.dataset.section === this.openSection ? null : button.dataset.section;
|
|
||||||
this.render(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
getCostOptions() {
|
|
||||||
const options = foundry.utils.deepClone(CONFIG.DH.GENERAL.abilityCosts);
|
|
||||||
const resource = this.action.parent.resource;
|
|
||||||
if (resource) {
|
|
||||||
options[this.action.parent.parent.id] = {
|
|
||||||
label: 'DAGGERHEART.GENERAL.itemResource',
|
|
||||||
group: 'Global'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
|
|
||||||
getRollTypeOptions() {
|
|
||||||
const types = foundry.utils.deepClone(CONFIG.DH.GENERAL.rollTypes);
|
|
||||||
if (!this.action.actor) return types;
|
|
||||||
Object.values(types).forEach(t => {
|
|
||||||
if (this.action.actor.type !== 'character' && t.playerOnly) delete types[t.id];
|
|
||||||
});
|
|
||||||
return types;
|
|
||||||
}
|
|
||||||
|
|
||||||
disableOption(index, costOptions, choices) {
|
|
||||||
const filtered = foundry.utils.deepClone(costOptions);
|
|
||||||
Object.keys(filtered).forEach(o => {
|
|
||||||
if (choices.find((c, idx) => c.type === o && index !== idx)) filtered[o].disabled = true;
|
|
||||||
});
|
|
||||||
return filtered;
|
|
||||||
}
|
|
||||||
|
|
||||||
getEffectDetails(id) {
|
|
||||||
return this.action.item.effects.get(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
_prepareSubmitData(_event, formData) {
|
|
||||||
const submitData = foundry.utils.expandObject(formData.object);
|
|
||||||
for (const keyPath of this.constructor.CLEAN_ARRAYS) {
|
|
||||||
const data = foundry.utils.getProperty(submitData, keyPath);
|
|
||||||
const dataValues = data ? Object.values(data) : [];
|
|
||||||
if (keyPath === 'cost') {
|
|
||||||
for (var value of dataValues) {
|
|
||||||
const item = this.action.parent.parent.id === value.key;
|
|
||||||
value.keyIsID = Boolean(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data) foundry.utils.setProperty(submitData, keyPath, dataValues);
|
|
||||||
}
|
|
||||||
return submitData;
|
|
||||||
}
|
|
||||||
|
|
||||||
static async updateForm(event, _, formData) {
|
|
||||||
const submitData = this._prepareSubmitData(event, formData),
|
|
||||||
data = foundry.utils.mergeObject(this.action.toObject(), submitData);
|
|
||||||
this.action = await this.action.update(data);
|
|
||||||
|
|
||||||
this.sheetUpdate?.(this.action);
|
|
||||||
this.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
static addElement(event) {
|
|
||||||
const data = this.action.toObject(),
|
|
||||||
key = event.target.closest('[data-key]').dataset.key;
|
|
||||||
if (!this.action[key]) return;
|
|
||||||
data[key].push({});
|
|
||||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
|
||||||
}
|
|
||||||
|
|
||||||
static removeElement(event, button) {
|
|
||||||
event.stopPropagation();
|
|
||||||
const data = this.action.toObject(),
|
|
||||||
key = event.target.closest('[data-key]').dataset.key,
|
|
||||||
index = button.dataset.index;
|
|
||||||
data[key].splice(index, 1);
|
|
||||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
|
||||||
}
|
|
||||||
|
|
||||||
static addDamage(event) {
|
|
||||||
if (!this.action.damage.parts) return;
|
|
||||||
const data = this.action.toObject(),
|
|
||||||
part = {};
|
|
||||||
if (this.action.actor?.isNPC) part.value = { multiplier: 'flat' };
|
|
||||||
data.damage.parts.push(part);
|
|
||||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
|
||||||
}
|
|
||||||
|
|
||||||
static removeDamage(event, button) {
|
|
||||||
if (!this.action.damage.parts) return;
|
|
||||||
const data = this.action.toObject(),
|
|
||||||
index = button.dataset.index;
|
|
||||||
data.damage.parts.splice(index, 1);
|
|
||||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
|
||||||
}
|
|
||||||
|
|
||||||
static async addEffect(event) {
|
|
||||||
if (!this.action.effects) return;
|
if (!this.action.effects) return;
|
||||||
const effectData = this._addEffectData.bind(this)(),
|
const effectData = this._addEffectData.bind(this)();
|
||||||
[created] = await this.action.item.createEmbeddedDocuments('ActiveEffect', [effectData], { render: false }),
|
const data = this.action.toObject();
|
||||||
data = this.action.toObject();
|
|
||||||
|
const [created] = await this.action.item.createEmbeddedDocuments('ActiveEffect', [effectData], {
|
||||||
|
render: false
|
||||||
|
});
|
||||||
data.effects.push({ _id: created._id });
|
data.effects.push({ _id: created._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._id).sheet.render(true);
|
||||||
|
|
@ -246,6 +46,10 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getEffectDetails(id) {
|
||||||
|
return this.action.item.effects.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
static removeEffect(event, button) {
|
static removeEffect(event, button) {
|
||||||
if (!this.action.effects) return;
|
if (!this.action.effects) return;
|
||||||
const index = button.dataset.index,
|
const index = button.dataset.index,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
import DHActionBaseConfig from './action-base-config.mjs';
|
||||||
|
|
||||||
|
export default class DHActionSettingsConfig extends DHActionBaseConfig {
|
||||||
|
constructor(action, effects, sheetUpdate) {
|
||||||
|
super(action);
|
||||||
|
|
||||||
|
this.effects = effects;
|
||||||
|
this.sheetUpdate = sheetUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
...DHActionBaseConfig.DEFAULT_OPTIONS,
|
||||||
|
actions: {
|
||||||
|
...DHActionBaseConfig.DEFAULT_OPTIONS.actions,
|
||||||
|
addEffect: this.addEffect,
|
||||||
|
removeEffect: this.removeEffect,
|
||||||
|
editEffect: this.editEffect
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async _prepareContext(options) {
|
||||||
|
const context = await super._prepareContext(options);
|
||||||
|
context.effects = this.effects;
|
||||||
|
context.getEffectDetails = this.getEffectDetails.bind(this);
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
getEffectDetails(id) {
|
||||||
|
return this.effects.find(x => x.id === id);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async addEffect(_event) {
|
||||||
|
if (!this.action.effects) return;
|
||||||
|
const effectData = game.system.api.data.activeEffects.BaseEffect.getDefaultObject();
|
||||||
|
const data = this.action.toObject();
|
||||||
|
|
||||||
|
this.sheetUpdate(data, effectData);
|
||||||
|
this.effects = [...this.effects, effectData];
|
||||||
|
data.effects.push({ _id: effectData.id });
|
||||||
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
|
}
|
||||||
|
|
||||||
|
static removeEffect(event, button) {
|
||||||
|
if (!this.action.effects) return;
|
||||||
|
const index = button.dataset.index,
|
||||||
|
effectId = this.action.effects[index]._id;
|
||||||
|
this.constructor.removeElement.bind(this)(event, button);
|
||||||
|
this.sheetUpdate(
|
||||||
|
this.action.toObject(),
|
||||||
|
this.effects.find(x => x.id === effectId),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async editEffect(event) {
|
||||||
|
const id = event.target.closest('[data-effect-id]')?.dataset?.effectId;
|
||||||
|
const updatedEffect = await game.system.api.applications.sheetConfigs.SettingActiveEffectConfig.configure(
|
||||||
|
this.getEffectDetails(id)
|
||||||
|
);
|
||||||
|
if (!updatedEffect) return;
|
||||||
|
|
||||||
|
this.effects = await this.sheetUpdate(this.action.toObject(), { ...updatedEffect, id });
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,6 +9,9 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
|
||||||
if (!ignoredActorKeys.includes(key)) {
|
if (!ignoredActorKeys.includes(key)) {
|
||||||
const model = game.system.api.models.actors[key];
|
const model = game.system.api.models.actors[key];
|
||||||
const attributes = CONFIG.Token.documentClass.getTrackedAttributes(model);
|
const attributes = CONFIG.Token.documentClass.getTrackedAttributes(model);
|
||||||
|
// As per DHToken._getTrackedAttributesFromSchema, attributes.bar have a max version as well.
|
||||||
|
const maxAttributes = attributes.bar.map(x => [...x, 'max']);
|
||||||
|
attributes.value.push(...maxAttributes);
|
||||||
const group = game.i18n.localize(model.metadata.label);
|
const group = game.i18n.localize(model.metadata.label);
|
||||||
const choices = CONFIG.Token.documentClass
|
const choices = CONFIG.Token.documentClass
|
||||||
.getTrackedAttributeChoices(attributes, model)
|
.getTrackedAttributeChoices(attributes, model)
|
||||||
|
|
@ -96,6 +99,13 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _prepareContext(options) {
|
||||||
|
const context = await super._prepareContext(options);
|
||||||
|
context.systemFields = context.document.system.schema.fields;
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
async _preparePartContext(partId, context) {
|
async _preparePartContext(partId, context) {
|
||||||
const partContext = await super._preparePartContext(partId, context);
|
const partContext = await super._preparePartContext(partId, context);
|
||||||
switch (partId) {
|
switch (partId) {
|
||||||
|
|
@ -105,7 +115,7 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
|
||||||
CONFIG.DH.SETTINGS.gameSettings.appearance
|
CONFIG.DH.SETTINGS.gameSettings.appearance
|
||||||
).showGenericStatusEffects;
|
).showGenericStatusEffects;
|
||||||
if (!useGeneric) {
|
if (!useGeneric) {
|
||||||
partContext.statuses = Object.values(CONFIG.DH.GENERAL.conditions).map(status => ({
|
partContext.statuses = Object.values(CONFIG.DH.GENERAL.conditions()).map(status => ({
|
||||||
value: status.id,
|
value: status.id,
|
||||||
label: game.i18n.localize(status.name)
|
label: game.i18n.localize(status.name)
|
||||||
}));
|
}));
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,19 @@ export default class DHAdversarySettings extends DHBaseActorSettings {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,19 @@ export default class DHEnvironmentSettings extends DHBaseActorSettings {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a new category entry to the actor.
|
* Adds a new category entry to the actor.
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
|
|
@ -109,9 +122,9 @@ export default class DHEnvironmentSettings extends DHBaseActorSettings {
|
||||||
|
|
||||||
async _onDrop(event) {
|
async _onDrop(event) {
|
||||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||||
if (data.fromInternal) return;
|
|
||||||
|
|
||||||
const item = await fromUuid(data.uuid);
|
const item = await fromUuid(data.uuid);
|
||||||
|
if (data.fromInternal && item?.parent?.uuid === this.actor.uuid) return;
|
||||||
|
|
||||||
if (item.type === 'adversary' && event.target.closest('.category-container')) {
|
if (item.type === 'adversary' && event.target.closest('.category-container')) {
|
||||||
const target = event.target.closest('.category-container');
|
const target = event.target.closest('.category-container');
|
||||||
const path = `system.potentialAdversaries.${target.dataset.potentialAdversary}.adversaries`;
|
const path = `system.potentialAdversaries.${target.dataset.potentialAdversary}.adversaries`;
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,30 @@
|
||||||
export default class DhPrototypeTokenConfig extends foundry.applications.sheets.PrototypeTokenConfig {
|
import DHTokenConfigMixin from './token-config-mixin.mjs';
|
||||||
|
import { getActorSizeFromForm } from './token-config-mixin.mjs';
|
||||||
|
|
||||||
|
export default class DhPrototypeTokenConfig extends DHTokenConfigMixin(
|
||||||
|
foundry.applications.sheets.PrototypeTokenConfig
|
||||||
|
) {
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
async _prepareResourcesTab() {
|
static DEFAULT_OPTIONS = {
|
||||||
const token = this.token;
|
...super.DEFAULT_OPTIONS,
|
||||||
const usesTrackableAttributes = !foundry.utils.isEmpty(CONFIG.Actor.trackableAttributes);
|
form: { handler: DhPrototypeTokenConfig.#onSubmit }
|
||||||
const attributeSource =
|
|
||||||
this.actor?.system instanceof foundry.abstract.DataModel && usesTrackableAttributes
|
|
||||||
? this.actor?.type
|
|
||||||
: this.actor?.system;
|
|
||||||
const TokenDocument = foundry.utils.getDocumentClass('Token');
|
|
||||||
const attributes = TokenDocument.getTrackedAttributes(attributeSource);
|
|
||||||
return {
|
|
||||||
barAttributes: TokenDocument.getTrackedAttributeChoices(attributes, attributeSource),
|
|
||||||
bar1: token.getBarAttribute?.('bar1'),
|
|
||||||
bar2: token.getBarAttribute?.('bar2'),
|
|
||||||
turnMarkerModes: DhPrototypeTokenConfig.TURN_MARKER_MODES,
|
|
||||||
turnMarkerAnimations: CONFIG.Combat.settings.turnMarkerAnimations
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process form submission for the sheet
|
||||||
|
* @this {PrototypeTokenConfig}
|
||||||
|
* @type {ApplicationFormSubmission}
|
||||||
|
*/
|
||||||
|
static async #onSubmit(event, form, formData) {
|
||||||
|
const submitData = this._processFormData(event, form, formData);
|
||||||
|
submitData.detectionModes ??= []; // Clear detection modes array
|
||||||
|
this._processChanges(submitData);
|
||||||
|
const changes = { prototypeToken: submitData };
|
||||||
|
|
||||||
|
const changedTokenSizeValue = getActorSizeFromForm(this.element, this.actor);
|
||||||
|
if (changedTokenSizeValue) changes.system = { size: changedTokenSizeValue };
|
||||||
|
|
||||||
|
this.actor.validate({ changes, clean: true, fallback: false });
|
||||||
|
await this.actor.update(changes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,231 @@
|
||||||
|
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);
|
||||||
|
const ignoredActorKeys = ['config', 'DhEnvironment'];
|
||||||
|
this.changeChoices = Object.keys(game.system.api.models.actors).reduce((acc, key) => {
|
||||||
|
if (!ignoredActorKeys.includes(key)) {
|
||||||
|
const model = game.system.api.models.actors[key];
|
||||||
|
const attributes = CONFIG.Token.documentClass.getTrackedAttributes(model);
|
||||||
|
const group = game.i18n.localize(model.metadata.label);
|
||||||
|
const choices = CONFIG.Token.documentClass
|
||||||
|
.getTrackedAttributeChoices(attributes, model)
|
||||||
|
.map(x => ({ ...x, group: group }));
|
||||||
|
acc.push(...choices);
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
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}`;
|
||||||
|
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 });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,21 +1,26 @@
|
||||||
import { actionsTypes } from '../../data/action/_module.mjs';
|
import { actionsTypes } from '../../data/action/_module.mjs';
|
||||||
import DHActionConfig from './action-config.mjs';
|
import ActionSettingsConfig from './action-settings-config.mjs';
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
||||||
export default class DowntimeConfig extends HandlebarsApplicationMixin(ApplicationV2) {
|
export default class SettingFeatureConfig extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
constructor(move, movePath, settings, options) {
|
constructor(configTitle, move, movePath, settings, optionalParts, options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
|
this.configTitle = configTitle;
|
||||||
this.move = move;
|
this.move = move;
|
||||||
|
|
||||||
this.movePath = movePath;
|
this.movePath = movePath;
|
||||||
this.actionsPath = `${movePath}.actions`;
|
this.actionsPath = `${movePath}.actions`;
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
|
|
||||||
|
const { hasIcon, hasEffects } = optionalParts;
|
||||||
|
this.hasIcon = hasIcon;
|
||||||
|
this.hasEffects = hasEffects;
|
||||||
}
|
}
|
||||||
|
|
||||||
get title() {
|
get title() {
|
||||||
return game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.downtimeMoves');
|
return this.configTitle;
|
||||||
}
|
}
|
||||||
|
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
|
|
@ -30,6 +35,7 @@ export default class DowntimeConfig extends HandlebarsApplicationMixin(Applicati
|
||||||
addItem: this.addItem,
|
addItem: this.addItem,
|
||||||
editItem: this.editItem,
|
editItem: this.editItem,
|
||||||
removeItem: this.removeItem,
|
removeItem: this.removeItem,
|
||||||
|
addEffect: this.addEffect,
|
||||||
resetMoves: this.resetMoves,
|
resetMoves: this.resetMoves,
|
||||||
saveForm: this.saveForm
|
saveForm: this.saveForm
|
||||||
},
|
},
|
||||||
|
|
@ -41,13 +47,14 @@ export default class DowntimeConfig extends HandlebarsApplicationMixin(Applicati
|
||||||
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
|
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
|
||||||
main: { template: 'systems/daggerheart/templates/settings/downtime-config/main.hbs' },
|
main: { template: 'systems/daggerheart/templates/settings/downtime-config/main.hbs' },
|
||||||
actions: { template: 'systems/daggerheart/templates/settings/downtime-config/actions.hbs' },
|
actions: { template: 'systems/daggerheart/templates/settings/downtime-config/actions.hbs' },
|
||||||
|
effects: { template: 'systems/daggerheart/templates/settings/downtime-config/effects.hbs' },
|
||||||
footer: { template: 'systems/daggerheart/templates/settings/downtime-config/footer.hbs' }
|
footer: { template: 'systems/daggerheart/templates/settings/downtime-config/footer.hbs' }
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
static TABS = {
|
static TABS = {
|
||||||
primary: {
|
primary: {
|
||||||
tabs: [{ id: 'main' }, { id: 'actions' }],
|
tabs: [{ id: 'main' }, { id: 'actions' }, { id: 'effects' }],
|
||||||
initial: 'main',
|
initial: 'main',
|
||||||
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
||||||
}
|
}
|
||||||
|
|
@ -55,6 +62,9 @@ export default class DowntimeConfig extends HandlebarsApplicationMixin(Applicati
|
||||||
|
|
||||||
async _prepareContext(_options) {
|
async _prepareContext(_options) {
|
||||||
const context = await super._prepareContext(_options);
|
const context = await super._prepareContext(_options);
|
||||||
|
context.tabs = this._filterTabs(context.tabs);
|
||||||
|
context.hasIcon = this.hasIcon;
|
||||||
|
context.hasEffects = this.hasEffects;
|
||||||
context.move = this.move;
|
context.move = this.move;
|
||||||
context.move.enrichedDescription = await foundry.applications.ux.TextEditor.enrichHTML(
|
context.move.enrichedDescription = await foundry.applications.ux.TextEditor.enrichHTML(
|
||||||
context.move.description
|
context.move.description
|
||||||
|
|
@ -92,6 +102,8 @@ export default class DowntimeConfig extends HandlebarsApplicationMixin(Applicati
|
||||||
return (
|
return (
|
||||||
(await foundry.applications.api.DialogV2.input({
|
(await foundry.applications.api.DialogV2.input({
|
||||||
window: { title: game.i18n.localize('DAGGERHEART.CONFIG.SelectAction.selectType') },
|
window: { title: game.i18n.localize('DAGGERHEART.CONFIG.SelectAction.selectType') },
|
||||||
|
position: { width: 300 },
|
||||||
|
classes: ['daggerheart', 'dh-style'],
|
||||||
content: await foundry.applications.handlebars.renderTemplate(
|
content: await foundry.applications.handlebars.renderTemplate(
|
||||||
'systems/daggerheart/templates/actionTypes/actionType.hbs',
|
'systems/daggerheart/templates/actionTypes/actionType.hbs',
|
||||||
{ types: CONFIG.DH.ACTIONS.actionTypes }
|
{ types: CONFIG.DH.ACTIONS.actionTypes }
|
||||||
|
|
@ -130,31 +142,109 @@ export default class DowntimeConfig extends HandlebarsApplicationMixin(Applicati
|
||||||
}
|
}
|
||||||
|
|
||||||
static async editItem(_, target) {
|
static async editItem(_, target) {
|
||||||
const actionId = target.dataset.id;
|
const { type, id } = target.dataset;
|
||||||
const action = this.move.actions.get(actionId);
|
if (type === 'effect') {
|
||||||
await new DHActionConfig(action, async updatedMove => {
|
const effectIndex = this.move.effects.findIndex(x => x.id === id);
|
||||||
await this.settings.updateSource({ [`${this.actionsPath}.${actionId}`]: updatedMove });
|
const effect = this.move.effects[effectIndex];
|
||||||
|
const updatedEffect =
|
||||||
|
await game.system.api.applications.sheetConfigs.SettingActiveEffectConfig.configure(effect);
|
||||||
|
if (!updatedEffect) return;
|
||||||
|
|
||||||
|
await this.settings.updateSource({
|
||||||
|
[`${this.movePath}.effects`]: this.move.effects.reduce((acc, effect, index) => {
|
||||||
|
acc.push(index === effectIndex ? { ...updatedEffect, id: effect.id } : effect);
|
||||||
|
return acc;
|
||||||
|
}, [])
|
||||||
|
});
|
||||||
this.move = foundry.utils.getProperty(this.settings, this.movePath);
|
this.move = foundry.utils.getProperty(this.settings, this.movePath);
|
||||||
this.render();
|
this.render();
|
||||||
|
} else {
|
||||||
|
const action = this.move.actions.get(id);
|
||||||
|
await new ActionSettingsConfig(action, this.move.effects, async (updatedMove, effectData, deleteEffect) => {
|
||||||
|
let updatedEffects = null;
|
||||||
|
if (effectData) {
|
||||||
|
const currentEffects = foundry.utils.getProperty(this.settings, `${this.movePath}.effects`);
|
||||||
|
const existingEffectIndex = currentEffects.findIndex(x => x.id === effectData.id);
|
||||||
|
|
||||||
|
updatedEffects = deleteEffect
|
||||||
|
? currentEffects.filter(x => x.id !== effectData.id)
|
||||||
|
: existingEffectIndex === -1
|
||||||
|
? [...currentEffects, effectData]
|
||||||
|
: currentEffects.with(existingEffectIndex, effectData);
|
||||||
|
await this.settings.updateSource({
|
||||||
|
[`${this.movePath}.effects`]: updatedEffects
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.settings.updateSource({ [`${this.actionsPath}.${id}`]: updatedMove });
|
||||||
|
this.move = foundry.utils.getProperty(this.settings, this.movePath);
|
||||||
|
this.render();
|
||||||
|
return updatedEffects;
|
||||||
}).render(true);
|
}).render(true);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static async removeItem(_, target) {
|
static async removeItem(_, target) {
|
||||||
|
const { type, id } = target.dataset;
|
||||||
|
if (type === 'effect') {
|
||||||
|
const move = foundry.utils.getProperty(this.settings, this.movePath);
|
||||||
|
for (const action of move.actions) {
|
||||||
|
const remainingEffects = action.effects.filter(x => x._id !== id);
|
||||||
|
if (action.effects.length !== remainingEffects.length) {
|
||||||
|
await action.update({
|
||||||
|
effects: remainingEffects.map(x => {
|
||||||
|
const { _id, ...rest } = x;
|
||||||
|
return { ...rest, _id: _id };
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await this.settings.updateSource({
|
||||||
|
[this.movePath]: {
|
||||||
|
effects: move.effects.filter(x => x.id !== id),
|
||||||
|
actions: move.actions
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
await this.settings.updateSource({ [`${this.actionsPath}.-=${target.dataset.id}`]: null });
|
await this.settings.updateSource({ [`${this.actionsPath}.-=${target.dataset.id}`]: null });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.move = foundry.utils.getProperty(this.settings, this.movePath);
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
static async addEffect(_, target) {
|
||||||
|
const currentEffects = foundry.utils.getProperty(this.settings, `${this.movePath}.effects`);
|
||||||
|
await this.settings.updateSource({
|
||||||
|
[`${this.movePath}.effects`]: [
|
||||||
|
...currentEffects,
|
||||||
|
game.system.api.data.activeEffects.BaseEffect.getDefaultObject()
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
this.move = foundry.utils.getProperty(this.settings, this.movePath);
|
this.move = foundry.utils.getProperty(this.settings, this.movePath);
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
static resetMoves() {}
|
static resetMoves() {}
|
||||||
|
|
||||||
|
_filterTabs(tabs) {
|
||||||
|
return this.hasEffects
|
||||||
|
? tabs
|
||||||
|
: Object.keys(tabs).reduce((acc, key) => {
|
||||||
|
if (key !== 'effects') acc[key] = tabs[key];
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
_onClose(options = {}) {
|
_onClose(options = {}) {
|
||||||
if (!options.submitted) this.move = null;
|
if (!options.submitted) this.move = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async configure(move, movePath, settings, options = {}) {
|
static async configure(configTitle, move, movePath, settings, optionalParts, options = {}) {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
const app = new this(move, movePath, settings, options);
|
const app = new this(configTitle, move, movePath, settings, optionalParts, options);
|
||||||
app.addEventListener('close', () => resolve(app.move), { once: true });
|
app.addEventListener('close', () => resolve(app.move), { once: true });
|
||||||
app.render({ force: true });
|
app.render({ force: true });
|
||||||
});
|
});
|
||||||
114
module/applications/sheets-configs/token-config-mixin.mjs
Normal file
114
module/applications/sheets-configs/token-config-mixin.mjs
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
export default function DHTokenConfigMixin(Base) {
|
||||||
|
class DHTokenConfigBase extends Base {
|
||||||
|
/** @override */
|
||||||
|
static PARTS = {
|
||||||
|
tabs: super.PARTS.tabs,
|
||||||
|
identity: super.PARTS.identity,
|
||||||
|
appearance: {
|
||||||
|
template: 'systems/daggerheart/templates/sheets-settings/token-config/appearance.hbs',
|
||||||
|
scrollable: ['']
|
||||||
|
},
|
||||||
|
vision: super.PARTS.vision,
|
||||||
|
light: super.PARTS.light,
|
||||||
|
resources: super.PARTS.resources,
|
||||||
|
footer: super.PARTS.footer
|
||||||
|
};
|
||||||
|
|
||||||
|
_attachPartListeners(partId, htmlElement, options) {
|
||||||
|
super._attachPartListeners(partId, htmlElement, options);
|
||||||
|
|
||||||
|
switch (partId) {
|
||||||
|
case 'appearance':
|
||||||
|
htmlElement
|
||||||
|
.querySelector('#dhTokenSize')
|
||||||
|
?.addEventListener('change', this.onTokenSizeChange.bind(this));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
async _prepareResourcesTab() {
|
||||||
|
const token = this.token;
|
||||||
|
const usesTrackableAttributes = !foundry.utils.isEmpty(CONFIG.Actor.trackableAttributes);
|
||||||
|
const attributeSource =
|
||||||
|
this.actor?.system instanceof foundry.abstract.DataModel && usesTrackableAttributes
|
||||||
|
? this.actor?.type
|
||||||
|
: this.actor?.system;
|
||||||
|
const TokenDocument = foundry.utils.getDocumentClass('Token');
|
||||||
|
const attributes = TokenDocument.getTrackedAttributes(attributeSource);
|
||||||
|
return {
|
||||||
|
barAttributes: TokenDocument.getTrackedAttributeChoices(attributes, attributeSource),
|
||||||
|
bar1: token.getBarAttribute?.('bar1'),
|
||||||
|
bar2: token.getBarAttribute?.('bar2'),
|
||||||
|
turnMarkerModes: DHTokenConfigBase.TURN_MARKER_MODES,
|
||||||
|
turnMarkerAnimations: CONFIG.Combat.settings.turnMarkerAnimations
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async _prepareAppearanceTab() {
|
||||||
|
const context = await super._prepareAppearanceTab();
|
||||||
|
context.tokenSizes = CONFIG.DH.ACTOR.tokenSize;
|
||||||
|
context.tokenSize = this.actor?.system?.size;
|
||||||
|
context.usesActorSize = this.actor?.system?.metadata?.usesSize;
|
||||||
|
context.actorSizeDisable = context.usesActorSize && this.actor.system.size !== 'custom';
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
_previewChanges(changes) {
|
||||||
|
if (!changes || !this._preview) return;
|
||||||
|
|
||||||
|
const tokenSizeSelect = this.element?.querySelector('#dhTokenSize');
|
||||||
|
if (this.actor && tokenSizeSelect && tokenSizeSelect.value !== 'custom') {
|
||||||
|
const tokenSizes = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes;
|
||||||
|
const tokenSize = tokenSizes[tokenSizeSelect.value];
|
||||||
|
changes.width = tokenSize;
|
||||||
|
changes.height = tokenSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
const deletions = { '-=actorId': null, '-=actorLink': null };
|
||||||
|
const mergeOptions = { inplace: false, performDeletions: true };
|
||||||
|
this._preview.updateSource(mergeObject(changes, deletions, mergeOptions));
|
||||||
|
|
||||||
|
if (this._preview?.object?.destroyed === false) {
|
||||||
|
this._preview.object.initializeSources();
|
||||||
|
this._preview.object.renderFlags.set({ refresh: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async onTokenSizeChange(event) {
|
||||||
|
const value = event.target.value;
|
||||||
|
const tokenSizeDimensions = this.element.querySelector('#tokenSizeDimensions');
|
||||||
|
if (tokenSizeDimensions) {
|
||||||
|
const disabled = value !== 'custom';
|
||||||
|
|
||||||
|
tokenSizeDimensions.dataset.tooltip = disabled
|
||||||
|
? game.i18n.localize('DAGGERHEART.APPLICATIONS.TokenConfig.actorSizeUsed')
|
||||||
|
: '';
|
||||||
|
|
||||||
|
const disabledIcon = tokenSizeDimensions.querySelector('i');
|
||||||
|
if (disabledIcon) {
|
||||||
|
disabledIcon.style.opacity = disabled ? '' : '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
const dimensionsInputs = tokenSizeDimensions.querySelectorAll('.form-fields input');
|
||||||
|
for (const input of dimensionsInputs) {
|
||||||
|
input.disabled = disabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return DHTokenConfigBase;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getActorSizeFromForm(element, actor) {
|
||||||
|
const tokenSizeSelect = element.querySelector('#dhTokenSize');
|
||||||
|
const isSizeDifferent = tokenSizeSelect?.value !== actor?.system?.size;
|
||||||
|
if (tokenSizeSelect && actor && isSizeDifferent) {
|
||||||
|
return tokenSizeSelect.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
@ -1,20 +1,11 @@
|
||||||
export default class DhTokenConfig extends foundry.applications.sheets.TokenConfig {
|
import DHTokenConfigMixin from './token-config-mixin.mjs';
|
||||||
/** @inheritDoc */
|
import { getActorSizeFromForm } from './token-config-mixin.mjs';
|
||||||
async _prepareResourcesTab() {
|
|
||||||
const token = this.token;
|
export default class DhTokenConfig extends DHTokenConfigMixin(foundry.applications.sheets.TokenConfig) {
|
||||||
const usesTrackableAttributes = !foundry.utils.isEmpty(CONFIG.Actor.trackableAttributes);
|
async _processSubmitData(event, form, submitData, options) {
|
||||||
const attributeSource =
|
const changedTokenSizeValue = getActorSizeFromForm(this.element, this.actor);
|
||||||
this.actor?.system instanceof foundry.abstract.DataModel && usesTrackableAttributes
|
if (changedTokenSizeValue) this.token.actor.update({ 'system.size': changedTokenSizeValue });
|
||||||
? this.actor?.type
|
|
||||||
: this.actor?.system;
|
super._processSubmitData(event, form, submitData, options);
|
||||||
const TokenDocument = foundry.utils.getDocumentClass('Token');
|
|
||||||
const attributes = TokenDocument.getTrackedAttributes(attributeSource);
|
|
||||||
return {
|
|
||||||
barAttributes: TokenDocument.getTrackedAttributeChoices(attributes, attributeSource),
|
|
||||||
bar1: token.getBarAttribute?.('bar1'),
|
|
||||||
bar2: token.getBarAttribute?.('bar2'),
|
|
||||||
turnMarkerModes: DhTokenConfig.TURN_MARKER_MODES,
|
|
||||||
turnMarkerAnimations: CONFIG.Combat.settings.turnMarkerAnimations
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,3 +2,4 @@ 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 Party } from './party.mjs';
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,60 @@
|
||||||
|
import { getDocFromElement } from '../../../helpers/utils.mjs';
|
||||||
import DHBaseActorSheet from '../api/base-actor.mjs';
|
import DHBaseActorSheet from '../api/base-actor.mjs';
|
||||||
|
|
||||||
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
|
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
|
||||||
|
|
||||||
export default class AdversarySheet extends DHBaseActorSheet {
|
export default class AdversarySheet extends DHBaseActorSheet {
|
||||||
|
/** @inheritDoc */
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
classes: ['adversary'],
|
classes: ['adversary'],
|
||||||
position: { width: 660, height: 766 },
|
position: { width: 660, height: 766 },
|
||||||
window: { resizable: true },
|
window: { resizable: true },
|
||||||
actions: {
|
actions: {
|
||||||
reactionRoll: AdversarySheet.#reactionRoll
|
toggleHitPoints: AdversarySheet.#toggleHitPoints,
|
||||||
|
toggleStress: AdversarySheet.#toggleStress,
|
||||||
|
reactionRoll: AdversarySheet.#reactionRoll,
|
||||||
|
toggleResourceDice: AdversarySheet.#toggleResourceDice,
|
||||||
|
handleResourceDice: AdversarySheet.#handleResourceDice
|
||||||
},
|
},
|
||||||
window: {
|
window: {
|
||||||
resizable: true
|
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 = {
|
static PARTS = {
|
||||||
sidebar: { template: 'systems/daggerheart/templates/sheets/actors/adversary/sidebar.hbs' },
|
limited: {
|
||||||
|
template: 'systems/daggerheart/templates/sheets/actors/adversary/limited.hbs',
|
||||||
|
scrollable: ['.limited-container']
|
||||||
|
},
|
||||||
|
sidebar: {
|
||||||
|
template: 'systems/daggerheart/templates/sheets/actors/adversary/sidebar.hbs',
|
||||||
|
scrollable: ['.shortcut-items-section']
|
||||||
|
},
|
||||||
header: { template: 'systems/daggerheart/templates/sheets/actors/adversary/header.hbs' },
|
header: { template: 'systems/daggerheart/templates/sheets/actors/adversary/header.hbs' },
|
||||||
features: { template: 'systems/daggerheart/templates/sheets/actors/adversary/features.hbs' },
|
features: {
|
||||||
notes: { template: 'systems/daggerheart/templates/sheets/actors/adversary/notes.hbs' },
|
template: 'systems/daggerheart/templates/sheets/actors/adversary/features.hbs',
|
||||||
effects: { template: 'systems/daggerheart/templates/sheets/actors/adversary/effects.hbs' }
|
scrollable: ['.feature-section']
|
||||||
|
},
|
||||||
|
notes: {
|
||||||
|
template: 'systems/daggerheart/templates/sheets/actors/adversary/notes.hbs'
|
||||||
|
},
|
||||||
|
effects: {
|
||||||
|
template: 'systems/daggerheart/templates/sheets/actors/adversary/effects.hbs',
|
||||||
|
scrollable: ['.effects-sections']
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
|
|
@ -32,10 +66,40 @@ export default class AdversarySheet extends DHBaseActorSheet {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
_initializeApplicationOptions(options) {
|
||||||
|
const applicationOptions = super._initializeApplicationOptions(options);
|
||||||
|
|
||||||
|
if (applicationOptions.document.testUserPermission(game.user, 'LIMITED', { exact: true })) {
|
||||||
|
applicationOptions.position.width = 360;
|
||||||
|
applicationOptions.position.height = 'auto';
|
||||||
|
}
|
||||||
|
|
||||||
|
return applicationOptions;
|
||||||
|
}
|
||||||
|
|
||||||
/**@inheritdoc */
|
/**@inheritdoc */
|
||||||
async _prepareContext(options) {
|
async _prepareContext(options) {
|
||||||
const context = await super._prepareContext(options);
|
const context = await super._prepareContext(options);
|
||||||
context.systemFields.attack.fields = this.document.system.attack.schema.fields;
|
context.systemFields.attack.fields = this.document.system.attack.schema.fields;
|
||||||
|
|
||||||
|
context.resources = Object.keys(this.document.system.resources).reduce((acc, key) => {
|
||||||
|
acc[key] = this.document.system.resources[key];
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
const maxResource = Math.max(context.resources.hitPoints.max, context.resources.stress.max);
|
||||||
|
context.resources.hitPoints.emptyPips =
|
||||||
|
context.resources.hitPoints.max < maxResource ? maxResource - context.resources.hitPoints.max : 0;
|
||||||
|
context.resources.stress.emptyPips =
|
||||||
|
context.resources.stress.max < maxResource ? maxResource - context.resources.stress.max : 0;
|
||||||
|
|
||||||
|
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
|
||||||
|
);
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -44,7 +108,11 @@ export default class AdversarySheet extends DHBaseActorSheet {
|
||||||
context = await super._preparePartContext(partId, context, options);
|
context = await super._preparePartContext(partId, context, options);
|
||||||
switch (partId) {
|
switch (partId) {
|
||||||
case 'header':
|
case 'header':
|
||||||
|
case 'limited':
|
||||||
await this._prepareHeaderContext(context, options);
|
await this._prepareHeaderContext(context, options);
|
||||||
|
|
||||||
|
const adversaryTypes = CONFIG.DH.ACTOR.allAdversaryTypes();
|
||||||
|
context.adversaryType = game.i18n.localize(adversaryTypes[this.document.system.type].label);
|
||||||
break;
|
break;
|
||||||
case 'notes':
|
case 'notes':
|
||||||
await this._prepareNotesContext(context, options);
|
await this._prepareNotesContext(context, options);
|
||||||
|
|
@ -53,6 +121,16 @@ export default class AdversarySheet extends DHBaseActorSheet {
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**@inheritdoc */
|
||||||
|
_attachPartListeners(partId, htmlElement, options) {
|
||||||
|
super._attachPartListeners(partId, htmlElement, options);
|
||||||
|
|
||||||
|
htmlElement.querySelectorAll('.inventory-item-resource').forEach(element => {
|
||||||
|
element.addEventListener('change', this.updateItemResource.bind(this));
|
||||||
|
element.addEventListener('click', e => e.stopPropagation());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare render context for the Biography part.
|
* Prepare render context for the Biography part.
|
||||||
* @param {ApplicationRenderContext} context
|
* @param {ApplicationRenderContext} context
|
||||||
|
|
@ -98,10 +176,41 @@ export default class AdversarySheet extends DHBaseActorSheet {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
async _onDragStart(event) {
|
||||||
|
const inventoryItem = event.currentTarget.closest('.inventory-item');
|
||||||
|
if (inventoryItem) {
|
||||||
|
event.dataTransfer.setDragImage(inventoryItem.querySelector('img'), 60, 0);
|
||||||
|
}
|
||||||
|
super._onDragStart(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
/* Application Clicks Actions */
|
/* Application Clicks Actions */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles hitpoint resource value.
|
||||||
|
* @type {ApplicationClickAction}
|
||||||
|
*/
|
||||||
|
static async #toggleHitPoints(_, button) {
|
||||||
|
const hitPointsValue = Number.parseInt(button.dataset.value);
|
||||||
|
const newValue =
|
||||||
|
this.document.system.resources.hitPoints.value >= hitPointsValue ? hitPointsValue - 1 : hitPointsValue;
|
||||||
|
await this.document.update({ 'system.resources.hitPoints.value': newValue });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles stress resource value.
|
||||||
|
* @type {ApplicationClickAction}
|
||||||
|
*/
|
||||||
|
static async #toggleStress(_, button) {
|
||||||
|
const StressValue = Number.parseInt(button.dataset.value);
|
||||||
|
const newValue = this.document.system.resources.stress.value >= StressValue ? StressValue - 1 : StressValue;
|
||||||
|
await this.document.update({ 'system.resources.stress.value': newValue });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs a reaction roll for an Adversary.
|
* Performs a reaction roll for an Adversary.
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
|
|
@ -112,13 +221,61 @@ export default class AdversarySheet extends DHBaseActorSheet {
|
||||||
title: `Reaction Roll: ${this.actor.name}`,
|
title: `Reaction Roll: ${this.actor.name}`,
|
||||||
headerTitle: 'Adversary Reaction Roll',
|
headerTitle: 'Adversary Reaction Roll',
|
||||||
roll: {
|
roll: {
|
||||||
type: 'reaction'
|
type: 'trait'
|
||||||
},
|
},
|
||||||
type: 'trait',
|
actionType: 'reaction',
|
||||||
hasRoll: true,
|
hasRoll: true,
|
||||||
data: this.actor.getRollData()
|
data: this.actor.getRollData()
|
||||||
};
|
};
|
||||||
|
|
||||||
this.actor.diceRoll(config);
|
this.actor.diceRoll(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle the used state of a resource dice.
|
||||||
|
* @type {ApplicationClickAction}
|
||||||
|
*/
|
||||||
|
static async #toggleResourceDice(event, target) {
|
||||||
|
const item = await getDocFromElement(target);
|
||||||
|
|
||||||
|
const { dice } = event.target.closest('.item-resource').dataset;
|
||||||
|
const diceState = item.system.resource.diceStates[dice];
|
||||||
|
|
||||||
|
await item.update({
|
||||||
|
[`system.resource.diceStates.${dice}.used`]: diceState ? !diceState.used : true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the roll values of resource dice.
|
||||||
|
* @type {ApplicationClickAction}
|
||||||
|
*/
|
||||||
|
static async #handleResourceDice(_, target) {
|
||||||
|
const item = await getDocFromElement(target);
|
||||||
|
if (!item) return;
|
||||||
|
|
||||||
|
const rollValues = await game.system.api.applications.dialogs.ResourceDiceDialog.create(item, this.document);
|
||||||
|
if (!rollValues) return;
|
||||||
|
|
||||||
|
await item.update({
|
||||||
|
'system.resource.diceStates': rollValues.reduce((acc, state, index) => {
|
||||||
|
acc[index] = { value: state.value, used: state.used };
|
||||||
|
return acc;
|
||||||
|
}, {})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Application Listener Actions */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
async updateItemResource(event) {
|
||||||
|
const item = await getDocFromElement(event.currentTarget);
|
||||||
|
if (!item) return;
|
||||||
|
|
||||||
|
const max = event.currentTarget.max ? Number(event.currentTarget.max) : null;
|
||||||
|
const value = max ? Math.min(Number(event.currentTarget.value), max) : event.currentTarget.value;
|
||||||
|
await item.update({ 'system.resource.value': value });
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,40 +1,53 @@
|
||||||
import DHBaseActorSheet from '../api/base-actor.mjs';
|
import DHBaseActorSheet from '../api/base-actor.mjs';
|
||||||
import DhpDeathMove from '../../dialogs/deathMove.mjs';
|
import DhpDeathMove from '../../dialogs/deathMove.mjs';
|
||||||
import { abilities } from '../../../config/actorConfig.mjs';
|
import { abilities } from '../../../config/actorConfig.mjs';
|
||||||
import DhCharacterlevelUp from '../../levelup/characterLevelup.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 { getDocFromElement, getDocFromElementSync } from '../../../helpers/utils.mjs';
|
||||||
import { ItemBrowser } from '../../ui/itemBrowser.mjs';
|
|
||||||
|
|
||||||
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
|
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
|
||||||
|
|
||||||
const { TextEditor } = foundry.applications.ux;
|
|
||||||
export default class CharacterSheet extends DHBaseActorSheet {
|
export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
/**@inheritdoc */
|
/**@inheritdoc */
|
||||||
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,
|
||||||
|
toggleHitPoints: CharacterSheet.#toggleHitPoints,
|
||||||
|
toggleStress: CharacterSheet.#toggleStress,
|
||||||
|
toggleArmor: CharacterSheet.#toggleArmor,
|
||||||
toggleHope: CharacterSheet.#toggleHope,
|
toggleHope: CharacterSheet.#toggleHope,
|
||||||
toggleLoadoutView: CharacterSheet.#toggleLoadoutView,
|
toggleLoadoutView: CharacterSheet.#toggleLoadoutView,
|
||||||
openPack: CharacterSheet.#openPack,
|
openPack: CharacterSheet.#openPack,
|
||||||
makeDeathMove: CharacterSheet.#makeDeathMove,
|
makeDeathMove: CharacterSheet.#makeDeathMove,
|
||||||
levelManagement: CharacterSheet.#levelManagement,
|
levelManagement: CharacterSheet.#levelManagement,
|
||||||
|
viewLevelups: CharacterSheet.#viewLevelups,
|
||||||
toggleEquipItem: CharacterSheet.#toggleEquipItem,
|
toggleEquipItem: CharacterSheet.#toggleEquipItem,
|
||||||
toggleResourceDice: CharacterSheet.#toggleResourceDice,
|
toggleResourceDice: CharacterSheet.#toggleResourceDice,
|
||||||
handleResourceDice: CharacterSheet.#handleResourceDice,
|
handleResourceDice: CharacterSheet.#handleResourceDice,
|
||||||
|
advanceResourceDie: CharacterSheet.#advanceResourceDie,
|
||||||
|
cancelBeastform: CharacterSheet.#cancelBeastform,
|
||||||
useDowntime: this.useDowntime,
|
useDowntime: this.useDowntime,
|
||||||
tempBrowser: CharacterSheet.#tempBrowser
|
viewParty: CharacterSheet.#viewParty,
|
||||||
},
|
},
|
||||||
window: {
|
window: {
|
||||||
resizable: true
|
resizable: true,
|
||||||
|
controls: [
|
||||||
|
{
|
||||||
|
icon: 'fa-solid fa-angles-up',
|
||||||
|
label: 'DAGGERHEART.ACTORS.Character.viewLevelups',
|
||||||
|
action: 'viewLevelups'
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
dragDrop: [
|
dragDrop: [
|
||||||
{
|
{
|
||||||
dragSelector: '[data-item-id][draggable="true"]',
|
dragSelector: '[data-item-id][draggable="true"], [data-item-id] [draggable="true"]',
|
||||||
dropSelector: null
|
dropSelector: null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -68,8 +81,14 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
|
|
||||||
/**@override */
|
/**@override */
|
||||||
static PARTS = {
|
static PARTS = {
|
||||||
|
limited: {
|
||||||
|
id: 'limited',
|
||||||
|
scrollable: ['.limited-container'],
|
||||||
|
template: 'systems/daggerheart/templates/sheets/actors/character/limited.hbs'
|
||||||
|
},
|
||||||
sidebar: {
|
sidebar: {
|
||||||
id: 'sidebar',
|
id: 'sidebar',
|
||||||
|
scrollable: ['.shortcut-items-section'],
|
||||||
template: 'systems/daggerheart/templates/sheets/actors/character/sidebar.hbs'
|
template: 'systems/daggerheart/templates/sheets/actors/character/sidebar.hbs'
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
|
|
@ -78,22 +97,27 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
},
|
},
|
||||||
features: {
|
features: {
|
||||||
id: 'features',
|
id: 'features',
|
||||||
|
scrollable: ['.features-sections'],
|
||||||
template: 'systems/daggerheart/templates/sheets/actors/character/features.hbs'
|
template: 'systems/daggerheart/templates/sheets/actors/character/features.hbs'
|
||||||
},
|
},
|
||||||
loadout: {
|
loadout: {
|
||||||
id: 'loadout',
|
id: 'loadout',
|
||||||
|
scrollable: ['.items-section'],
|
||||||
template: 'systems/daggerheart/templates/sheets/actors/character/loadout.hbs'
|
template: 'systems/daggerheart/templates/sheets/actors/character/loadout.hbs'
|
||||||
},
|
},
|
||||||
inventory: {
|
inventory: {
|
||||||
id: 'inventory',
|
id: 'inventory',
|
||||||
|
scrollable: ['.items-section'],
|
||||||
template: 'systems/daggerheart/templates/sheets/actors/character/inventory.hbs'
|
template: 'systems/daggerheart/templates/sheets/actors/character/inventory.hbs'
|
||||||
},
|
},
|
||||||
biography: {
|
biography: {
|
||||||
id: 'biography',
|
id: 'biography',
|
||||||
|
scrollable: ['.items-section'],
|
||||||
template: 'systems/daggerheart/templates/sheets/actors/character/biography.hbs'
|
template: 'systems/daggerheart/templates/sheets/actors/character/biography.hbs'
|
||||||
},
|
},
|
||||||
effects: {
|
effects: {
|
||||||
id: 'effects',
|
id: 'effects',
|
||||||
|
scrollable: ['.effects-sections'],
|
||||||
template: 'systems/daggerheart/templates/sheets/actors/character/effects.hbs'
|
template: 'systems/daggerheart/templates/sheets/actors/character/effects.hbs'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -114,28 +138,51 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
|
|
||||||
htmlElement.querySelectorAll('.inventory-item-resource').forEach(element => {
|
htmlElement.querySelectorAll('.inventory-item-resource').forEach(element => {
|
||||||
element.addEventListener('change', this.updateItemResource.bind(this));
|
element.addEventListener('change', this.updateItemResource.bind(this));
|
||||||
});
|
element.addEventListener('click', e => e.stopPropagation());
|
||||||
htmlElement.querySelectorAll('.inventory-item-quantity').forEach(element => {
|
|
||||||
element.addEventListener('change', this.updateItemQuantity.bind(this));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add listener for armor marks input
|
// Add listener for armor marks input
|
||||||
htmlElement.querySelectorAll('.armor-marks-input').forEach(element => {
|
htmlElement.querySelectorAll('.armor-marks-input').forEach(element => {
|
||||||
element.addEventListener('change', this.updateArmorMarks.bind(this));
|
element.addEventListener('change', this.updateArmorMarks.bind(this));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
htmlElement.querySelectorAll('.item-resource.die').forEach(element => {
|
||||||
|
element.addEventListener('contextmenu', this.lowerResourceDie.bind(this));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
_initializeApplicationOptions(options) {
|
||||||
|
const applicationOptions = super._initializeApplicationOptions(options);
|
||||||
|
|
||||||
|
if (applicationOptions.document.testUserPermission(game.user, 'LIMITED', { exact: true })) {
|
||||||
|
applicationOptions.position.width = 360;
|
||||||
|
applicationOptions.position.height = 'auto';
|
||||||
|
}
|
||||||
|
|
||||||
|
return applicationOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
async _onRender(context, options) {
|
async _onRender(context, options) {
|
||||||
await super._onRender(context, options);
|
await super._onRender(context, options);
|
||||||
|
|
||||||
|
if (!this.document.testUserPermission(game.user, 'LIMITED', { exact: true })) {
|
||||||
this.element
|
this.element
|
||||||
.querySelector('.level-value')
|
.querySelector('.level-value')
|
||||||
?.addEventListener('change', event => this.document.updateLevel(Number(event.currentTarget.value)));
|
?.addEventListener('change', event => this.document.updateLevel(Number(event.currentTarget.value)));
|
||||||
|
|
||||||
|
const observer = this.document.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OBSERVER, {
|
||||||
|
exact: true
|
||||||
|
});
|
||||||
|
if (observer) {
|
||||||
|
this.element.querySelector('.window-content').classList.add('viewMode');
|
||||||
|
}
|
||||||
|
|
||||||
this._createFilterMenus();
|
this._createFilterMenus();
|
||||||
this._createSearchFilter();
|
this._createSearchFilter();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
/* Prepare Context */
|
/* Prepare Context */
|
||||||
|
|
@ -154,24 +201,17 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
context.inventory = {
|
context.resources = Object.keys(this.document.system.resources).reduce((acc, key) => {
|
||||||
currency: {
|
acc[key] = this.document.system.resources[key];
|
||||||
title: game.i18n.localize('DAGGERHEART.CONFIG.Gold.title'),
|
return acc;
|
||||||
coins: game.i18n.localize('DAGGERHEART.CONFIG.Gold.coins'),
|
}, {});
|
||||||
handfuls: game.i18n.localize('DAGGERHEART.CONFIG.Gold.handfuls'),
|
const maxResource = Math.max(context.resources.hitPoints.max, context.resources.stress.max);
|
||||||
bags: game.i18n.localize('DAGGERHEART.CONFIG.Gold.bags'),
|
context.resources.hitPoints.emptyPips =
|
||||||
chests: game.i18n.localize('DAGGERHEART.CONFIG.Gold.chests')
|
context.resources.hitPoints.max < maxResource ? maxResource - context.resources.hitPoints.max : 0;
|
||||||
}
|
context.resources.stress.emptyPips =
|
||||||
};
|
context.resources.stress.max < maxResource ? maxResource - context.resources.stress.max : 0;
|
||||||
|
|
||||||
const homebrewCurrency = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).currency;
|
context.beastformActive = this.document.effects.find(x => x.type === 'beastform');
|
||||||
if (homebrewCurrency.enabled) {
|
|
||||||
context.inventory.currency = homebrewCurrency;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context.inventory.length === 0) {
|
|
||||||
context.inventory = Array(1).fill(Array(5).fill([]));
|
|
||||||
}
|
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
@ -209,7 +249,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
* @protected
|
* @protected
|
||||||
*/
|
*/
|
||||||
async _prepareLoadoutContext(context, _options) {
|
async _prepareLoadoutContext(context, _options) {
|
||||||
context.cardView = !game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.displayDomainCardsAsList);
|
context.cardView = game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.displayDomainCardsAsCard);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -279,6 +319,40 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.loadoutMaxReached'));
|
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.loadoutMaxReached'));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'recall',
|
||||||
|
icon: 'fa-solid fa-bolt-lightning',
|
||||||
|
condition: target => {
|
||||||
|
const doc = getDocFromElementSync(target);
|
||||||
|
return doc && doc.system.inVault;
|
||||||
|
},
|
||||||
|
callback: async (target, event) => {
|
||||||
|
const doc = await getDocFromElement(target);
|
||||||
|
const actorLoadout = doc.actor.system.loadoutSlot;
|
||||||
|
if (!actorLoadout.available) {
|
||||||
|
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.loadoutMaxReached'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (doc.system.recallCost == 0) {
|
||||||
|
return doc.update({ 'system.inVault': false });
|
||||||
|
}
|
||||||
|
const type = 'effect';
|
||||||
|
const cls = game.system.api.models.actions.actionsTypes[type];
|
||||||
|
const action = new cls({
|
||||||
|
...cls.getSourceConfig(doc.system),
|
||||||
|
type: type,
|
||||||
|
chatDisplay: false,
|
||||||
|
cost: [{
|
||||||
|
key: 'stress',
|
||||||
|
value: doc.system.recallCost
|
||||||
|
}]
|
||||||
|
}, { parent: doc.system });
|
||||||
|
const config = await action.use(event);
|
||||||
|
if (config) {
|
||||||
|
return doc.update({ 'system.inVault': false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'toVault',
|
name: 'toVault',
|
||||||
icon: 'fa-solid fa-arrow-down',
|
icon: 'fa-solid fa-arrow-down',
|
||||||
|
|
@ -550,14 +624,6 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateItemQuantity(event) {
|
|
||||||
const item = await getDocFromElement(event.currentTarget);
|
|
||||||
if (!item) return;
|
|
||||||
|
|
||||||
await item.update({ 'system.quantity': event.currentTarget.value });
|
|
||||||
this.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
async updateArmorMarks(event) {
|
async updateArmorMarks(event) {
|
||||||
const armor = this.document.system.armor;
|
const armor = this.document.system.armor;
|
||||||
if (!armor) return;
|
if (!armor) return;
|
||||||
|
|
@ -585,7 +651,14 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
if (!value || !subclass)
|
if (!value || !subclass)
|
||||||
return ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.missingClassOrSubclass'));
|
return ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.missingClassOrSubclass'));
|
||||||
|
|
||||||
new DhCharacterlevelUp(this.document).render({ force: true });
|
new CharacterLevelup(this.document).render({ force: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the charater level management window in viewMode.
|
||||||
|
*/
|
||||||
|
static #viewLevelups() {
|
||||||
|
new LevelupViewMode(this.document).render({ force: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -604,14 +677,22 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
const { key } = button.dataset;
|
const { key } = button.dataset;
|
||||||
|
|
||||||
const presets = {
|
const presets = {
|
||||||
compendium: 'daggerheart',
|
|
||||||
folder: key,
|
folder: key,
|
||||||
|
filter:
|
||||||
|
key === 'subclasses'
|
||||||
|
? {
|
||||||
|
'system.linkedClass.uuid': {
|
||||||
|
key: 'system.linkedClass.uuid',
|
||||||
|
value: this.document.system.class.value._stats.compendiumSource
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
render: {
|
render: {
|
||||||
noFolder: true
|
noFolder: true
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return new ItemBrowser({ presets }).render({ force: true });
|
ui.compendiumBrowser.open(presets);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -629,41 +710,21 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
roll: {
|
roll: {
|
||||||
trait: button.dataset.attribute
|
trait: button.dataset.attribute
|
||||||
},
|
},
|
||||||
hasRoll: true
|
hasRoll: true,
|
||||||
};
|
actionType: 'action',
|
||||||
const result = await this.document.diceRoll({
|
|
||||||
...config,
|
|
||||||
headerTitle: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${this.actor.name}`,
|
headerTitle: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${this.actor.name}`,
|
||||||
title: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
title: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
||||||
ability: abilityLabel
|
ability: abilityLabel
|
||||||
})
|
})
|
||||||
});
|
|
||||||
|
|
||||||
this.consumeResource(result?.costs);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove when Action Refactor part #2 done
|
|
||||||
async consumeResource(costs) {
|
|
||||||
if (!costs?.length) return;
|
|
||||||
const usefulResources = {
|
|
||||||
...foundry.utils.deepClone(this.actor.system.resources),
|
|
||||||
fear: {
|
|
||||||
value: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear),
|
|
||||||
max: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxFear,
|
|
||||||
reversed: false
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
const resources = game.system.api.fields.ActionFields.CostField.getRealCosts(costs).map(c => {
|
const result = await this.document.diceRoll(config);
|
||||||
const resource = usefulResources[c.key];
|
|
||||||
return {
|
|
||||||
key: c.key,
|
|
||||||
value: (c.total ?? c.value) * (resource.isReversed ? 1 : -1),
|
|
||||||
target: resource.target,
|
|
||||||
keyIsID: resource.keyIsID
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.actor.modifyResource(resources);
|
/* This could be avoided by baking config.costs into config.resourceUpdates. Didn't feel like messing with it at the time */
|
||||||
|
const costResources = result.costs
|
||||||
|
.filter(x => x.enabled)
|
||||||
|
.map(cost => ({ ...cost, value: -cost.value, total: -cost.total }));
|
||||||
|
config.resourceUpdates.addResources(costResources);
|
||||||
|
await config.resourceUpdates.updateResources();
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: redo toggleEquipItem method
|
//TODO: redo toggleEquipItem method
|
||||||
|
|
@ -708,11 +769,42 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
*/
|
*/
|
||||||
static async #toggleLoadoutView(_, button) {
|
static async #toggleLoadoutView(_, button) {
|
||||||
const newAbilityView = button.dataset.value !== 'true';
|
const newAbilityView = button.dataset.value === 'true';
|
||||||
await game.user.setFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.displayDomainCardsAsList, newAbilityView);
|
await game.user.setFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.displayDomainCardsAsCard, newAbilityView);
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles hitpoint resource value.
|
||||||
|
* @type {ApplicationClickAction}
|
||||||
|
*/
|
||||||
|
static async #toggleHitPoints(_, button) {
|
||||||
|
const hitPointsValue = Number.parseInt(button.dataset.value);
|
||||||
|
const newValue =
|
||||||
|
this.document.system.resources.hitPoints.value >= hitPointsValue ? hitPointsValue - 1 : hitPointsValue;
|
||||||
|
await this.document.update({ 'system.resources.hitPoints.value': newValue });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles stress resource value.
|
||||||
|
* @type {ApplicationClickAction}
|
||||||
|
*/
|
||||||
|
static async #toggleStress(_, button) {
|
||||||
|
const StressValue = Number.parseInt(button.dataset.value);
|
||||||
|
const newValue = this.document.system.resources.stress.value >= StressValue ? StressValue - 1 : StressValue;
|
||||||
|
await this.document.update({ 'system.resources.stress.value': newValue });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles ArmorScore resource value.
|
||||||
|
* @type {ApplicationClickAction}
|
||||||
|
*/
|
||||||
|
static async #toggleArmor(_, button, element) {
|
||||||
|
const ArmorValue = Number.parseInt(button.dataset.value);
|
||||||
|
const newValue = this.document.system.armor.system.marks.value >= ArmorValue ? ArmorValue - 1 : ArmorValue;
|
||||||
|
await this.document.system.armor.update({ 'system.marks.value': newValue });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggles a hope resource value.
|
* Toggles a hope resource value.
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
|
|
@ -752,13 +844,6 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Temp
|
|
||||||
*/
|
|
||||||
static async #tempBrowser(_, target) {
|
|
||||||
new ItemBrowser().render({ force: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle the roll values of resource dice.
|
* Handle the roll values of resource dice.
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
|
|
@ -778,6 +863,71 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** */
|
||||||
|
static #advanceResourceDie(_, target) {
|
||||||
|
this.updateResourceDie(target, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
lowerResourceDie(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
this.updateResourceDie(event.target, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateResourceDie(target, advance) {
|
||||||
|
const item = await getDocFromElement(target);
|
||||||
|
if (!item) return;
|
||||||
|
|
||||||
|
const advancedValue = item.system.resource.value + (advance ? 1 : -1);
|
||||||
|
await item.update({
|
||||||
|
'system.resource.value': Math.min(advancedValue, Number(item.system.resource.dieFaces.split('d')[1]))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static async #cancelBeastform(_, target) {
|
||||||
|
const item = await getDocFromElement(target);
|
||||||
|
if (!item) return;
|
||||||
|
game.system.api.fields.ActionFields.BeastformField.handleActiveTransformations.call(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #viewParty(_, target) {
|
||||||
|
const parties = this.document.parties;
|
||||||
|
if (parties.size <= 1) {
|
||||||
|
parties.first()?.sheet.render({ force: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const buttons = parties.map((p) => {
|
||||||
|
const button = document.createElement("button");
|
||||||
|
button.type = "button";
|
||||||
|
button.classList.add("plain");
|
||||||
|
const img = document.createElement("img");
|
||||||
|
img.src = p.img;
|
||||||
|
button.append(img);
|
||||||
|
const name = document.createElement("span");
|
||||||
|
name.textContent = p.name;
|
||||||
|
button.append(name);
|
||||||
|
button.addEventListener("click", () => {
|
||||||
|
p.sheet?.render({ force: true });
|
||||||
|
game.tooltip.dismissLockedTooltips();
|
||||||
|
});
|
||||||
|
return button;
|
||||||
|
});
|
||||||
|
|
||||||
|
const html = document.createElement("div");
|
||||||
|
html.classList.add("party-list");
|
||||||
|
html.append(...buttons);
|
||||||
|
|
||||||
|
game.tooltip.dismissLockedTooltips();
|
||||||
|
game.tooltip.activate(target, {
|
||||||
|
html,
|
||||||
|
locked: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open the downtime application.
|
* Open the downtime application.
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
|
|
@ -788,40 +938,48 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
async _onDragStart(event) {
|
async _onDragStart(event) {
|
||||||
const item = await getDocFromElement(event.target);
|
const inventoryItem = event.currentTarget.closest('.inventory-item');
|
||||||
|
if (inventoryItem) {
|
||||||
const dragData = {
|
event.dataTransfer.setDragImage(inventoryItem.querySelector('img'), 60, 0);
|
||||||
type: item.documentName,
|
}
|
||||||
uuid: item.uuid
|
|
||||||
};
|
|
||||||
|
|
||||||
event.dataTransfer.setData('text/plain', JSON.stringify(dragData));
|
|
||||||
|
|
||||||
super._onDragStart(event);
|
super._onDragStart(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onDrop(event) {
|
async _onDropItem(event, item) {
|
||||||
// Prevent event bubbling to avoid duplicate handling
|
if (this.document.uuid === item.parent?.uuid) {
|
||||||
event.preventDefault();
|
return super._onDropItem(event, item);
|
||||||
event.stopPropagation();
|
}
|
||||||
|
|
||||||
super._onDrop(event);
|
if (item.type === 'beastform') {
|
||||||
this._onDropItem(event, TextEditor.getDragEventData(event));
|
if (this.document.effects.find(x => x.type === 'beastform')) {
|
||||||
|
return ui.notifications.warn(
|
||||||
|
game.i18n.localize('DAGGERHEART.UI.Notifications.beastformAlreadyApplied')
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onDropItem(event, data) {
|
|
||||||
const item = await Item.implementation.fromDropData(data);
|
|
||||||
const itemData = item.toObject();
|
const itemData = item.toObject();
|
||||||
|
const data = await game.system.api.data.items.DHBeastform.getWildcardImage(this.document, itemData);
|
||||||
if (item.type === 'domainCard' && !this.document.system.loadoutSlot.available) {
|
if (!data?.selectedImage) {
|
||||||
itemData.system.inVault = true;
|
return;
|
||||||
|
} else if (data) {
|
||||||
|
if (data.usesDynamicToken) itemData.system.tokenRingImg = data.selectedImage;
|
||||||
|
else itemData.system.tokenImg = data.selectedImage;
|
||||||
|
return await this._onDropItemCreate(itemData);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.document.uuid === item.parent?.uuid) return this._onSortItem(event, itemData);
|
// If this is a type that gets deleted, delete it first (but still defer to super)
|
||||||
const createdItem = await this._onDropItemCreate(itemData);
|
const typesThatReplace = ['ancestry', 'community'];
|
||||||
|
if (typesThatReplace.includes(item.type)) {
|
||||||
|
await this.document.deleteEmbeddedDocuments(
|
||||||
|
'Item',
|
||||||
|
this.document.items.filter(x => x.type === item.type).map(x => x.id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return createdItem;
|
return super._onDropItem(event, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onDropItemCreate(itemData, event) {
|
async _onDropItemCreate(itemData, event) {
|
||||||
|
|
|
||||||
|
|
@ -8,14 +8,23 @@ export default class DhCompanionSheet extends DHBaseActorSheet {
|
||||||
classes: ['actor', 'companion'],
|
classes: ['actor', 'companion'],
|
||||||
position: { width: 340 },
|
position: { width: 340 },
|
||||||
actions: {
|
actions: {
|
||||||
|
toggleStress: DhCompanionSheet.#toggleStress,
|
||||||
|
actionRoll: DhCompanionSheet.#actionRoll,
|
||||||
levelManagement: DhCompanionSheet.#levelManagement
|
levelManagement: DhCompanionSheet.#levelManagement
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static PARTS = {
|
static PARTS = {
|
||||||
|
limited: {
|
||||||
|
template: 'systems/daggerheart/templates/sheets/actors/companion/limited.hbs',
|
||||||
|
scrollable: ['.limited-container']
|
||||||
|
},
|
||||||
header: { template: 'systems/daggerheart/templates/sheets/actors/companion/header.hbs' },
|
header: { template: 'systems/daggerheart/templates/sheets/actors/companion/header.hbs' },
|
||||||
details: { template: 'systems/daggerheart/templates/sheets/actors/companion/details.hbs' },
|
details: { template: 'systems/daggerheart/templates/sheets/actors/companion/details.hbs' },
|
||||||
effects: { template: 'systems/daggerheart/templates/sheets/actors/companion/effects.hbs' }
|
effects: {
|
||||||
|
template: 'systems/daggerheart/templates/sheets/actors/companion/effects.hbs',
|
||||||
|
scrollable: ['.effects-sections']
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
@ -42,6 +51,61 @@ export default class DhCompanionSheet extends DHBaseActorSheet {
|
||||||
/* Application Clicks Actions */
|
/* Application Clicks Actions */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles stress resource value.
|
||||||
|
* @type {ApplicationClickAction}
|
||||||
|
*/
|
||||||
|
static async #toggleStress(_, button) {
|
||||||
|
const StressValue = Number.parseInt(button.dataset.value);
|
||||||
|
const newValue = this.document.system.resources.stress.value >= StressValue ? StressValue - 1 : StressValue;
|
||||||
|
await this.document.update({ 'system.resources.stress.value': newValue });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static async #actionRoll(event) {
|
||||||
|
const partner = this.actor.system.partner;
|
||||||
|
const config = {
|
||||||
|
event,
|
||||||
|
title: `${game.i18n.localize('DAGGERHEART.GENERAL.Roll.action')}: ${this.actor.name}`,
|
||||||
|
headerTitle: `Companion ${game.i18n.localize('DAGGERHEART.GENERAL.Roll.action')}`,
|
||||||
|
roll: {
|
||||||
|
trait: partner.system.spellcastModifierTrait?.key
|
||||||
|
},
|
||||||
|
hasRoll: true,
|
||||||
|
data: partner.getRollData()
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await partner.diceRoll(config);
|
||||||
|
this.consumeResource(result?.costs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove when Action Refactor part #2 done
|
||||||
|
async consumeResource(costs) {
|
||||||
|
if (!costs?.length) return;
|
||||||
|
|
||||||
|
const partner = this.actor.system.partner;
|
||||||
|
const usefulResources = {
|
||||||
|
...foundry.utils.deepClone(partner.system.resources),
|
||||||
|
fear: {
|
||||||
|
value: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear),
|
||||||
|
max: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxFear,
|
||||||
|
reversed: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const resources = game.system.api.fields.ActionFields.CostField.getRealCosts(costs).map(c => {
|
||||||
|
const resource = usefulResources[c.key];
|
||||||
|
return {
|
||||||
|
key: c.key,
|
||||||
|
value: (c.total ?? c.value) * (resource.isReversed ? 1 : -1),
|
||||||
|
target: resource.target
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
await partner.modifyResource(resources);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens the companions level management window.
|
* Opens the companions level management window.
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { getDocFromElement } from '../../../helpers/utils.mjs';
|
||||||
import DHBaseActorSheet from '../api/base-actor.mjs';
|
import DHBaseActorSheet from '../api/base-actor.mjs';
|
||||||
|
|
||||||
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
|
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
|
||||||
|
|
@ -8,21 +9,44 @@ export default class DhpEnvironment extends DHBaseActorSheet {
|
||||||
classes: ['environment'],
|
classes: ['environment'],
|
||||||
position: {
|
position: {
|
||||||
width: 500,
|
width: 500,
|
||||||
height: 725
|
height: 740
|
||||||
},
|
},
|
||||||
window: {
|
window: {
|
||||||
resizable: true
|
resizable: true,
|
||||||
|
controls: [
|
||||||
|
{
|
||||||
|
icon: 'fa-solid fa-signature',
|
||||||
|
label: 'DAGGERHEART.UI.Tooltip.configureAttribution',
|
||||||
|
action: 'editAttribution'
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
actions: {},
|
actions: {
|
||||||
dragDrop: [{ dragSelector: '.action-section .inventory-item', dropSelector: null }]
|
toggleResourceDice: DhpEnvironment.#toggleResourceDice,
|
||||||
|
handleResourceDice: DhpEnvironment.#handleResourceDice
|
||||||
|
},
|
||||||
|
dragDrop: [
|
||||||
|
{
|
||||||
|
dragSelector: '[data-item-id][draggable="true"], [data-item-id] [draggable="true"]',
|
||||||
|
dropSelector: null
|
||||||
|
}
|
||||||
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
/**@override */
|
/**@override */
|
||||||
static PARTS = {
|
static PARTS = {
|
||||||
|
limited: {
|
||||||
|
template: 'systems/daggerheart/templates/sheets/actors/environment/limited.hbs',
|
||||||
|
scrollable: ['.limited-container']
|
||||||
|
},
|
||||||
header: { template: 'systems/daggerheart/templates/sheets/actors/environment/header.hbs' },
|
header: { template: 'systems/daggerheart/templates/sheets/actors/environment/header.hbs' },
|
||||||
features: { template: 'systems/daggerheart/templates/sheets/actors/environment/features.hbs' },
|
features: {
|
||||||
|
template: 'systems/daggerheart/templates/sheets/actors/environment/features.hbs',
|
||||||
|
scrollable: ['.feature-section']
|
||||||
|
},
|
||||||
potentialAdversaries: {
|
potentialAdversaries: {
|
||||||
template: 'systems/daggerheart/templates/sheets/actors/environment/potentialAdversaries.hbs'
|
template: 'systems/daggerheart/templates/sheets/actors/environment/potentialAdversaries.hbs',
|
||||||
|
scrollable: ['.items-section']
|
||||||
},
|
},
|
||||||
notes: { template: 'systems/daggerheart/templates/sheets/actors/environment/notes.hbs' }
|
notes: { template: 'systems/daggerheart/templates/sheets/actors/environment/notes.hbs' }
|
||||||
};
|
};
|
||||||
|
|
@ -36,12 +60,28 @@ export default class DhpEnvironment extends DHBaseActorSheet {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
_initializeApplicationOptions(options) {
|
||||||
|
const applicationOptions = super._initializeApplicationOptions(options);
|
||||||
|
|
||||||
|
if (applicationOptions.document.testUserPermission(game.user, 'LIMITED', { exact: true })) {
|
||||||
|
applicationOptions.position.width = 360;
|
||||||
|
applicationOptions.position.height = 'auto';
|
||||||
|
}
|
||||||
|
|
||||||
|
return applicationOptions;
|
||||||
|
}
|
||||||
|
|
||||||
/**@inheritdoc */
|
/**@inheritdoc */
|
||||||
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;
|
||||||
|
case 'features':
|
||||||
|
await this._prepareFeaturesContext(context, options);
|
||||||
break;
|
break;
|
||||||
case 'notes':
|
case 'notes':
|
||||||
await this._prepareNotesContext(context, options);
|
await this._prepareNotesContext(context, options);
|
||||||
|
|
@ -78,6 +118,22 @@ export default class DhpEnvironment extends DHBaseActorSheet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 Header part.
|
* Prepare render context for the Header part.
|
||||||
* @param {ApplicationRenderContext} context
|
* @param {ApplicationRenderContext} context
|
||||||
|
|
@ -98,12 +154,51 @@ export default class DhpEnvironment extends DHBaseActorSheet {
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
async _onDragStart(event) {
|
async _onDragStart(event) {
|
||||||
const item = event.currentTarget.closest('.inventory-item');
|
const item = event.currentTarget.closest('.inventory-item[data-type=adversary]');
|
||||||
|
|
||||||
if (item) {
|
if (item) {
|
||||||
const adversaryData = { type: 'Actor', uuid: item.dataset.itemUuid };
|
const adversaryData = { type: 'Actor', uuid: item.dataset.itemUuid };
|
||||||
event.dataTransfer.setData('text/plain', JSON.stringify(adversaryData));
|
event.dataTransfer.setData('text/plain', JSON.stringify(adversaryData));
|
||||||
event.dataTransfer.setDragImage(item, 60, 0);
|
event.dataTransfer.setDragImage(item, 60, 0);
|
||||||
|
} else {
|
||||||
|
return super._onDragStart(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Application Clicks Actions */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle the used state of a resource dice.
|
||||||
|
* @type {ApplicationClickAction}
|
||||||
|
*/
|
||||||
|
static async #toggleResourceDice(event, target) {
|
||||||
|
const item = await getDocFromElement(target);
|
||||||
|
|
||||||
|
const { dice } = event.target.closest('.item-resource').dataset;
|
||||||
|
const diceState = item.system.resource.diceStates[dice];
|
||||||
|
|
||||||
|
await item.update({
|
||||||
|
[`system.resource.diceStates.${dice}.used`]: diceState ? !diceState.used : true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the roll values of resource dice.
|
||||||
|
* @type {ApplicationClickAction}
|
||||||
|
*/
|
||||||
|
static async #handleResourceDice(_, target) {
|
||||||
|
const item = await getDocFromElement(target);
|
||||||
|
if (!item) return;
|
||||||
|
|
||||||
|
const rollValues = await game.system.api.applications.dialogs.ResourceDiceDialog.create(item, this.document);
|
||||||
|
if (!rollValues) return;
|
||||||
|
|
||||||
|
await item.update({
|
||||||
|
'system.resource.diceStates': rollValues.reduce((acc, state, index) => {
|
||||||
|
acc[index] = { value: state.value, used: state.used };
|
||||||
|
return acc;
|
||||||
|
}, {})
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
479
module/applications/sheets/actors/party.mjs
Normal file
479
module/applications/sheets/actors/party.mjs
Normal file
|
|
@ -0,0 +1,479 @@
|
||||||
|
import DHBaseActorSheet from '../api/base-actor.mjs';
|
||||||
|
import { getDocFromElement } from '../../../helpers/utils.mjs';
|
||||||
|
import { ItemBrowser } from '../../ui/itemBrowser.mjs';
|
||||||
|
import FilterMenu from '../../ux/filter-menu.mjs';
|
||||||
|
import DaggerheartMenu from '../../sidebar/tabs/daggerheartMenu.mjs';
|
||||||
|
import { socketEvent } from '../../../systemRegistration/socket.mjs';
|
||||||
|
import GroupRollDialog from '../../dialogs/group-roll-dialog.mjs';
|
||||||
|
import DhpActor from '../../../documents/actor.mjs';
|
||||||
|
import DHItem from '../../../documents/item.mjs';
|
||||||
|
|
||||||
|
export default class Party extends DHBaseActorSheet {
|
||||||
|
constructor(options) {
|
||||||
|
super(options);
|
||||||
|
|
||||||
|
this.refreshSelections = DaggerheartMenu.defaultRefreshSelections();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**@inheritdoc */
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
classes: ['party'],
|
||||||
|
position: {
|
||||||
|
width: 550,
|
||||||
|
height: 900
|
||||||
|
},
|
||||||
|
window: {
|
||||||
|
resizable: true
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
deletePartyMember: Party.#deletePartyMember,
|
||||||
|
deleteItem: Party.#deleteItem,
|
||||||
|
toggleHope: Party.#toggleHope,
|
||||||
|
toggleHitPoints: Party.#toggleHitPoints,
|
||||||
|
toggleStress: Party.#toggleStress,
|
||||||
|
toggleArmorSlot: Party.#toggleArmorSlot,
|
||||||
|
tempBrowser: Party.#tempBrowser,
|
||||||
|
refeshActions: Party.#refeshActions,
|
||||||
|
triggerRest: Party.#triggerRest,
|
||||||
|
tagTeamRoll: Party.#tagTeamRoll,
|
||||||
|
groupRoll: Party.#groupRoll,
|
||||||
|
selectRefreshable: DaggerheartMenu.selectRefreshable,
|
||||||
|
refreshActors: DaggerheartMenu.refreshActors
|
||||||
|
},
|
||||||
|
dragDrop: [{ dragSelector: '[data-item-id]', dropSelector: null }]
|
||||||
|
};
|
||||||
|
|
||||||
|
/**@override */
|
||||||
|
static PARTS = {
|
||||||
|
header: { template: 'systems/daggerheart/templates/sheets/actors/party/header.hbs' },
|
||||||
|
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
|
||||||
|
partyMembers: { template: 'systems/daggerheart/templates/sheets/actors/party/party-members.hbs' },
|
||||||
|
resources: {
|
||||||
|
template: 'systems/daggerheart/templates/sheets/actors/party/resources.hbs',
|
||||||
|
scrollable: ['']
|
||||||
|
},
|
||||||
|
/* NOT YET IMPLEMENTED */
|
||||||
|
// projects: {
|
||||||
|
// template: 'systems/daggerheart/templates/sheets/actors/party/projects.hbs',
|
||||||
|
// scrollable: ['']
|
||||||
|
// },
|
||||||
|
inventory: {
|
||||||
|
template: 'systems/daggerheart/templates/sheets/actors/party/inventory.hbs',
|
||||||
|
scrollable: ['.tab.inventory .items-section']
|
||||||
|
},
|
||||||
|
notes: { template: 'systems/daggerheart/templates/sheets/actors/party/notes.hbs' }
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
static TABS = {
|
||||||
|
primary: {
|
||||||
|
tabs: [
|
||||||
|
{ id: 'partyMembers' },
|
||||||
|
{ id: 'resources' },
|
||||||
|
/* NOT YET IMPLEMENTED */
|
||||||
|
// { id: 'projects' },
|
||||||
|
{ id: 'inventory' },
|
||||||
|
{ id: 'notes' }
|
||||||
|
],
|
||||||
|
initial: 'partyMembers',
|
||||||
|
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static ALLOWED_ACTOR_TYPES = ['character', 'companion', 'adversary'];
|
||||||
|
static DICE_ROLL_ACTOR_TYPES = ['character'];
|
||||||
|
|
||||||
|
async _onRender(context, options) {
|
||||||
|
await super._onRender(context, options);
|
||||||
|
this._createFilterMenus();
|
||||||
|
this._createSearchFilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Prepare Context */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
async _preparePartContext(partId, context, options) {
|
||||||
|
context = await super._preparePartContext(partId, context, options);
|
||||||
|
switch (partId) {
|
||||||
|
case 'header':
|
||||||
|
await this._prepareHeaderContext(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 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
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles a hope resource value.
|
||||||
|
* @type {ApplicationClickAction}
|
||||||
|
*/
|
||||||
|
static async #toggleHope(_, target) {
|
||||||
|
const hopeValue = Number.parseInt(target.dataset.value);
|
||||||
|
const actor = await foundry.utils.fromUuid(target.dataset.actorId);
|
||||||
|
const newValue = actor.system.resources.hope.value >= hopeValue ? hopeValue - 1 : hopeValue;
|
||||||
|
await actor.update({ 'system.resources.hope.value': newValue });
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles a hp resource value.
|
||||||
|
* @type {ApplicationClickAction}
|
||||||
|
*/
|
||||||
|
static async #toggleHitPoints(_, target) {
|
||||||
|
const hitPointsValue = Number.parseInt(target.dataset.value);
|
||||||
|
const actor = await foundry.utils.fromUuid(target.dataset.actorId);
|
||||||
|
const newValue = actor.system.resources.hitPoints.value >= hitPointsValue ? hitPointsValue - 1 : hitPointsValue;
|
||||||
|
await actor.update({ 'system.resources.hitPoints.value': newValue });
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles a stress resource value.
|
||||||
|
* @type {ApplicationClickAction}
|
||||||
|
*/
|
||||||
|
static async #toggleStress(_, target) {
|
||||||
|
const stressValue = Number.parseInt(target.dataset.value);
|
||||||
|
const actor = await foundry.utils.fromUuid(target.dataset.actorId);
|
||||||
|
const newValue = actor.system.resources.stress.value >= stressValue ? stressValue - 1 : stressValue;
|
||||||
|
await actor.update({ 'system.resources.stress.value': newValue });
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles a armor slot resource value.
|
||||||
|
* @type {ApplicationClickAction}
|
||||||
|
*/
|
||||||
|
static async #toggleArmorSlot(_, target, element) {
|
||||||
|
const armorItem = await foundry.utils.fromUuid(target.dataset.itemUuid);
|
||||||
|
const armorValue = Number.parseInt(target.dataset.value);
|
||||||
|
const newValue = armorItem.system.marks.value >= armorValue ? armorValue - 1 : armorValue;
|
||||||
|
await armorItem.update({ 'system.marks.value': newValue });
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens Compedium Browser
|
||||||
|
*/
|
||||||
|
static async #tempBrowser(_, target) {
|
||||||
|
new ItemBrowser().render({ force: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #refeshActions() {
|
||||||
|
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||||
|
window: {
|
||||||
|
title: 'New Section',
|
||||||
|
icon: 'fa-solid fa-campground'
|
||||||
|
},
|
||||||
|
content: await foundry.applications.handlebars.renderTemplate(
|
||||||
|
'systems/daggerheart/templates/sidebar/daggerheart-menu/main.hbs',
|
||||||
|
{
|
||||||
|
refreshables: DaggerheartMenu.defaultRefreshSelections()
|
||||||
|
}
|
||||||
|
),
|
||||||
|
classes: ['daggerheart', 'dialog', 'dh-style', 'tab', 'sidebar-tab', 'daggerheartMenu-sidebar']
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!confirmed) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #triggerRest(_, button) {
|
||||||
|
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||||
|
window: {
|
||||||
|
title: game.i18n.localize(`DAGGERHEART.APPLICATIONS.Downtime.${button.dataset.type}.title`),
|
||||||
|
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?',
|
||||||
|
classes: ['daggerheart', 'dialog', 'dh-style']
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!confirmed) return;
|
||||||
|
|
||||||
|
this.document.system.partyMembers.forEach(actor => {
|
||||||
|
game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||||
|
action: socketEvent.DowntimeTrigger,
|
||||||
|
data: {
|
||||||
|
actorId: actor.uuid,
|
||||||
|
downtimeType: button.dataset.type
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static async downtimeMoveQuery({ actorId, downtimeType }) {
|
||||||
|
const actor = await foundry.utils.fromUuid(actorId);
|
||||||
|
if (!actor || !actor?.isOwner) reject();
|
||||||
|
new game.system.api.applications.dialogs.Downtime(actor, downtimeType === 'shortRest').render({
|
||||||
|
force: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #tagTeamRoll() {
|
||||||
|
new game.system.api.applications.dialogs.TagTeamDialog(
|
||||||
|
this.document.system.partyMembers.filter(x => Party.DICE_ROLL_ACTOR_TYPES.includes(x.type))
|
||||||
|
).render({
|
||||||
|
force: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #groupRoll(_params) {
|
||||||
|
new GroupRollDialog(
|
||||||
|
this.document.system.partyMembers.filter(x => Party.DICE_ROLL_ACTOR_TYPES.includes(x.type))
|
||||||
|
).render({ force: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the set of ContextMenu options for Consumable and Loot.
|
||||||
|
* @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - The Array of context options passed to the ContextMenu instance
|
||||||
|
* @this {CharacterSheet}
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
static #getItemContextOptions() {
|
||||||
|
return this._getContextMenuCommonOptions.call(this, { usable: true, toChat: true });
|
||||||
|
}
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Filter Tracking */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The currently active search filter.
|
||||||
|
* @type {foundry.applications.ux.SearchFilter}
|
||||||
|
*/
|
||||||
|
#search = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The currently active search filter.
|
||||||
|
* @type {FilterMenu}
|
||||||
|
*/
|
||||||
|
#menu = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tracks which item IDs are currently displayed, organized by filter type and section.
|
||||||
|
* @type {{
|
||||||
|
* inventory: {
|
||||||
|
* search: Set<string>,
|
||||||
|
* menu: Set<string>
|
||||||
|
* },
|
||||||
|
* loadout: {
|
||||||
|
* search: Set<string>,
|
||||||
|
* menu: Set<string>
|
||||||
|
* },
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
#filteredItems = {
|
||||||
|
inventory: {
|
||||||
|
search: new Set(),
|
||||||
|
menu: new Set()
|
||||||
|
},
|
||||||
|
loadout: {
|
||||||
|
search: new Set(),
|
||||||
|
menu: new Set()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Search Inputs */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create and initialize search filter instances for the inventory and loadout sections.
|
||||||
|
*
|
||||||
|
* Sets up two {@link foundry.applications.ux.SearchFilter} instances:
|
||||||
|
* - One for the inventory, which filters items in the inventory grid.
|
||||||
|
* - One for the loadout, which filters items in the loadout/card grid.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_createSearchFilter() {
|
||||||
|
//Filters could be a application option if needed
|
||||||
|
const filters = [
|
||||||
|
{
|
||||||
|
key: 'inventory',
|
||||||
|
input: 'input[type="search"].search-inventory',
|
||||||
|
content: '[data-application-part="inventory"] .items-section',
|
||||||
|
callback: this._onSearchFilterInventory.bind(this)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const { key, input, content, callback } of filters) {
|
||||||
|
const filter = new foundry.applications.ux.SearchFilter({
|
||||||
|
inputSelector: input,
|
||||||
|
contentSelector: content,
|
||||||
|
callback
|
||||||
|
});
|
||||||
|
filter.bind(this.element);
|
||||||
|
this.#search[key] = filter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle invetory items search and filtering.
|
||||||
|
* @param {KeyboardEvent} event The keyboard input event.
|
||||||
|
* @param {string} query The input search string.
|
||||||
|
* @param {RegExp} rgx The regular expression query that should be matched against.
|
||||||
|
* @param {HTMLElement} html The container to filter items from.
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
async _onSearchFilterInventory(_event, query, rgx, html) {
|
||||||
|
this.#filteredItems.inventory.search.clear();
|
||||||
|
|
||||||
|
for (const li of html.querySelectorAll('.inventory-item')) {
|
||||||
|
const item = await getDocFromElement(li);
|
||||||
|
const matchesSearch = !query || foundry.applications.ux.SearchFilter.testQuery(rgx, item.name);
|
||||||
|
if (matchesSearch) this.#filteredItems.inventory.search.add(item.id);
|
||||||
|
const { menu } = this.#filteredItems.inventory;
|
||||||
|
li.hidden = !(menu.has(item.id) && matchesSearch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Filter Menus */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
_createFilterMenus() {
|
||||||
|
//Menus could be a application option if needed
|
||||||
|
const menus = [
|
||||||
|
{
|
||||||
|
key: 'inventory',
|
||||||
|
container: '[data-application-part="inventory"]',
|
||||||
|
content: '.items-section',
|
||||||
|
callback: this._onMenuFilterInventory.bind(this),
|
||||||
|
target: '.filter-button',
|
||||||
|
filters: FilterMenu.invetoryFilters
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
menus.forEach(m => {
|
||||||
|
const container = this.element.querySelector(m.container);
|
||||||
|
this.#menu[m.key] = new FilterMenu(container, m.target, m.filters, m.callback, {
|
||||||
|
contentSelector: m.content
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback when filters change
|
||||||
|
* @param {PointerEvent} event
|
||||||
|
* @param {HTMLElement} html
|
||||||
|
* @param {import('../ux/filter-menu.mjs').FilterItem[]} filters
|
||||||
|
*/
|
||||||
|
async _onMenuFilterInventory(_event, html, filters) {
|
||||||
|
this.#filteredItems.inventory.menu.clear();
|
||||||
|
|
||||||
|
for (const li of html.querySelectorAll('.inventory-item')) {
|
||||||
|
const item = await getDocFromElement(li);
|
||||||
|
|
||||||
|
const matchesMenu =
|
||||||
|
filters.length === 0 || filters.some(f => foundry.applications.ux.SearchFilter.evaluateFilter(item, f));
|
||||||
|
if (matchesMenu) this.#filteredItems.inventory.menu.add(item.id);
|
||||||
|
|
||||||
|
const { search } = this.#filteredItems.inventory;
|
||||||
|
li.hidden = !(search.has(item.id) && matchesMenu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
async _onDropActor(event, document) {
|
||||||
|
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||||
|
if (document instanceof DhpActor && Party.ALLOWED_ACTOR_TYPES.includes(document.type)) {
|
||||||
|
const currentMembers = this.document.system.partyMembers.map(x => x.uuid);
|
||||||
|
if (currentMembers.includes(data.uuid)) {
|
||||||
|
return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.duplicateCharacter'));
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.document.update({ 'system.partyMembers': [...currentMembers, document.uuid] });
|
||||||
|
} else {
|
||||||
|
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.onlyCharactersInPartySheet'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #deletePartyMember(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.adversary'),
|
||||||
|
name: doc.name
|
||||||
|
})
|
||||||
|
},
|
||||||
|
content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: doc.name })
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!confirmed) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentMembers = this.document.system.partyMembers.map(x => x.uuid);
|
||||||
|
const newMemberdList = currentMembers.filter(uuid => uuid !== doc.uuid);
|
||||||
|
await this.document.update({ 'system.partyMembers': newMemberdList });
|
||||||
|
}
|
||||||
|
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -44,9 +44,8 @@ export default class DHBaseActorSettings extends DHApplicationMixin(DocumentShee
|
||||||
const context = await super._prepareContext(options);
|
const context = await super._prepareContext(options);
|
||||||
context.isNPC = this.actor.isNPC;
|
context.isNPC = this.actor.isNPC;
|
||||||
|
|
||||||
if (context.systemFields.attack) {
|
if (context.systemFields.attack)
|
||||||
context.systemFields.attack.fields = this.actor.system.attack.schema.fields;
|
context.systemFields.attack.fields = this.actor.system.attack.schema.fields;
|
||||||
}
|
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
const { HandlebarsApplicationMixin } = foundry.applications.api;
|
const { HandlebarsApplicationMixin } = foundry.applications.api;
|
||||||
import { getDocFromElement, getDocFromElementSync, tagifyElement } from '../../../helpers/utils.mjs';
|
import { getDocFromElement, getDocFromElementSync, tagifyElement } from '../../../helpers/utils.mjs';
|
||||||
import { ItemBrowser } from '../../ui/itemBrowser.mjs';
|
|
||||||
|
|
||||||
const typeSettingsMap = {
|
const typeSettingsMap = {
|
||||||
character: 'extendCharacterDescriptions',
|
character: 'extendCharacterDescriptions',
|
||||||
|
|
@ -85,6 +84,8 @@ export default function DHApplicationMixin(Base) {
|
||||||
this._dragDrop = this._createDragDropHandlers();
|
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}
|
||||||
|
|
@ -98,10 +99,12 @@ export default function DHApplicationMixin(Base) {
|
||||||
deleteDoc: DHSheetV2.#deleteDoc,
|
deleteDoc: DHSheetV2.#deleteDoc,
|
||||||
toChat: DHSheetV2.#toChat,
|
toChat: DHSheetV2.#toChat,
|
||||||
useItem: DHSheetV2.#useItem,
|
useItem: DHSheetV2.#useItem,
|
||||||
|
viewItem: DHSheetV2.#viewItem,
|
||||||
toggleEffect: DHSheetV2.#toggleEffect,
|
toggleEffect: DHSheetV2.#toggleEffect,
|
||||||
toggleExtended: DHSheetV2.#toggleExtended,
|
toggleExtended: DHSheetV2.#toggleExtended,
|
||||||
addNewItem: DHSheetV2.#addNewItem,
|
addNewItem: DHSheetV2.#addNewItem,
|
||||||
browseItem: DHSheetV2.#browseItem
|
browseItem: DHSheetV2.#browseItem,
|
||||||
|
editAttribution: DHSheetV2.#editAttribution
|
||||||
},
|
},
|
||||||
contextMenus: [
|
contextMenus: [
|
||||||
{
|
{
|
||||||
|
|
@ -121,10 +124,47 @@ export default function DHApplicationMixin(Base) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
dragDrop: [],
|
dragDrop: [{ dragSelector: '.inventory-item[data-type="effect"]', dropSelector: null }],
|
||||||
tagifyConfigs: []
|
tagifyConfigs: []
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**@inheritdoc */
|
||||||
|
async _renderFrame(options) {
|
||||||
|
const frame = await super._renderFrame(options);
|
||||||
|
|
||||||
|
const hideAttribution = game.settings.get(
|
||||||
|
CONFIG.DH.id,
|
||||||
|
CONFIG.DH.SETTINGS.gameSettings.appearance
|
||||||
|
).hideAttribution;
|
||||||
|
const headerAttribution = !this.#nonHeaderAttribution.includes(this.document.type);
|
||||||
|
if (!hideAttribution && this.document.system.metadata.hasAttribution && headerAttribution) {
|
||||||
|
const { source, page } = this.document.system.attribution;
|
||||||
|
const attribution = [source, page ? `pg ${page}.` : null].filter(x => x).join('. ');
|
||||||
|
const element = `<label class="attribution-header-label">${attribution}</label>`;
|
||||||
|
this.window.controls.insertAdjacentHTML('beforebegin', element);
|
||||||
|
}
|
||||||
|
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh the custom parts of the application frame
|
||||||
|
*/
|
||||||
|
refreshFrame() {
|
||||||
|
const hideAttribution = game.settings.get(
|
||||||
|
CONFIG.DH.id,
|
||||||
|
CONFIG.DH.SETTINGS.gameSettings.appearance
|
||||||
|
).hideAttribution;
|
||||||
|
const headerAttribution = !this.#nonHeaderAttribution.includes(this.document.type);
|
||||||
|
if (!hideAttribution && this.document.system.metadata.hasAttribution && headerAttribution) {
|
||||||
|
const { source, page } = this.document.system.attribution;
|
||||||
|
const attribution = [source, page ? `pg ${page}.` : null].filter(x => x).join('. ');
|
||||||
|
|
||||||
|
const label = this.window.header.querySelector('.attribution-header-label');
|
||||||
|
label.innerHTML = attribution;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Related documents that should cause a rerender of this application when updated.
|
* Related documents that should cause a rerender of this application when updated.
|
||||||
*/
|
*/
|
||||||
|
|
@ -138,6 +178,79 @@ export default function DHApplicationMixin(Base) {
|
||||||
_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));
|
this._dragDrop.forEach(d => d.bind(htmlElement));
|
||||||
|
|
||||||
|
// Handle delta inputs
|
||||||
|
for (const deltaInput of htmlElement.querySelectorAll('input[data-allow-delta]')) {
|
||||||
|
deltaInput.dataset.numValue = deltaInput.value;
|
||||||
|
deltaInput.inputMode = 'numeric';
|
||||||
|
|
||||||
|
const handleUpdate = (delta = 0) => {
|
||||||
|
const min = Number(deltaInput.min) || 0;
|
||||||
|
const max = Number(deltaInput.max) || Infinity;
|
||||||
|
const current = Number(deltaInput.dataset.numValue);
|
||||||
|
const rawNumber = Number(deltaInput.value);
|
||||||
|
if (Number.isNaN(rawNumber)) {
|
||||||
|
deltaInput.value = delta ? Math.clamp(current + delta, min, max) : current;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newValue =
|
||||||
|
deltaInput.value.startsWith('+') || deltaInput.value.startsWith('-')
|
||||||
|
? Math.clamp(current + rawNumber + delta, min, max)
|
||||||
|
: Math.clamp(rawNumber + delta, min, max);
|
||||||
|
deltaInput.value = deltaInput.dataset.numValue = newValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Force valid characters while inputting
|
||||||
|
deltaInput.addEventListener('input', () => {
|
||||||
|
deltaInput.value = /[+=\-]?\d*/.exec(deltaInput.value)?.at(0) ?? deltaInput.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Recreate Keyup/Keydown support
|
||||||
|
deltaInput.addEventListener('keydown', event => {
|
||||||
|
const step = event.key === 'ArrowUp' ? 1 : event.key === 'ArrowDown' ? -1 : 0;
|
||||||
|
if (step !== 0) {
|
||||||
|
handleUpdate(step);
|
||||||
|
deltaInput.dispatchEvent(new Event("change", { bubbles: true }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mousewheel while focused support
|
||||||
|
deltaInput.addEventListener(
|
||||||
|
'wheel',
|
||||||
|
event => {
|
||||||
|
if (deltaInput === document.activeElement) {
|
||||||
|
event.preventDefault();
|
||||||
|
handleUpdate(Math.sign(-1 * event.deltaY));
|
||||||
|
deltaInput.dispatchEvent(new Event("change", { bubbles: true }));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ passive: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
deltaInput.addEventListener('change', () => {
|
||||||
|
handleUpdate();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle contenteditable
|
||||||
|
for (const input of htmlElement.querySelectorAll('[contenteditable][data-property]')) {
|
||||||
|
const property = input.dataset.property;
|
||||||
|
input.addEventListener("blur", () => {
|
||||||
|
const selection = document.getSelection();
|
||||||
|
if (input.contains(selection.anchorNode)) {
|
||||||
|
selection.empty();
|
||||||
|
}
|
||||||
|
this.document.update({ [property]: input.textContent });
|
||||||
|
});
|
||||||
|
|
||||||
|
input.addEventListener("keydown", event => {
|
||||||
|
if (event.key === "Enter") input.blur();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Chrome sometimes add <br>, which aren't a problem for the value but are for the placeholder
|
||||||
|
input.addEventListener("input", () => input.querySelectorAll("br").forEach((i) => i.remove()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**@inheritdoc */
|
/**@inheritdoc */
|
||||||
|
|
@ -164,11 +277,19 @@ export default function DHApplicationMixin(Base) {
|
||||||
this.relatedDocs.filter(doc => doc).map(doc => delete doc.apps[this.id]);
|
this.relatedDocs.filter(doc => doc).map(doc => delete doc.apps[this.id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
async _renderHTML(context, options) {
|
||||||
|
const rendered = await super._renderHTML(context, options);
|
||||||
|
for (const result of Object.values(rendered)) {
|
||||||
|
await this.#prepareInventoryDescription(result);
|
||||||
|
}
|
||||||
|
return rendered;
|
||||||
|
}
|
||||||
|
|
||||||
/**@inheritdoc */
|
/**@inheritdoc */
|
||||||
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);
|
||||||
await this.#prepareInventoryDescription(context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
@ -176,8 +297,8 @@ export default function DHApplicationMixin(Base) {
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**@inheritdoc */
|
/**@inheritdoc */
|
||||||
_syncPartState(partId, newElement, priorElement, state) {
|
_preSyncPartState(partId, newElement, priorElement, state) {
|
||||||
super._syncPartState(partId, newElement, priorElement, state);
|
super._preSyncPartState(partId, newElement, priorElement, state);
|
||||||
for (const el of priorElement.querySelectorAll('.extensible.extended')) {
|
for (const el of priorElement.querySelectorAll('.extensible.extended')) {
|
||||||
const { actionId, itemUuid } = el.parentElement.dataset;
|
const { actionId, itemUuid } = el.parentElement.dataset;
|
||||||
const selector = `${actionId ? `[data-action-id="${actionId}"]` : `[data-item-uuid="${itemUuid}"]`} .extensible`;
|
const selector = `${actionId ? `[data-action-id="${actionId}"]` : `[data-item-uuid="${itemUuid}"]`} .extensible`;
|
||||||
|
|
@ -249,14 +370,38 @@ export default function DHApplicationMixin(Base) {
|
||||||
* @param {DragEvent} event
|
* @param {DragEvent} event
|
||||||
* @protected
|
* @protected
|
||||||
*/
|
*/
|
||||||
_onDragStart(event) {}
|
async _onDragStart(event) {
|
||||||
|
const inventoryItem = event.currentTarget.closest('.inventory-item');
|
||||||
|
if (inventoryItem) {
|
||||||
|
const { type, itemUuid } = inventoryItem.dataset;
|
||||||
|
if (type === 'effect') {
|
||||||
|
const effect = await foundry.utils.fromUuid(itemUuid);
|
||||||
|
const effectData = {
|
||||||
|
type: 'ActiveEffect',
|
||||||
|
data: { ...effect.toObject(), _id: null },
|
||||||
|
fromInternal: this.document.uuid
|
||||||
|
};
|
||||||
|
event.dataTransfer.setData('text/plain', JSON.stringify(effectData));
|
||||||
|
event.dataTransfer.setDragImage(inventoryItem.querySelector('img'), 60, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle drop event.
|
* Handle drop event.
|
||||||
* @param {DragEvent} event
|
* @param {DragEvent} event
|
||||||
* @protected
|
* @protected
|
||||||
*/
|
*/
|
||||||
_onDrop(event) {}
|
_onDrop(event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||||
|
if (data.type === 'ActiveEffect' && data.fromInternal !== this.document.uuid) {
|
||||||
|
this.document.createEmbeddedDocuments('ActiveEffect', [data.data]);
|
||||||
|
} else {
|
||||||
|
// Fallback to super, but note that item sheets do not have this function
|
||||||
|
return super._onDrop?.(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
/* Context Menu */
|
/* Context Menu */
|
||||||
|
|
@ -359,7 +504,9 @@ export default function DHApplicationMixin(Base) {
|
||||||
callback: async (target, event) => {
|
callback: async (target, event) => {
|
||||||
const doc = await getDocFromElement(target),
|
const doc = await getDocFromElement(target),
|
||||||
action = doc?.system?.attack ?? doc;
|
action = doc?.system?.attack ?? doc;
|
||||||
return action && action.use(event, { byPassRoll: true });
|
const config = action.prepareConfig(event);
|
||||||
|
config.hasRoll = false;
|
||||||
|
return action && action.workflow.get('damage').execute(config, null, true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -378,7 +525,7 @@ export default function DHApplicationMixin(Base) {
|
||||||
options.push({
|
options.push({
|
||||||
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.sendToChat',
|
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.sendToChat',
|
||||||
icon: 'fa-solid fa-message',
|
icon: 'fa-solid fa-message',
|
||||||
callback: async target => (await getDocFromElement(target)).toChat(this.document.id)
|
callback: async target => (await getDocFromElement(target)).toChat(this.document.uuid)
|
||||||
});
|
});
|
||||||
|
|
||||||
if (deletable)
|
if (deletable)
|
||||||
|
|
@ -419,11 +566,12 @@ export default function DHApplicationMixin(Base) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepares and enriches an inventory item or action description for display.
|
* Prepares and enriches an inventory item or action description for display.
|
||||||
|
* @param {HTMLElement} element the element to enrich the inventory items of
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async #prepareInventoryDescription(context) {
|
async #prepareInventoryDescription(element) {
|
||||||
// Get all inventory item elements with a data-item-uuid attribute
|
// Get all inventory item elements with a data-item-uuid attribute
|
||||||
const inventoryItems = this.element.querySelectorAll('.inventory-item[data-item-uuid]');
|
const inventoryItems = element.querySelectorAll('.inventory-item[data-item-uuid]');
|
||||||
for (const el of inventoryItems) {
|
for (const el of inventoryItems) {
|
||||||
// Get the doc uuid from the element
|
// Get the doc uuid from the element
|
||||||
const { itemUuid } = el?.dataset || {};
|
const { itemUuid } = el?.dataset || {};
|
||||||
|
|
@ -514,28 +662,27 @@ export default function DHApplicationMixin(Base) {
|
||||||
static async #browseItem(event, target) {
|
static async #browseItem(event, target) {
|
||||||
const type = target.dataset.compendium ?? target.dataset.type;
|
const type = target.dataset.compendium ?? target.dataset.type;
|
||||||
|
|
||||||
const presets = {};
|
const presets = {
|
||||||
|
render: {
|
||||||
|
noFolder: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'loot':
|
case 'loot':
|
||||||
|
presets.folder = 'equipments.folders.loots';
|
||||||
|
break;
|
||||||
case 'consumable':
|
case 'consumable':
|
||||||
|
presets.folder = 'equipments.folders.consumables';
|
||||||
|
break;
|
||||||
case 'armor':
|
case 'armor':
|
||||||
|
presets.folder = 'equipments.folders.armors';
|
||||||
|
break;
|
||||||
case 'weapon':
|
case 'weapon':
|
||||||
presets.compendium = 'daggerheart';
|
presets.folder = 'equipments.folders.weapons';
|
||||||
presets.folder = 'equipments';
|
|
||||||
presets.render = {
|
|
||||||
noFolder: true
|
|
||||||
};
|
|
||||||
presets.filter = {
|
|
||||||
type: { key: 'type', value: type, forced: true }
|
|
||||||
};
|
|
||||||
break;
|
break;
|
||||||
case 'domainCard':
|
case 'domainCard':
|
||||||
presets.compendium = 'daggerheart';
|
|
||||||
presets.folder = 'domains';
|
presets.folder = 'domains';
|
||||||
presets.render = {
|
|
||||||
noFolder: true
|
|
||||||
};
|
|
||||||
presets.filter = {
|
presets.filter = {
|
||||||
'level.max': { key: 'level.max', value: this.document.system.levelData.level.current },
|
'level.max': { key: 'level.max', value: this.document.system.levelData.level.current },
|
||||||
'system.domain': { key: 'system.domain', value: this.document.system.domains }
|
'system.domain': { key: 'system.domain', value: this.document.system.domains }
|
||||||
|
|
@ -545,7 +692,15 @@ export default function DHApplicationMixin(Base) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ItemBrowser({ presets }).render({ force: true });
|
ui.compendiumBrowser.open(presets);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open the attribution dialog
|
||||||
|
* @type {ApplicationClickAction}
|
||||||
|
*/
|
||||||
|
static async #editAttribution() {
|
||||||
|
new game.system.api.applications.dialogs.AttributionDialog(this.document).render({ force: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -568,7 +723,6 @@ export default function DHApplicationMixin(Base) {
|
||||||
if (featureOnCharacter) {
|
if (featureOnCharacter) {
|
||||||
systemData = {
|
systemData = {
|
||||||
originItemType: this.document.type,
|
originItemType: this.document.type,
|
||||||
originId: this.document.id,
|
|
||||||
identifier: this.document.system.isMulticlass ? 'multiclass' : null
|
identifier: this.document.system.isMulticlass ? 'multiclass' : null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -582,6 +736,9 @@ export default function DHApplicationMixin(Base) {
|
||||||
};
|
};
|
||||||
if (inVault) data['system.inVault'] = true;
|
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];
|
||||||
|
}
|
||||||
|
|
||||||
const doc = await cls.create(data, { parent, renderSheet: !event.shiftKey });
|
const doc = await cls.create(data, { parent, renderSheet: !event.shiftKey });
|
||||||
if (parentIsItem && type === 'feature') {
|
if (parentIsItem && type === 'feature') {
|
||||||
|
|
@ -618,7 +775,7 @@ export default function DHApplicationMixin(Base) {
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
*/
|
*/
|
||||||
static async #toChat(_event, target) {
|
static async #toChat(_event, target) {
|
||||||
let doc = await getDocFromElement(target);
|
const doc = await getDocFromElement(target);
|
||||||
return doc.toChat(doc.uuid);
|
return doc.toChat(doc.uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -627,10 +784,19 @@ export default function DHApplicationMixin(Base) {
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
*/
|
*/
|
||||||
static async #useItem(event, target) {
|
static async #useItem(event, target) {
|
||||||
let doc = await getDocFromElement(target);
|
const doc = await getDocFromElement(target);
|
||||||
await doc.use(event);
|
await doc.use(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View an item by opening its sheet
|
||||||
|
* @type {ApplicationClickAction}
|
||||||
|
*/
|
||||||
|
static async #viewItem(_, target) {
|
||||||
|
const doc = await getDocFromElement(target);
|
||||||
|
await doc.sheet.render({ force: true });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle a ActiveEffect
|
* Toggle a ActiveEffect
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { getDocFromElement, itemIsIdentical } from '../../../helpers/utils.mjs';
|
||||||
import DHBaseActorSettings from './actor-setting.mjs';
|
import DHBaseActorSettings from './actor-setting.mjs';
|
||||||
import DHApplicationMixin from './application-mixin.mjs';
|
import DHApplicationMixin from './application-mixin.mjs';
|
||||||
|
|
||||||
|
|
@ -33,7 +34,10 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
dragDrop: [{ dragSelector: '.inventory-item[data-type="attack"]', dropSelector: null }]
|
dragDrop: [
|
||||||
|
{ dragSelector: '.inventory-item[data-type="attack"]', dropSelector: null },
|
||||||
|
{ dragSelector: ".currency[data-currency] .drag-handle", dropSelector: null }
|
||||||
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
@ -47,6 +51,12 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
||||||
return (this.#settingSheet ??= SheetClass ? new SheetClass({ document: this.document }) : null);
|
return (this.#settingSheet ??= SheetClass ? new SheetClass({ document: this.document }) : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isVisible() {
|
||||||
|
const viewPermission = this.document.testUserPermission(game.user, this.options.viewPermission);
|
||||||
|
const limitedOnly = this.document.testUserPermission(game.user, this.options.viewPermission, { exact: true });
|
||||||
|
return limitedOnly ? this.document.system.metadata.hasLimitedView : viewPermission;
|
||||||
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
/* Prepare Context */
|
/* Prepare Context */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
@ -55,6 +65,36 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
||||||
async _prepareContext(_options) {
|
async _prepareContext(_options) {
|
||||||
const context = await super._prepareContext(_options);
|
const context = await super._prepareContext(_options);
|
||||||
context.isNPC = this.document.isNPC;
|
context.isNPC = this.document.isNPC;
|
||||||
|
context.useResourcePips = game.settings.get(
|
||||||
|
CONFIG.DH.id,
|
||||||
|
CONFIG.DH.SETTINGS.gameSettings.appearance
|
||||||
|
).useResourcePips;
|
||||||
|
context.showAttribution = !game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance)
|
||||||
|
.hideAttribution;
|
||||||
|
|
||||||
|
// Prepare inventory data
|
||||||
|
if (['party', 'character'].includes(this.document.type)) {
|
||||||
|
context.inventory = {
|
||||||
|
currencies: {},
|
||||||
|
weapons: this.document.itemTypes.weapon.sort((a, b) => a.sort - b.sort),
|
||||||
|
armor: this.document.itemTypes.armor.sort((a, b) => a.sort - b.sort),
|
||||||
|
consumables: this.document.itemTypes.consumable.sort((a, b) => a.sort - b.sort),
|
||||||
|
loot: this.document.itemTypes.loot.sort((a, b) => a.sort - b.sort)
|
||||||
|
};
|
||||||
|
const { title, ...currencies } = game.settings.get(
|
||||||
|
CONFIG.DH.id,
|
||||||
|
CONFIG.DH.SETTINGS.gameSettings.Homebrew
|
||||||
|
).currency;
|
||||||
|
for (const key in currencies) {
|
||||||
|
context.inventory.currencies[key] = {
|
||||||
|
...currencies[key],
|
||||||
|
field: context.systemFields.gold.fields[key],
|
||||||
|
value: context.source.system.gold[key]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
context.inventory.hasCurrency = Object.values(context.inventory.currencies).some((c) => c.enabled);
|
||||||
|
}
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -69,10 +109,39 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_configureRenderParts(options) {
|
||||||
|
const parts = super._configureRenderParts(options);
|
||||||
|
if (!this.document.system.metadata.hasLimitedView) return parts;
|
||||||
|
|
||||||
|
if (this.document.testUserPermission(game.user, 'LIMITED', { exact: true })) return { limited: parts.limited };
|
||||||
|
|
||||||
|
return Object.keys(parts).reduce((acc, key) => {
|
||||||
|
if (key !== 'limited') acc[key] = parts[key];
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
async _onRender(context, options) {
|
||||||
|
await super._onRender(context, options);
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.document.system.metadata.hasLimitedView &&
|
||||||
|
this.document.testUserPermission(game.user, 'LIMITED', { exact: true })
|
||||||
|
) {
|
||||||
|
this.element.classList = `${this.element.classList} limited`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**@inheritdoc */
|
/**@inheritdoc */
|
||||||
_attachPartListeners(partId, htmlElement, options) {
|
_attachPartListeners(partId, htmlElement, options) {
|
||||||
super._attachPartListeners(partId, htmlElement, options);
|
super._attachPartListeners(partId, htmlElement, options);
|
||||||
|
|
||||||
|
htmlElement.querySelectorAll('.inventory-item-quantity').forEach(element => {
|
||||||
|
element.addEventListener('change', this.updateItemQuantity.bind(this));
|
||||||
|
element.addEventListener('click', e => e.stopPropagation());
|
||||||
|
});
|
||||||
htmlElement.querySelectorAll('.item-button .action-uses-button').forEach(element => {
|
htmlElement.querySelectorAll('.item-button .action-uses-button').forEach(element => {
|
||||||
element.addEventListener('contextmenu', DHBaseActorSheet.#modifyActionUses);
|
element.addEventListener('contextmenu', DHBaseActorSheet.#modifyActionUses);
|
||||||
});
|
});
|
||||||
|
|
@ -111,6 +180,15 @@ 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 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Application Listener Actions */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
async updateItemQuantity(event) {
|
||||||
|
const item = await getDocFromElement(event.currentTarget);
|
||||||
|
await item?.update({ 'system.quantity': event.currentTarget.value });
|
||||||
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
/* Application Clicks Actions */
|
/* Application Clicks Actions */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
@ -179,13 +257,98 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
||||||
/* Application Drag/Drop */
|
/* Application Drag/Drop */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
async _onDrop(event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||||
|
if (data.type === 'Currency' && ['character', 'party'].includes(this.document.type)) {
|
||||||
|
const originActor = await foundry.utils.fromUuid(data.originActor);
|
||||||
|
if (!originActor || originActor.uuid === this.document.uuid) return;
|
||||||
|
const currency = data.currency;
|
||||||
|
const quantity = await game.system.api.applications.dialogs.ItemTransferDialog.configure({
|
||||||
|
originActor,
|
||||||
|
targetActor: this.document,
|
||||||
|
currency
|
||||||
|
});
|
||||||
|
if (quantity) {
|
||||||
|
originActor.update({ [`system.gold.${currency}`]: Math.max(0, originActor.system.gold[currency] - quantity) });
|
||||||
|
this.document.update({ [`system.gold.${currency}`]: this.document.system.gold[currency] + quantity });
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super._onDrop(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _onDropItem(event, item) {
|
||||||
|
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||||
|
const originActor = item.actor;
|
||||||
|
if (
|
||||||
|
item.actor?.uuid === this.document.uuid ||
|
||||||
|
!originActor ||
|
||||||
|
!['character', 'party'].includes(this.document.type)
|
||||||
|
) {
|
||||||
|
return super._onDropItem(event, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Handling transfer of inventoryItems */
|
||||||
|
if (item.system.metadata.isInventoryItem) {
|
||||||
|
if (item.system.metadata.isQuantifiable) {
|
||||||
|
const actorItem = originActor.items.get(data.originId);
|
||||||
|
const quantityTransfered = await game.system.api.applications.dialogs.ItemTransferDialog.configure({
|
||||||
|
item,
|
||||||
|
targetActor: this.document
|
||||||
|
});
|
||||||
|
|
||||||
|
if (quantityTransfered) {
|
||||||
|
if (quantityTransfered === actorItem.system.quantity) {
|
||||||
|
await originActor.deleteEmbeddedDocuments('Item', [data.originId]);
|
||||||
|
} else {
|
||||||
|
await actorItem.update({
|
||||||
|
'system.quantity': actorItem.system.quantity - quantityTransfered
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingItem = this.document.items.find(x => itemIsIdentical(x, item));
|
||||||
|
if (existingItem) {
|
||||||
|
await existingItem.update({
|
||||||
|
'system.quantity': existingItem.system.quantity + quantityTransfered
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const createData = item.toObject();
|
||||||
|
await this.document.createEmbeddedDocuments('Item', [
|
||||||
|
{
|
||||||
|
...createData,
|
||||||
|
system: {
|
||||||
|
...createData.system,
|
||||||
|
quantity: quantityTransfered
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await originActor.deleteEmbeddedDocuments('Item', [data.originId]);
|
||||||
|
await this.document.createEmbeddedDocuments('Item', [item.toObject()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On dragStart on the item.
|
* On dragStart on the item.
|
||||||
* @param {DragEvent} event - The drag event
|
* @param {DragEvent} event - The drag event
|
||||||
*/
|
*/
|
||||||
async _onDragStart(event) {
|
async _onDragStart(event) {
|
||||||
const attackItem = event.currentTarget.closest('.inventory-item[data-type="attack"]');
|
// Handle drag/dropping currencies
|
||||||
|
const currencyEl = event.currentTarget.closest(".currency[data-currency]");
|
||||||
|
if (currencyEl) {
|
||||||
|
const currency = currencyEl.dataset.currency;
|
||||||
|
const data = { type: 'Currency', currency, originActor: this.document.uuid };
|
||||||
|
event.dataTransfer.setData('text/plain', JSON.stringify(data));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle drag/dropping attacks
|
||||||
|
const attackItem = event.currentTarget.closest('.inventory-item[data-type="attack"]');
|
||||||
if (attackItem) {
|
if (attackItem) {
|
||||||
const attackData = {
|
const attackData = {
|
||||||
type: 'Attack',
|
type: 'Attack',
|
||||||
|
|
@ -195,6 +358,20 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
||||||
};
|
};
|
||||||
event.dataTransfer.setData('text/plain', JSON.stringify(attackData));
|
event.dataTransfer.setData('text/plain', JSON.stringify(attackData));
|
||||||
event.dataTransfer.setDragImage(attackItem.querySelector('img'), 60, 0);
|
event.dataTransfer.setDragImage(attackItem.querySelector('img'), 60, 0);
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const item = await getDocFromElement(event.target);
|
||||||
|
if (item) {
|
||||||
|
const dragData = {
|
||||||
|
originActor: this.document.uuid,
|
||||||
|
originId: item.id,
|
||||||
|
type: item.documentName,
|
||||||
|
uuid: item.uuid
|
||||||
|
};
|
||||||
|
event.dataTransfer.setData('text/plain', JSON.stringify(dragData));
|
||||||
|
}
|
||||||
|
|
||||||
|
super._onDragStart(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,16 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
classes: ['item'],
|
classes: ['item'],
|
||||||
position: { width: 600 },
|
position: { width: 600 },
|
||||||
window: { resizable: true },
|
window: {
|
||||||
|
resizable: true,
|
||||||
|
controls: [
|
||||||
|
{
|
||||||
|
icon: 'fa-solid fa-signature',
|
||||||
|
label: 'DAGGERHEART.UI.Tooltip.configureAttribution',
|
||||||
|
action: 'editAttribution'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
form: {
|
form: {
|
||||||
submitOnChange: true
|
submitOnChange: true
|
||||||
},
|
},
|
||||||
|
|
@ -24,9 +33,9 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
||||||
removeResource: DHBaseItemSheet.#removeResource
|
removeResource: DHBaseItemSheet.#removeResource
|
||||||
},
|
},
|
||||||
dragDrop: [
|
dragDrop: [
|
||||||
{ dragSelector: null, dropSelector: '.tab.features .drop-section' },
|
{ dragSelector: null, dropSelector: '.drop-section' },
|
||||||
{ dragSelector: '.feature-item', dropSelector: null },
|
{ dragSelector: '.feature-item', dropSelector: null },
|
||||||
{ dragSelector: '.action-item', dropSelector: null }
|
{ dragSelector: '.inventory-item', dropSelector: null }
|
||||||
],
|
],
|
||||||
contextMenus: [
|
contextMenus: [
|
||||||
{
|
{
|
||||||
|
|
@ -55,6 +64,15 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
||||||
/* Prepare Context */
|
/* Prepare Context */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**@inheritdoc */
|
||||||
|
async _prepareContext(options) {
|
||||||
|
const context = await super._prepareContext(options);
|
||||||
|
context.showAttribution = !game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance)
|
||||||
|
.hideAttribution;
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
/**@inheritdoc */
|
/**@inheritdoc */
|
||||||
async _preparePartContext(partId, context, options) {
|
async _preparePartContext(partId, context, options) {
|
||||||
await super._preparePartContext(partId, context, options);
|
await super._preparePartContext(partId, context, options);
|
||||||
|
|
@ -149,12 +167,12 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
||||||
const { type } = target.dataset;
|
const { type } = target.dataset;
|
||||||
const cls = foundry.documents.Item.implementation;
|
const cls = foundry.documents.Item.implementation;
|
||||||
|
|
||||||
|
const multiclass = this.document.system.isMulticlass ? 'multiclass' : null;
|
||||||
let systemData = {};
|
let systemData = {};
|
||||||
if (this.document.parent?.type === 'character') {
|
if (this.document.parent?.type === 'character') {
|
||||||
systemData = {
|
systemData = {
|
||||||
originItemType: this.document.type,
|
originItemType: this.document.type,
|
||||||
originId: this.document.id,
|
identifier: multiclass ?? type
|
||||||
identifier: this.document.system.isMulticlass ? 'multiclass' : null
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -181,19 +199,33 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
||||||
static async #deleteFeature(_, element) {
|
static async #deleteFeature(_, element) {
|
||||||
const target = element.closest('[data-item-uuid]');
|
const target = element.closest('[data-item-uuid]');
|
||||||
const feature = await getDocFromElement(target);
|
const feature = await getDocFromElement(target);
|
||||||
|
|
||||||
if (!feature) {
|
if (!feature) {
|
||||||
await this.document.update({
|
await this.document.update({
|
||||||
'system.features': this.document.system.features
|
'system.features': this.document.system.features
|
||||||
.filter(x => x.item)
|
.filter(x => x.item)
|
||||||
.map(x => ({ ...x, item: x.item.uuid }))
|
.map(x => ({ ...x, item: x.item.uuid }))
|
||||||
});
|
});
|
||||||
} else
|
} else {
|
||||||
|
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||||
|
window: {
|
||||||
|
title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', {
|
||||||
|
type: game.i18n.localize('TYPES.Item.feature'),
|
||||||
|
name: feature.name
|
||||||
|
})
|
||||||
|
},
|
||||||
|
content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: feature.name })
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!confirmed) return;
|
||||||
|
|
||||||
await this.document.update({
|
await this.document.update({
|
||||||
'system.features': this.document.system.features
|
'system.features': this.document.system.features
|
||||||
.filter(x => target.dataset.type !== x.type || x.item.uuid !== feature.uuid)
|
.filter(x => target.dataset.type !== x.type || x.item.uuid !== feature.uuid)
|
||||||
.map(x => ({ ...x, item: x.item.uuid }))
|
.map(x => ({ ...x, item: x.item.uuid }))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a resource to the item.
|
* Add a resource to the item.
|
||||||
|
|
@ -224,35 +256,30 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
||||||
* @param {DragEvent} event - The drag event
|
* @param {DragEvent} event - The drag event
|
||||||
*/
|
*/
|
||||||
async _onDragStart(event) {
|
async _onDragStart(event) {
|
||||||
|
/* Can prolly be improved a lot, but I don't wanna >_< */
|
||||||
const featureItem = event.currentTarget.closest('.feature-item');
|
const featureItem = event.currentTarget.closest('.feature-item');
|
||||||
|
const inventoryItem = event.currentTarget.closest('.inventory-item');
|
||||||
|
const lineItem = event.currentTarget.closest('.item-line');
|
||||||
|
const dragItemData = featureItem ?? inventoryItem ?? lineItem;
|
||||||
|
|
||||||
if (featureItem) {
|
const dragItem = await foundry.utils.fromUuid(dragItemData.dataset.itemUuid);
|
||||||
const feature = this.document.system.features.find(x => x?.id === featureItem.id);
|
if (dragItem) {
|
||||||
if (!feature) {
|
if (!dragItem) {
|
||||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureIsMissing'));
|
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureIsMissing'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const featureData = { type: 'Item', data: { ...feature.toObject(), _id: null }, fromInternal: true };
|
let dragData = {};
|
||||||
event.dataTransfer.setData('text/plain', JSON.stringify(featureData));
|
if (dragItemData.dataset.type === 'effect')
|
||||||
event.dataTransfer.setDragImage(featureItem.querySelector('img'), 60, 0);
|
dragData = {
|
||||||
} else {
|
type: 'ActiveEffect',
|
||||||
const actionItem = event.currentTarget.closest('.action-item');
|
fromInternal: this.document.uuid,
|
||||||
if (actionItem) {
|
data: { ...dragItem, uuid: dragItem.uuid, id: dragItem.id }
|
||||||
const action = this.document.system.actions[actionItem.dataset.index];
|
|
||||||
if (!action) {
|
|
||||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.actionIsMissing'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const actionData = {
|
|
||||||
type: 'Action',
|
|
||||||
data: { ...action.toObject(), id: action.id, itemUuid: this.document.uuid },
|
|
||||||
fromInternal: true
|
|
||||||
};
|
};
|
||||||
event.dataTransfer.setData('text/plain', JSON.stringify(actionData));
|
else dragData = { type: 'Item', uuid: dragItem.uuid, id: dragItem.id, fromInternal: this.document.id };
|
||||||
event.dataTransfer.setDragImage(actionItem.querySelector('img'), 60, 0);
|
|
||||||
}
|
event.dataTransfer.setData('text/plain', JSON.stringify(dragData));
|
||||||
|
event.dataTransfer.setDragImage(dragItemData.querySelector('img'), 60, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -261,8 +288,10 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
||||||
* @param {DragEvent} event - The drag event
|
* @param {DragEvent} event - The drag event
|
||||||
*/
|
*/
|
||||||
async _onDrop(event) {
|
async _onDrop(event) {
|
||||||
|
super._onDrop(event);
|
||||||
|
|
||||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||||
if (data.fromInternal) return;
|
if (data.fromInternal === this.document.id) return;
|
||||||
|
|
||||||
const target = event.target.closest('fieldset.drop-section');
|
const target = event.target.closest('fieldset.drop-section');
|
||||||
let item = await fromUuid(data.uuid);
|
let item = await fromUuid(data.uuid);
|
||||||
|
|
@ -271,14 +300,15 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
||||||
|
|
||||||
if (this.document.parent?.type === 'character') {
|
if (this.document.parent?.type === 'character') {
|
||||||
const itemData = item.toObject();
|
const itemData = item.toObject();
|
||||||
|
const multiclass = this.document.system.isMulticlass ? 'multiclass' : null;
|
||||||
item = await cls.create(
|
item = await cls.create(
|
||||||
{
|
{
|
||||||
...itemData,
|
...itemData,
|
||||||
|
_stats: { compendiumSource: this.document.uuid },
|
||||||
system: {
|
system: {
|
||||||
...itemData.system,
|
...itemData.system,
|
||||||
originItemType: this.document.type,
|
originItemType: this.document.type,
|
||||||
originId: this.document.id,
|
identifier: multiclass ?? target.dataset.type
|
||||||
identifier: this.document.system.isMulticlass ? 'multiclass' : null
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ parent: this.document.parent }
|
{ parent: this.document.parent }
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ export default function ItemAttachmentSheet(Base) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onDrop(event) {
|
async _onDrop(event) {
|
||||||
const data = TextEditor.getDragEventData(event);
|
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||||
|
|
||||||
const attachmentsSection = event.target.closest('.attachments-section');
|
const attachmentsSection = event.target.closest('.attachments-section');
|
||||||
if (!attachmentsSection) return super._onDrop(event);
|
if (!attachmentsSection) return super._onDrop(event);
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,9 @@ export default class AncestrySheet extends DHHeritageSheet {
|
||||||
* @param {DragEvent} event - The drag event
|
* @param {DragEvent} event - The drag event
|
||||||
*/
|
*/
|
||||||
async _onDrop(event) {
|
async _onDrop(event) {
|
||||||
|
const data = TextEditor.getDragEventData(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');
|
||||||
const typeField =
|
const typeField =
|
||||||
this.document.system[target.dataset.type === 'primary' ? 'primaryFeature' : 'secondaryFeature'];
|
this.document.system[target.dataset.type === 'primary' ? 'primaryFeature' : 'secondaryFeature'];
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ export default class ArmorSheet extends ItemAttachmentSheet(DHBaseItemSheet) {
|
||||||
tagifyConfigs: [
|
tagifyConfigs: [
|
||||||
{
|
{
|
||||||
selector: '.features-input',
|
selector: '.features-input',
|
||||||
options: () => CONFIG.DH.ITEM.armorFeatures,
|
options: () => CONFIG.DH.ITEM.orderedArmorFeatures(),
|
||||||
callback: ArmorSheet.#onFeatureSelect
|
callback: ArmorSheet.#onFeatureSelect
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,7 @@ export default class BeastformSheet extends DHBaseItemSheet {
|
||||||
name: context.document.system.advantageOn[key].value
|
name: context.document.system.advantageOn[key].value
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
context.dimensionsDisabled = context.document.system.tokenSize.size !== 'custom';
|
||||||
break;
|
break;
|
||||||
case 'effects':
|
case 'effects':
|
||||||
context.effects.actives = context.effects.actives.map(effect => {
|
context.effects.actives = context.effects.actives.map(effect => {
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,10 @@ export default class ClassSheet extends DHBaseItemSheet {
|
||||||
template: 'systems/daggerheart/templates/sheets/items/class/settings.hbs',
|
template: 'systems/daggerheart/templates/sheets/items/class/settings.hbs',
|
||||||
scrollable: ['.settings']
|
scrollable: ['.settings']
|
||||||
},
|
},
|
||||||
|
questions: {
|
||||||
|
template: 'systems/daggerheart/templates/sheets/items/class/questions.hbs',
|
||||||
|
scrollable: ['.questions']
|
||||||
|
},
|
||||||
effects: {
|
effects: {
|
||||||
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs',
|
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs',
|
||||||
scrollable: ['.effects']
|
scrollable: ['.effects']
|
||||||
|
|
@ -55,7 +59,13 @@ export default class ClassSheet extends DHBaseItemSheet {
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
static TABS = {
|
static TABS = {
|
||||||
primary: {
|
primary: {
|
||||||
tabs: [{ id: 'description' }, { id: 'features' }, { id: 'settings' }, { id: 'effects' }],
|
tabs: [
|
||||||
|
{ id: 'description' },
|
||||||
|
{ id: 'features' },
|
||||||
|
{ id: 'settings' },
|
||||||
|
{ id: 'questions' },
|
||||||
|
{ id: 'effects' }
|
||||||
|
],
|
||||||
initial: 'description',
|
initial: 'description',
|
||||||
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
||||||
}
|
}
|
||||||
|
|
@ -116,15 +126,25 @@ export default class ClassSheet extends DHBaseItemSheet {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const data = TextEditor.getDragEventData(event);
|
const data = TextEditor.getDragEventData(event);
|
||||||
const item = await fromUuid(data.uuid);
|
const item = await fromUuid(data.uuid);
|
||||||
|
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 (item.type === 'subclass') {
|
if (itemType === 'subclass') {
|
||||||
|
if (item.system.linkedClass) {
|
||||||
|
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({
|
await this.document.update({
|
||||||
'system.subclasses': [...this.document.system.subclasses.map(x => x.uuid), item.uuid]
|
'system.subclasses': [...this.document.system.subclasses.map(x => x.uuid), item.uuid]
|
||||||
});
|
});
|
||||||
} else if (item.type === 'feature') {
|
} 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 (item.type === 'weapon') {
|
if (itemType === 'weapon') {
|
||||||
if (target.classList.contains('primary-weapon-section')) {
|
if (target.classList.contains('primary-weapon-section')) {
|
||||||
if (!item.system.secondary)
|
if (!item.system.secondary)
|
||||||
await this.document.update({
|
await this.document.update({
|
||||||
|
|
@ -136,21 +156,21 @@ export default class ClassSheet extends DHBaseItemSheet {
|
||||||
'system.characterGuide.suggestedSecondaryWeapon': item.uuid
|
'system.characterGuide.suggestedSecondaryWeapon': item.uuid
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (item.type === 'armor') {
|
} else if (itemType === 'armor') {
|
||||||
if (target.classList.contains('armor-section')) {
|
if (target.classList.contains('armor-section')) {
|
||||||
await this.document.update({
|
await this.document.update({
|
||||||
'system.characterGuide.suggestedArmor': item.uuid
|
'system.characterGuide.suggestedArmor': item.uuid
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (target.classList.contains('choice-a-section')) {
|
} else if (target.classList.contains('choice-a-section')) {
|
||||||
if (item.type === 'loot' || item.type === 'consumable') {
|
if (itemType === 'loot' || itemType === 'consumable') {
|
||||||
const filteredChoiceA = this.document.system.inventory.choiceA;
|
const filteredChoiceA = this.document.system.inventory.choiceA;
|
||||||
if (filteredChoiceA.length < 2)
|
if (filteredChoiceA.length < 2)
|
||||||
await this.document.update({
|
await this.document.update({
|
||||||
'system.inventory.choiceA': [...filteredChoiceA.map(x => x.uuid), item.uuid]
|
'system.inventory.choiceA': [...filteredChoiceA.map(x => x.uuid), item.uuid]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (item.type === 'loot') {
|
} else if (itemType === 'loot') {
|
||||||
if (target.classList.contains('take-section')) {
|
if (target.classList.contains('take-section')) {
|
||||||
const filteredTake = this.document.system.inventory.take.filter(x => x);
|
const filteredTake = this.document.system.inventory.take.filter(x => x);
|
||||||
if (filteredTake.length < 3)
|
if (filteredTake.length < 3)
|
||||||
|
|
@ -180,7 +200,13 @@ 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);
|
||||||
await this.document.update({ [`system.${target}`]: prop.filter(i => i.uuid !== uuid).map(x => x.uuid) });
|
|
||||||
|
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) });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -31,4 +31,11 @@ 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.
|
||||||
|
/**@override */
|
||||||
|
async _prepareContext(options) {
|
||||||
|
const context = await super._prepareContext(options);
|
||||||
|
context.featureFormChoices = CONFIG.DH.ITEM.featureForm;
|
||||||
|
return context;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ export default class WeaponSheet extends ItemAttachmentSheet(DHBaseItemSheet) {
|
||||||
tagifyConfigs: [
|
tagifyConfigs: [
|
||||||
{
|
{
|
||||||
selector: '.features-input',
|
selector: '.features-input',
|
||||||
options: () => CONFIG.DH.ITEM.weaponFeatures,
|
options: () => CONFIG.DH.ITEM.orderedWeaponFeatures(),
|
||||||
callback: WeaponSheet.#onFeatureSelect
|
callback: WeaponSheet.#onFeatureSelect
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
3
module/applications/sidebar/_module.mjs
Normal file
3
module/applications/sidebar/_module.mjs
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
export { default as DaggerheartMenu } from './tabs/daggerheartMenu.mjs';
|
||||||
|
export { default as DhActorDirectory } from './tabs/actorDirectory.mjs';
|
||||||
|
export { default as DhSidebar } from './sidebar.mjs';
|
||||||
73
module/applications/sidebar/sidebar.mjs
Normal file
73
module/applications/sidebar/sidebar.mjs
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
export default class DhSidebar extends foundry.applications.sidebar.Sidebar {
|
||||||
|
/** @override */
|
||||||
|
static TABS = {
|
||||||
|
chat: {
|
||||||
|
documentName: 'ChatMessage'
|
||||||
|
},
|
||||||
|
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: {
|
||||||
|
tooltip: 'DAGGERHEART.UI.Sidebar.daggerheartMenu.title',
|
||||||
|
img: 'systems/daggerheart/assets/logos/FoundryBorneLogoWhite.svg',
|
||||||
|
gmOnly: true
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
tooltip: 'SIDEBAR.TabSettings',
|
||||||
|
icon: 'fa-solid fa-gears'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static PARTS = {
|
||||||
|
tabs: {
|
||||||
|
id: 'tabs',
|
||||||
|
template: 'systems/daggerheart/templates/sidebar/tabs.hbs'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
async _prepareTabContext(context, options) {
|
||||||
|
context.tabs = Object.entries(this.constructor.TABS).reduce((obj, [k, v]) => {
|
||||||
|
let { documentName, gmOnly, tooltip, icon, img } = v;
|
||||||
|
if (gmOnly && !game.user.isGM) return obj;
|
||||||
|
if (documentName) {
|
||||||
|
tooltip ??= getDocumentClass(documentName).metadata.labelPlural;
|
||||||
|
icon ??= CONFIG[documentName]?.sidebarIcon;
|
||||||
|
}
|
||||||
|
obj[k] = { tooltip, icon, img };
|
||||||
|
obj[k].active = this.tabGroups.primary === k;
|
||||||
|
return obj;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
}
|
||||||
46
module/applications/sidebar/tabs/actorDirectory.mjs
Normal file
46
module/applications/sidebar/tabs/actorDirectory.mjs
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
export default class DhActorDirectory extends foundry.applications.sidebar.tabs.ActorDirectory {
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
renderUpdateKeys: ['system.levelData.level.current', 'system.partner', 'system.tier']
|
||||||
|
};
|
||||||
|
|
||||||
|
static _entryPartial = 'systems/daggerheart/templates/ui/sidebar/actor-document-partial.hbs';
|
||||||
|
|
||||||
|
async _prepareDirectoryContext(context, options) {
|
||||||
|
await super._prepareDirectoryContext(context, options);
|
||||||
|
const adversaryTypes = CONFIG.DH.ACTOR.allAdversaryTypes();
|
||||||
|
const environmentTypes = CONFIG.DH.ACTOR.environmentTypes;
|
||||||
|
context.getTypeLabel = document => {
|
||||||
|
return document.type === 'adversary'
|
||||||
|
? game.i18n.localize(adversaryTypes[document.system.type]?.label ?? 'TYPES.Actor.adversary')
|
||||||
|
: document.type === 'environment'
|
||||||
|
? game.i18n.localize(environmentTypes[document.system.type]?.label ?? 'TYPES.Actor.environment')
|
||||||
|
: null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
_onDragStart(event) {
|
||||||
|
let actor;
|
||||||
|
const { entryId } = event.currentTarget.dataset;
|
||||||
|
if (entryId) {
|
||||||
|
actor = this.collection.get(entryId);
|
||||||
|
if (!actor?.visible) return false;
|
||||||
|
}
|
||||||
|
super._onDragStart(event);
|
||||||
|
|
||||||
|
// Create the drag preview.
|
||||||
|
if (actor && canvas.ready) {
|
||||||
|
const img = event.currentTarget.querySelector('img');
|
||||||
|
const pt = actor.prototypeToken;
|
||||||
|
const usesSize = actor.system.metadata.usesSize;
|
||||||
|
const tokenSizes = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes;
|
||||||
|
const width = usesSize ? tokenSizes[actor.system.size] : pt.width;
|
||||||
|
const height = usesSize ? tokenSizes[actor.system.size] : pt.height;
|
||||||
|
|
||||||
|
const w = width * canvas.dimensions.size * Math.abs(pt.texture.scaleX) * canvas.stage.scale.x;
|
||||||
|
const h = height * canvas.dimensions.size * Math.abs(pt.texture.scaleY) * canvas.stage.scale.y;
|
||||||
|
const preview = foundry.applications.ux.DragDrop.implementation.createDragImage(img, w, h);
|
||||||
|
event.dataTransfer.setDragImage(preview, w / 2, h / 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
162
module/applications/sidebar/tabs/daggerheartMenu.mjs
Normal file
162
module/applications/sidebar/tabs/daggerheartMenu.mjs
Normal file
|
|
@ -0,0 +1,162 @@
|
||||||
|
import { refreshIsAllowed } from '../../../helpers/utils.mjs';
|
||||||
|
|
||||||
|
const { HandlebarsApplicationMixin } = foundry.applications.api;
|
||||||
|
const { AbstractSidebarTab } = foundry.applications.sidebar;
|
||||||
|
/**
|
||||||
|
* The daggerheart menu tab.
|
||||||
|
* @extends {AbstractSidebarTab}
|
||||||
|
* @mixes HandlebarsApplication
|
||||||
|
*/
|
||||||
|
export default class DaggerheartMenu extends HandlebarsApplicationMixin(AbstractSidebarTab) {
|
||||||
|
constructor(options) {
|
||||||
|
super(options);
|
||||||
|
|
||||||
|
this.refreshSelections = DaggerheartMenu.defaultRefreshSelections();
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultRefreshSelections() {
|
||||||
|
return {
|
||||||
|
session: { selected: false, label: game.i18n.localize('DAGGERHEART.GENERAL.RefreshType.session') },
|
||||||
|
scene: { selected: false, label: game.i18n.localize('DAGGERHEART.GENERAL.RefreshType.scene') },
|
||||||
|
longRest: { selected: false, label: game.i18n.localize('DAGGERHEART.GENERAL.RefreshType.longrest') },
|
||||||
|
shortRest: { selected: false, label: game.i18n.localize('DAGGERHEART.GENERAL.RefreshType.shortrest') }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
classes: ['dh-style'],
|
||||||
|
window: {
|
||||||
|
title: 'SIDEBAR.TabSettings'
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
selectRefreshable: DaggerheartMenu.#selectRefreshable,
|
||||||
|
refreshActors: DaggerheartMenu.#refreshActors
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static tabName = 'daggerheartMenu';
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static PARTS = {
|
||||||
|
main: { template: 'systems/daggerheart/templates/sidebar/daggerheart-menu/main.hbs' }
|
||||||
|
};
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
async _prepareContext(options) {
|
||||||
|
const context = await super._prepareContext(options);
|
||||||
|
context.refreshables = this.refreshSelections;
|
||||||
|
context.disableRefresh = Object.values(this.refreshSelections).every(x => !x.selected);
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRefreshables(types) {
|
||||||
|
const refreshedActors = {};
|
||||||
|
for (let actor of game.actors) {
|
||||||
|
if (['character', 'adversary'].includes(actor.type) && actor.prototypeToken.actorLink) {
|
||||||
|
const updates = {};
|
||||||
|
for (let item of actor.items) {
|
||||||
|
if (item.system.metadata?.hasResource && refreshIsAllowed(types, item.system.resource?.recovery)) {
|
||||||
|
if (!refreshedActors[actor.id])
|
||||||
|
refreshedActors[actor.id] = { name: actor.name, img: actor.img, refreshed: new Set() };
|
||||||
|
refreshedActors[actor.id].refreshed.add(
|
||||||
|
game.i18n.localize(CONFIG.DH.GENERAL.refreshTypes[item.system.resource.recovery].label)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!updates[item.id]?.system) updates[item.id] = { system: {} };
|
||||||
|
|
||||||
|
const increasing =
|
||||||
|
item.system.resource.progression === CONFIG.DH.ITEM.itemResourceProgression.increasing.id;
|
||||||
|
updates[item.id].system = {
|
||||||
|
...updates[item.id].system,
|
||||||
|
'resource.value': increasing
|
||||||
|
? 0
|
||||||
|
: Roll.replaceFormulaData(item.system.resource.max, actor.getRollData())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (item.system.metadata?.hasActions) {
|
||||||
|
const refreshTypes = new Set();
|
||||||
|
const actions = item.system.actions.filter(action => {
|
||||||
|
if (refreshIsAllowed(types, action.uses.recovery)) {
|
||||||
|
refreshTypes.add(action.uses.recovery);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
if (actions.length === 0) continue;
|
||||||
|
|
||||||
|
if (!refreshedActors[actor.id])
|
||||||
|
refreshedActors[actor.id] = { name: actor.name, img: actor.img, refreshed: new Set() };
|
||||||
|
refreshedActors[actor.id].refreshed.add(
|
||||||
|
...refreshTypes.map(type => game.i18n.localize(CONFIG.DH.GENERAL.refreshTypes[type].label))
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!updates[item.id]?.system) updates[item.id] = { system: {} };
|
||||||
|
|
||||||
|
updates[item.id].system = {
|
||||||
|
...updates[item.id].system,
|
||||||
|
...actions.reduce(
|
||||||
|
(acc, action) => {
|
||||||
|
acc.actions[action.id] = { 'uses.value': 0 };
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{ actions: updates[item.id].system.actions ?? {} }
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let key in updates) {
|
||||||
|
const update = updates[key];
|
||||||
|
await actor.items.get(key).update(update);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return refreshedActors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Application Clicks Actions */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
static async #selectRefreshable(_event, button) {
|
||||||
|
const { type } = button.dataset;
|
||||||
|
this.refreshSelections[type].selected = !this.refreshSelections[type].selected;
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #refreshActors() {
|
||||||
|
const refreshKeys = Object.keys(this.refreshSelections).filter(key => this.refreshSelections[key].selected);
|
||||||
|
await this.getRefreshables(refreshKeys);
|
||||||
|
const types = refreshKeys.map(x => this.refreshSelections[x].label).join(', ');
|
||||||
|
ui.notifications.info(
|
||||||
|
game.i18n.format('DAGGERHEART.UI.Notifications.gmMenuRefresh', {
|
||||||
|
types: `[${types}]`
|
||||||
|
})
|
||||||
|
);
|
||||||
|
this.refreshSelections = DaggerheartMenu.defaultRefreshSelections();
|
||||||
|
|
||||||
|
const cls = getDocumentClass('ChatMessage');
|
||||||
|
const msg = {
|
||||||
|
user: game.user.id,
|
||||||
|
content: await foundry.applications.handlebars.renderTemplate(
|
||||||
|
'systems/daggerheart/templates/ui/chat/refreshMessage.hbs',
|
||||||
|
{
|
||||||
|
types: types
|
||||||
|
}
|
||||||
|
),
|
||||||
|
title: game.i18n.localize('DAGGERHEART.UI.Chat.refreshMessage.title'),
|
||||||
|
speaker: cls.getSpeaker()
|
||||||
|
};
|
||||||
|
|
||||||
|
cls.create(msg);
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
|
export { default as CountdownEdit } from './countdownEdit.mjs';
|
||||||
|
export { default as DhCountdowns } from './countdowns.mjs';
|
||||||
export { default as DhChatLog } from './chatLog.mjs';
|
export { default as DhChatLog } from './chatLog.mjs';
|
||||||
export { default as DhCombatTracker } from './combatTracker.mjs';
|
export { default as DhCombatTracker } from './combatTracker.mjs';
|
||||||
export * as DhCountdowns from './countdowns.mjs';
|
export { default as DhEffectsDisplay } from './effectsDisplay.mjs';
|
||||||
export { default as DhFearTracker } from './fearTracker.mjs';
|
export { default as DhFearTracker } from './fearTracker.mjs';
|
||||||
export { default as DhHotbar } from './hotbar.mjs';
|
export { default as DhHotbar } from './hotbar.mjs';
|
||||||
|
export { ItemBrowser } from './itemBrowser.mjs';
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { emitAsGM, GMUpdateEvent } from '../../systemRegistration/socket.mjs';
|
import { abilities } from '../../config/actorConfig.mjs';
|
||||||
|
import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.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) {
|
||||||
|
|
@ -37,7 +38,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
// }
|
// }
|
||||||
// },
|
// },
|
||||||
{
|
{
|
||||||
name: 'Reroll Damage',
|
name: game.i18n.localize('DAGGERHEART.UI.ChatLog.rerollDamage'),
|
||||||
icon: '<i class="fa-solid fa-dice"></i>',
|
icon: '<i class="fa-solid fa-dice"></i>',
|
||||||
condition: li => {
|
condition: li => {
|
||||||
const message = game.messages.get(li.dataset.messageId);
|
const message = game.messages.get(li.dataset.messageId);
|
||||||
|
|
@ -54,30 +55,31 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
addChatListeners = async (app, html, data) => {
|
addChatListeners = async (document, html, data) => {
|
||||||
html.querySelectorAll('.duality-action-damage').forEach(element =>
|
const message = data?.message ?? document.toObject(false);
|
||||||
element.addEventListener('click', event => this.onRollDamage(event, data.message))
|
|
||||||
);
|
|
||||||
html.querySelectorAll('.target-save').forEach(element =>
|
|
||||||
element.addEventListener('click', event => this.onRollSave(event, data.message))
|
|
||||||
);
|
|
||||||
html.querySelectorAll('.roll-all-save-button').forEach(element =>
|
|
||||||
element.addEventListener('click', event => this.onRollAllSave(event, data.message))
|
|
||||||
);
|
|
||||||
html.querySelectorAll('.simple-roll-button').forEach(element =>
|
html.querySelectorAll('.simple-roll-button').forEach(element =>
|
||||||
element.addEventListener('click', event => this.onRollSimple(event, data.message))
|
element.addEventListener('click', event => this.onRollSimple(event, message))
|
||||||
);
|
|
||||||
html.querySelectorAll('.healing-button').forEach(element =>
|
|
||||||
element.addEventListener('click', event => this.onHealing(event, data.message))
|
|
||||||
);
|
);
|
||||||
html.querySelectorAll('.ability-use-button').forEach(element =>
|
html.querySelectorAll('.ability-use-button').forEach(element =>
|
||||||
element.addEventListener('click', event => this.abilityUseButton(event, data.message))
|
element.addEventListener('click', event => this.abilityUseButton(event, message))
|
||||||
);
|
);
|
||||||
html.querySelectorAll('.action-use-button').forEach(element =>
|
html.querySelectorAll('.action-use-button').forEach(element =>
|
||||||
element.addEventListener('click', event => this.actionUseButton(event, data.message))
|
element.addEventListener('click', event => this.actionUseButton(event, message))
|
||||||
);
|
);
|
||||||
html.querySelectorAll('.reroll-button').forEach(element =>
|
html.querySelectorAll('.reroll-button').forEach(element =>
|
||||||
element.addEventListener('click', event => this.rerollEvent(event, data.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)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -90,80 +92,6 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
super.close(options);
|
super.close(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getActor(uuid) {
|
|
||||||
return await foundry.utils.fromUuid(uuid);
|
|
||||||
}
|
|
||||||
|
|
||||||
getAction(actor, itemId, actionId) {
|
|
||||||
const item = actor.items.get(itemId),
|
|
||||||
action =
|
|
||||||
actor.system.attack?._id === actionId
|
|
||||||
? actor.system.attack
|
|
||||||
: item.system.attack?._id === actionId
|
|
||||||
? item.system.attack
|
|
||||||
: item?.system?.actions?.get(actionId);
|
|
||||||
return action;
|
|
||||||
}
|
|
||||||
|
|
||||||
async onRollDamage(event, message) {
|
|
||||||
event.stopPropagation();
|
|
||||||
const actor = await this.getActor(message.system.source.actor);
|
|
||||||
if (game.user.character?.id !== actor.id && !game.user.isGM) return true;
|
|
||||||
if (message.system.source.item && message.system.source.action) {
|
|
||||||
const action = this.getAction(actor, message.system.source.item, message.system.source.action);
|
|
||||||
if (!action || !action?.rollDamage) return;
|
|
||||||
await action.rollDamage(event, message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async onRollSave(event, message) {
|
|
||||||
event.stopPropagation();
|
|
||||||
const actor = await this.getActor(message.system.source.actor),
|
|
||||||
tokenId = event.target.closest('[data-token]')?.dataset.token,
|
|
||||||
token = game.canvas.tokens.get(tokenId);
|
|
||||||
if (!token?.actor || !token.isOwner) return true;
|
|
||||||
if (message.system.source.item && message.system.source.action) {
|
|
||||||
const action = this.getAction(actor, message.system.source.item, message.system.source.action);
|
|
||||||
if (!action || !action?.hasSave) return;
|
|
||||||
action.rollSave(token.actor, event, message).then(result =>
|
|
||||||
emitAsGM(
|
|
||||||
GMUpdateEvent.UpdateSaveMessage,
|
|
||||||
action.updateSaveMessage.bind(action, result, message, token.id),
|
|
||||||
{
|
|
||||||
action: action.uuid,
|
|
||||||
message: message._id,
|
|
||||||
token: token.id,
|
|
||||||
result
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async onRollAllSave(event, message) {
|
|
||||||
event.stopPropagation();
|
|
||||||
if (!game.user.isGM) return;
|
|
||||||
const targets = event.target.parentElement.querySelectorAll('[data-token] .target-save');
|
|
||||||
const actor = await this.getActor(message.system.source.actor),
|
|
||||||
action = this.getAction(actor, message.system.source.item, message.system.source.action);
|
|
||||||
targets.forEach(async el => {
|
|
||||||
const tokenId = el.closest('[data-token]')?.dataset.token,
|
|
||||||
token = game.canvas.tokens.get(tokenId);
|
|
||||||
if (!token.actor) return;
|
|
||||||
if (game.user === token.actor.owner) el.dispatchEvent(new PointerEvent('click', { shiftKey: true }));
|
|
||||||
else {
|
|
||||||
token.actor.owner
|
|
||||||
.query('reactionRoll', {
|
|
||||||
actionId: action.uuid,
|
|
||||||
actorId: token.actor.uuid,
|
|
||||||
event,
|
|
||||||
message
|
|
||||||
})
|
|
||||||
.then(result => action.updateSaveMessage(result, message, token.id));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async onRollSimple(event, message) {
|
async onRollSimple(event, message) {
|
||||||
const buttonType = event.target.dataset.type ?? 'damage',
|
const buttonType = event.target.dataset.type ?? 'damage',
|
||||||
total = message.rolls.reduce((a, c) => a + Roll.fromJSON(c).total, 0),
|
total = message.rolls.reduce((a, c) => a + Roll.fromJSON(c).total, 0),
|
||||||
|
|
@ -197,17 +125,32 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
item.system.attack?.id === event.currentTarget.id
|
item.system.attack?.id === event.currentTarget.id
|
||||||
? item.system.attack
|
? item.system.attack
|
||||||
: item.system.actions.get(event.currentTarget.id);
|
: item.system.actions.get(event.currentTarget.id);
|
||||||
if (event.currentTarget.dataset.directDamage) action.use(event, { byPassRoll: true });
|
if (event.currentTarget.dataset.directDamage) {
|
||||||
else action.use(event);
|
const config = action.prepareConfig(event);
|
||||||
|
config.hasRoll = false;
|
||||||
|
action.workflow.get('damage').execute(config, null, true);
|
||||||
|
} else action.use(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
async actionUseButton(event, message) {
|
async actionUseButton(event, message) {
|
||||||
const { moveIndex, actionIndex } = event.currentTarget.dataset;
|
const { moveIndex, actionIndex, movePath } = event.currentTarget.dataset;
|
||||||
const parent = await foundry.utils.fromUuid(message.system.actor);
|
const targetUuid = event.currentTarget.closest('.action-use-button-parent').querySelector('select')?.value;
|
||||||
|
const parent = await foundry.utils.fromUuid(targetUuid || message.system.actor)
|
||||||
|
|
||||||
const actionType = message.system.moves[moveIndex].actions[actionIndex];
|
const actionType = message.system.moves[moveIndex].actions[actionIndex];
|
||||||
const cls = game.system.api.models.actions.actionsTypes[actionType.type];
|
const cls = game.system.api.models.actions.actionsTypes[actionType.type];
|
||||||
const action = new cls(
|
const action = new cls(
|
||||||
{ ...actionType, _id: foundry.utils.randomID(), name: game.i18n.localize(actionType.name) },
|
{
|
||||||
|
...actionType,
|
||||||
|
_id: foundry.utils.randomID(),
|
||||||
|
name: game.i18n.localize(actionType.name),
|
||||||
|
originItem: {
|
||||||
|
type: CONFIG.DH.ITEM.originItemType.restMove,
|
||||||
|
itemPath: movePath,
|
||||||
|
actionIndex: actionIndex
|
||||||
|
},
|
||||||
|
targetUuid: targetUuid
|
||||||
|
},
|
||||||
{ parent: parent.system }
|
{ parent: parent.system }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -249,6 +192,182 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
'system.roll': newRoll,
|
'system.roll': newRoll,
|
||||||
'rolls': [parsedRoll]
|
'rolls': [parsedRoll]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.TagTeamRoll });
|
||||||
|
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||||
|
action: socketEvent.Refresh,
|
||||||
|
data: {
|
||||||
|
refreshType: RefreshType.TagTeamRoll
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
import { EncounterCountdowns } from '../ui/countdowns.mjs';
|
import { AdversaryBPPerEncounter } from '../../config/encounterConfig.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 = {
|
||||||
actions: {
|
actions: {
|
||||||
requestSpotlight: this.requestSpotlight,
|
requestSpotlight: this.requestSpotlight,
|
||||||
toggleSpotlight: this.toggleSpotlight,
|
toggleSpotlight: this.toggleSpotlight,
|
||||||
setActionTokens: this.setActionTokens,
|
setActionTokens: this.setActionTokens
|
||||||
openCountdowns: this.openCountdowns
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -22,11 +21,33 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
async _preparePartContext(_partId, context, _options) {
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
async _prepareContext(options) {
|
||||||
|
const context = await super._prepareContext(options);
|
||||||
|
|
||||||
|
await this._prepareTrackerContext(context, options);
|
||||||
|
await this._prepareCombatContext(context, options);
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
async _prepareCombatContext(context, options) {
|
async _prepareCombatContext(context, options) {
|
||||||
await super._prepareCombatContext(context, options);
|
await super._prepareCombatContext(context, options);
|
||||||
|
|
||||||
|
const modifierBP =
|
||||||
|
this.combats
|
||||||
|
.find(x => x.active)
|
||||||
|
?.system?.extendedBattleToggles?.reduce((acc, toggle) => (acc ?? 0) + toggle.category, null) ?? null;
|
||||||
|
const maxBP = CONFIG.DH.ENCOUNTER.BaseBPPerEncounter(context.characters.length) + modifierBP;
|
||||||
|
const currentBP = AdversaryBPPerEncounter(context.adversaries, context.characters);
|
||||||
|
|
||||||
Object.assign(context, {
|
Object.assign(context, {
|
||||||
fear: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear)
|
fear: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear),
|
||||||
|
battlepoints: { max: maxBP, current: currentBP, hasModifierBP: modifierBP !== null }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -35,11 +56,27 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
||||||
|
|
||||||
const adversaries = context.turns?.filter(x => x.isNPC) ?? [];
|
const adversaries = context.turns?.filter(x => x.isNPC) ?? [];
|
||||||
const characters = context.turns?.filter(x => !x.isNPC) ?? [];
|
const characters = context.turns?.filter(x => !x.isNPC) ?? [];
|
||||||
|
const spotlightQueueEnabled = game.settings.get(
|
||||||
|
CONFIG.DH.id,
|
||||||
|
CONFIG.DH.SETTINGS.gameSettings.SpotlightRequestQueue
|
||||||
|
);
|
||||||
|
|
||||||
|
const spotlightRequests = characters
|
||||||
|
?.filter(x => !x.isNPC && spotlightQueueEnabled)
|
||||||
|
.filter(x => x.system.spotlight.requestOrderIndex > 0)
|
||||||
|
.sort((a, b) => {
|
||||||
|
const valueA = a.system.spotlight.requestOrderIndex;
|
||||||
|
const valueB = b.system.spotlight.requestOrderIndex;
|
||||||
|
return valueA - valueB;
|
||||||
|
});
|
||||||
|
|
||||||
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,
|
||||||
characters
|
characters: characters
|
||||||
|
?.filter(x => !x.isNPC)
|
||||||
|
.filter(x => !spotlightQueueEnabled || x.system.spotlight.requestOrderIndex == 0),
|
||||||
|
spotlightRequests
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -90,6 +127,7 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
||||||
resource,
|
resource,
|
||||||
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,
|
||||||
img: await this._getCombatantThumbnail(combatant)
|
img: await this._getCombatantThumbnail(combatant)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -114,7 +152,8 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
||||||
async setCombatantSpotlight(combatantId) {
|
async setCombatantSpotlight(combatantId) {
|
||||||
const update = {
|
const update = {
|
||||||
system: {
|
system: {
|
||||||
'spotlight.requesting': false
|
'spotlight.requesting': false,
|
||||||
|
'spotlight.requestOrderIndex': 0
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const combatant = this.viewed.combatants.get(combatantId);
|
const combatant = this.viewed.combatants.get(combatantId);
|
||||||
|
|
@ -126,7 +165,14 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
||||||
|
|
||||||
if (this.viewed.turn !== toggleTurn) {
|
if (this.viewed.turn !== toggleTurn) {
|
||||||
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
|
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
|
||||||
await updateCountdowns(CONFIG.DH.GENERAL.countdownTypes.spotlight.id);
|
if (combatant.actor?.type === 'character') {
|
||||||
|
await updateCountdowns(
|
||||||
|
CONFIG.DH.GENERAL.countdownProgressionTypes.spotlight.id,
|
||||||
|
CONFIG.DH.GENERAL.countdownProgressionTypes.characterSpotlight.id
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await updateCountdowns(CONFIG.DH.GENERAL.countdownProgressionTypes.spotlight.id);
|
||||||
|
}
|
||||||
|
|
||||||
const autoPoints = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).actionPoints;
|
const autoPoints = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).actionPoints;
|
||||||
if (autoPoints) {
|
if (autoPoints) {
|
||||||
|
|
@ -142,11 +188,15 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
||||||
}
|
}
|
||||||
|
|
||||||
static async requestSpotlight(_, target) {
|
static async requestSpotlight(_, target) {
|
||||||
|
const characters = this.viewed.turns?.filter(x => !x.isNPC) ?? [];
|
||||||
|
const orderValues = characters.map(character => character.system.spotlight.requestOrderIndex);
|
||||||
|
const maxRequestIndex = Math.max(...orderValues);
|
||||||
const { combatantId } = target.closest('[data-combatant-id]')?.dataset ?? {};
|
const { combatantId } = target.closest('[data-combatant-id]')?.dataset ?? {};
|
||||||
const combatant = this.viewed.combatants.get(combatantId);
|
const combatant = this.viewed.combatants.get(combatantId);
|
||||||
await combatant.update({
|
await combatant.update({
|
||||||
'system.spotlight': {
|
'system.spotlight': {
|
||||||
requesting: !combatant.system.spotlight.requesting
|
requesting: !combatant.system.spotlight.requesting,
|
||||||
|
requestOrderIndex: !combatant.system.spotlight.requesting ? maxRequestIndex + 1 : 0
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -168,8 +218,4 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
||||||
await combatant.update({ 'system.actionTokens': newIndex });
|
await combatant.update({ 'system.actionTokens': newIndex });
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
static openCountdowns() {
|
|
||||||
new EncounterCountdowns().open();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
238
module/applications/ui/countdownEdit.mjs
Normal file
238
module/applications/ui/countdownEdit.mjs
Normal file
|
|
@ -0,0 +1,238 @@
|
||||||
|
import { DhCountdown } from '../../data/countdowns.mjs';
|
||||||
|
import { waitForDiceSoNice } from '../../helpers/utils.mjs';
|
||||||
|
import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||||
|
|
||||||
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
||||||
|
export default class CountdownEdit extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.data = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||||
|
this.editingCountdowns = new Set();
|
||||||
|
this.currentEditCountdown = null;
|
||||||
|
this.hideNewCountdowns = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
classes: ['daggerheart', 'dialog', 'dh-style', 'countdown-edit'],
|
||||||
|
tag: 'form',
|
||||||
|
position: { width: 600 },
|
||||||
|
window: {
|
||||||
|
title: 'DAGGERHEART.APPLICATIONS.CountdownEdit.title',
|
||||||
|
icon: 'fa-solid fa-clock-rotate-left'
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
addCountdown: CountdownEdit.#addCountdown,
|
||||||
|
toggleCountdownEdit: CountdownEdit.#toggleCountdownEdit,
|
||||||
|
editCountdownImage: CountdownEdit.#editCountdownImage,
|
||||||
|
editCountdownOwnership: CountdownEdit.#editCountdownOwnership,
|
||||||
|
randomiseCountdownStart: CountdownEdit.#randomiseCountdownStart,
|
||||||
|
removeCountdown: CountdownEdit.#removeCountdown
|
||||||
|
},
|
||||||
|
form: { handler: this.updateData, submitOnChange: true }
|
||||||
|
};
|
||||||
|
|
||||||
|
static PARTS = {
|
||||||
|
countdowns: {
|
||||||
|
template: 'systems/daggerheart/templates/ui/countdown-edit.hbs',
|
||||||
|
scrollable: ['.expanded-view', '.edit-content']
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async _prepareContext(_options) {
|
||||||
|
const context = await super._prepareContext(_options);
|
||||||
|
context.isGM = game.user.isGM;
|
||||||
|
context.ownershipDefaultOptions = CONFIG.DH.GENERAL.basicOwnershiplevels;
|
||||||
|
context.defaultOwnership = this.data.defaultOwnership;
|
||||||
|
context.countdownBaseTypes = CONFIG.DH.GENERAL.countdownBaseTypes;
|
||||||
|
context.countdownProgressionTypes = CONFIG.DH.GENERAL.countdownProgressionTypes;
|
||||||
|
context.countdownLoopingTypes = CONFIG.DH.GENERAL.countdownLoopingTypes;
|
||||||
|
context.hideNewCountdowns = this.hideNewCountdowns;
|
||||||
|
context.countdowns = Object.keys(this.data.countdowns).reduce((acc, key) => {
|
||||||
|
const countdown = this.data.countdowns[key];
|
||||||
|
const isLooping = countdown.progress.looping !== CONFIG.DH.GENERAL.countdownLoopingTypes.noLooping;
|
||||||
|
const loopTooltip = isLooping
|
||||||
|
? countdown.progress.looping === CONFIG.DH.GENERAL.countdownLoopingTypes.increasing.id
|
||||||
|
? 'DAGGERHEART.UI.Countdowns.increasingLoop'
|
||||||
|
: countdown.progress.looping === CONFIG.DH.GENERAL.countdownLoopingTypes.decreasing.id
|
||||||
|
? 'DAGGERHEART.UI.Countdowns.decreasingLoop'
|
||||||
|
: 'DAGGERHEART.UI.Countdowns.loop'
|
||||||
|
: null;
|
||||||
|
const randomizeValid = !new Roll(countdown.progress.startFormula ?? '').isDeterministic;
|
||||||
|
acc[key] = {
|
||||||
|
...countdown,
|
||||||
|
typeName: game.i18n.localize(CONFIG.DH.GENERAL.countdownBaseTypes[countdown.type].label),
|
||||||
|
progress: {
|
||||||
|
...countdown.progress,
|
||||||
|
typeName: game.i18n.localize(
|
||||||
|
CONFIG.DH.GENERAL.countdownProgressionTypes[countdown.progress.type].label
|
||||||
|
)
|
||||||
|
},
|
||||||
|
editing: this.editingCountdowns.has(key),
|
||||||
|
randomizeValid,
|
||||||
|
loopTooltip
|
||||||
|
};
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
async _postRender(_context, _options) {
|
||||||
|
if (this.currentEditCountdown) {
|
||||||
|
setTimeout(() => {
|
||||||
|
const input = this.element.querySelector(
|
||||||
|
`.countdown-edit-container[data-id="${this.currentEditCountdown}"] input`
|
||||||
|
);
|
||||||
|
if (input) {
|
||||||
|
input.select();
|
||||||
|
this.currentEditCountdown = null;
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
canPerformEdit() {
|
||||||
|
if (game.user.isGM) return true;
|
||||||
|
|
||||||
|
if (!game.users.activeGM) {
|
||||||
|
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.gmRequired'));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateSetting(update) {
|
||||||
|
const noGM = !game.users.find(x => x.isGM && x.active);
|
||||||
|
if (noGM) {
|
||||||
|
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.gmRequired'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.data.updateSource(update);
|
||||||
|
await emitAsGM(GMUpdateEvent.UpdateCountdowns, this.gmSetSetting.bind(this.data), this.data, null, {
|
||||||
|
refreshType: RefreshType.Countdown
|
||||||
|
});
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
static async updateData(_event, _, formData) {
|
||||||
|
const { hideNewCountdowns, ...settingsData } = foundry.utils.expandObject(formData.object);
|
||||||
|
|
||||||
|
// Sync current and max if max is changing and they were equal before
|
||||||
|
for (const [id, countdown] of Object.entries(settingsData.countdowns ?? {})) {
|
||||||
|
const existing = this.data.countdowns[id];
|
||||||
|
countdown.progress.current = this.getMatchingCurrentValue(
|
||||||
|
existing,
|
||||||
|
countdown.progress.start,
|
||||||
|
countdown.progress.current
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.hideNewCountdowns = hideNewCountdowns;
|
||||||
|
this.updateSetting(settingsData);
|
||||||
|
}
|
||||||
|
|
||||||
|
getMatchingCurrentValue(oldCount, newStart, newCurrent) {
|
||||||
|
const wasEqual = oldCount && oldCount.progress.current === oldCount.progress.start;
|
||||||
|
if (wasEqual && newStart !== oldCount.progress.start) {
|
||||||
|
return newStart;
|
||||||
|
} else {
|
||||||
|
return Math.min(newCurrent, newStart);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async gmSetSetting(data) {
|
||||||
|
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, data),
|
||||||
|
game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||||
|
action: socketEvent.Refresh,
|
||||||
|
data: { refreshType: RefreshType.Countdown }
|
||||||
|
});
|
||||||
|
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Countdown });
|
||||||
|
}
|
||||||
|
|
||||||
|
static #addCountdown() {
|
||||||
|
const id = foundry.utils.randomID();
|
||||||
|
this.editingCountdowns.add(id);
|
||||||
|
this.currentEditCountdown = id;
|
||||||
|
this.updateSetting({
|
||||||
|
[`countdowns.${id}`]: DhCountdown.defaultCountdown(null, this.hideNewCountdowns)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static #editCountdownImage(_, target) {
|
||||||
|
const countdown = this.data.countdowns[target.id];
|
||||||
|
const fp = new foundry.applications.apps.FilePicker.implementation({
|
||||||
|
current: countdown.img,
|
||||||
|
type: 'image',
|
||||||
|
callback: async path => this.updateSetting({ [`countdowns.${target.id}.img`]: path }),
|
||||||
|
top: this.position.top + 40,
|
||||||
|
left: this.position.left + 10
|
||||||
|
});
|
||||||
|
return fp.browse();
|
||||||
|
}
|
||||||
|
|
||||||
|
static #toggleCountdownEdit(_, button) {
|
||||||
|
const { countdownId } = button.dataset;
|
||||||
|
|
||||||
|
const isEditing = this.editingCountdowns.has(countdownId);
|
||||||
|
if (isEditing) this.editingCountdowns.delete(countdownId);
|
||||||
|
else {
|
||||||
|
this.editingCountdowns.add(countdownId);
|
||||||
|
this.currentEditCountdown = countdownId;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #editCountdownOwnership(_, button) {
|
||||||
|
const countdown = this.data.countdowns[button.dataset.countdownId];
|
||||||
|
const data = await game.system.api.applications.dialogs.OwnershipSelection.configure(
|
||||||
|
countdown.name,
|
||||||
|
countdown.ownership,
|
||||||
|
this.data.defaultOwnership
|
||||||
|
);
|
||||||
|
if (!data) return;
|
||||||
|
|
||||||
|
this.updateSetting({ [`countdowns.${button.dataset.countdownId}`]: data });
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #randomiseCountdownStart(_, button) {
|
||||||
|
const countdown = this.data.countdowns[button.dataset.countdownId];
|
||||||
|
const roll = await new Roll(countdown.progress.startFormula).roll();
|
||||||
|
const message = await roll.toMessage({ title: 'Countdown' });
|
||||||
|
|
||||||
|
await waitForDiceSoNice(message);
|
||||||
|
await this.updateSetting({
|
||||||
|
[`countdowns.${button.dataset.countdownId}.progress`]: {
|
||||||
|
start: roll.total,
|
||||||
|
current: this.getMatchingCurrentValue(countdown, roll.total, countdown.progress.current)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #removeCountdown(event, button) {
|
||||||
|
const { countdownId } = button.dataset;
|
||||||
|
|
||||||
|
if (!event.shiftKey) {
|
||||||
|
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||||
|
window: {
|
||||||
|
title: game.i18n.localize('DAGGERHEART.APPLICATIONS.CountdownEdit.removeCountdownTitle')
|
||||||
|
},
|
||||||
|
content: game.i18n.format('DAGGERHEART.APPLICATIONS.CountdownEdit.removeCountdownText', {
|
||||||
|
name: this.data.countdowns[countdownId].name
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if (!confirmed) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.editingCountdowns.has(countdownId)) this.editingCountdowns.delete(countdownId);
|
||||||
|
this.updateSetting({ [`countdowns.-=${countdownId}`]: null });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,355 +1,295 @@
|
||||||
import { GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
import { waitForDiceSoNice } from '../../helpers/utils.mjs';
|
||||||
import constructHTMLButton from '../../helpers/utils.mjs';
|
import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||||
import OwnershipSelection from '../dialogs/ownershipSelection.mjs';
|
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
||||||
class Countdowns extends HandlebarsApplicationMixin(ApplicationV2) {
|
/**
|
||||||
constructor(basePath) {
|
* A UI element which displays the countdowns in this world.
|
||||||
super({});
|
*
|
||||||
|
* @extends ApplicationV2
|
||||||
|
* @mixes HandlebarsApplication
|
||||||
|
*/
|
||||||
|
|
||||||
this.basePath = basePath;
|
export default class DhCountdowns extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
}
|
constructor(options = {}) {
|
||||||
|
super(options);
|
||||||
get title() {
|
|
||||||
return game.i18n.format('DAGGERHEART.APPLICATIONS.Countdown.title', {
|
this.setupHooks();
|
||||||
type: game.i18n.localize(`DAGGERHEART.APPLICATIONS.Countdown.types.${this.basePath}`)
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
classes: ['daggerheart', 'dh-style', 'countdown'],
|
id: 'countdowns',
|
||||||
tag: 'form',
|
tag: 'div',
|
||||||
position: { width: 740, height: 700 },
|
classes: ['daggerheart', 'dh-style', 'countdowns', 'faded-ui'],
|
||||||
window: {
|
window: {
|
||||||
|
icon: 'fa-solid fa-clock-rotate-left',
|
||||||
frame: true,
|
frame: true,
|
||||||
title: 'Countdowns',
|
title: 'DAGGERHEART.UI.Countdowns.title',
|
||||||
resizable: true,
|
positioned: false,
|
||||||
|
resizable: false,
|
||||||
minimizable: false
|
minimizable: false
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
addCountdown: this.addCountdown,
|
toggleViewMode: DhCountdowns.#toggleViewMode,
|
||||||
removeCountdown: this.removeCountdown,
|
editCountdowns: DhCountdowns.#editCountdowns,
|
||||||
editImage: this.onEditImage,
|
loopCountdown: DhCountdowns.#loopCountdown,
|
||||||
openOwnership: this.openOwnership,
|
decreaseCountdown: (_, target) => this.editCountdown(false, target),
|
||||||
openCountdownOwnership: this.openCountdownOwnership,
|
increaseCountdown: (_, target) => this.editCountdown(true, target)
|
||||||
toggleSimpleView: this.toggleSimpleView
|
|
||||||
},
|
},
|
||||||
form: { handler: this.updateData, submitOnChange: true }
|
position: {
|
||||||
|
width: 400,
|
||||||
|
height: 222,
|
||||||
|
top: 50
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** @override */
|
||||||
static PARTS = {
|
static PARTS = {
|
||||||
countdowns: {
|
resources: {
|
||||||
template: 'systems/daggerheart/templates/ui/countdowns.hbs',
|
root: true,
|
||||||
scrollable: ['.expanded-view']
|
template: 'systems/daggerheart/templates/ui/countdowns.hbs'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
_attachPartListeners(partId, htmlElement, options) {
|
get element() {
|
||||||
super._attachPartListeners(partId, htmlElement, options);
|
return document.body.querySelector('.daggerheart.dh-style.countdowns');
|
||||||
|
|
||||||
htmlElement.querySelectorAll('.mini-countdown-container').forEach(element => {
|
|
||||||
element.addEventListener('click', event => this.updateCountdownValue.bind(this)(event, false));
|
|
||||||
element.addEventListener('contextmenu', event => this.updateCountdownValue.bind(this)(event, true));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async _preFirstRender(context, options) {
|
|
||||||
options.position =
|
|
||||||
game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS[`${this.basePath}Countdown`].position) ??
|
|
||||||
Countdowns.DEFAULT_OPTIONS.position;
|
|
||||||
|
|
||||||
const viewSetting =
|
|
||||||
game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS[`${this.basePath}Countdown`].simple) ?? !game.user.isGM;
|
|
||||||
this.simpleView =
|
|
||||||
game.user.isGM || !this.testUserPermission(CONST.DOCUMENT_OWNERSHIP_LEVELS.OBSERVER) ? viewSetting : true;
|
|
||||||
context.simple = this.simpleView;
|
|
||||||
}
|
|
||||||
|
|
||||||
_onPosition(position) {
|
|
||||||
game.user.setFlag(CONFIG.DH.id, CONFIG.DH.FLAGS[`${this.basePath}Countdown`].position, position);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**@inheritdoc */
|
||||||
async _renderFrame(options) {
|
async _renderFrame(options) {
|
||||||
const frame = await super._renderFrame(options);
|
const frame = await super._renderFrame(options);
|
||||||
|
|
||||||
if (this.testUserPermission(CONST.DOCUMENT_OWNERSHIP_LEVELS.OBSERVER)) {
|
const iconOnly =
|
||||||
const button = constructHTMLButton({
|
game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.countdownMode) ===
|
||||||
label: '',
|
CONFIG.DH.GENERAL.countdownAppMode.iconOnly;
|
||||||
classes: ['header-control', 'icon', 'fa-solid', 'fa-wrench'],
|
if (iconOnly) frame.classList.add('icon-only');
|
||||||
dataset: { action: 'toggleSimpleView', tooltip: 'DAGGERHEART.APPLICATIONS.Countdown.toggleSimple' }
|
else frame.classList.remove('icon-only');
|
||||||
});
|
|
||||||
this.window.controls.after(button);
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
testUserPermission(level, exact, altSettings) {
|
/** Returns countdown data filtered by ownership */
|
||||||
if (game.user.isGM) return true;
|
#getCountdowns() {
|
||||||
|
const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||||
const settings =
|
const values = Object.entries(setting.countdowns).map(([key, countdown]) => ({
|
||||||
altSettings ?? game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns)[this.basePath];
|
key,
|
||||||
const defaultAllowed = exact ? settings.ownership.default === level : settings.ownership.default >= level;
|
countdown,
|
||||||
const userAllowed = exact
|
ownership: DhCountdowns.#getPlayerOwnership(game.user, setting, countdown)
|
||||||
? settings.playerOwnership[game.user.id]?.value === level
|
}));
|
||||||
: settings.playerOwnership[game.user.id]?.value >= level;
|
return values.filter(v => v.ownership !== CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE);
|
||||||
return defaultAllowed || userAllowed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async _prepareContext(_options) {
|
/** @override */
|
||||||
const context = await super._prepareContext(_options);
|
async _prepareContext(options) {
|
||||||
const countdownData = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns)[
|
const context = await super._prepareContext(options);
|
||||||
this.basePath
|
|
||||||
];
|
|
||||||
|
|
||||||
context.isGM = game.user.isGM;
|
context.isGM = game.user.isGM;
|
||||||
context.base = this.basePath;
|
|
||||||
|
|
||||||
context.canCreate = this.testUserPermission(CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER, true);
|
context.iconOnly =
|
||||||
context.source = {
|
game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.countdownMode) ===
|
||||||
...countdownData,
|
CONFIG.DH.GENERAL.countdownAppMode.iconOnly;
|
||||||
countdowns: Object.keys(countdownData.countdowns).reduce((acc, key) => {
|
const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||||
const countdown = countdownData.countdowns[key];
|
context.countdowns = this.#getCountdowns().reduce((acc, { key, countdown, ownership }) => {
|
||||||
|
const playersWithAccess = game.users.reduce((acc, user) => {
|
||||||
|
const ownership = DhCountdowns.#getPlayerOwnership(user, setting, countdown);
|
||||||
|
if (!user.isGM && ownership && ownership !== CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE) {
|
||||||
|
acc.push(user);
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
const nonGmPlayers = game.users.filter(x => !x.isGM);
|
||||||
|
|
||||||
|
const countdownEditable = game.user.isGM || ownership === CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER;
|
||||||
|
const isLooping = countdown.progress.looping !== CONFIG.DH.GENERAL.countdownLoopingTypes.noLooping;
|
||||||
|
const loopTooltip = isLooping
|
||||||
|
? countdown.progress.looping === CONFIG.DH.GENERAL.countdownLoopingTypes.increasing.id
|
||||||
|
? 'DAGGERHEART.UI.Countdowns.increasingLoop'
|
||||||
|
: countdown.progress.looping === CONFIG.DH.GENERAL.countdownLoopingTypes.decreasing.id
|
||||||
|
? 'DAGGERHEART.UI.Countdowns.decreasingLoop'
|
||||||
|
: 'DAGGERHEART.UI.Countdowns.loop'
|
||||||
|
: null;
|
||||||
|
const loopDisabled =
|
||||||
|
!countdownEditable ||
|
||||||
|
(isLooping && (countdown.progress.current > 0 || countdown.progress.start === '0'));
|
||||||
|
|
||||||
if (this.testUserPermission(CONST.DOCUMENT_OWNERSHIP_LEVELS.LIMITED, false, countdown)) {
|
|
||||||
acc[key] = {
|
acc[key] = {
|
||||||
...countdown,
|
...countdown,
|
||||||
canEdit: this.testUserPermission(CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER, true, countdown)
|
editable: countdownEditable,
|
||||||
|
noPlayerAccess: nonGmPlayers.length && playersWithAccess.length === 0,
|
||||||
|
shouldLoop: isLooping && countdown.progress.current === 0 && countdown.progress.start > 0,
|
||||||
|
loopDisabled: isLooping ? loopDisabled : null,
|
||||||
|
loopTooltip: isLooping && game.i18n.localize(loopTooltip)
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {})
|
}, {});
|
||||||
};
|
|
||||||
context.systemFields = countdownData.schema.fields;
|
|
||||||
context.countdownFields = context.systemFields.countdowns.element.fields;
|
|
||||||
context.simple = this.simpleView;
|
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async updateData(event, _, formData) {
|
static #getPlayerOwnership(user, setting, countdown) {
|
||||||
const data = foundry.utils.expandObject(formData.object);
|
const playerOwnership = countdown.ownership[user.id];
|
||||||
const newSetting = foundry.utils.mergeObject(
|
return playerOwnership === undefined || playerOwnership === CONST.DOCUMENT_OWNERSHIP_LEVELS.INHERIT
|
||||||
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns).toObject(),
|
? setting.defaultOwnership
|
||||||
data
|
: playerOwnership;
|
||||||
);
|
}
|
||||||
|
|
||||||
if (game.user.isGM) {
|
cooldownRefresh = ({ refreshType }) => {
|
||||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, newSetting);
|
if (refreshType === RefreshType.Countdown) this.render();
|
||||||
|
};
|
||||||
|
|
||||||
|
static canPerformEdit() {
|
||||||
|
if (game.user.isGM) return true;
|
||||||
|
|
||||||
|
const noGM = !game.users.find(x => x.isGM && x.active);
|
||||||
|
if (noGM) {
|
||||||
|
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.gmRequired'));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #toggleViewMode() {
|
||||||
|
const currentMode = game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.countdownMode);
|
||||||
|
const appMode = CONFIG.DH.GENERAL.countdownAppMode;
|
||||||
|
const newMode = currentMode === appMode.textIcon ? appMode.iconOnly : appMode.textIcon;
|
||||||
|
await game.user.setFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.countdownMode, newMode);
|
||||||
|
|
||||||
|
if (newMode === appMode.iconOnly) this.element.classList.add('icon-only');
|
||||||
|
else this.element.classList.remove('icon-only');
|
||||||
this.render();
|
this.render();
|
||||||
} else {
|
}
|
||||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
|
||||||
action: socketEvent.GMUpdate,
|
static async #editCountdowns() {
|
||||||
data: {
|
new game.system.api.applications.ui.CountdownEdit().render(true);
|
||||||
action: GMUpdateEvent.UpdateSetting,
|
}
|
||||||
uuid: CONFIG.DH.SETTINGS.gameSettings.Countdowns,
|
|
||||||
update: newSetting
|
static async #loopCountdown(_, target) {
|
||||||
|
if (!DhCountdowns.canPerformEdit()) return;
|
||||||
|
|
||||||
|
const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||||
|
const countdown = settings.countdowns[target.id];
|
||||||
|
|
||||||
|
let progressMax = countdown.progress.start;
|
||||||
|
let message = null;
|
||||||
|
if (countdown.progress.startFormula) {
|
||||||
|
const roll = await new Roll(countdown.progress.startFormula).evaluate();
|
||||||
|
progressMax = roll.total;
|
||||||
|
message = await roll.toMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
const newMax =
|
||||||
|
countdown.progress.looping === CONFIG.DH.GENERAL.countdownLoopingTypes.increasing.id
|
||||||
|
? Number(progressMax) + 1
|
||||||
|
: countdown.progress.looping === CONFIG.DH.GENERAL.countdownLoopingTypes.decreasing.id
|
||||||
|
? Math.max(Number(progressMax) - 1, 0)
|
||||||
|
: progressMax;
|
||||||
|
|
||||||
|
await waitForDiceSoNice(message);
|
||||||
|
await settings.updateSource({
|
||||||
|
[`countdowns.${target.id}.progress`]: {
|
||||||
|
current: newMax,
|
||||||
|
start: newMax
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
await emitAsGM(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, {
|
||||||
|
refreshType: RefreshType.Countdown
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateSetting(update) {
|
static async editCountdown(increase, target) {
|
||||||
if (game.user.isGM) {
|
if (!DhCountdowns.canPerformEdit()) return;
|
||||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, update);
|
|
||||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||||
|
const countdown = settings.countdowns[target.id];
|
||||||
|
const newCurrent = increase
|
||||||
|
? Math.min(countdown.progress.current + 1, countdown.progress.start)
|
||||||
|
: Math.max(countdown.progress.current - 1, 0);
|
||||||
|
await settings.updateSource({ [`countdowns.${target.id}.progress.current`]: newCurrent });
|
||||||
|
await emitAsGM(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, {
|
||||||
|
refreshType: RefreshType.Countdown
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static async gmSetSetting(data) {
|
||||||
|
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, data),
|
||||||
|
game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||||
action: socketEvent.Refresh,
|
action: socketEvent.Refresh,
|
||||||
data: {
|
data: { refreshType: RefreshType.Countdown }
|
||||||
refreshType: RefreshType.Countdown,
|
|
||||||
application: `${this.basePath}-countdowns`
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Countdown });
|
||||||
this.render();
|
|
||||||
} else {
|
|
||||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
|
||||||
action: socketEvent.GMUpdate,
|
|
||||||
data: {
|
|
||||||
action: GMUpdateEvent.UpdateSetting,
|
|
||||||
uuid: CONFIG.DH.SETTINGS.gameSettings.Countdowns,
|
|
||||||
update: update,
|
|
||||||
refresh: { refreshType: RefreshType.Countdown, application: `${this.basePath}-countdowns` }
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static onEditImage(_, target) {
|
setupHooks() {
|
||||||
const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns)[this.basePath];
|
Hooks.on(socketEvent.Refresh, this.cooldownRefresh.bind());
|
||||||
const current = setting.countdowns[target.dataset.countdown].img;
|
|
||||||
const fp = new foundry.applications.apps.FilePicker.implementation({
|
|
||||||
current,
|
|
||||||
type: 'image',
|
|
||||||
callback: async path => this.updateImage.bind(this)(path, target.dataset.countdown),
|
|
||||||
top: this.position.top + 40,
|
|
||||||
left: this.position.left + 10
|
|
||||||
});
|
|
||||||
return fp.browse();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateImage(path, countdown) {
|
async close(options) {
|
||||||
const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
/* Opt out of Foundry's standard behavior of closing all application windows marked as UI when Escape is pressed */
|
||||||
await setting.updateSource({
|
if (options.closeKey) return;
|
||||||
[`${this.basePath}.countdowns.${countdown}.img`]: path
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.updateSetting(setting);
|
Hooks.off(socketEvent.Refresh, this.cooldownRefresh);
|
||||||
|
return super.close(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
static openOwnership(_, target) {
|
/**
|
||||||
new Promise((resolve, reject) => {
|
* Sends updates of the countdowns to the GM player. Since this is asynchronous, be sure to
|
||||||
const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns)[this.basePath];
|
* update all the countdowns at the same time.
|
||||||
const ownership = { default: setting.ownership.default, players: setting.playerOwnership };
|
*
|
||||||
new OwnershipSelection(resolve, reject, this.title, ownership).render(true);
|
* @param {...any} progressTypes Countdowns to be updated
|
||||||
}).then(async ownership => {
|
*/
|
||||||
const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
static async updateCountdowns(...progressTypes) {
|
||||||
await setting.updateSource({
|
const { countdownAutomation } = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation);
|
||||||
[`${this.basePath}.ownership`]: ownership
|
if (!countdownAutomation) return;
|
||||||
});
|
|
||||||
|
|
||||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, setting.toObject());
|
|
||||||
this.render();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static openCountdownOwnership(_, target) {
|
|
||||||
const countdownId = target.dataset.countdown;
|
|
||||||
new Promise((resolve, reject) => {
|
|
||||||
const countdown = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns)[this.basePath]
|
|
||||||
.countdowns[countdownId];
|
|
||||||
const ownership = { default: countdown.ownership.default, players: countdown.playerOwnership };
|
|
||||||
new OwnershipSelection(resolve, reject, countdown.name, ownership).render(true);
|
|
||||||
}).then(async ownership => {
|
|
||||||
const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
|
||||||
await setting.updateSource({
|
|
||||||
[`${this.basePath}.countdowns.${countdownId}.ownership`]: ownership
|
|
||||||
});
|
|
||||||
|
|
||||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, setting);
|
|
||||||
this.render();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static async toggleSimpleView() {
|
|
||||||
this.simpleView = !this.simpleView;
|
|
||||||
await game.user.setFlag(CONFIG.DH.id, CONFIG.DH.FLAGS[`${this.basePath}Countdown`].simple, this.simpleView);
|
|
||||||
this.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
async updateCountdownValue(event, increase) {
|
|
||||||
const countdownSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
const countdownSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||||
const countdown = countdownSetting[this.basePath].countdowns[event.currentTarget.dataset.countdown];
|
const updatedCountdowns = Object.keys(countdownSetting.countdowns).reduce((acc, key) => {
|
||||||
|
const countdown = countdownSetting.countdowns[key];
|
||||||
if (!this.testUserPermission(CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER)) {
|
if (progressTypes.indexOf(countdown.progress.type) !== -1 && countdown.progress.current > 0) {
|
||||||
return;
|
acc.push(key);
|
||||||
}
|
|
||||||
|
|
||||||
const currentValue = countdown.progress.current;
|
|
||||||
|
|
||||||
if (increase && currentValue === countdown.progress.max) return;
|
|
||||||
if (!increase && currentValue === 0) return;
|
|
||||||
|
|
||||||
await countdownSetting.updateSource({
|
|
||||||
[`${this.basePath}.countdowns.${event.currentTarget.dataset.countdown}.progress.current`]: increase
|
|
||||||
? currentValue + 1
|
|
||||||
: currentValue - 1
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.updateSetting(countdownSetting.toObject());
|
|
||||||
}
|
|
||||||
|
|
||||||
static async addCountdown() {
|
|
||||||
const countdownSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
|
||||||
await countdownSetting.updateSource({
|
|
||||||
[`${this.basePath}.countdowns.${foundry.utils.randomID()}`]: {
|
|
||||||
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Countdown.newCountdown'),
|
|
||||||
ownership: game.user.isGM
|
|
||||||
? {}
|
|
||||||
: {
|
|
||||||
players: {
|
|
||||||
[game.user.id]: { type: CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.updateSetting(countdownSetting.toObject());
|
|
||||||
}
|
|
||||||
|
|
||||||
static async removeCountdown(_, target) {
|
|
||||||
const countdownSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
|
||||||
const countdownName = countdownSetting[this.basePath].countdowns[target.dataset.countdown].name;
|
|
||||||
|
|
||||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
|
||||||
window: {
|
|
||||||
title: game.i18n.localize('DAGGERHEART.APPLICATIONS.Countdown.removeCountdownTitle')
|
|
||||||
},
|
|
||||||
content: game.i18n.format('DAGGERHEART.APPLICATIONS.Countdown.removeCountdownText', { name: countdownName })
|
|
||||||
});
|
|
||||||
if (!confirmed) return;
|
|
||||||
|
|
||||||
await countdownSetting.updateSource({ [`${this.basePath}.countdowns.-=${target.dataset.countdown}`]: null });
|
|
||||||
|
|
||||||
await this.updateSetting(countdownSetting.toObject());
|
|
||||||
}
|
|
||||||
|
|
||||||
async open() {
|
|
||||||
await this.render(true);
|
|
||||||
if (
|
|
||||||
Object.keys(
|
|
||||||
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns)[this.basePath].countdowns
|
|
||||||
).length > 0
|
|
||||||
) {
|
|
||||||
this.minimize();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class NarrativeCountdowns extends Countdowns {
|
|
||||||
constructor() {
|
|
||||||
super('narrative');
|
|
||||||
}
|
|
||||||
|
|
||||||
static DEFAULT_OPTIONS = {
|
|
||||||
id: 'narrative-countdowns'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export class EncounterCountdowns extends Countdowns {
|
|
||||||
constructor() {
|
|
||||||
super('encounter');
|
|
||||||
}
|
|
||||||
|
|
||||||
static DEFAULT_OPTIONS = {
|
|
||||||
id: 'encounter-countdowns'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateCountdowns(progressType) {
|
|
||||||
const countdownSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
|
||||||
const update = Object.keys(countdownSetting).reduce((update, typeKey) => {
|
|
||||||
return foundry.utils.mergeObject(
|
|
||||||
update,
|
|
||||||
Object.keys(countdownSetting[typeKey].countdowns).reduce((acc, countdownKey) => {
|
|
||||||
const countdown = countdownSetting[typeKey].countdowns[countdownKey];
|
|
||||||
if (countdown.progress.current > 0 && countdown.progress.type.value === progressType) {
|
|
||||||
acc[`${typeKey}.countdowns.${countdownKey}.progress.current`] = countdown.progress.current - 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {})
|
}, []);
|
||||||
);
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
await countdownSetting.updateSource(update);
|
const countdownData = countdownSetting.toObject();
|
||||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, countdownSetting);
|
const settings = {
|
||||||
|
...countdownData,
|
||||||
const data = { refreshType: RefreshType.Countdown };
|
countdowns: Object.keys(countdownData.countdowns).reduce((acc, key) => {
|
||||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
const countdown = foundry.utils.deepClone(countdownData.countdowns[key]);
|
||||||
action: socketEvent.Refresh,
|
if (updatedCountdowns.includes(key)) {
|
||||||
data
|
countdown.progress.current -= 1;
|
||||||
});
|
}
|
||||||
Hooks.callAll(socketEvent.Refresh, data);
|
|
||||||
|
acc[key] = countdown;
|
||||||
|
return acc;
|
||||||
|
}, {})
|
||||||
|
};
|
||||||
|
await emitAsGM(GMUpdateEvent.UpdateCountdowns,
|
||||||
|
DhCountdowns.gmSetSetting.bind(settings),
|
||||||
|
settings, null, {
|
||||||
|
refreshType: RefreshType.Countdown
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async _onRender(context, options) {
|
||||||
|
await super._onRender(context, options);
|
||||||
|
this.element.hidden = !game.user.isGM && this.#getCountdowns().length === 0;
|
||||||
|
if (options?.force) {
|
||||||
|
document.getElementById('ui-right-column-1')?.appendChild(this.element);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
117
module/applications/ui/effectsDisplay.mjs
Normal file
117
module/applications/ui/effectsDisplay.mjs
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
import { RefreshType } from '../../systemRegistration/socket.mjs';
|
||||||
|
|
||||||
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A UI element which displays the Active Effects on a selected token.
|
||||||
|
*
|
||||||
|
* @extends ApplicationV2
|
||||||
|
* @mixes HandlebarsApplication
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default class DhEffectsDisplay extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
|
constructor(options = {}) {
|
||||||
|
super(options);
|
||||||
|
|
||||||
|
this.setupHooks();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
id: 'effects-display',
|
||||||
|
tag: 'div',
|
||||||
|
classes: ['daggerheart', 'dh-style', 'effects-display'],
|
||||||
|
window: {
|
||||||
|
frame: false,
|
||||||
|
positioned: false,
|
||||||
|
resizable: false,
|
||||||
|
minimizable: false
|
||||||
|
},
|
||||||
|
actions: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static PARTS = {
|
||||||
|
resources: {
|
||||||
|
root: true,
|
||||||
|
template: 'systems/daggerheart/templates/ui/effects-display.hbs'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
get element() {
|
||||||
|
return document.body.querySelector('.daggerheart.dh-style.effects-display');
|
||||||
|
}
|
||||||
|
|
||||||
|
get hidden() {
|
||||||
|
return this.element.classList.contains('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
_attachPartListeners(partId, htmlElement, options) {
|
||||||
|
super._attachPartListeners(partId, htmlElement, options);
|
||||||
|
|
||||||
|
if (this.element) {
|
||||||
|
this.element.querySelectorAll('.effect-container a').forEach(element => {
|
||||||
|
element.addEventListener('contextmenu', this.removeEffect.bind(this));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
async _prepareContext(options) {
|
||||||
|
const context = await super._prepareContext(options);
|
||||||
|
context.effects = DhEffectsDisplay.getTokenEffects();
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
static getTokenEffects = token => {
|
||||||
|
const actor = token
|
||||||
|
? token.actor
|
||||||
|
: canvas.tokens.controlled.length === 0
|
||||||
|
? !game.user.isGM
|
||||||
|
? game.user.character
|
||||||
|
: null
|
||||||
|
: canvas.tokens.controlled[0].actor;
|
||||||
|
return actor?.getActiveEffects() ?? [];
|
||||||
|
};
|
||||||
|
|
||||||
|
toggleHidden(token, focused) {
|
||||||
|
const effects = DhEffectsDisplay.getTokenEffects(focused ? token : null);
|
||||||
|
this.element.hidden = effects.length === 0;
|
||||||
|
|
||||||
|
Hooks.callAll(CONFIG.DH.HOOKS.effectDisplayToggle, this.element.hidden, token);
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeEffect(event) {
|
||||||
|
const element = event.target.closest('.effect-container');
|
||||||
|
const effects = DhEffectsDisplay.getTokenEffects();
|
||||||
|
const effect = effects.find(x => x.id === element.dataset.effectId);
|
||||||
|
await effect.delete();
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
setupHooks() {
|
||||||
|
Hooks.on('controlToken', this.toggleHidden.bind(this));
|
||||||
|
Hooks.on(RefreshType.EffectsDisplay, this.toggleHidden.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
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('controlToken', this.toggleHidden);
|
||||||
|
Hooks.off(RefreshType.EffectsDisplay, this.toggleHidden);
|
||||||
|
return super.close(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _onRender(context, options) {
|
||||||
|
await super._onRender(context, options);
|
||||||
|
|
||||||
|
this.element.hidden = context.effects.length === 0;
|
||||||
|
if (options?.force) {
|
||||||
|
document.getElementById('ui-right-column-1')?.appendChild(this.element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { emitAsGM, GMUpdateEvent, socketEvent } from "../../systemRegistration/socket.mjs";
|
import { emitAsGM, GMUpdateEvent } from '../../systemRegistration/socket.mjs';
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
||||||
|
|
@ -78,7 +78,7 @@ export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
async _preRender(context, options) {
|
async _preRender(context, options) {
|
||||||
if (this.currentFear > this.maxFear)
|
if (this.currentFear > this.maxFear && game.user.isGM)
|
||||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear, this.maxFear);
|
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear, this.maxFear);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -106,19 +106,10 @@ export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateFear(value) {
|
async updateFear(value) {
|
||||||
return emitAsGM(GMUpdateEvent.UpdateFear, game.settings.set.bind(game.settings, CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear), value);
|
return emitAsGM(
|
||||||
/* if(!game.user.isGM)
|
GMUpdateEvent.UpdateFear,
|
||||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
game.settings.set.bind(game.settings, CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear),
|
||||||
action: socketEvent.GMUpdate,
|
value
|
||||||
data: {
|
);
|
||||||
action: GMUpdateEvent.UpdateFear,
|
|
||||||
update: value
|
|
||||||
}
|
|
||||||
});
|
|
||||||
else
|
|
||||||
game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear, value); */
|
|
||||||
/* if (!game.user.isGM) return;
|
|
||||||
value = Math.max(0, Math.min(this.maxFear, value));
|
|
||||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear, value); */
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,16 +15,14 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
this.fieldFilter = [];
|
this.fieldFilter = [];
|
||||||
this.selectedMenu = { path: [], data: null };
|
this.selectedMenu = { path: [], data: null };
|
||||||
this.config = CONFIG.DH.ITEMBROWSER.compendiumConfig;
|
this.config = CONFIG.DH.ITEMBROWSER.compendiumConfig;
|
||||||
this.presets = options.presets;
|
this.presets = {};
|
||||||
|
this.compendiumBrowserTypeKey = 'compendiumBrowserDefault';
|
||||||
if (this.presets?.compendium && this.presets?.folder)
|
|
||||||
ItemBrowser.selectFolder.call(this, null, null, this.presets.compendium, this.presets.folder);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
id: 'itemBrowser',
|
id: 'itemBrowser',
|
||||||
classes: ['daggerheart', 'dh-style', 'dialog', 'compendium-browser'],
|
classes: ['daggerheart', 'dh-style', 'dialog', 'compendium-browser', 'daggerheart-loader'],
|
||||||
tag: 'div',
|
tag: 'div',
|
||||||
window: {
|
window: {
|
||||||
frame: true,
|
frame: true,
|
||||||
|
|
@ -84,17 +82,29 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @inheritDoc */
|
|
||||||
async _preFirstRender(context, options) {
|
|
||||||
if (context.presets?.render?.noFolder || context.presets?.render?.lite) options.position.width = 600;
|
|
||||||
|
|
||||||
await super._preFirstRender(context, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
async _preRender(context, options) {
|
async _preRender(context, options) {
|
||||||
if (context.presets?.render?.noFolder || context.presets?.render?.lite)
|
this.presets = options.presets ?? {};
|
||||||
options.parts.splice(options.parts.indexOf('sidebar'), 1);
|
const noFolder = this.presets?.render?.noFolder;
|
||||||
|
if (noFolder === true) {
|
||||||
|
this.compendiumBrowserTypeKey = 'compendiumBrowserNoFolder';
|
||||||
|
}
|
||||||
|
const lite = this.presets?.render?.lite;
|
||||||
|
if (lite === true) {
|
||||||
|
this.compendiumBrowserTypeKey = 'compendiumBrowserLite';
|
||||||
|
}
|
||||||
|
const userPresetPosition = game.user.getFlag(
|
||||||
|
CONFIG.DH.id,
|
||||||
|
CONFIG.DH.FLAGS[`${this.compendiumBrowserTypeKey}`].position
|
||||||
|
);
|
||||||
|
|
||||||
|
options.position = userPresetPosition ?? ItemBrowser.DEFAULT_OPTIONS.position;
|
||||||
|
|
||||||
|
if (!userPresetPosition) {
|
||||||
|
const width = noFolder === true || lite === true ? 600 : 850;
|
||||||
|
if (this.rendered) this.setPosition({ width });
|
||||||
|
else options.position.width = width;
|
||||||
|
}
|
||||||
|
|
||||||
await super._preRender(context, options);
|
await super._preRender(context, options);
|
||||||
}
|
}
|
||||||
|
|
@ -103,32 +113,35 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
async _onRender(context, options) {
|
async _onRender(context, options) {
|
||||||
await super._onRender(context, options);
|
await super._onRender(context, options);
|
||||||
|
|
||||||
this._createSearchFilter();
|
this.element
|
||||||
this._createFilterInputs();
|
.querySelectorAll('[data-action="selectFolder"]')
|
||||||
this._createDragProcess();
|
.forEach(element =>
|
||||||
|
element.classList.toggle('is-selected', element.dataset.folderId === this.selectedMenu.path.join('.'))
|
||||||
if (context.presets?.render?.lite) this.element.classList.add('lite');
|
|
||||||
|
|
||||||
if (context.presets?.render?.noFolder) this.element.classList.add('no-folder');
|
|
||||||
|
|
||||||
if (context.presets?.render?.noFilter) this.element.classList.add('no-filter');
|
|
||||||
|
|
||||||
if (this.presets?.filter) {
|
|
||||||
Object.entries(this.presets.filter).forEach(
|
|
||||||
([k, v]) => (this.fieldFilter.find(c => c.name === k).value = v.value)
|
|
||||||
);
|
);
|
||||||
await this._onInputFilterBrowser();
|
|
||||||
|
this._createSearchFilter();
|
||||||
|
|
||||||
|
this.element.classList.toggle('lite', this.presets?.render?.lite === true);
|
||||||
|
this.element.classList.toggle('no-folder', this.presets?.render?.noFolder === true);
|
||||||
|
this.element.classList.toggle('no-filter', this.presets?.render?.noFilter === true);
|
||||||
|
this.element.querySelectorAll('.folder-list > [data-action="selectFolder"]').forEach(element => {
|
||||||
|
element.hidden =
|
||||||
|
this.presets.render?.folders?.length && !this.presets.render.folders.includes(element.dataset.folderId);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_onPosition(position) {
|
||||||
|
game.user.setFlag(CONFIG.DH.id, CONFIG.DH.FLAGS[`${this.compendiumBrowserTypeKey}`].position, position);
|
||||||
}
|
}
|
||||||
|
|
||||||
_attachPartListeners(partId, htmlElement, options) {
|
_attachPartListeners(partId, htmlElement, options) {
|
||||||
super._attachPartListeners(partId, htmlElement, options);
|
super._attachPartListeners(partId, htmlElement, options);
|
||||||
|
|
||||||
htmlElement
|
htmlElement.querySelectorAll('[data-action="selectFolder"]').forEach(element =>
|
||||||
.querySelectorAll('[data-action="selectFolder"]')
|
element.addEventListener('contextmenu', event => {
|
||||||
.forEach(element => element.addEventListener("contextmenu", (event) => {
|
|
||||||
event.target.classList.toggle('expanded');
|
event.target.classList.toggle('expanded');
|
||||||
}))
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
@ -139,22 +152,26 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
async _prepareContext(options) {
|
async _prepareContext(options) {
|
||||||
const context = await super._prepareContext(options);
|
const context = await super._prepareContext(options);
|
||||||
context.compendiums = this.getCompendiumFolders(foundry.utils.deepClone(this.config));
|
context.compendiums = this.getCompendiumFolders(foundry.utils.deepClone(this.config));
|
||||||
// context.pathTitle = this.pathTile;
|
|
||||||
context.menu = this.selectedMenu;
|
context.menu = this.selectedMenu;
|
||||||
context.formatLabel = this.formatLabel;
|
context.formatLabel = this.formatLabel;
|
||||||
context.formatChoices = this.formatChoices;
|
context.formatChoices = this.formatChoices;
|
||||||
context.fieldFilter = this.fieldFilter = this._createFieldFilter();
|
|
||||||
context.items = this.items;
|
context.items = this.items;
|
||||||
context.presets = this.presets;
|
context.presets = this.presets;
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open(presets = {}) {
|
||||||
|
this.presets = presets;
|
||||||
|
ItemBrowser.selectFolder.call(this);
|
||||||
|
}
|
||||||
|
|
||||||
getCompendiumFolders(config, parent = null, depth = 0) {
|
getCompendiumFolders(config, parent = null, depth = 0) {
|
||||||
let folders = [];
|
let folders = [];
|
||||||
Object.values(config).forEach(c => {
|
Object.values(config).forEach(c => {
|
||||||
|
// if(this.presets.render?.folders?.length && !this.presets.render.folders.includes(c.id)) return;
|
||||||
const folder = {
|
const folder = {
|
||||||
id: c.id,
|
id: c.id,
|
||||||
label: c.label,
|
label: game.i18n.localize(c.label),
|
||||||
selected: (!parent || parent.selected) && this.selectedMenu.path[depth] === c.id
|
selected: (!parent || parent.selected) && this.selectedMenu.path[depth] === c.id
|
||||||
};
|
};
|
||||||
folder.folders = c.folders
|
folder.folders = c.folders
|
||||||
|
|
@ -162,40 +179,31 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
: [];
|
: [];
|
||||||
folders.push(folder);
|
folders.push(folder);
|
||||||
});
|
});
|
||||||
|
folders.sort((a, b) => a.label.localeCompare(b.label));
|
||||||
|
|
||||||
return folders;
|
return folders;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async selectFolder(_, target, compend, folder) {
|
static async selectFolder(_, target) {
|
||||||
const config = foundry.utils.deepClone(this.config),
|
const folderId = target?.dataset?.folderId ?? this.presets.folder,
|
||||||
compendium = compend ?? target.closest('[data-compendium-id]').dataset.compendiumId,
|
folderData = foundry.utils.getProperty(this.config, folderId) ?? {};
|
||||||
folderId = folder ?? target.dataset.folderId,
|
|
||||||
folderPath = `${compendium}.folders.${folderId}`,
|
const columns = ItemBrowser.getFolderConfig(folderData).map(col => ({
|
||||||
folderData = foundry.utils.getProperty(config, folderPath);
|
...col,
|
||||||
|
label: game.i18n.localize(col.label)
|
||||||
|
}));
|
||||||
|
|
||||||
this.selectedMenu = {
|
this.selectedMenu = {
|
||||||
path: folderPath.split('.'),
|
path: folderId?.split('.') ?? [],
|
||||||
data: {
|
data: {
|
||||||
...folderData,
|
...folderData,
|
||||||
columns: ItemBrowser.getFolderConfig(folderData)
|
columns: columns
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let items = [];
|
await this.render({ force: true, presets: this.presets });
|
||||||
for (const key of folderData.keys) {
|
|
||||||
const comp = game.packs.get(`${compendium}.${key}`);
|
|
||||||
if (!comp) return;
|
|
||||||
items = items.concat(await comp.getDocuments({ type__in: folderData.type }));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.items = ItemBrowser.sortBy(items, 'name');
|
if (this.selectedMenu?.data?.type?.length) this.loadItems();
|
||||||
|
|
||||||
if(target) {
|
|
||||||
target.closest('.compendium-sidebar').querySelectorAll('[data-action="selectFolder"]').forEach(element => element.classList.remove("is-selected"))
|
|
||||||
target.classList.add('is-selected');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.render({ force: true });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_replaceHTML(result, content, options) {
|
_replaceHTML(result, content, options) {
|
||||||
|
|
@ -203,6 +211,76 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
super._replaceHTML(result, content, options);
|
super._replaceHTML(result, content, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadItems() {
|
||||||
|
let loadTimeout = this.toggleLoader(true);
|
||||||
|
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
game.packs.forEach(pack => {
|
||||||
|
promises.push(
|
||||||
|
new Promise(async resolve => {
|
||||||
|
const items = await pack.getDocuments({ type__in: this.selectedMenu?.data?.type });
|
||||||
|
resolve(items);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Promise.all(promises).then(async result => {
|
||||||
|
this.items = ItemBrowser.sortBy(
|
||||||
|
result.flatMap(r => r),
|
||||||
|
'name'
|
||||||
|
);
|
||||||
|
this.fieldFilter = this._createFieldFilter();
|
||||||
|
|
||||||
|
if (this.presets?.filter) {
|
||||||
|
Object.entries(this.presets.filter).forEach(([k, v]) => {
|
||||||
|
const filter = this.fieldFilter.find(c => c.name === k);
|
||||||
|
if (filter) filter.value = v.value;
|
||||||
|
});
|
||||||
|
// await this._onInputFilterBrowser();
|
||||||
|
}
|
||||||
|
|
||||||
|
const filterList = await foundry.applications.handlebars.renderTemplate(
|
||||||
|
'systems/daggerheart/templates/ui/itemBrowser/filterContainer.hbs',
|
||||||
|
{
|
||||||
|
fieldFilter: this.fieldFilter,
|
||||||
|
presets: this.presets,
|
||||||
|
formatChoices: this.formatChoices
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.element.querySelector('.filter-content .wrapper').innerHTML = filterList;
|
||||||
|
const filterContainer = this.element.querySelector('.filter-header > [data-action="expandContent"]');
|
||||||
|
if (this.fieldFilter.length === 0) filterContainer.setAttribute('disabled', '');
|
||||||
|
else filterContainer.removeAttribute('disabled');
|
||||||
|
|
||||||
|
const itemList = await foundry.applications.handlebars.renderTemplate(
|
||||||
|
'systems/daggerheart/templates/ui/itemBrowser/itemContainer.hbs',
|
||||||
|
{
|
||||||
|
items: this.items,
|
||||||
|
menu: this.selectedMenu,
|
||||||
|
formatLabel: this.formatLabel
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.element.querySelector('.item-list').innerHTML = itemList;
|
||||||
|
|
||||||
|
this._createFilterInputs();
|
||||||
|
await this._onInputFilterBrowser();
|
||||||
|
this._createDragProcess();
|
||||||
|
|
||||||
|
clearTimeout(loadTimeout);
|
||||||
|
this.toggleLoader(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleLoader(state) {
|
||||||
|
const container = this.element.querySelector('.item-list');
|
||||||
|
return setTimeout(() => {
|
||||||
|
container.classList.toggle('daggerheart-loader', state);
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
static expandContent(_, target) {
|
static expandContent(_, target) {
|
||||||
const parent = target.parentElement;
|
const parent = target.parentElement;
|
||||||
parent.classList.toggle('expanded');
|
parent.classList.toggle('expanded');
|
||||||
|
|
@ -216,7 +294,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
const property = foundry.utils.getProperty(item, field.key);
|
const property = foundry.utils.getProperty(item, field.key);
|
||||||
if (Array.isArray(property)) property.join(', ');
|
if (Array.isArray(property)) property.join(', ');
|
||||||
if (typeof field.format !== 'function') return property ?? '-';
|
if (typeof field.format !== 'function') return property ?? '-';
|
||||||
return field.format(property);
|
return game.i18n.localize(field.format(property));
|
||||||
}
|
}
|
||||||
|
|
||||||
formatChoices(data) {
|
formatChoices(data) {
|
||||||
|
|
@ -235,8 +313,14 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
filters.forEach(f => {
|
filters.forEach(f => {
|
||||||
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();
|
f.choices = f.choices(this.items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear field label so template uses our custom label parameter
|
||||||
|
if (f.field && f.label) {
|
||||||
|
f.field.label = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
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;
|
||||||
});
|
});
|
||||||
|
|
@ -248,11 +332,8 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create and initialize search filter instances for the inventory and loadout sections.
|
* Create and initialize search filter instance.
|
||||||
*
|
*
|
||||||
* Sets up two {@link foundry.applications.ux.SearchFilter} instances:
|
|
||||||
* - One for the inventory, which filters items in the inventory grid.
|
|
||||||
* - One for the loadout, which filters items in the loadout/card grid.
|
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_createSearchFilter() {
|
_createSearchFilter() {
|
||||||
|
|
@ -317,6 +398,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
for (const li of html.querySelectorAll('.item-container')) {
|
for (const li of html.querySelectorAll('.item-container')) {
|
||||||
const itemUUID = li.dataset.itemUuid,
|
const itemUUID = li.dataset.itemUuid,
|
||||||
item = this.items.find(i => i.uuid === itemUUID);
|
item = this.items.find(i => i.uuid === itemUUID);
|
||||||
|
if (!item) continue;
|
||||||
const matchesSearch = !query || foundry.applications.ux.SearchFilter.testQuery(rgx, item.name);
|
const matchesSearch = !query || foundry.applications.ux.SearchFilter.testQuery(rgx, item.name);
|
||||||
if (matchesSearch) this.#filteredItems.browser.search.add(item.id);
|
if (matchesSearch) this.#filteredItems.browser.search.add(item.id);
|
||||||
const { input } = this.#filteredItems.browser;
|
const { input } = this.#filteredItems.browser;
|
||||||
|
|
@ -348,7 +430,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
if (matchesMenu) this.#filteredItems.browser.input.add(item.id);
|
if (matchesMenu) this.#filteredItems.browser.input.add(item.id);
|
||||||
|
|
||||||
const { search } = this.#filteredItems.browser;
|
const { search } = this.#filteredItems.browser;
|
||||||
li.hidden = !(search.has(item.id) && matchesMenu);
|
li.hidden = !((this.#search.browser.query.length === 0 || search.has(item.id)) && matchesMenu);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -387,6 +469,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
|
|
||||||
static resetFilters() {
|
static resetFilters() {
|
||||||
this.render({ force: true });
|
this.render({ force: true });
|
||||||
|
this.loadItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
static getFolderConfig(folder, property = 'columns') {
|
static getFolderConfig(folder, property = 'columns') {
|
||||||
|
|
@ -408,11 +491,13 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
|
|
||||||
const newOrder = [...itemList].reverse().sort((a, b) => {
|
const newOrder = [...itemList].reverse().sort((a, b) => {
|
||||||
const aProp = a.querySelector(`[data-item-key="${key}"]`),
|
const aProp = a.querySelector(`[data-item-key="${key}"]`),
|
||||||
bProp = b.querySelector(`[data-item-key="${key}"]`);
|
bProp = b.querySelector(`[data-item-key="${key}"]`),
|
||||||
|
aValue = isNaN(aProp.innerText) ? aProp.innerText : Number(aProp.innerText),
|
||||||
|
bValue = isNaN(bProp.innerText) ? bProp.innerText : Number(bProp.innerText);
|
||||||
if (type === 'DESC') {
|
if (type === 'DESC') {
|
||||||
return aProp.innerText < bProp.innerText ? 1 : -1;
|
return aValue < bValue ? 1 : -1;
|
||||||
} else {
|
} else {
|
||||||
return aProp.innerText > bProp.innerText ? 1 : -1;
|
return aValue > bValue ? 1 : -1;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -441,4 +526,41 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
_canDragStart() {
|
_canDragStart() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static injectSidebarButton(html) {
|
||||||
|
if (!game.user.isGM) return;
|
||||||
|
const sectionId = html.dataset.tab,
|
||||||
|
menus = {
|
||||||
|
actors: {
|
||||||
|
folder: 'adversaries',
|
||||||
|
render: {
|
||||||
|
folders: ['adversaries', 'characters', 'environments']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
items: {
|
||||||
|
folder: 'equipments',
|
||||||
|
render: {
|
||||||
|
noFolder: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
compendium: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Object.keys(menus).includes(sectionId)) {
|
||||||
|
const headerActions = html.querySelector('.header-actions');
|
||||||
|
|
||||||
|
const button = document.createElement('button');
|
||||||
|
button.type = 'button';
|
||||||
|
button.classList.add('open-compendium-browser');
|
||||||
|
button.innerHTML = `
|
||||||
|
<i class="fa-solid fa-book-atlas"></i>
|
||||||
|
${game.i18n.localize('DAGGERHEART.UI.Tooltip.compendiumBrowser')}
|
||||||
|
`;
|
||||||
|
button.addEventListener('click', event => {
|
||||||
|
ui.compendiumBrowser.open(menus[sectionId]);
|
||||||
|
});
|
||||||
|
|
||||||
|
headerActions.append(button);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,29 +10,48 @@ export default class DhMeasuredTemplate extends foundry.canvas.placeables.Measur
|
||||||
const splitRulerText = this.ruler.text.split(' ');
|
const splitRulerText = this.ruler.text.split(' ');
|
||||||
if (splitRulerText.length > 0) {
|
if (splitRulerText.length > 0) {
|
||||||
const rulerValue = Number(splitRulerText[0]);
|
const rulerValue = Number(splitRulerText[0]);
|
||||||
const vagueLabel = this.constructor.getDistanceLabel(rulerValue, rangeMeasurementSettings);
|
const result = DhMeasuredTemplate.getRangeLabels(rulerValue, rangeMeasurementSettings);
|
||||||
this.ruler.text = vagueLabel;
|
this.ruler.text = result.distance + (result.units ? ' ' + result.units : '');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static getDistanceLabel(distance, settings) {
|
static getRangeLabels(distanceValue, settings) {
|
||||||
if (distance <= settings.melee) {
|
let result = { distance: distanceValue, units: '' };
|
||||||
return game.i18n.localize('DAGGERHEART.CONFIG.Range.melee.name');
|
const sceneRangeMeasurement = canvas.scene.flags.daggerheart?.rangeMeasurement;
|
||||||
}
|
|
||||||
if (distance <= settings.veryClose) {
|
const { disable, custom } = CONFIG.DH.GENERAL.sceneRangeMeasurementSetting;
|
||||||
return game.i18n.localize('DAGGERHEART.CONFIG.Range.veryClose.name');
|
if (sceneRangeMeasurement?.setting === disable.id) {
|
||||||
}
|
result.distance = distanceValue;
|
||||||
if (distance <= settings.close) {
|
result.units = canvas.scene?.grid?.units;
|
||||||
return game.i18n.localize('DAGGERHEART.CONFIG.Range.close.name');
|
return result;
|
||||||
}
|
|
||||||
if (distance <= settings.far) {
|
|
||||||
return game.i18n.localize('DAGGERHEART.CONFIG.Range.far.name');
|
|
||||||
}
|
|
||||||
if (distance > settings.far) {
|
|
||||||
return game.i18n.localize('DAGGERHEART.CONFIG.Range.veryFar.name');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return '';
|
const melee = sceneRangeMeasurement?.setting === custom.id ? sceneRangeMeasurement.melee : settings.melee;
|
||||||
|
const veryClose =
|
||||||
|
sceneRangeMeasurement?.setting === custom.id ? sceneRangeMeasurement.veryClose : settings.veryClose;
|
||||||
|
const close = sceneRangeMeasurement?.setting === custom.id ? sceneRangeMeasurement.close : settings.close;
|
||||||
|
const far = sceneRangeMeasurement?.setting === custom.id ? sceneRangeMeasurement.far : settings.far;
|
||||||
|
if (distanceValue <= melee) {
|
||||||
|
result.distance = game.i18n.localize('DAGGERHEART.CONFIG.Range.melee.name');
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (distanceValue <= veryClose) {
|
||||||
|
result.distance = game.i18n.localize('DAGGERHEART.CONFIG.Range.veryClose.name');
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (distanceValue <= close) {
|
||||||
|
result.distance = game.i18n.localize('DAGGERHEART.CONFIG.Range.close.name');
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (distanceValue <= far) {
|
||||||
|
result.distance = game.i18n.localize('DAGGERHEART.CONFIG.Range.far.name');
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (distanceValue > far) {
|
||||||
|
result.distance = game.i18n.localize('DAGGERHEART.CONFIG.Range.veryFar.name');
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,9 @@ export default class DhpRuler extends foundry.canvas.interaction.Ruler {
|
||||||
const range = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules).rangeMeasurement;
|
const range = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules).rangeMeasurement;
|
||||||
|
|
||||||
if (range.enabled) {
|
if (range.enabled) {
|
||||||
const distance = DhMeasuredTemplate.getDistanceLabel(waypoint.measurement.distance.toNearest(0.01), range);
|
const result = DhMeasuredTemplate.getRangeLabels(waypoint.measurement.distance.toNearest(0.01), range);
|
||||||
context.cost = { total: distance, units: null };
|
context.cost = { total: result.distance, units: result.units };
|
||||||
context.distance = { total: distance, units: null };
|
context.distance = { total: result.distance, units: result.units };
|
||||||
}
|
}
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
|
|
|
||||||
|
|
@ -10,29 +10,7 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
|
||||||
this.effects.overlay = null;
|
this.effects.overlay = null;
|
||||||
|
|
||||||
// Categorize effects
|
// Categorize effects
|
||||||
const statusMap = new Map(foundry.CONFIG.statusEffects.map(status => [status.id, status]));
|
const activeEffects = this.actor?.getActiveEffects() ?? [];
|
||||||
const activeEffects = (this.actor ? this.actor.effects.filter(x => !x.disabled) : []).reduce((acc, effect) => {
|
|
||||||
acc.push(effect);
|
|
||||||
|
|
||||||
const currentStatusActiveEffects = acc.filter(
|
|
||||||
x => x.statuses.size === 1 && x.name === game.i18n.localize(statusMap.get(x.statuses.first())?.name)
|
|
||||||
);
|
|
||||||
for (var status of effect.statuses) {
|
|
||||||
if (!currentStatusActiveEffects.find(x => x.statuses.has(status))) {
|
|
||||||
const statusData = statusMap.get(status);
|
|
||||||
if (statusData) {
|
|
||||||
acc.push({
|
|
||||||
name: game.i18n.localize(statusData.name),
|
|
||||||
statuses: [status],
|
|
||||||
img: statusData.icon,
|
|
||||||
tint: effect.tint
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, []);
|
|
||||||
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
|
||||||
|
|
@ -56,6 +34,69 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
|
||||||
this.renderFlags.set({ refreshEffects: true });
|
this.renderFlags.set({ refreshEffects: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the distance from this token to another token object.
|
||||||
|
* This value is corrected to handle alternate token sizes and other grid types
|
||||||
|
* according to the diagonal rules.
|
||||||
|
*/
|
||||||
|
distanceTo(target) {
|
||||||
|
if (!canvas.ready) return NaN;
|
||||||
|
if (this === target) return 0;
|
||||||
|
|
||||||
|
const originPoint = this.center;
|
||||||
|
const destinationPoint = target.center;
|
||||||
|
|
||||||
|
// Compute for gridless. This version returns circular edge to edge + grid distance,
|
||||||
|
// so that tokens that are touching return 5.
|
||||||
|
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
|
||||||
|
const boundsCorrection = canvas.grid.distance / canvas.grid.size;
|
||||||
|
const originRadius = this.bounds.width * boundsCorrection / 2;
|
||||||
|
const targetRadius = target.bounds.width * boundsCorrection / 2;
|
||||||
|
const distance = canvas.grid.measurePath([originPoint, destinationPoint]).distance;
|
||||||
|
return distance - originRadius - targetRadius + canvas.grid.distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute what the closest grid space of each token is, then compute that distance
|
||||||
|
const originEdge = this.#getEdgeBoundary(this.bounds, originPoint, destinationPoint);
|
||||||
|
const targetEdge = this.#getEdgeBoundary(target.bounds, originPoint, destinationPoint);
|
||||||
|
const adjustedOriginPoint = canvas.grid.getTopLeftPoint({
|
||||||
|
x: originEdge.x + Math.sign(originPoint.x - originEdge.x),
|
||||||
|
y: originEdge.y + Math.sign(originPoint.y - originEdge.y)
|
||||||
|
});
|
||||||
|
const adjustDestinationPoint = canvas.grid.getTopLeftPoint({
|
||||||
|
x: targetEdge.x + Math.sign(destinationPoint.x - targetEdge.x),
|
||||||
|
y: targetEdge.y + Math.sign(destinationPoint.y - targetEdge.y)
|
||||||
|
});
|
||||||
|
return canvas.grid.measurePath([adjustedOriginPoint, adjustDestinationPoint]).distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the point at which a line starting at origin and ending at destination intersects the edge of the bounds */
|
||||||
|
#getEdgeBoundary(bounds, originPoint, destinationPoint) {
|
||||||
|
const points = [
|
||||||
|
{ x: bounds.x, y: bounds.y },
|
||||||
|
{ x: bounds.x + bounds.width, y: bounds.y },
|
||||||
|
{ x: bounds.x + bounds.width, y: bounds.y + bounds.height },
|
||||||
|
{ x: bounds.x, y: bounds.y + bounds.height }
|
||||||
|
];
|
||||||
|
const pairsToTest = [
|
||||||
|
[points[0], points[1]],
|
||||||
|
[points[1], points[2]],
|
||||||
|
[points[2], points[3]],
|
||||||
|
[points[3], points[0]]
|
||||||
|
];
|
||||||
|
for (const pair of pairsToTest) {
|
||||||
|
const result = foundry.utils.lineSegmentIntersection(originPoint, destinationPoint, pair[0], pair[1]);
|
||||||
|
if (result) return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tests if the token is at least adjacent with another, with some leeway for diagonals */
|
||||||
|
isAdjacentWith(token) {
|
||||||
|
return this.distanceTo(token) <= (canvas.grid.distance * 1.5);
|
||||||
|
}
|
||||||
|
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
_drawBar(number, bar, data) {
|
_drawBar(number, bar, data) {
|
||||||
const val = Number(data.value);
|
const val = Number(data.value);
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,9 @@ export default class DhpTokenRuler extends foundry.canvas.placeables.tokens.Toke
|
||||||
const range = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules).rangeMeasurement;
|
const range = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules).rangeMeasurement;
|
||||||
|
|
||||||
if (range.enabled) {
|
if (range.enabled) {
|
||||||
const distance = DhMeasuredTemplate.getDistanceLabel(waypoint.measurement.distance.toNearest(0.01), range);
|
const result = DhMeasuredTemplate.getRangeLabels(waypoint.measurement.distance.toNearest(0.01), range);
|
||||||
context.cost = { total: distance, units: null };
|
context.cost = { total: result.distance, units: result.units };
|
||||||
context.distance = { total: distance, units: null };
|
context.distance = { total: result.distance, units: result.units };
|
||||||
}
|
}
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,10 @@ export * as actionConfig from './actionConfig.mjs';
|
||||||
export * as actorConfig from './actorConfig.mjs';
|
export * as actorConfig from './actorConfig.mjs';
|
||||||
export * as domainConfig from './domainConfig.mjs';
|
export * as domainConfig from './domainConfig.mjs';
|
||||||
export * as effectConfig from './effectConfig.mjs';
|
export * as effectConfig from './effectConfig.mjs';
|
||||||
|
export * as encounterConfig from './encounterConfig.mjs';
|
||||||
export * as flagsConfig from './flagsConfig.mjs';
|
export * as flagsConfig from './flagsConfig.mjs';
|
||||||
export * as generalConfig from './generalConfig.mjs';
|
export * as generalConfig from './generalConfig.mjs';
|
||||||
|
export * as hooksConfig from './hooksConfig.mjs';
|
||||||
export * as itemConfig from './itemConfig.mjs';
|
export * as itemConfig from './itemConfig.mjs';
|
||||||
export * as settingsConfig from './settingsConfig.mjs';
|
export * as settingsConfig from './settingsConfig.mjs';
|
||||||
export * as systemConfig from './system.mjs';
|
export * as systemConfig from './system.mjs';
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,15 @@ export const actionTypes = {
|
||||||
attack: {
|
attack: {
|
||||||
id: 'attack',
|
id: 'attack',
|
||||||
name: 'DAGGERHEART.ACTIONS.TYPES.attack.name',
|
name: 'DAGGERHEART.ACTIONS.TYPES.attack.name',
|
||||||
icon: 'fa-khanda',
|
icon: 'fa-hand-fist',
|
||||||
tooltip: 'DAGGERHEART.ACTIONS.TYPES.attack.tooltip'
|
tooltip: 'DAGGERHEART.ACTIONS.TYPES.attack.tooltip'
|
||||||
},
|
},
|
||||||
|
countdown: {
|
||||||
|
id: 'countdown',
|
||||||
|
name: 'DAGGERHEART.ACTIONS.TYPES.countdown.name',
|
||||||
|
icon: 'fa-hourglass-half',
|
||||||
|
tooltip: 'DAGGERHEART.ACTIONS.TYPES.countdown.tooltip'
|
||||||
|
},
|
||||||
healing: {
|
healing: {
|
||||||
id: 'healing',
|
id: 'healing',
|
||||||
name: 'DAGGERHEART.ACTIONS.TYPES.healing.name',
|
name: 'DAGGERHEART.ACTIONS.TYPES.healing.name',
|
||||||
|
|
|
||||||
|
|
@ -108,55 +108,72 @@ export const adversaryTypes = {
|
||||||
bruiser: {
|
bruiser: {
|
||||||
id: 'bruiser',
|
id: 'bruiser',
|
||||||
label: 'DAGGERHEART.CONFIG.AdversaryType.bruiser.label',
|
label: 'DAGGERHEART.CONFIG.AdversaryType.bruiser.label',
|
||||||
description: 'DAGGERHEART.ACTORS.Adversary.bruiser.description'
|
description: 'DAGGERHEART.ACTORS.Adversary.bruiser.description',
|
||||||
|
bpCost: 4
|
||||||
},
|
},
|
||||||
horde: {
|
horde: {
|
||||||
id: 'horde',
|
id: 'horde',
|
||||||
label: 'DAGGERHEART.CONFIG.AdversaryType.horde.label',
|
label: 'DAGGERHEART.CONFIG.AdversaryType.horde.label',
|
||||||
description: 'DAGGERHEART.ACTORS.Adversary.horde.description'
|
description: 'DAGGERHEART.ACTORS.Adversary.horde.description',
|
||||||
|
bpCost: 2
|
||||||
},
|
},
|
||||||
leader: {
|
leader: {
|
||||||
id: 'leader',
|
id: 'leader',
|
||||||
label: 'DAGGERHEART.CONFIG.AdversaryType.leader.label',
|
label: 'DAGGERHEART.CONFIG.AdversaryType.leader.label',
|
||||||
description: 'DAGGERHEART.ACTORS.Adversary.leader.description'
|
description: 'DAGGERHEART.ACTORS.Adversary.leader.description',
|
||||||
|
bpCost: 3,
|
||||||
|
bpDescription: 'DAGGERHEART.CONFIG.AdversaryType.leader.'
|
||||||
},
|
},
|
||||||
minion: {
|
minion: {
|
||||||
id: 'minion',
|
id: 'minion',
|
||||||
label: 'DAGGERHEART.CONFIG.AdversaryType.minion.label',
|
label: 'DAGGERHEART.CONFIG.AdversaryType.minion.label',
|
||||||
description: 'DAGGERHEART.ACTORS.Adversary.minion.description'
|
description: 'DAGGERHEART.ACTORS.Adversary.minion.description',
|
||||||
|
bpCost: 1,
|
||||||
|
partyAmountPerBP: true
|
||||||
},
|
},
|
||||||
ranged: {
|
ranged: {
|
||||||
id: 'ranged',
|
id: 'ranged',
|
||||||
label: 'DAGGERHEART.CONFIG.AdversaryType.ranged.label',
|
label: 'DAGGERHEART.CONFIG.AdversaryType.ranged.label',
|
||||||
description: 'DAGGERHEART.ACTORS.Adversary.ranged.description'
|
description: 'DAGGERHEART.ACTORS.Adversary.ranged.description',
|
||||||
|
bpCost: 2
|
||||||
},
|
},
|
||||||
skulk: {
|
skulk: {
|
||||||
id: 'skulk',
|
id: 'skulk',
|
||||||
label: 'DAGGERHEART.CONFIG.AdversaryType.skulk.label',
|
label: 'DAGGERHEART.CONFIG.AdversaryType.skulk.label',
|
||||||
description: 'DAGGERHEART.ACTORS.Adversary.skulk.description'
|
description: 'DAGGERHEART.ACTORS.Adversary.skulk.description',
|
||||||
|
bpCost: 2
|
||||||
},
|
},
|
||||||
social: {
|
social: {
|
||||||
id: 'social',
|
id: 'social',
|
||||||
label: 'DAGGERHEART.CONFIG.AdversaryType.social.label',
|
label: 'DAGGERHEART.CONFIG.AdversaryType.social.label',
|
||||||
description: 'DAGGERHEART.ACTORS.Adversary.social.description'
|
description: 'DAGGERHEART.ACTORS.Adversary.social.description',
|
||||||
|
bpCost: 1
|
||||||
},
|
},
|
||||||
solo: {
|
solo: {
|
||||||
id: 'solo',
|
id: 'solo',
|
||||||
label: 'DAGGERHEART.CONFIG.AdversaryType.solo.label',
|
label: 'DAGGERHEART.CONFIG.AdversaryType.solo.label',
|
||||||
description: 'DAGGERHEART.ACTORS.Adversary.solo.description'
|
description: 'DAGGERHEART.ACTORS.Adversary.solo.description',
|
||||||
|
bpCost: 5
|
||||||
},
|
},
|
||||||
standard: {
|
standard: {
|
||||||
id: 'standard',
|
id: 'standard',
|
||||||
label: 'DAGGERHEART.CONFIG.AdversaryType.standard.label',
|
label: 'DAGGERHEART.CONFIG.AdversaryType.standard.label',
|
||||||
description: 'DAGGERHEART.ACTORS.Adversary.standard.description'
|
description: 'DAGGERHEART.ACTORS.Adversary.standard.description',
|
||||||
|
bpCost: 2
|
||||||
},
|
},
|
||||||
support: {
|
support: {
|
||||||
id: 'support',
|
id: 'support',
|
||||||
label: 'DAGGERHEART.CONFIG.AdversaryType.support.label',
|
label: 'DAGGERHEART.CONFIG.AdversaryType.support.label',
|
||||||
description: 'DAGGERHEART.ACTORS.Adversary.support.description'
|
description: 'DAGGERHEART.ACTORS.Adversary.support.description',
|
||||||
|
bpCost: 1
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const allAdversaryTypes = () => ({
|
||||||
|
...adversaryTypes,
|
||||||
|
...game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).adversaryTypes
|
||||||
|
});
|
||||||
|
|
||||||
export const environmentTypes = {
|
export const environmentTypes = {
|
||||||
exploration: {
|
exploration: {
|
||||||
label: 'DAGGERHEART.CONFIG.EnvironmentType.exploration.label',
|
label: 'DAGGERHEART.CONFIG.EnvironmentType.exploration.label',
|
||||||
|
|
@ -194,6 +211,44 @@ export const adversaryTraits = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const tokenSize = {
|
||||||
|
custom: {
|
||||||
|
id: 'custom',
|
||||||
|
value: 0,
|
||||||
|
label: 'DAGGERHEART.GENERAL.custom'
|
||||||
|
},
|
||||||
|
tiny: {
|
||||||
|
id: 'tiny',
|
||||||
|
value: 1,
|
||||||
|
label: 'DAGGERHEART.CONFIG.TokenSize.tiny'
|
||||||
|
},
|
||||||
|
small: {
|
||||||
|
id: 'small',
|
||||||
|
value: 2,
|
||||||
|
label: 'DAGGERHEART.CONFIG.TokenSize.small'
|
||||||
|
},
|
||||||
|
medium: {
|
||||||
|
id: 'medium',
|
||||||
|
value: 3,
|
||||||
|
label: 'DAGGERHEART.CONFIG.TokenSize.medium'
|
||||||
|
},
|
||||||
|
large: {
|
||||||
|
id: 'large',
|
||||||
|
value: 4,
|
||||||
|
label: 'DAGGERHEART.CONFIG.TokenSize.large'
|
||||||
|
},
|
||||||
|
huge: {
|
||||||
|
id: 'huge',
|
||||||
|
value: 5,
|
||||||
|
label: 'DAGGERHEART.CONFIG.TokenSize.huge'
|
||||||
|
},
|
||||||
|
gargantuan: {
|
||||||
|
id: 'gargantuan',
|
||||||
|
value: 6,
|
||||||
|
label: 'DAGGERHEART.CONFIG.TokenSize.gargantuan'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const levelChoices = {
|
export const levelChoices = {
|
||||||
attributes: {
|
attributes: {
|
||||||
name: 'attributes',
|
name: 'attributes',
|
||||||
|
|
|
||||||
146
module/config/encounterConfig.mjs
Normal file
146
module/config/encounterConfig.mjs
Normal file
|
|
@ -0,0 +1,146 @@
|
||||||
|
export const BaseBPPerEncounter = nrCharacters => 3 * nrCharacters + 2;
|
||||||
|
|
||||||
|
export const AdversaryBPPerEncounter = (adversaries, characters) => {
|
||||||
|
const adversaryTypes = CONFIG.DH.ACTOR.allAdversaryTypes();
|
||||||
|
return adversaries
|
||||||
|
.reduce((acc, adversary) => {
|
||||||
|
const existingEntry = acc.find(
|
||||||
|
x => x.adversary.name === adversary.name && x.adversary.type === adversary.type
|
||||||
|
);
|
||||||
|
if (existingEntry) {
|
||||||
|
existingEntry.nr += 1;
|
||||||
|
} else if (adversary.type) {
|
||||||
|
acc.push({ adversary, nr: 1 });
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, [])
|
||||||
|
.reduce((acc, entry) => {
|
||||||
|
const adversary = entry.adversary;
|
||||||
|
const type = adversaryTypes[adversary.type];
|
||||||
|
const bpCost = type.bpCost ?? 0;
|
||||||
|
if (type.partyAmountPerBP) {
|
||||||
|
acc += characters.length === 0 ? 0 : Math.ceil(entry.nr / characters.length);
|
||||||
|
} else {
|
||||||
|
acc += bpCost * entry.nr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const adversaryTypeCostBrackets = {
|
||||||
|
1: [
|
||||||
|
{
|
||||||
|
sort: 1,
|
||||||
|
types: ['minion'],
|
||||||
|
description: 'DAGGERHEART.CONFIG.AdversaryTypeCost.minion'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sort: 2,
|
||||||
|
types: ['social', 'support'],
|
||||||
|
description: 'DAGGERHEART.CONFIG.AdversaryTypeCost.support'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
2: [
|
||||||
|
{
|
||||||
|
sort: 1,
|
||||||
|
types: ['horde', 'ranged', 'skulk', 'standard'],
|
||||||
|
description: 'DAGGERHEART.CONFIG.AdversaryTypeCost.standard'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
3: [
|
||||||
|
{
|
||||||
|
sort: 1,
|
||||||
|
types: ['leader'],
|
||||||
|
description: 'DAGGERHEART.CONFIG.AdversaryTypeCost.leader'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
4: [
|
||||||
|
{
|
||||||
|
sort: 1,
|
||||||
|
types: ['bruiser'],
|
||||||
|
description: 'DAGGERHEART.CONFIG.AdversaryTypeCost.bruiser'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
5: [
|
||||||
|
{
|
||||||
|
sort: 1,
|
||||||
|
types: ['solo'],
|
||||||
|
description: 'DAGGERHEART.CONFIG.AdversaryTypeCost.solo'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BPModifiers = {
|
||||||
|
[-2]: {
|
||||||
|
manySolos: {
|
||||||
|
sort: 1,
|
||||||
|
description: 'DAGGERHEART.CONFIG.BPModifiers.manySolos.description',
|
||||||
|
automatic: true,
|
||||||
|
conditional: (_combat, adversaries) => {
|
||||||
|
return adversaries.filter(x => x.system.type === 'solo').length > 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
increaseDamage: {
|
||||||
|
sort: 2,
|
||||||
|
description: 'DAGGERHEART.CONFIG.BPModifiers.increaseDamage.description',
|
||||||
|
effectTargetTypes: ['adversary'],
|
||||||
|
effects: [
|
||||||
|
{
|
||||||
|
name: 'DAGGERHEART.CONFIG.BPModifiers.increaseDamage.effect.name',
|
||||||
|
description: 'DAGGERHEART.CONFIG.BPModifiers.increaseDamage.effect.description',
|
||||||
|
img: 'icons/magic/control/buff-flight-wings-red.webp',
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
key: 'system.bonuses.damage.physical.dice',
|
||||||
|
mode: 2,
|
||||||
|
value: '1d4'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'system.bonuses.damage.magical.dice',
|
||||||
|
mode: 2,
|
||||||
|
value: '1d4'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[-1]: {
|
||||||
|
lessDifficult: {
|
||||||
|
sort: 2,
|
||||||
|
description: 'DAGGERHEART.CONFIG.BPModifiers.lessDifficult.description'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
1: {
|
||||||
|
lowerTier: {
|
||||||
|
sort: 1,
|
||||||
|
description: 'DAGGERHEART.CONFIG.BPModifiers.lowerTier.description',
|
||||||
|
automatic: true,
|
||||||
|
conditional: (_combat, adversaries, characters) => {
|
||||||
|
const characterMaxTier = characters.reduce((maxTier, character) => {
|
||||||
|
return character.system.tier > maxTier ? character.system.tier : maxTier;
|
||||||
|
}, 1);
|
||||||
|
return adversaries.some(adversary => adversary.system.tier < characterMaxTier);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
noToughies: {
|
||||||
|
sort: 2,
|
||||||
|
description: 'DAGGERHEART.CONFIG.BPModifiers.noToughies.description',
|
||||||
|
automatic: true,
|
||||||
|
conditional: (_combat, adversaries) => {
|
||||||
|
const toughyTypes = ['bruiser', 'horde', 'leader', 'solo'];
|
||||||
|
return (
|
||||||
|
adversaries.length > 0 &&
|
||||||
|
!adversaries.some(adversary => toughyTypes.includes(adversary.system.type))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
2: {
|
||||||
|
moreDangerous: {
|
||||||
|
sort: 2,
|
||||||
|
description: 'DAGGERHEART.CONFIG.BPModifiers.moreDangerous.description'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
export const displayDomainCardsAsList = 'displayDomainCardsAsList';
|
export const displayDomainCardsAsCard = 'displayDomainCardsAsCard';
|
||||||
export const narrativeCountdown = {
|
export const narrativeCountdown = {
|
||||||
simple: 'countdown-narrative-simple',
|
simple: 'countdown-narrative-simple',
|
||||||
position: 'countdown-narrative-position'
|
position: 'countdown-narrative-position'
|
||||||
|
|
@ -8,8 +8,23 @@ export const encounterCountdown = {
|
||||||
position: 'countdown-encounter-position'
|
position: 'countdown-encounter-position'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const compendiumBrowserDefault = {
|
||||||
|
position: 'compendium-browser-default-position'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const compendiumBrowserNoFolder = {
|
||||||
|
position: 'compendium-browser-no-folder-position'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const compendiumBrowserLite = {
|
||||||
|
position: 'compendium-browser-lite-position'
|
||||||
|
};
|
||||||
|
|
||||||
export const itemAttachmentSource = 'attachmentSource';
|
export const itemAttachmentSource = 'attachmentSource';
|
||||||
|
|
||||||
export const userFlags = {
|
export const userFlags = {
|
||||||
welcomeMessage: 'welcome-message'
|
welcomeMessage: 'welcome-message',
|
||||||
|
countdownMode: 'countdown-mode'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const combatToggle = 'combat-toggle-origin';
|
||||||
|
|
|
||||||
|
|
@ -90,22 +90,22 @@ export const rangeInclusion = {
|
||||||
export const otherTargetTypes = {
|
export const otherTargetTypes = {
|
||||||
friendly: {
|
friendly: {
|
||||||
id: 'friendly',
|
id: 'friendly',
|
||||||
label: 'Friendly'
|
label: 'DAGGERHEART.CONFIG.TargetTypes.friendly'
|
||||||
},
|
},
|
||||||
hostile: {
|
hostile: {
|
||||||
id: 'hostile',
|
id: 'hostile',
|
||||||
label: 'Hostile'
|
label: 'DAGGERHEART.CONFIG.TargetTypes.hostile'
|
||||||
},
|
},
|
||||||
any: {
|
any: {
|
||||||
id: 'any',
|
id: 'any',
|
||||||
label: 'Any'
|
label: 'DAGGERHEART.CONFIG.TargetTypes.any'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const targetTypes = {
|
export const targetTypes = {
|
||||||
self: {
|
self: {
|
||||||
id: 'self',
|
id: 'self',
|
||||||
label: 'Self'
|
label: 'DAGGERHEART.CONFIG.TargetTypes.self'
|
||||||
},
|
},
|
||||||
...otherTargetTypes
|
...otherTargetTypes
|
||||||
};
|
};
|
||||||
|
|
@ -164,28 +164,36 @@ export const healingTypes = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const defeatedConditions = {
|
export const defeatedConditions = () => {
|
||||||
|
const defeated = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).defeated;
|
||||||
|
return Object.keys(defeatedConditionChoices).reduce((acc, key) => {
|
||||||
|
const choice = defeatedConditionChoices[key];
|
||||||
|
acc[key] = {
|
||||||
|
...choice,
|
||||||
|
img: defeated[`${choice.id}Icon`],
|
||||||
|
description: `DAGGERHEART.CONFIG.Condition.${choice.id}.description`
|
||||||
|
};
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const defeatedConditionChoices = {
|
||||||
defeated: {
|
defeated: {
|
||||||
id: 'defeated',
|
id: 'defeated',
|
||||||
name: 'DAGGERHEART.CONFIG.Condition.defeated.name',
|
name: 'DAGGERHEART.CONFIG.Condition.defeated.name'
|
||||||
img: 'icons/magic/control/fear-fright-mask-orange.webp',
|
|
||||||
description: 'DAGGERHEART.CONFIG.Condition.defeated.description'
|
|
||||||
},
|
},
|
||||||
unconscious: {
|
unconscious: {
|
||||||
id: 'unconscious',
|
id: 'unconscious',
|
||||||
name: 'DAGGERHEART.CONFIG.Condition.unconscious.name',
|
name: 'DAGGERHEART.CONFIG.Condition.unconscious.name'
|
||||||
img: 'icons/magic/control/sleep-bubble-purple.webp',
|
|
||||||
description: 'DAGGERHEART.CONFIG.Condition.unconscious.description'
|
|
||||||
},
|
},
|
||||||
dead: {
|
dead: {
|
||||||
id: 'dead',
|
id: 'dead',
|
||||||
name: 'DAGGERHEART.CONFIG.Condition.dead.name',
|
name: 'DAGGERHEART.CONFIG.Condition.dead.name'
|
||||||
img: 'icons/magic/death/grave-tombstone-glow-teal.webp',
|
|
||||||
description: 'DAGGERHEART.CONFIG.Condition.dead.description'
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const conditions = {
|
export const conditions = () => ({
|
||||||
vulnerable: {
|
vulnerable: {
|
||||||
id: 'vulnerable',
|
id: 'vulnerable',
|
||||||
name: 'DAGGERHEART.CONFIG.Condition.vulnerable.name',
|
name: 'DAGGERHEART.CONFIG.Condition.vulnerable.name',
|
||||||
|
|
@ -204,8 +212,8 @@ export const conditions = {
|
||||||
img: 'icons/magic/control/debuff-chains-shackle-movement-red.webp',
|
img: 'icons/magic/control/debuff-chains-shackle-movement-red.webp',
|
||||||
description: 'DAGGERHEART.CONFIG.Condition.restrained.description'
|
description: 'DAGGERHEART.CONFIG.Condition.restrained.description'
|
||||||
},
|
},
|
||||||
...defeatedConditions
|
...defeatedConditions()
|
||||||
};
|
});
|
||||||
|
|
||||||
export const defaultRestOptions = {
|
export const defaultRestOptions = {
|
||||||
shortRest: () => ({
|
shortRest: () => ({
|
||||||
|
|
@ -224,7 +232,7 @@ export const defaultRestOptions = {
|
||||||
actionType: 'action',
|
actionType: 'action',
|
||||||
chatDisplay: false,
|
chatDisplay: false,
|
||||||
target: {
|
target: {
|
||||||
type: 'self'
|
type: 'friendly'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: [
|
parts: [
|
||||||
|
|
@ -290,7 +298,7 @@ export const defaultRestOptions = {
|
||||||
actionType: 'action',
|
actionType: 'action',
|
||||||
chatDisplay: false,
|
chatDisplay: false,
|
||||||
target: {
|
target: {
|
||||||
type: 'self'
|
type: 'friendly'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: [
|
parts: [
|
||||||
|
|
@ -333,7 +341,7 @@ export const defaultRestOptions = {
|
||||||
actionType: 'action',
|
actionType: 'action',
|
||||||
chatDisplay: false,
|
chatDisplay: false,
|
||||||
target: {
|
target: {
|
||||||
type: 'self'
|
type: 'friendly'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: [
|
parts: [
|
||||||
|
|
@ -399,7 +407,7 @@ export const defaultRestOptions = {
|
||||||
actionType: 'action',
|
actionType: 'action',
|
||||||
chatDisplay: false,
|
chatDisplay: false,
|
||||||
target: {
|
target: {
|
||||||
type: 'self'
|
type: 'friendly'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: [
|
parts: [
|
||||||
|
|
@ -561,6 +569,19 @@ export const refreshTypes = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const itemAbilityCosts = {
|
||||||
|
resource: {
|
||||||
|
id: 'resource',
|
||||||
|
label: 'DAGGERHEART.GENERAL.resource',
|
||||||
|
group: 'Global'
|
||||||
|
},
|
||||||
|
quantity: {
|
||||||
|
id: 'quantity',
|
||||||
|
label: 'DAGGERHEART.GENERAL.quantity',
|
||||||
|
group: 'Global'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const abilityCosts = {
|
export const abilityCosts = {
|
||||||
hitPoints: {
|
hitPoints: {
|
||||||
id: 'hitPoints',
|
id: 'hitPoints',
|
||||||
|
|
@ -574,33 +595,46 @@ export const abilityCosts = {
|
||||||
},
|
},
|
||||||
hope: {
|
hope: {
|
||||||
id: 'hope',
|
id: 'hope',
|
||||||
label: 'Hope',
|
label: 'DAGGERHEART.CONFIG.HealingType.hope.name',
|
||||||
group: 'TYPES.Actor.character'
|
group: 'TYPES.Actor.character'
|
||||||
},
|
},
|
||||||
armor: {
|
armor: {
|
||||||
id: 'armor',
|
id: 'armor',
|
||||||
label: 'Armor Slot',
|
label: 'DAGGERHEART.CONFIG.HealingType.armor.name',
|
||||||
group: 'TYPES.Actor.character'
|
group: 'TYPES.Actor.character'
|
||||||
},
|
},
|
||||||
fear: {
|
fear: {
|
||||||
id: 'fear',
|
id: 'fear',
|
||||||
label: 'Fear',
|
label: 'DAGGERHEART.CONFIG.HealingType.fear.name',
|
||||||
group: 'TYPES.Actor.adversary'
|
group: 'TYPES.Actor.adversary'
|
||||||
}
|
},
|
||||||
|
resource: itemAbilityCosts.resource
|
||||||
};
|
};
|
||||||
|
|
||||||
export const countdownTypes = {
|
export const countdownProgressionTypes = {
|
||||||
spotlight: {
|
actionRoll: {
|
||||||
id: 'spotlight',
|
id: 'actionRoll',
|
||||||
label: 'DAGGERHEART.CONFIG.CountdownType.spotlight'
|
label: 'DAGGERHEART.CONFIG.CountdownType.actionRoll'
|
||||||
},
|
},
|
||||||
characterAttack: {
|
characterAttack: {
|
||||||
id: 'characterAttack',
|
id: 'characterAttack',
|
||||||
label: 'DAGGERHEART.CONFIG.CountdownType.characterAttack'
|
label: 'DAGGERHEART.CONFIG.CountdownType.characterAttack'
|
||||||
},
|
},
|
||||||
|
characterSpotlight: {
|
||||||
|
id: 'characterSpotlight',
|
||||||
|
label: 'DAGGERHEART.CONFIG.CountdownType.characterSpotlight'
|
||||||
|
},
|
||||||
custom: {
|
custom: {
|
||||||
id: 'custom',
|
id: 'custom',
|
||||||
label: 'DAGGERHEART.CONFIG.CountdownType.custom'
|
label: 'DAGGERHEART.CONFIG.CountdownType.custom'
|
||||||
|
},
|
||||||
|
fear: {
|
||||||
|
id: 'fear',
|
||||||
|
label: 'DAGGERHEART.CONFIG.CountdownType.fear'
|
||||||
|
},
|
||||||
|
spotlight: {
|
||||||
|
id: 'spotlight',
|
||||||
|
label: 'DAGGERHEART.CONFIG.CountdownType.spotlight'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
export const rollTypes = {
|
export const rollTypes = {
|
||||||
|
|
@ -624,8 +658,76 @@ export const rollTypes = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const attributionSources = {
|
||||||
|
daggerheart: {
|
||||||
|
label: 'Daggerheart',
|
||||||
|
values: [{ label: 'Daggerheart SRD' }]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const fearDisplay = {
|
export const fearDisplay = {
|
||||||
token: { value: 'token', label: 'DAGGERHEART.SETTINGS.Appearance.fearDisplay.token' },
|
token: { value: 'token', label: 'DAGGERHEART.SETTINGS.Appearance.fearDisplay.token' },
|
||||||
bar: { value: 'bar', label: 'DAGGERHEART.SETTINGS.Appearance.fearDisplay.bar' },
|
bar: { value: 'bar', label: 'DAGGERHEART.SETTINGS.Appearance.fearDisplay.bar' },
|
||||||
hide: { value: 'hide', label: 'DAGGERHEART.SETTINGS.Appearance.fearDisplay.hide' }
|
hide: { value: 'hide', label: 'DAGGERHEART.SETTINGS.Appearance.fearDisplay.hide' }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const basicOwnershiplevels = {
|
||||||
|
0: { value: 0, label: 'OWNERSHIP.NONE' },
|
||||||
|
2: { value: 2, label: 'OWNERSHIP.OBSERVER' },
|
||||||
|
3: { value: 3, label: 'OWNERSHIP.OWNER' }
|
||||||
|
};
|
||||||
|
|
||||||
|
export const simpleOwnershiplevels = {
|
||||||
|
[-1]: { value: -1, label: 'OWNERSHIP.INHERIT' },
|
||||||
|
...basicOwnershiplevels
|
||||||
|
};
|
||||||
|
|
||||||
|
export const countdownBaseTypes = {
|
||||||
|
narrative: {
|
||||||
|
id: 'narrative',
|
||||||
|
label: 'DAGGERHEART.APPLICATIONS.Countdown.types.narrative'
|
||||||
|
},
|
||||||
|
encounter: {
|
||||||
|
id: 'encounter',
|
||||||
|
label: 'DAGGERHEART.APPLICATIONS.Countdown.types.encounter'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const countdownLoopingTypes = {
|
||||||
|
noLooping: {
|
||||||
|
id: 'noLooping',
|
||||||
|
label: 'DAGGERHEART.APPLICATIONS.Countdown.loopingTypes.noLooping'
|
||||||
|
},
|
||||||
|
looping: {
|
||||||
|
id: 'looping',
|
||||||
|
label: 'DAGGERHEART.APPLICATIONS.Countdown.loopingTypes.looping'
|
||||||
|
},
|
||||||
|
increasing: {
|
||||||
|
id: 'increasing',
|
||||||
|
label: 'DAGGERHEART.APPLICATIONS.Countdown.loopingTypes.increasing'
|
||||||
|
},
|
||||||
|
decreasing: {
|
||||||
|
id: 'decreasing',
|
||||||
|
label: 'DAGGERHEART.APPLICATIONS.Countdown.loopingTypes.decreasing'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const countdownAppMode = {
|
||||||
|
textIcon: 'text-icon',
|
||||||
|
iconOnly: 'icon-only'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sceneRangeMeasurementSetting = {
|
||||||
|
disable: {
|
||||||
|
id: 'disable',
|
||||||
|
label: 'Disable Daggerheart Range Measurement'
|
||||||
|
},
|
||||||
|
default: {
|
||||||
|
id: 'default',
|
||||||
|
label: 'Default'
|
||||||
|
},
|
||||||
|
custom: {
|
||||||
|
id: 'custom',
|
||||||
|
label: 'Custom'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
||||||
5
module/config/hooksConfig.mjs
Normal file
5
module/config/hooksConfig.mjs
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
const hooksConfig = {
|
||||||
|
effectDisplayToggle: 'DHEffectDisplayToggle'
|
||||||
|
};
|
||||||
|
|
||||||
|
export default hooksConfig;
|
||||||
|
|
@ -2,404 +2,558 @@ export const typeConfig = {
|
||||||
adversaries: {
|
adversaries: {
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
key: "system.tier",
|
key: 'system.tier',
|
||||||
label: "Tier"
|
label: 'DAGGERHEART.GENERAL.Tiers.singular'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "system.type",
|
key: 'system.type',
|
||||||
label: "Type"
|
label: 'DAGGERHEART.GENERAL.type'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
filters: [
|
filters: [
|
||||||
{
|
{
|
||||||
key: "system.tier",
|
key: 'system.tier',
|
||||||
label: "Tier",
|
label: 'DAGGERHEART.GENERAL.Tiers.singular',
|
||||||
field: 'system.api.models.actors.DhAdversary.schema.fields.tier'
|
field: 'system.api.models.actors.DhAdversary.schema.fields.tier'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "system.type",
|
key: 'system.type',
|
||||||
label: "Type",
|
label: 'DAGGERHEART.GENERAL.type',
|
||||||
field: 'system.api.models.actors.DhAdversary.schema.fields.type'
|
field: 'system.api.models.actors.DhAdversary.schema.fields.type'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "system.difficulty",
|
key: 'system.difficulty',
|
||||||
name: "difficulty.min",
|
name: 'difficulty.min',
|
||||||
label: "Difficulty (Min)",
|
label: 'DAGGERHEART.UI.ItemBrowser.difficultyMin',
|
||||||
field: 'system.api.models.actors.DhAdversary.schema.fields.difficulty',
|
field: 'system.api.models.actors.DhAdversary.schema.fields.difficulty',
|
||||||
operator: "gte"
|
operator: 'gte'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "system.difficulty",
|
key: 'system.difficulty',
|
||||||
name: "difficulty.max",
|
name: 'difficulty.max',
|
||||||
label: "Difficulty (Max)",
|
label: 'DAGGERHEART.UI.ItemBrowser.difficultyMax',
|
||||||
field: 'system.api.models.actors.DhAdversary.schema.fields.difficulty',
|
field: 'system.api.models.actors.DhAdversary.schema.fields.difficulty',
|
||||||
operator: "lte"
|
operator: 'lte'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "system.resources.hitPoints.max",
|
key: 'system.resources.hitPoints.max',
|
||||||
name: "hp.min",
|
name: 'hp.min',
|
||||||
label: "Hit Points (Min)",
|
label: 'DAGGERHEART.UI.ItemBrowser.hitPointsMin',
|
||||||
field: 'system.api.models.actors.DhAdversary.schema.fields.resources.fields.hitPoints.fields.max',
|
field: 'system.api.models.actors.DhAdversary.schema.fields.resources.fields.hitPoints.fields.max',
|
||||||
operator: "gte"
|
operator: 'gte'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "system.resources.hitPoints.max",
|
key: 'system.resources.hitPoints.max',
|
||||||
name: "hp.max",
|
name: 'hp.max',
|
||||||
label: "Hit Points (Max)",
|
label: 'DAGGERHEART.UI.ItemBrowser.hitPointsMax',
|
||||||
field: 'system.api.models.actors.DhAdversary.schema.fields.resources.fields.hitPoints.fields.max',
|
field: 'system.api.models.actors.DhAdversary.schema.fields.resources.fields.hitPoints.fields.max',
|
||||||
operator: "lte"
|
operator: 'lte'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "system.resources.stress.max",
|
key: 'system.resources.stress.max',
|
||||||
name: "stress.min",
|
name: 'stress.min',
|
||||||
label: "Stress (Min)",
|
label: 'DAGGERHEART.UI.ItemBrowser.stressMin',
|
||||||
field: 'system.api.models.actors.DhAdversary.schema.fields.resources.fields.stress.fields.max',
|
field: 'system.api.models.actors.DhAdversary.schema.fields.resources.fields.stress.fields.max',
|
||||||
operator: "gte"
|
operator: 'gte'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "system.resources.stress.max",
|
key: 'system.resources.stress.max',
|
||||||
name: "stress.max",
|
name: 'stress.max',
|
||||||
label: "Stress (Max)",
|
label: 'DAGGERHEART.UI.ItemBrowser.stressMax',
|
||||||
field: 'system.api.models.actors.DhAdversary.schema.fields.resources.fields.stress.fields.max',
|
field: 'system.api.models.actors.DhAdversary.schema.fields.resources.fields.stress.fields.max',
|
||||||
operator: "lte"
|
operator: 'lte'
|
||||||
},
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
items: {
|
items: {
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
key: "type",
|
key: 'type',
|
||||||
label: "Type"
|
label: 'DAGGERHEART.GENERAL.type'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "system.secondary",
|
key: 'system.secondary',
|
||||||
label: "Subtype",
|
label: 'DAGGERHEART.UI.ItemBrowser.subtype',
|
||||||
format: (isSecondary) => isSecondary ? "secondary" : (isSecondary === false ? "primary" : '-')
|
format: isSecondary => (isSecondary ? 'secondary' : isSecondary === false ? 'primary' : '-')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "system.tier",
|
key: 'system.tier',
|
||||||
label: "Tier"
|
label: 'DAGGERHEART.GENERAL.Tiers.singular'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
filters: [
|
filters: [
|
||||||
{
|
{
|
||||||
key: "type",
|
key: 'type',
|
||||||
label: "Type",
|
label: 'DAGGERHEART.GENERAL.type',
|
||||||
choices: () => CONFIG.Item.documentClass.TYPES.filter(t => ["armor", "weapon", "consumable", "loot"].includes(t)).map(t => ({ value: t, label: t }))
|
choices: () =>
|
||||||
|
CONFIG.Item.documentClass.TYPES.filter(t =>
|
||||||
|
['armor', 'weapon', 'consumable', 'loot'].includes(t)
|
||||||
|
).map(t => ({ value: t, label: t }))
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "system.secondary",
|
key: 'system.secondary',
|
||||||
label: "Subtype",
|
label: 'DAGGERHEART.UI.ItemBrowser.subtype',
|
||||||
choices: [
|
choices: [
|
||||||
{ value: false, label: "Primary Weapon"},
|
{ value: false, label: 'DAGGERHEART.ITEMS.Weapon.primaryWeapon' },
|
||||||
{ value: true, label: "Secondary Weapon"}
|
{ value: true, label: 'DAGGERHEART.ITEMS.Weapon.secondaryWeapon' }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "system.tier",
|
key: 'system.tier',
|
||||||
label: "Tier",
|
label: 'DAGGERHEART.GENERAL.Tiers.singular',
|
||||||
choices: [{ value: "1", label: "1"}, { value: "2", label: "2"}, { value: "3", label: "3"}, { value: "4", label: "4"}]
|
choices: [
|
||||||
|
{ value: '1', label: '1' },
|
||||||
|
{ value: '2', label: '2' },
|
||||||
|
{ value: '3', label: '3' },
|
||||||
|
{ value: '4', label: '4' }
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "system.burden",
|
key: 'system.burden',
|
||||||
label: "Burden",
|
label: 'DAGGERHEART.GENERAL.burden',
|
||||||
field: 'system.api.models.items.DHWeapon.schema.fields.burden'
|
field: 'system.api.models.items.DHWeapon.schema.fields.burden'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "system.attack.roll.trait",
|
key: 'system.attack.roll.trait',
|
||||||
label: "Trait",
|
label: 'DAGGERHEART.GENERAL.Trait.single',
|
||||||
field: 'system.api.models.actions.actionsTypes.attack.schema.fields.roll.fields.trait'
|
field: 'system.api.models.actions.actionsTypes.attack.schema.fields.roll.fields.trait'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "system.attack.range",
|
key: 'system.attack.range',
|
||||||
label: "Range",
|
label: 'DAGGERHEART.GENERAL.range',
|
||||||
field: 'system.api.models.actions.actionsTypes.attack.schema.fields.range'
|
field: 'system.api.models.actions.actionsTypes.attack.schema.fields.range'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "system.baseScore",
|
key: 'system.baseScore',
|
||||||
name: "armor.min",
|
name: 'armor.min',
|
||||||
label: "Armor Score (Min)",
|
label: 'DAGGERHEART.UI.ItemBrowser.armorScoreMin',
|
||||||
field: 'system.api.models.items.DHArmor.schema.fields.baseScore',
|
field: 'system.api.models.items.DHArmor.schema.fields.baseScore',
|
||||||
operator: "gte"
|
operator: 'gte'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "system.baseScore",
|
key: 'system.baseScore',
|
||||||
name: "armor.max",
|
name: 'armor.max',
|
||||||
label: "Armor Score (Max)",
|
label: 'DAGGERHEART.UI.ItemBrowser.armorScoreMax',
|
||||||
field: 'system.api.models.items.DHArmor.schema.fields.baseScore',
|
field: 'system.api.models.items.DHArmor.schema.fields.baseScore',
|
||||||
operator: "lte"
|
operator: 'lte'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "system.itemFeatures",
|
key: 'system.itemFeatures',
|
||||||
label: "Features",
|
label: 'DAGGERHEART.GENERAL.features',
|
||||||
choices: () => [...Object.entries(CONFIG.DH.ITEM.weaponFeatures), ...Object.entries(CONFIG.DH.ITEM.armorFeatures)].map(([k,v]) => ({ value: k, label: v.label})),
|
choices: () =>
|
||||||
operator: "contains3"
|
[
|
||||||
|
...Object.entries(CONFIG.DH.ITEM.weaponFeatures),
|
||||||
|
...Object.entries(CONFIG.DH.ITEM.armorFeatures)
|
||||||
|
].map(([k, v]) => ({ value: k, label: v.label })),
|
||||||
|
operator: 'contains3'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
weapons: {
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
key: 'system.secondary',
|
||||||
|
label: 'DAGGERHEART.UI.ItemBrowser.subtype',
|
||||||
|
format: isSecondary => (isSecondary ? 'secondary' : isSecondary === false ? 'primary' : '-')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'system.tier',
|
||||||
|
label: 'DAGGERHEART.GENERAL.Tiers.singular'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
key: 'system.secondary',
|
||||||
|
label: 'DAGGERHEART.UI.ItemBrowser.subtype',
|
||||||
|
choices: [
|
||||||
|
{ value: false, label: 'DAGGERHEART.ITEMS.Weapon.primaryWeapon' },
|
||||||
|
{ value: true, label: 'DAGGERHEART.ITEMS.Weapon.secondaryWeapon' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'system.tier',
|
||||||
|
label: 'DAGGERHEART.GENERAL.Tiers.singular',
|
||||||
|
choices: [
|
||||||
|
{ value: '1', label: '1' },
|
||||||
|
{ value: '2', label: '2' },
|
||||||
|
{ value: '3', label: '3' },
|
||||||
|
{ value: '4', label: '4' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'system.burden',
|
||||||
|
label: 'DAGGERHEART.GENERAL.burden',
|
||||||
|
field: 'system.api.models.items.DHWeapon.schema.fields.burden'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'system.attack.roll.trait',
|
||||||
|
label: 'DAGGERHEART.GENERAL.Trait.single',
|
||||||
|
field: 'system.api.models.actions.actionsTypes.attack.schema.fields.roll.fields.trait'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'system.attack.range',
|
||||||
|
label: 'DAGGERHEART.GENERAL.range',
|
||||||
|
field: 'system.api.models.actions.actionsTypes.attack.schema.fields.range'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'system.itemFeatures',
|
||||||
|
label: 'DAGGERHEART.GENERAL.features',
|
||||||
|
choices: () =>
|
||||||
|
Object.entries(CONFIG.DH.ITEM.weaponFeatures).map(([k, v]) => ({ value: k, label: v.label })),
|
||||||
|
operator: 'contains3'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
armors: {
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
key: 'system.tier',
|
||||||
|
label: 'DAGGERHEART.GENERAL.Tiers.singular'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
key: 'system.tier',
|
||||||
|
label: 'DAGGERHEART.GENERAL.Tiers.singular',
|
||||||
|
choices: [
|
||||||
|
{ value: '1', label: '1' },
|
||||||
|
{ value: '2', label: '2' },
|
||||||
|
{ value: '3', label: '3' },
|
||||||
|
{ value: '4', label: '4' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'system.baseScore',
|
||||||
|
name: 'armor.min',
|
||||||
|
label: 'DAGGERHEART.UI.ItemBrowser.armorScoreMin',
|
||||||
|
field: 'system.api.models.items.DHArmor.schema.fields.baseScore',
|
||||||
|
operator: 'gte'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'system.baseScore',
|
||||||
|
name: 'armor.max',
|
||||||
|
label: 'DAGGERHEART.UI.ItemBrowser.armorScoreMax',
|
||||||
|
field: 'system.api.models.items.DHArmor.schema.fields.baseScore',
|
||||||
|
operator: 'lte'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'system.itemFeatures',
|
||||||
|
label: 'DAGGERHEART.GENERAL.features',
|
||||||
|
choices: () =>
|
||||||
|
Object.entries(CONFIG.DH.ITEM.armorFeatures).map(([k, v]) => ({ value: k, label: v.label })),
|
||||||
|
operator: 'contains3'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
features: {
|
features: {
|
||||||
columns: [
|
columns: [],
|
||||||
|
filters: []
|
||||||
],
|
|
||||||
filters: [
|
|
||||||
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
cards: {
|
cards: {
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
key: "system.type",
|
key: 'system.type',
|
||||||
label: "Type"
|
label: 'DAGGERHEART.GENERAL.type'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "system.domain",
|
key: 'system.domain',
|
||||||
label: "Domain"
|
label: 'DAGGERHEART.GENERAL.Domain.single'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "system.level",
|
key: 'system.level',
|
||||||
label: "Level"
|
label: 'DAGGERHEART.GENERAL.level'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
filters: [
|
filters: [
|
||||||
{
|
{
|
||||||
key: "system.type",
|
key: 'system.type',
|
||||||
label: "Type",
|
label: 'DAGGERHEART.GENERAL.type',
|
||||||
field: 'system.api.models.items.DHDomainCard.schema.fields.type'
|
field: 'system.api.models.items.DHDomainCard.schema.fields.type'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "system.domain",
|
key: 'system.domain',
|
||||||
label: "Domain",
|
label: 'DAGGERHEART.GENERAL.Domain.single',
|
||||||
field: 'system.api.models.items.DHDomainCard.schema.fields.domain',
|
field: 'system.api.models.items.DHDomainCard.schema.fields.domain',
|
||||||
operator: "contains2"
|
operator: 'contains2'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "system.level",
|
key: 'system.level',
|
||||||
name: "level.min",
|
name: 'level.min',
|
||||||
label: "Level (Min)",
|
label: 'DAGGERHEART.UI.ItemBrowser.levelMin',
|
||||||
field: 'system.api.models.items.DHDomainCard.schema.fields.level',
|
field: 'system.api.models.items.DHDomainCard.schema.fields.level',
|
||||||
operator: "gte"
|
operator: 'gte'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "system.level",
|
key: 'system.level',
|
||||||
name: "level.max",
|
name: 'level.max',
|
||||||
label: "Level (Max)",
|
label: 'DAGGERHEART.UI.ItemBrowser.levelMax',
|
||||||
field: 'system.api.models.items.DHDomainCard.schema.fields.level',
|
field: 'system.api.models.items.DHDomainCard.schema.fields.level',
|
||||||
operator: "lte"
|
operator: 'lte'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "system.recallCost",
|
key: 'system.recallCost',
|
||||||
name: "recall.min",
|
name: 'recall.min',
|
||||||
label: "Recall Cost (Min)",
|
label: 'DAGGERHEART.UI.ItemBrowser.recallCostMin',
|
||||||
field: 'system.api.models.items.DHDomainCard.schema.fields.recallCost',
|
field: 'system.api.models.items.DHDomainCard.schema.fields.recallCost',
|
||||||
operator: "gte"
|
operator: 'gte'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "system.recallCost",
|
key: 'system.recallCost',
|
||||||
name: "recall.max",
|
name: 'recall.max',
|
||||||
label: "Recall Cost (Max)",
|
label: 'DAGGERHEART.UI.ItemBrowser.recallCostMax',
|
||||||
field: 'system.api.models.items.DHDomainCard.schema.fields.recallCost',
|
field: 'system.api.models.items.DHDomainCard.schema.fields.recallCost',
|
||||||
operator: "lte"
|
operator: 'lte'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
classes: {
|
classes: {
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
key: "system.evasion",
|
key: 'system.evasion',
|
||||||
label: "Evasion"
|
label: 'DAGGERHEART.GENERAL.evasion'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "system.hitPoints",
|
key: 'system.hitPoints',
|
||||||
label: "Hit Points"
|
label: 'DAGGERHEART.GENERAL.HitPoints.plural'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "system.domains",
|
key: 'system.domains',
|
||||||
label: "Domains"
|
label: 'DAGGERHEART.GENERAL.Domain.plural'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
filters: [
|
filters: [
|
||||||
{
|
{
|
||||||
key: "system.evasion",
|
key: 'system.evasion',
|
||||||
name: "evasion.min",
|
name: 'evasion.min',
|
||||||
label: "Evasion (Min)",
|
label: 'DAGGERHEART.UI.ItemBrowser.evasionMin',
|
||||||
field: 'system.api.models.items.DHClass.schema.fields.evasion',
|
field: 'system.api.models.items.DHClass.schema.fields.evasion',
|
||||||
operator: "gte"
|
operator: 'gte'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "system.evasion",
|
key: 'system.evasion',
|
||||||
name: "evasion.max",
|
name: 'evasion.max',
|
||||||
label: "Evasion (Max)",
|
label: 'DAGGERHEART.UI.ItemBrowser.evasionMax',
|
||||||
field: 'system.api.models.items.DHClass.schema.fields.evasion',
|
field: 'system.api.models.items.DHClass.schema.fields.evasion',
|
||||||
operator: "lte"
|
operator: 'lte'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "system.hitPoints",
|
key: 'system.hitPoints',
|
||||||
name: "hp.min",
|
name: 'hp.min',
|
||||||
label: "Hit Points (Min)",
|
label: 'DAGGERHEART.UI.ItemBrowser.hitPointsMin',
|
||||||
field: 'system.api.models.items.DHClass.schema.fields.hitPoints',
|
field: 'system.api.models.items.DHClass.schema.fields.hitPoints',
|
||||||
operator: "gte"
|
operator: 'gte'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "system.hitPoints",
|
key: 'system.hitPoints',
|
||||||
name: "hp.max",
|
name: 'hp.max',
|
||||||
label: "Hit Points (Max)",
|
label: 'DAGGERHEART.UI.ItemBrowser.hitPointsMax',
|
||||||
field: 'system.api.models.items.DHClass.schema.fields.hitPoints',
|
field: 'system.api.models.items.DHClass.schema.fields.hitPoints',
|
||||||
operator: "lte"
|
operator: 'lte'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "system.domains",
|
key: 'system.domains',
|
||||||
label: "Domains",
|
label: 'DAGGERHEART.GENERAL.Domain.plural',
|
||||||
choices: () => Object.values(CONFIG.DH.DOMAIN.domains).map(d => ({ value: d.id, label: d.label})),
|
choices: () => Object.values(CONFIG.DH.DOMAIN.allDomains()).map(d => ({ value: d.id, label: d.label })),
|
||||||
operator: "contains2"
|
operator: 'contains2'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
subclasses: {
|
subclasses: {
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
key: "id",
|
key: 'system.linkedClass',
|
||||||
label: "Class",
|
label: 'Class',
|
||||||
format: (id) => {
|
format: linkedClass => linkedClass?.name ?? 'DAGGERHEART.UI.ItemBrowser.missing'
|
||||||
return "";
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "system.spellcastingTrait",
|
key: 'system.spellcastingTrait',
|
||||||
label: "Spellcasting Trait"
|
label: 'DAGGERHEART.ITEMS.Subclass.spellcastingTrait'
|
||||||
}
|
|
||||||
],
|
|
||||||
filters: []
|
|
||||||
},
|
|
||||||
beastforms: {
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
key: "system.tier",
|
|
||||||
label: "Tier"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "system.mainTrait",
|
|
||||||
label: "Main Trait"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
filters: [
|
filters: [
|
||||||
{
|
{
|
||||||
key: "system.tier",
|
key: 'system.linkedClass.uuid',
|
||||||
label: "Tier",
|
label: 'Class',
|
||||||
|
choices: items => {
|
||||||
|
const list = items
|
||||||
|
.filter(item => item.system.linkedClass)
|
||||||
|
.map(item => ({
|
||||||
|
value: item.system.linkedClass.uuid,
|
||||||
|
label: item.system.linkedClass.name
|
||||||
|
}));
|
||||||
|
return list.reduce((a, c) => {
|
||||||
|
if (!a.find(i => i.value === c.value)) a.push(c);
|
||||||
|
return a;
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
beastforms: {
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
key: 'system.tier',
|
||||||
|
label: 'DAGGERHEART.GENERAL.Tiers.singular'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'system.mainTrait',
|
||||||
|
label: 'DAGGERHEART.GENERAL.Trait.single'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
key: 'system.tier',
|
||||||
|
label: 'DAGGERHEART.GENERAL.Tiers.singular',
|
||||||
field: 'system.api.models.items.DHBeastform.schema.fields.tier'
|
field: 'system.api.models.items.DHBeastform.schema.fields.tier'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "system.mainTrait",
|
key: 'system.mainTrait',
|
||||||
label: "Main Trait",
|
label: 'DAGGERHEART.GENERAL.Trait.single',
|
||||||
field: 'system.api.models.items.DHBeastform.schema.fields.mainTrait'
|
field: 'system.api.models.items.DHBeastform.schema.fields.mainTrait'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
export const compendiumConfig = {
|
export const compendiumConfig = {
|
||||||
"daggerheart": {
|
characters: {
|
||||||
id: "daggerheart",
|
id: 'characters',
|
||||||
label: "DAGGERHEART",
|
keys: ['characters'],
|
||||||
|
label: 'DAGGERHEART.UI.ItemBrowser.folders.characters',
|
||||||
|
type: ['character']
|
||||||
|
// listType: 'characters'
|
||||||
|
},
|
||||||
|
adversaries: {
|
||||||
|
id: 'adversaries',
|
||||||
|
keys: ['adversaries'],
|
||||||
|
label: 'DAGGERHEART.UI.ItemBrowser.folders.adversaries',
|
||||||
|
type: ['adversary'],
|
||||||
|
listType: 'adversaries'
|
||||||
|
},
|
||||||
|
ancestries: {
|
||||||
|
id: 'ancestries',
|
||||||
|
keys: ['ancestries'],
|
||||||
|
label: 'DAGGERHEART.UI.ItemBrowser.folders.ancestries',
|
||||||
|
type: ['ancestry']
|
||||||
|
/* folders: {
|
||||||
|
features: {
|
||||||
|
id: 'features',
|
||||||
|
keys: ['ancestries'],
|
||||||
|
label: 'DAGGERHEART.UI.ItemBrowser.folders.features',
|
||||||
|
type: ['feature']
|
||||||
|
}
|
||||||
|
} */
|
||||||
|
},
|
||||||
|
equipments: {
|
||||||
|
id: 'equipments',
|
||||||
|
keys: ['armors', 'weapons', 'consumables', 'loot'],
|
||||||
|
label: 'DAGGERHEART.UI.ItemBrowser.folders.equipment',
|
||||||
|
type: ['armor', 'weapon', 'consumable', 'loot'],
|
||||||
|
listType: 'items',
|
||||||
folders: {
|
folders: {
|
||||||
"adversaries": {
|
weapons: {
|
||||||
id: "adversaries",
|
id: 'weapons',
|
||||||
keys: ["adversaries"],
|
keys: ['weapons'],
|
||||||
label: "Adversaries",
|
label: 'DAGGERHEART.UI.ItemBrowser.folders.weapons',
|
||||||
type: ["adversary"],
|
type: ['weapon'],
|
||||||
listType: "adversaries"
|
listType: 'weapons'
|
||||||
},
|
},
|
||||||
"ancestries": {
|
armors: {
|
||||||
id: "ancestries",
|
id: 'armors',
|
||||||
keys: ["ancestries"],
|
keys: ['armors'],
|
||||||
label: "Ancestries",
|
label: 'DAGGERHEART.UI.ItemBrowser.folders.armors',
|
||||||
type: ["ancestry"],
|
type: ['armor'],
|
||||||
folders: {
|
listType: 'armors'
|
||||||
"features": {
|
},
|
||||||
id: "features",
|
consumables: {
|
||||||
keys: ["ancestries"],
|
id: 'consumables',
|
||||||
label: "Features",
|
keys: ['consumables'],
|
||||||
type: ["feature"]
|
label: 'DAGGERHEART.UI.ItemBrowser.folders.consumables',
|
||||||
|
type: ['consumable']
|
||||||
|
},
|
||||||
|
loots: {
|
||||||
|
id: 'loots',
|
||||||
|
keys: ['loots'],
|
||||||
|
label: 'DAGGERHEART.UI.ItemBrowser.folders.loots',
|
||||||
|
type: ['loot']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"equipments": {
|
classes: {
|
||||||
id: "equipments",
|
id: 'classes',
|
||||||
keys: ["armors", "weapons", "consumables", "loot"],
|
keys: ['classes'],
|
||||||
label: "Equipment",
|
label: 'DAGGERHEART.UI.ItemBrowser.folders.classes',
|
||||||
type: ["armor", "weapon", "consumable", "loot"],
|
type: ['class'],
|
||||||
listType: "items"
|
/* folders: {
|
||||||
|
features: {
|
||||||
|
id: 'features',
|
||||||
|
keys: ['classes'],
|
||||||
|
label: 'DAGGERHEART.UI.ItemBrowser.folders.features',
|
||||||
|
type: ['feature']
|
||||||
},
|
},
|
||||||
"classes": {
|
items: {
|
||||||
id: "classes",
|
id: 'items',
|
||||||
keys: ["classes"],
|
keys: ['classes'],
|
||||||
label: "Classes",
|
label: 'DAGGERHEART.UI.ItemBrowser.folders.items',
|
||||||
type: ["class"],
|
type: ['armor', 'weapon', 'consumable', 'loot'],
|
||||||
folders: {
|
listType: 'items'
|
||||||
"features": {
|
}
|
||||||
id: "features",
|
}, */
|
||||||
keys: ["classes"],
|
listType: 'classes'
|
||||||
label: "Features",
|
|
||||||
type: ["feature"]
|
|
||||||
},
|
},
|
||||||
"items": {
|
subclasses: {
|
||||||
id: "items",
|
id: 'subclasses',
|
||||||
keys: ["classes"],
|
keys: ['subclasses'],
|
||||||
label: "Items",
|
label: 'DAGGERHEART.UI.ItemBrowser.folders.subclasses',
|
||||||
type: ["armor", "weapon", "consumable", "loot"],
|
type: ['subclass'],
|
||||||
listType: "items"
|
listType: 'subclasses'
|
||||||
}
|
|
||||||
},
|
},
|
||||||
listType: "classes"
|
domains: {
|
||||||
|
id: 'domains',
|
||||||
|
keys: ['domains'],
|
||||||
|
label: 'DAGGERHEART.UI.ItemBrowser.folders.domainCards',
|
||||||
|
type: ['domainCard'],
|
||||||
|
listType: 'cards'
|
||||||
},
|
},
|
||||||
"subclasses": {
|
communities: {
|
||||||
id: "subclasses",
|
id: 'communities',
|
||||||
keys: ["subclasses"],
|
keys: ['communities'],
|
||||||
label: "Subclasses",
|
label: 'DAGGERHEART.UI.ItemBrowser.folders.communities',
|
||||||
type: ["subclass"],
|
type: ['community']
|
||||||
listType: "subclasses"
|
/* folders: {
|
||||||
|
features: {
|
||||||
|
id: 'features',
|
||||||
|
keys: ['communities'],
|
||||||
|
label: 'DAGGERHEART.UI.ItemBrowser.folders.features',
|
||||||
|
type: ['feature']
|
||||||
|
}
|
||||||
|
} */
|
||||||
},
|
},
|
||||||
"domains": {
|
environments: {
|
||||||
id: "domains",
|
id: 'environments',
|
||||||
keys: ["domains"],
|
keys: ['environments'],
|
||||||
label: "Domain Cards",
|
label: 'DAGGERHEART.UI.ItemBrowser.folders.environments',
|
||||||
type: ["domainCard"],
|
type: ['environment']
|
||||||
listType: "cards"
|
|
||||||
},
|
},
|
||||||
"communities": {
|
beastforms: {
|
||||||
id: "communities",
|
id: 'beastforms',
|
||||||
keys: ["communities"],
|
keys: ['beastforms'],
|
||||||
label: "Communities",
|
label: 'DAGGERHEART.UI.ItemBrowser.folders.beastforms',
|
||||||
type: ["community"],
|
type: ['beastform'],
|
||||||
folders: {
|
listType: 'beastforms'
|
||||||
"features": {
|
/* folders: {
|
||||||
id: "features",
|
features: {
|
||||||
keys: ["communities"],
|
id: 'features',
|
||||||
label: "Features",
|
keys: ['beastforms'],
|
||||||
type: ["feature"]
|
label: 'DAGGERHEART.UI.ItemBrowser.folders.features',
|
||||||
}
|
type: ['feature']
|
||||||
}
|
}
|
||||||
|
} */
|
||||||
},
|
},
|
||||||
"environments": {
|
features: {
|
||||||
id: "environments",
|
id: 'features',
|
||||||
keys: ["environments"],
|
keys: ['features'],
|
||||||
label: "Environments",
|
label: 'DAGGERHEART.UI.ItemBrowser.folders.features',
|
||||||
type: ["environment"]
|
type: ['feature']
|
||||||
},
|
|
||||||
"beastforms": {
|
|
||||||
id: "beastforms",
|
|
||||||
keys: ["beastforms"],
|
|
||||||
label: "Beastforms",
|
|
||||||
type: ["beastform"],
|
|
||||||
listType: "beastforms",
|
|
||||||
folders: {
|
|
||||||
"features": {
|
|
||||||
id: "features",
|
|
||||||
keys: ["beastforms"],
|
|
||||||
label: "Features",
|
|
||||||
type: ["feature"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ export const armorFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'damage',
|
type: 'damage',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.ArmorFeature.burning.actions.burn.name',
|
name: 'DAGGERHEART.CONFIG.ArmorFeature.burning.actions.burn.name',
|
||||||
description: 'DAGGERHEART.CONFIG.ArmorFeature.burning.actions.burn.description',
|
description: 'DAGGERHEART.CONFIG.ArmorFeature.burning.actions.burn.description',
|
||||||
|
|
@ -174,7 +173,6 @@ export const armorFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.ArmorFeature.hopeful.actions.hope.name',
|
name: 'DAGGERHEART.CONFIG.ArmorFeature.hopeful.actions.hope.name',
|
||||||
description: 'DAGGERHEART.CONFIG.ArmorFeature.hopeful.actions.hope.description',
|
description: 'DAGGERHEART.CONFIG.ArmorFeature.hopeful.actions.hope.description',
|
||||||
|
|
@ -188,7 +186,6 @@ export const armorFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.ArmorFeature.impenetrable.actions.impenetrable.name',
|
name: 'DAGGERHEART.CONFIG.ArmorFeature.impenetrable.actions.impenetrable.name',
|
||||||
description: 'DAGGERHEART.CONFIG.ArmorFeature.impenetrable.actions.impenetrable.description',
|
description: 'DAGGERHEART.CONFIG.ArmorFeature.impenetrable.actions.impenetrable.description',
|
||||||
|
|
@ -231,7 +228,6 @@ export const armorFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.ArmorFeature.painful.actions.pain.name',
|
name: 'DAGGERHEART.CONFIG.ArmorFeature.painful.actions.pain.name',
|
||||||
description: 'DAGGERHEART.CONFIG.ArmorFeature.painful.actions.pain.description',
|
description: 'DAGGERHEART.CONFIG.ArmorFeature.painful.actions.pain.description',
|
||||||
|
|
@ -269,7 +265,6 @@ export const armorFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.ArmorFeature.quiet.actions.quiet.name',
|
name: 'DAGGERHEART.CONFIG.ArmorFeature.quiet.actions.quiet.name',
|
||||||
description: 'DAGGERHEART.CONFIG.ArmorFeature.quiet.actions.quiet.description',
|
description: 'DAGGERHEART.CONFIG.ArmorFeature.quiet.actions.quiet.description',
|
||||||
|
|
@ -306,7 +301,6 @@ export const armorFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'attack',
|
type: 'attack',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.ArmorFeature.resilient.actions.resilient.name',
|
name: 'DAGGERHEART.CONFIG.ArmorFeature.resilient.actions.resilient.name',
|
||||||
description: 'DAGGERHEART.CONFIG.ArmorFeature.resilient.actions.resilient.description',
|
description: 'DAGGERHEART.CONFIG.ArmorFeature.resilient.actions.resilient.description',
|
||||||
|
|
@ -353,7 +347,6 @@ export const armorFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.ArmorFeature.shifting.actions.shift.name',
|
name: 'DAGGERHEART.CONFIG.ArmorFeature.shifting.actions.shift.name',
|
||||||
description: 'DAGGERHEART.CONFIG.ArmorFeature.shifting.actions.shift.description',
|
description: 'DAGGERHEART.CONFIG.ArmorFeature.shifting.actions.shift.description',
|
||||||
|
|
@ -373,7 +366,6 @@ export const armorFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'attack',
|
type: 'attack',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.ArmorFeature.timeslowing.actions.slowTime.name',
|
name: 'DAGGERHEART.CONFIG.ArmorFeature.timeslowing.actions.slowTime.name',
|
||||||
description: 'DAGGERHEART.CONFIG.ArmorFeature.timeslowing.actions.slowTime.description',
|
description: 'DAGGERHEART.CONFIG.ArmorFeature.timeslowing.actions.slowTime.description',
|
||||||
|
|
@ -401,7 +393,6 @@ export const armorFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.ArmorFeature.truthseeking.actions.truthseeking.name',
|
name: 'DAGGERHEART.CONFIG.ArmorFeature.truthseeking.actions.truthseeking.name',
|
||||||
description: 'DAGGERHEART.CONFIG.ArmorFeature.truthseeking.actions.truthseeking.description',
|
description: 'DAGGERHEART.CONFIG.ArmorFeature.truthseeking.actions.truthseeking.description',
|
||||||
|
|
@ -444,7 +435,8 @@ export const armorFeatures = {
|
||||||
{
|
{
|
||||||
key: 'system.resistance.magical.reduction',
|
key: 'system.resistance.magical.reduction',
|
||||||
mode: 2,
|
mode: 2,
|
||||||
value: '@system.armorScore'
|
value: '@system.armorScore',
|
||||||
|
priority: 21
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -452,6 +444,43 @@ export const armorFeatures = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const allArmorFeatures = () => {
|
||||||
|
const homebrewFeatures = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).itemFeatures
|
||||||
|
.armorFeatures;
|
||||||
|
return {
|
||||||
|
...armorFeatures,
|
||||||
|
...Object.keys(homebrewFeatures).reduce((acc, key) => {
|
||||||
|
const feature = homebrewFeatures[key];
|
||||||
|
const actions = feature.actions.map(action => ({
|
||||||
|
...action,
|
||||||
|
effects: action.effects.map(effect => feature.effects.find(x => x.id === effect._id)),
|
||||||
|
type: action.type
|
||||||
|
}));
|
||||||
|
const actionEffects = actions.flatMap(a => a.effects);
|
||||||
|
|
||||||
|
const effects = feature.effects.filter(effect => !actionEffects.some(x => x.id === effect.id));
|
||||||
|
|
||||||
|
acc[key] = { ...feature, label: feature.name, effects, actions };
|
||||||
|
return acc;
|
||||||
|
}, {})
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const orderedArmorFeatures = () => {
|
||||||
|
const homebrewFeatures = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).itemFeatures
|
||||||
|
.armorFeatures;
|
||||||
|
const allFeatures = { ...armorFeatures, ...homebrewFeatures };
|
||||||
|
const all = Object.keys(allFeatures).map(key => {
|
||||||
|
const feature = allFeatures[key];
|
||||||
|
return {
|
||||||
|
...feature,
|
||||||
|
id: key,
|
||||||
|
label: feature.label ?? feature.name
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return Object.values(all).sort((a, b) => game.i18n.localize(a.label).localeCompare(game.i18n.localize(b.label)));
|
||||||
|
};
|
||||||
|
|
||||||
export const weaponFeatures = {
|
export const weaponFeatures = {
|
||||||
barrier: {
|
barrier: {
|
||||||
label: 'DAGGERHEART.CONFIG.WeaponFeature.barrier.name',
|
label: 'DAGGERHEART.CONFIG.WeaponFeature.barrier.name',
|
||||||
|
|
@ -500,7 +529,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.bouncing.actions.bounce.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.bouncing.actions.bounce.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.bouncing.actions.bounce.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.bouncing.actions.bounce.description',
|
||||||
|
|
@ -545,7 +573,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.brutal.actions.addDamage.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.brutal.actions.addDamage.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.brutal.actions.addDamage.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.brutal.actions.addDamage.description',
|
||||||
|
|
@ -559,7 +586,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.burning.actions.burn.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.burning.actions.burn.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.burning.actions.burn.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.burning.actions.burn.description',
|
||||||
|
|
@ -573,7 +599,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.charged.actions.markStress.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.charged.actions.markStress.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.charged.actions.markStress.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.charged.actions.markStress.description',
|
||||||
|
|
@ -610,7 +635,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.concussive.actions.attack.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.concussive.actions.attack.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.concussive.actions.attack.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.concussive.actions.attack.description',
|
||||||
|
|
@ -651,7 +675,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.deadly.actions.extraDamage.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.deadly.actions.extraDamage.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.deadly.actions.extraDamage.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.deadly.actions.extraDamage.description',
|
||||||
|
|
@ -665,7 +688,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.deflecting.actions.deflect.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.deflecting.actions.deflect.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.deflecting.actions.deflect.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.deflecting.actions.deflect.description',
|
||||||
|
|
@ -688,7 +710,8 @@ export const weaponFeatures = {
|
||||||
{
|
{
|
||||||
key: 'system.evasion',
|
key: 'system.evasion',
|
||||||
mode: 2,
|
mode: 2,
|
||||||
value: '@system.armorScore'
|
value: '@system.armorScore',
|
||||||
|
priority: 21
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -702,7 +725,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'damage',
|
type: 'damage',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.destructive.actions.attack.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.destructive.actions.attack.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.destructive.actions.attack.descriptive',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.destructive.actions.attack.descriptive',
|
||||||
|
|
@ -747,7 +769,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.devastating.actions.devastate.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.devastating.actions.devastate.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.devastating.actions.devastate.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.devastating.actions.devastate.description',
|
||||||
|
|
@ -798,7 +819,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.doubledUp.actions.doubleUp.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.doubledUp.actions.doubleUp.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.doubledUp.actions.doubleUp.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.doubledUp.actions.doubleUp.description',
|
||||||
|
|
@ -812,7 +832,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.dueling.actions.duel.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.dueling.actions.duel.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.dueling.actions.duel.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.dueling.actions.duel.description',
|
||||||
|
|
@ -826,7 +845,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect', // Should prompt a dc 14 reaction save on adversaries
|
type: 'effect', // Should prompt a dc 14 reaction save on adversaries
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.eruptive.actions.erupt.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.eruptive.actions.erupt.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.eruptive.actions.erupt.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.eruptive.actions.erupt.description',
|
||||||
|
|
@ -840,7 +858,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.grappling.actions.grapple.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.grappling.actions.grapple.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.grappling.actions.grapple.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.grappling.actions.grapple.description',
|
||||||
|
|
@ -860,11 +877,13 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.greedy.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.greedy.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.greedy.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.greedy.description',
|
||||||
img: 'icons/commodities/currency/coins-crown-stack-gold.webp',
|
img: 'icons/commodities/currency/coins-crown-stack-gold.webp',
|
||||||
|
target: {
|
||||||
|
type: 'self'
|
||||||
|
},
|
||||||
// Should cost handful of gold,
|
// Should cost handful of gold,
|
||||||
effects: [
|
effects: [
|
||||||
{
|
{
|
||||||
|
|
@ -889,7 +908,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'healing',
|
type: 'healing',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.healing.actions.heal.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.healing.actions.heal.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.healing.actions.heal.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.healing.actions.heal.description',
|
||||||
|
|
@ -937,7 +955,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.hooked.actions.hook.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.hooked.actions.hook.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.hooked.actions.hook.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.hooked.actions.hook.description',
|
||||||
|
|
@ -951,7 +968,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.hot.actions.hot.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.hot.actions.hot.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.hot.actions.hot.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.hot.actions.hot.description',
|
||||||
|
|
@ -965,7 +981,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.invigorating.actions.invigorate.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.invigorating.actions.invigorate.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.invigorating.actions.invigorate.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.invigorating.actions.invigorate.description',
|
||||||
|
|
@ -979,7 +994,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.lifestealing.actions.lifesteal.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.lifestealing.actions.lifesteal.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.lifestealing.actions.lifesteal.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.lifestealing.actions.lifesteal.description',
|
||||||
|
|
@ -993,7 +1007,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.lockedOn.actions.lockOn.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.lockedOn.actions.lockOn.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.lockedOn.actions.lockOn.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.lockedOn.actions.lockOn.description',
|
||||||
|
|
@ -1007,7 +1020,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.long.actions.long.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.long.actions.long.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.long.actions.long.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.long.actions.long.description',
|
||||||
|
|
@ -1021,7 +1033,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.lucky.actions.luck.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.lucky.actions.luck.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.lucky.actions.luck.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.lucky.actions.luck.description',
|
||||||
|
|
@ -1059,7 +1070,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.painful.actions.pain.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.painful.actions.pain.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.painful.actions.pain.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.painful.actions.pain.description',
|
||||||
|
|
@ -1105,7 +1115,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.parry.actions.parry.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.parry.actions.parry.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.parry.actions.parry.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.parry.actions.parry.description',
|
||||||
|
|
@ -1119,7 +1128,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.persuasive.actions.persuade.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.persuasive.actions.persuade.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.persuasive.actions.persuade.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.persuasive.actions.persuade.description',
|
||||||
|
|
@ -1156,7 +1164,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.pompous.actions.pompous.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.pompous.actions.pompous.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.pompous.actions.pompous.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.pompous.actions.pompous.description',
|
||||||
|
|
@ -1200,7 +1207,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.quick.actions.quick.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.quick.actions.quick.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.quick.actions.quick.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.quick.actions.quick.description',
|
||||||
|
|
@ -1238,7 +1244,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.reloading.actions.reload.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.reloading.actions.reload.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.reloading.actions.reload.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.reloading.actions.reload.description',
|
||||||
|
|
@ -1252,7 +1257,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.retractable.actions.retract.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.retractable.actions.retract.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.retractable.actions.retract.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.retractable.actions.retract.description',
|
||||||
|
|
@ -1266,7 +1270,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.returning.actions.return.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.returning.actions.return.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.returning.actions.return.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.returning.actions.return.description',
|
||||||
|
|
@ -1280,7 +1283,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.scary.actions.scare.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.scary.actions.scare.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.scary.actions.scare.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.scary.actions.scare.description',
|
||||||
|
|
@ -1324,7 +1326,8 @@ export const weaponFeatures = {
|
||||||
{
|
{
|
||||||
key: 'system.bonuses.damage.primaryWeapon.bonus',
|
key: 'system.bonuses.damage.primaryWeapon.bonus',
|
||||||
mode: 2,
|
mode: 2,
|
||||||
value: '@system.traits.agility.value'
|
value: '@system.traits.agility.value',
|
||||||
|
priority: 21
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -1336,7 +1339,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.sheltering.actions.shelter.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.sheltering.actions.shelter.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.sheltering.actions.shelter.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.sheltering.actions.shelter.description',
|
||||||
|
|
@ -1350,7 +1352,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.startling.actions.startle.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.startling.actions.startle.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.startling.actions.startle.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.startling.actions.startle.description',
|
||||||
|
|
@ -1370,7 +1371,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.timebending.actions.bendTime.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.timebending.actions.bendTime.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.timebending.actions.bendTime.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.timebending.actions.bendTime.description',
|
||||||
|
|
@ -1380,6 +1380,50 @@ export const weaponFeatures = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const allWeaponFeatures = () => {
|
||||||
|
const homebrewFeatures = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).itemFeatures
|
||||||
|
.weaponFeatures;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...weaponFeatures,
|
||||||
|
...Object.keys(homebrewFeatures).reduce((acc, key) => {
|
||||||
|
const feature = homebrewFeatures[key];
|
||||||
|
|
||||||
|
const actions = feature.actions.map(action => ({
|
||||||
|
...action,
|
||||||
|
effects: action.effects.map(effect => feature.effects.find(x => x.id === effect._id)),
|
||||||
|
type: action.type
|
||||||
|
}));
|
||||||
|
const actionEffects = actions.flatMap(a => a.effects);
|
||||||
|
const effects = feature.effects.filter(effect => !actionEffects.some(x => x.id === effect.id));
|
||||||
|
|
||||||
|
acc[key] = { ...feature, label: feature.name, effects, actions };
|
||||||
|
return acc;
|
||||||
|
}, {})
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const orderedWeaponFeatures = () => {
|
||||||
|
const homebrewFeatures = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).itemFeatures
|
||||||
|
.weaponFeatures;
|
||||||
|
const allFeatures = { ...weaponFeatures, ...homebrewFeatures };
|
||||||
|
const all = Object.keys(allFeatures).map(key => {
|
||||||
|
const feature = allFeatures[key];
|
||||||
|
return {
|
||||||
|
...feature,
|
||||||
|
id: key,
|
||||||
|
label: feature.label ?? feature.name
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return Object.values(all).sort((a, b) => game.i18n.localize(a.label).localeCompare(game.i18n.localize(b.label)));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const featureForm = {
|
||||||
|
passive: 'DAGGERHEART.CONFIG.FeatureForm.passive',
|
||||||
|
action: 'DAGGERHEART.CONFIG.FeatureForm.action',
|
||||||
|
reaction: 'DAGGERHEART.CONFIG.FeatureForm.reaction'
|
||||||
|
};
|
||||||
|
|
||||||
export const featureTypes = {
|
export const featureTypes = {
|
||||||
ancestry: {
|
ancestry: {
|
||||||
id: 'ancestry',
|
id: 'ancestry',
|
||||||
|
|
@ -1437,21 +1481,6 @@ export const featureSubTypes = {
|
||||||
mastery: 'mastery'
|
mastery: 'mastery'
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actionTypes = {
|
|
||||||
passive: {
|
|
||||||
id: 'passive',
|
|
||||||
label: 'DAGGERHEART.CONFIG.ActionType.passive'
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
id: 'action',
|
|
||||||
label: 'DAGGERHEART.CONFIG.ActionType.action'
|
|
||||||
},
|
|
||||||
reaction: {
|
|
||||||
id: 'reaction',
|
|
||||||
label: 'DAGGERHEART.CONFIG.ActionType.reaction'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const itemResourceTypes = {
|
export const itemResourceTypes = {
|
||||||
simple: {
|
simple: {
|
||||||
id: 'simple',
|
id: 'simple',
|
||||||
|
|
@ -1460,6 +1489,10 @@ export const itemResourceTypes = {
|
||||||
diceValue: {
|
diceValue: {
|
||||||
id: 'diceValue',
|
id: 'diceValue',
|
||||||
label: 'DAGGERHEART.CONFIG.ItemResourceType.diceValue'
|
label: 'DAGGERHEART.CONFIG.ItemResourceType.diceValue'
|
||||||
|
},
|
||||||
|
die: {
|
||||||
|
id: 'die',
|
||||||
|
label: 'DAGGERHEART.CONFIG.ItemResourceType.die'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1488,3 +1521,8 @@ export const beastformTypes = {
|
||||||
label: 'DAGGERHEART.CONFIG.BeastformType.hybrid'
|
label: 'DAGGERHEART.CONFIG.BeastformType.hybrid'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const originItemType = {
|
||||||
|
itemCollection: 'itemCollection',
|
||||||
|
restMove: 'restMove'
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -26,5 +26,27 @@ export const gameSettings = {
|
||||||
Fear: 'ResourcesFear'
|
Fear: 'ResourcesFear'
|
||||||
},
|
},
|
||||||
LevelTiers: 'LevelTiers',
|
LevelTiers: 'LevelTiers',
|
||||||
Countdowns: 'Countdowns'
|
Countdowns: 'Countdowns',
|
||||||
|
LastMigrationVersion: 'LastMigrationVersion',
|
||||||
|
TagTeamRoll: 'TagTeamRoll',
|
||||||
|
SpotlightRequestQueue: 'SpotlightRequestQueue',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const actionAutomationChoices = {
|
||||||
|
never: {
|
||||||
|
id: 'never',
|
||||||
|
label: 'Never'
|
||||||
|
},
|
||||||
|
showDialog: {
|
||||||
|
id: 'showDialog',
|
||||||
|
label: 'Show Dialog only'
|
||||||
|
},
|
||||||
|
// npcOnly: {
|
||||||
|
// id: "npcOnly",
|
||||||
|
// label: "Always for non-characters"
|
||||||
|
// },
|
||||||
|
always: {
|
||||||
|
id: 'always',
|
||||||
|
label: 'Always'
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,20 @@
|
||||||
import * as GENERAL from './generalConfig.mjs';
|
import * as GENERAL from './generalConfig.mjs';
|
||||||
import * as DOMAIN from './domainConfig.mjs';
|
import * as DOMAIN from './domainConfig.mjs';
|
||||||
|
import * as ENCOUNTER from './encounterConfig.mjs';
|
||||||
import * as ACTOR from './actorConfig.mjs';
|
import * as ACTOR from './actorConfig.mjs';
|
||||||
import * as ITEM from './itemConfig.mjs';
|
import * as ITEM from './itemConfig.mjs';
|
||||||
import * as SETTINGS from './settingsConfig.mjs';
|
import * as SETTINGS from './settingsConfig.mjs';
|
||||||
import * as EFFECTS from './effectConfig.mjs';
|
import * as EFFECTS from './effectConfig.mjs';
|
||||||
import * as ACTIONS from './actionConfig.mjs';
|
import * as ACTIONS from './actionConfig.mjs';
|
||||||
import * as FLAGS from './flagsConfig.mjs';
|
import * as FLAGS from './flagsConfig.mjs';
|
||||||
import * as ITEMBROWSER from './itemBrowserConfig.mjs'
|
import HOOKS from './hooksConfig.mjs';
|
||||||
|
import * as ITEMBROWSER from './itemBrowserConfig.mjs';
|
||||||
|
|
||||||
export const SYSTEM_ID = 'daggerheart';
|
export const SYSTEM_ID = 'daggerheart';
|
||||||
|
|
||||||
export const SYSTEM = {
|
export const SYSTEM = {
|
||||||
id: SYSTEM_ID,
|
id: SYSTEM_ID,
|
||||||
|
ENCOUNTER,
|
||||||
GENERAL,
|
GENERAL,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
ACTOR,
|
ACTOR,
|
||||||
|
|
@ -20,5 +23,6 @@ export const SYSTEM = {
|
||||||
EFFECTS,
|
EFFECTS,
|
||||||
ACTIONS,
|
ACTIONS,
|
||||||
FLAGS,
|
FLAGS,
|
||||||
|
HOOKS,
|
||||||
ITEMBROWSER
|
ITEMBROWSER
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
export { default as DhCombat } from './combat.mjs';
|
export { default as DhCombat } from './combat.mjs';
|
||||||
export { default as DhCombatant } from './combatant.mjs';
|
export { default as DhCombatant } from './combatant.mjs';
|
||||||
|
export { default as DhTagTeamRoll } from './tagTeamRoll.mjs';
|
||||||
|
|
||||||
|
export * as countdowns from './countdowns.mjs';
|
||||||
export * as actions from './action/_module.mjs';
|
export * as actions from './action/_module.mjs';
|
||||||
export * as activeEffects from './activeEffect/_module.mjs';
|
export * as activeEffects from './activeEffect/_module.mjs';
|
||||||
export * as actors from './actor/_module.mjs';
|
export * as actors from './actor/_module.mjs';
|
||||||
export * as chatMessages from './chat-message/_modules.mjs';
|
export * as chatMessages from './chat-message/_modules.mjs';
|
||||||
export * as fields from './fields/_module.mjs';
|
export * as fields from './fields/_module.mjs';
|
||||||
export * as items from './item/_module.mjs';
|
export * as items from './item/_module.mjs';
|
||||||
|
export * as scenes from './scene/_module.mjs';
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import AttackAction from './attackAction.mjs';
|
import AttackAction from './attackAction.mjs';
|
||||||
import BaseAction from './baseAction.mjs';
|
import BaseAction from './baseAction.mjs';
|
||||||
import BeastformAction from './beastformAction.mjs';
|
import BeastformAction from './beastformAction.mjs';
|
||||||
|
import CountdownAction from './countdownAction.mjs';
|
||||||
import DamageAction from './damageAction.mjs';
|
import DamageAction from './damageAction.mjs';
|
||||||
import EffectAction from './effectAction.mjs';
|
import EffectAction from './effectAction.mjs';
|
||||||
import HealingAction from './healingAction.mjs';
|
import HealingAction from './healingAction.mjs';
|
||||||
|
|
@ -10,6 +11,7 @@ import SummonAction from './summonAction.mjs';
|
||||||
export const actionsTypes = {
|
export const actionsTypes = {
|
||||||
base: BaseAction,
|
base: BaseAction,
|
||||||
attack: AttackAction,
|
attack: AttackAction,
|
||||||
|
countdown: CountdownAction,
|
||||||
damage: DamageAction,
|
damage: DamageAction,
|
||||||
healing: HealingAction,
|
healing: HealingAction,
|
||||||
summon: SummonAction,
|
summon: SummonAction,
|
||||||
|
|
|
||||||
|
|
@ -37,8 +37,10 @@ export default class DHAttackAction extends DHDamageAction {
|
||||||
async use(event, options) {
|
async use(event, options) {
|
||||||
const result = await super.use(event, options);
|
const result = await super.use(event, options);
|
||||||
|
|
||||||
|
if (result.message.system.action.roll?.type === 'attack') {
|
||||||
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
|
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
|
||||||
await updateCountdowns(CONFIG.DH.GENERAL.countdownTypes.characterAttack.id);
|
await updateCountdowns(CONFIG.DH.GENERAL.countdownProgressionTypes.characterAttack.id);
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
@ -51,11 +53,13 @@ export default class DHAttackAction extends DHDamageAction {
|
||||||
const labels = [];
|
const labels = [];
|
||||||
const { roll, range, damage } = this;
|
const { roll, range, damage } = this;
|
||||||
|
|
||||||
if (roll.trait) labels.push(game.i18n.localize(`DAGGERHEART.CONFIG.Traits.${roll.trait}.short`))
|
if (roll.trait) labels.push(game.i18n.localize(`DAGGERHEART.CONFIG.Traits.${roll.trait}.short`));
|
||||||
if (range) labels.push(game.i18n.localize(`DAGGERHEART.CONFIG.Range.${range}.short`));
|
if (range) labels.push(game.i18n.localize(`DAGGERHEART.CONFIG.Range.${range}.short`));
|
||||||
|
|
||||||
for (const { value, type } of damage.parts) {
|
const useAltDamage = this.actor?.effects?.find(x => x.type === 'horde')?.active;
|
||||||
const str = Roll.replaceFormulaData(value.getFormula(), this.actor?.getRollData() ?? {});
|
for (const { value, valueAlt, type } of damage.parts) {
|
||||||
|
const usedValue = useAltDamage ? valueAlt : value;
|
||||||
|
const str = Roll.replaceFormulaData(usedValue.getFormula(), this.actor?.getRollData() ?? {});
|
||||||
|
|
||||||
const icons = Array.from(type)
|
const icons = Array.from(type)
|
||||||
.map(t => CONFIG.DH.GENERAL.damageTypes[t]?.icon)
|
.map(t => CONFIG.DH.GENERAL.damageTypes[t]?.icon)
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,10 @@
|
||||||
import DhpActor from '../../documents/actor.mjs';
|
import DhpActor from '../../documents/actor.mjs';
|
||||||
import D20RollDialog from '../../applications/dialogs/d20RollDialog.mjs';
|
import D20RollDialog from '../../applications/dialogs/d20RollDialog.mjs';
|
||||||
import { ActionMixin } from '../fields/actionField.mjs';
|
import { ActionMixin } from '../fields/actionField.mjs';
|
||||||
import { abilities } from '../../config/actorConfig.mjs';
|
import { originItemField } from '../chat-message/actorRoll.mjs';
|
||||||
|
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
/*
|
|
||||||
!!! I'm currently refactoring the whole Action thing, it's a WIP !!!
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
ToDo
|
ToDo
|
||||||
- Target Check / Target Picker
|
- Target Check / Target Picker
|
||||||
|
|
@ -20,48 +16,108 @@ const fields = foundry.data.fields;
|
||||||
export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel) {
|
export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel) {
|
||||||
static extraSchemas = ['cost', 'uses', 'range'];
|
static extraSchemas = ['cost', 'uses', 'range'];
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
static defineSchema() {
|
static defineSchema() {
|
||||||
const schemaFields = {
|
const schemaFields = {
|
||||||
_id: new fields.DocumentIdField({ initial: () => foundry.utils.randomID() }),
|
_id: new fields.DocumentIdField({ initial: () => foundry.utils.randomID() }),
|
||||||
systemPath: new fields.StringField({ required: true, initial: 'actions' }),
|
systemPath: new fields.StringField({ required: true, initial: 'actions' }),
|
||||||
type: new fields.StringField({ initial: undefined, readonly: true, required: true }),
|
type: new fields.StringField({ initial: undefined, readonly: true, required: true }),
|
||||||
|
baseAction: new fields.BooleanField({ initial: false }),
|
||||||
name: new fields.StringField({ initial: undefined }),
|
name: new fields.StringField({ initial: undefined }),
|
||||||
description: new fields.HTMLField(),
|
description: new fields.HTMLField(),
|
||||||
img: new fields.FilePathField({ initial: undefined, categories: ['IMAGE'], base64: false }),
|
img: new fields.FilePathField({ initial: undefined, categories: ['IMAGE'], base64: false }),
|
||||||
chatDisplay: new fields.BooleanField({ initial: true, label: 'DAGGERHEART.ACTIONS.Config.displayInChat' }),
|
chatDisplay: new fields.BooleanField({ initial: true, label: 'DAGGERHEART.ACTIONS.Config.displayInChat' }),
|
||||||
|
originItem: originItemField(),
|
||||||
actionType: new fields.StringField({
|
actionType: new fields.StringField({
|
||||||
choices: CONFIG.DH.ITEM.actionTypes,
|
choices: CONFIG.DH.ITEM.actionTypes,
|
||||||
initial: 'action',
|
initial: 'action',
|
||||||
nullable: true
|
nullable: false,
|
||||||
})
|
required: true
|
||||||
|
}),
|
||||||
|
targetUuid: new fields.StringField({ initial: undefined })
|
||||||
};
|
};
|
||||||
|
|
||||||
this.extraSchemas.forEach(s => {
|
this.extraSchemas.forEach(s => {
|
||||||
let clsField;
|
let clsField = this.getActionField(s);
|
||||||
if ((clsField = this.getActionField(s))) schemaFields[s] = new clsField();
|
if (clsField) schemaFields[s] = new clsField();
|
||||||
});
|
});
|
||||||
|
|
||||||
return schemaFields;
|
return schemaFields;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default values to supply to schema fields when they are created in the actionConfig. Defined by implementing classes.
|
||||||
|
*/
|
||||||
|
get defaultValues() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a Map containing each Action step based on fields define in schema. Ordered by Fields order property.
|
||||||
|
*
|
||||||
|
* Each step can be called individually as long as needed config is provided.
|
||||||
|
* Ex: <action>.workflow.get("damage").execute(config)
|
||||||
|
* @returns {Map}
|
||||||
|
*/
|
||||||
|
defineWorkflow() {
|
||||||
|
const workflow = new Map();
|
||||||
|
this.constructor.extraSchemas.forEach(s => {
|
||||||
|
let clsField = this.constructor.getActionField(s);
|
||||||
|
if (clsField?.execute) {
|
||||||
|
workflow.set(s, { order: clsField.order, execute: clsField.execute.bind(this) });
|
||||||
|
if (s === 'damage')
|
||||||
|
workflow.set('applyDamage', { order: 75, execute: clsField.applyDamage.bind(this) });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return new Map([...workflow.entries()].sort(([aKey, aValue], [bKey, bValue]) => aValue.order - bValue.order));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter returning the workflow property or creating it the first time the property is called
|
||||||
|
*/
|
||||||
|
get workflow() {
|
||||||
|
if (this.hasOwnProperty('_workflow')) return this._workflow;
|
||||||
|
const workflow = Object.freeze(this.defineWorkflow());
|
||||||
|
Object.defineProperty(this, '_workflow', { value: workflow, writable: false });
|
||||||
|
return workflow;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the Field class from ActionFields global config
|
||||||
|
* @param {string} name Field short name, equal to Action property
|
||||||
|
* @returns Action Field
|
||||||
|
*/
|
||||||
static getActionField(name) {
|
static getActionField(name) {
|
||||||
const field = game.system.api.fields.ActionFields[`${name.capitalize()}Field`];
|
const field = game.system.api.fields.ActionFields[`${name.capitalize()}Field`];
|
||||||
return fields.DataField.isPrototypeOf(field) && field;
|
return fields.DataField.isPrototypeOf(field) && field;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
prepareData() {
|
prepareData() {
|
||||||
this.name = this.name || game.i18n.localize(CONFIG.DH.ACTIONS.actionTypes[this.type].name);
|
this.name = this.name || game.i18n.localize(CONFIG.DH.ACTIONS.actionTypes[this.type].name);
|
||||||
this.img = this.img ?? this.parent?.parent?.img;
|
this.img = this.img ?? this.parent?.parent?.img;
|
||||||
|
|
||||||
|
/* Fallback to feature description */
|
||||||
|
this.description = this.description || this.parent?.description;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Action ID
|
||||||
|
*/
|
||||||
get id() {
|
get id() {
|
||||||
return this._id;
|
return this._id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return Item the action is attached too.
|
||||||
|
*/
|
||||||
get item() {
|
get item() {
|
||||||
return this.parent.parent;
|
return this.parent.parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the first Actor parent found.
|
||||||
|
*/
|
||||||
get actor() {
|
get actor() {
|
||||||
return this.item instanceof DhpActor
|
return this.item instanceof DhpActor
|
||||||
? this.item
|
? this.item
|
||||||
|
|
@ -74,6 +130,11 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
return 'trait';
|
return 'trait';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare base data based on Action Type & Parent Type
|
||||||
|
* @param {object} parent
|
||||||
|
* @returns {object}
|
||||||
|
*/
|
||||||
static getSourceConfig(parent) {
|
static getSourceConfig(parent) {
|
||||||
const updateSource = {};
|
const updateSource = {};
|
||||||
if (parent?.parent?.type === 'weapon' && this === game.system.api.models.actions.actionsTypes.attack) {
|
if (parent?.parent?.type === 'weapon' && this === game.system.api.models.actions.actionsTypes.attack) {
|
||||||
|
|
@ -96,13 +157,15 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
return updateSource;
|
return updateSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtain a data object used to evaluate any dice rolls associated with this particular Action
|
||||||
|
* @param {object} [data ={}] Optional data object from previous configuration/rolls
|
||||||
|
* @returns {object}
|
||||||
|
*/
|
||||||
getRollData(data = {}) {
|
getRollData(data = {}) {
|
||||||
if (!this.actor) return null;
|
const actorData = this.actor ? this.actor.getRollData(false) : {};
|
||||||
const actorData = this.actor.getRollData(false);
|
|
||||||
|
|
||||||
// Add Roll results to RollDatas
|
|
||||||
actorData.result = data.roll?.total ?? 1;
|
actorData.result = data.roll?.total ?? 1;
|
||||||
|
|
||||||
actorData.scale = data.costs?.length // Right now only return the first scalable cost.
|
actorData.scale = data.costs?.length // Right now only return the first scalable cost.
|
||||||
? (data.costs.find(c => c.scalable)?.total ?? 1)
|
? (data.costs.find(c => c.scalable)?.total ?? 1)
|
||||||
: 1;
|
: 1;
|
||||||
|
|
@ -111,19 +174,28 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
return actorData;
|
return actorData;
|
||||||
}
|
}
|
||||||
|
|
||||||
async use(event, options = {}) {
|
/**
|
||||||
|
* Execute each part of the Action workflow in order, calling a specific event before and after each part.
|
||||||
|
* @param {object} config Config object usually created from prepareConfig method
|
||||||
|
*/
|
||||||
|
async executeWorkflow(config) {
|
||||||
|
for (const [key, part] of this.workflow) {
|
||||||
|
if (Hooks.call(`${CONFIG.DH.id}.pre${key.capitalize()}Action`, this, config) === false) return;
|
||||||
|
if ((await part.execute(config)) === false) return;
|
||||||
|
if (Hooks.call(`${CONFIG.DH.id}.post${key.capitalize()}Action`, this, config) === false) return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main method to use the Action
|
||||||
|
* @param {Event} event Event from the button used to trigger the Action
|
||||||
|
* @returns {object}
|
||||||
|
*/
|
||||||
|
async use(event) {
|
||||||
if (!this.actor) throw new Error("An Action can't be used outside of an Actor context.");
|
if (!this.actor) throw new Error("An Action can't be used outside of an Actor context.");
|
||||||
|
|
||||||
if (this.chatDisplay) await this.toChat();
|
let config = this.prepareConfig(event);
|
||||||
let { byPassRoll } = options,
|
if (!config) return;
|
||||||
config = this.prepareConfig(event, byPassRoll);
|
|
||||||
for (let i = 0; i < this.constructor.extraSchemas.length; i++) {
|
|
||||||
let clsField = this.constructor.getActionField(this.constructor.extraSchemas[i]);
|
|
||||||
if (clsField?.prepareConfig) {
|
|
||||||
const keep = clsField.prepareConfig.call(this, config);
|
|
||||||
if (config.isFastForward && !keep) return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Hooks.call(`${CONFIG.DH.id}.preUseAction`, this, config) === false) return;
|
if (Hooks.call(`${CONFIG.DH.id}.preUseAction`, this, config) === false) return;
|
||||||
|
|
||||||
|
|
@ -133,282 +205,168 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
if (!config) return;
|
if (!config) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.hasRoll) {
|
// Execute the Action Worflow in order based of schema fields
|
||||||
const rollConfig = this.prepareRoll(config);
|
await this.executeWorkflow(config);
|
||||||
config.roll = rollConfig;
|
await config.resourceUpdates.updateResources();
|
||||||
config = await this.actor.diceRoll(config);
|
|
||||||
if (!config) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.doFollowUp(config)) {
|
|
||||||
if (this.rollDamage && this.damage.parts.length) await this.rollDamage(event, config);
|
|
||||||
else if (this.trigger) await this.trigger(event, config);
|
|
||||||
else if (this.hasSave || this.hasEffect) {
|
|
||||||
const roll = new CONFIG.Dice.daggerheart.DHRoll('');
|
|
||||||
roll._evaluated = true;
|
|
||||||
await CONFIG.Dice.daggerheart.DHRoll.toMessage(roll, config);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Consume resources
|
|
||||||
await this.consume(config);
|
|
||||||
|
|
||||||
if (Hooks.call(`${CONFIG.DH.id}.postUseAction`, this, config) === false) return;
|
if (Hooks.call(`${CONFIG.DH.id}.postUseAction`, this, config) === false) return;
|
||||||
|
|
||||||
|
if (this.chatDisplay) await this.toChat();
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* */
|
/**
|
||||||
prepareConfig(event, byPass = false) {
|
* Create the basic config common to every action type
|
||||||
const hasRoll = this.getUseHasRoll(byPass);
|
* @param {Event} event Event from the button used to trigger the Action
|
||||||
return {
|
* @returns {object}
|
||||||
|
*/
|
||||||
|
prepareBaseConfig(event) {
|
||||||
|
const config = {
|
||||||
event,
|
event,
|
||||||
title: `${this.item.name}: ${this.name}`,
|
title: `${this.item instanceof CONFIG.Actor.documentClass ? '' : `${this.item.name}: `}${game.i18n.localize(this.name)}`,
|
||||||
source: {
|
source: {
|
||||||
item: this.item._id,
|
item: this.item._id,
|
||||||
|
originItem: this.originItem,
|
||||||
action: this._id,
|
action: this._id,
|
||||||
actor: this.actor.uuid
|
actor: this.actor.uuid
|
||||||
},
|
},
|
||||||
dialog: {
|
dialog: {},
|
||||||
configure: hasRoll
|
actionType: this.actionType,
|
||||||
},
|
hasRoll: this.hasRoll,
|
||||||
type: this.type,
|
hasDamage: this.hasDamage,
|
||||||
hasRoll: hasRoll,
|
hasHealing: this.hasHealing,
|
||||||
hasDamage: this.damage?.parts?.length && this.type !== 'healing',
|
hasEffect: this.hasEffect,
|
||||||
hasHealing: this.damage?.parts?.length && this.type === 'healing',
|
|
||||||
hasEffect: !!this.effects?.length,
|
|
||||||
hasSave: this.hasSave,
|
hasSave: this.hasSave,
|
||||||
|
isDirect: !!this.damage?.direct,
|
||||||
selectedRollMode: game.settings.get('core', 'rollMode'),
|
selectedRollMode: game.settings.get('core', 'rollMode'),
|
||||||
isFastForward: event.shiftKey,
|
|
||||||
data: this.getRollData(),
|
data: this.getRollData(),
|
||||||
evaluate: hasRoll
|
evaluate: this.hasRoll,
|
||||||
|
resourceUpdates: new ResourceUpdateMap(this.actor),
|
||||||
|
targetUuid: this.targetUuid
|
||||||
};
|
};
|
||||||
|
|
||||||
|
DHBaseAction.applyKeybindings(config);
|
||||||
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the config for that action used for its workflow
|
||||||
|
* @param {Event} event Event from the button used to trigger the Action
|
||||||
|
* @returns {object}
|
||||||
|
*/
|
||||||
|
prepareConfig(event) {
|
||||||
|
const config = this.prepareBaseConfig(event);
|
||||||
|
for (const clsField of Object.values(this.schema.fields)) {
|
||||||
|
if (clsField?.prepareConfig) if (clsField.prepareConfig.call(this, config) === false) return false;
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method used to know if a configuration dialog must be shown or not when there is no roll.
|
||||||
|
* @param {*} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
requireConfigurationDialog(config) {
|
requireConfigurationDialog(config) {
|
||||||
return !config.event.shiftKey && !config.hasRoll && (config.costs?.length || config.uses);
|
return !config.event.shiftKey && !config.hasRoll && (config.costs?.length || config.uses);
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareRoll() {
|
/**
|
||||||
const roll = {
|
* Consume Action configured resources & uses.
|
||||||
baseModifiers: this.roll.getModifier(),
|
* That method is only used when we want those resources to be consumed outside of the use method workflow.
|
||||||
label: 'Attack',
|
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
|
||||||
type: this.actionType,
|
* @param {boolean} successCost
|
||||||
difficulty: this.roll?.difficulty,
|
*/
|
||||||
formula: this.roll.getFormula(),
|
|
||||||
advantage: CONFIG.DH.ACTIONS.advantageState[this.roll.advState].value
|
|
||||||
};
|
|
||||||
if (this.roll?.type === 'diceSet' || !this.hasRoll) roll.lite = true;
|
|
||||||
|
|
||||||
return roll;
|
|
||||||
}
|
|
||||||
|
|
||||||
doFollowUp(config) {
|
|
||||||
return !config.hasRoll;
|
|
||||||
}
|
|
||||||
|
|
||||||
async consume(config, successCost = false) {
|
async consume(config, successCost = false) {
|
||||||
const usefulResources = {
|
await this.workflow.get('cost')?.execute(config, successCost);
|
||||||
...foundry.utils.deepClone(this.actor.system.resources),
|
await this.workflow.get('uses')?.execute(config, successCost);
|
||||||
fear: {
|
|
||||||
value: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear),
|
|
||||||
max: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxFear,
|
|
||||||
reversed: false
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for (var cost of config.costs) {
|
if (config.roll && !config.roll.success && successCost) {
|
||||||
if (cost.keyIsID) {
|
|
||||||
usefulResources[cost.key] = {
|
|
||||||
value: cost.value,
|
|
||||||
target: this.parent.parent,
|
|
||||||
keyIsID: true
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const resources = game.system.api.fields.ActionFields.CostField.getRealCosts(config.costs)
|
|
||||||
.filter(
|
|
||||||
c =>
|
|
||||||
(!successCost && (!c.consumeOnSuccess || config.roll?.success)) ||
|
|
||||||
(successCost && c.consumeOnSuccess)
|
|
||||||
)
|
|
||||||
.reduce((a, c) => {
|
|
||||||
const resource = usefulResources[c.key];
|
|
||||||
if (resource) {
|
|
||||||
a.push({
|
|
||||||
key: c.key,
|
|
||||||
value: (c.total ?? c.value) * (resource.isReversed ? 1 : -1),
|
|
||||||
target: resource.target,
|
|
||||||
keyIsID: resource.keyIsID
|
|
||||||
});
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
await (this.actor.system.partner ?? this.actor).modifyResource(resources);
|
|
||||||
if (
|
|
||||||
config.uses?.enabled &&
|
|
||||||
((!successCost && (!config.uses?.consumeOnSuccess || config.roll?.success)) ||
|
|
||||||
(successCost && config.uses?.consumeOnSuccess))
|
|
||||||
)
|
|
||||||
this.update({ 'uses.value': this.uses.value + 1 });
|
|
||||||
|
|
||||||
if (config.roll?.success || successCost) {
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
(config.message ?? config.parent).update({ 'system.successConsumed': true });
|
(config.message ?? config.parent).update({ 'system.successConsumed': true });
|
||||||
}, 50);
|
}, 50);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* */
|
|
||||||
|
|
||||||
/* ROLL */
|
/**
|
||||||
getUseHasRoll(byPass = false) {
|
* Set if a configuration dialog must be shown or not if a special keyboard key is pressed.
|
||||||
return this.hasRoll && !byPass;
|
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
|
||||||
|
*/
|
||||||
|
static applyKeybindings(config) {
|
||||||
|
config.dialog.configure ??= !(config.event.shiftKey || config.event.altKey || config.event.ctrlKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getters to know which parts the action is composed of. A field can exist but configured to not be used.
|
||||||
|
* @returns {boolean} If that part is in the action.
|
||||||
|
*/
|
||||||
|
|
||||||
get hasRoll() {
|
get hasRoll() {
|
||||||
return !!this.roll?.type;
|
return !!this.roll?.type;
|
||||||
}
|
}
|
||||||
|
|
||||||
get modifiers() {
|
get hasDamage() {
|
||||||
if (!this.actor) return [];
|
return this.damage?.parts?.length && this.type !== 'healing';
|
||||||
const modifiers = [];
|
}
|
||||||
/** Placeholder for specific bonuses **/
|
|
||||||
return modifiers;
|
get hasHealing() {
|
||||||
|
return this.damage?.parts?.length && this.type === 'healing';
|
||||||
}
|
}
|
||||||
/* ROLL */
|
|
||||||
|
|
||||||
/* SAVE */
|
|
||||||
get hasSave() {
|
get hasSave() {
|
||||||
return !!this.save?.trait;
|
return !!this.save?.trait;
|
||||||
}
|
}
|
||||||
/* SAVE */
|
|
||||||
|
|
||||||
/* EFFECTS */
|
|
||||||
get hasEffect() {
|
get hasEffect() {
|
||||||
return this.effects?.length > 0;
|
return this.effects?.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
async applyEffects(event, data, targets) {
|
|
||||||
targets ??= data.system.targets;
|
|
||||||
const force = true; /* Where should this come from? */
|
|
||||||
if (!this.effects?.length || !targets.length) return;
|
|
||||||
let effects = this.effects;
|
|
||||||
targets.forEach(async token => {
|
|
||||||
if (!token.hit && !force) return;
|
|
||||||
if (this.hasSave && token.saved.success === true) {
|
|
||||||
effects = this.effects.filter(e => e.onSave === true);
|
|
||||||
}
|
|
||||||
if (!effects.length) return;
|
|
||||||
effects.forEach(async e => {
|
|
||||||
const actor = canvas.tokens.get(token.id)?.actor,
|
|
||||||
effect = this.item.effects.get(e._id);
|
|
||||||
if (!actor || !effect) return;
|
|
||||||
await this.applyEffect(effect, actor);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async applyEffect(effect, actor) {
|
|
||||||
const existingEffect = actor.effects.find(e => e.origin === effect.uuid);
|
|
||||||
if (existingEffect) {
|
|
||||||
return effect.update(
|
|
||||||
foundry.utils.mergeObject({
|
|
||||||
...effect.constructor.getInitialDuration(),
|
|
||||||
disabled: false
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, create a new effect on the target
|
|
||||||
const effectData = foundry.utils.mergeObject({
|
|
||||||
...effect.toObject(),
|
|
||||||
disabled: false,
|
|
||||||
transfer: false,
|
|
||||||
origin: effect.uuid
|
|
||||||
});
|
|
||||||
await ActiveEffect.implementation.create(effectData, { parent: actor });
|
|
||||||
}
|
|
||||||
/* EFFECTS */
|
|
||||||
|
|
||||||
/* SAVE */
|
|
||||||
async rollSave(actor, event, message) {
|
|
||||||
if (!actor) return;
|
|
||||||
const title = actor.isNPC
|
|
||||||
? game.i18n.localize('DAGGERHEART.GENERAL.reactionRoll')
|
|
||||||
: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
|
||||||
ability: game.i18n.localize(abilities[this.save.trait]?.label)
|
|
||||||
});
|
|
||||||
return actor.diceRoll({
|
|
||||||
event,
|
|
||||||
title,
|
|
||||||
roll: {
|
|
||||||
trait: this.save.trait,
|
|
||||||
difficulty: this.save.difficulty ?? this.actor?.baseSaveDifficulty,
|
|
||||||
type: 'reaction'
|
|
||||||
},
|
|
||||||
type: 'trait',
|
|
||||||
hasRoll: true,
|
|
||||||
data: actor.getRollData()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
updateSaveMessage(result, message, targetId) {
|
|
||||||
if (!result) return;
|
|
||||||
const updateMsg = this.updateChatMessage.bind(this, message, targetId, {
|
|
||||||
result: result.roll.total,
|
|
||||||
success: result.roll.success
|
|
||||||
});
|
|
||||||
if (game.modules.get('dice-so-nice')?.active)
|
|
||||||
game.dice3d.waitFor3DAnimationByMessageID(result.message.id ?? result.message._id).then(() => updateMsg());
|
|
||||||
else updateMsg();
|
|
||||||
}
|
|
||||||
|
|
||||||
static rollSaveQuery({ actionId, actorId, event, message }) {
|
|
||||||
return new Promise(async (resolve, reject) => {
|
|
||||||
const actor = await fromUuid(actorId),
|
|
||||||
action = await fromUuid(actionId);
|
|
||||||
if (!actor || !actor?.isOwner) reject();
|
|
||||||
action.rollSave(actor, event, message).then(result => resolve(result));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/* SAVE */
|
|
||||||
|
|
||||||
async updateChatMessage(message, targetId, changes, chain = true) {
|
|
||||||
setTimeout(async () => {
|
|
||||||
const chatMessage = ui.chat.collection.get(message._id);
|
|
||||||
|
|
||||||
await chatMessage.update({
|
|
||||||
flags: {
|
|
||||||
[game.system.id]: {
|
|
||||||
reactionRolls: {
|
|
||||||
[targetId]: changes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, 100);
|
|
||||||
if (chain) {
|
|
||||||
if (message.system.source.message)
|
|
||||||
this.updateChatMessage(ui.chat.collection.get(message.system.source.message), targetId, changes, false);
|
|
||||||
const relatedChatMessages = ui.chat.collection.filter(c => c.system.source?.message === message._id);
|
|
||||||
relatedChatMessages.forEach(c => {
|
|
||||||
this.updateChatMessage(c, targetId, changes, false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a list of localized tags for this action.
|
* Generates a list of localized tags for this action.
|
||||||
* @returns {string[]} An array of localized tag strings.
|
* @returns {string[]} An array of localized tag strings.
|
||||||
*/
|
*/
|
||||||
_getTags() {
|
_getTags() {
|
||||||
const tags = [
|
const tags = [game.i18n.localize(`DAGGERHEART.ACTIONS.TYPES.${this.type}.name`)];
|
||||||
game.i18n.localize(`DAGGERHEART.ACTIONS.TYPES.${this.type}.name`),
|
|
||||||
game.i18n.localize(`DAGGERHEART.CONFIG.ActionType.${this.actionType}`)
|
|
||||||
];
|
|
||||||
|
|
||||||
return tags;
|
return tags;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class ResourceUpdateMap extends Map {
|
||||||
|
#actor;
|
||||||
|
|
||||||
|
constructor(actor) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.#actor = actor;
|
||||||
|
}
|
||||||
|
|
||||||
|
addResources(resources) {
|
||||||
|
for (const resource of resources) {
|
||||||
|
if (!resource.key) continue;
|
||||||
|
|
||||||
|
const existing = this.get(resource.key);
|
||||||
|
if (existing) {
|
||||||
|
this.set(resource.key, {
|
||||||
|
...existing,
|
||||||
|
value: existing.value + (resource.value ?? 0),
|
||||||
|
total: existing.total + (resource.total ?? 0)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.set(resource.key, resource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#getResources() {
|
||||||
|
return Array.from(this.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateResources() {
|
||||||
|
if (this.#actor) {
|
||||||
|
const target = this.#actor.system.partner ?? this.#actor;
|
||||||
|
await target.modifyResource(this.#getResources());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
import BeastformDialog from '../../applications/dialogs/beastformDialog.mjs';
|
|
||||||
import DHBaseAction from './baseAction.mjs';
|
import DHBaseAction from './baseAction.mjs';
|
||||||
|
|
||||||
export default class DhBeastformAction extends DHBaseAction {
|
export default class DhBeastformAction extends DHBaseAction {
|
||||||
static extraSchemas = [...super.extraSchemas, 'beastform'];
|
static extraSchemas = [...super.extraSchemas, 'beastform'];
|
||||||
|
|
||||||
async use(event, options) {
|
/* async use(event, options) {
|
||||||
const beastformConfig = this.prepareBeastformConfig();
|
const beastformConfig = this.prepareBeastformConfig();
|
||||||
|
|
||||||
const abort = await this.handleActiveTransformations();
|
const abort = await this.handleActiveTransformations();
|
||||||
|
|
@ -82,5 +81,5 @@ export default class DhBeastformAction extends DHBaseAction {
|
||||||
beastformEffects.map(x => x.id)
|
beastformEffects.map(x => x.id)
|
||||||
);
|
);
|
||||||
return existingEffects;
|
return existingEffects;
|
||||||
}
|
} */
|
||||||
}
|
}
|
||||||
|
|
|
||||||
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