mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-06-06 21:04:16 +02:00
Compare commits
81 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
65b34872e3 | ||
|
|
a911f80d46 | ||
|
|
3d98f16ac6 | ||
|
|
98c8e1f498 | ||
|
|
2e524878b3 | ||
|
|
161c8222dc | ||
|
|
00a1d9766c | ||
|
|
f7b82b7d7e | ||
|
|
3d7199e7e5 | ||
|
|
b3ab7ee3a7 | ||
|
|
3157fd450f | ||
|
|
cfd9950aae | ||
|
|
702c9e0b6a | ||
|
|
607ee3b580 | ||
|
|
931b84d16d | ||
|
|
2a79468ce1 | ||
|
|
c4d314171b | ||
|
|
24073e4a16 | ||
|
|
cc5bfbe27d | ||
|
|
f66088971d | ||
|
|
882143c1bb | ||
|
|
baa72ff461 | ||
|
|
cf28e011f2 | ||
|
|
623f62e4b9 | ||
|
|
40109dbbe4 | ||
|
|
d12220c64f | ||
|
|
f4282429cd | ||
|
|
1f02795b5d | ||
|
|
d7b37f8178 | ||
|
|
f8df53ed83 | ||
|
|
8ee5db2832 | ||
|
|
bbc521ece0 | ||
|
|
be8d7f6469 | ||
|
|
3a4b66f487 | ||
|
|
cdf159b4a7 | ||
|
|
413a37483c | ||
|
|
c5e21d9d92 | ||
|
|
10c0b6b51e | ||
|
|
652a554c9a | ||
|
|
c0ed5fe697 | ||
|
|
ff65abe09b | ||
|
|
ec7a7b378d | ||
|
|
8e34356905 | ||
|
|
35bceac520 | ||
|
|
436acb0617 | ||
|
|
1d114633f5 | ||
|
|
1c70b46639 | ||
|
|
ae38245877 | ||
|
|
af5d3d4568 | ||
|
|
6b5c1ff965 | ||
|
|
42a22a49f0 | ||
|
|
da77c2a190 | ||
|
|
68decf0b57 | ||
|
|
4f0670cc35 | ||
|
|
b346ce6766 | ||
|
|
dddd0581f7 | ||
|
|
83329fac46 | ||
|
|
ee0b7b2792 | ||
|
|
4d062a6892 | ||
|
|
487c1fd9a2 | ||
|
|
3aa5cd806a | ||
|
|
2e93b79633 | ||
|
|
244dbd4902 | ||
|
|
c7aed6825a | ||
|
|
9cb5112b62 | ||
|
|
81b6f7fc51 | ||
|
|
828fffd552 | ||
|
|
fc5626ac47 | ||
|
|
2e62545aa7 | ||
|
|
b09c712dd5 | ||
|
|
ca4336bd39 | ||
|
|
77ac11c522 | ||
|
|
50311679a5 | ||
|
|
3a7bcd1b0a | ||
|
|
511e4bd644 | ||
|
|
395820513b | ||
|
|
3566ea3fd3 | ||
|
|
29d502fb97 | ||
|
|
685a25d25a | ||
|
|
dd045b3df7 | ||
|
|
0aabcec340 |
1068 changed files with 13161 additions and 22571 deletions
|
|
@ -1,6 +1,3 @@
|
|||
[*]
|
||||
indent_size = 4
|
||||
indent_style = spaces
|
||||
end_of_line = lf
|
||||
[*.yml]
|
||||
indent_size = 2
|
||||
|
|
|
|||
2
.gitattributes
vendored
2
.gitattributes
vendored
|
|
@ -1,2 +0,0 @@
|
|||
* text=auto eol=lf
|
||||
*.json text eol=lf
|
||||
42
.github/workflows/ci.yml
vendored
42
.github/workflows/ci.yml
vendored
|
|
@ -1,42 +0,0 @@
|
|||
name: Project CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
push:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [24.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
|
||||
- name: Cache NPM Deps
|
||||
id: cache-npm
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: node_modules/
|
||||
key: npm-${{ hashFiles('package-lock.json') }}
|
||||
|
||||
- name: Install NPM Deps
|
||||
if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }}
|
||||
run: npm ci
|
||||
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
3
.github/workflows/deploy.yml
vendored
3
.github/workflows/deploy.yml
vendored
|
|
@ -35,9 +35,8 @@ jobs:
|
|||
env:
|
||||
version: ${{steps.get_version.outputs.version-without-v}}
|
||||
url: https://github.com/${{github.repository}}
|
||||
manifest: https://raw.githubusercontent.com/${{github.repository}}/v14/system.json
|
||||
manifest: https://raw.githubusercontent.com/${{github.repository}}/V13/system.json
|
||||
download: https://github.com/${{github.repository}}/releases/download/${{github.event.release.tag_name}}/system.zip
|
||||
flags.hotReload: false
|
||||
|
||||
# Create a zip file with all files required by the module to add to the release
|
||||
- run: zip -r ./system.zip system.json README.md LICENSE build/daggerheart.js build/tagify.css styles/daggerheart.css assets/ templates/ packs/ lang/
|
||||
|
|
|
|||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -6,4 +6,3 @@ Build
|
|||
build
|
||||
foundry
|
||||
styles/daggerheart.css
|
||||
styles/daggerheart.css.map
|
||||
|
|
|
|||
13
.prettierrc
Normal file
13
.prettierrc
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"trailingComma": "none",
|
||||
"tabWidth": 4,
|
||||
"useTabs": false,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"quoteProps": "consistent",
|
||||
"bracketSpacing": true,
|
||||
"arrowParens": "avoid",
|
||||
"printWidth": 120,
|
||||
"endOfLine": "lf",
|
||||
"bracketSameLine": true
|
||||
}
|
||||
|
|
@ -1,9 +1,78 @@
|
|||
# Contributing to Daggerheart
|
||||
# Contributing to Foundryborne
|
||||
|
||||
Thank you for your interest in contributing to the Foundryborne project!
|
||||
Welcome! This is a community-driven project to bring [Daggerheart](https://www.daggerheart.com/) to [FoundryVTT](https://foundryvtt.com/) as a full system. We're excited to have you here and appreciate your interest in contributing.
|
||||
|
||||
To ensure that all contributions align with our project goals and architectural standards, we ask that you **do not submit outside contributions without first receiving feedback from the development team.**
|
||||
---
|
||||
|
||||
If you have an idea or a fix you'd like to contribute, please start a discussion or open an issue first. We'd love to hear from you and collaborate on the best way to move forward!
|
||||
## 🤝 How to Contribute
|
||||
|
||||
Thank you for your understanding and support.
|
||||
We welcome contributions of all kinds:
|
||||
|
||||
- Bug reports
|
||||
- Feature suggestions
|
||||
- Code contributions
|
||||
- UI/UX mockups
|
||||
- Documentation improvements
|
||||
- Questions and discussions
|
||||
|
||||
Please be respectful and collaborative — we’re all here to build something great together.
|
||||
|
||||
### Community Translations
|
||||
|
||||
Please note that we are not accepting community translations in the main project. Instead, community translations should be published as a module.
|
||||
|
||||
---
|
||||
|
||||
## 🧭 General Guidelines
|
||||
|
||||
- **Use GitHub Issues** to report bugs or propose features
|
||||
- **Start a Discussion** for larger ideas or questions
|
||||
- **Open a Pull Request** once you've confirmed your work aligns with project direction
|
||||
- **Keep things modular and maintainable** — if you're not sure how to structure something, ask!
|
||||
- **Orient your code on existing examples**, and feel free to suggest a standard if it makes things clearer
|
||||
|
||||
---
|
||||
|
||||
## 🗂️ Project Structure
|
||||
|
||||
Please try to follow the general logic of the existing code when submitting PRs.
|
||||
|
||||
We encourage contributors to leave comments or open Discussions when proposing structural or organizational changes.
|
||||
|
||||
---
|
||||
|
||||
## 🧾 Issue & PR Best Practices
|
||||
|
||||
**For Issues:**
|
||||
|
||||
- Use clear, descriptive titles
|
||||
- Provide a concise explanation of the problem or idea
|
||||
- Include reproduction steps or example scenarios if it's a bug
|
||||
- Add screenshots or logs if helpful
|
||||
|
||||
**For Pull Requests:**
|
||||
|
||||
- Use a clear title summarizing the change
|
||||
- Provide a brief description of what your code does and why
|
||||
- Link to any related Issues
|
||||
- Keep PRs focused — smaller is better
|
||||
|
||||
---
|
||||
|
||||
## 🔖 Labels and Boards
|
||||
|
||||
We use GitHub labels to help organize contributions. If your issue or PR relates to a specific category, feel free to tag it. There is also a GitHub Project Board to help track active work and priorities.
|
||||
|
||||
---
|
||||
|
||||
## 📣 Communication
|
||||
|
||||
Discussions are currently happening on GitHub — in Issues, PRs, and [GitHub Discussions](https://github.com/Foundryborne/daggerheart/discussions). You're welcome to use any of these, though we may consolidate to one in the future.
|
||||
|
||||
---
|
||||
|
||||
## 🤗 Thank You!
|
||||
|
||||
Whether you're fixing a typo or designing entire mechanics — every contribution matters. Thank you for helping bring _Daggerheart_ to life in FoundryVTT through **Foundryborne**!
|
||||
|
||||
🐸🛠️
|
||||
|
|
|
|||
|
|
@ -66,10 +66,6 @@ You can find the documentation here: https://github.com/Foundryborne/daggerheart
|
|||
|
||||
Looking to contribute to the project? Look no further, check out our [contributing guide](CONTRIBUTING.md), and keep the [Code of Conduct](coc.md) in mind when working on things.
|
||||
|
||||
## AI Policy
|
||||
|
||||
The Foundryborne Daggerheart system does not make use of AI (generative or otherwise) for any area of its implementation. We expect all contributors to follow this same policy when contributing with a pull request; contributions made using AI will be rejected outright.
|
||||
|
||||
## Disclaimer:
|
||||
|
||||
**Daggerheart System**
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" style="height: 512px; width: 512px;"><path d="M0 0h512v512H0z" fill="#000" fill-opacity="1"></path><g class="" transform="translate(0,0)" style=""><path d="M418.813 30.625c-21.178 26.27-49.712 50.982-84.125 70.844-36.778 21.225-75.064 33.62-110.313 38.06a310.317 310.317 0 0 0 6.813 18.25c16.01.277 29.366-.434 36.406-1.5l9.47-1.53 8.436-1.28.22 10.186a307.48 307.48 0 0 1-1.095 18.72l56.625 8.843c.86-.095 1.713-.15 2.563-.157 11.188-.114 21.44 7.29 24.468 18.593.657 2.448.922 4.903.845 7.313 5.972-2.075 11.753-4.305 17.28-6.72l9.595-4.188 2.313 10.22a340.211 340.211 0 0 1 7.375 48.062C438.29 247.836 468.438 225.71 493 197.5c-3.22-36.73-16.154-78.04-39.125-117.813a290.509 290.509 0 0 0-2.22-3.78l-27.56 71.374c5.154.762 10.123 3.158 14.092 7.126 9.81 9.807 9.813 25.69 0 35.5-9.812 9.81-25.722 9.807-35.53 0-8.86-8.858-9.69-22.68-2.532-32.5l38.938-100.844a322.02 322.02 0 0 0-20.25-25.937zM51.842 118.72c-8.46 17.373-15.76 36.198-21.187 56.436-14.108 52.617-13.96 103.682-2.812 143.438 13.3-2.605 26.442-3.96 39.312-4.03 1.855-.012 3.688.02 5.53.06 20.857.48 40.98 4.332 59.97 11.5a355.064 355.064 0 0 1-1.656-34.218c0-27.8 3.135-54.377 9-78.937l2.47-10.407 9.655 4.562c29.467 13.98 66.194 23.424 106.28 25.22 5.136-20.05 8.19-39.78 9.408-58.75-35.198 4.83-75.387 2.766-116.407-8.22-38.363-10.272-72.314-26.78-99.562-46.656zm230.594 82.218c-1.535 10.452-3.615 21.03-6.218 31.687a312.754 312.754 0 0 0 46-3.97 24.98 24.98 0 0 1-1.532-21.748l-38.25-5.97zM105 201.375l4.156 18.22-21.594 4.905c8.75 5.174 13.353 15.703 10.594 26-3.32 12.394-16.045 19.758-28.437 16.438-12.394-3.32-19.76-16.075-16.44-28.47a23.235 23.235 0 0 1 3.126-6.874l-21.062 4.78-4.125-18.218 73.78-16.78zm388.594 22.813c-25.53 25.46-55.306 45.445-86.906 60.5.05 2.397.093 4.8.093 7.218 0 9.188-.354 18.232-1.03 27.125 16.635 1.33 32.045-1.7 45.344-9.374 25.925-14.962 40.608-45.694 42.5-85.47zm-338.844 3c-4.03 19.993-6.33 41.31-6.406 63.593l.125-.342c30.568 10.174 62.622 17.572 95.25 21.375l7.5.875.718 7.5 5.687 60.125-18.625 1.75-2.53-26.75a23.117 23.117 0 0 1-14.845.968c-12.393-3.32-19.76-16.042-16.438-28.436.285-1.06.647-2.08 1.063-3.063a496.627 496.627 0 0 1-57.406-14.53c2.69 49.62 16.154 94.04 36.094 126.656 22.366 36.588 52.13 57.78 83.968 57.78 31.838.003 61.602-21.19 83.97-57.78 19.536-31.96 32.846-75.244 35.905-123.656a499.132 499.132 0 0 1-48.25 11.656c1.914 4.57 2.415 9.78 1.033 14.938-3.322 12.394-16.045 19.758-28.438 16.437a23.01 23.01 0 0 1-2.125-.686l-2.5 26.47-18.594-1.752 5.688-60.125.72-7.5 7.498-.875c29.245-3.407 57.995-9.717 85.657-18.312v-1.594c0-21.573-2.27-42.23-6.064-61.75C351.132 242.653 313.092 250 272.312 250c-43.59 0-83.986-8.658-117.562-22.813zm-87.5 105.968c-10.87.102-21.995 1.22-33.375 3.313 12.695 31.62 33.117 53.07 59 60 16.9 4.523 34.896 2.536 52.813-5.25-4.382-13.89-7.874-28.606-10.344-43.97-21.115-9.623-43.934-14.32-68.094-14.094zm137.5 80.22h130.813c-40.082 44.594-92.623 42.844-130.813 0z" fill="#fff" fill-opacity="1"></path></g></svg>
|
||||
|
Before Width: | Height: | Size: 3 KiB |
|
|
@ -1,3 +1,3 @@
|
|||
<svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M 7.12 0.5 H 52.88 C 56.29 0.5 58.88 3.57 58.3 6.93 L 51.46 46.68 C 51.16 48.44 50.02 49.95 48.4 50.71 L 32.36 58.33 C 30.87 59.04 29.13 59.04 27.64 58.33 L 11.6 50.71 C 9.98 49.95 8.84 48.44 8.54 46.68 L 1.7 6.93 C 1.12 3.57 3.71 0.5 7.12 0.5 Z" fill="transparent" stroke="#18162e"/>
|
||||
<path d="M6.12012 0.5H51.8799C55.2901 0.500041 57.8779 3.57175 57.2998 6.93262L50.4639 46.6777C50.1604 48.4411 49.0179 49.9467 47.4014 50.7139L31.3584 58.3271C29.8661 59.0354 28.1339 59.0354 26.6416 58.3271L10.5986 50.7139C8.98214 49.9467 7.83959 48.4411 7.53613 46.6777L0.700195 6.93262C0.122088 3.57175 2.7099 0.500042 6.12012 0.5Z" fill="transparent" stroke="#18162e"/>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 397 B After Width: | Height: | Size: 476 B |
|
|
@ -1,3 +1,3 @@
|
|||
<svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M 7.12 0.5 H 52.88 C 56.29 0.5 58.88 3.57 58.3 6.93 L 51.46 46.68 C 51.16 48.44 50.02 49.95 48.4 50.71 L 32.36 58.33 C 30.87 59.04 29.13 59.04 27.64 58.33 L 11.6 50.71 C 9.98 49.95 8.84 48.44 8.54 46.68 L 1.7 6.93 C 1.12 3.57 3.71 0.5 7.12 0.5 Z" fill="#18152E" stroke="#F3C267"/>
|
||||
<path d="M6.12012 0.5H51.8799C55.2901 0.500041 57.8779 3.57175 57.2998 6.93262L50.4639 46.6777C50.1604 48.4411 49.0179 49.9467 47.4014 50.7139L31.3584 58.3271C29.8661 59.0354 28.1339 59.0354 26.6416 58.3271L10.5986 50.7139C8.98214 49.9467 7.83959 48.4411 7.53613 46.6777L0.700195 6.93262C0.122088 3.57175 2.7099 0.500042 6.12012 0.5Z" fill="#18152E" stroke="#F3C267"/>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 393 B After Width: | Height: | Size: 472 B |
24
daggerheart.d.ts
vendored
24
daggerheart.d.ts
vendored
|
|
@ -1,11 +1,8 @@
|
|||
import '@client/global.mjs';
|
||||
import '@common/global.mjs';
|
||||
import '@common/primitives/global.mjs';
|
||||
import Canvas from '@client/canvas/board.mjs';
|
||||
|
||||
// Foundry's use of `Object.assign(globalThis) means many globally available objects are not read as such
|
||||
// This declare global hopefully fixes that
|
||||
// Note: eslint is not aware of these, whatever is added here should go in the eslint's globals list
|
||||
declare global {
|
||||
/**
|
||||
* A simple event framework used throughout Foundry Virtual Tabletop.
|
||||
|
|
@ -15,28 +12,9 @@ declare global {
|
|||
class Hooks extends foundry.helpers.Hooks {}
|
||||
const fromUuid = foundry.utils.fromUuid;
|
||||
const fromUuidSync = foundry.utils.fromUuidSync;
|
||||
/**
|
||||
* A representation of a color in hexadecimal format.
|
||||
* This class provides methods for transformations and manipulations of colors.
|
||||
*/
|
||||
class Color extends foundry.utils.Color {}
|
||||
|
||||
/**
|
||||
* The singleton game canvas
|
||||
*/
|
||||
const canvas: Canvas;
|
||||
|
||||
const ActiveEffect: foundry.documents.ActiveEffect;
|
||||
const Actor: foundry.documents.Actor;
|
||||
const BaseScene: foundry.documents.BaseScene;
|
||||
const ChatMessage: foundry.documents.ChatMessage;
|
||||
const Combat: foundry.documents.Combat;
|
||||
const Combatant: foundry.documents.Combatant;
|
||||
const Item: foundry.documents.Item;
|
||||
const Macro: foundry.documents.Macro;
|
||||
const Scene: foundry.documents.Scene;
|
||||
const TokenDocument: foundry.documents.TokenDocument;
|
||||
|
||||
const Collection: foundry.utils.Collection;
|
||||
const FormDataExtended: foundry.applications.ux.FormDataExtended;
|
||||
const TextEditor: foundry.applications.ux.TextEditor;
|
||||
}
|
||||
|
|
|
|||
136
daggerheart.mjs
136
daggerheart.mjs
|
|
@ -9,7 +9,10 @@ import * as dice from './module/dice/_module.mjs';
|
|||
import * as fields from './module/data/fields/_module.mjs';
|
||||
import RegisterHandlebarsHelpers from './module/helpers/handlebarsHelper.mjs';
|
||||
import { enricherConfig, enricherRenderSetup } from './module/enrichers/_module.mjs';
|
||||
import { getCommandTarget, rollCommandToJSON } from './module/helpers/utils.mjs';
|
||||
import { BaseRoll, DHRoll, DualityRoll, D20Roll, DamageRoll, FateRoll } from './module/dice/_module.mjs';
|
||||
import { enrichedDualityRoll } from './module/enrichers/DualityRollEnricher.mjs';
|
||||
import { enrichedFateRoll, getFateTypeData } from './module/enrichers/FateRollEnricher.mjs';
|
||||
import {
|
||||
handlebarsRegistration,
|
||||
runMigrations,
|
||||
|
|
@ -18,6 +21,7 @@ import {
|
|||
} from './module/systemRegistration/_module.mjs';
|
||||
import { placeables, DhTokenLayer } from './module/canvas/_module.mjs';
|
||||
import './node_modules/@yaireo/tagify/dist/tagify.css';
|
||||
import TemplateManager from './module/documents/templateManager.mjs';
|
||||
import TokenManager from './module/documents/tokenManager.mjs';
|
||||
|
||||
CONFIG.DH = SYSTEM;
|
||||
|
|
@ -32,13 +36,6 @@ CONFIG.Dice.daggerheart = {
|
|||
FateRoll: FateRoll
|
||||
};
|
||||
|
||||
CONFIG.RegionBehavior.dataModels = {
|
||||
...CONFIG.RegionBehavior.dataModels,
|
||||
...data.regionBehaviors
|
||||
};
|
||||
|
||||
Object.assign(CONFIG.Dice.termTypes, dice.diceTypes);
|
||||
|
||||
CONFIG.Actor.documentClass = documents.DhpActor;
|
||||
CONFIG.Actor.dataModels = models.actors.config;
|
||||
CONFIG.Actor.collection = collections.DhActorCollection;
|
||||
|
|
@ -48,7 +45,6 @@ CONFIG.Item.dataModels = models.items.config;
|
|||
|
||||
CONFIG.ActiveEffect.documentClass = documents.DhActiveEffect;
|
||||
CONFIG.ActiveEffect.dataModels = models.activeEffects.config;
|
||||
CONFIG.ActiveEffect.changeTypes = { ...CONFIG.ActiveEffect.changeTypes, ...models.activeEffects.changeEffects };
|
||||
|
||||
CONFIG.Combat.documentClass = documents.DhpCombat;
|
||||
CONFIG.Combat.dataModels = { base: models.DhCombat };
|
||||
|
|
@ -60,13 +56,11 @@ CONFIG.ChatMessage.documentClass = documents.DhChatMessage;
|
|||
CONFIG.ChatMessage.template = 'systems/daggerheart/templates/ui/chat/chat-message.hbs';
|
||||
|
||||
CONFIG.Canvas.rulerClass = placeables.DhRuler;
|
||||
CONFIG.Canvas.layers.regions.layerClass = placeables.DhRegionLayer;
|
||||
CONFIG.Canvas.layers.templates.layerClass = placeables.DhTemplateLayer;
|
||||
CONFIG.Canvas.layers.tokens.layerClass = DhTokenLayer;
|
||||
|
||||
CONFIG.MeasuredTemplate.objectClass = placeables.DhMeasuredTemplate;
|
||||
|
||||
CONFIG.Region.objectClass = placeables.DhRegion;
|
||||
|
||||
CONFIG.RollTable.documentClass = documents.DhRollTable;
|
||||
CONFIG.RollTable.resultTemplate = 'systems/daggerheart/templates/ui/chat/table-result.hbs';
|
||||
|
||||
|
|
@ -90,6 +84,7 @@ 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();
|
||||
CONFIG.ux.TokenManager = new TokenManager();
|
||||
CONFIG.debug.triggers = false;
|
||||
|
||||
|
|
@ -196,11 +191,6 @@ Hooks.once('init', () => {
|
|||
makeDefault: true,
|
||||
label: sheetLabel('TYPES.Actor.environment')
|
||||
});
|
||||
Actors.registerSheet(SYSTEM.id, applications.sheets.actors.NPC, {
|
||||
types: ['npc'],
|
||||
makeDefault: true,
|
||||
label: sheetLabel('TYPES.Actor.npc')
|
||||
});
|
||||
Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Party, {
|
||||
types: ['party'],
|
||||
makeDefault: true,
|
||||
|
|
@ -223,7 +213,6 @@ Hooks.once('init', () => {
|
|||
SYSTEM.id,
|
||||
applications.sheetConfigs.ActiveEffectConfig,
|
||||
{
|
||||
types: ['base', 'beastform', 'horde'],
|
||||
makeDefault: true,
|
||||
label: sheetLabel('DOCUMENT.ActiveEffect')
|
||||
}
|
||||
|
|
@ -281,6 +270,7 @@ Hooks.on('setup', () => {
|
|||
...damageThresholds,
|
||||
'proficiency',
|
||||
'evasion',
|
||||
'armorScore',
|
||||
'scars',
|
||||
'levelData.level.current'
|
||||
]
|
||||
|
|
@ -342,27 +332,75 @@ Hooks.on('renderHandlebarsApplication', (_, element) => {
|
|||
enricherRenderSetup(element);
|
||||
});
|
||||
|
||||
Hooks.on(CONFIG.DH.HOOKS.hooksConfig.tagTeamStart, async data => {
|
||||
if (data.openForAllPlayers && data.partyId) {
|
||||
const party = game.actors.get(data.partyId);
|
||||
if (!party) return;
|
||||
Hooks.on('chatMessage', (_, message) => {
|
||||
if (message.startsWith('/dr')) {
|
||||
const result =
|
||||
message.trim().toLowerCase() === '/dr' ? { result: {} } : rollCommandToJSON(message.replace(/\/dr\s?/, ''));
|
||||
if (!result) {
|
||||
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.dualityParsing'));
|
||||
return false;
|
||||
}
|
||||
|
||||
const TagTeamDialog = game.system.api.applications.dialogs.TagTeamDialog;
|
||||
const dialog = foundry.applications.instances.get(`TagTeamDialog-${party.id}`) ?? new TagTeamDialog(party);
|
||||
dialog.tabGroups.application = 'tagTeamRoll';
|
||||
await dialog.render({ force: true });
|
||||
const { result: rollCommand, flavor } = result;
|
||||
|
||||
const reaction = rollCommand.reaction;
|
||||
const traitValue = rollCommand.trait?.toLowerCase();
|
||||
const advantage = rollCommand.advantage
|
||||
? CONFIG.DH.ACTIONS.advantageState.advantage.value
|
||||
: rollCommand.disadvantage
|
||||
? CONFIG.DH.ACTIONS.advantageState.disadvantage.value
|
||||
: undefined;
|
||||
const difficulty = rollCommand.difficulty;
|
||||
const grantResources = rollCommand.grantResources;
|
||||
|
||||
const target = getCommandTarget({ allowNull: true });
|
||||
const title =
|
||||
(flavor ?? traitValue)
|
||||
? game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
||||
ability: game.i18n.localize(SYSTEM.ACTOR.abilities[traitValue].label)
|
||||
})
|
||||
: game.i18n.localize('DAGGERHEART.GENERAL.duality');
|
||||
|
||||
enrichedDualityRoll({
|
||||
reaction,
|
||||
traitValue,
|
||||
target,
|
||||
difficulty,
|
||||
title,
|
||||
label: game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll'),
|
||||
actionType: null,
|
||||
advantage,
|
||||
grantResources
|
||||
});
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
Hooks.on(CONFIG.DH.HOOKS.hooksConfig.groupRollStart, async data => {
|
||||
if (data.openForAllPlayers && data.partyId) {
|
||||
const party = game.actors.get(data.partyId);
|
||||
if (!party) return;
|
||||
if (message.startsWith('/fr')) {
|
||||
const result =
|
||||
message.trim().toLowerCase() === '/fr' ? { result: {} } : rollCommandToJSON(message.replace(/\/fr\s?/, ''));
|
||||
|
||||
const GroupRollDialog = game.system.api.applications.dialogs.GroupRollDialog;
|
||||
const dialog = foundry.applications.instances.get(`GroupRollDialog-${party.id}`) ?? new GroupRollDialog(party);
|
||||
dialog.tabGroups.application = 'groupRoll';
|
||||
await dialog.render({ force: true });
|
||||
if (!result) {
|
||||
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.fateParsing'));
|
||||
return false;
|
||||
}
|
||||
|
||||
const { result: rollCommand, flavor } = result;
|
||||
const fateTypeData = getFateTypeData(rollCommand?.type);
|
||||
|
||||
if (!fateTypeData)
|
||||
return ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.fateTypeParsing'));
|
||||
|
||||
const { value: fateType, label: fateTypeLabel } = fateTypeData;
|
||||
const target = getCommandTarget({ allowNull: true });
|
||||
const title = flavor ?? game.i18n.localize('DAGGERHEART.GENERAL.fateRoll');
|
||||
|
||||
enrichedFateRoll({
|
||||
target,
|
||||
title,
|
||||
label: fateTypeLabel,
|
||||
fateType
|
||||
});
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -446,33 +484,3 @@ Hooks.on('canvasTearDown', canvas => {
|
|||
Hooks.on('canvasReady', canas => {
|
||||
game.system.registeredTriggers.registerSceneTriggers(canvas.scene);
|
||||
});
|
||||
|
||||
/** Make the user to select a document type, instead of having a default doc type for them to accidentally keep */
|
||||
Hooks.on('renderDialogV2', (_dialog, html) => {
|
||||
if (!html.classList.contains('dialog')) return;
|
||||
const cls = html.classList.contains('item-create')
|
||||
? documents.DHItem.implementation
|
||||
: html.classList.contains('actor-create')
|
||||
? documents.DhpActor.implementation
|
||||
: null;
|
||||
if (!cls) return;
|
||||
|
||||
const form = html.querySelector('form');
|
||||
const submit = html.querySelector('button[type=submit]');
|
||||
const select = html.querySelector('select[name=type]');
|
||||
const nameInput = html.querySelector('input[name=name]');
|
||||
if (!form || !select || !submit || !nameInput) return;
|
||||
|
||||
nameInput.placeholder = cls.defaultName({});
|
||||
const emptyOption = document.createElement('option');
|
||||
emptyOption.value = '';
|
||||
emptyOption.selected = true;
|
||||
select.required = true;
|
||||
select.prepend(emptyOption);
|
||||
submit.addEventListener('click', event => {
|
||||
if (!form.reportValidity()) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,101 +0,0 @@
|
|||
import globals from 'globals';
|
||||
import { defineConfig, globalIgnores } from 'eslint/config';
|
||||
import tseslint from 'typescript-eslint';
|
||||
import js from '@eslint/js';
|
||||
import stylistic from '@stylistic/eslint-plugin';
|
||||
|
||||
/** @type {Partial<RulesConfig>} */
|
||||
export const stylisticRules = {
|
||||
'@stylistic/indent': [
|
||||
'error',
|
||||
4,
|
||||
{
|
||||
SwitchCase: 1
|
||||
}
|
||||
],
|
||||
'@stylistic/max-len': ['error', {
|
||||
code: 120,
|
||||
ignoreComments: true,
|
||||
ignoreStrings: true,
|
||||
ignoreTemplateLiterals: true,
|
||||
ignoreRegExpLiterals: true
|
||||
}],
|
||||
'@stylistic/quotes': ['error', 'single', { allowTemplateLiterals: 'always' }],
|
||||
'@stylistic/arrow-parens': ['error', 'as-needed'],
|
||||
'@stylistic/quote-props': ['error', 'as-needed'],
|
||||
'@stylistic/array-bracket-newline': ['error', 'consistent'],
|
||||
'@stylistic/key-spacing': 'error',
|
||||
'@stylistic/comma-dangle': ['error', 'never'],
|
||||
'@stylistic/space-in-parens': ['error', 'never'],
|
||||
'@stylistic/space-infix-ops': 2,
|
||||
'@stylistic/keyword-spacing': 2,
|
||||
'@stylistic/semi-spacing': 2,
|
||||
'@stylistic/no-multi-spaces': 2,
|
||||
'@stylistic/no-extra-semi': 2,
|
||||
'@stylistic/no-whitespace-before-property': 2,
|
||||
'@stylistic/space-unary-ops': 2
|
||||
};
|
||||
|
||||
export default defineConfig([
|
||||
globalIgnores(['foundry/**/*', 'build/**/*']),
|
||||
{
|
||||
files: ['gulpfile.js', 'postcss.config.js'],
|
||||
languageOptions: { globals: globals.node }
|
||||
},
|
||||
{
|
||||
files: ['**/*.{js,mjs,cjs}'],
|
||||
plugins: {
|
||||
'@stylistic': stylistic
|
||||
},
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
CONFIG: 'readonly',
|
||||
CONST: 'readonly',
|
||||
// Global classes
|
||||
Color: 'readonly',
|
||||
Handlebars: 'readonly',
|
||||
Hooks: 'readonly',
|
||||
PIXI: 'readonly',
|
||||
ProseMirror: 'readonly',
|
||||
Roll: 'readonly',
|
||||
// global namespaces
|
||||
canvas: 'readonly',
|
||||
foundry: 'readonly',
|
||||
game: 'readonly',
|
||||
ui: 'readonly',
|
||||
// global functions
|
||||
fromUuid: 'readonly',
|
||||
fromUuidSync: 'readonly',
|
||||
getDocumentClass: 'readonly',
|
||||
_del: 'readonly',
|
||||
_replace: 'readonly',
|
||||
_loc: 'readonly',
|
||||
// Documents
|
||||
ActiveEffect: 'readonly',
|
||||
Actor: 'readonly',
|
||||
BaseScene: 'readonly',
|
||||
ChatMessage: 'readonly',
|
||||
Combat: 'readonly',
|
||||
Combatant: 'readonly',
|
||||
Item: 'readonly',
|
||||
Macro: 'readonly',
|
||||
Scene: 'readonly',
|
||||
TokenDocument: 'readonly',
|
||||
// Other
|
||||
Collection: 'readonly',
|
||||
FormDataExtended: 'readonly',
|
||||
TextEditor: 'readonly'
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
'no-undef': 'error',
|
||||
// 'no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
|
||||
...stylisticRules
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ['**/*.ts'],
|
||||
extends: [js.configs.recommended, tseslint.configs.recommended]
|
||||
}
|
||||
]);
|
||||
|
|
@ -1,15 +1,9 @@
|
|||
// Less configuration
|
||||
var gulp = require('gulp');
|
||||
var less = require('gulp-less');
|
||||
var sourcemaps = require('gulp-sourcemaps');
|
||||
|
||||
gulp.task('less', function (cb) {
|
||||
gulp.src('styles/daggerheart.less')
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(less())
|
||||
.on('error', console.error.bind(console))
|
||||
.pipe(sourcemaps.write('.'))
|
||||
.pipe(gulp.dest('styles'));
|
||||
gulp.src('styles/daggerheart.less').pipe(less()).pipe(gulp.dest('styles'));
|
||||
cb();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "es2022",
|
||||
"target": "es2022",
|
||||
"module": "ES6",
|
||||
"target": "ES6",
|
||||
"paths": {
|
||||
"@client/*": ["./foundry/client/*"],
|
||||
"@common/*": ["./foundry/common/*"]
|
||||
|
|
|
|||
221
lang/en.json
221
lang/en.json
|
|
@ -14,16 +14,13 @@
|
|||
"beastform": "Beastform"
|
||||
},
|
||||
"ActiveEffect": {
|
||||
"base": "Standard",
|
||||
"beastform": "Beastform",
|
||||
"horde": "Horde"
|
||||
"beastform": "Beastform"
|
||||
},
|
||||
"Actor": {
|
||||
"character": "Character",
|
||||
"companion": "Companion",
|
||||
"adversary": "Adversary",
|
||||
"environment": "Environment",
|
||||
"npc": "NPC",
|
||||
"party": "Party"
|
||||
}
|
||||
},
|
||||
|
|
@ -56,7 +53,6 @@
|
|||
},
|
||||
"damage": {
|
||||
"name": "Damage",
|
||||
"critical": "Damage (Critical)",
|
||||
"tooltip": "Direct damage without a roll."
|
||||
},
|
||||
"effect": {
|
||||
|
|
@ -89,14 +85,7 @@
|
|||
},
|
||||
"Config": {
|
||||
"beastform": {
|
||||
"exact": { "label": "Beastform Max Tier", "hint": "The Character's Tier is used if empty" },
|
||||
"modifications": {
|
||||
"traitBonuses": {
|
||||
"label": { "single": "Trait Bonus", "plural": "Trait Bonuses" },
|
||||
"hint": "Pick bonuses you apply to freely chosen traits at the time of transforming",
|
||||
"bonus": "Bonus Amount"
|
||||
}
|
||||
}
|
||||
"exact": { "label": "Beastform Max Tier", "hint": "The Character's Tier is used if empty" }
|
||||
},
|
||||
"countdown": {
|
||||
"defaultOwnership": "Default Ownership",
|
||||
|
|
@ -110,17 +99,9 @@
|
|||
"customFormula": "Custom Formula",
|
||||
"formula": "Formula"
|
||||
},
|
||||
"area": {
|
||||
"sectionTitle": "Areas",
|
||||
"shape": "Shape",
|
||||
"size": "Size"
|
||||
},
|
||||
"displayInChat": "Display in chat",
|
||||
"deleteTriggerTitle": "Delete Trigger",
|
||||
"deleteTriggerContent": "Are you sure you want to delete the {trigger} trigger?",
|
||||
"advantageState": "Advantage State",
|
||||
"damageOnSave": "Damage on Save",
|
||||
"useDefaultItemValues": "Use default Item values"
|
||||
"deleteTriggerContent": "Are you sure you want to delete the {trigger} trigger?"
|
||||
},
|
||||
"RollField": {
|
||||
"diceRolling": {
|
||||
|
|
@ -132,11 +113,10 @@
|
|||
}
|
||||
},
|
||||
"Settings": {
|
||||
"attackModifier": "Attack Modifier",
|
||||
"attackBonus": "Attack Bonus",
|
||||
"attackName": "Attack Name",
|
||||
"criticalThreshold": "Critical Threshold",
|
||||
"includeBase": { "label": "Include Item Damage" },
|
||||
"groupAttack": { "label": "Group Attack" },
|
||||
"multiplier": "Multiplier",
|
||||
"saveHint": "Set a default Trait to enable Reaction Roll. It can be changed later in Reaction Roll Dialog.",
|
||||
"resultBased": {
|
||||
|
|
@ -167,9 +147,7 @@
|
|||
"Config": {
|
||||
"rangeDependence": {
|
||||
"title": "Range Dependence"
|
||||
},
|
||||
"stacking": { "title": "Stacking" },
|
||||
"targetDispositions": "Affected Dispositions"
|
||||
}
|
||||
},
|
||||
"RangeDependance": {
|
||||
"hint": "Settings for an optional distance at which this effect should activate",
|
||||
|
|
@ -221,9 +199,6 @@
|
|||
"headerTitle": "Adversary Reaction Roll"
|
||||
}
|
||||
},
|
||||
"Base": {
|
||||
"CannotAddType": "Cannot add {itemType} items to {actorType} actors."
|
||||
},
|
||||
"Character": {
|
||||
"advantageSources": {
|
||||
"label": "Advantage Sources",
|
||||
|
|
@ -247,8 +222,6 @@
|
|||
},
|
||||
"defaultHopeDice": "Default Hope Dice",
|
||||
"defaultFearDice": "Default Fear Dice",
|
||||
"defaultAdvantageDice": "Default Advantage Dice",
|
||||
"defaultDisadvantageDice": "Default Disadvantage Dice",
|
||||
"disadvantageSources": {
|
||||
"label": "Disadvantage Sources",
|
||||
"hint": "Add single words or short text as reminders and hints of what a character has disadvantage on."
|
||||
|
|
@ -334,25 +307,7 @@
|
|||
},
|
||||
"newAdversary": "New Adversary"
|
||||
},
|
||||
"NPC": {
|
||||
"FIELDS": {
|
||||
"motives": { "label": "Motives" }
|
||||
}
|
||||
},
|
||||
"Party": {
|
||||
"Subtitle": {
|
||||
"character": "{community} {ancestry} | {subclass} {class}",
|
||||
"companion": "Companion of {partner}"
|
||||
},
|
||||
"RemoveConfirmation": {
|
||||
"title": "Remove member {name}",
|
||||
"text": "Are you sure you want to remove {name} from the party?"
|
||||
},
|
||||
"Thresholds": {
|
||||
"minor": "MIN",
|
||||
"major": "MAJ",
|
||||
"severe": "SEV"
|
||||
},
|
||||
"triggerRestContent": "This will trigger a dialog to players make their downtime moves. Are you sure?"
|
||||
}
|
||||
},
|
||||
|
|
@ -389,7 +344,7 @@
|
|||
"selectSecondaryWeapon": "Select Secondary Weapon",
|
||||
"selectSubclass": "Select Subclass",
|
||||
"setupSkipTitle": "Skipping Character Setup",
|
||||
"setupSkipContent": "You are skipping the Character Setup by adding this manually. The character setup is the blinking button in the top-right. Are you sure you want to continue?",
|
||||
"setupSkipContent": "You are skipping the Character Setup by adding this manually. The character setup is the blinking arrows in the top-right. Are you sure you want to continue?",
|
||||
"startingItems": "Starting Items",
|
||||
"story": "Story",
|
||||
"storyExplanation": "Select which background and connection prompts you want to copy into your character's background.",
|
||||
|
|
@ -412,11 +367,7 @@
|
|||
"giveSpotlight": "Give The Spotlight",
|
||||
"requestingSpotlight": "Requesting The Spotlight",
|
||||
"requestSpotlight": "Request The Spotlight",
|
||||
"openCountdowns": "Countdowns",
|
||||
"adversaryCategories": {
|
||||
"friendly": "Friendly",
|
||||
"adversaries": "Adversaries"
|
||||
}
|
||||
"openCountdowns": "Countdowns"
|
||||
},
|
||||
"CompendiumBrowserSettings": {
|
||||
"title": "Enable Compendiums",
|
||||
|
|
@ -497,15 +448,14 @@
|
|||
},
|
||||
"DaggerheartMenu": {
|
||||
"title": "GM Tools",
|
||||
"refreshFeatures": "Refresh Features",
|
||||
"fallingAndCollision": "Falling And Collision Damage"
|
||||
"refreshFeatures": "Refresh Features"
|
||||
},
|
||||
"DeleteConfirmation": {
|
||||
"title": "Delete {type} - {name}",
|
||||
"text": "Are you sure you want to delete {name}?"
|
||||
},
|
||||
"DamageReduction": {
|
||||
"maxUseableArmor": "Useable Armor Slots",
|
||||
"armorMarks": "Armor Marks",
|
||||
"armorWithStress": "Spend 1 stress to use an extra mark",
|
||||
"thresholdImmunities": "Threshold Immunities",
|
||||
"stress": "Stress",
|
||||
|
|
@ -711,13 +661,19 @@
|
|||
},
|
||||
"PendingReactionsDialog": {
|
||||
"title": "Pending Reaction Rolls Found",
|
||||
"unfinishedRolls": "Some Tokens have not finished their Reaction Rolls.",
|
||||
"warning": "Unfinished reaction rolls will be considered as failed.",
|
||||
"confirmation": "Are you sure you want to continue?"
|
||||
"unfinishedRolls": "Some Tokens still need to roll their Reaction Roll.",
|
||||
"confirmation": "Are you sure you want to continue ?",
|
||||
"warning": "Undone reaction rolls will be considered as failed"
|
||||
},
|
||||
"ReactionRoll": {
|
||||
"title": "Reaction Roll: {trait}"
|
||||
},
|
||||
"RerollDialog": {
|
||||
"title": "Reroll",
|
||||
"damageTitle": "Reroll Damage",
|
||||
"deselectDiceNotification": "Deselect one of the selected dice first",
|
||||
"acceptCurrentRolls": "Accept Current Rolls"
|
||||
},
|
||||
"ResourceDice": {
|
||||
"title": "{name} Resource",
|
||||
"rerollDice": "Reroll Dice"
|
||||
|
|
@ -731,28 +687,15 @@
|
|||
},
|
||||
"TagTeamSelect": {
|
||||
"title": "Tag Team Roll",
|
||||
"FIELDS": {
|
||||
"initiator": {
|
||||
"memberId": { "label": "Initiating Character" },
|
||||
"cost": { "label": "Hope Cost" }
|
||||
}
|
||||
},
|
||||
"leaderTitle": "Initiating Character",
|
||||
"membersTitle": "Participants",
|
||||
"partyTeam": "Party Team",
|
||||
"hopeCost": "Hope Cost",
|
||||
"initiatingCharacter": "Initiating Character",
|
||||
"selectParticipants": "Select the two participants",
|
||||
"startTagTeamRoll": "Start Tag Team Roll",
|
||||
"openDialogForAll": "Open Dialog For All",
|
||||
"rollType": "Roll Type",
|
||||
"makeYourRoll": "Make your roll",
|
||||
"cancelTagTeamRoll": "Cancel Tag Team Roll",
|
||||
"finishTagTeamRoll": "Finish Tag Team Roll",
|
||||
"linkMessageHint": "Make a roll from your character sheet to link it to the Tag Team Roll",
|
||||
"damageNotRolled": "Damage not rolled in chat message yet",
|
||||
"insufficientHope": "The initiating character doesn't have enough hope",
|
||||
"createTagTeam": "Create Tag Team Roll",
|
||||
"createTagTeam": "Create TagTeam Roll",
|
||||
"chatMessageRollTitle": "Roll",
|
||||
"cancelConfirmTitle": "Cancel Tag Team Roll",
|
||||
"cancelConfirmText": "Are you sure you want to cancel the Tag Team Roll? This will close it for all other players too.",
|
||||
|
|
@ -761,26 +704,8 @@
|
|||
"selectRoll": "Select which roll value to be used for the Tag Team"
|
||||
}
|
||||
},
|
||||
"GroupRollSelect": {
|
||||
"cancelConfirmText": "Are you sure you want to cancel the Group Roll? This will close it for all other players too.",
|
||||
"cancelConfirmTitle": "Cancel Group Roll",
|
||||
"initializationTitle": "Character Selection",
|
||||
"finishGroupRoll": "Finish Group Roll",
|
||||
"leader": "Leader",
|
||||
"leaderRoll": "Leader Roll",
|
||||
"members": "Members",
|
||||
"openDialogForAll": "Open Dialog For All",
|
||||
"removeRoll": "Remove Roll",
|
||||
"resultsHint": "Results will appear when characters roll",
|
||||
"selectLeaderHint": "Select one Character to be the leader",
|
||||
"selectParticipantsHint": "Select one Character to be the leader",
|
||||
"startGroupRoll": "Start Group Roll",
|
||||
"title": "Group Roll"
|
||||
},
|
||||
"TokenConfig": {
|
||||
"actorSizeUsed": "Actor size is set, determining the dimensions",
|
||||
"tokenSize": "Token Size",
|
||||
"sizeCategory": "Size Category"
|
||||
"actorSizeUsed": "Actor size is set, determining the dimensions"
|
||||
}
|
||||
},
|
||||
"CLASS": {
|
||||
|
|
@ -790,15 +715,6 @@
|
|||
}
|
||||
},
|
||||
"CONFIG": {
|
||||
"ActiveEffectDuration": {
|
||||
"temporary": "Temporary",
|
||||
"act": "Next Spotlight",
|
||||
"scene": "Next Scene",
|
||||
"shortRest": "Next Rest",
|
||||
"longRest": "Next Long Rest",
|
||||
"session": "Next Session",
|
||||
"custom": "Custom"
|
||||
},
|
||||
"ActionAutomationChoices": {
|
||||
"never": "Never",
|
||||
"showDialog": "Show Dialog Only",
|
||||
|
|
@ -871,11 +787,6 @@
|
|||
"bruiser": "for each Bruiser adversary.",
|
||||
"solo": "for each Solo adversary."
|
||||
},
|
||||
"ArmorInteraction": {
|
||||
"none": { "label": "Ignores Armor" },
|
||||
"active": { "label": "Active w/ Armor" },
|
||||
"inactive": { "label": "Inactive w/ Armor" }
|
||||
},
|
||||
"ArmorFeature": {
|
||||
"burning": {
|
||||
"name": "Burning",
|
||||
|
|
@ -1226,12 +1137,6 @@
|
|||
"description": ""
|
||||
}
|
||||
},
|
||||
"fallAndCollision": {
|
||||
"veryClose": { "label": "Very Close", "chatTitle": "Fall Damage: Very Close" },
|
||||
"close": { "label": "Close", "chatTitle": "Fall Damage: Close" },
|
||||
"far": { "label": "Far", "chatTitle": "Fall Damage: Far" },
|
||||
"collision": { "label": "Collision", "chatTitle": "Dangerous Collision" }
|
||||
},
|
||||
"FeatureForm": {
|
||||
"label": "Feature Form",
|
||||
"passive": "Passive",
|
||||
|
|
@ -1342,11 +1247,6 @@
|
|||
"selectType": "Select Action Type",
|
||||
"selectAction": "Action Selection"
|
||||
},
|
||||
"TagTeamRollTypes": {
|
||||
"trait": "Trait",
|
||||
"ability": "Ability",
|
||||
"damageAbility": "Damage Ability"
|
||||
},
|
||||
"TargetTypes": {
|
||||
"any": "Any",
|
||||
"friendly": "Friendly",
|
||||
|
|
@ -1359,8 +1259,8 @@
|
|||
"cone": "Cone",
|
||||
"emanation": "Emanation",
|
||||
"inFront": "In Front",
|
||||
"rectangle": "Rectangle",
|
||||
"line": "Line"
|
||||
"rect": "Rectangle",
|
||||
"ray": "Ray"
|
||||
},
|
||||
"TokenSize": {
|
||||
"tiny": "Tiny",
|
||||
|
|
@ -1975,17 +1875,6 @@
|
|||
"name": "Healing Roll"
|
||||
}
|
||||
},
|
||||
"ChangeTypes": {
|
||||
"armor": {
|
||||
"newArmorEffect": "Armor Effect",
|
||||
"FIELDS": {
|
||||
"interaction": {
|
||||
"label": "Armor Interaction",
|
||||
"hint": "Does the character wearing armor suppress this effect?"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Duration": {
|
||||
"passive": "Passive",
|
||||
"temporary": "Temporary"
|
||||
|
|
@ -2010,10 +1899,6 @@
|
|||
}
|
||||
},
|
||||
"GENERAL": {
|
||||
"Ability": {
|
||||
"single": "Ability",
|
||||
"plural": "Abilities"
|
||||
},
|
||||
"Action": {
|
||||
"single": "Action",
|
||||
"plural": "Actions"
|
||||
|
|
@ -2399,7 +2284,6 @@
|
|||
"duality": "Duality",
|
||||
"dualityDice": "Duality Dice",
|
||||
"dualityRoll": "Duality Roll",
|
||||
"effect": "Effect",
|
||||
"enabled": "Enabled",
|
||||
"evasion": "Evasion",
|
||||
"equipment": "Equipment",
|
||||
|
|
@ -2448,11 +2332,9 @@
|
|||
"single": "Miss",
|
||||
"plural": "Miss"
|
||||
},
|
||||
"missingX": "Missing {x}",
|
||||
"maxWithThing": "Max {thing}",
|
||||
"missingDragDropThing": "Drop {thing} here",
|
||||
"multiclass": "Multiclass",
|
||||
"name": "Name",
|
||||
"newCategory": "New Category",
|
||||
"newThing": "New {thing}",
|
||||
"next": "Next",
|
||||
|
|
@ -2476,10 +2358,6 @@
|
|||
"rerolled": "Rerolled",
|
||||
"rerollThing": "Reroll {thing}",
|
||||
"resource": "Resource",
|
||||
"result": {
|
||||
"single": "Result",
|
||||
"plural": "Results"
|
||||
},
|
||||
"roll": "Roll",
|
||||
"rollAll": "Roll All",
|
||||
"rollDamage": "Roll Damage",
|
||||
|
|
@ -2539,9 +2417,6 @@
|
|||
"recovery": { "label": "Recovery" },
|
||||
"type": { "label": "Type" },
|
||||
"value": { "label": "Value" }
|
||||
},
|
||||
"identifier": {
|
||||
"label": "Identifier"
|
||||
}
|
||||
},
|
||||
"Ancestry": {
|
||||
|
|
@ -2567,11 +2442,10 @@
|
|||
"tokenImg": { "label": "Token Image" },
|
||||
"tokenRingImg": { "label": "Subject Texture" },
|
||||
"tokenSize": {
|
||||
"placeholder": "Token Size",
|
||||
"disabledPlaceholder": "Token Size",
|
||||
"placeholder": "Using character dimensions",
|
||||
"disabledPlaceholder": "Set by character size",
|
||||
"height": { "label": "Height" },
|
||||
"width": { "label": "Width" },
|
||||
"depth": { "label": "Depth" },
|
||||
"scale": { "label": "Token Scale" }
|
||||
},
|
||||
"evolved": {
|
||||
|
|
@ -2657,7 +2531,8 @@
|
|||
"MACROS": {
|
||||
"Spotlight": {
|
||||
"errors": {
|
||||
"noTokenSelected": "A token on the canvas must either be selected or hovered to spotlight it"
|
||||
"noActiveCombat": "There is no active encounter",
|
||||
"noCombatantSelected": "A combatant token must be either selected or hovered to spotlight it"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -2732,10 +2607,6 @@
|
|||
"hint": "Automatically increase the GM's fear pool on a fear duality roll result."
|
||||
},
|
||||
"FIELDS": {
|
||||
"autoExpireActiveEffects": {
|
||||
"label": "Auto Expire Active Effects",
|
||||
"hint": "Active Effects with set durations will automatically be removed when their durations are up"
|
||||
},
|
||||
"damageReductionRulesDefault": {
|
||||
"label": "Damage Reduction Rules Default",
|
||||
"hint": "Wether using armor and reductions has rules on by default"
|
||||
|
|
@ -2833,15 +2704,6 @@
|
|||
"hideObserverPermissionInChat": {
|
||||
"label": "Hide Chat Info From Players",
|
||||
"hint": "Information such as hit/miss on attack rolls against adversaries will be hidden"
|
||||
},
|
||||
"hidePartyStats": {
|
||||
"label": "Hide Party Stats",
|
||||
"hint": "Resources and stats in the party sheet's member list will be hidden to the following users, even if the user is part of the same party",
|
||||
"choices": {
|
||||
"never": "Never, always show",
|
||||
"players": "Hide From Players",
|
||||
"always": "Hide from Everyone"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -2914,10 +2776,6 @@
|
|||
}
|
||||
},
|
||||
"Keybindings": {
|
||||
"partySheet": {
|
||||
"name": "Toggle Party Sheet",
|
||||
"hint": "Open or close the active party's sheet"
|
||||
},
|
||||
"spotlight": {
|
||||
"name": "Spotlight Combatant",
|
||||
"hint": "Move the spotlight to a hovered or selected token that's present in an active encounter"
|
||||
|
|
@ -2962,7 +2820,6 @@
|
|||
"system": "Dice Preset",
|
||||
"font": "Font",
|
||||
"critical": "Duality Critical Animation",
|
||||
"muted": "Muted",
|
||||
"diceAppearance": "Dice Appearance",
|
||||
"animations": "Animations",
|
||||
"defaultAnimations": "Set Animations As Player Defaults",
|
||||
|
|
@ -3071,6 +2928,18 @@
|
|||
"immunityTo": "Immunity: {immunities}"
|
||||
},
|
||||
"featureTitle": "Class Feature",
|
||||
"groupRoll": {
|
||||
"title": "Group Roll",
|
||||
"leader": "Leader",
|
||||
"partyTeam": "Party Team",
|
||||
"team": "Team",
|
||||
"selectLeader": "Select a Leader",
|
||||
"selectMember": "Select a Member",
|
||||
"rerollTitle": "Reroll Group Roll",
|
||||
"rerollContent": "Are you sure you want to reroll your {trait} roll?",
|
||||
"rerollTooltip": "Reroll",
|
||||
"wholePartySelected": "The whole party is selected"
|
||||
},
|
||||
"healingRoll": {
|
||||
"title": "Heal - {damage}",
|
||||
"heal": "Heal",
|
||||
|
|
@ -3097,7 +2966,6 @@
|
|||
}
|
||||
},
|
||||
"ChatLog": {
|
||||
"rerollActionRoll": "Reroll Action",
|
||||
"rerollDamage": "Reroll Damage",
|
||||
"assignTagRoll": "Assign as Tag Roll"
|
||||
},
|
||||
|
|
@ -3114,8 +2982,6 @@
|
|||
},
|
||||
"EffectsDisplay": {
|
||||
"removeThing": "[Right Click] Remove {thing}",
|
||||
"increaseStacks": "[Left Click] Increment Stacks",
|
||||
"decreaseStacks": "[Right Click] Decrement Stacks",
|
||||
"appliedBy": "Applied By: {by}"
|
||||
},
|
||||
"ItemBrowser": {
|
||||
|
|
@ -3231,6 +3097,7 @@
|
|||
"subclassesAlreadyPresent": "You already have a class and multiclass subclass",
|
||||
"noDiceSystem": "Your selected dice {system} does not have a {faces} dice",
|
||||
"gmMenuRefresh": "You refreshed all actions and resources {types}",
|
||||
"subclassAlreadyLinked": "{name} is already a subclass in the class {class}. Remove it from there if you want it to be a subclass to this class.",
|
||||
"gmRequired": "This action requires an online GM",
|
||||
"gmOnly": "This can only be accessed by the GM",
|
||||
"noActorOwnership": "You do not have permissions for this character",
|
||||
|
|
@ -3239,12 +3106,7 @@
|
|||
"tokenActorsMissing": "[{names}] missing Actors",
|
||||
"domainTouchRequirement": "This domain card requires {nr} {domain} cards in the loadout to be used",
|
||||
"knowTheTide": "Know The Tide gained a token",
|
||||
"lackingItemTransferPermission": "User {user} lacks owner permission needed to transfer items to {target}",
|
||||
"noTokenTargeted": "No token is targeted",
|
||||
"behaviorRegionRequiresGM": "Creating a Region with an attached Behavior requires an online GM"
|
||||
},
|
||||
"Progress": {
|
||||
"migrationLabel": "Performing system migration. Please wait and do not close Foundry."
|
||||
"lackingItemTransferPermission": "User {user} lacks owner permission needed to transfer items to {target}"
|
||||
},
|
||||
"Sidebar": {
|
||||
"actorDirectory": {
|
||||
|
|
@ -3253,8 +3115,6 @@
|
|||
"companion": "Level {level} - {partner}",
|
||||
"companionNoPartner": "No Partner",
|
||||
"duplicateToNewTier": "Duplicate to New Tier",
|
||||
"activateParty": "Make Active Party",
|
||||
"partyIsActive": "Active",
|
||||
"createAdversary": "Create Adversary",
|
||||
"pickTierTitle": "Pick a new tier for this adversary"
|
||||
},
|
||||
|
|
@ -3267,7 +3127,6 @@
|
|||
"Tooltip": {
|
||||
"disableEffect": "Disable Effect",
|
||||
"enableEffect": "Enable Effect",
|
||||
"edit": "Edit",
|
||||
"openItemWorld": "Open Item World",
|
||||
"openActorWorld": "Open Actor World",
|
||||
"sendToChat": "Send to Chat",
|
||||
|
|
|
|||
|
|
@ -154,8 +154,8 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
|||
v.active = this.tabGroups[v.group]
|
||||
? this.tabGroups[v.group] === v.id
|
||||
: this.tabGroups.primary !== 'equipment'
|
||||
? v.active
|
||||
: false;
|
||||
? v.active
|
||||
: false;
|
||||
v.cssClass = v.active ? 'active' : '';
|
||||
|
||||
switch (v.id) {
|
||||
|
|
@ -211,9 +211,9 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
|||
|
||||
context.suggestedTraits = this.setup.class.system
|
||||
? Object.keys(this.setup.class.system.characterGuide.suggestedTraits).map(traitKey => {
|
||||
const trait = this.setup.class.system.characterGuide.suggestedTraits[traitKey];
|
||||
return `${game.i18n.localize(`DAGGERHEART.CONFIG.Traits.${traitKey}.short`)} ${trait > 0 ? `+${trait}` : trait}`;
|
||||
})
|
||||
const trait = this.setup.class.system.characterGuide.suggestedTraits[traitKey];
|
||||
return `${game.i18n.localize(`DAGGERHEART.CONFIG.Traits.${traitKey}.short`)} ${trait > 0 ? `+${trait}` : trait}`;
|
||||
})
|
||||
: [];
|
||||
context.traits = {
|
||||
values: Object.keys(this.setup.traits).map(traitKey => {
|
||||
|
|
@ -439,18 +439,15 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
|||
'system.domain': { key: 'system.domain', value: this.setup.class?.system.domains ?? null }
|
||||
};
|
||||
|
||||
if (type === 'subclasses') {
|
||||
const classItem = this.setup.class;
|
||||
const uuid = classItem?._stats.compendiumSource ?? classItem?.uuid;
|
||||
if (type === 'subclasses')
|
||||
presets.filter = {
|
||||
'system.linkedClass': { key: 'system.linkedClass', value: uuid }
|
||||
'system.linkedClass.uuid': { key: 'system.linkedClass.uuid', value: this.setup.class?.uuid }
|
||||
};
|
||||
}
|
||||
|
||||
if (equipment.includes(type))
|
||||
presets.filter = {
|
||||
'system.tier': { key: 'system.tier', value: 1 },
|
||||
type: { key: 'type', value: type }
|
||||
'type': { key: 'type', value: type }
|
||||
};
|
||||
|
||||
ui.compendiumBrowser.open(presets);
|
||||
|
|
@ -562,7 +559,7 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
|||
experiences: {
|
||||
...this.setup.experiences,
|
||||
...Object.keys(this.character.system.experiences).reduce((acc, key) => {
|
||||
acc[`${key}`] = _del;
|
||||
acc[`-=${key}`] = null;
|
||||
return acc;
|
||||
}, {})
|
||||
}
|
||||
|
|
@ -613,8 +610,7 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
|||
[foundry.utils.randomID()]: {}
|
||||
};
|
||||
} else if (item.type === 'subclass' && event.target.closest('.subclass-card')) {
|
||||
const classSubclasses = await this.setup.class.system.fetchSubclasses();
|
||||
if (classSubclasses.every(subclass => subclass.uuid !== item.uuid)) {
|
||||
if (this.setup.class.system.subclasses.every(subclass => subclass.uuid !== item.uuid)) {
|
||||
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.subclassNotInClass'));
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ export default class CompendiumBrowserSettings extends HandlebarsApplicationMixi
|
|||
const excludedSourceData = this.browserSettings.excludedSources;
|
||||
const excludedPackData = this.browserSettings.excludedPacks;
|
||||
context.typePackCollections = game.packs.reduce((acc, pack) => {
|
||||
const { type, label, packageType, packageName: basePackageName, name, id } = pack.metadata;
|
||||
const { type, label, packageType, packageName: basePackageName, id } = pack.metadata;
|
||||
if (!CompendiumBrowserSettings.#browserPackTypes.includes(type)) return acc;
|
||||
|
||||
const isWorldPack = packageType === 'world';
|
||||
|
|
@ -68,15 +68,13 @@ export default class CompendiumBrowserSettings extends HandlebarsApplicationMixi
|
|||
if (!acc[type].sources[packageName])
|
||||
acc[type].sources[packageName] = { label: sourceLabel, checked: sourceChecked, packs: [] };
|
||||
|
||||
const included =
|
||||
!excludedPackData[packageName] ||
|
||||
!excludedPackData[packageName][name]?.excludedDocumentTypes.includes(type);
|
||||
const checked = !excludedPackData[id] || !excludedPackData[id].excludedDocumentTypes.includes(type);
|
||||
|
||||
acc[type].sources[packageName].packs.push({
|
||||
name,
|
||||
pack: id,
|
||||
type,
|
||||
label: id === game.system.id ? game.system.title : game.i18n.localize(label),
|
||||
checked: included
|
||||
checked: checked
|
||||
});
|
||||
|
||||
return acc;
|
||||
|
|
@ -108,16 +106,16 @@ export default class CompendiumBrowserSettings extends HandlebarsApplicationMixi
|
|||
toggleTypedPack(event) {
|
||||
event.stopPropagation();
|
||||
|
||||
const { type, source, packName } = event.target.dataset;
|
||||
const currentlyExcluded = this.browserSettings.excludedPacks[source]?.[packName]
|
||||
? this.browserSettings.excludedPacks[source][packName].excludedDocumentTypes.includes(type)
|
||||
const { type, pack } = event.target.dataset;
|
||||
const currentlyExcluded = this.browserSettings.excludedPacks[pack]
|
||||
? this.browserSettings.excludedPacks[pack].excludedDocumentTypes.includes(type)
|
||||
: false;
|
||||
|
||||
this.browserSettings.excludedPacks[source] ??= {};
|
||||
this.browserSettings.excludedPacks[source][packName] ??= { excludedDocumentTypes: [] };
|
||||
this.browserSettings.excludedPacks[source][packName].excludedDocumentTypes = currentlyExcluded
|
||||
? this.browserSettings.excludedPacks[source][packName].excludedDocumentTypes.filter(x => x !== type)
|
||||
: [...(this.browserSettings.excludedPacks[source][packName]?.excludedDocumentTypes ?? []), type];
|
||||
if (!this.browserSettings.excludedPacks[pack])
|
||||
this.browserSettings.excludedPacks[pack] = { excludedDocumentTypes: [] };
|
||||
this.browserSettings.excludedPacks[pack].excludedDocumentTypes = currentlyExcluded
|
||||
? this.browserSettings.excludedPacks[pack].excludedDocumentTypes.filter(x => x !== type)
|
||||
: [...(this.browserSettings.excludedPacks[pack]?.excludedDocumentTypes ?? []), type];
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,9 +10,10 @@ export { default as ImageSelectDialog } from './imageSelectDialog.mjs';
|
|||
export { default as ItemTransferDialog } from './itemTransfer.mjs';
|
||||
export { default as MulticlassChoiceDialog } from './multiclassChoiceDialog.mjs';
|
||||
export { default as OwnershipSelection } from './ownershipSelection.mjs';
|
||||
export { default as RerollDamageDialog } from './rerollDamageDialog.mjs';
|
||||
export { default as ResourceDiceDialog } from './resourceDiceDialog.mjs';
|
||||
export { default as ActionSelectionDialog } from './actionSelectionDialog.mjs';
|
||||
export { default as GroupRollDialog } from './group-roll-dialog.mjs';
|
||||
export { default as TagTeamDialog } from './tagTeamDialog.mjs';
|
||||
export { default as GroupRollDialog } from './groupRollDialog.mjs';
|
||||
export { default as RiskItAllDialog } from './riskItAllDialog.mjs';
|
||||
export { default as CompendiumBrowserSettingsDialog } from './CompendiumBrowserSettings.mjs';
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ export default class ActionSelectionDialog extends HandlebarsApplicationMixin(Ap
|
|||
|
||||
static async #onChooseAction(event, button) {
|
||||
const { actionId } = button.dataset;
|
||||
this.#action = this.item.system.actionsList.find(a => a._id === actionId);
|
||||
this.#action = this.#item.system.actionsList.find(a => a._id === actionId);
|
||||
Object.defineProperty(this.#event, 'shiftKey', {
|
||||
get() {
|
||||
return event.shiftKey;
|
||||
|
|
|
|||
|
|
@ -10,12 +10,6 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
|
|||
this.selected = null;
|
||||
this.evolved = { form: null };
|
||||
this.hybrid = { forms: {}, advantages: {}, features: {} };
|
||||
this.modifications = {
|
||||
traitBonuses: configData.modifications.traitBonuses.map(x => ({
|
||||
trait: null,
|
||||
bonus: x.bonus
|
||||
}))
|
||||
};
|
||||
|
||||
this._dragDrop = this._createDragDropHandlers();
|
||||
}
|
||||
|
|
@ -34,7 +28,6 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
|
|||
selectBeastform: this.selectBeastform,
|
||||
toggleHybridFeature: this.toggleHybridFeature,
|
||||
toggleHybridAdvantage: this.toggleHybridAdvantage,
|
||||
toggleTraitBonus: this.toggleTraitBonus,
|
||||
submitBeastform: this.submitBeastform
|
||||
},
|
||||
form: {
|
||||
|
|
@ -55,7 +48,6 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
|
|||
tabs: { template: 'systems/daggerheart/templates/dialogs/beastform/tabs.hbs' },
|
||||
beastformTier: { template: 'systems/daggerheart/templates/dialogs/beastform/beastformTier.hbs' },
|
||||
advanced: { template: 'systems/daggerheart/templates/dialogs/beastform/advanced.hbs' },
|
||||
modifications: { template: 'systems/daggerheart/templates/dialogs/beastform/modifications.hbs' },
|
||||
footer: { template: 'systems/daggerheart/templates/dialogs/beastform/footer.hbs' }
|
||||
};
|
||||
|
||||
|
|
@ -154,9 +146,6 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
|
|||
{}
|
||||
);
|
||||
|
||||
context.modifications = this.modifications;
|
||||
context.traits = CONFIG.DH.ACTOR.abilities;
|
||||
|
||||
context.tier = beastformTiers[this.tabGroups.primary];
|
||||
context.tierKey = this.tabGroups.primary;
|
||||
|
||||
|
|
@ -166,9 +155,6 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
|
|||
}
|
||||
|
||||
canSubmit() {
|
||||
const modificationsFinished = this.modifications.traitBonuses.every(x => x.trait);
|
||||
if (!modificationsFinished) return false;
|
||||
|
||||
if (this.selected) {
|
||||
switch (this.selected.system.beastformType) {
|
||||
case 'normal':
|
||||
|
|
@ -275,13 +261,6 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
|
|||
this.render();
|
||||
}
|
||||
|
||||
static toggleTraitBonus(_, button) {
|
||||
const { index, trait } = button.dataset;
|
||||
this.modifications.traitBonuses[index].trait =
|
||||
this.modifications.traitBonuses[index].trait === trait ? null : trait;
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async submitBeastform() {
|
||||
await this.close({ submitted: true });
|
||||
}
|
||||
|
|
@ -313,23 +292,6 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
|
|||
}
|
||||
}
|
||||
|
||||
const beastformEffect = selected.effects.find(x => x.type === 'beastform');
|
||||
for (const traitBonus of app.modifications.traitBonuses) {
|
||||
const existingChange = beastformEffect.changes.find(
|
||||
x => x.key === `system.traits.${traitBonus.trait}.value`
|
||||
);
|
||||
if (existingChange) {
|
||||
existingChange.value = Number.parseInt(existingChange.value) + traitBonus.bonus;
|
||||
} else {
|
||||
beastformEffect.changes.push({
|
||||
key: `system.traits.${traitBonus.trait}.value`,
|
||||
mode: 2,
|
||||
priority: null,
|
||||
value: traitBonus.bonus
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
resolve({
|
||||
selected: selected,
|
||||
evolved: { ...app.evolved, form: evolved },
|
||||
|
|
|
|||
|
|
@ -77,8 +77,8 @@ export default class CharacterResetDialog extends HandlebarsApplicationMixin(App
|
|||
|
||||
if (!this.data.optional.portrait.keep) {
|
||||
foundry.utils.setProperty(update, 'img', this.actor.schema.fields.img.initial(this.actor));
|
||||
foundry.utils.setProperty(update, 'prototypeToken.texture', _replace({}));
|
||||
foundry.utils.setProperty(update, 'prototypeToken.ring', _replace({}));
|
||||
foundry.utils.setProperty(update, 'prototypeToken.==texture', {});
|
||||
foundry.utils.setProperty(update, 'prototypeToken.==ring', {});
|
||||
}
|
||||
|
||||
if (this.data.optional.biography.keep)
|
||||
|
|
@ -89,7 +89,7 @@ export default class CharacterResetDialog extends HandlebarsApplicationMixin(App
|
|||
const { system, ...rest } = update;
|
||||
await this.actor.update({
|
||||
...rest,
|
||||
system: _replace(system ?? {})
|
||||
'==system': system ?? {}
|
||||
});
|
||||
|
||||
const inventoryItemTypes = ['weapon', 'armor', 'consumable', 'loot'];
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
updateIsAdvantage: this.updateIsAdvantage,
|
||||
selectExperience: this.selectExperience,
|
||||
toggleReaction: this.toggleReaction,
|
||||
toggleTagTeamRoll: this.toggleTagTeamRoll,
|
||||
toggleSelectedEffect: this.toggleSelectedEffect,
|
||||
submitRoll: this.submitRoll
|
||||
},
|
||||
|
|
@ -70,8 +71,8 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
context.rollConfig = this.config;
|
||||
context.hasRoll = !!this.config.roll;
|
||||
context.canRoll = true;
|
||||
context.selectedMessageMode = this.config.selectedMessageMode ?? game.settings.get('core', 'messageMode');
|
||||
context.rollModes = Object.entries(CONFIG.ChatMessage.modes).map(([action, { label, icon }]) => ({
|
||||
context.selectedRollMode = this.config.selectedRollMode ?? game.settings.get('core', 'rollMode');
|
||||
context.rollModes = Object.entries(CONFIG.Dice.rollModes).map(([action, { label, icon }]) => ({
|
||||
action,
|
||||
label,
|
||||
icon
|
||||
|
|
@ -123,10 +124,6 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
context.advantage = this.config.roll?.advantage;
|
||||
context.disadvantage = this.config.roll?.disadvantage;
|
||||
context.diceOptions = CONFIG.DH.GENERAL.diceTypes;
|
||||
context.dieFaces = CONFIG.DH.GENERAL.dieFaces.reduce((acc, face) => {
|
||||
acc[face] = `d${face}`;
|
||||
return acc;
|
||||
}, {});
|
||||
context.isLite = this.config.roll?.lite;
|
||||
context.extraFormula = this.config.extraFormula;
|
||||
context.formula = this.roll.constructFormula(this.config);
|
||||
|
|
@ -136,6 +133,12 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
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;
|
||||
}
|
||||
|
||||
|
|
@ -146,17 +149,19 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
}));
|
||||
}
|
||||
|
||||
static updateRollConfiguration(_event, _, formData) {
|
||||
static updateRollConfiguration(event, _, formData) {
|
||||
const { ...rest } = foundry.utils.expandObject(formData.object);
|
||||
|
||||
this.config.selectedMessageMode = rest.selectedMessageMode;
|
||||
this.config.selectedRollMode = rest.selectedRollMode;
|
||||
|
||||
if (this.config.costs) {
|
||||
this.config.costs = foundry.utils.mergeObject(this.config.costs, rest.costs);
|
||||
}
|
||||
if (this.config.uses) this.config.uses = foundry.utils.mergeObject(this.config.uses, rest.uses);
|
||||
if (rest.roll?.dice) {
|
||||
this.roll = foundry.utils.mergeObject(this.roll, rest.roll.dice);
|
||||
Object.entries(rest.roll.dice).forEach(([key, value]) => {
|
||||
this.roll[key] = value;
|
||||
});
|
||||
}
|
||||
if (rest.hasOwnProperty('trait')) {
|
||||
this.config.roll.trait = rest.trait;
|
||||
|
|
@ -175,15 +180,6 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
this.disadvantage = advantage === -1;
|
||||
|
||||
this.config.roll.advantage = this.config.roll.advantage === advantage ? 0 : advantage;
|
||||
if (this.config.roll.advantage === 0) return this.render();
|
||||
|
||||
const defaultFaces =
|
||||
this.config.roll.advantage === 1
|
||||
? this.config.data.rules.roll.defaultAdvantageDice
|
||||
: this.config.data.rules.roll.defaultDisadvantageDice;
|
||||
const faces = Number.parseInt(defaultFaces);
|
||||
this.roll.advantageFaces = Number.isNaN(faces) ? this.roll.advantageFaces : faces;
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
|
|
@ -196,14 +192,14 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
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,
|
||||
{
|
||||
extKey: button.dataset.key,
|
||||
key: this.config?.data?.parent?.isNPC ? 'fear' : 'hope',
|
||||
value: 1,
|
||||
name: this.config.data?.system.experiences?.[button.dataset.key]?.name
|
||||
}
|
||||
];
|
||||
...this.config.costs,
|
||||
{
|
||||
extKey: button.dataset.key,
|
||||
key: this.config?.data?.parent?.isNPC ? 'fear' : 'hope',
|
||||
value: 1,
|
||||
name: this.config.data?.system.experiences?.[button.dataset.key]?.name
|
||||
}
|
||||
];
|
||||
this.render();
|
||||
}
|
||||
|
||||
|
|
@ -213,12 +209,17 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
this.config.actionType = this.reactionOverride
|
||||
? 'reaction'
|
||||
: this.config.actionType === 'reaction'
|
||||
? 'action'
|
||||
: this.config.actionType;
|
||||
? 'action'
|
||||
: this.config.actionType;
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
||||
static toggleTagTeamRoll() {
|
||||
this.config.tagTeamSelected = !this.config.tagTeamSelected;
|
||||
this.render();
|
||||
}
|
||||
|
||||
static toggleSelectedEffect(_event, button) {
|
||||
this.selectedEffects[button.dataset.key].selected = !this.selectedEffects[button.dataset.key].selected;
|
||||
this.render();
|
||||
|
|
|
|||
|
|
@ -22,8 +22,6 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
|
|||
},
|
||||
actions: {
|
||||
toggleSelectedEffect: this.toggleSelectedEffect,
|
||||
updateGroupAttack: this.updateGroupAttack,
|
||||
toggleCritical: this.toggleCritical,
|
||||
submitRoll: this.submitRoll
|
||||
},
|
||||
form: {
|
||||
|
|
@ -54,9 +52,8 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
|
|||
context.formula = this.roll.constructFormula(this.config);
|
||||
context.hasHealing = this.config.hasHealing;
|
||||
context.directDamage = this.config.directDamage;
|
||||
context.selectedMessageMode = this.config.selectedMessageMode;
|
||||
context.isCritical = this.config.isCritical;
|
||||
context.rollModes = Object.entries(CONFIG.ChatMessage.modes).map(([action, { label, icon }]) => ({
|
||||
context.selectedRollMode = this.config.selectedRollMode;
|
||||
context.rollModes = Object.entries(CONFIG.Dice.rollModes).map(([action, { label, icon }]) => ({
|
||||
action,
|
||||
label,
|
||||
icon
|
||||
|
|
@ -65,45 +62,15 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
|
|||
context.hasSelectedEffects = Boolean(Object.keys(this.selectedEffects).length);
|
||||
context.selectedEffects = this.selectedEffects;
|
||||
|
||||
context.damageOptions = this.config.damageOptions;
|
||||
context.rangeOptions = CONFIG.DH.GENERAL.groupAttackRange;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
static updateRollConfiguration(_event, _, formData) {
|
||||
const data = foundry.utils.expandObject(formData.object);
|
||||
foundry.utils.mergeObject(this.config.roll, data.roll);
|
||||
foundry.utils.mergeObject(this.config.modifiers, data.modifiers);
|
||||
this.config.selectedMessageMode = data.selectedMessageMode;
|
||||
const { ...rest } = foundry.utils.expandObject(formData.object);
|
||||
foundry.utils.mergeObject(this.config.roll, rest.roll);
|
||||
foundry.utils.mergeObject(this.config.modifiers, rest.modifiers);
|
||||
this.config.selectedRollMode = rest.selectedRollMode;
|
||||
|
||||
if (data.damageOptions) {
|
||||
const numAttackers = data.damageOptions.groupAttack?.numAttackers;
|
||||
if (typeof numAttackers !== 'number' || numAttackers % 1 !== 0) {
|
||||
data.damageOptions.groupAttack.numAttackers = null;
|
||||
}
|
||||
|
||||
foundry.utils.mergeObject(this.config.damageOptions, data.damageOptions);
|
||||
}
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
static updateGroupAttack() {
|
||||
const targets = Array.from(game.user.targets);
|
||||
if (targets.length === 0)
|
||||
return ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.noTokenTargeted'));
|
||||
|
||||
const actorId = this.roll.data.parent.id;
|
||||
const range = this.config.damageOptions.groupAttack.range;
|
||||
const groupAttackTokens = game.system.api.fields.ActionFields.DamageField.getGroupAttackTokens(actorId, range);
|
||||
|
||||
this.config.damageOptions.groupAttack.numAttackers = groupAttackTokens.length;
|
||||
this.render();
|
||||
}
|
||||
|
||||
static toggleCritical() {
|
||||
this.config.isCritical = !this.config.isCritical;
|
||||
this.render();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { damageKeyToNumber, getArmorSources, getDamageLabel } from '../../helpers/utils.mjs';
|
||||
import { damageKeyToNumber, getDamageLabel } from '../../helpers/utils.mjs';
|
||||
|
||||
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||
|
||||
|
|
@ -10,7 +10,6 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
|||
this.reject = reject;
|
||||
this.actor = actor;
|
||||
this.damage = damage;
|
||||
|
||||
this.damageType = damageType;
|
||||
this.rulesDefault = game.settings.get(
|
||||
CONFIG.DH.id,
|
||||
|
|
@ -21,21 +20,14 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
|||
this.rulesDefault
|
||||
);
|
||||
|
||||
const orderedArmorSources = getArmorSources(actor).filter(s => !s.disabled);
|
||||
const armor = orderedArmorSources.reduce((acc, { name, document }) => {
|
||||
const { current, max } = document.type === 'armor' ? document.system.armor : document.system.armorData;
|
||||
acc.push({
|
||||
name,
|
||||
effect: document,
|
||||
marks: [...Array(max).keys()].reduce((acc, _, index) => {
|
||||
const spent = index < current;
|
||||
acc[foundry.utils.randomID()] = { selected: false, disabled: spent, spent };
|
||||
return acc;
|
||||
}, {})
|
||||
});
|
||||
const canApplyArmor = damageType.every(t => actor.system.armorApplicableDamageTypes[t] === true);
|
||||
const availableArmor = actor.system.armorScore - actor.system.armor.system.marks.value;
|
||||
const maxArmorMarks = canApplyArmor ? availableArmor : 0;
|
||||
|
||||
const armor = [...Array(maxArmorMarks).keys()].reduce((acc, _) => {
|
||||
acc[foundry.utils.randomID()] = { selected: false };
|
||||
return acc;
|
||||
}, []);
|
||||
}, {});
|
||||
const stress = [...Array(actor.system.rules.damageReduction.maxArmorMarked.stressExtra ?? 0).keys()].reduce(
|
||||
(acc, _) => {
|
||||
acc[foundry.utils.randomID()] = { selected: false };
|
||||
|
|
@ -129,42 +121,36 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
|||
context.thresholdImmunities =
|
||||
Object.keys(this.thresholdImmunities).length > 0 ? this.thresholdImmunities : null;
|
||||
|
||||
const { selectedStressMarks, stressReductions, currentMarks, currentDamage, maxArmorUsed, availableArmor } =
|
||||
const { selectedArmorMarks, selectedStressMarks, stressReductions, currentMarks, currentDamage } =
|
||||
this.getDamageInfo();
|
||||
|
||||
context.armorScore = this.actor.system.armorScore.max;
|
||||
context.armorScore = this.actor.system.armorScore;
|
||||
context.armorMarks = currentMarks;
|
||||
context.basicMarksUsed =
|
||||
selectedArmorMarks.length === this.actor.system.rules.damageReduction.maxArmorMarked.value;
|
||||
|
||||
const stressReductionStress = this.availableStressReductions
|
||||
? stressReductions.reduce((acc, red) => acc + red.cost, 0)
|
||||
: 0;
|
||||
const stress = this.actor.system.resources.stress;
|
||||
context.stress =
|
||||
selectedStressMarks.length > 0 || this.availableStressReductions
|
||||
? {
|
||||
value: stress.value + selectedStressMarks.length + stressReductionStress,
|
||||
max: stress.max
|
||||
}
|
||||
value:
|
||||
this.actor.system.resources.stress.value + selectedStressMarks.length + stressReductionStress,
|
||||
max: this.actor.system.resources.stress.max
|
||||
}
|
||||
: null;
|
||||
|
||||
context.maxArmorUsed = maxArmorUsed;
|
||||
context.availableArmor = availableArmor;
|
||||
context.basicMarksUsed = availableArmor === 0 || selectedStressMarks.length;
|
||||
|
||||
const armorSources = [];
|
||||
for (const source of this.marks.armor) {
|
||||
armorSources.push({
|
||||
label: source.name,
|
||||
uuid: source.effect.uuid,
|
||||
marks: source.marks
|
||||
});
|
||||
}
|
||||
const maxArmor = this.actor.system.rules.damageReduction.maxArmorMarked.value;
|
||||
context.marks = {
|
||||
armor: armorSources,
|
||||
armor: Object.keys(this.marks.armor).reduce((acc, key, index) => {
|
||||
const mark = this.marks.armor[key];
|
||||
if (!this.rulesOn || index + 1 <= maxArmor) acc[key] = mark;
|
||||
|
||||
return acc;
|
||||
}, {}),
|
||||
stress: this.marks.stress
|
||||
};
|
||||
|
||||
context.usesStressArmor = Object.keys(context.marks.stress).length;
|
||||
context.availableStressReductions = this.availableStressReductions;
|
||||
|
||||
context.damage = getDamageLabel(this.damage);
|
||||
|
|
@ -181,31 +167,27 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
|||
}
|
||||
|
||||
getDamageInfo = () => {
|
||||
const selectedArmorMarks = this.marks.armor.flatMap(x => Object.values(x.marks).filter(x => x.selected));
|
||||
const selectedArmorMarks = Object.values(this.marks.armor).filter(x => x.selected);
|
||||
const selectedStressMarks = Object.values(this.marks.stress).filter(x => x.selected);
|
||||
const stressReductions = this.availableStressReductions
|
||||
? Object.values(this.availableStressReductions).filter(red => red.selected)
|
||||
: [];
|
||||
const currentMarks = this.actor.system.armorScore.value + selectedArmorMarks.length;
|
||||
|
||||
const maxArmorUsed = this.actor.system.rules.damageReduction.maxArmorMarked.value + selectedStressMarks.length;
|
||||
const availableArmor =
|
||||
maxArmorUsed -
|
||||
this.marks.armor.reduce((acc, source) => {
|
||||
acc += Object.values(source.marks).filter(x => x.selected).length;
|
||||
return acc;
|
||||
}, 0);
|
||||
const currentMarks =
|
||||
this.actor.system.armor.system.marks.value + selectedArmorMarks.length + selectedStressMarks.length;
|
||||
|
||||
const armorMarkReduction =
|
||||
selectedArmorMarks.length * this.actor.system.rules.damageReduction.increasePerArmorMark;
|
||||
let currentDamage = Math.max(this.damage - armorMarkReduction - stressReductions.length, 0);
|
||||
let currentDamage = Math.max(
|
||||
this.damage - armorMarkReduction - selectedStressMarks.length - stressReductions.length,
|
||||
0
|
||||
);
|
||||
if (this.reduceSeverity) {
|
||||
currentDamage = Math.max(currentDamage - this.reduceSeverity, 0);
|
||||
}
|
||||
|
||||
if (this.thresholdImmunities[currentDamage]) currentDamage = 0;
|
||||
|
||||
return { selectedStressMarks, stressReductions, currentMarks, currentDamage, maxArmorUsed, availableArmor };
|
||||
return { selectedArmorMarks, selectedStressMarks, stressReductions, currentMarks, currentDamage };
|
||||
};
|
||||
|
||||
static toggleRules() {
|
||||
|
|
@ -213,10 +195,13 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
|||
|
||||
const maxArmor = this.actor.system.rules.damageReduction.maxArmorMarked.value;
|
||||
this.marks = {
|
||||
armor: this.marks.armor.map((mark, index) => {
|
||||
armor: Object.keys(this.marks.armor).reduce((acc, key, index) => {
|
||||
const mark = this.marks.armor[key];
|
||||
const keepSelectValue = !this.rulesOn || index + 1 <= maxArmor;
|
||||
return { ...mark, selected: keepSelectValue ? mark.selected : false };
|
||||
}),
|
||||
acc[key] = { ...mark, selected: keepSelectValue ? mark.selected : false };
|
||||
|
||||
return acc;
|
||||
}, {}),
|
||||
stress: this.marks.stress
|
||||
};
|
||||
|
||||
|
|
@ -224,8 +209,8 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
|||
}
|
||||
|
||||
static setMarks(_, target) {
|
||||
const currentMark = foundry.utils.getProperty(this.marks, target.dataset.path);
|
||||
const { selectedStressMarks, stressReductions, currentDamage, availableArmor } = this.getDamageInfo();
|
||||
const currentMark = this.marks[target.dataset.type][target.dataset.key];
|
||||
const { selectedStressMarks, stressReductions, currentMarks, currentDamage } = this.getDamageInfo();
|
||||
|
||||
if (!currentMark.selected && currentDamage === 0) {
|
||||
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.damageAlreadyNone'));
|
||||
|
|
@ -233,18 +218,12 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
|||
}
|
||||
|
||||
if (this.rulesOn) {
|
||||
if (target.dataset.type === 'armor' && !currentMark.selected && !availableArmor) {
|
||||
if (!currentMark.selected && currentMarks === this.actor.system.armorScore) {
|
||||
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noAvailableArmorMarks'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const stressUsed = selectedStressMarks.length;
|
||||
if (target.dataset.type === 'armor' && stressUsed) {
|
||||
const updateResult = this.updateStressArmor(target.dataset.id, !currentMark.selected);
|
||||
if (updateResult === false) return;
|
||||
}
|
||||
|
||||
if (currentMark.selected) {
|
||||
const currentDamageLabel = getDamageLabel(currentDamage);
|
||||
for (let reduction of stressReductions) {
|
||||
|
|
@ -253,16 +232,8 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
|||
}
|
||||
}
|
||||
|
||||
if (target.dataset.type === 'stress' && currentMark.armorMarkId) {
|
||||
for (const source of this.marks.armor) {
|
||||
const match = Object.keys(source.marks).find(key => key === currentMark.armorMarkId);
|
||||
if (match) {
|
||||
source.marks[match].selected = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
currentMark.armorMarkId = null;
|
||||
if (target.dataset.type === 'armor' && selectedStressMarks.length > 0) {
|
||||
selectedStressMarks.forEach(mark => (mark.selected = false));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -270,25 +241,6 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
|||
this.render();
|
||||
}
|
||||
|
||||
updateStressArmor(armorMarkId, select) {
|
||||
let stressMarkKey = null;
|
||||
if (select) {
|
||||
stressMarkKey = Object.keys(this.marks.stress).find(
|
||||
key => this.marks.stress[key].selected && !this.marks.stress[key].armorMarkId
|
||||
);
|
||||
} else {
|
||||
stressMarkKey = Object.keys(this.marks.stress).find(
|
||||
key => this.marks.stress[key].armorMarkId === armorMarkId
|
||||
);
|
||||
if (!stressMarkKey)
|
||||
stressMarkKey = Object.keys(this.marks.stress).find(key => this.marks.stress[key].selected);
|
||||
}
|
||||
|
||||
if (!stressMarkKey) return false;
|
||||
|
||||
this.marks.stress[stressMarkKey].armorMarkId = select ? armorMarkId : null;
|
||||
}
|
||||
|
||||
static useStressReduction(_, target) {
|
||||
const damageValue = Number(target.dataset.reduction);
|
||||
const stressReduction = this.availableStressReductions[damageValue];
|
||||
|
|
@ -327,18 +279,11 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
|||
}
|
||||
|
||||
static async takeDamage() {
|
||||
const { selectedStressMarks, stressReductions, currentDamage } = this.getDamageInfo();
|
||||
const armorChanges = this.marks.armor.reduce((acc, source) => {
|
||||
const amount = Object.values(source.marks).filter(x => x.selected).length;
|
||||
if (amount) acc.push({ uuid: source.effect.uuid, amount });
|
||||
const { selectedArmorMarks, selectedStressMarks, stressReductions, currentDamage } = this.getDamageInfo();
|
||||
const armorSpent = selectedArmorMarks.length + selectedStressMarks.length;
|
||||
const stressSpent = selectedStressMarks.length + stressReductions.reduce((acc, red) => acc + red.cost, 0);
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
const stressSpent =
|
||||
selectedStressMarks.filter(x => x.armorMarkId).length +
|
||||
stressReductions.reduce((acc, red) => acc + red.cost, 0);
|
||||
|
||||
this.resolve({ modifiedDamage: currentDamage, armorChanges, stressSpent });
|
||||
this.resolve({ modifiedDamage: currentDamage, armorSpent, stressSpent });
|
||||
await this.close(true);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -57,7 +57,6 @@ export default class DhDeathMove extends HandlebarsApplicationMixin(ApplicationV
|
|||
|
||||
let returnMessage = game.i18n.localize('DAGGERHEART.UI.Chat.deathMove.avoidScar');
|
||||
if (config.roll.fate.value <= this.actor.system.levelData.level.current) {
|
||||
const maxHope = this.actor.system.resources.hope.max + this.actor.system.scars;
|
||||
const newScarAmount = this.actor.system.scars + 1;
|
||||
await this.actor.update({
|
||||
system: {
|
||||
|
|
@ -65,7 +64,7 @@ export default class DhDeathMove extends HandlebarsApplicationMixin(ApplicationV
|
|||
}
|
||||
});
|
||||
|
||||
if (newScarAmount >= maxHope) {
|
||||
if (newScarAmount >= this.actor.system.resources.hope.max) {
|
||||
await this.actor.setDeathMoveDefeated(CONFIG.DH.GENERAL.defeatedConditionChoices.dead.id);
|
||||
return game.i18n.format('DAGGERHEART.UI.Chat.deathMove.journeysEnd', { scars: newScarAmount });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { expireActiveEffects, refreshIsAllowed } from '../../helpers/utils.mjs';
|
||||
import { refreshIsAllowed } from '../../helpers/utils.mjs';
|
||||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
||||
|
|
@ -203,7 +203,7 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
|||
const msg = {
|
||||
user: game.user.id,
|
||||
system: {
|
||||
moves: moves.map(move => ({ ...move, actions: Array.from(move.actions) })),
|
||||
moves: moves,
|
||||
actor: this.actor.uuid
|
||||
},
|
||||
speaker: cls.getSpeaker(),
|
||||
|
|
@ -259,16 +259,12 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
|||
const resetValue = increasing
|
||||
? 0
|
||||
: feature.system.resource.max
|
||||
? new Roll(
|
||||
Roll.replaceFormulaData(feature.system.resource.max, this.actor.getRollData())
|
||||
).evaluateSync().total
|
||||
: 0;
|
||||
? new Roll(Roll.replaceFormulaData(feature.system.resource.max, this.actor.getRollData())).evaluateSync().total
|
||||
: 0;
|
||||
|
||||
await feature.update({ 'system.resource.value': resetValue });
|
||||
}
|
||||
|
||||
expireActiveEffects(this.actor, [this.shortRest ? 'shortRest' : 'longRest']);
|
||||
|
||||
this.close();
|
||||
} else {
|
||||
this.render();
|
||||
|
|
|
|||
204
module/applications/dialogs/group-roll-dialog.mjs
Normal file
204
module/applications/dialogs/group-roll-dialog.mjs
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
import autocomplete from 'autocompleter';
|
||||
import { abilities } from '../../config/actorConfig.mjs';
|
||||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
||||
export default class GroupRollDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
constructor(actors) {
|
||||
super();
|
||||
this.actors = actors;
|
||||
this.actorLeader = {};
|
||||
this.actorsMembers = [];
|
||||
}
|
||||
|
||||
get title() {
|
||||
return 'Group Roll';
|
||||
}
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
tag: 'form',
|
||||
classes: ['daggerheart', 'views', 'dh-style', 'dialog', 'group-roll'],
|
||||
position: { width: 'auto', height: 'auto' },
|
||||
window: {
|
||||
title: 'DAGGERHEART.UI.Chat.groupRoll.title'
|
||||
},
|
||||
actions: {
|
||||
roll: GroupRollDialog.#roll,
|
||||
removeLeader: GroupRollDialog.#removeLeader,
|
||||
removeMember: GroupRollDialog.#removeMember
|
||||
},
|
||||
form: { handler: this.updateData, submitOnChange: true, closeOnSubmit: false }
|
||||
};
|
||||
|
||||
static PARTS = {
|
||||
application: {
|
||||
id: 'group-roll',
|
||||
template: 'systems/daggerheart/templates/dialogs/group-roll/group-roll.hbs'
|
||||
}
|
||||
};
|
||||
|
||||
_attachPartListeners(partId, htmlElement, options) {
|
||||
super._attachPartListeners(partId, htmlElement, options);
|
||||
const leaderChoices = this.actors.filter(x => this.actorsMembers.every(member => member.actor?.id !== x.id));
|
||||
const memberChoices = this.actors.filter(
|
||||
x => this.actorLeader?.actor?.id !== x.id && this.actorsMembers.every(member => member.actor?.id !== x.id)
|
||||
);
|
||||
|
||||
htmlElement.querySelectorAll('.leader-change-input').forEach(element => {
|
||||
autocomplete({
|
||||
input: element,
|
||||
fetch: function (text, update) {
|
||||
if (!text) {
|
||||
update(leaderChoices);
|
||||
} else {
|
||||
text = text.toLowerCase();
|
||||
var suggestions = leaderChoices.filter(n => n.name.toLowerCase().includes(text));
|
||||
update(suggestions);
|
||||
}
|
||||
},
|
||||
render: function (actor, search) {
|
||||
const actorName = game.i18n.localize(actor.name);
|
||||
const matchIndex = actorName.toLowerCase().indexOf(search);
|
||||
|
||||
const beforeText = actorName.slice(0, matchIndex);
|
||||
const matchText = actorName.slice(matchIndex, matchIndex + search.length);
|
||||
const after = actorName.slice(matchIndex + search.length, actorName.length);
|
||||
const img = document.createElement('img');
|
||||
img.src = actor.img;
|
||||
|
||||
const element = document.createElement('li');
|
||||
element.appendChild(img);
|
||||
|
||||
const label = document.createElement('span');
|
||||
label.innerHTML =
|
||||
`${beforeText}${matchText ? `<strong>${matchText}</strong>` : ''}${after}`.replaceAll(
|
||||
' ',
|
||||
' '
|
||||
);
|
||||
element.appendChild(label);
|
||||
|
||||
return element;
|
||||
},
|
||||
renderGroup: function (label) {
|
||||
const itemElement = document.createElement('div');
|
||||
itemElement.textContent = game.i18n.localize(label);
|
||||
return itemElement;
|
||||
},
|
||||
onSelect: actor => {
|
||||
element.value = actor.uuid;
|
||||
this.actorLeader = { actor: actor, trait: 'agility', difficulty: 0 };
|
||||
this.render();
|
||||
},
|
||||
click: e => e.fetch(),
|
||||
customize: function (_input, _inputRect, container) {
|
||||
container.style.zIndex = foundry.applications.api.ApplicationV2._maxZ;
|
||||
},
|
||||
minLength: 0
|
||||
});
|
||||
});
|
||||
|
||||
htmlElement.querySelectorAll('.team-push-input').forEach(element => {
|
||||
autocomplete({
|
||||
input: element,
|
||||
fetch: function (text, update) {
|
||||
if (!text) {
|
||||
update(memberChoices);
|
||||
} else {
|
||||
text = text.toLowerCase();
|
||||
var suggestions = memberChoices.filter(n => n.name.toLowerCase().includes(text));
|
||||
update(suggestions);
|
||||
}
|
||||
},
|
||||
render: function (actor, search) {
|
||||
const actorName = game.i18n.localize(actor.name);
|
||||
const matchIndex = actorName.toLowerCase().indexOf(search);
|
||||
|
||||
const beforeText = actorName.slice(0, matchIndex);
|
||||
const matchText = actorName.slice(matchIndex, matchIndex + search.length);
|
||||
const after = actorName.slice(matchIndex + search.length, actorName.length);
|
||||
const img = document.createElement('img');
|
||||
img.src = actor.img;
|
||||
|
||||
const element = document.createElement('li');
|
||||
element.appendChild(img);
|
||||
|
||||
const label = document.createElement('span');
|
||||
label.innerHTML =
|
||||
`${beforeText}${matchText ? `<strong>${matchText}</strong>` : ''}${after}`.replaceAll(
|
||||
' ',
|
||||
' '
|
||||
);
|
||||
element.appendChild(label);
|
||||
|
||||
return element;
|
||||
},
|
||||
renderGroup: function (label) {
|
||||
const itemElement = document.createElement('div');
|
||||
itemElement.textContent = game.i18n.localize(label);
|
||||
return itemElement;
|
||||
},
|
||||
onSelect: actor => {
|
||||
element.value = actor.uuid;
|
||||
this.actorsMembers.push({ actor: actor, trait: 'agility', difficulty: 0 });
|
||||
this.render({ force: true });
|
||||
},
|
||||
click: e => e.fetch(),
|
||||
customize: function (_input, _inputRect, container) {
|
||||
container.style.zIndex = foundry.applications.api.ApplicationV2._maxZ;
|
||||
},
|
||||
minLength: 0
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
context.leader = this.actorLeader;
|
||||
context.members = this.actorsMembers;
|
||||
context.traitList = abilities;
|
||||
|
||||
context.allSelected = this.actorsMembers.length + (this.actorLeader?.actor ? 1 : 0) === this.actors.length;
|
||||
context.rollDisabled = context.members.length === 0 || !this.actorLeader?.actor;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
static updateData(event, _, formData) {
|
||||
const { actorLeader, actorsMembers } = foundry.utils.expandObject(formData.object);
|
||||
this.actorLeader = foundry.utils.mergeObject(this.actorLeader, actorLeader);
|
||||
this.actorsMembers = foundry.utils.mergeObject(this.actorsMembers, actorsMembers);
|
||||
this.render(true);
|
||||
}
|
||||
|
||||
static async #removeLeader(_, button) {
|
||||
this.actorLeader = null;
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async #removeMember(_, button) {
|
||||
this.actorsMembers = this.actorsMembers.filter(m => m.actor.uuid !== button.dataset.memberUuid);
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async #roll() {
|
||||
const cls = getDocumentClass('ChatMessage');
|
||||
const systemData = {
|
||||
leader: this.actorLeader,
|
||||
members: this.actorsMembers
|
||||
};
|
||||
const msg = {
|
||||
type: 'groupRoll',
|
||||
user: game.user.id,
|
||||
speaker: cls.getSpeaker(),
|
||||
title: game.i18n.localize('DAGGERHEART.UI.Chat.groupRoll.title'),
|
||||
system: systemData,
|
||||
content: await foundry.applications.handlebars.renderTemplate(
|
||||
'systems/daggerheart/templates/ui/chat/groupRoll.hbs',
|
||||
{ system: systemData }
|
||||
)
|
||||
};
|
||||
|
||||
cls.create(msg);
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,500 +0,0 @@
|
|||
import { ResourceUpdateMap } from '../../data/action/baseAction.mjs';
|
||||
import { emitGMUpdate, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||
import Party from '../sheets/actors/party.mjs';
|
||||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
||||
export default class GroupRollDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
constructor(party) {
|
||||
super({ id: `GroupRollDialog-${party.id}` });
|
||||
|
||||
this.party = party;
|
||||
this.partyMembers = party.system.partyMembers
|
||||
.filter(x => Party.DICE_ROLL_ACTOR_TYPES.includes(x.type))
|
||||
.map(member => ({
|
||||
...member.toObject(),
|
||||
uuid: member.uuid,
|
||||
id: member.id,
|
||||
selected: true,
|
||||
owned: member.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER)
|
||||
}));
|
||||
|
||||
this.leader = null;
|
||||
this.openForAllPlayers = true;
|
||||
|
||||
this.tabGroups.application = Object.keys(party.system.groupRoll.participants).length
|
||||
? 'groupRoll'
|
||||
: 'initialization';
|
||||
|
||||
Hooks.on(socketEvent.Refresh, this.groupRollRefresh.bind());
|
||||
}
|
||||
|
||||
get title() {
|
||||
return game.i18n.localize('DAGGERHEART.APPLICATIONS.GroupRollSelect.title');
|
||||
}
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
tag: 'form',
|
||||
classes: ['daggerheart', 'views', 'dh-style', 'dialog', 'group-roll-dialog'],
|
||||
position: { width: 390, height: 'auto' },
|
||||
window: {
|
||||
icon: 'fa-solid fa-users'
|
||||
},
|
||||
actions: {
|
||||
toggleSelectMember: this.#toggleSelectMember,
|
||||
startGroupRoll: this.#startGroupRoll,
|
||||
makeRoll: this.#makeRoll,
|
||||
removeRoll: this.#removeRoll,
|
||||
rerollDice: this.#rerollDice,
|
||||
markSuccessful: this.#markSuccessful,
|
||||
cancelRoll: this.#onCancelRoll,
|
||||
finishRoll: this.#finishRoll
|
||||
},
|
||||
form: { handler: this.updateData, submitOnChange: true, closeOnSubmit: false }
|
||||
};
|
||||
|
||||
static PARTS = {
|
||||
initialization: {
|
||||
id: 'initialization',
|
||||
template: 'systems/daggerheart/templates/dialogs/groupRollDialog/initialization.hbs'
|
||||
},
|
||||
main: {
|
||||
id: 'main',
|
||||
template: 'systems/daggerheart/templates/dialogs/groupRollDialog/main.hbs'
|
||||
},
|
||||
leader: {
|
||||
id: 'leader',
|
||||
template: 'systems/daggerheart/templates/dialogs/groupRollDialog/parts/member.hbs'
|
||||
},
|
||||
result: {
|
||||
id: 'result',
|
||||
template: 'systems/daggerheart/templates/dialogs/groupRollDialog/parts/result.hbs'
|
||||
},
|
||||
footer: {
|
||||
id: 'footer',
|
||||
template: 'systems/daggerheart/templates/dialogs/groupRollDialog/parts/footer.hbs'
|
||||
}
|
||||
};
|
||||
|
||||
/** @inheritdoc */
|
||||
static TABS = {
|
||||
application: {
|
||||
tabs: [{ id: 'initialization' }, { id: 'groupRoll' }]
|
||||
}
|
||||
};
|
||||
|
||||
_attachPartListeners(partId, htmlElement, options) {
|
||||
super._attachPartListeners(partId, htmlElement, options);
|
||||
|
||||
htmlElement
|
||||
.querySelector('.main-character-field')
|
||||
?.addEventListener('input', this.updateLeaderField.bind(this));
|
||||
}
|
||||
|
||||
_configureRenderParts(options) {
|
||||
const parts = super._configureRenderParts(options);
|
||||
for (const memberKey of Object.keys(this.party.system.groupRoll.aidingCharacters)) {
|
||||
parts[memberKey] = {
|
||||
id: memberKey,
|
||||
template: 'systems/daggerheart/templates/dialogs/groupRollDialog/parts/member.hbs'
|
||||
};
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
|
||||
context.isGM = game.user.isGM;
|
||||
context.isEditable =
|
||||
game.user.isGM ||
|
||||
this.party.system.partyMembers.some(actor => {
|
||||
const selected = Boolean(this.party.system.groupRoll.participants[actor.id]);
|
||||
return selected && actor.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER);
|
||||
});
|
||||
context.fields = this.party.system.schema.fields.groupRoll.fields;
|
||||
context.data = this.party.system.groupRoll;
|
||||
context.traitOptions = CONFIG.DH.ACTOR.abilities;
|
||||
context.members = {};
|
||||
context.aidKeys = Object.keys(this.party.system.groupRoll.aidingCharacters);
|
||||
context.allHaveRolled = Object.keys(context.data.participants).every(key => {
|
||||
const data = context.data.participants[key];
|
||||
return Boolean(data.rollData);
|
||||
});
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
async _preparePartContext(partId, context, options) {
|
||||
const partContext = await super._preparePartContext(partId, context, options);
|
||||
partContext.partId = partId;
|
||||
partContext.leader = this.getRollCharacterData(this.party.system.groupRoll.leader);
|
||||
|
||||
switch (partId) {
|
||||
case 'initialization':
|
||||
partContext.groupRollFields = this.party.system.schema.fields.groupRoll.fields;
|
||||
partContext.memberSelection = this.partyMembers;
|
||||
|
||||
const selectedMembers = partContext.memberSelection.filter(x => x.selected);
|
||||
|
||||
partContext.selectedLeader = this.leader;
|
||||
partContext.selectedLeaderOptions = selectedMembers
|
||||
.filter(actor => actor.owned)
|
||||
.map(x => ({ value: x.id, label: x.name }));
|
||||
partContext.selectedLeaderDisabled = !selectedMembers.length;
|
||||
|
||||
partContext.canStartGroupRoll = selectedMembers.length > 1 && this.leader?.memberId;
|
||||
partContext.openForAllPlayers = this.openForAllPlayers;
|
||||
break;
|
||||
case 'result':
|
||||
const leader = this.party.system.groupRoll.leader;
|
||||
partContext.hasRolled =
|
||||
leader?.rollData ||
|
||||
Object.values(this.party.system.groupRoll?.aidingCharacters ?? {}).some(x => x.successful !== null);
|
||||
const { modifierTotal, modifiers } = Object.values(this.party.system.groupRoll.aidingCharacters).reduce(
|
||||
(acc, curr) => {
|
||||
const modifier = curr.successful === true ? 1 : curr.successful === false ? -1 : null;
|
||||
if (modifier) {
|
||||
acc.modifierTotal += modifier;
|
||||
acc.modifiers.push(modifier);
|
||||
}
|
||||
|
||||
return acc;
|
||||
},
|
||||
{ modifierTotal: 0, modifiers: [] }
|
||||
);
|
||||
const leaderTotal = leader?.rollData ? leader.roll.total : null;
|
||||
partContext.groupRoll = {
|
||||
totalLabel: leader?.rollData
|
||||
? game.i18n.format('DAGGERHEART.GENERAL.withThing', {
|
||||
thing: leader.roll.totalLabel
|
||||
})
|
||||
: null,
|
||||
totalDualityClass: leader?.roll?.isCritical ? 'critical' : leader?.roll?.withHope ? 'hope' : 'fear',
|
||||
total: leaderTotal + modifierTotal,
|
||||
leaderTotal: leaderTotal,
|
||||
modifiers
|
||||
};
|
||||
break;
|
||||
case 'footer':
|
||||
partContext.canFinishRoll =
|
||||
Boolean(this.party.system.groupRoll.leader?.rollData) &&
|
||||
Object.values(this.party.system.groupRoll.aidingCharacters).every(x => x.successful !== null);
|
||||
break;
|
||||
}
|
||||
|
||||
if (Object.keys(this.party.system.groupRoll.aidingCharacters).includes(partId)) {
|
||||
const characterData = this.party.system.groupRoll.aidingCharacters[partId];
|
||||
partContext.members[partId] = this.getRollCharacterData(characterData, partId);
|
||||
}
|
||||
|
||||
return partContext;
|
||||
}
|
||||
|
||||
getRollCharacterData(data, partId) {
|
||||
if (!data) return {};
|
||||
|
||||
const actor = game.actors.get(data.id);
|
||||
const isLeader = data === this.party.system.groupRoll.leader;
|
||||
|
||||
const roll = data.roll;
|
||||
const withTypeSuffix = !roll ? null : roll.isCritical ? 'criticalShort' : roll.withHope ? 'hope' : 'fear';
|
||||
const thing = withTypeSuffix ? _loc(`DAGGERHEART.GENERAL.${withTypeSuffix}`) : null;
|
||||
|
||||
return {
|
||||
...data,
|
||||
type: isLeader ? 'leader' : 'aid',
|
||||
basePath: isLeader ? 'system.groupRoll.leader' : `system.groupRoll.aidingCharacters.${data.id}`,
|
||||
rollChoiceLabel: _loc(CONFIG.DH.ACTOR.abilities[data.rollChoice]?.label),
|
||||
roll: data.roll,
|
||||
isEditable: actor?.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER),
|
||||
key: partId,
|
||||
readyToRoll: Boolean(data.rollChoice),
|
||||
hasRolled: Boolean(data.rollData),
|
||||
modifier: data.successful ? 1 : data.successful === false ? -1 : 0,
|
||||
withLabelShort: thing ? _loc('DAGGERHEART.GENERAL.withThing', { thing }) : null
|
||||
};
|
||||
}
|
||||
|
||||
#getCharacterDataById(id) {
|
||||
if (!id) return null;
|
||||
|
||||
const groupRoll = this.party.system.groupRoll;
|
||||
if (id === 'leader' || id === groupRoll.leader?.id) {
|
||||
return { data: groupRoll.leader, basePath: 'system.groupRoll.leader' };
|
||||
} else if (id in groupRoll.aidingCharacters) {
|
||||
return { data: groupRoll.aidingCharacters[id], basePath: `system.groupRoll.aidingCharacters.${id}` };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static async updateData(event, _, formData) {
|
||||
const partyData = foundry.utils.expandObject(formData.object);
|
||||
this.updatePartyData(partyData, this.getUpdatingParts(event.target));
|
||||
}
|
||||
|
||||
async updatePartyData(update, updatingParts, options = { render: true }) {
|
||||
if (!game.users.activeGM)
|
||||
return ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.gmRequired'));
|
||||
|
||||
const gmUpdate = async update => {
|
||||
await this.party.update(update);
|
||||
this.render({ parts: updatingParts });
|
||||
game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||
action: socketEvent.Refresh,
|
||||
data: { refreshType: RefreshType.GroupRoll, action: 'refresh', parts: updatingParts }
|
||||
});
|
||||
};
|
||||
|
||||
await emitGMUpdate(
|
||||
GMUpdateEvent.UpdateDocument,
|
||||
gmUpdate,
|
||||
update,
|
||||
this.party.uuid,
|
||||
options.render ? { refreshType: RefreshType.GroupRoll, action: 'refresh', parts: updatingParts } : undefined
|
||||
);
|
||||
}
|
||||
|
||||
getUpdatingParts(target) {
|
||||
const { initialization, leader, result, footer } = this.constructor.PARTS;
|
||||
const isInitialization = this.tabGroups.application === initialization.id;
|
||||
const updatingMember = target.closest('.member-roll-container.aid')?.dataset?.memberKey;
|
||||
const updatingLeader = target.closest('.member-roll-container.leader');
|
||||
|
||||
return [
|
||||
...(isInitialization ? [initialization.id] : []),
|
||||
...(updatingMember ? [updatingMember] : []),
|
||||
...(updatingLeader ? [leader.id] : []),
|
||||
...(!isInitialization ? [result.id, footer.id] : [])
|
||||
];
|
||||
}
|
||||
|
||||
groupRollRefresh = ({ refreshType, action, parts }) => {
|
||||
if (refreshType !== RefreshType.GroupRoll) return;
|
||||
|
||||
switch (action) {
|
||||
case 'startGroupRoll':
|
||||
this.tabGroups.application = 'groupRoll';
|
||||
break;
|
||||
case 'refresh':
|
||||
this.render({ parts });
|
||||
break;
|
||||
case 'close':
|
||||
this.close();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
async close(options = {}) {
|
||||
/* Opt out of Foundry's standard behavior of closing all application windows marked as UI when Escape is pressed */
|
||||
if (options.closeKey) return;
|
||||
|
||||
Hooks.off(socketEvent.Refresh, this.groupRollRefresh);
|
||||
return super.close(options);
|
||||
}
|
||||
|
||||
//#region Initialization
|
||||
static #toggleSelectMember(_, button) {
|
||||
const member = this.partyMembers.find(x => x.id === button.dataset.id);
|
||||
member.selected = !member.selected;
|
||||
if (this.leader?.memberId === member.id) {
|
||||
this.leader = null;
|
||||
}
|
||||
this.render();
|
||||
}
|
||||
|
||||
updateLeaderField(event) {
|
||||
if (!this.leader) this.leader = {};
|
||||
this.leader.memberId = event.target.value;
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async #startGroupRoll() {
|
||||
const leader = this.partyMembers.find(x => x.id === this.leader.memberId);
|
||||
const aidingCharacters = this.partyMembers.reduce((acc, curr) => {
|
||||
if (curr.selected && curr.id !== this.leader.memberId)
|
||||
acc[curr.id] = { id: curr.id, name: curr.name, img: curr.img };
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
await this.party.update({
|
||||
'system.groupRoll': _replace(
|
||||
new game.system.api.data.GroupRollData({
|
||||
...this.party.system.groupRoll.toObject(),
|
||||
leader: { id: leader.id, name: leader.name, img: leader.img },
|
||||
aidingCharacters
|
||||
})
|
||||
)
|
||||
});
|
||||
|
||||
const hookData = { openForAllPlayers: this.openForAllPlayers, partyId: this.party.id };
|
||||
Hooks.callAll(CONFIG.DH.HOOKS.hooksConfig.groupRollStart, hookData);
|
||||
game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||
action: socketEvent.GroupRollStart,
|
||||
data: hookData
|
||||
});
|
||||
|
||||
this.render();
|
||||
}
|
||||
//#endregion
|
||||
|
||||
/** @this GroupRollDialog */
|
||||
static async #makeRoll(_event, button) {
|
||||
const member = button.closest('[data-member-key]').dataset.memberKey;
|
||||
const { data, basePath } = this.#getCharacterDataById(member);
|
||||
const actor = game.actors.find(x => x.id === data.id);
|
||||
if (!actor) return;
|
||||
|
||||
const result = await actor.rollTrait(data.rollChoice, {
|
||||
skips: {
|
||||
createMessage: true,
|
||||
resources: true,
|
||||
triggers: true
|
||||
}
|
||||
});
|
||||
|
||||
if (!result) return;
|
||||
|
||||
const rollData = result.messageRoll.toJSON();
|
||||
delete rollData.options.messageRoll;
|
||||
this.updatePartyData(
|
||||
{
|
||||
[basePath]: { rollData, successful: null }
|
||||
},
|
||||
this.getUpdatingParts(button)
|
||||
);
|
||||
}
|
||||
|
||||
/** @this GroupRollDialog */
|
||||
static async #removeRoll(_event, button) {
|
||||
const member = button.closest('[data-member-key]').dataset.memberKey;
|
||||
const { basePath } = this.#getCharacterDataById(member);
|
||||
this.updatePartyData(
|
||||
{
|
||||
[basePath]: {
|
||||
rollData: null,
|
||||
rollChoice: null,
|
||||
selected: false,
|
||||
successful: null
|
||||
}
|
||||
},
|
||||
this.getUpdatingParts(button)
|
||||
);
|
||||
}
|
||||
|
||||
/** @this GroupRollDialog */
|
||||
static async #rerollDice(_, button) {
|
||||
const { diceType } = button.dataset;
|
||||
const { data, basePath } = this.#getCharacterDataById(button.dataset.member);
|
||||
|
||||
const dieIndex = diceType === 'hope' ? 0 : diceType === 'fear' ? 1 : 2;
|
||||
const newRoll = game.system.api.dice.DualityRoll.fromData(data.rollData);
|
||||
const dice = newRoll.dice[dieIndex];
|
||||
await dice.reroll(`/r1=${dice.total}`, {
|
||||
liveRoll: {
|
||||
roll: newRoll,
|
||||
isReaction: true
|
||||
}
|
||||
});
|
||||
const rollData = newRoll.toJSON();
|
||||
this.updatePartyData(
|
||||
{
|
||||
[`${basePath}.rollData`]: rollData
|
||||
},
|
||||
this.getUpdatingParts(button)
|
||||
);
|
||||
}
|
||||
|
||||
static #markSuccessful(_event, button) {
|
||||
const memberKey = button.closest('[data-member-key]').dataset.memberKey;
|
||||
const previousValue = this.party.system.groupRoll.aidingCharacters[memberKey].successful;
|
||||
const newValue = Boolean(button.dataset.success === 'true');
|
||||
this.updatePartyData(
|
||||
{
|
||||
[`system.groupRoll.aidingCharacters.${memberKey}.successful`]:
|
||||
previousValue === newValue ? null : newValue
|
||||
},
|
||||
this.getUpdatingParts(button)
|
||||
);
|
||||
}
|
||||
|
||||
static async #onCancelRoll(_event, _button, options = { confirm: true }) {
|
||||
this.cancelRoll(options);
|
||||
}
|
||||
|
||||
async cancelRoll(options = { confirm: true }) {
|
||||
if (options.confirm) {
|
||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||
window: {
|
||||
title: game.i18n.localize('DAGGERHEART.APPLICATIONS.GroupRollSelect.cancelConfirmTitle')
|
||||
},
|
||||
content: game.i18n.localize('DAGGERHEART.APPLICATIONS.GroupRollSelect.cancelConfirmText')
|
||||
});
|
||||
|
||||
if (!confirmed) return;
|
||||
}
|
||||
|
||||
await this.updatePartyData(
|
||||
{
|
||||
'system.groupRoll': {
|
||||
leader: null,
|
||||
aidingCharacters: _replace({})
|
||||
}
|
||||
},
|
||||
[],
|
||||
{ render: false }
|
||||
);
|
||||
|
||||
this.close();
|
||||
game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||
action: socketEvent.Refresh,
|
||||
data: { refreshType: RefreshType.GroupRoll, action: 'close' }
|
||||
});
|
||||
}
|
||||
|
||||
static async #finishRoll() {
|
||||
const totalRoll = this.party.system.groupRoll.leader.roll;
|
||||
for (const character of Object.values(this.party.system.groupRoll.aidingCharacters)) {
|
||||
totalRoll.terms.push(new foundry.dice.terms.OperatorTerm({ operator: character.successful ? '+' : '-' }));
|
||||
totalRoll.terms.push(new foundry.dice.terms.NumericTerm({ number: 1 }));
|
||||
}
|
||||
|
||||
await totalRoll._evaluate();
|
||||
|
||||
const systemData = totalRoll.options;
|
||||
const actor = game.actors.get(this.party.system.groupRoll.leader.id);
|
||||
|
||||
const cls = getDocumentClass('ChatMessage'),
|
||||
msgData = {
|
||||
type: 'dualityRoll',
|
||||
user: game.user.id,
|
||||
title: game.i18n.localize('DAGGERHEART.APPLICATIONS.GroupRollSelect.title'),
|
||||
speaker: cls.getSpeaker({ actor }),
|
||||
system: systemData,
|
||||
rolls: [JSON.stringify(totalRoll)],
|
||||
sound: null,
|
||||
flags: { core: { RollTable: true } }
|
||||
};
|
||||
|
||||
await cls.create(msgData);
|
||||
|
||||
const resourceMap = new ResourceUpdateMap(actor);
|
||||
if (totalRoll.isCritical) {
|
||||
resourceMap.addResources([
|
||||
{ key: 'stress', value: -1, total: 1 },
|
||||
{ key: 'hope', value: 1, total: 1 }
|
||||
]);
|
||||
} else if (totalRoll.withHope) {
|
||||
resourceMap.addResources([{ key: 'hope', value: 1, total: 1 }]);
|
||||
} else {
|
||||
resourceMap.addResources([{ key: 'fear', value: 1, total: 1 }]);
|
||||
}
|
||||
|
||||
resourceMap.updateResources();
|
||||
|
||||
/* Fin */
|
||||
this.cancelRoll({ confirm: false });
|
||||
}
|
||||
}
|
||||
|
|
@ -38,15 +38,13 @@ export default class ItemTransferDialog extends HandlebarsApplicationMixin(Appli
|
|||
originActor ??= item?.actor;
|
||||
const homebrewKey = CONFIG.DH.SETTINGS.gameSettings.Homebrew;
|
||||
const currencySetting = game.settings.get(CONFIG.DH.id, homebrewKey).currency?.[currency] ?? null;
|
||||
const max = item?.system.quantity ?? originActor.system.gold[currency] ?? 0;
|
||||
|
||||
return {
|
||||
originActor,
|
||||
targetActor,
|
||||
itemImage: item?.img,
|
||||
currencyIcon: currencySetting?.icon,
|
||||
max,
|
||||
initial: targetActor.system.metadata.quantifiable?.includes(item.type) ? max : 1,
|
||||
max: item?.system.quantity ?? originActor.system.gold[currency] ?? 0,
|
||||
title: item?.name ?? currencySetting?.label
|
||||
};
|
||||
}
|
||||
|
|
|
|||
290
module/applications/dialogs/rerollDamageDialog.mjs
Normal file
290
module/applications/dialogs/rerollDamageDialog.mjs
Normal file
|
|
@ -0,0 +1,290 @@
|
|||
import { RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||
|
||||
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||
|
||||
export default class RerollDamageDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
constructor(message, options = {}) {
|
||||
super(options);
|
||||
|
||||
this.message = message;
|
||||
this.damage = Object.keys(message.system.damage).reduce((acc, typeKey) => {
|
||||
const type = message.system.damage[typeKey];
|
||||
acc[typeKey] = Object.keys(type.parts).reduce((acc, partKey) => {
|
||||
const part = type.parts[partKey];
|
||||
acc[partKey] = Object.keys(part.dice).reduce((acc, diceKey) => {
|
||||
const dice = part.dice[diceKey];
|
||||
const activeResults = dice.results.filter(x => x.active);
|
||||
acc[diceKey] = {
|
||||
dice: dice.dice,
|
||||
selectedResults: activeResults.length,
|
||||
maxSelected: activeResults.length,
|
||||
results: activeResults.map(x => ({ ...x, selected: true }))
|
||||
};
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
id: 'reroll-dialog',
|
||||
classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'reroll-dialog'],
|
||||
window: {
|
||||
icon: 'fa-solid fa-dice'
|
||||
},
|
||||
actions: {
|
||||
toggleResult: RerollDamageDialog.#toggleResult,
|
||||
selectRoll: RerollDamageDialog.#selectRoll,
|
||||
doReroll: RerollDamageDialog.#doReroll,
|
||||
save: RerollDamageDialog.#save
|
||||
}
|
||||
};
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
main: {
|
||||
id: 'main',
|
||||
template: 'systems/daggerheart/templates/dialogs/rerollDialog/damage/main.hbs'
|
||||
},
|
||||
footer: {
|
||||
id: 'footer',
|
||||
template: 'systems/daggerheart/templates/dialogs/rerollDialog/footer.hbs'
|
||||
}
|
||||
};
|
||||
|
||||
get title() {
|
||||
return game.i18n.localize('DAGGERHEART.APPLICATIONS.RerollDialog.damageTitle');
|
||||
}
|
||||
|
||||
_attachPartListeners(partId, htmlElement, options) {
|
||||
super._attachPartListeners(partId, htmlElement, options);
|
||||
|
||||
htmlElement.querySelectorAll('.to-reroll-input').forEach(element => {
|
||||
element.addEventListener('change', this.toggleDice.bind(this));
|
||||
});
|
||||
}
|
||||
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
context.damage = this.damage;
|
||||
context.disabledReroll = !this.getRerollDice().length;
|
||||
context.saveDisabled = !this.isSelectionDone();
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
static async #save() {
|
||||
const update = {
|
||||
'system.damage': Object.keys(this.damage).reduce((acc, typeKey) => {
|
||||
const type = this.damage[typeKey];
|
||||
let typeTotal = 0;
|
||||
const messageType = this.message.system.damage[typeKey];
|
||||
const parts = Object.keys(type).map(partKey => {
|
||||
const part = type[partKey];
|
||||
const messagePart = messageType.parts[partKey];
|
||||
let partTotal = messagePart.modifierTotal;
|
||||
const dice = Object.keys(part).map(diceKey => {
|
||||
const dice = part[diceKey];
|
||||
const total = dice.results.reduce((acc, result) => {
|
||||
if (result.active) acc += result.result;
|
||||
return acc;
|
||||
}, 0);
|
||||
partTotal += total;
|
||||
const messageDice = messagePart.dice[diceKey];
|
||||
return {
|
||||
...messageDice,
|
||||
total: total,
|
||||
results: dice.results.map(x => ({
|
||||
...x,
|
||||
hasRerolls: dice.results.length > 1
|
||||
}))
|
||||
};
|
||||
});
|
||||
|
||||
typeTotal += partTotal;
|
||||
return {
|
||||
...messagePart,
|
||||
total: partTotal,
|
||||
dice: dice
|
||||
};
|
||||
});
|
||||
|
||||
acc[typeKey] = {
|
||||
...messageType,
|
||||
total: typeTotal,
|
||||
parts: parts
|
||||
};
|
||||
|
||||
return acc;
|
||||
}, {})
|
||||
};
|
||||
await this.message.update(update);
|
||||
|
||||
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.TagTeamRoll });
|
||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||
action: socketEvent.Refresh,
|
||||
data: {
|
||||
refreshType: RefreshType.TagTeamRoll
|
||||
}
|
||||
});
|
||||
|
||||
await this.close();
|
||||
}
|
||||
|
||||
getRerollDice() {
|
||||
const rerollDice = [];
|
||||
Object.keys(this.damage).forEach(typeKey => {
|
||||
const type = this.damage[typeKey];
|
||||
Object.keys(type).forEach(partKey => {
|
||||
const part = type[partKey];
|
||||
Object.keys(part).forEach(diceKey => {
|
||||
const dice = part[diceKey];
|
||||
Object.keys(dice.results).forEach(resultKey => {
|
||||
const result = dice.results[resultKey];
|
||||
if (result.toReroll) {
|
||||
rerollDice.push({
|
||||
...result,
|
||||
dice: dice.dice,
|
||||
type: typeKey,
|
||||
part: partKey,
|
||||
dice: diceKey,
|
||||
result: resultKey
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return rerollDice;
|
||||
}
|
||||
|
||||
isSelectionDone() {
|
||||
const diceFinishedData = [];
|
||||
Object.keys(this.damage).forEach(typeKey => {
|
||||
const type = this.damage[typeKey];
|
||||
Object.keys(type).forEach(partKey => {
|
||||
const part = type[partKey];
|
||||
Object.keys(part).forEach(diceKey => {
|
||||
const dice = part[diceKey];
|
||||
const selected = dice.results.reduce((acc, result) => acc + (result.active ? 1 : 0), 0);
|
||||
diceFinishedData.push(selected === dice.maxSelected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return diceFinishedData.every(x => x);
|
||||
}
|
||||
|
||||
toggleDice(event) {
|
||||
const target = event.target;
|
||||
const { type, part, dice } = target.dataset;
|
||||
const toggleDice = this.damage[type][part][dice];
|
||||
|
||||
const existingDiceRerolls = this.getRerollDice().filter(
|
||||
x => x.type === type && x.part === part && x.dice === dice
|
||||
);
|
||||
|
||||
const allRerolled = existingDiceRerolls.length === toggleDice.results.filter(x => x.active).length;
|
||||
|
||||
toggleDice.toReroll = !allRerolled;
|
||||
toggleDice.results.forEach(result => {
|
||||
if (result.active) {
|
||||
result.toReroll = !allRerolled;
|
||||
}
|
||||
});
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
static #toggleResult(event) {
|
||||
event.stopPropagation();
|
||||
|
||||
const target = event.target.closest('.to-reroll-result');
|
||||
const { type, part, dice, result } = target.dataset;
|
||||
const toggleDice = this.damage[type][part][dice];
|
||||
const toggleResult = toggleDice.results[result];
|
||||
toggleResult.toReroll = !toggleResult.toReroll;
|
||||
|
||||
const existingDiceRerolls = this.getRerollDice().filter(
|
||||
x => x.type === type && x.part === part && x.dice === dice
|
||||
);
|
||||
|
||||
const allToReroll = existingDiceRerolls.length === toggleDice.results.filter(x => x.active).length;
|
||||
toggleDice.toReroll = allToReroll;
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async #selectRoll(_, button) {
|
||||
const { type, part, dice, result } = button.dataset;
|
||||
|
||||
const diceVal = this.damage[type][part][dice];
|
||||
const diceResult = diceVal.results[result];
|
||||
if (!diceResult.active && diceVal.results.filter(x => x.active).length === diceVal.maxSelected) {
|
||||
return ui.notifications.warn(
|
||||
game.i18n.localize('DAGGERHEART.APPLICATIONS.RerollDialog.deselectDiceNotification')
|
||||
);
|
||||
}
|
||||
|
||||
if (diceResult.active) {
|
||||
diceVal.toReroll = false;
|
||||
diceResult.toReroll = false;
|
||||
}
|
||||
|
||||
diceVal.selectedResults += diceResult.active ? -1 : 1;
|
||||
diceResult.active = !diceResult.active;
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async #doReroll() {
|
||||
const toReroll = this.getRerollDice().map(x => {
|
||||
const { type, part, dice, result } = x;
|
||||
const diceData = this.damage[type][part][dice].results[result];
|
||||
return {
|
||||
...diceData,
|
||||
dice: this.damage[type][part][dice].dice,
|
||||
typeKey: type,
|
||||
partKey: part,
|
||||
diceKey: dice,
|
||||
resultsIndex: result
|
||||
};
|
||||
});
|
||||
|
||||
const roll = await new Roll(toReroll.map(x => `1${x.dice}`).join(' + ')).evaluate();
|
||||
|
||||
if (game.modules.get('dice-so-nice')?.active) {
|
||||
const diceSoNiceRoll = {
|
||||
_evaluated: true,
|
||||
dice: roll.dice,
|
||||
options: { appearance: {} }
|
||||
};
|
||||
|
||||
await game.dice3d.showForRoll(diceSoNiceRoll, game.user, true);
|
||||
}
|
||||
|
||||
toReroll.forEach((data, index) => {
|
||||
const { typeKey, partKey, diceKey, resultsIndex } = data;
|
||||
const rerolledDice = roll.dice[index];
|
||||
|
||||
const dice = this.damage[typeKey][partKey][diceKey];
|
||||
dice.toReroll = false;
|
||||
dice.results[resultsIndex].active = false;
|
||||
dice.results[resultsIndex].discarded = true;
|
||||
dice.results[resultsIndex].toReroll = false;
|
||||
dice.results.splice(dice.results.length, 0, {
|
||||
...rerolledDice.results[0],
|
||||
toReroll: false,
|
||||
selected: true
|
||||
});
|
||||
});
|
||||
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
279
module/applications/dialogs/rerollDialog.mjs
Normal file
279
module/applications/dialogs/rerollDialog.mjs
Normal file
|
|
@ -0,0 +1,279 @@
|
|||
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||
|
||||
export default class RerollDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
constructor(message, options = {}) {
|
||||
super(options);
|
||||
|
||||
this.message = message;
|
||||
this.damage = Object.keys(message.system.damage).reduce((acc, typeKey) => {
|
||||
const type = message.system.damage[typeKey];
|
||||
acc[typeKey] = Object.keys(type.parts).reduce((acc, partKey) => {
|
||||
const part = type.parts[partKey];
|
||||
acc[partKey] = Object.keys(part.dice).reduce((acc, diceKey) => {
|
||||
const dice = part.dice[diceKey];
|
||||
const activeResults = dice.results.filter(x => x.active);
|
||||
acc[diceKey] = {
|
||||
dice: dice.dice,
|
||||
selectedResults: activeResults.length,
|
||||
maxSelected: activeResults.length,
|
||||
results: activeResults.map(x => ({ ...x, selected: true }))
|
||||
};
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
id: 'reroll-dialog',
|
||||
classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'reroll-dialog'],
|
||||
window: {
|
||||
icon: 'fa-solid fa-dice'
|
||||
},
|
||||
actions: {
|
||||
toggleResult: RerollDialog.#toggleResult,
|
||||
selectRoll: RerollDialog.#selectRoll,
|
||||
doReroll: RerollDialog.#doReroll,
|
||||
save: RerollDialog.#save
|
||||
}
|
||||
};
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
main: {
|
||||
id: 'main',
|
||||
template: 'systems/daggerheart/templates/dialogs/rerollDialog/main.hbs'
|
||||
},
|
||||
footer: {
|
||||
id: 'footer',
|
||||
template: 'systems/daggerheart/templates/dialogs/rerollDialog/footer.hbs'
|
||||
}
|
||||
};
|
||||
|
||||
get title() {
|
||||
return game.i18n.localize('DAGGERHEART.APPLICATIONS.RerollDialog.title');
|
||||
}
|
||||
|
||||
_attachPartListeners(partId, htmlElement, options) {
|
||||
super._attachPartListeners(partId, htmlElement, options);
|
||||
|
||||
htmlElement.querySelectorAll('.to-reroll-input').forEach(element => {
|
||||
element.addEventListener('change', this.toggleDice.bind(this));
|
||||
});
|
||||
}
|
||||
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
context.damage = this.damage;
|
||||
context.disabledReroll = !this.getRerollDice().length;
|
||||
context.saveDisabled = !this.isSelectionDone();
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
static async #save() {
|
||||
const update = {
|
||||
'system.damage': Object.keys(this.damage).reduce((acc, typeKey) => {
|
||||
const type = this.damage[typeKey];
|
||||
let typeTotal = 0;
|
||||
const messageType = this.message.system.damage[typeKey];
|
||||
const parts = Object.keys(type).map(partKey => {
|
||||
const part = type[partKey];
|
||||
const messagePart = messageType.parts[partKey];
|
||||
let partTotal = messagePart.modifierTotal;
|
||||
const dice = Object.keys(part).map(diceKey => {
|
||||
const dice = part[diceKey];
|
||||
const total = dice.results.reduce((acc, result) => {
|
||||
if (result.active) acc += result.result;
|
||||
return acc;
|
||||
}, 0);
|
||||
partTotal += total;
|
||||
const messageDice = messagePart.dice[diceKey];
|
||||
return {
|
||||
...messageDice,
|
||||
total: total,
|
||||
results: dice.results.map(x => ({
|
||||
...x,
|
||||
hasRerolls: dice.results.length > 1
|
||||
}))
|
||||
};
|
||||
});
|
||||
|
||||
typeTotal += partTotal;
|
||||
return {
|
||||
...messagePart,
|
||||
total: partTotal,
|
||||
dice: dice
|
||||
};
|
||||
});
|
||||
|
||||
acc[typeKey] = {
|
||||
...messageType,
|
||||
total: typeTotal,
|
||||
parts: parts
|
||||
};
|
||||
|
||||
return acc;
|
||||
}, {})
|
||||
};
|
||||
await this.message.update(update);
|
||||
await this.close();
|
||||
}
|
||||
|
||||
getRerollDice() {
|
||||
const rerollDice = [];
|
||||
Object.keys(this.damage).forEach(typeKey => {
|
||||
const type = this.damage[typeKey];
|
||||
Object.keys(type).forEach(partKey => {
|
||||
const part = type[partKey];
|
||||
Object.keys(part).forEach(diceKey => {
|
||||
const dice = part[diceKey];
|
||||
Object.keys(dice.results).forEach(resultKey => {
|
||||
const result = dice.results[resultKey];
|
||||
if (result.toReroll) {
|
||||
rerollDice.push({
|
||||
...result,
|
||||
dice: dice.dice,
|
||||
type: typeKey,
|
||||
part: partKey,
|
||||
dice: diceKey,
|
||||
result: resultKey
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return rerollDice;
|
||||
}
|
||||
|
||||
isSelectionDone() {
|
||||
const diceFinishedData = [];
|
||||
Object.keys(this.damage).forEach(typeKey => {
|
||||
const type = this.damage[typeKey];
|
||||
Object.keys(type).forEach(partKey => {
|
||||
const part = type[partKey];
|
||||
Object.keys(part).forEach(diceKey => {
|
||||
const dice = part[diceKey];
|
||||
const selected = dice.results.reduce((acc, result) => acc + (result.active ? 1 : 0), 0);
|
||||
diceFinishedData.push(selected === dice.maxSelected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return diceFinishedData.every(x => x);
|
||||
}
|
||||
|
||||
toggleDice(event) {
|
||||
const target = event.target;
|
||||
const { type, part, dice } = target.dataset;
|
||||
const toggleDice = this.damage[type][part][dice];
|
||||
|
||||
const existingDiceRerolls = this.getRerollDice().filter(
|
||||
x => x.type === type && x.part === part && x.dice === dice
|
||||
);
|
||||
|
||||
const allRerolled = existingDiceRerolls.length === toggleDice.results.filter(x => x.active).length;
|
||||
|
||||
toggleDice.toReroll = !allRerolled;
|
||||
toggleDice.results.forEach(result => {
|
||||
if (result.active) {
|
||||
result.toReroll = !allRerolled;
|
||||
}
|
||||
});
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
static #toggleResult(event) {
|
||||
event.stopPropagation();
|
||||
|
||||
const target = event.target.closest('.to-reroll-result');
|
||||
const { type, part, dice, result } = target.dataset;
|
||||
const toggleDice = this.damage[type][part][dice];
|
||||
const toggleResult = toggleDice.results[result];
|
||||
toggleResult.toReroll = !toggleResult.toReroll;
|
||||
|
||||
const existingDiceRerolls = this.getRerollDice().filter(
|
||||
x => x.type === type && x.part === part && x.dice === dice
|
||||
);
|
||||
|
||||
const allToReroll = existingDiceRerolls.length === toggleDice.results.length;
|
||||
toggleDice.toReroll = allToReroll;
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async #selectRoll(_, button) {
|
||||
const { type, part, dice, result } = button.dataset;
|
||||
|
||||
const diceVal = this.damage[type][part][dice];
|
||||
const diceResult = diceVal.results[result];
|
||||
if (!diceResult.active && diceVal.results.filter(x => x.active).length === diceVal.maxSelected) {
|
||||
return ui.notifications.warn(
|
||||
game.i18n.localize('DAGGERHEART.APPLICATIONS.RerollDialog.deselectDiceNotification')
|
||||
);
|
||||
}
|
||||
|
||||
if (diceResult.active) {
|
||||
diceVal.toReroll = false;
|
||||
diceResult.toReroll = false;
|
||||
}
|
||||
|
||||
diceVal.selectedResults += diceResult.active ? -1 : 1;
|
||||
diceResult.active = !diceResult.active;
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async #doReroll() {
|
||||
const toReroll = this.getRerollDice().map(x => {
|
||||
const { type, part, dice, result } = x;
|
||||
const diceData = this.damage[type][part][dice].results[result];
|
||||
return {
|
||||
...diceData,
|
||||
dice: this.damage[type][part][dice].dice,
|
||||
typeKey: type,
|
||||
partKey: part,
|
||||
diceKey: dice,
|
||||
resultsIndex: result
|
||||
};
|
||||
});
|
||||
|
||||
const roll = await new Roll(toReroll.map(x => `1${x.dice}`).join(' + ')).evaluate();
|
||||
|
||||
if (game.modules.get('dice-so-nice')?.active) {
|
||||
const diceSoNiceRoll = {
|
||||
_evaluated: true,
|
||||
dice: roll.dice,
|
||||
options: { appearance: {} }
|
||||
};
|
||||
|
||||
await game.dice3d.showForRoll(diceSoNiceRoll, game.user, true);
|
||||
}
|
||||
|
||||
toReroll.forEach((data, index) => {
|
||||
const { typeKey, partKey, diceKey, resultsIndex } = data;
|
||||
const rerolledDice = roll.dice[index];
|
||||
|
||||
const dice = this.damage[typeKey][partKey][diceKey];
|
||||
dice.toReroll = false;
|
||||
dice.results[resultsIndex].active = false;
|
||||
dice.results[resultsIndex].discarded = true;
|
||||
dice.results[resultsIndex].toReroll = false;
|
||||
dice.results.splice(dice.results.length, 0, {
|
||||
...rerolledDice.results[0],
|
||||
toReroll: false,
|
||||
selected: true
|
||||
});
|
||||
});
|
||||
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { itemAbleRollParse, triggerChatRollFx } from '../../helpers/utils.mjs';
|
||||
import { itemAbleRollParse } from '../../helpers/utils.mjs';
|
||||
|
||||
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||
|
||||
|
|
@ -69,7 +69,7 @@ export default class ResourceDiceDialog extends HandlebarsApplicationMixin(Appli
|
|||
const max = itemAbleRollParse(this.item.system.resource.max, this.actor, this.item);
|
||||
const diceFormula = `${max}${this.item.system.resource.dieFaces}`;
|
||||
const roll = await new Roll(diceFormula).evaluate();
|
||||
await triggerChatRollFx([roll]);
|
||||
if (game.modules.get('dice-so-nice')?.active) await game.dice3d.showForRoll(roll, game.user, true);
|
||||
this.rollValues = roll.terms[0].results.map(x => ({ value: x.result, used: false }));
|
||||
this.resetUsed = true;
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -50,11 +50,11 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
|||
).showGenericStatusEffects;
|
||||
context.genericStatusEffects = useGeneric
|
||||
? Object.keys(context.statusEffects).reduce((acc, key) => {
|
||||
const effect = context.statusEffects[key];
|
||||
if (!effect.systemEffect) acc[key] = effect;
|
||||
const effect = context.statusEffects[key];
|
||||
if (!effect.systemEffect) acc[key] = effect;
|
||||
|
||||
return acc;
|
||||
}, {})
|
||||
return acc;
|
||||
}, {})
|
||||
: null;
|
||||
|
||||
context.hasCompanion = this.actor.system.companion;
|
||||
|
|
@ -68,11 +68,11 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
|||
const warning =
|
||||
tokensWithoutActors.length === 1
|
||||
? game.i18n.format('DAGGERHEART.UI.Notifications.tokenActorMissing', {
|
||||
name: tokensWithoutActors[0].name
|
||||
})
|
||||
name: tokensWithoutActors[0].name
|
||||
})
|
||||
: game.i18n.format('DAGGERHEART.UI.Notifications.tokenActorsMissing', {
|
||||
names: tokensWithoutActors.map(x => x.name).join(', ')
|
||||
});
|
||||
names: tokensWithoutActors.map(x => x.name).join(', ')
|
||||
});
|
||||
|
||||
const tokens = canvas.tokens.controlled
|
||||
.filter(t => t.actor && !DHTokenHUD.#nonCombatTypes.includes(t.actor.type))
|
||||
|
|
@ -122,16 +122,15 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
|||
|
||||
async toggleClowncar(actors) {
|
||||
const animationDuration = 500;
|
||||
const scene = game.scenes.get(game.user.viewedScene);
|
||||
/* getDependentTokens returns already removed tokens with id = null. Need to filter that until it's potentially fixed from Foundry */
|
||||
const activeTokens = actors.flatMap(member =>
|
||||
member.getDependentTokens({ scenes: scene }).filter(x => x._id && !x._destroyed)
|
||||
);
|
||||
const activeTokens = actors.flatMap(member => member.getActiveTokens());
|
||||
const { x: actorX, y: actorY } = this.document;
|
||||
if (activeTokens.length > 0) {
|
||||
for (let token of activeTokens) {
|
||||
await token.update({ x: actorX, y: actorY, alpha: 0 }, { animation: { duration: animationDuration } });
|
||||
setTimeout(() => token.delete(), animationDuration);
|
||||
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);
|
||||
|
|
@ -141,16 +140,11 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
|||
tokenData.push(data.toObject());
|
||||
}
|
||||
|
||||
const viewedLevel = game.scenes.get(game.user.viewedScene).levels.get(game.user.viewedLevel);
|
||||
const elevation = this.actor.token?.elevation ?? viewedLevel.elevation.bottom;
|
||||
|
||||
const newTokens = await activeScene.createEmbeddedDocuments(
|
||||
'Token',
|
||||
tokenData.map(tokenData => ({
|
||||
...tokenData,
|
||||
alpha: 0,
|
||||
level: viewedLevel,
|
||||
elevation: elevation,
|
||||
x: actorX,
|
||||
y: actorY
|
||||
}))
|
||||
|
|
@ -174,8 +168,8 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
|||
nonZeroIndex === sideMiddle
|
||||
? 0
|
||||
: nonZeroIndex < sideMiddle
|
||||
? -nonZeroIndex
|
||||
: nonZeroIndex - sideMiddle;
|
||||
? -nonZeroIndex
|
||||
: nonZeroIndex - sideMiddle;
|
||||
return { x: actorX - sizeX * distance, y: actorY - sizeY * distanceCoefficient };
|
||||
} else if (index < side + inbetween) {
|
||||
const inbetweenIndex = nonZeroIndex - side;
|
||||
|
|
@ -183,8 +177,8 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
|||
inbetweenIndex === inbetweenMiddle
|
||||
? 0
|
||||
: inbetweenIndex < inbetweenMiddle
|
||||
? -inbetweenIndex
|
||||
: 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;
|
||||
|
|
@ -192,8 +186,8 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
|||
sideIndex === sideMiddle
|
||||
? 0
|
||||
: sideIndex < sideMiddle
|
||||
? sideIndex
|
||||
: -(sideIndex - sideMiddle);
|
||||
? sideIndex
|
||||
: -(sideIndex - sideMiddle);
|
||||
return { x: actorX + sizeX * distance, y: actorY + sizeY * distanceCoefficient };
|
||||
} else {
|
||||
const inbetweenIndex = nonZeroIndex - 2 * side - inbetween;
|
||||
|
|
@ -201,8 +195,8 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
|||
inbetweenIndex === inbetweenMiddle
|
||||
? 0
|
||||
: inbetweenIndex < inbetweenMiddle
|
||||
? inbetweenIndex
|
||||
: -(inbetweenIndex - inbetweenMiddle);
|
||||
? inbetweenIndex
|
||||
: -(inbetweenIndex - inbetweenMiddle);
|
||||
return { x: actorX - sizeX * distanceCoefficient, y: actorY + sizeY * distance };
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -156,7 +156,6 @@ export default class DhCharacterLevelUp extends LevelUpBase {
|
|||
if (multiclasses?.[0]) {
|
||||
const data = multiclasses[0];
|
||||
const multiclass = data.data.length > 0 ? await foundry.utils.fromUuid(data.data[0]) : {};
|
||||
const subclasses = (await multiclass?.system?.fetchSubclasses()) ?? [];
|
||||
|
||||
context.multiclass = {
|
||||
...data,
|
||||
|
|
@ -176,12 +175,13 @@ export default class DhCharacterLevelUp extends LevelUpBase {
|
|||
alreadySelected
|
||||
};
|
||||
}) ?? [],
|
||||
subclasses: subclasses.map(subclass => ({
|
||||
...subclass,
|
||||
uuid: subclass.uuid,
|
||||
selected: data.secondaryData.subclass === subclass.uuid,
|
||||
disabled: data.secondaryData.subclass && data.secondaryData.subclass !== subclass.uuid
|
||||
})),
|
||||
subclasses:
|
||||
multiclass?.system?.subclasses.map(subclass => ({
|
||||
...subclass,
|
||||
uuid: subclass.uuid,
|
||||
selected: data.secondaryData.subclass === subclass.uuid,
|
||||
disabled: data.secondaryData.subclass && data.secondaryData.subclass !== subclass.uuid
|
||||
})) ?? [],
|
||||
compendium: 'classes',
|
||||
limit: 1
|
||||
};
|
||||
|
|
@ -210,9 +210,9 @@ export default class DhCharacterLevelUp extends LevelUpBase {
|
|||
|
||||
achievementExperiences = level.achievements.experiences
|
||||
? Object.values(level.achievements.experiences).reduce((acc, experience) => {
|
||||
if (experience.name) acc.push(experience);
|
||||
return acc;
|
||||
}, [])
|
||||
if (experience.name) acc.push(experience);
|
||||
return acc;
|
||||
}, [])
|
||||
: [];
|
||||
}
|
||||
|
||||
|
|
@ -315,15 +315,15 @@ export default class DhCharacterLevelUp extends LevelUpBase {
|
|||
: null;
|
||||
advancement[choiceKey] = multiclassItem
|
||||
? {
|
||||
...multiclassItem.toObject(),
|
||||
domain: checkbox.secondaryData.domain
|
||||
? game.i18n.localize(
|
||||
CONFIG.DH.DOMAIN.allDomains()[checkbox.secondaryData.domain]
|
||||
.label
|
||||
)
|
||||
: null,
|
||||
subclass: subclass ? subclass.name : null
|
||||
}
|
||||
...multiclassItem.toObject(),
|
||||
domain: checkbox.secondaryData.domain
|
||||
? game.i18n.localize(
|
||||
CONFIG.DH.DOMAIN.allDomains()[checkbox.secondaryData.domain]
|
||||
.label
|
||||
)
|
||||
: null,
|
||||
subclass: subclass ? subclass.name : null
|
||||
}
|
||||
: {};
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ export default class DhCompanionLevelUp extends BaseLevelUp {
|
|||
break;
|
||||
case 'summary':
|
||||
const levelKeys = Object.keys(this.levelup.levels);
|
||||
const actorDamageDice = this.actor.system.attack.damage.parts.hitPoints.value.dice;
|
||||
const actorDamageDice = this.actor.system.attack.damage.parts[0].value.dice;
|
||||
const actorRange = this.actor.system.attack.range;
|
||||
|
||||
let achievementExperiences = [];
|
||||
|
|
@ -77,9 +77,9 @@ export default class DhCompanionLevelUp extends BaseLevelUp {
|
|||
|
||||
achievementExperiences = level.achievements.experiences
|
||||
? Object.values(level.achievements.experiences).reduce((acc, experience) => {
|
||||
if (experience.name) acc.push(experience);
|
||||
return acc;
|
||||
}, [])
|
||||
if (experience.name) acc.push(experience);
|
||||
return acc;
|
||||
}, [])
|
||||
: [];
|
||||
}
|
||||
context.achievements = {
|
||||
|
|
@ -155,15 +155,15 @@ export default class DhCompanionLevelUp extends BaseLevelUp {
|
|||
vicious: {
|
||||
damage: advancement.vicious?.damage
|
||||
? {
|
||||
old: actorDamageDice,
|
||||
new: advancement.vicious.damage
|
||||
}
|
||||
old: actorDamageDice,
|
||||
new: advancement.vicious.damage
|
||||
}
|
||||
: null,
|
||||
range: advancement.vicious?.range
|
||||
? {
|
||||
old: game.i18n.localize(`DAGGERHEART.CONFIG.Range.${actorRange}.name`),
|
||||
new: game.i18n.localize(advancement.vicious.range.label)
|
||||
}
|
||||
old: game.i18n.localize(`DAGGERHEART.CONFIG.Range.${actorRange}.name`),
|
||||
new: game.i18n.localize(advancement.vicious.range.label)
|
||||
}
|
||||
: null
|
||||
},
|
||||
simple: advancement.simple ?? {}
|
||||
|
|
|
|||
|
|
@ -135,6 +135,192 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
|||
context.tabs.advancements.progress = { selected: selections, max: currentLevel.maxSelections };
|
||||
context.showTabs = this.tabGroups.primary !== 'summary';
|
||||
break;
|
||||
|
||||
const actorArmor = this.actor.system.armor;
|
||||
const levelKeys = Object.keys(this.levelup.levels);
|
||||
let achivementProficiency = 0;
|
||||
const achievementCards = [];
|
||||
let achievementExperiences = [];
|
||||
for (var levelKey of levelKeys) {
|
||||
const level = this.levelup.levels[levelKey];
|
||||
if (Number(levelKey) < this.levelup.startLevel) continue;
|
||||
|
||||
achivementProficiency += level.achievements.proficiency ?? 0;
|
||||
const cards = level.achievements.domainCards ? Object.values(level.achievements.domainCards) : null;
|
||||
if (cards) {
|
||||
for (var card of cards) {
|
||||
const itemCard = await foundry.utils.fromUuid(card.uuid);
|
||||
achievementCards.push(itemCard);
|
||||
}
|
||||
}
|
||||
|
||||
achievementExperiences = level.achievements.experiences
|
||||
? Object.values(level.achievements.experiences).reduce((acc, experience) => {
|
||||
if (experience.name) acc.push(experience);
|
||||
return acc;
|
||||
}, [])
|
||||
: [];
|
||||
}
|
||||
|
||||
context.achievements = {
|
||||
proficiency: {
|
||||
old: this.actor.system.proficiency,
|
||||
new: this.actor.system.proficiency + achivementProficiency,
|
||||
shown: achivementProficiency > 0
|
||||
},
|
||||
damageThresholds: {
|
||||
major: {
|
||||
old: this.actor.system.damageThresholds.major,
|
||||
new: this.actor.system.damageThresholds.major + changedActorLevel - currentActorLevel
|
||||
},
|
||||
severe: {
|
||||
old: this.actor.system.damageThresholds.severe,
|
||||
new:
|
||||
this.actor.system.damageThresholds.severe +
|
||||
(actorArmor
|
||||
? changedActorLevel - currentActorLevel
|
||||
: (changedActorLevel - currentActorLevel) * 2)
|
||||
},
|
||||
unarmored: !actorArmor
|
||||
},
|
||||
domainCards: {
|
||||
values: achievementCards,
|
||||
shown: achievementCards.length > 0
|
||||
},
|
||||
experiences: {
|
||||
values: achievementExperiences
|
||||
}
|
||||
};
|
||||
|
||||
const advancement = {};
|
||||
for (var levelKey of levelKeys) {
|
||||
const level = this.levelup.levels[levelKey];
|
||||
if (Number(levelKey) < this.levelup.startLevel) continue;
|
||||
|
||||
for (var choiceKey of Object.keys(level.choices)) {
|
||||
const choice = level.choices[choiceKey];
|
||||
for (var checkbox of Object.values(choice)) {
|
||||
switch (choiceKey) {
|
||||
case 'proficiency':
|
||||
case 'hitPoint':
|
||||
case 'stress':
|
||||
case 'evasion':
|
||||
advancement[choiceKey] = advancement[choiceKey]
|
||||
? advancement[choiceKey] + Number(checkbox.value)
|
||||
: Number(checkbox.value);
|
||||
break;
|
||||
case 'trait':
|
||||
if (!advancement[choiceKey]) advancement[choiceKey] = {};
|
||||
for (var traitKey of checkbox.data) {
|
||||
if (!advancement[choiceKey][traitKey]) advancement[choiceKey][traitKey] = 0;
|
||||
advancement[choiceKey][traitKey] += 1;
|
||||
}
|
||||
break;
|
||||
case 'domainCard':
|
||||
if (!advancement[choiceKey]) advancement[choiceKey] = [];
|
||||
if (checkbox.data.length === 1) {
|
||||
const choiceItem = await foundry.utils.fromUuid(checkbox.data[0]);
|
||||
advancement[choiceKey].push(choiceItem.toObject());
|
||||
}
|
||||
break;
|
||||
case 'experience':
|
||||
if (!advancement[choiceKey]) advancement[choiceKey] = [];
|
||||
const data = checkbox.data.map(data => {
|
||||
const experience = Object.keys(this.actor.system.experiences).find(
|
||||
x => x === data
|
||||
);
|
||||
return this.actor.system.experiences[experience]?.description ?? '';
|
||||
});
|
||||
advancement[choiceKey].push({ data: data, value: checkbox.value });
|
||||
break;
|
||||
case 'subclass':
|
||||
if (checkbox.data[0]) {
|
||||
const subclassItem = await foundry.utils.fromUuid(checkbox.data[0]);
|
||||
if (!advancement[choiceKey]) advancement[choiceKey] = [];
|
||||
advancement[choiceKey].push({
|
||||
...subclassItem.toObject(),
|
||||
featureLabel: game.i18n.localize(
|
||||
subclassFeatureLabels[Number(checkbox.secondaryData.featureState)]
|
||||
)
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'multiclass':
|
||||
const multiclassItem = await foundry.utils.fromUuid(checkbox.data[0]);
|
||||
const subclass = multiclassItem
|
||||
? await foundry.utils.fromUuid(checkbox.secondaryData.subclass)
|
||||
: null;
|
||||
advancement[choiceKey] = multiclassItem
|
||||
? {
|
||||
...multiclassItem.toObject(),
|
||||
domain: checkbox.secondaryData.domain
|
||||
? game.i18n.localize(
|
||||
CONFIG.DH.DOMAIN.allDomains()[checkbox.secondaryData.domain]
|
||||
.label
|
||||
)
|
||||
: null,
|
||||
subclass: subclass ? subclass.name : null
|
||||
}
|
||||
: {};
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.advancements = {
|
||||
statistics: {
|
||||
proficiency: {
|
||||
old: context.achievements.proficiency.new,
|
||||
new: context.achievements.proficiency.new + (advancement.proficiency ?? 0)
|
||||
},
|
||||
hitPoints: {
|
||||
old: this.actor.system.resources.hitPoints.max,
|
||||
new: this.actor.system.resources.hitPoints.max + (advancement.hitPoint ?? 0)
|
||||
},
|
||||
stress: {
|
||||
old: this.actor.system.resources.stress.max,
|
||||
new: this.actor.system.resources.stress.max + (advancement.stress ?? 0)
|
||||
},
|
||||
evasion: {
|
||||
old: this.actor.system.evasion,
|
||||
new: this.actor.system.evasion + (advancement.evasion ?? 0)
|
||||
}
|
||||
},
|
||||
traits: Object.keys(this.actor.system.traits).reduce((acc, traitKey) => {
|
||||
if (advancement.trait?.[traitKey]) {
|
||||
if (!acc) acc = {};
|
||||
acc[traitKey] = {
|
||||
label: game.i18n.localize(abilities[traitKey].label),
|
||||
old: this.actor.system.traits[traitKey].value,
|
||||
new: this.actor.system.traits[traitKey].value + advancement.trait[traitKey]
|
||||
};
|
||||
}
|
||||
return acc;
|
||||
}, null),
|
||||
domainCards: advancement.domainCard ?? [],
|
||||
experiences:
|
||||
advancement.experience?.flatMap(x => x.data.map(data => ({ name: data, modifier: x.value }))) ??
|
||||
[],
|
||||
multiclass: advancement.multiclass,
|
||||
subclass: advancement.subclass
|
||||
};
|
||||
|
||||
context.advancements.statistics.proficiency.shown =
|
||||
context.advancements.statistics.proficiency.new > context.advancements.statistics.proficiency.old;
|
||||
context.advancements.statistics.hitPoints.shown =
|
||||
context.advancements.statistics.hitPoints.new > context.advancements.statistics.hitPoints.old;
|
||||
context.advancements.statistics.stress.shown =
|
||||
context.advancements.statistics.stress.new > context.advancements.statistics.stress.old;
|
||||
context.advancements.statistics.evasion.shown =
|
||||
context.advancements.statistics.evasion.new > context.advancements.statistics.evasion.old;
|
||||
context.advancements.statistics.shown =
|
||||
context.advancements.statistics.proficiency.shown ||
|
||||
context.advancements.statistics.hitPoints.shown ||
|
||||
context.advancements.statistics.stress.shown ||
|
||||
context.advancements.statistics.evasion.shown;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return context;
|
||||
|
|
@ -172,14 +358,14 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
|||
const experienceIncreaseTagify = htmlElement.querySelector('.levelup-experience-increases');
|
||||
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;
|
||||
}, {}),
|
||||
...this.actor.system.experiences
|
||||
}, {})
|
||||
};
|
||||
tagifyElement(
|
||||
experienceIncreaseTagify,
|
||||
|
|
@ -198,35 +384,37 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
|||
this._dragDrop.forEach(d => d.bind(htmlElement));
|
||||
}
|
||||
|
||||
tagifyUpdate = type => async (_, { option, removed }) => {
|
||||
const updatePath = Object.keys(this.levelup.levels[this.levelup.currentLevel].choices).reduce(
|
||||
(acc, choiceKey) => {
|
||||
const choice = this.levelup.levels[this.levelup.currentLevel].choices[choiceKey];
|
||||
Object.keys(choice).forEach(checkboxNr => {
|
||||
const checkbox = choice[checkboxNr];
|
||||
if (
|
||||
choiceKey === type &&
|
||||
(removed ? checkbox.data.includes(option) : checkbox.data.length < checkbox.amount)
|
||||
) {
|
||||
acc = `levels.${this.levelup.currentLevel}.choices.${choiceKey}.${checkboxNr}.data`;
|
||||
}
|
||||
});
|
||||
tagifyUpdate =
|
||||
type =>
|
||||
async (_, { option, removed }) => {
|
||||
const updatePath = Object.keys(this.levelup.levels[this.levelup.currentLevel].choices).reduce(
|
||||
(acc, choiceKey) => {
|
||||
const choice = this.levelup.levels[this.levelup.currentLevel].choices[choiceKey];
|
||||
Object.keys(choice).forEach(checkboxNr => {
|
||||
const checkbox = choice[checkboxNr];
|
||||
if (
|
||||
choiceKey === type &&
|
||||
(removed ? checkbox.data.includes(option) : checkbox.data.length < checkbox.amount)
|
||||
) {
|
||||
acc = `levels.${this.levelup.currentLevel}.choices.${choiceKey}.${checkboxNr}.data`;
|
||||
}
|
||||
});
|
||||
|
||||
return acc;
|
||||
},
|
||||
null
|
||||
);
|
||||
return acc;
|
||||
},
|
||||
null
|
||||
);
|
||||
|
||||
if (!updatePath) {
|
||||
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.noSelectionsLeft'));
|
||||
return;
|
||||
}
|
||||
if (!updatePath) {
|
||||
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.noSelectionsLeft'));
|
||||
return;
|
||||
}
|
||||
|
||||
const currentData = foundry.utils.getProperty(this.levelup, updatePath);
|
||||
const updatedData = removed ? currentData.filter(x => x !== option) : [...currentData, option];
|
||||
await this.levelup.updateSource({ [updatePath]: updatedData });
|
||||
this.render();
|
||||
};
|
||||
const currentData = foundry.utils.getProperty(this.levelup, updatePath);
|
||||
const updatedData = removed ? currentData.filter(x => x !== option) : [...currentData, option];
|
||||
await this.levelup.updateSource({ [updatePath]: updatedData });
|
||||
this.render();
|
||||
};
|
||||
|
||||
static async updateForm(event, _, formData) {
|
||||
const { levelup } = foundry.utils.expandObject(formData.object);
|
||||
|
|
@ -289,7 +477,7 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
|||
const secondaryData = Object.keys(
|
||||
foundry.utils.getProperty(this.levelup, `${target.dataset.path}.secondaryData`)
|
||||
).reduce((acc, key) => {
|
||||
acc[key] = _del;
|
||||
acc[`-=${key}`] = null;
|
||||
return acc;
|
||||
}, {});
|
||||
await this.levelup.updateSource({
|
||||
|
|
@ -323,9 +511,9 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
|||
const current = foundry.utils.getProperty(this.levelup, `${basePath}.${button.dataset.option}`);
|
||||
if (Number(button.dataset.cost) > 1 || Object.keys(current).length === 1) {
|
||||
// Simple handling that doesn't cover potential Custom LevelTiers.
|
||||
update[`${basePath}.${button.dataset.option}`] = _del;
|
||||
update[`${basePath}.-=${button.dataset.option}`] = null;
|
||||
} else {
|
||||
update[`${basePath}.${button.dataset.option}.${button.dataset.checkboxNr}`] = _del;
|
||||
update[`${basePath}.${button.dataset.option}.-=${button.dataset.checkboxNr}`] = null;
|
||||
}
|
||||
} else {
|
||||
if (this.levelup.levels[this.levelup.currentLevel].nrSelections.available < Number(button.dataset.cost)) {
|
||||
|
|
@ -405,10 +593,10 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
|||
const domainCards = this.levelup.levels[this.levelup.currentLevel].achievements.domainCards;
|
||||
const illegalDomainCards = option.secondaryData.domain
|
||||
? Object.keys(domainCards)
|
||||
.map(key => ({ ...domainCards[key], key }))
|
||||
.filter(
|
||||
x => x.uuid && foundry.utils.fromUuidSync(x.uuid).system.domain === option.secondaryData.domain
|
||||
)
|
||||
.map(key => ({ ...domainCards[key], key }))
|
||||
.filter(
|
||||
x => x.uuid && foundry.utils.fromUuidSync(x.uuid).system.domain === option.secondaryData.domain
|
||||
)
|
||||
: [];
|
||||
illegalDomainCards.forEach(card => {
|
||||
update[`levels.${this.levelup.currentLevel}.achievements.domainCards.${card.key}.uuid`] = null;
|
||||
|
|
|
|||
|
|
@ -62,15 +62,7 @@ export default class DhSceneConfigSettings extends foundry.applications.sheets.S
|
|||
}
|
||||
|
||||
async _onDrop(event) {
|
||||
event.stopPropagation();
|
||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||
if (data.type === 'Level') {
|
||||
const level = await foundry.documents.Level.fromDropData(data);
|
||||
if (level?.parent === this.document) return this._onSortLevel(event, level);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const item = await foundry.utils.fromUuid(data.uuid);
|
||||
if (item instanceof game.system.api.documents.DhpActor && item.type === 'environment') {
|
||||
let sceneUuid = data.uuid;
|
||||
|
|
@ -120,6 +112,12 @@ export default class DhSceneConfigSettings extends foundry.applications.sheets.S
|
|||
foundry.utils.fromUuidSync(x)
|
||||
);
|
||||
|
||||
for (const key of Object.keys(this.document._source.flags.daggerheart?.sceneEnvironments ?? {})) {
|
||||
if (!submitData.flags.daggerheart.sceneEnvironments[key]) {
|
||||
submitData.flags.daggerheart.sceneEnvironments[`-=${key}`] = null;
|
||||
}
|
||||
}
|
||||
|
||||
super._processSubmitData(event, form, submitData, options);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App
|
|||
type: 'button',
|
||||
action: 'reset',
|
||||
icon: 'fa-solid fa-arrow-rotate-left',
|
||||
label: game.i18n.localize('SETTINGS.UI.ACTIONS.Reset')
|
||||
label: game.i18n.localize('Reset')
|
||||
},
|
||||
{ type: 'submit', icon: 'fa-solid fa-floppy-disk', label: game.i18n.localize('EDITOR.Save') }
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { DhHomebrew } from '../../data/settings/_module.mjs';
|
||||
import { Resource } from '../../data/settings/Homebrew.mjs';
|
||||
import { slugify } from '../../helpers/utils.mjs';
|
||||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
||||
|
|
@ -111,7 +112,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
|
||||
switch (partId) {
|
||||
case 'domains':
|
||||
const selectedDomain = this.settings.domains[this.selected.domain] ?? null;
|
||||
const selectedDomain = this.selected.domain ? this.settings.domains[this.selected.domain] : null;
|
||||
const enrichedDescription = selectedDomain
|
||||
? await foundry.applications.ux.TextEditor.implementation.enrichHTML(selectedDomain.description)
|
||||
: null;
|
||||
|
|
@ -251,8 +252,8 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
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');
|
||||
? 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,
|
||||
|
|
@ -297,7 +298,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
const isDowntime = ['shortRest', 'longRest'].includes(type);
|
||||
const path = isDowntime ? `restMoves.${type}.moves` : `itemFeatures.${type}`;
|
||||
await this.settings.updateSource({
|
||||
[`${path}.${id}`]: _del
|
||||
[`${path}.-=${id}`]: null
|
||||
});
|
||||
|
||||
game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, this.settings.toObject());
|
||||
|
|
@ -321,7 +322,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
const fields = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).schema.fields;
|
||||
|
||||
const removeUpdate = Object.keys(this.settings.restMoves[target.dataset.type].moves).reduce((acc, key) => {
|
||||
acc[key] = _del;
|
||||
acc[`-=${key}`] = null;
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
|
@ -381,7 +382,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
[`itemFeatures.${target.dataset.type}`]: Object.keys(
|
||||
this.settings.itemFeatures[target.dataset.type]
|
||||
).reduce((acc, key) => {
|
||||
acc[key] = _del;
|
||||
acc[`-=${key}`] = null;
|
||||
|
||||
return acc;
|
||||
}, {})
|
||||
|
|
@ -402,12 +403,12 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
const domainName = button.form.elements.domainName.value;
|
||||
if (!domainName) return;
|
||||
|
||||
const newSlug = domainName.slugify();
|
||||
const newSlug = slugify(domainName);
|
||||
const existingDomains = [
|
||||
...Object.values(this.settings.domains),
|
||||
...Object.values(CONFIG.DH.DOMAIN.domains)
|
||||
];
|
||||
if (existingDomains.find(x => x.id === newSlug)) {
|
||||
if (existingDomains.find(x => slugify(game.i18n.localize(x.label)) === newSlug)) {
|
||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.domains.duplicateDomain'));
|
||||
return;
|
||||
}
|
||||
|
|
@ -454,12 +455,12 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
if (!confirmed) return;
|
||||
|
||||
await this.settings.updateSource({
|
||||
[`domains.${this.selected.domain}`]: _del
|
||||
[`domains.-=${this.selected.domain}`]: null
|
||||
});
|
||||
|
||||
const currentSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew);
|
||||
if (currentSettings.domains[this.selected.domain]) {
|
||||
await currentSettings.updateSource({ [`domains.${this.selected.domain}`]: _del });
|
||||
await currentSettings.updateSource({ [`domains.-=${this.selected.domain}`]: null });
|
||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, currentSettings);
|
||||
}
|
||||
|
||||
|
|
@ -506,7 +507,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
|
||||
static async deleteAdversaryType(_, target) {
|
||||
const { key } = target.dataset;
|
||||
await this.settings.updateSource({ [`adversaryTypes.${key}`]: _del });
|
||||
await this.settings.updateSource({ [`adversaryTypes.-=${key}`]: null });
|
||||
|
||||
this.selected.adversaryType = this.selected.adversaryType === key ? null : this.selected.adversaryType;
|
||||
this.render();
|
||||
|
|
@ -528,7 +529,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
const identifier = button.form.elements.identifier.value;
|
||||
if (!identifier) return;
|
||||
|
||||
const sluggedIdentifier = identifier.slugify();
|
||||
const sluggedIdentifier = slugify(identifier);
|
||||
|
||||
await this.settings.updateSource({
|
||||
[`resources.${actorType}.resources.${sluggedIdentifier}`]: Resource.getDefaultResourceData(identifier)
|
||||
|
|
@ -562,7 +563,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
|
||||
const { actorType, resourceKey } = target.dataset;
|
||||
await this.settings.updateSource({
|
||||
[`resources.${actorType}.resources.${resourceKey}`]: _del
|
||||
[`resources.${actorType}.resources.-=${resourceKey}`]: null
|
||||
});
|
||||
|
||||
game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, this.settings.toObject());
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ export { default as ActionConfig } from './action-config.mjs';
|
|||
export { default as ActionSettingsConfig } from './action-settings-config.mjs';
|
||||
export { default as CharacterSettings } from './character-settings.mjs';
|
||||
export { default as AdversarySettings } from './adversary-settings.mjs';
|
||||
export { default as NPCSettings } from './npc-settings.mjs';
|
||||
export { default as CompanionSettings } from './companion-settings.mjs';
|
||||
export { default as 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 ActiveEffectConfig } from './activeEffectConfig.mjs';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { getUnusedDamageTypes } from '../../helpers/utils.mjs';
|
||||
import DaggerheartSheet from '../sheets/daggerheart-sheet.mjs';
|
||||
|
||||
const { ApplicationV2 } = foundry.applications.api;
|
||||
|
|
@ -36,9 +35,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
|||
editDoc: this.editDoc,
|
||||
addTrigger: this.addTrigger,
|
||||
removeTrigger: this.removeTrigger,
|
||||
expandTrigger: this.expandTrigger,
|
||||
addBeastformTraitBonus: this.addBeastformTraitBonus,
|
||||
removeBeastformTraitBonus: this.removeBeastformTraitBonus
|
||||
expandTrigger: this.expandTrigger
|
||||
},
|
||||
form: {
|
||||
handler: this.updateForm,
|
||||
|
|
@ -107,7 +104,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
|||
}
|
||||
};
|
||||
|
||||
static CLEAN_ARRAYS = ['cost', 'effects', 'summon'];
|
||||
static CLEAN_ARRAYS = ['damage.parts', 'cost', 'effects', 'summon'];
|
||||
|
||||
_getTabs(tabs) {
|
||||
for (const v of Object.values(tabs)) {
|
||||
|
|
@ -156,13 +153,8 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
|||
context.openSection = this.openSection;
|
||||
context.tabs = this._getTabs(this.constructor.TABS);
|
||||
context.config = CONFIG.DH;
|
||||
if (this.action.damage) {
|
||||
context.allDamageTypesUsed = !getUnusedDamageTypes(this.action.damage.parts).length;
|
||||
|
||||
if (this.action.damage.hasOwnProperty('includeBase') && this.action.type === 'attack')
|
||||
context.hasBaseDamage = !!this.action.parent.attack;
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
@ -204,7 +196,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
|||
};
|
||||
}
|
||||
|
||||
if (this.action.parent.metadata?.isInventoryItem) {
|
||||
if (this.action.parent.metadata?.isQuantifiable) {
|
||||
options.quantity = {
|
||||
label: 'DAGGERHEART.GENERAL.itemQuantity',
|
||||
group: 'Global'
|
||||
|
|
@ -264,9 +256,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
|||
key = event.target.closest('[data-key]').dataset.key;
|
||||
if (!this.action[key]) return;
|
||||
|
||||
const value = key === 'areas' ? { name: this.action.item.name } : {};
|
||||
|
||||
data[key].push(this.action.defaultValues[key] ?? value);
|
||||
data[key].push(this.action.defaultValues[key] ?? {});
|
||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||
}
|
||||
|
||||
|
|
@ -301,64 +291,18 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
|||
|
||||
static addDamage(_event) {
|
||||
if (!this.action.damage.parts) return;
|
||||
|
||||
const choices = getUnusedDamageTypes(this.action._source.damage.parts);
|
||||
const content = new foundry.data.fields.StringField({
|
||||
label: game.i18n.localize('Damage Type'),
|
||||
choices,
|
||||
required: true
|
||||
}).toFormGroup(
|
||||
{},
|
||||
{
|
||||
name: 'type',
|
||||
localize: true,
|
||||
nameAttr: 'value',
|
||||
labelAttr: 'label'
|
||||
}
|
||||
).outerHTML;
|
||||
|
||||
const callback = (_, button) => {
|
||||
const data = this.action.toObject();
|
||||
const type = choices[button.form.elements.type.value].value;
|
||||
const part = this.action.schema.fields.damage.fields.parts.element.getInitialValue();
|
||||
part.applyTo = type;
|
||||
if (type === CONFIG.DH.GENERAL.healingTypes.hitPoints.id)
|
||||
part.type = this.action.schema.fields.damage.fields.parts.element.fields.type.element.initial;
|
||||
|
||||
data.damage.parts[type] = part;
|
||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||
};
|
||||
|
||||
const typeDialog = new foundry.applications.api.DialogV2({
|
||||
buttons: [
|
||||
foundry.utils.mergeObject(
|
||||
{
|
||||
action: 'ok',
|
||||
label: 'Confirm',
|
||||
icon: 'fas fa-check',
|
||||
default: true
|
||||
},
|
||||
{ callback: callback }
|
||||
)
|
||||
],
|
||||
content: content,
|
||||
rejectClose: false,
|
||||
modal: false,
|
||||
window: {
|
||||
title: game.i18n.localize('Add Damage')
|
||||
},
|
||||
position: { width: 300 }
|
||||
});
|
||||
|
||||
typeDialog.render(true);
|
||||
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();
|
||||
const key = button.dataset.key;
|
||||
delete data.damage.parts[key];
|
||||
data.damage.parts[`${key}`] = _del;
|
||||
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) });
|
||||
}
|
||||
|
||||
|
|
@ -416,21 +360,6 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
|||
}
|
||||
}
|
||||
|
||||
static async addBeastformTraitBonus() {
|
||||
const data = this.action.toObject();
|
||||
data.beastform.modifications.traitBonuses = [
|
||||
...data.beastform.modifications.traitBonuses,
|
||||
this.action.schema.fields.beastform.fields.modifications.fields.traitBonuses.element.getInitialValue()
|
||||
];
|
||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||
}
|
||||
|
||||
static async removeBeastformTraitBonus(_event, button) {
|
||||
const data = this.action.toObject();
|
||||
data.beastform.modifications.traitBonuses.splice(button.dataset.index, 1);
|
||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||
}
|
||||
|
||||
updateSummonCount(event) {
|
||||
event.stopPropagation();
|
||||
const wrapper = event.target.closest('.summon-count-wrapper');
|
||||
|
|
|
|||
|
|
@ -19,19 +19,17 @@ export default class DHActionConfig extends DHActionBaseConfig {
|
|||
return context;
|
||||
}
|
||||
|
||||
static async addEffect(event) {
|
||||
const { areaIndex } = event.target.dataset;
|
||||
static async addEffect(_event) {
|
||||
if (!this.action.effects) return;
|
||||
const effectData = this._addEffectData.bind(this)();
|
||||
const data = this.action.toObject();
|
||||
|
||||
const created = await this.action.item.createEmbeddedDocuments('ActiveEffect', [
|
||||
game.system.api.data.activeEffects.BaseEffect.getDefaultObject({ transfer: false })
|
||||
]);
|
||||
|
||||
if (areaIndex !== undefined) data.areas[areaIndex].effects.push(created[0]._id);
|
||||
else data.effects.push({ _id: created[0]._id });
|
||||
const [created] = await this.action.item.createEmbeddedDocuments('ActiveEffect', [effectData], {
|
||||
render: false
|
||||
});
|
||||
data.effects.push({ _id: created._id });
|
||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||
this.action.item.effects.get(created[0]._id).sheet.render(true);
|
||||
this.action.item.effects.get(created._id).sheet.render(true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -54,19 +52,9 @@ export default class DHActionConfig extends DHActionBaseConfig {
|
|||
|
||||
static removeEffect(event, button) {
|
||||
if (!this.action.effects) return;
|
||||
|
||||
const { areaIndex, index } = button.dataset;
|
||||
let effectId = null;
|
||||
if (areaIndex !== undefined) {
|
||||
effectId = this.action.areas[areaIndex].effects[index];
|
||||
const data = this.action.toObject();
|
||||
data.areas[areaIndex].effects.splice(index, 1);
|
||||
this.constructor.updateForm.call(this, null, null, { object: foundry.utils.flattenObject(data) });
|
||||
} else {
|
||||
const index = button.dataset.index,
|
||||
effectId = this.action.effects[index]._id;
|
||||
this.constructor.removeElement.call(this, event, button);
|
||||
}
|
||||
|
||||
this.constructor.removeElement.bind(this)(event, button);
|
||||
this.action.item.deleteEmbeddedDocuments('ActiveEffect', [effectId]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,35 +31,21 @@ export default class DHActionSettingsConfig extends DHActionBaseConfig {
|
|||
}
|
||||
|
||||
static async addEffect(_event) {
|
||||
const { areaIndex } = event.target.dataset;
|
||||
if (!this.action.effects) return;
|
||||
|
||||
const effectData = game.system.api.data.activeEffects.BaseEffect.getDefaultObject({ transfer: false });
|
||||
const effectData = game.system.api.data.activeEffects.BaseEffect.getDefaultObject();
|
||||
const data = this.action.toObject();
|
||||
|
||||
this.sheetUpdate(data, effectData);
|
||||
this.effects = [...this.effects, effectData];
|
||||
|
||||
if (areaIndex !== undefined) data.areas[areaIndex].effects.push(effectData.id);
|
||||
else data.effects.push({ _id: effectData.id });
|
||||
|
||||
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 { areaIndex, index } = button.dataset;
|
||||
let effectId = null;
|
||||
if (areaIndex !== undefined) {
|
||||
effectId = this.action.areas[areaIndex].effects[index];
|
||||
const data = this.action.toObject();
|
||||
data.areas[areaIndex].effects.splice(index, 1);
|
||||
this.constructor.updateForm.call(this, null, null, { object: foundry.utils.flattenObject(data) });
|
||||
} else {
|
||||
const index = button.dataset.index,
|
||||
effectId = this.action.effects[index]._id;
|
||||
this.constructor.removeElement.call(this, event, button);
|
||||
}
|
||||
|
||||
this.constructor.removeElement.bind(this)(event, button);
|
||||
this.sheetUpdate(
|
||||
this.action.toObject(),
|
||||
this.effects.find(x => x.id === effectId),
|
||||
|
|
@ -69,7 +55,7 @@ export default class DHActionSettingsConfig extends DHActionBaseConfig {
|
|||
|
||||
static async editEffect(event) {
|
||||
const id = event.target.closest('[data-effect-id]')?.dataset?.effectId;
|
||||
const updatedEffect = await game.system.api.applications.sheetConfigs.ActiveEffectConfig.configureSetting(
|
||||
const updatedEffect = await game.system.api.applications.sheetConfigs.SettingActiveEffectConfig.configure(
|
||||
this.getEffectDetails(id)
|
||||
);
|
||||
if (!updatedEffect) return;
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
|
|||
settings: { template: 'systems/daggerheart/templates/sheets/activeEffect/settings.hbs' },
|
||||
changes: {
|
||||
template: 'systems/daggerheart/templates/sheets/activeEffect/changes.hbs',
|
||||
templates: ['systems/daggerheart/templates/sheets/activeEffect/change.hbs'],
|
||||
scrollable: ['ol[data-changes]']
|
||||
},
|
||||
footer: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-form-footer.hbs' }
|
||||
|
|
@ -41,7 +40,7 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
|
|||
* @returns {ChangeChoice { value: string, label: string, hint: string, group: string }[]}
|
||||
*/
|
||||
static getChangeChoices() {
|
||||
const ignoredActorKeys = ['config', 'DhEnvironment', 'DhParty', 'DhNPC'];
|
||||
const ignoredActorKeys = ['config', 'DhEnvironment', 'DhParty'];
|
||||
|
||||
const getAllLeaves = (root, group, parentPath = '') => {
|
||||
const leaves = [];
|
||||
|
|
@ -150,18 +149,6 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
|
|||
minLength: 0
|
||||
});
|
||||
});
|
||||
|
||||
htmlElement
|
||||
.querySelector('.stacking-change-checkbox')
|
||||
?.addEventListener('change', this.stackingChangeToggle.bind(this));
|
||||
|
||||
htmlElement
|
||||
.querySelector('.armor-change-checkbox')
|
||||
?.addEventListener('change', this.armorChangeToggle.bind(this));
|
||||
|
||||
htmlElement
|
||||
.querySelector('.armor-damage-thresholds-checkbox')
|
||||
?.addEventListener('change', this.armorDamageThresholdToggle.bind(this));
|
||||
}
|
||||
|
||||
async _prepareContext(options) {
|
||||
|
|
@ -175,7 +162,6 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
|
|||
const partContext = await super._preparePartContext(partId, context);
|
||||
switch (partId) {
|
||||
case 'details':
|
||||
partContext.isItemEffect = partContext.isItemEffect || this.options.isSetting;
|
||||
const useGeneric = game.settings.get(
|
||||
CONFIG.DH.id,
|
||||
CONFIG.DH.SETTINGS.gameSettings.appearance
|
||||
|
|
@ -187,166 +173,8 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
|
|||
}));
|
||||
}
|
||||
break;
|
||||
case 'settings':
|
||||
const groups = {
|
||||
time: _loc('EFFECT.DURATION.UNITS.GROUPS.time'),
|
||||
combat: _loc('EFFECT.DURATION.UNITS.GROUPS.combat')
|
||||
};
|
||||
partContext.durationUnits = CONST.ACTIVE_EFFECT_DURATION_UNITS.map(value => ({
|
||||
value,
|
||||
label: _loc(`EFFECT.DURATION.UNITS.${value}`),
|
||||
group: CONST.ACTIVE_EFFECT_TIME_DURATION_UNITS.includes(value) ? groups.time : groups.combat
|
||||
}));
|
||||
break;
|
||||
case 'changes':
|
||||
const singleTypes = ['armor'];
|
||||
const typedChanges = context.source.changes.reduce((acc, change, index) => {
|
||||
if (singleTypes.includes(change.type)) {
|
||||
acc[change.type] = { ...change, index };
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
partContext.changes = partContext.changes.filter(c => !!c);
|
||||
partContext.typedChanges = typedChanges;
|
||||
break;
|
||||
}
|
||||
|
||||
return partContext;
|
||||
}
|
||||
|
||||
stackingChangeToggle(event) {
|
||||
const stackingFields = this.document.system.schema.fields.stacking.fields;
|
||||
const systemData = {
|
||||
stacking: event.target.checked
|
||||
? { value: stackingFields.value.initial, max: stackingFields.max.initial }
|
||||
: null
|
||||
};
|
||||
return this.submit({ updateData: { system: systemData } });
|
||||
}
|
||||
|
||||
armorChangeToggle(event) {
|
||||
if (event.target.checked) {
|
||||
this.addArmorChange();
|
||||
} else {
|
||||
this.removeTypedChange(event.target.dataset.index);
|
||||
}
|
||||
}
|
||||
|
||||
/* Could be generalised if needed later */
|
||||
addArmorChange() {
|
||||
const submitData = this._processFormData(null, this.form, new FormDataExtended(this.form));
|
||||
const changes = Object.values(submitData.system?.changes ?? {});
|
||||
changes.push(game.system.api.data.activeEffects.changeTypes.armor.getInitialValue());
|
||||
return this.submit({ updateData: { system: { changes } } });
|
||||
}
|
||||
|
||||
removeTypedChange(indexString) {
|
||||
const submitData = this._processFormData(null, this.form, new FormDataExtended(this.form));
|
||||
const changes = Object.values(submitData.system.changes);
|
||||
const index = Number(indexString);
|
||||
changes.splice(index, 1);
|
||||
return this.submit({ updateData: { system: { changes } } });
|
||||
}
|
||||
|
||||
armorDamageThresholdToggle(event) {
|
||||
const submitData = this._processFormData(null, this.form, new FormDataExtended(this.form));
|
||||
const changes = Object.values(submitData.system?.changes ?? {});
|
||||
const index = Number(event.target.dataset.index);
|
||||
if (event.target.checked) {
|
||||
changes[index].value.damageThresholds = { major: 0, severe: 0 };
|
||||
} else {
|
||||
changes[index].value.damageThresholds = null;
|
||||
}
|
||||
|
||||
return this.submit({ updateData: { system: { changes } } });
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
_renderChange(context) {
|
||||
const { change, index, defaultPriority } = context;
|
||||
if (!(change.type in CONFIG.DH.GENERAL.baseActiveEffectModes)) return null;
|
||||
|
||||
const changeTypesSchema = this.document.system.schema.fields.changes.element.types;
|
||||
const fields = context.fields ?? (changeTypesSchema[change.type] ?? changeTypesSchema.add).fields;
|
||||
if (typeof change.value !== 'string') change.value = JSON.stringify(change.value);
|
||||
Object.assign(
|
||||
change,
|
||||
['key', 'type', 'value', 'priority'].reduce((paths, fieldName) => {
|
||||
paths[`${fieldName}Path`] = `system.changes.${index}.${fieldName}`;
|
||||
return paths;
|
||||
}, {})
|
||||
);
|
||||
return (
|
||||
game.system.api.documents.DhActiveEffect.CHANGE_TYPES[change.type].render?.(
|
||||
change,
|
||||
index,
|
||||
defaultPriority
|
||||
) ??
|
||||
foundry.applications.handlebars.renderTemplate(
|
||||
'systems/daggerheart/templates/sheets/activeEffect/change.hbs',
|
||||
{
|
||||
change,
|
||||
index,
|
||||
defaultPriority,
|
||||
fields,
|
||||
types: Object.keys(CONFIG.DH.GENERAL.baseActiveEffectModes).reduce((r, key) => {
|
||||
r[key] = CONFIG.DH.GENERAL.baseActiveEffectModes[key].label;
|
||||
return r;
|
||||
}, {})
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
_onChangeForm(_formConfig, event) {
|
||||
if (foundry.utils.isElementInstanceOf(event.target, 'select') && event.target.name === 'system.duration.type') {
|
||||
const durationSection = this.element.querySelector('.custom-duration-section');
|
||||
if (event.target.value === 'custom') durationSection.classList.add('visible');
|
||||
else durationSection.classList.remove('visible');
|
||||
|
||||
const durationDescription = this.element.querySelector('.duration-description');
|
||||
if (event.target.value === 'temporary') durationDescription.classList.add('visible');
|
||||
else durationDescription.classList.remove('visible');
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
_processFormData(event, form, formData) {
|
||||
const submitData = super._processFormData(event, form, formData);
|
||||
if (submitData.start && !submitData.start.time) submitData.start.time = '0';
|
||||
else if (!submitData) submitData.start = null;
|
||||
|
||||
return submitData;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
_processSubmitData(event, form, submitData, options) {
|
||||
if (this.options.isSetting) {
|
||||
// Settings should update source instead
|
||||
this.document.updateSource(submitData);
|
||||
this.render();
|
||||
} else {
|
||||
return super._processSubmitData(event, form, submitData, options);
|
||||
}
|
||||
}
|
||||
|
||||
/** Creates an active effect config for a setting */
|
||||
static async configureSetting(effect, options = {}) {
|
||||
const document = new CONFIG.ActiveEffect.documentClass({ ...foundry.utils.duplicate(effect), _id: effect.id });
|
||||
return new Promise(resolve => {
|
||||
const app = new this({ document, ...options, isSetting: true });
|
||||
app.addEventListener(
|
||||
'close',
|
||||
() => {
|
||||
const newEffect = app.document.toObject(true);
|
||||
newEffect.id = newEffect._id;
|
||||
delete newEffect._id;
|
||||
resolve(newEffect);
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
app.render({ force: true });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ export default class DHAdversarySettings extends DHBaseActorSettings {
|
|||
});
|
||||
if (!confirmed) return;
|
||||
|
||||
await this.actor.update({ [`system.experiences.${target.dataset.experience}`]: _del });
|
||||
await this.actor.update({ [`system.experiences.-=${target.dataset.experience}`]: null });
|
||||
}
|
||||
|
||||
async _onDragStart(event) {
|
||||
|
|
@ -110,7 +110,6 @@ export default class DHAdversarySettings extends DHBaseActorSettings {
|
|||
}
|
||||
|
||||
async _onDrop(event) {
|
||||
event.stopPropagation();
|
||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||
|
||||
const item = await fromUuid(data.uuid);
|
||||
|
|
|
|||
|
|
@ -101,8 +101,8 @@ export default class DHCharacterSettings extends DHBaseActorSettings {
|
|||
|
||||
if (relinkAchievementData.length > 0) {
|
||||
relinkAchievementData.forEach(data => {
|
||||
updates[`system.levelData.levelups.${data.levelKey}.achievements.experiences.${data.experience}`] =
|
||||
_del;
|
||||
updates[`system.levelData.levelups.${data.levelKey}.achievements.experiences.-=${data.experience}`] =
|
||||
null;
|
||||
});
|
||||
} else if (relinkSelectionData.length > 0) {
|
||||
relinkSelectionData.forEach(data => {
|
||||
|
|
@ -137,7 +137,7 @@ export default class DHCharacterSettings extends DHBaseActorSettings {
|
|||
|
||||
await this.actor.update({
|
||||
...updates,
|
||||
[`system.experiences.${target.dataset.experience}`]: _del
|
||||
[`system.experiences.-=${target.dataset.experience}`]: null
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,6 +117,6 @@ export default class DHCompanionSettings extends DHBaseActorSettings {
|
|||
});
|
||||
if (!confirmed) return;
|
||||
|
||||
await this.actor.update({ [`system.experiences.${target.dataset.experience}`]: _del });
|
||||
await this.actor.update({ [`system.experiences.-=${target.dataset.experience}`]: null });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,9 +68,9 @@ export default class DHEnvironmentSettings extends DHBaseActorSettings {
|
|||
*/
|
||||
static async #addCategory() {
|
||||
await this.actor.update({
|
||||
[`system.potentialAdversaries.${foundry.utils.randomID()}`]: {
|
||||
label: game.i18n.localize('DAGGERHEART.ACTORS.Environment.newAdversary')
|
||||
}
|
||||
[`system.potentialAdversaries.${foundry.utils.randomID()}.label`]: game.i18n.localize(
|
||||
'DAGGERHEART.ACTORS.Environment.newAdversary'
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -79,7 +79,7 @@ export default class DHEnvironmentSettings extends DHBaseActorSettings {
|
|||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #removeCategory(_, target) {
|
||||
await this.actor.update({ [`system.potentialAdversaries.${target.dataset.categoryId}`]: _del });
|
||||
await this.actor.update({ [`system.potentialAdversaries.-=${target.dataset.categoryId}`]: null });
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -121,7 +121,6 @@ export default class DHEnvironmentSettings extends DHBaseActorSettings {
|
|||
}
|
||||
|
||||
async _onDrop(event) {
|
||||
event.stopPropagation();
|
||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||
const item = await fromUuid(data.uuid);
|
||||
if (data.fromInternal && item?.parent?.uuid === this.actor.uuid) return;
|
||||
|
|
|
|||
|
|
@ -1,85 +0,0 @@
|
|||
import DHBaseActorSettings from '../sheets/api/actor-setting.mjs';
|
||||
|
||||
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
|
||||
|
||||
export default class DHNPCSettings extends DHBaseActorSettings {
|
||||
/**@inheritdoc */
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ['npc-settings'],
|
||||
position: { width: 455, height: 'auto' },
|
||||
actions: {},
|
||||
dragDrop: [
|
||||
{ dragSelector: null, dropSelector: '.tab.features' },
|
||||
{ dragSelector: '.feature-item', dropSelector: null }
|
||||
]
|
||||
};
|
||||
|
||||
/**@override */
|
||||
static PARTS = {
|
||||
header: {
|
||||
id: 'header',
|
||||
template: 'systems/daggerheart/templates/sheets-settings/npc-settings/header.hbs'
|
||||
},
|
||||
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
|
||||
details: {
|
||||
id: 'details',
|
||||
template: 'systems/daggerheart/templates/sheets-settings/npc-settings/details.hbs'
|
||||
},
|
||||
features: {
|
||||
id: 'features',
|
||||
template: 'systems/daggerheart/templates/sheets-settings/npc-settings/features.hbs'
|
||||
}
|
||||
};
|
||||
|
||||
/** @override */
|
||||
static TABS = {
|
||||
primary: {
|
||||
tabs: [{ id: 'details' }, { id: 'features' }],
|
||||
initial: 'details',
|
||||
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
||||
}
|
||||
};
|
||||
|
||||
async _prepareContext(options) {
|
||||
const context = await super._prepareContext(options);
|
||||
|
||||
const featureForms = ['passive', 'action', 'reaction'];
|
||||
context.features = context.document.system.features.sort((a, b) =>
|
||||
a.system.featureForm !== b.system.featureForm
|
||||
? featureForms.indexOf(a.system.featureForm) - featureForms.indexOf(b.system.featureForm)
|
||||
: a.sort - b.sort
|
||||
);
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
async _onDragStart(event) {
|
||||
const featureItem = event.currentTarget.closest('.feature-item');
|
||||
|
||||
if (featureItem) {
|
||||
const feature = this.actor.items.get(featureItem.id);
|
||||
const featureData = { type: 'Item', uuid: feature.uuid, fromInternal: true };
|
||||
event.dataTransfer.setData('text/plain', JSON.stringify(featureData));
|
||||
event.dataTransfer.setDragImage(featureItem.querySelector('img'), 60, 0);
|
||||
}
|
||||
}
|
||||
|
||||
async _onDrop(event) {
|
||||
event.stopPropagation();
|
||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||
|
||||
const item = await fromUuid(data.uuid);
|
||||
if (item?.type === 'feature') {
|
||||
if (data.fromInternal && item.parent?.uuid === this.actor.uuid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const itemData = item.toObject();
|
||||
delete itemData._id;
|
||||
|
||||
await this.actor.createEmbeddedDocuments('Item', [itemData]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,223 @@
|
|||
import autocomplete from 'autocompleter';
|
||||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
||||
export default class SettingActiveEffectConfig extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
constructor(effect) {
|
||||
super({});
|
||||
|
||||
this.effect = foundry.utils.deepClone(effect);
|
||||
this.changeChoices = game.system.api.applications.sheetConfigs.ActiveEffectConfig.getChangeChoices();
|
||||
}
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ['daggerheart', 'sheet', 'dh-style', 'active-effect-config', 'standard-form'],
|
||||
tag: 'form',
|
||||
position: {
|
||||
width: 560
|
||||
},
|
||||
form: {
|
||||
submitOnChange: false,
|
||||
closeOnSubmit: false,
|
||||
handler: SettingActiveEffectConfig.#onSubmit
|
||||
},
|
||||
actions: {
|
||||
editImage: SettingActiveEffectConfig.#editImage,
|
||||
addChange: SettingActiveEffectConfig.#addChange,
|
||||
deleteChange: SettingActiveEffectConfig.#deleteChange
|
||||
}
|
||||
};
|
||||
|
||||
static PARTS = {
|
||||
header: { template: 'systems/daggerheart/templates/sheets/activeEffect/header.hbs' },
|
||||
tabs: { template: 'templates/generic/tab-navigation.hbs' },
|
||||
details: { template: 'systems/daggerheart/templates/sheets/activeEffect/details.hbs', scrollable: [''] },
|
||||
settings: { template: 'systems/daggerheart/templates/sheets/activeEffect/settings.hbs' },
|
||||
changes: {
|
||||
template: 'systems/daggerheart/templates/sheets/activeEffect/changes.hbs',
|
||||
scrollable: ['ol[data-changes]']
|
||||
},
|
||||
footer: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-form-footer.hbs' }
|
||||
};
|
||||
|
||||
static TABS = {
|
||||
sheet: {
|
||||
tabs: [
|
||||
{ id: 'details', icon: 'fa-solid fa-book' },
|
||||
{ id: 'settings', icon: 'fa-solid fa-bars', label: 'DAGGERHEART.GENERAL.Tabs.settings' },
|
||||
{ id: 'changes', icon: 'fa-solid fa-gears' }
|
||||
],
|
||||
initial: 'details',
|
||||
labelPrefix: 'EFFECT.TABS'
|
||||
}
|
||||
};
|
||||
|
||||
/**@inheritdoc */
|
||||
async _onFirstRender(context, options) {
|
||||
await super._onFirstRender(context, options);
|
||||
}
|
||||
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
context.source = this.effect;
|
||||
context.fields = game.system.api.documents.DhActiveEffect.schema.fields;
|
||||
context.systemFields = game.system.api.data.activeEffects.BaseEffect._schema.fields;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
_attachPartListeners(partId, htmlElement, options) {
|
||||
super._attachPartListeners(partId, htmlElement, options);
|
||||
const changeChoices = this.changeChoices;
|
||||
|
||||
htmlElement.querySelectorAll('.effect-change-input').forEach(element => {
|
||||
autocomplete({
|
||||
input: element,
|
||||
fetch: function (text, update) {
|
||||
if (!text) {
|
||||
update(changeChoices);
|
||||
} else {
|
||||
text = text.toLowerCase();
|
||||
var suggestions = changeChoices.filter(n => n.label.toLowerCase().includes(text));
|
||||
update(suggestions);
|
||||
}
|
||||
},
|
||||
render: function (item, search) {
|
||||
const label = game.i18n.localize(item.label);
|
||||
const matchIndex = label.toLowerCase().indexOf(search);
|
||||
|
||||
const beforeText = label.slice(0, matchIndex);
|
||||
const matchText = label.slice(matchIndex, matchIndex + search.length);
|
||||
const after = label.slice(matchIndex + search.length, label.length);
|
||||
|
||||
const element = document.createElement('li');
|
||||
element.innerHTML =
|
||||
`${beforeText}${matchText ? `<strong>${matchText}</strong>` : ''}${after}`.replaceAll(
|
||||
' ',
|
||||
' '
|
||||
);
|
||||
if (item.hint) {
|
||||
element.dataset.tooltip = game.i18n.localize(item.hint);
|
||||
}
|
||||
|
||||
return element;
|
||||
},
|
||||
renderGroup: function (label) {
|
||||
const itemElement = document.createElement('div');
|
||||
itemElement.textContent = game.i18n.localize(label);
|
||||
return itemElement;
|
||||
},
|
||||
onSelect: function (item) {
|
||||
element.value = `system.${item.value}`;
|
||||
},
|
||||
click: e => e.fetch(),
|
||||
customize: function (_input, _inputRect, container) {
|
||||
container.style.zIndex = foundry.applications.api.ApplicationV2._maxZ;
|
||||
},
|
||||
minLength: 0
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async _preparePartContext(partId, context) {
|
||||
if (partId in context.tabs) context.tab = context.tabs[partId];
|
||||
switch (partId) {
|
||||
case 'details':
|
||||
context.statuses = CONFIG.statusEffects.map(s => ({ value: s.id, label: game.i18n.localize(s.name) }));
|
||||
context.isActorEffect = false;
|
||||
context.isItemEffect = true;
|
||||
const useGeneric = game.settings.get(
|
||||
CONFIG.DH.id,
|
||||
CONFIG.DH.SETTINGS.gameSettings.appearance
|
||||
).showGenericStatusEffects;
|
||||
if (!useGeneric) {
|
||||
context.statuses = [
|
||||
...context.statuses,
|
||||
Object.values(CONFIG.DH.GENERAL.conditions).map(status => ({
|
||||
value: status.id,
|
||||
label: game.i18n.localize(status.name)
|
||||
}))
|
||||
];
|
||||
}
|
||||
break;
|
||||
case 'changes':
|
||||
context.modes = Object.entries(CONST.ACTIVE_EFFECT_MODES).reduce((modes, [key, value]) => {
|
||||
modes[value] = game.i18n.localize(`EFFECT.MODE_${key}`);
|
||||
return modes;
|
||||
}, {});
|
||||
|
||||
context.priorities = ActiveEffectConfig.DEFAULT_PRIORITIES;
|
||||
break;
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
static async #onSubmit(_event, _form, formData) {
|
||||
this.data = foundry.utils.expandObject(formData.object);
|
||||
this.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit a Document image.
|
||||
* @this {DocumentSheetV2}
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #editImage(_event, target) {
|
||||
if (target.nodeName !== 'IMG') {
|
||||
throw new Error('The editImage action is available only for IMG elements.');
|
||||
}
|
||||
|
||||
const attr = target.dataset.edit;
|
||||
const current = foundry.utils.getProperty(this.effect, attr);
|
||||
const fp = new FilePicker.implementation({
|
||||
current,
|
||||
type: 'image',
|
||||
callback: path => (target.src = path),
|
||||
position: {
|
||||
top: this.position.top + 40,
|
||||
left: this.position.left + 10
|
||||
}
|
||||
});
|
||||
|
||||
await fp.browse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new change to the effect's changes array.
|
||||
* @this {ActiveEffectConfig}
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #addChange() {
|
||||
const { changes, ...rest } = foundry.utils.expandObject(new FormDataExtended(this.form).object);
|
||||
const updatedChanges = Object.values(changes ?? {});
|
||||
updatedChanges.push({});
|
||||
|
||||
this.effect = { ...rest, changes: updatedChanges };
|
||||
this.render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a change from the effect's changes array.
|
||||
* @this {ActiveEffectConfig}
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #deleteChange(event) {
|
||||
const submitData = foundry.utils.expandObject(new FormDataExtended(this.form).object);
|
||||
const updatedChanges = Object.values(submitData.changes);
|
||||
const row = event.target.closest('li');
|
||||
const index = Number(row.dataset.index) || 0;
|
||||
updatedChanges.splice(index, 1);
|
||||
|
||||
this.effect = { ...submitData, changes: updatedChanges };
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async configure(effect, options = {}) {
|
||||
return new Promise(resolve => {
|
||||
const app = new this(effect, options);
|
||||
app.addEventListener('close', () => resolve(app.data), { once: true });
|
||||
app.render({ force: true });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -147,7 +147,7 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App
|
|||
const effectIndex = this.move.effects.findIndex(x => x.id === id);
|
||||
const effect = this.move.effects[effectIndex];
|
||||
const updatedEffect =
|
||||
await game.system.api.applications.sheetConfigs.ActiveEffectConfig.configureSetting(effect);
|
||||
await game.system.api.applications.sheetConfigs.SettingActiveEffectConfig.configure(effect);
|
||||
if (!updatedEffect) return;
|
||||
|
||||
await this.updateMove({
|
||||
|
|
@ -168,8 +168,8 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App
|
|||
updatedEffects = deleteEffect
|
||||
? currentEffects.filter(x => x.id !== effectData.id)
|
||||
: existingEffectIndex === -1
|
||||
? [...currentEffects, effectData]
|
||||
: currentEffects.with(existingEffectIndex, effectData);
|
||||
? [...currentEffects, effectData]
|
||||
: currentEffects.with(existingEffectIndex, effectData);
|
||||
await this.updateMove({
|
||||
[`${this.movePath}.effects`]: updatedEffects
|
||||
});
|
||||
|
|
@ -206,7 +206,7 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App
|
|||
}
|
||||
});
|
||||
} else {
|
||||
await this.updateMove({ [`${this.actionsPath}.${target.dataset.id}`]: _del });
|
||||
await this.updateMove({ [`${this.actionsPath}.-=${target.dataset.id}`]: null });
|
||||
}
|
||||
|
||||
this.render();
|
||||
|
|
@ -235,9 +235,9 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App
|
|||
return this.hasEffects
|
||||
? tabs
|
||||
: Object.keys(tabs).reduce((acc, key) => {
|
||||
if (key !== 'effects') acc[key] = tabs[key];
|
||||
return acc;
|
||||
}, {});
|
||||
if (key !== 'effects') acc[key] = tabs[key];
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
/** @override */
|
||||
|
|
|
|||
|
|
@ -67,9 +67,9 @@ export default function DHTokenConfigMixin(Base) {
|
|||
changes.height = tokenSize;
|
||||
}
|
||||
|
||||
// const deletions = { actorId: _del };
|
||||
// const mergeOptions = { inplace: false, performDeletions: true, actorLink: false };
|
||||
this._preview.updateSource(changes);
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -2,5 +2,4 @@ export { default as Adversary } from './adversary.mjs';
|
|||
export { default as Character } from './character.mjs';
|
||||
export { default as Companion } from './companion.mjs';
|
||||
export { default as Environment } from './environment.mjs';
|
||||
export { default as NPC } from './npc.mjs';
|
||||
export { default as Party } from './party.mjs';
|
||||
|
|
|
|||
|
|
@ -31,16 +31,6 @@ export default class AdversarySheet extends DHBaseActorSheet {
|
|||
dragSelector: '[data-item-id][draggable="true"], [data-item-id] [draggable="true"]',
|
||||
dropSelector: null
|
||||
}
|
||||
],
|
||||
contextMenus: [
|
||||
{
|
||||
handler: DHBaseActorSheet.getBaseAttackContextOptions,
|
||||
selector: '[data-item-uuid][data-type="attack"]',
|
||||
options: {
|
||||
parentClassHooks: false,
|
||||
fixed: true
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import DHBaseActorSheet from '../api/base-actor.mjs';
|
||||
import DhDeathMove from '../../dialogs/deathMove.mjs';
|
||||
import { abilities } from '../../../config/actorConfig.mjs';
|
||||
import { CharacterLevelup, LevelupViewMode } from '../../levelup/_module.mjs';
|
||||
import DhCharacterCreation from '../../characterCreation/characterCreation.mjs';
|
||||
import FilterMenu from '../../ux/filter-menu.mjs';
|
||||
import { getArmorSources, getDocFromElement, getDocFromElementSync, sortBy } from '../../../helpers/utils.mjs';
|
||||
import { getDocFromElement, getDocFromElementSync } from '../../../helpers/utils.mjs';
|
||||
|
||||
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
|
||||
|
||||
|
|
@ -12,6 +13,8 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
static DEFAULT_OPTIONS = {
|
||||
classes: ['character'],
|
||||
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: {
|
||||
toggleVault: CharacterSheet.#toggleVault,
|
||||
rollAttribute: CharacterSheet.#rollAttribute,
|
||||
|
|
@ -32,8 +35,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
cancelBeastform: CharacterSheet.#cancelBeastform,
|
||||
toggleResourceManagement: CharacterSheet.#toggleResourceManagement,
|
||||
useDowntime: this.useDowntime,
|
||||
viewParty: CharacterSheet.#viewParty,
|
||||
toggleArmorMangement: CharacterSheet.#toggleArmorManagement
|
||||
viewParty: CharacterSheet.#viewParty
|
||||
},
|
||||
window: {
|
||||
resizable: true,
|
||||
|
|
@ -57,22 +59,6 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
}
|
||||
],
|
||||
contextMenus: [
|
||||
{
|
||||
handler: CharacterSheet.#getCreationMainContextOptions,
|
||||
selector: '.character-details [data-action="editDoc"]',
|
||||
options: {
|
||||
parentClassHooks: false,
|
||||
fixed: true
|
||||
}
|
||||
},
|
||||
{
|
||||
handler: DHBaseActorSheet.getBaseAttackContextOptions,
|
||||
selector: '[data-item-uuid][data-type="attack"]',
|
||||
options: {
|
||||
parentClassHooks: false,
|
||||
fixed: true
|
||||
}
|
||||
},
|
||||
{
|
||||
handler: CharacterSheet.#getDomainCardContextOptions,
|
||||
selector: '[data-item-uuid][data-type="domainCard"]',
|
||||
|
|
@ -82,7 +68,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
}
|
||||
},
|
||||
{
|
||||
handler: CharacterSheet.#getEquipmentContextOptions,
|
||||
handler: CharacterSheet.#getEquipamentContextOptions,
|
||||
selector: '[data-item-uuid][data-type="armor"], [data-item-uuid][data-type="weapon"]',
|
||||
options: {
|
||||
parentClassHooks: false,
|
||||
|
|
@ -184,19 +170,6 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
return applicationOptions;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
_toggleDisabled(disabled) {
|
||||
// Overriden to only disable text inputs by default.
|
||||
// Everything else is done by checking @root.editable in the sheet
|
||||
const form = this.form;
|
||||
for (const input of form.querySelectorAll('input:not([type=search]), .editor.prosemirror')) {
|
||||
input.disabled = disabled;
|
||||
}
|
||||
for (const element of form.querySelectorAll('.input[contenteditable]')) {
|
||||
element.classList.toggle('disabled', disabled);
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
async _onRender(context, options) {
|
||||
await super._onRender(context, options);
|
||||
|
|
@ -228,9 +201,8 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
context.attributes = Object.keys(this.document.system.traits).reduce((acc, key) => {
|
||||
acc[key] = {
|
||||
...this.document.system.traits[key],
|
||||
label: _loc(CONFIG.DH.ACTOR.abilities[key].label),
|
||||
verbs: CONFIG.DH.ACTOR.abilities[key].verbs.map(x => game.i18n.localize(x)),
|
||||
isSpellcasting: this.document.system.spellcastModifierTrait?.key === key
|
||||
name: game.i18n.localize(CONFIG.DH.ACTOR.abilities[key].name),
|
||||
verbs: CONFIG.DH.ACTOR.abilities[key].verbs.map(x => game.i18n.localize(x))
|
||||
};
|
||||
|
||||
return acc;
|
||||
|
|
@ -246,11 +218,6 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
context.resources.stress.emptyPips =
|
||||
context.resources.stress.max < maxResource ? maxResource - context.resources.stress.max : 0;
|
||||
|
||||
context.equippedItems = sortBy(
|
||||
this.document.items.filter(i => i.system.equipped && (i.type === 'weapon' || i.usable)),
|
||||
i => (i.type === 'weapon' ? (i.system.secondary ? 1 : 0) : 2)
|
||||
);
|
||||
|
||||
context.beastformActive = this.document.effects.find(x => x.type === 'beastform');
|
||||
|
||||
return context;
|
||||
|
|
@ -338,56 +305,6 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
/* Context Menu */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
static #getCreationMainContextOptions() {
|
||||
/** Returns true if the item is managed by the level up wizard. Such items shouldn't allow things like manual removal */
|
||||
function isItemWizardManaged(item) {
|
||||
const actor = item?.actor;
|
||||
if (!actor) return false;
|
||||
|
||||
// If levelup automation is off in general or for this character, all items are unmanaged
|
||||
// This is disabled until we have proper granted feature removal, for now this feature is to correct errors
|
||||
// const levelupAuto = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).levelupAuto;
|
||||
// if (!levelupAuto) return false;
|
||||
|
||||
// Core items aren't part of levelup data. TODO: add some way to flag a specific character as no auto leveling
|
||||
const classPair = actor.system.class;
|
||||
const coreItems = [actor.system.ancestry, actor.system.community, classPair?.value, classPair?.subclass];
|
||||
if (coreItems.includes(item)) return true;
|
||||
|
||||
const levelups = Object.values(actor.system.levelData?.levelups) ?? [];
|
||||
const uuid = item.uuid;
|
||||
const sourceUuid = item._stats.compendiumSource; // on older characters this may be missing
|
||||
return levelups.some(data => {
|
||||
if (item.type === 'subclass') {
|
||||
const selectedSubclasses = data.selections.map(s => s.secondaryData?.subclass).filter(s => !!s);
|
||||
return sourceUuid
|
||||
? selectedSubclasses.includes(sourceUuid)
|
||||
: selectedSubclasses.length && item.system.isMulticlass;
|
||||
}
|
||||
|
||||
const matchesCard = data.achievements.domainCards.some(i => i.itemUuid === uuid);
|
||||
const matchesSelection = data.selections.some(s => s.itemUuid === uuid);
|
||||
return matchesCard || matchesSelection;
|
||||
});
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
label: 'CONTROLS.CommonDelete',
|
||||
icon: 'fa-solid fa-trash',
|
||||
visible: target => {
|
||||
const doc = getDocFromElementSync(target);
|
||||
return doc?.isOwner && !isItemWizardManaged(doc);
|
||||
},
|
||||
onClick: async (event, target) => {
|
||||
const doc = await getDocFromElement(target);
|
||||
if (event.shiftKey) return doc.delete();
|
||||
else return doc.deleteDialog();
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the set of ContextMenu options for DomainCards.
|
||||
* @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - The Array of context options passed to the ContextMenu instance
|
||||
|
|
@ -398,13 +315,13 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
/**@type {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} */
|
||||
const options = [
|
||||
{
|
||||
label: 'toLoadout',
|
||||
name: 'toLoadout',
|
||||
icon: 'fa-solid fa-arrow-up',
|
||||
visible: target => {
|
||||
condition: target => {
|
||||
const doc = getDocFromElementSync(target);
|
||||
return doc?.isOwner && doc.system.inVault;
|
||||
return doc && doc.system.inVault;
|
||||
},
|
||||
onClick: async (_, target) => {
|
||||
callback: async target => {
|
||||
const doc = await getDocFromElement(target);
|
||||
const actorLoadout = doc.actor.system.loadoutSlot;
|
||||
if (actorLoadout.available) return doc.update({ 'system.inVault': false });
|
||||
|
|
@ -412,13 +329,13 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
}
|
||||
},
|
||||
{
|
||||
label: 'recall',
|
||||
name: 'recall',
|
||||
icon: 'fa-solid fa-bolt-lightning',
|
||||
visible: target => {
|
||||
condition: target => {
|
||||
const doc = getDocFromElementSync(target);
|
||||
return doc?.isOwner && doc.system.inVault;
|
||||
return doc && doc.system.inVault;
|
||||
},
|
||||
onClick: async (event, target) => {
|
||||
callback: async (target, event) => {
|
||||
const doc = await getDocFromElement(target);
|
||||
const actorLoadout = doc.actor.system.loadoutSlot;
|
||||
if (!actorLoadout.available) {
|
||||
|
|
@ -451,17 +368,17 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
}
|
||||
},
|
||||
{
|
||||
label: 'toVault',
|
||||
name: 'toVault',
|
||||
icon: 'fa-solid fa-arrow-down',
|
||||
visible: target => {
|
||||
condition: target => {
|
||||
const doc = getDocFromElementSync(target);
|
||||
return doc?.isOwner && !doc.system.inVault;
|
||||
return doc && !doc.system.inVault;
|
||||
},
|
||||
onClick: async (_, target) => (await getDocFromElement(target)).update({ 'system.inVault': true })
|
||||
callback: async target => (await getDocFromElement(target)).update({ 'system.inVault': true })
|
||||
}
|
||||
].map(option => ({
|
||||
...option,
|
||||
label: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.label}`,
|
||||
name: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.name}`,
|
||||
icon: `<i class="${option.icon}"></i>`
|
||||
}));
|
||||
|
||||
|
|
@ -474,29 +391,29 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
* @this {CharacterSheet}
|
||||
* @protected
|
||||
*/
|
||||
static #getEquipmentContextOptions() {
|
||||
static #getEquipamentContextOptions() {
|
||||
const options = [
|
||||
{
|
||||
label: 'equip',
|
||||
name: 'equip',
|
||||
icon: 'fa-solid fa-hands',
|
||||
visible: target => {
|
||||
condition: target => {
|
||||
const doc = getDocFromElementSync(target);
|
||||
return doc.isOwner && doc && !doc.system.equipped;
|
||||
return doc && !doc.system.equipped;
|
||||
},
|
||||
onClick: (event, target) => CharacterSheet.#toggleEquipItem.call(this, event, target)
|
||||
callback: (target, event) => CharacterSheet.#toggleEquipItem.call(this, event, target)
|
||||
},
|
||||
{
|
||||
label: 'unequip',
|
||||
name: 'unequip',
|
||||
icon: 'fa-solid fa-hands',
|
||||
visible: target => {
|
||||
condition: target => {
|
||||
const doc = getDocFromElementSync(target);
|
||||
return doc.isOwner && doc && doc.system.equipped;
|
||||
return doc && doc.system.equipped;
|
||||
},
|
||||
onClick: (event, target) => CharacterSheet.#toggleEquipItem.call(this, event, target)
|
||||
callback: (target, event) => CharacterSheet.#toggleEquipItem.call(this, event, target)
|
||||
}
|
||||
].map(option => ({
|
||||
...option,
|
||||
label: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.label}`,
|
||||
name: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.name}`,
|
||||
icon: `<i class="${option.icon}"></i>`
|
||||
}));
|
||||
|
||||
|
|
@ -722,12 +639,12 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
}
|
||||
|
||||
async updateArmorMarks(event) {
|
||||
const inputValue = Number(event.currentTarget.value);
|
||||
const { value, max } = this.document.system.armorScore;
|
||||
const changeValue = Math.min(inputValue - value, max - value);
|
||||
const armor = this.document.system.armor;
|
||||
if (!armor) return;
|
||||
|
||||
event.currentTarget.value = inputValue < 0 ? 0 : value + changeValue;
|
||||
this.document.system.updateArmorValue({ value: changeValue });
|
||||
const maxMarks = this.document.system.armorScore;
|
||||
const value = Math.min(Math.max(Number(event.currentTarget.value), 0), maxMarks);
|
||||
await armor.update({ 'system.marks.value': value });
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
|
@ -785,11 +702,11 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
filter:
|
||||
key === 'subclasses'
|
||||
? {
|
||||
'system.linkedClass.uuid': {
|
||||
key: 'system.linkedClass.uuid',
|
||||
value: this.document.system.class.value?._stats.compendiumSource
|
||||
}
|
||||
}
|
||||
'system.linkedClass.uuid': {
|
||||
key: 'system.linkedClass.uuid',
|
||||
value: this.document.system.class.value._stats.compendiumSource
|
||||
}
|
||||
}
|
||||
: undefined,
|
||||
render: {
|
||||
noFolder: true
|
||||
|
|
@ -803,16 +720,35 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
* Rolls an attribute check based on the clicked button's dataset attribute.
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #rollAttribute(_event, button) {
|
||||
const result = await this.document.rollTrait(button.dataset.attribute);
|
||||
static async #rollAttribute(event, button) {
|
||||
const abilityLabel = game.i18n.localize(abilities[button.dataset.attribute].label);
|
||||
const config = {
|
||||
event: event,
|
||||
title: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${this.actor.name}`,
|
||||
headerTitle: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
||||
ability: abilityLabel
|
||||
}),
|
||||
effects: await game.system.api.data.actions.actionsTypes.base.getEffects(this.document),
|
||||
roll: {
|
||||
trait: button.dataset.attribute,
|
||||
type: 'trait'
|
||||
},
|
||||
hasRoll: true,
|
||||
actionType: 'action',
|
||||
headerTitle: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${this.actor.name}`,
|
||||
title: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
||||
ability: abilityLabel
|
||||
})
|
||||
};
|
||||
const result = await this.document.diceRoll(config);
|
||||
if (!result) return;
|
||||
|
||||
/* 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 })) ||
|
||||
{};
|
||||
result.resourceUpdates.addResources(costResources);
|
||||
await result.resourceUpdates.updateResources();
|
||||
config.resourceUpdates.addResources(costResources);
|
||||
await config.resourceUpdates.updateResources();
|
||||
}
|
||||
|
||||
//TODO: redo toggleEquipItem method
|
||||
|
|
@ -887,13 +823,10 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
* Toggles ArmorScore resource value.
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #toggleArmor(_, button, _element) {
|
||||
const { value, max } = this.document.system.armorScore;
|
||||
const inputValue = Number.parseInt(button.dataset.value);
|
||||
const newValue = value >= inputValue ? inputValue - 1 : inputValue;
|
||||
const changeValue = Math.min(newValue - value, max - value);
|
||||
|
||||
this.document.system.updateArmorValue({ value: changeValue });
|
||||
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 });
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1019,99 +952,6 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
});
|
||||
}
|
||||
|
||||
static async #toggleArmorManagement(_event, target) {
|
||||
const existingTooltip = document.body.querySelector('.locked-tooltip .armor-management-container');
|
||||
if (existingTooltip) {
|
||||
game.tooltip.dismissLockedTooltips();
|
||||
return;
|
||||
}
|
||||
|
||||
const armorSources = getArmorSources(this.document)
|
||||
.filter(s => !s.disabled)
|
||||
.toReversed()
|
||||
.map(({ name, document, data }) => ({
|
||||
...data,
|
||||
uuid: document.uuid,
|
||||
name
|
||||
}));
|
||||
if (!armorSources.length) return;
|
||||
|
||||
const useResourcePips = game.settings.get(
|
||||
CONFIG.DH.id,
|
||||
CONFIG.DH.SETTINGS.gameSettings.appearance
|
||||
).useResourcePips;
|
||||
const html = document.createElement('div');
|
||||
html.innerHTML = await foundry.applications.handlebars.renderTemplate(
|
||||
`systems/daggerheart/templates/ui/tooltip/armorManagement.hbs`,
|
||||
{
|
||||
sources: armorSources,
|
||||
useResourcePips
|
||||
}
|
||||
);
|
||||
|
||||
game.tooltip.dismissLockedTooltips();
|
||||
game.tooltip.activate(target, {
|
||||
html,
|
||||
locked: true,
|
||||
cssClass: 'bordered-tooltip dh-style',
|
||||
direction: 'DOWN'
|
||||
});
|
||||
|
||||
html.querySelectorAll('.armor-slot').forEach(element => {
|
||||
element.addEventListener('click', CharacterSheet.armorSourcePipUpdate);
|
||||
});
|
||||
}
|
||||
|
||||
static async armorSourceInput(event) {
|
||||
const effect = await foundry.utils.fromUuid(event.target.dataset.uuid);
|
||||
const value = Math.max(Math.min(Number.parseInt(event.target.value), effect.system.armorData.max), 0);
|
||||
event.target.value = value;
|
||||
const progressBar = event.target.closest('.status-bar.armor-slots').querySelector('progress');
|
||||
progressBar.value = value;
|
||||
}
|
||||
|
||||
/** Update specific armor source */
|
||||
static async armorSourcePipUpdate(event) {
|
||||
const target = event.target.closest('.armor-slot');
|
||||
const { uuid, value } = target.dataset;
|
||||
const document = await foundry.utils.fromUuid(uuid);
|
||||
|
||||
let inputValue = Number.parseInt(value);
|
||||
let decreasing = false;
|
||||
let newCurrent = 0;
|
||||
|
||||
if (document.type === 'armor') {
|
||||
decreasing = document.system.armor.current >= inputValue;
|
||||
newCurrent = decreasing ? inputValue - 1 : inputValue;
|
||||
await document.update({ 'system.armor.current': newCurrent });
|
||||
} else if (document.system.armorData) {
|
||||
const { current } = document.system.armorData;
|
||||
decreasing = current >= inputValue;
|
||||
newCurrent = decreasing ? inputValue - 1 : inputValue;
|
||||
|
||||
const newChanges = document.system.changes.map(change => ({
|
||||
...change,
|
||||
value: change.type === 'armor' ? { ...change.value, current: newCurrent } : change.value
|
||||
}));
|
||||
|
||||
await document.update({ 'system.changes': newChanges });
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
const container = target.closest('.slot-bar');
|
||||
for (const armorSlot of container.querySelectorAll('.armor-slot i')) {
|
||||
const index = Number.parseInt(armorSlot.dataset.index);
|
||||
if (decreasing && index >= newCurrent) {
|
||||
armorSlot.classList.remove('fa-shield');
|
||||
armorSlot.classList.add('fa-shield-halved');
|
||||
} else if (!decreasing && index < newCurrent) {
|
||||
armorSlot.classList.add('fa-shield');
|
||||
armorSlot.classList.remove('fa-shield-halved');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static async #toggleResourceManagement(event, button) {
|
||||
event.stopPropagation();
|
||||
const existingTooltip = document.body.querySelector('.locked-tooltip .resource-management-container');
|
||||
|
|
@ -1145,11 +985,12 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
);
|
||||
|
||||
const target = button.closest('.resource-section');
|
||||
|
||||
game.tooltip.dismissLockedTooltips();
|
||||
game.tooltip.activate(target, {
|
||||
html,
|
||||
locked: true,
|
||||
cssClass: 'bordered-tooltip dh-style',
|
||||
cssClass: 'bordered-tooltip',
|
||||
direction: 'DOWN',
|
||||
noOffset: true
|
||||
});
|
||||
|
|
|
|||
|
|
@ -11,17 +11,7 @@ export default class DhCompanionSheet extends DHBaseActorSheet {
|
|||
toggleStress: DhCompanionSheet.#toggleStress,
|
||||
actionRoll: DhCompanionSheet.#actionRoll,
|
||||
levelManagement: DhCompanionSheet.#levelManagement
|
||||
},
|
||||
contextMenus: [
|
||||
{
|
||||
handler: DHBaseActorSheet.getBaseAttackContextOptions,
|
||||
selector: '[data-item-uuid][data-type="attack"]',
|
||||
options: {
|
||||
parentClassHooks: false,
|
||||
fixed: true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
static PARTS = {
|
||||
|
|
|
|||
|
|
@ -1,136 +0,0 @@
|
|||
import DHBaseActorSheet from '../api/base-actor.mjs';
|
||||
|
||||
export default class NPCSheet extends DHBaseActorSheet {
|
||||
/** @inheritDoc */
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ['npc'],
|
||||
position: { width: 660, height: 600 },
|
||||
window: { resizable: true },
|
||||
actions: {},
|
||||
window: {
|
||||
resizable: true,
|
||||
controls: [
|
||||
{
|
||||
icon: 'fa-solid fa-signature',
|
||||
label: 'DAGGERHEART.UI.Tooltip.configureAttribution',
|
||||
action: 'editAttribution'
|
||||
}
|
||||
]
|
||||
},
|
||||
dragDrop: [
|
||||
{
|
||||
dragSelector: '[data-item-id][draggable="true"], [data-item-id] [draggable="true"]',
|
||||
dropSelector: null
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
static PARTS = {
|
||||
header: { template: 'systems/daggerheart/templates/sheets/actors/npc/header.hbs' },
|
||||
tabs: { template: 'systems/daggerheart/templates/sheets/actors/npc/navigation.hbs' },
|
||||
features: {
|
||||
template: 'systems/daggerheart/templates/sheets/actors/npc/features.hbs',
|
||||
scrollable: ['.feature-section']
|
||||
},
|
||||
notes: {
|
||||
template: 'systems/daggerheart/templates/sheets/actors/npc/notes.hbs'
|
||||
}
|
||||
};
|
||||
|
||||
/** @inheritdoc */
|
||||
static TABS = {
|
||||
primary: {
|
||||
tabs: [{ id: 'notes' }, { id: 'features' }],
|
||||
initial: 'notes',
|
||||
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
||||
}
|
||||
};
|
||||
|
||||
/** @inheritdoc */
|
||||
_prepareTabs(group) {
|
||||
const result = super._prepareTabs(group);
|
||||
if (group === 'primary') {
|
||||
result.features.empty = this.document.system.features.length === 0;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
async _preparePartContext(partId, context, options) {
|
||||
context = await super._preparePartContext(partId, context, options);
|
||||
switch (partId) {
|
||||
case 'header':
|
||||
await this._prepareHeaderContext(context, options);
|
||||
break;
|
||||
case 'features':
|
||||
await this._prepareFeaturesContext(context, options);
|
||||
break;
|
||||
case 'notes':
|
||||
await this._prepareNotesContext(context, options);
|
||||
break;
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare render context for the Header part.
|
||||
* @param {ApplicationRenderContext} context
|
||||
* @param {ApplicationRenderOptions} options
|
||||
* @returns {Promise<void>}
|
||||
* @protected
|
||||
*/
|
||||
async _prepareHeaderContext(context, _options) {
|
||||
const { system } = this.document;
|
||||
const { TextEditor } = foundry.applications.ux;
|
||||
|
||||
context.description = await TextEditor.implementation.enrichHTML(system.description, {
|
||||
secrets: this.document.isOwner,
|
||||
relativeTo: this.document
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare render context for the Features part.
|
||||
* @param {ApplicationRenderContext} context
|
||||
* @param {ApplicationRenderOptions} options
|
||||
* @returns {Promise<void>}
|
||||
* @protected
|
||||
*/
|
||||
async _prepareFeaturesContext(context, _options) {
|
||||
const featureForms = ['passive', 'action', 'reaction'];
|
||||
context.features = this.document.system.features.sort((a, b) =>
|
||||
a.system.featureForm !== b.system.featureForm
|
||||
? featureForms.indexOf(a.system.featureForm) - featureForms.indexOf(b.system.featureForm)
|
||||
: a.sort - b.sort
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare render context for the Biography part.
|
||||
* @param {ApplicationRenderContext} context
|
||||
* @param {ApplicationRenderOptions} options
|
||||
* @returns {Promise<void>}
|
||||
* @protected
|
||||
*/
|
||||
async _prepareNotesContext(context, _options) {
|
||||
const { system } = this.document;
|
||||
const { TextEditor } = foundry.applications.ux;
|
||||
|
||||
const paths = {
|
||||
notes: 'notes'
|
||||
};
|
||||
|
||||
for (const [key, path] of Object.entries(paths)) {
|
||||
const value = foundry.utils.getProperty(system, path);
|
||||
context[key] = {
|
||||
field: system.schema.getField(path),
|
||||
value,
|
||||
enriched: await TextEditor.implementation.enrichHTML(value, {
|
||||
secrets: this.document.isOwner,
|
||||
relativeTo: this.document
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
import DHBaseActorSheet from '../api/base-actor.mjs';
|
||||
import { getDocFromElement, sortBy } from '../../../helpers/utils.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';
|
||||
|
||||
export default class Party extends DHBaseActorSheet {
|
||||
|
|
@ -17,15 +18,15 @@ export default class Party extends DHBaseActorSheet {
|
|||
static DEFAULT_OPTIONS = {
|
||||
classes: ['party'],
|
||||
position: {
|
||||
width: 600,
|
||||
width: 550,
|
||||
height: 900
|
||||
},
|
||||
window: {
|
||||
resizable: true
|
||||
},
|
||||
actions: {
|
||||
openDocument: Party.#openDocument,
|
||||
deletePartyMember: Party.#deletePartyMember,
|
||||
deleteItem: Party.#deleteItem,
|
||||
toggleHope: Party.#toggleHope,
|
||||
toggleHitPoints: Party.#toggleHitPoints,
|
||||
toggleStress: Party.#toggleStress,
|
||||
|
|
@ -34,7 +35,9 @@ export default class Party extends DHBaseActorSheet {
|
|||
refeshActions: Party.#refeshActions,
|
||||
triggerRest: Party.#triggerRest,
|
||||
tagTeamRoll: Party.#tagTeamRoll,
|
||||
groupRoll: Party.#groupRoll
|
||||
groupRoll: Party.#groupRoll,
|
||||
selectRefreshable: DaggerheartMenu.selectRefreshable,
|
||||
refreshActors: DaggerheartMenu.refreshActors
|
||||
},
|
||||
dragDrop: [{ dragSelector: '[data-item-id]', dropSelector: null }]
|
||||
};
|
||||
|
|
@ -43,10 +46,16 @@ export default class Party extends DHBaseActorSheet {
|
|||
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',
|
||||
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']
|
||||
|
|
@ -57,13 +66,20 @@ export default class Party extends DHBaseActorSheet {
|
|||
/** @inheritdoc */
|
||||
static TABS = {
|
||||
primary: {
|
||||
tabs: [{ id: 'partyMembers' }, { id: 'inventory' }, { id: 'notes' }],
|
||||
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', 'npc'];
|
||||
static ALLOWED_ACTOR_TYPES = ['character', 'companion', 'adversary'];
|
||||
static DICE_ROLL_ACTOR_TYPES = ['character'];
|
||||
|
||||
async _onRender(context, options) {
|
||||
|
|
@ -76,22 +92,12 @@ export default class Party extends DHBaseActorSheet {
|
|||
/* Prepare Context */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
async _prepareContext(options) {
|
||||
const context = await super._prepareContext(options);
|
||||
const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Metagaming);
|
||||
context.showStats =
|
||||
settings.hidePartyStats === 'never' || (settings.hidePartyStats === 'players' && game.user.isGM);
|
||||
return context;
|
||||
}
|
||||
|
||||
async _preparePartContext(partId, context, options) {
|
||||
context = await super._preparePartContext(partId, context, options);
|
||||
switch (partId) {
|
||||
case 'header':
|
||||
await this._prepareHeaderContext(context, options);
|
||||
break;
|
||||
case 'partyMembers':
|
||||
await this._prepareMembersContext(context, options);
|
||||
case 'notes':
|
||||
await this._prepareNotesContext(context, options);
|
||||
break;
|
||||
|
|
@ -114,61 +120,6 @@ export default class Party extends DHBaseActorSheet {
|
|||
secrets: this.document.isOwner,
|
||||
relativeTo: this.document
|
||||
});
|
||||
context.tagTeamActive = Boolean(this.document.system.tagTeam.initiator);
|
||||
context.groupRollActive = Boolean(this.document.system.groupRoll.leader);
|
||||
}
|
||||
|
||||
async _prepareMembersContext(context, _options) {
|
||||
context.partyMembers = [];
|
||||
const traits = ['agility', 'strength', 'finesse', 'instinct', 'presence', 'knowledge'];
|
||||
for (const actor of this.document.system.partyMembers) {
|
||||
const weapons = [];
|
||||
if (actor.type === 'character') {
|
||||
if (actor.system.usedUnarmed) {
|
||||
weapons.push(actor.system.usedUnarmed);
|
||||
}
|
||||
const equipped = actor.items.filter(i => i.system.equipped && i.type === 'weapon');
|
||||
weapons.push(...sortBy(equipped, i => (i.system.secondary ? 1 : 0)));
|
||||
}
|
||||
|
||||
context.partyMembers.push({
|
||||
uuid: actor.uuid,
|
||||
img: actor.img,
|
||||
name: actor.name,
|
||||
subtitle: (() => {
|
||||
if (!['character', 'companion'].includes(actor.type)) {
|
||||
return game.i18n.format(`TYPES.Actor.${actor.type}`);
|
||||
}
|
||||
|
||||
const { value: classItem, subclass } = actor.system.class ?? {};
|
||||
const partner = actor.system.partner;
|
||||
const ancestry = actor.system.ancestry;
|
||||
const community = actor.system.community;
|
||||
if (partner || (classItem && subclass && ancestry && community)) {
|
||||
return game.i18n.format(`DAGGERHEART.ACTORS.Party.Subtitle.${actor.type}`, {
|
||||
class: classItem?.name,
|
||||
subclass: subclass?.name,
|
||||
partner: partner?.name,
|
||||
ancestry: ancestry?.name,
|
||||
community: community?.name
|
||||
});
|
||||
}
|
||||
})(),
|
||||
type: actor.type,
|
||||
resources: actor.system.resources,
|
||||
armorScore: actor.system.armorScore,
|
||||
damageThresholds: actor.system.damageThresholds,
|
||||
evasion: actor.system.evasion,
|
||||
difficulty: actor.system.difficulty,
|
||||
traits: actor.system.traits
|
||||
? traits.map(t => ({
|
||||
label: game.i18n.localize(`DAGGERHEART.CONFIG.Traits.${t}.short`),
|
||||
value: actor.system.traits[t].value
|
||||
}))
|
||||
: null,
|
||||
weapons
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -199,12 +150,6 @@ export default class Party extends DHBaseActorSheet {
|
|||
}
|
||||
}
|
||||
|
||||
static async #openDocument(_, target) {
|
||||
const uuid = target.dataset.uuid;
|
||||
const document = await foundry.utils.fromUuid(uuid);
|
||||
document?.sheet?.render(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles a hope resource value.
|
||||
* @type {ApplicationClickAction}
|
||||
|
|
@ -245,14 +190,11 @@ export default class Party extends DHBaseActorSheet {
|
|||
* Toggles a armor slot resource value.
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #toggleArmorSlot(_, target) {
|
||||
const actor = await foundry.utils.fromUuid(target.dataset.actorId);
|
||||
const { value, max } = actor.system.armorScore;
|
||||
const inputValue = Number.parseInt(target.dataset.value);
|
||||
const newValue = value >= inputValue ? inputValue - 1 : inputValue;
|
||||
const changeValue = Math.min(newValue - value, max - value);
|
||||
|
||||
await actor.system.updateArmorValue({ value: changeValue });
|
||||
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();
|
||||
}
|
||||
|
||||
|
|
@ -306,18 +248,24 @@ export default class Party extends DHBaseActorSheet {
|
|||
|
||||
static async downtimeMoveQuery({ actorId, downtimeType }) {
|
||||
const actor = await foundry.utils.fromUuid(actorId);
|
||||
if (!actor || !actor?.isOwner) return;
|
||||
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).render({ force: true });
|
||||
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 game.system.api.applications.dialogs.GroupRollDialog(this.document).render({ force: true });
|
||||
new GroupRollDialog(
|
||||
this.document.system.partyMembers.filter(x => Party.DICE_ROLL_ACTOR_TYPES.includes(x.type))
|
||||
).render({ force: true });
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
|
@ -479,22 +427,43 @@ export default class Party extends DHBaseActorSheet {
|
|||
}
|
||||
|
||||
static async #deletePartyMember(event, target) {
|
||||
const doc = await foundry.utils.fromUuid(target.closest('[data-uuid]')?.dataset.uuid);
|
||||
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.ACTORS.Party.RemoveConfirmation.title', {
|
||||
title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', {
|
||||
type: game.i18n.localize('TYPES.Actor.adversary'),
|
||||
name: doc.name
|
||||
})
|
||||
},
|
||||
content: game.i18n.format('DAGGERHEART.ACTORS.Party.RemoveConfirmation.text', { 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 newMembersList = currentMembers.filter(uuid => uuid !== doc.uuid);
|
||||
await this.document.update({ 'system.partyMembers': newMembersList });
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,15 +72,20 @@ const typeSettingsMap = {
|
|||
*/
|
||||
export default function DHApplicationMixin(Base) {
|
||||
class DHSheetV2 extends HandlebarsApplicationMixin(Base) {
|
||||
#nonHeaderAttribution = ['environment', 'ancestry', 'community', 'domainCard'];
|
||||
|
||||
/**
|
||||
* @param {DHSheetV2Configuration} [options={}]
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
super(options);
|
||||
/**
|
||||
* @type {foundry.applications.ux.DragDrop[]}
|
||||
* @private
|
||||
*/
|
||||
this._dragDrop = this._createDragDropHandlers();
|
||||
}
|
||||
|
||||
#nonHeaderAttribution = ['environment', 'ancestry', 'community', 'domainCard'];
|
||||
|
||||
/**
|
||||
* The default options for the sheet.
|
||||
* @type {DHSheetV2Configuration}
|
||||
|
|
@ -89,7 +94,7 @@ export default function DHApplicationMixin(Base) {
|
|||
classes: ['daggerheart', 'sheet', 'dh-style'],
|
||||
actions: {
|
||||
triggerContextMenu: DHSheetV2.#triggerContextMenu,
|
||||
createDoc: DHSheetV2.#onCreateDoc,
|
||||
createDoc: DHSheetV2.#createDoc,
|
||||
editDoc: DHSheetV2.#editDoc,
|
||||
deleteDoc: DHSheetV2.#deleteDoc,
|
||||
toChat: DHSheetV2.#toChat,
|
||||
|
|
@ -97,8 +102,8 @@ export default function DHApplicationMixin(Base) {
|
|||
viewItem: DHSheetV2.#viewItem,
|
||||
toggleEffect: DHSheetV2.#toggleEffect,
|
||||
toggleExtended: DHSheetV2.#toggleExtended,
|
||||
addNewItem: DHSheetV2.#onAddNewItem,
|
||||
browseItem: DHSheetV2.#onBrowseItem,
|
||||
addNewItem: DHSheetV2.#addNewItem,
|
||||
browseItem: DHSheetV2.#browseItem,
|
||||
editAttribution: DHSheetV2.#editAttribution
|
||||
},
|
||||
contextMenus: [
|
||||
|
|
@ -172,6 +177,7 @@ export default function DHApplicationMixin(Base) {
|
|||
/**@inheritdoc */
|
||||
_attachPartListeners(partId, htmlElement, options) {
|
||||
super._attachPartListeners(partId, htmlElement, options);
|
||||
this._dragDrop.forEach(d => d.bind(htmlElement));
|
||||
|
||||
// Handle delta inputs
|
||||
for (const deltaInput of htmlElement.querySelectorAll('input[data-allow-delta]')) {
|
||||
|
|
@ -284,16 +290,6 @@ export default function DHApplicationMixin(Base) {
|
|||
async _onRender(context, options) {
|
||||
await super._onRender(context, options);
|
||||
this._createTagifyElements(this.options.tagifyConfigs);
|
||||
|
||||
for (const d of this.options.dragDrop) {
|
||||
new foundry.applications.ux.DragDrop.implementation({
|
||||
...d,
|
||||
callbacks: {
|
||||
dragstart: this._onDragStart.bind(this),
|
||||
drop: this._onDrop.bind(this)
|
||||
}
|
||||
}).bind(this.element);
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
|
@ -354,6 +350,21 @@ export default function DHApplicationMixin(Base) {
|
|||
/* Drag and Drop */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Creates drag-drop handlers from the configured options.
|
||||
* @returns {foundry.applications.ux.DragDrop[]}
|
||||
* @private
|
||||
*/
|
||||
_createDragDropHandlers() {
|
||||
return this.options.dragDrop.map(d => {
|
||||
d.callbacks = {
|
||||
dragstart: this._onDragStart.bind(this),
|
||||
drop: this._onDrop.bind(this)
|
||||
};
|
||||
return new foundry.applications.ux.DragDrop.implementation(d);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle dragStart event.
|
||||
* @param {DragEvent} event
|
||||
|
|
@ -418,26 +429,26 @@ export default function DHApplicationMixin(Base) {
|
|||
/**@type {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} */
|
||||
const options = [
|
||||
{
|
||||
label: 'disableEffect',
|
||||
name: 'disableEffect',
|
||||
icon: 'fa-solid fa-lightbulb',
|
||||
visible: element => {
|
||||
condition: element => {
|
||||
const target = element.closest('[data-item-uuid]');
|
||||
return !target.dataset.disabled && target.dataset.itemType !== 'beastform';
|
||||
},
|
||||
onClick: async (_, target) => (await getDocFromElement(target)).update({ disabled: true })
|
||||
callback: async target => (await getDocFromElement(target)).update({ disabled: true })
|
||||
},
|
||||
{
|
||||
label: 'enableEffect',
|
||||
name: 'enableEffect',
|
||||
icon: 'fa-regular fa-lightbulb',
|
||||
visible: element => {
|
||||
condition: element => {
|
||||
const target = element.closest('[data-item-uuid]');
|
||||
return target.dataset.disabled && target.dataset.itemType !== 'beastform';
|
||||
},
|
||||
onClick: async (_, target) => (await getDocFromElement(target)).update({ disabled: false })
|
||||
callback: async target => (await getDocFromElement(target)).update({ disabled: false })
|
||||
}
|
||||
].map(option => ({
|
||||
...option,
|
||||
label: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.label}`,
|
||||
name: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.name}`,
|
||||
icon: `<i class="${option.icon}"></i>`
|
||||
}));
|
||||
|
||||
|
|
@ -468,34 +479,29 @@ export default function DHApplicationMixin(Base) {
|
|||
_getContextMenuCommonOptions({ usable = false, toChat = false, deletable = true }) {
|
||||
const options = [
|
||||
{
|
||||
label: 'CONTROLS.CommonEdit',
|
||||
name: 'CONTROLS.CommonEdit',
|
||||
icon: 'fa-solid fa-pen-to-square',
|
||||
visible: target => {
|
||||
condition: target => {
|
||||
const { dataset } = target.closest('[data-item-uuid]');
|
||||
const doc = getDocFromElementSync(target);
|
||||
return (
|
||||
(!dataset.noCompendiumEdit && !doc) ||
|
||||
(doc?.isOwner && (!doc?.hasOwnProperty('systemPath') || doc?.inCollection))
|
||||
(doc && (!doc?.hasOwnProperty('systemPath') || doc?.inCollection))
|
||||
);
|
||||
},
|
||||
onClick: async (_, target) => {
|
||||
return (await getDocFromElement(target)).sheet.render({ force: true });
|
||||
}
|
||||
callback: async target => (await getDocFromElement(target)).sheet.render({ force: true })
|
||||
}
|
||||
];
|
||||
|
||||
if (usable) {
|
||||
options.unshift({
|
||||
label: 'DAGGERHEART.GENERAL.damage',
|
||||
name: 'DAGGERHEART.GENERAL.damage',
|
||||
icon: 'fa-solid fa-explosion',
|
||||
visible: target => {
|
||||
condition: target => {
|
||||
const doc = getDocFromElementSync(target);
|
||||
const hasDamage =
|
||||
!foundry.utils.isEmpty(doc?.system?.attack?.damage.parts) ||
|
||||
!foundry.utils.isEmpty(doc?.damage?.parts);
|
||||
return doc?.isOwner && hasDamage;
|
||||
return doc?.system?.attack?.damage.parts.length || doc?.damage?.parts.length;
|
||||
},
|
||||
onClick: async (event, target) => {
|
||||
callback: async (target, event) => {
|
||||
const doc = await getDocFromElement(target),
|
||||
action = doc?.system?.attack ?? doc;
|
||||
const config = action.prepareConfig(event);
|
||||
|
|
@ -509,33 +515,32 @@ export default function DHApplicationMixin(Base) {
|
|||
});
|
||||
|
||||
options.unshift({
|
||||
label: 'DAGGERHEART.APPLICATIONS.ContextMenu.useItem',
|
||||
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.useItem',
|
||||
icon: 'fa-solid fa-burst',
|
||||
visible: target => {
|
||||
condition: target => {
|
||||
const doc = getDocFromElementSync(target);
|
||||
return doc?.isOwner && !(doc.type === 'domainCard' && doc.system.inVault);
|
||||
return doc && !(doc.type === 'domainCard' && doc.system.inVault);
|
||||
},
|
||||
onClick: async (event, target) => (await getDocFromElement(target)).use(event)
|
||||
callback: async (target, event) => (await getDocFromElement(target)).use(event)
|
||||
});
|
||||
}
|
||||
|
||||
if (toChat)
|
||||
options.push({
|
||||
label: 'DAGGERHEART.APPLICATIONS.ContextMenu.sendToChat',
|
||||
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.sendToChat',
|
||||
icon: 'fa-solid fa-message',
|
||||
onClick: async (_, target) => (await getDocFromElement(target)).toChat(this.document.uuid)
|
||||
callback: async target => (await getDocFromElement(target)).toChat(this.document.uuid)
|
||||
});
|
||||
|
||||
if (deletable)
|
||||
options.push({
|
||||
label: 'CONTROLS.CommonDelete',
|
||||
name: 'CONTROLS.CommonDelete',
|
||||
icon: 'fa-solid fa-trash',
|
||||
visible: element => {
|
||||
condition: element => {
|
||||
const target = element.closest('[data-item-uuid]');
|
||||
const doc = getDocFromElementSync(target);
|
||||
return doc?.isOwner !== false && target.dataset.itemType !== 'beastform';
|
||||
return target.dataset.itemType !== 'beastform';
|
||||
},
|
||||
onClick: async (event, target) => {
|
||||
callback: async (target, event) => {
|
||||
const doc = await getDocFromElement(target);
|
||||
if (event.shiftKey) return doc.delete();
|
||||
else return doc.deleteDialog();
|
||||
|
|
@ -641,7 +646,7 @@ export default function DHApplicationMixin(Base) {
|
|||
/* Application Clicks Actions */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
static async #onAddNewItem(event, target) {
|
||||
static async #addNewItem(event, target) {
|
||||
const createChoice = await foundry.applications.api.DialogV2.wait({
|
||||
classes: ['dh-style', 'two-big-buttons'],
|
||||
buttons: [
|
||||
|
|
@ -660,11 +665,11 @@ export default function DHApplicationMixin(Base) {
|
|||
|
||||
if (!createChoice) return;
|
||||
|
||||
if (createChoice === 'browse') return DHSheetV2.#onBrowseItem.call(this, event, target);
|
||||
else return DHSheetV2.#onCreateDoc.call(this, event, target);
|
||||
if (createChoice === 'browse') return DHSheetV2.#browseItem.call(this, event, target);
|
||||
else return DHSheetV2.#createDoc.call(this, event, target);
|
||||
}
|
||||
|
||||
static async #onBrowseItem(_event, target) {
|
||||
static async #browseItem(event, target) {
|
||||
const type = target.dataset.compendium ?? target.dataset.type;
|
||||
|
||||
const presets = {
|
||||
|
|
@ -715,17 +720,17 @@ export default function DHApplicationMixin(Base) {
|
|||
* Create an embedded document.
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #onCreateDoc(event, target) {
|
||||
static async #createDoc(event, target) {
|
||||
const { documentClass, type, inVault, disabled } = target.dataset;
|
||||
const parentIsItem = this.document.documentName === 'Item';
|
||||
const featureOnCharacter = this.document.parent?.type === 'character' && type === 'feature';
|
||||
const parent = featureOnCharacter
|
||||
? this.document.parent
|
||||
: parentIsItem && documentClass === 'Item'
|
||||
? type === 'action'
|
||||
? this.document.system
|
||||
: null
|
||||
: this.document;
|
||||
? type === 'action'
|
||||
? this.document.system
|
||||
: null
|
||||
: this.document;
|
||||
|
||||
let systemData = {};
|
||||
if (featureOnCharacter) {
|
||||
|
|
@ -737,21 +742,15 @@ export default function DHApplicationMixin(Base) {
|
|||
|
||||
const cls =
|
||||
type === 'action' ? game.system.api.models.actions.actionsTypes.base : getDocumentClass(documentClass);
|
||||
|
||||
const data = {
|
||||
name: cls.defaultName({ type, parent }),
|
||||
type,
|
||||
system: systemData
|
||||
};
|
||||
if (inVault) data['system.inVault'] = true;
|
||||
if (disabled) data.disabled = true;
|
||||
|
||||
if (type === 'domainCard') {
|
||||
if (parent?.system.domains?.length) data.system.domain = parent.system.domains[0];
|
||||
if (inVault) data.system.inVault = true;
|
||||
} else if (type === 'weapon') {
|
||||
// Passing an empty system object to weapon causes validation failure due to attack action initialization
|
||||
// todo: determine why, fix it at its source, then remove this fallback
|
||||
delete data.system;
|
||||
if (type === 'domainCard' && parent?.system.domains?.length) {
|
||||
data.system.domain = parent.system.domains[0];
|
||||
}
|
||||
|
||||
const doc = await cls.create(data, { parent, renderSheet: !event.shiftKey });
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
|||
.hideAttribution;
|
||||
|
||||
// Prepare inventory data
|
||||
if (this.document.system.metadata.hasInventory) {
|
||||
if (['party', 'character'].includes(this.document.type)) {
|
||||
context.inventory = {
|
||||
currencies: {},
|
||||
weapons: this.document.itemTypes.weapon.sort((a, b) => a.sort - b.sort),
|
||||
|
|
@ -160,21 +160,12 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
|||
inactives: []
|
||||
};
|
||||
|
||||
for (const effect of this.actor.allApplicableEffects({ noTransferArmor: true })) {
|
||||
for (const effect of this.actor.allApplicableEffects()) {
|
||||
const list = effect.active ? context.effects.actives : context.effects.inactives;
|
||||
list.push(effect);
|
||||
}
|
||||
}
|
||||
|
||||
/** Add support for input content editables */
|
||||
_toggleDisabled(disabled) {
|
||||
super._toggleDisabled(disabled);
|
||||
const form = this.form;
|
||||
for (const element of form.querySelectorAll('.input[contenteditable]')) {
|
||||
element.classList.toggle('disabled', disabled);
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Context Menu */
|
||||
/* -------------------------------------------- */
|
||||
|
|
@ -189,43 +180,6 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
|||
return this._getContextMenuCommonOptions.call(this, { usable: true, toChat: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the set of ContextMenu options for the base attack.
|
||||
* @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - The Array of context options passed to the ContextMenu instance
|
||||
* @this {CharacterSheet}
|
||||
* @protected
|
||||
*/
|
||||
static getBaseAttackContextOptions() {
|
||||
/**@type {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} */
|
||||
return [
|
||||
{
|
||||
label: 'DAGGERHEART.CONFIG.RollTypes.attack.name',
|
||||
icon: 'fa-solid fa-burst',
|
||||
onClick: async (event, target) => (await getDocFromElement(target)).use(event)
|
||||
},
|
||||
{
|
||||
label: 'DAGGERHEART.GENERAL.damage',
|
||||
icon: 'fa-solid fa-explosion',
|
||||
onClick: async (event, target) => {
|
||||
const doc = await getDocFromElement(target),
|
||||
action = doc?.system?.attack ?? doc;
|
||||
const config = action.prepareConfig(event);
|
||||
config.effects = await game.system.api.data.actions.actionsTypes.base.getEffects(
|
||||
this.document,
|
||||
doc
|
||||
);
|
||||
config.hasRoll = false;
|
||||
return action && action.workflow.get('damage').execute(config, null, true);
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'DAGGERHEART.APPLICATIONS.ContextMenu.sendToChat',
|
||||
icon: 'fa-solid fa-message',
|
||||
onClick: async (_, target) => (await getDocFromElement(target)).toChat(this.document.uuid)
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Application Listener Actions */
|
||||
/* -------------------------------------------- */
|
||||
|
|
@ -274,6 +228,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
|||
'systems/daggerheart/templates/ui/chat/action.hbs',
|
||||
systemData
|
||||
),
|
||||
title: game.i18n.localize('DAGGERHEART.ACTIONS.Config.displayInChat'),
|
||||
speaker: cls.getSpeaker(),
|
||||
flags: {
|
||||
daggerheart: {
|
||||
|
|
@ -329,7 +284,11 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
|||
async _onDropItem(event, item) {
|
||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||
const originActor = item.actor;
|
||||
if (!originActor || originActor.uuid === this.document.uuid || !this.document.system.metadata.hasInventory) {
|
||||
if (
|
||||
item.actor?.uuid === this.document.uuid ||
|
||||
!originActor ||
|
||||
!['character', 'party'].includes(this.document.type)
|
||||
) {
|
||||
return super._onDropItem(event, item);
|
||||
}
|
||||
|
||||
|
|
@ -344,79 +303,47 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
|||
);
|
||||
}
|
||||
|
||||
// Perform the actual transfer, showing a dialog when doing it
|
||||
const availableQuantity = Math.max(1, item.system.quantity);
|
||||
const actorItem = originActor.items.get(data.originId) ?? item;
|
||||
if (availableQuantity > 1) {
|
||||
const quantityTransferred = await game.system.api.applications.dialogs.ItemTransferDialog.configure({
|
||||
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
|
||||
});
|
||||
return this.#transferItem(actorItem, quantityTransferred);
|
||||
|
||||
if (quantityTransfered) {
|
||||
const existingItem = this.document.items.find(x => itemIsIdentical(x, item));
|
||||
if (existingItem) {
|
||||
await existingItem.update({
|
||||
'system.quantity': existingItem.system.quantity + quantityTransfered
|
||||
});
|
||||
} else {
|
||||
const createData = item.toObject();
|
||||
await this.document.createEmbeddedDocuments('Item', [
|
||||
{
|
||||
...createData,
|
||||
system: {
|
||||
...createData.system,
|
||||
quantity: quantityTransfered
|
||||
}
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
if (quantityTransfered === actorItem.system.quantity) {
|
||||
await originActor.deleteEmbeddedDocuments('Item', [data.originId]);
|
||||
} else {
|
||||
await actorItem.update({
|
||||
'system.quantity': actorItem.system.quantity - quantityTransfered
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return this.#transferItem(actorItem, availableQuantity);
|
||||
await this.document.createEmbeddedDocuments('Item', [item.toObject()]);
|
||||
await originActor.deleteEmbeddedDocuments('Item', [data.originId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to perform the actual transfer of an item to this actor, including stack/unstack logic based on target quantifiability.
|
||||
* Make sure item is the actor item before calling this method or there will be issues
|
||||
*/
|
||||
async #transferItem(item, quantity) {
|
||||
const originActor = item.actor;
|
||||
const targetActor = this.document;
|
||||
const allowStacking = targetActor.system.metadata.quantifiable?.includes(item.type);
|
||||
|
||||
const batch = [];
|
||||
|
||||
// First add/update the item to the target actor
|
||||
const existing = allowStacking ? targetActor.items.find(x => itemIsIdentical(x, item)) : null;
|
||||
if (existing) {
|
||||
batch.push({
|
||||
action: 'update',
|
||||
documentName: 'Item',
|
||||
parent: targetActor,
|
||||
updates: [{ _id: existing.id, 'system.quantity': existing.system.quantity + quantity }]
|
||||
});
|
||||
} else {
|
||||
const itemsToCreate = [];
|
||||
if (allowStacking) {
|
||||
itemsToCreate.push(foundry.utils.mergeObject(item.toObject(true), { system: { quantity } }));
|
||||
} else {
|
||||
const createData = new Array(Math.max(1, quantity))
|
||||
.fill(0)
|
||||
.map(() => foundry.utils.mergeObject(item.toObject(), { system: { quantity: 1 } }));
|
||||
itemsToCreate.push(...createData);
|
||||
}
|
||||
batch.push({
|
||||
action: 'create',
|
||||
documentName: 'Item',
|
||||
parent: targetActor,
|
||||
data: itemsToCreate
|
||||
});
|
||||
}
|
||||
|
||||
// Remove the item from the original actor (by either deleting it, or updating its quantity)
|
||||
if (quantity >= item.system.quantity) {
|
||||
batch.push({
|
||||
action: 'delete',
|
||||
documentName: 'Item',
|
||||
parent: originActor,
|
||||
ids: [item.id]
|
||||
});
|
||||
} else {
|
||||
batch.push({
|
||||
action: 'update',
|
||||
documentName: 'Item',
|
||||
parent: originActor,
|
||||
updates: [{ _id: item.id, 'system.quantity': item.system.quantity - quantity }]
|
||||
});
|
||||
}
|
||||
|
||||
return foundry.documents.modifyBatch(batch);
|
||||
}
|
||||
|
||||
/**
|
||||
* On dragStart on the item.
|
||||
* @param {DragEvent} event - The drag event
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
|||
options.push({
|
||||
name: 'CONTROLS.CommonDelete',
|
||||
icon: '<i class="fa-solid fa-trash"></i>',
|
||||
onClick: async (_, target) => {
|
||||
callback: async target => {
|
||||
const feature = await getDocFromElement(target);
|
||||
if (!feature) return;
|
||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||
|
|
|
|||
|
|
@ -29,6 +29,16 @@ export default function ItemAttachmentSheet(Base) {
|
|||
}
|
||||
};
|
||||
|
||||
async _preparePartContext(partId, context) {
|
||||
await super._preparePartContext(partId, context);
|
||||
|
||||
if (partId === 'attachments') {
|
||||
context.attachedItems = await prepareAttachmentContext(this.document);
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
async _onDrop(event) {
|
||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||
|
||||
|
|
|
|||
|
|
@ -31,12 +31,11 @@ export default class AncestrySheet extends DHHeritageSheet {
|
|||
if (data.type === 'ActiveEffect') return super._onDrop(event);
|
||||
|
||||
const target = event.target.closest('fieldset.drop-section');
|
||||
if (target) {
|
||||
const typeField =
|
||||
this.document.system[target.dataset.type === 'primary' ? 'primaryFeature' : 'secondaryFeature'];
|
||||
if (!typeField) {
|
||||
super._onDrop(event);
|
||||
}
|
||||
const typeField =
|
||||
this.document.system[target.dataset.type === 'primary' ? 'primaryFeature' : 'secondaryFeature'];
|
||||
|
||||
if (!typeField) {
|
||||
super._onDrop(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,15 +47,6 @@ export default class ArmorSheet extends ItemAttachmentSheet(DHBaseItemSheet) {
|
|||
return context;
|
||||
}
|
||||
|
||||
async updateArmorEffect(event) {
|
||||
const value = Number.parseInt(event.target.value);
|
||||
const armorEffect = this.document.system.armorEffect;
|
||||
if (Number.isNaN(value) || !armorEffect) return;
|
||||
|
||||
await armorEffect.system.armorChange.updateArmorMax(value);
|
||||
this.render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback function used by `tagifyElement`.
|
||||
* @param {Array<Object>} selectedOptions - The currently selected tag objects.
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ export default class BeastformSheet extends DHBaseItemSheet {
|
|||
|
||||
async advantageOnRemove(event) {
|
||||
await this.document.update({
|
||||
[`system.advantageOn.${event.detail.data.value}`]: _del
|
||||
[`system.advantageOn.-=${event.detail.data.value}`]: null
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,10 +104,9 @@ export default class ClassSheet extends DHBaseItemSheet {
|
|||
}
|
||||
|
||||
/**@inheritdoc */
|
||||
async _prepareContext(options) {
|
||||
const context = await super._prepareContext(options);
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
context.domains = this.document.system.domains;
|
||||
context.subclasses = await this.document.system.fetchSubclasses();
|
||||
return context;
|
||||
}
|
||||
|
||||
|
|
@ -129,8 +128,20 @@ export default class ClassSheet extends DHBaseItemSheet {
|
|||
const item = await fromUuid(data.uuid);
|
||||
const itemType = data.type === 'ActiveEffect' ? data.type : item.type;
|
||||
const target = event.target.closest('fieldset.drop-section');
|
||||
|
||||
if (['feature', 'ActiveEffect'].includes(itemType)) {
|
||||
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({
|
||||
'system.subclasses': [...this.document.system.subclasses.map(x => x.uuid), item.uuid]
|
||||
});
|
||||
} else if (['feature', 'ActiveEffect'].includes(itemType)) {
|
||||
super._onDrop(event);
|
||||
} else if (this.document.parent?.type !== 'character') {
|
||||
if (itemType === 'weapon') {
|
||||
|
|
@ -189,6 +200,12 @@ export default class ClassSheet extends DHBaseItemSheet {
|
|||
static async #removeItemFromCollection(_event, element) {
|
||||
const { uuid, target } = element.dataset;
|
||||
const prop = foundry.utils.getProperty(this.document.system, target);
|
||||
|
||||
if (target === 'subclasses') {
|
||||
const subclass = await foundry.utils.fromUuid(uuid);
|
||||
await subclass?.update({ 'system.linkedClass': null });
|
||||
}
|
||||
|
||||
await this.document.update({ [`system.${target}`]: prop.filter(i => i && i.uuid !== uuid).map(x => x.uuid) });
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ export default class FeatureSheet extends DHBaseItemSheet {
|
|||
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
||||
}
|
||||
};
|
||||
//Might be wrong location but testing out if here is okay.
|
||||
//Might be wrong location but testing out if here is okay.
|
||||
/**@override */
|
||||
async _prepareContext(options) {
|
||||
const context = await super._prepareContext(options);
|
||||
|
|
|
|||
|
|
@ -40,36 +40,4 @@ export default class SubclassSheet extends DHBaseItemSheet {
|
|||
get relatedDocs() {
|
||||
return this.document.system.features.map(x => x.item);
|
||||
}
|
||||
|
||||
async _prepareContext(options) {
|
||||
const context = await super._prepareContext(options);
|
||||
if (this.document.system.linkedClass) {
|
||||
const classData = await fromUuid(this.document.system.linkedClass);
|
||||
context.class = classData ?? {
|
||||
name: _loc('DAGGERHEART.GENERAL.missingX', { x: _loc('TYPES.Item.class') }),
|
||||
missing: true
|
||||
};
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
async _onDrop(event) {
|
||||
event.stopPropagation();
|
||||
const data = TextEditor.getDragEventData(event);
|
||||
const item = await fromUuid(data.uuid);
|
||||
const itemType = data.type === 'ActiveEffect' ? data.type : item.type;
|
||||
if (itemType === 'class') {
|
||||
const uuid = item._stats.compendiumSource ?? item.uuid;
|
||||
if (this.document.system.linkedClass !== uuid) {
|
||||
await this.document.update({ 'system.linkedClass': uuid });
|
||||
// Re-render all class sheets for instant feedback
|
||||
for (const app of foundry.applications.instances.values()) {
|
||||
if (app.document?.type === 'class') app.render();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
return super._onDrop(event);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,15 +108,14 @@ export default class DhRollTableSheet extends foundry.applications.sheets.RollTa
|
|||
getSystemFlagUpdate() {
|
||||
const deleteUpdate = Object.keys(this.document._source.flags.daggerheart?.altFormula ?? {}).reduce(
|
||||
(acc, formulaKey) => {
|
||||
if (!this.daggerheartFlag.altFormula[formulaKey]) acc.altFormula[formulaKey] = _del;
|
||||
if (!this.daggerheartFlag.altFormula[formulaKey]) acc.altFormula[`-=${formulaKey}`] = null;
|
||||
|
||||
return acc;
|
||||
},
|
||||
{ altFormula: {} }
|
||||
);
|
||||
|
||||
const flagData = this.daggerheartFlag.toObject();
|
||||
return { ...flagData, altFormula: { ...flagData.altFormula, ...deleteUpdate.altFormula } };
|
||||
return { ['flags.daggerheart']: foundry.utils.mergeObject(this.daggerheartFlag.toObject(), deleteUpdate) };
|
||||
}
|
||||
|
||||
static async #addFormula() {
|
||||
|
|
@ -128,7 +127,7 @@ export default class DhRollTableSheet extends foundry.applications.sheets.RollTa
|
|||
|
||||
static async #removeFormula(_event, target) {
|
||||
await this.daggerheartFlag.updateSource({
|
||||
[`altFormula.${target.dataset.key}`]: _del
|
||||
[`altFormula.-=${target.dataset.key}`]: null
|
||||
});
|
||||
this.render({ internalRefresh: true });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,52 @@
|
|||
export default class DhSidebar extends foundry.applications.sidebar.Sidebar {
|
||||
static buildTabs() {
|
||||
const { settings, ...tabs } = super.TABS;
|
||||
return {
|
||||
...tabs,
|
||||
daggerheartMenu: {
|
||||
tooltip: 'DAGGERHEART.UI.Sidebar.daggerheartMenu.title',
|
||||
img: 'systems/daggerheart/assets/logos/FoundryBorneLogoWhite.svg',
|
||||
gmOnly: true
|
||||
},
|
||||
settings
|
||||
};
|
||||
}
|
||||
|
||||
/** @override */
|
||||
static TABS = DhSidebar.buildTabs();
|
||||
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 = {
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ export default class DhActorDirectory extends foundry.applications.sidebar.tabs.
|
|||
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;
|
||||
? game.i18n.localize(environmentTypes[document.system.type]?.label ?? 'TYPES.Actor.environment')
|
||||
: null;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -46,67 +46,50 @@ export default class DhActorDirectory extends foundry.applications.sidebar.tabs.
|
|||
|
||||
_getEntryContextOptions() {
|
||||
const options = super._getEntryContextOptions();
|
||||
options.push(
|
||||
{
|
||||
label: 'DAGGERHEART.UI.Sidebar.actorDirectory.duplicateToNewTier',
|
||||
icon: `<i class="fa-solid fa-arrow-trend-up" inert></i>`,
|
||||
visible: li => {
|
||||
const actor = game.actors.get(li.dataset.entryId);
|
||||
return actor?.type === 'adversary' && actor.system.type !== 'social';
|
||||
},
|
||||
callback: async li => {
|
||||
const actor = game.actors.get(li.dataset.entryId);
|
||||
if (!actor) throw new Error('Unexpected missing actor');
|
||||
|
||||
const tiers = [1, 2, 3, 4].filter(t => t !== actor.system.tier);
|
||||
const content = document.createElement('div');
|
||||
const select = document.createElement('select');
|
||||
select.name = 'tier';
|
||||
select.append(
|
||||
...tiers.map(t => {
|
||||
const option = document.createElement('option');
|
||||
option.value = t;
|
||||
option.textContent = game.i18n.localize(`DAGGERHEART.GENERAL.Tiers.${t}`);
|
||||
return option;
|
||||
})
|
||||
);
|
||||
content.append(select);
|
||||
|
||||
const tier = await foundry.applications.api.Dialog.input({
|
||||
classes: ['dh-style', 'dialog'],
|
||||
window: { title: 'DAGGERHEART.UI.Sidebar.actorDirectory.pickTierTitle' },
|
||||
content,
|
||||
ok: {
|
||||
label: 'DAGGERHEART.UI.Sidebar.actorDirectory.createAdversary',
|
||||
callback: (event, button, dialog) => Number(button.form.elements.tier.value)
|
||||
}
|
||||
});
|
||||
|
||||
if (tier === actor.system.tier) {
|
||||
ui.notifications.warn('This actor is already at this tier');
|
||||
} else if (tier) {
|
||||
const source = actor.system.adjustForTier(tier);
|
||||
await Actor.create(source);
|
||||
ui.notifications.info(`Tier ${tier} ${actor.name} created`);
|
||||
}
|
||||
}
|
||||
options.push({
|
||||
name: 'DAGGERHEART.UI.Sidebar.actorDirectory.duplicateToNewTier',
|
||||
icon: `<i class="fa-solid fa-arrow-trend-up" inert></i>`,
|
||||
condition: li => {
|
||||
const actor = game.actors.get(li.dataset.entryId);
|
||||
return actor?.type === 'adversary' && actor.system.type !== 'social';
|
||||
},
|
||||
{
|
||||
label: 'DAGGERHEART.UI.Sidebar.actorDirectory.activateParty',
|
||||
icon: `<i class="fa-regular fa-square"></i>`,
|
||||
visible: li => {
|
||||
const actor = game.actors.get(li.dataset.entryId);
|
||||
return actor && actor.type === 'party' && !actor.system.active;
|
||||
},
|
||||
callback: async li => {
|
||||
const actor = game.actors.get(li.dataset.entryId);
|
||||
if (!actor) throw new Error('Unexpected missing actor');
|
||||
callback: async li => {
|
||||
const actor = game.actors.get(li.dataset.entryId);
|
||||
if (!actor) throw new Error('Unexpected missing actor');
|
||||
|
||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.ActiveParty, actor.id);
|
||||
ui.actors.render();
|
||||
const tiers = [1, 2, 3, 4].filter(t => t !== actor.system.tier);
|
||||
const content = document.createElement('div');
|
||||
const select = document.createElement('select');
|
||||
select.name = 'tier';
|
||||
select.append(
|
||||
...tiers.map(t => {
|
||||
const option = document.createElement('option');
|
||||
option.value = t;
|
||||
option.textContent = game.i18n.localize(`DAGGERHEART.GENERAL.Tiers.${t}`);
|
||||
return option;
|
||||
})
|
||||
);
|
||||
content.append(select);
|
||||
|
||||
const tier = await foundry.applications.api.Dialog.input({
|
||||
classes: ['dh-style', 'dialog'],
|
||||
window: { title: 'DAGGERHEART.UI.Sidebar.actorDirectory.pickTierTitle' },
|
||||
content,
|
||||
ok: {
|
||||
label: 'DAGGERHEART.UI.Sidebar.actorDirectory.createAdversary',
|
||||
callback: (event, button, dialog) => Number(button.form.elements.tier.value)
|
||||
}
|
||||
});
|
||||
|
||||
if (tier === actor.system.tier) {
|
||||
ui.notifications.warn('This actor is already at this tier');
|
||||
} else if (tier) {
|
||||
const source = actor.system.adjustForTier(tier);
|
||||
await Actor.create(source);
|
||||
ui.notifications.info(`Tier ${tier} ${actor.name} created`);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
return options;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,8 +31,7 @@ export default class DaggerheartMenu extends HandlebarsApplicationMixin(Abstract
|
|||
},
|
||||
actions: {
|
||||
selectRefreshable: DaggerheartMenu.#selectRefreshable,
|
||||
refreshActors: DaggerheartMenu.#refreshActors,
|
||||
createFallCollisionDamage: DaggerheartMenu.#createFallCollisionDamage
|
||||
refreshActors: DaggerheartMenu.#refreshActors
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -51,7 +50,6 @@ export default class DaggerheartMenu extends HandlebarsApplicationMixin(Abstract
|
|||
const context = await super._prepareContext(options);
|
||||
context.refreshables = this.refreshSelections;
|
||||
context.disableRefresh = Object.values(this.refreshSelections).every(x => !x.selected);
|
||||
context.fallAndCollision = CONFIG.DH.GENERAL.fallAndCollisionDamage;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
|
@ -73,22 +71,4 @@ export default class DaggerheartMenu extends HandlebarsApplicationMixin(Abstract
|
|||
this.refreshSelections = DaggerheartMenu.defaultRefreshSelections();
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async #createFallCollisionDamage(_event, button) {
|
||||
const data = CONFIG.DH.GENERAL.fallAndCollisionDamage[button.dataset.key];
|
||||
const roll = new Roll(data.damageFormula);
|
||||
await roll.evaluate();
|
||||
|
||||
/* class BaseRoll needed to get rendered by foundryRoll.hbs */
|
||||
const rollJSON = roll.toJSON();
|
||||
rollJSON.class = 'BaseRoll';
|
||||
|
||||
foundry.documents.ChatMessage.implementation.create({
|
||||
title: game.i18n.localize(data.chatTitle),
|
||||
author: game.user.id,
|
||||
speaker: foundry.documents.ChatMessage.implementation.getSpeaker(),
|
||||
rolls: [rollJSON],
|
||||
sound: CONFIG.sounds.dice
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,4 +7,3 @@ export { default as DhFearTracker } from './fearTracker.mjs';
|
|||
export { default as DhHotbar } from './hotbar.mjs';
|
||||
export { default as DhSceneNavigation } from './sceneNavigation.mjs';
|
||||
export { ItemBrowser } from './itemBrowser.mjs';
|
||||
export { default as DhProgress } from './progress.mjs';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { enrichedDualityRoll } from '../../enrichers/DualityRollEnricher.mjs';
|
||||
import { enrichedFateRoll, getFateTypeData } from '../../enrichers/FateRollEnricher.mjs';
|
||||
import { getCommandTarget, rollCommandToJSON } from '../../helpers/utils.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 {
|
||||
constructor(options) {
|
||||
|
|
@ -22,114 +21,35 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
classes: ['daggerheart']
|
||||
};
|
||||
|
||||
static CHAT_COMMANDS = {
|
||||
...super.CHAT_COMMANDS,
|
||||
dr: {
|
||||
rgx: /^(?:\/dr)((?:\s)[^]*)?/,
|
||||
fn: (_, match) => {
|
||||
const argString = match[1]?.trim();
|
||||
const result = argString ? rollCommandToJSON(argString) : { result: {} };
|
||||
if (!result) {
|
||||
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.dualityParsing'));
|
||||
return false;
|
||||
}
|
||||
|
||||
const { result: rollCommand, flavor } = result;
|
||||
|
||||
const reaction = rollCommand.reaction;
|
||||
const traitValue = rollCommand.trait?.toLowerCase();
|
||||
const advantage = rollCommand.advantage
|
||||
? CONFIG.DH.ACTIONS.advantageState.advantage.value
|
||||
: rollCommand.disadvantage
|
||||
? CONFIG.DH.ACTIONS.advantageState.disadvantage.value
|
||||
: undefined;
|
||||
const difficulty = rollCommand.difficulty;
|
||||
const grantResources = rollCommand.grantResources;
|
||||
|
||||
const target = getCommandTarget({ allowNull: true });
|
||||
const title =
|
||||
(flavor ?? traitValue)
|
||||
? game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
||||
ability: game.i18n.localize(CONFIG.DH.ACTOR.abilities[traitValue].label)
|
||||
})
|
||||
: game.i18n.localize('DAGGERHEART.GENERAL.duality');
|
||||
|
||||
enrichedDualityRoll({
|
||||
reaction,
|
||||
traitValue,
|
||||
target,
|
||||
difficulty,
|
||||
title,
|
||||
label: game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll'),
|
||||
actionType: null,
|
||||
advantage,
|
||||
grantResources
|
||||
});
|
||||
return false;
|
||||
}
|
||||
},
|
||||
fr: {
|
||||
rgx: /^(?:\/fr)((?:\s)[^]*)?/,
|
||||
fn: (_, match) => {
|
||||
const argString = match[1]?.trim();
|
||||
const result = argString ? rollCommandToJSON(argString) : { result: {} };
|
||||
|
||||
if (!result) {
|
||||
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.fateParsing'));
|
||||
return false;
|
||||
}
|
||||
|
||||
const { result: rollCommand, flavor } = result;
|
||||
const fateTypeData = getFateTypeData(rollCommand?.type);
|
||||
|
||||
if (!fateTypeData)
|
||||
return ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.fateTypeParsing'));
|
||||
|
||||
const { value: fateType, label: fateTypeLabel } = fateTypeData;
|
||||
const target = getCommandTarget({ allowNull: true });
|
||||
const title = flavor ?? game.i18n.localize('DAGGERHEART.GENERAL.fateRoll');
|
||||
|
||||
enrichedFateRoll({
|
||||
target,
|
||||
title,
|
||||
label: fateTypeLabel,
|
||||
fateType
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_getEntryContextOptions() {
|
||||
return [
|
||||
...super._getEntryContextOptions(),
|
||||
// {
|
||||
// name: 'Reroll',
|
||||
// icon: '<i class="fa-solid fa-dice"></i>',
|
||||
// condition: li => {
|
||||
// const message = game.messages.get(li.dataset.messageId);
|
||||
|
||||
// return (game.user.isGM || message.isAuthor) && message.rolls.length > 0;
|
||||
// },
|
||||
// callback: li => {
|
||||
// const message = game.messages.get(li.dataset.messageId);
|
||||
// new game.system.api.applications.dialogs.RerollDialog(message).render({ force: true });
|
||||
// }
|
||||
// },
|
||||
{
|
||||
label: 'DAGGERHEART.UI.ChatLog.rerollActionRoll',
|
||||
name: game.i18n.localize('DAGGERHEART.UI.ChatLog.rerollDamage'),
|
||||
icon: '<i class="fa-solid fa-dice"></i>',
|
||||
visible: li => {
|
||||
const message = game.messages.get(li.dataset.messageId);
|
||||
return message.system.hasRoll && (game.user.isGM || message.isAuthor);
|
||||
},
|
||||
callback: async li => {
|
||||
const message = game.messages.get(li.dataset.messageId);
|
||||
const reroll = await message.rolls[0].reroll({ liveRoll: true });
|
||||
message.update({ rolls: [reroll] });
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'DAGGERHEART.UI.ChatLog.rerollDamage',
|
||||
icon: '<i class="fa-solid fa-dice"></i>',
|
||||
visible: li => {
|
||||
condition: li => {
|
||||
const message = game.messages.get(li.dataset.messageId);
|
||||
const hasRolledDamage = message.system.hasDamage
|
||||
? Object.keys(message.system.damage).length > 0
|
||||
: false;
|
||||
return (game.user.isGM || message.isAuthor) && hasRolledDamage;
|
||||
},
|
||||
callback: async li => {
|
||||
callback: li => {
|
||||
const message = game.messages.get(li.dataset.messageId);
|
||||
const update = await message.system.getRerolledDamage();
|
||||
message.update(update);
|
||||
new game.system.api.applications.dialogs.RerollDamageDialog(message).render({ force: true });
|
||||
}
|
||||
}
|
||||
];
|
||||
|
|
@ -149,6 +69,18 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
html.querySelectorAll('.reroll-button').forEach(element =>
|
||||
element.addEventListener('click', event => this.rerollEvent(event, message))
|
||||
);
|
||||
html.querySelectorAll('.group-roll-button').forEach(element =>
|
||||
element.addEventListener('click', event => this.groupRollButton(event, message))
|
||||
);
|
||||
html.querySelectorAll('.group-roll-reroll').forEach(element =>
|
||||
element.addEventListener('click', event => this.groupRollReroll(event, message))
|
||||
);
|
||||
html.querySelectorAll('.group-roll-success').forEach(element =>
|
||||
element.addEventListener('click', event => this.groupRollSuccessEvent(event, message))
|
||||
);
|
||||
html.querySelectorAll('.group-roll-header-expand-section').forEach(element =>
|
||||
element.addEventListener('click', this.groupRollExpandSection)
|
||||
);
|
||||
html.querySelectorAll('.risk-it-all-button').forEach(element =>
|
||||
element.addEventListener('click', event => this.riskItAllClearStressAndHitPoints(event, data))
|
||||
);
|
||||
|
|
@ -243,7 +175,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
action.use(event);
|
||||
}
|
||||
|
||||
async rerollEvent(event, messageData) {
|
||||
async rerollEvent(event, message) {
|
||||
event.stopPropagation();
|
||||
if (!event.shiftKey) {
|
||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||
|
|
@ -255,43 +187,208 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
if (!confirmed) return;
|
||||
}
|
||||
|
||||
const message = game.messages.get(messageData._id);
|
||||
const target = event.target.closest('[data-die-index]');
|
||||
|
||||
if (target.dataset.type === 'damage') {
|
||||
const { damageType, part, dice, result } = target.dataset;
|
||||
const damagePart = message.system.damage[damageType].parts[part];
|
||||
const { parsedRoll, rerolledDice } = await game.system.api.dice.DamageRoll.reroll(damagePart, dice, result);
|
||||
const damageParts = message.system.damage[damageType].parts.map((damagePart, index) => {
|
||||
if (index !== Number(part)) return damagePart;
|
||||
return {
|
||||
...damagePart,
|
||||
total: parsedRoll.total,
|
||||
dice: rerolledDice
|
||||
};
|
||||
});
|
||||
const updateMessage = game.messages.get(message._id);
|
||||
await updateMessage.update({
|
||||
[`system.damage.${damageType}`]: {
|
||||
total: parsedRoll.total,
|
||||
parts: damageParts
|
||||
}
|
||||
});
|
||||
game.system.api.dice.DamageRoll.reroll(target, message);
|
||||
} else {
|
||||
const rerollDice = message.system.roll.dice[target.dataset.dieIndex];
|
||||
await rerollDice.reroll(`/r1=${rerollDice.total}`, {
|
||||
liveRoll: {
|
||||
roll: message.system.roll,
|
||||
actor: message.system.actionActor,
|
||||
isReaction: message.system.roll.options.actionType === 'reaction'
|
||||
}
|
||||
let originalRoll_parsed = message.rolls.map(roll => JSON.parse(roll))[0];
|
||||
const rollClass =
|
||||
game.system.api.dice[
|
||||
message.type === 'dualityRoll'
|
||||
? 'DualityRoll'
|
||||
: target.dataset.type === 'damage'
|
||||
? 'DHRoll'
|
||||
: 'D20Roll'
|
||||
];
|
||||
|
||||
if (!game.modules.get('dice-so-nice')?.active) foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice });
|
||||
|
||||
const { newRoll, parsedRoll } = await rollClass.reroll(originalRoll_parsed, target, message);
|
||||
|
||||
await game.messages.get(message._id).update({
|
||||
'system.roll': newRoll,
|
||||
'rolls': [parsedRoll]
|
||||
});
|
||||
await message.update({
|
||||
rolls: [message.system.roll.toJSON()]
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
async riskItAllClearStressAndHitPoints(event, data) {
|
||||
const resourceValue = event.target.dataset.resourceValue;
|
||||
const actor = game.actors.get(event.target.dataset.actorId);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
import { AdversaryBPPerEncounter } from '../../config/encounterConfig.mjs';
|
||||
import { expireActiveEffects } from '../../helpers/utils.mjs';
|
||||
import { clearPreviousSpotlight } from '../../macros/spotlightCombatant.mjs';
|
||||
|
||||
export default class DhCombatTracker extends foundry.applications.sidebar.tabs.CombatTracker {
|
||||
static DEFAULT_OPTIONS = {
|
||||
|
|
@ -56,9 +54,7 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
|||
async _prepareTrackerContext(context, options) {
|
||||
await super._prepareTrackerContext(context, options);
|
||||
|
||||
const npcs = context.turns?.filter(x => x.isNPC) ?? [];
|
||||
const adversaries = npcs.filter(x => x.disposition !== CONST.TOKEN_DISPOSITIONS.FRIENDLY);
|
||||
const friendlies = npcs.filter(x => x.disposition === CONST.TOKEN_DISPOSITIONS.FRIENDLY);
|
||||
const adversaries = context.turns?.filter(x => x.isNPC) ?? [];
|
||||
const characters = context.turns?.filter(x => !x.isNPC) ?? [];
|
||||
const spotlightQueueEnabled = game.settings.get(
|
||||
CONFIG.DH.id,
|
||||
|
|
@ -77,56 +73,25 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
|||
Object.assign(context, {
|
||||
actionTokens: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules).actionTokens,
|
||||
adversaries,
|
||||
friendlies,
|
||||
allCharacters: characters,
|
||||
characters: characters.filter(x => !spotlightQueueEnabled || x.system.spotlight.requestOrderIndex == 0),
|
||||
spotlightRequests
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the dialog used to edit the name of the currently viewed Combat encounter.
|
||||
* @this {CombatTracker}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async #onEditName() {
|
||||
const combat = this.viewed;
|
||||
if (!combat || !game.user.isGM) return null;
|
||||
const field = combat.schema.fields.name;
|
||||
const inputHTML = field.toFormGroup({}, { name: 'name', value: combat.name, autofocus: true }).outerHTML;
|
||||
const formData = await foundry.applications.api.DialogV2.input({
|
||||
window: { icon: 'fa-solid fa-tag', title: 'COMBAT.ACTIONS.EditNameTitle' },
|
||||
position: { width: 480 },
|
||||
content: inputHTML
|
||||
});
|
||||
await combat.update({ name: formData.name || '' });
|
||||
}
|
||||
|
||||
_getCombatContextOptions() {
|
||||
return [
|
||||
{
|
||||
label: 'COMBAT.ACTIONS.EditName',
|
||||
icon: 'fa-solid fa-tag',
|
||||
visible: () => game.user.isGM && !!this.viewed,
|
||||
onClick: () => DhCombatTracker.#onEditName.call(this)
|
||||
name: 'COMBAT.ClearMovementHistories',
|
||||
icon: '<i class="fa-solid fa-shoe-prints"></i>',
|
||||
condition: () => game.user.isGM && this.viewed?.combatants.size > 0,
|
||||
callback: () => this.viewed.clearMovementHistories()
|
||||
},
|
||||
{
|
||||
label: 'COMBAT.ACTIONS.LinkToScene',
|
||||
icon: '<i class="fa-solid fa-link"></i>',
|
||||
visible: () => game.user.isGM && !this.scene,
|
||||
onClick: () => this.viewed.toggleSceneLink()
|
||||
},
|
||||
{
|
||||
label: 'COMBAT.ACTIONS.UnlinkFromScene',
|
||||
icon: '<i class="fa-solid fa-unlink"></i>',
|
||||
visible: () => game.user.isGM && !!this.scene,
|
||||
onClick: () => this.viewed.toggleSceneLink()
|
||||
},
|
||||
{
|
||||
label: 'COMBAT.End',
|
||||
icon: 'fa-solid fa-xmark',
|
||||
visible: () => game.user.isGM && !!this.viewed,
|
||||
onClick: () => this.viewed.endCombat()
|
||||
name: 'COMBAT.Delete',
|
||||
icon: '<i class="fa-solid fa-trash"></i>',
|
||||
condition: () => game.user.isGM && !!this.viewed,
|
||||
callback: () => this.viewed.endCombat()
|
||||
}
|
||||
];
|
||||
}
|
||||
|
|
@ -162,8 +127,7 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
|||
active: index === combat.turn,
|
||||
canPing: combatant.sceneId === canvas.scene?.id && game.user.hasPermission('PING_CANVAS'),
|
||||
type: combatant.actor?.system?.type,
|
||||
img: await this._getCombatantThumbnail(combatant),
|
||||
disposition: combatant.token?.disposition
|
||||
img: await this._getCombatantThumbnail(combatant)
|
||||
};
|
||||
|
||||
turn.css = [turn.active ? 'active' : null, hidden ? 'hide' : null, isDefeated ? 'defeated' : null].filterJoin(
|
||||
|
|
@ -185,13 +149,13 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
|||
}
|
||||
|
||||
async setCombatantSpotlight(combatantId) {
|
||||
const combatant = this.viewed.combatants.get(combatantId);
|
||||
const update = {
|
||||
system: {
|
||||
'spotlight.requesting': false,
|
||||
'spotlight.requestOrderIndex': 0
|
||||
}
|
||||
};
|
||||
const combatant = this.viewed.combatants.get(combatantId);
|
||||
|
||||
const toggleTurn = this.viewed.combatants.contents
|
||||
.sort(this.viewed._sortCombatants)
|
||||
|
|
@ -213,8 +177,6 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
|||
if (autoPoints) {
|
||||
update.system.actionTokens = Math.max(combatant.system.actionTokens - 1, 0);
|
||||
}
|
||||
|
||||
if (combatant.actor) expireActiveEffects(combatant.actor, [CONFIG.DH.GENERAL.activeEffectDurations.act.id]);
|
||||
}
|
||||
|
||||
await this.viewed.update({
|
||||
|
|
@ -222,14 +184,6 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
|||
round: this.viewed.round + 1
|
||||
});
|
||||
await combatant.update(update);
|
||||
if (combatant.token) clearPreviousSpotlight();
|
||||
}
|
||||
|
||||
async clearTurn() {
|
||||
await this.viewed.update({
|
||||
turn: null,
|
||||
round: this.viewed.round + 1
|
||||
});
|
||||
}
|
||||
|
||||
static async requestSpotlight(_, target) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { DhCountdown } from '../../data/countdowns.mjs';
|
||||
import { waitForDiceSoNice } from '../../helpers/utils.mjs';
|
||||
import { emitGMUpdate, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||
import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
||||
|
|
@ -56,8 +56,8 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio
|
|||
? 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'
|
||||
? 'DAGGERHEART.UI.Countdowns.decreasingLoop'
|
||||
: 'DAGGERHEART.UI.Countdowns.loop'
|
||||
: null;
|
||||
const randomizeValid = !new Roll(countdown.progress.startFormula ?? '').isDeterministic;
|
||||
acc[key] = {
|
||||
|
|
@ -114,7 +114,7 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio
|
|||
}
|
||||
|
||||
await this.data.updateSource(update);
|
||||
await emitGMUpdate(GMUpdateEvent.UpdateCountdowns, this.gmSetSetting.bind(this.data), this.data, null, {
|
||||
await emitAsGM(GMUpdateEvent.UpdateCountdowns, this.gmSetSetting.bind(this.data), this.data, null, {
|
||||
refreshType: RefreshType.Countdown
|
||||
});
|
||||
|
||||
|
|
@ -148,11 +148,11 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio
|
|||
}
|
||||
|
||||
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 }
|
||||
});
|
||||
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 });
|
||||
}
|
||||
|
||||
|
|
@ -233,6 +233,6 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio
|
|||
}
|
||||
|
||||
if (this.editingCountdowns.has(countdownId)) this.editingCountdowns.delete(countdownId);
|
||||
this.updateSetting({ [`countdowns.${countdownId}`]: _del });
|
||||
this.updateSetting({ [`countdowns.-=${countdownId}`]: null });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { waitForDiceSoNice } from '../../helpers/utils.mjs';
|
||||
import { emitGMUpdate, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||
import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
||||
|
|
@ -21,19 +21,19 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
|||
static DEFAULT_OPTIONS = {
|
||||
id: 'countdowns',
|
||||
tag: 'div',
|
||||
classes: ['daggerheart', 'dh-style', 'countdowns'],
|
||||
classes: ['daggerheart', 'dh-style', 'countdowns', 'faded-ui'],
|
||||
window: {
|
||||
icon: 'fa-solid fa-clock-rotate-left',
|
||||
frame: false,
|
||||
frame: true,
|
||||
title: 'DAGGERHEART.UI.Countdowns.title',
|
||||
positioned: false,
|
||||
resizable: false,
|
||||
minimizable: false
|
||||
},
|
||||
actions: {
|
||||
toggleViewMode: DhCountdowns.#onToggleViewMode,
|
||||
editCountdowns: DhCountdowns.#onEditCountdowns,
|
||||
loopCountdown: DhCountdowns.#onLoopCountdown,
|
||||
toggleViewMode: DhCountdowns.#toggleViewMode,
|
||||
editCountdowns: DhCountdowns.#editCountdowns,
|
||||
loopCountdown: DhCountdowns.#loopCountdown,
|
||||
decreaseCountdown: (_, target) => this.editCountdown(false, target),
|
||||
increaseCountdown: (_, target) => this.editCountdown(true, target)
|
||||
},
|
||||
|
|
@ -52,6 +52,10 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
|||
}
|
||||
};
|
||||
|
||||
get element() {
|
||||
return document.body.querySelector('.daggerheart.dh-style.countdowns');
|
||||
}
|
||||
|
||||
/**@inheritdoc */
|
||||
async _renderFrame(options) {
|
||||
const frame = await super._renderFrame(options);
|
||||
|
|
@ -62,6 +66,19 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
|||
if (iconOnly) frame.classList.add('icon-only');
|
||||
else frame.classList.remove('icon-only');
|
||||
|
||||
const header = frame.querySelector('.window-header');
|
||||
header.querySelector('button[data-action="close"]').remove();
|
||||
|
||||
if (game.user.isGM) {
|
||||
const editTooltip = game.i18n.localize('DAGGERHEART.APPLICATIONS.CountdownEdit.editTitle');
|
||||
const editButton = `<a style="margin-right: 8px;" class="header-control" data-tooltip="${editTooltip}" aria-label="${editTooltip}" data-action="editCountdowns"><i class="fa-solid fa-wrench"></i></a>`;
|
||||
header.insertAdjacentHTML('beforeEnd', editButton);
|
||||
}
|
||||
|
||||
const minimizeTooltip = game.i18n.localize('DAGGERHEART.UI.Countdowns.toggleIconMode');
|
||||
const minimizeButton = `<a class="header-control" data-tooltip="${minimizeTooltip}" aria-label="${minimizeTooltip}" data-action="toggleViewMode"><i class="fa-solid fa-down-left-and-up-right-to-center"></i></a>`;
|
||||
header.insertAdjacentHTML('beforeEnd', minimizeButton);
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
|
|
@ -101,8 +118,8 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
|||
? 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'
|
||||
? 'DAGGERHEART.UI.Countdowns.decreasingLoop'
|
||||
: 'DAGGERHEART.UI.Countdowns.loop'
|
||||
: null;
|
||||
const loopDisabled =
|
||||
!countdownEditable ||
|
||||
|
|
@ -123,8 +140,6 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
|||
}
|
||||
|
||||
static #getPlayerOwnership(user, setting, countdown) {
|
||||
if (user.isGM) return CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER;
|
||||
|
||||
const playerOwnership = countdown.ownership[user.id];
|
||||
return playerOwnership === undefined || playerOwnership === CONST.DOCUMENT_OWNERSHIP_LEVELS.INHERIT
|
||||
? setting.defaultOwnership
|
||||
|
|
@ -147,7 +162,7 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
|||
return true;
|
||||
}
|
||||
|
||||
static async #onToggleViewMode() {
|
||||
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;
|
||||
|
|
@ -158,16 +173,15 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
|||
this.render();
|
||||
}
|
||||
|
||||
static async #onEditCountdowns() {
|
||||
static async #editCountdowns() {
|
||||
new game.system.api.applications.ui.CountdownEdit().render(true);
|
||||
}
|
||||
|
||||
static async #onLoopCountdown(_, target) {
|
||||
static async #loopCountdown(_, target) {
|
||||
if (!DhCountdowns.canPerformEdit()) return;
|
||||
|
||||
const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||
const countdownId = target.closest('[data-countdown]').dataset.countdown;
|
||||
const countdown = settings.countdowns[countdownId];
|
||||
const countdown = settings.countdowns[target.id];
|
||||
|
||||
let progressMax = countdown.progress.start;
|
||||
let message = null;
|
||||
|
|
@ -181,17 +195,17 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
|||
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;
|
||||
? Math.max(Number(progressMax) - 1, 0)
|
||||
: progressMax;
|
||||
|
||||
await waitForDiceSoNice(message);
|
||||
await settings.updateSource({
|
||||
[`countdowns.${countdownId}.progress`]: {
|
||||
[`countdowns.${target.id}.progress`]: {
|
||||
current: newMax,
|
||||
start: newMax
|
||||
}
|
||||
});
|
||||
await emitGMUpdate(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, {
|
||||
await emitAsGM(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, {
|
||||
refreshType: RefreshType.Countdown
|
||||
});
|
||||
}
|
||||
|
|
@ -200,23 +214,22 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
|||
if (!DhCountdowns.canPerformEdit()) return;
|
||||
|
||||
const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||
const countdownId = target.closest('[data-countdown]').dataset.countdown;
|
||||
const countdown = settings.countdowns[countdownId];
|
||||
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.${countdownId}.progress.current`]: newCurrent });
|
||||
await emitGMUpdate(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, {
|
||||
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,
|
||||
data: { refreshType: RefreshType.Countdown }
|
||||
});
|
||||
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 });
|
||||
}
|
||||
|
||||
|
|
@ -265,8 +278,10 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
|||
return acc;
|
||||
}, {})
|
||||
};
|
||||
await emitGMUpdate(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, {
|
||||
refreshType: RefreshType.Countdown
|
||||
await emitAsGM(GMUpdateEvent.UpdateCountdowns,
|
||||
DhCountdowns.gmSetSetting.bind(settings),
|
||||
settings, null, {
|
||||
refreshType: RefreshType.Countdown
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { getIconVisibleActiveEffects } from '../../helpers/utils.mjs';
|
||||
import { RefreshType } from '../../systemRegistration/socket.mjs';
|
||||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
|
@ -49,9 +48,11 @@ export default class DhEffectsDisplay extends HandlebarsApplicationMixin(Applica
|
|||
|
||||
_attachPartListeners(partId, htmlElement, options) {
|
||||
super._attachPartListeners(partId, htmlElement, options);
|
||||
for (const element of this.element?.querySelectorAll('.effect-container a') ?? []) {
|
||||
element.addEventListener('click', e => this.#onClickEffect(e));
|
||||
element.addEventListener('contextmenu', e => this.#onClickEffect(e, -1));
|
||||
|
||||
if (this.element) {
|
||||
this.element.querySelectorAll('.effect-container a').forEach(element => {
|
||||
element.addEventListener('contextmenu', this.removeEffect.bind(this));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -67,11 +68,11 @@ export default class DhEffectsDisplay extends HandlebarsApplicationMixin(Applica
|
|||
const actor = token
|
||||
? token.actor
|
||||
: canvas.tokens.controlled.length === 0
|
||||
? !game.user.isGM
|
||||
? game.user.character
|
||||
: null
|
||||
: canvas.tokens.controlled[0].actor;
|
||||
return getIconVisibleActiveEffects(actor?.getActiveEffects() ?? []);
|
||||
? !game.user.isGM
|
||||
? game.user.character
|
||||
: null
|
||||
: canvas.tokens.controlled[0].actor;
|
||||
return actor?.getActiveEffects() ?? [];
|
||||
};
|
||||
|
||||
toggleHidden(token, focused) {
|
||||
|
|
@ -85,22 +86,11 @@ export default class DhEffectsDisplay extends HandlebarsApplicationMixin(Applica
|
|||
this.render();
|
||||
}
|
||||
|
||||
async #onClickEffect(event, delta = 1) {
|
||||
async removeEffect(event) {
|
||||
const element = event.target.closest('.effect-container');
|
||||
const effects = DhEffectsDisplay.getTokenEffects();
|
||||
const effect = effects.find(x => x.id === element.dataset.effectId);
|
||||
if (!effect || (delta >= 0 && !effect.system.stacking)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const maxValue = effect.system.stacking?.max ?? Infinity;
|
||||
const newValue = Math.clamp((effect.system.stacking?.value ?? 1) + delta, 0, maxValue);
|
||||
if (newValue > 0) {
|
||||
await effect.update({ 'system.stacking.value': newValue });
|
||||
} else {
|
||||
await effect.delete();
|
||||
}
|
||||
this.render();
|
||||
await effect.delete();
|
||||
}
|
||||
|
||||
setupHooks() {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { emitGMUpdate, GMUpdateEvent } from '../../systemRegistration/socket.mjs';
|
||||
import { emitAsGM, GMUpdateEvent } from '../../systemRegistration/socket.mjs';
|
||||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
||||
|
|
@ -104,7 +104,7 @@ export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV
|
|||
}
|
||||
|
||||
async updateFear(value) {
|
||||
return emitGMUpdate(
|
||||
return emitAsGM(
|
||||
GMUpdateEvent.UpdateFear,
|
||||
game.settings.set.bind(game.settings, CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear),
|
||||
value
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { getDocFromElement } from '../../helpers/utils.mjs';
|
||||
import { RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
|
@ -48,8 +47,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
expandContent: this.expandContent,
|
||||
resetFilters: this.resetFilters,
|
||||
sortList: this.sortList,
|
||||
openSettings: this.openSettings,
|
||||
viewSheet: this.#onViewSheet
|
||||
openSettings: this.openSettings
|
||||
},
|
||||
position: {
|
||||
left: 100,
|
||||
|
|
@ -111,8 +109,8 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
CONFIG.DH.id,
|
||||
CONFIG.DH.FLAGS[`${this.compendiumBrowserTypeKey}`].position
|
||||
);
|
||||
|
||||
options.position = userPresetPosition ?? ItemBrowser.DEFAULT_OPTIONS.position;
|
||||
delete options.position.zIndex;
|
||||
|
||||
if (!userPresetPosition) {
|
||||
const width = noFolder === true || lite === true ? 600 : 850;
|
||||
|
|
@ -279,7 +277,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
(await foundry.applications.ux.TextEditor.implementation.enrichHTML(item.description));
|
||||
}
|
||||
|
||||
this.fieldFilter = await this._createFieldFilter();
|
||||
this.fieldFilter = this._createFieldFilter();
|
||||
|
||||
if (this.presets?.filter) {
|
||||
Object.entries(this.presets.filter).forEach(([k, v]) => {
|
||||
|
|
@ -308,8 +306,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
{
|
||||
items: this.items,
|
||||
menu: this.selectedMenu,
|
||||
formatLabel: this.formatLabel,
|
||||
viewSheet: this.items[0] instanceof Actor
|
||||
formatLabel: this.formatLabel
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -358,12 +355,12 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
);
|
||||
}
|
||||
|
||||
async _createFieldFilter() {
|
||||
_createFieldFilter() {
|
||||
const filters = ItemBrowser.getFolderConfig(this.selectedMenu.data, 'filters');
|
||||
for (const f of filters) {
|
||||
filters.forEach(f => {
|
||||
if (typeof f.field === 'string') f.field = foundry.utils.getProperty(game, f.field);
|
||||
else if (typeof f.choices === 'function') {
|
||||
f.choices = await f.choices(this.items);
|
||||
f.choices = f.choices(this.items);
|
||||
}
|
||||
|
||||
// Clear field label so template uses our custom label parameter
|
||||
|
|
@ -373,8 +370,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
|
||||
f.name ??= f.key;
|
||||
f.value = this.presets?.filter?.[f.name]?.value ?? null;
|
||||
}
|
||||
|
||||
});
|
||||
return filters;
|
||||
}
|
||||
|
||||
|
|
@ -571,11 +567,6 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
}
|
||||
}
|
||||
|
||||
static async #onViewSheet(_, target) {
|
||||
const document = await getDocFromElement(target);
|
||||
document?.sheet?.render(true);
|
||||
}
|
||||
|
||||
_createDragProcess() {
|
||||
new foundry.applications.ux.DragDrop.implementation({
|
||||
dragSelector: '.item-container',
|
||||
|
|
@ -614,16 +605,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
items: {
|
||||
folder: 'equipments',
|
||||
render: {
|
||||
folders: [
|
||||
'equipments',
|
||||
'ancestries',
|
||||
'classes',
|
||||
'subclasses',
|
||||
'domains',
|
||||
'communities',
|
||||
'beastforms'
|
||||
// excluded: features
|
||||
]
|
||||
noFolder: true
|
||||
}
|
||||
},
|
||||
compendium: {}
|
||||
|
|
|
|||
|
|
@ -1,27 +0,0 @@
|
|||
export default class DhProgress {
|
||||
#notification;
|
||||
|
||||
constructor({ max, label = '' }) {
|
||||
this.max = max;
|
||||
this.label = label;
|
||||
this.#notification = ui.notifications.info(this.label, { progress: true });
|
||||
}
|
||||
|
||||
updateMax(newMax) {
|
||||
this.max = newMax;
|
||||
}
|
||||
|
||||
advance({ by = 1, label = this.label } = {}) {
|
||||
if (this.value === this.max) return;
|
||||
this.value = (this.value ?? 0) + Math.abs(by);
|
||||
this.#notification.update({ message: label, pct: this.value / this.max });
|
||||
}
|
||||
|
||||
close({ label = '' } = {}) {
|
||||
this.#notification.update({ message: label, pct: 1 });
|
||||
}
|
||||
|
||||
static createMigrationProgress(max = 0) {
|
||||
return new DhProgress({ max, label: game.i18n.localize('DAGGERHEART.UI.Progress.migrationLabel') });
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { emitGMUpdate, GMUpdateEvent } from '../../systemRegistration/socket.mjs';
|
||||
import { emitAsGM, GMUpdateEvent } from '../../systemRegistration/socket.mjs';
|
||||
|
||||
export default class DhSceneNavigation extends foundry.applications.ui.SceneNavigation {
|
||||
/** @inheritdoc */
|
||||
|
|
@ -31,7 +31,7 @@ export default class DhSceneNavigation extends foundry.applications.ui.SceneNavi
|
|||
const environments = daggerheartInfo.sceneEnvironments.filter(
|
||||
x => x && x.testUserPermission(game.user, 'LIMITED')
|
||||
);
|
||||
const hasEnvironments = environments.length > 0 && x.active;
|
||||
const hasEnvironments = environments.length > 0 && x.isView;
|
||||
return {
|
||||
...x,
|
||||
hasEnvironments,
|
||||
|
|
@ -39,10 +39,9 @@ export default class DhSceneNavigation extends foundry.applications.ui.SceneNavi
|
|||
environments: environments
|
||||
};
|
||||
});
|
||||
|
||||
context.scenes.active = extendScenes(context.scenes.active);
|
||||
context.scenes.inactive = extendScenes(context.scenes.inactive);
|
||||
context.scenes.viewed = context.scenes.viewed ? extendScenes([context.scenes.viewed])[0] : null;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
|
|
@ -68,7 +67,7 @@ export default class DhSceneNavigation extends foundry.applications.ui.SceneNavi
|
|||
1
|
||||
)[0];
|
||||
newEnvironments.unshift(newFirst);
|
||||
emitGMUpdate(
|
||||
emitAsGM(
|
||||
GMUpdateEvent.UpdateDocument,
|
||||
scene.update.bind(scene),
|
||||
{ 'flags.daggerheart.sceneEnvironments': newEnvironments },
|
||||
|
|
|
|||
|
|
@ -1,4 +1,97 @@
|
|||
/**
|
||||
* @typedef ContextMenuEntry
|
||||
* @property {string} name The context menu label. Can be localized.
|
||||
* @property {string} [icon] A string containing an HTML icon element for the menu item.
|
||||
* @property {string} [classes] Additional CSS classes to apply to this menu item.
|
||||
* @property {string} [group] An identifier for a group this entry belongs to.
|
||||
* @property {ContextMenuJQueryCallback} callback The function to call when the menu item is clicked.
|
||||
* @property {ContextMenuCondition|boolean} [condition] A function to call or boolean value to determine if this entry
|
||||
* appears in the menu.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @callback ContextMenuCondition
|
||||
* @param {jQuery|HTMLElement} html The element of the context menu entry.
|
||||
* @returns {boolean} Whether the entry should be rendered in the context menu.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @callback ContextMenuCallback
|
||||
* @param {HTMLElement} target The element that the context menu has been triggered for.
|
||||
* @returns {unknown}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @callback ContextMenuJQueryCallback
|
||||
* @param {HTMLElement|jQuery} target The element that the context menu has been triggered for. Will
|
||||
* either be a jQuery object or an HTMLElement instance, depending
|
||||
* on how the ContextMenu was configured.
|
||||
* @returns {unknown}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef ContextMenuOptions
|
||||
* @property {string} [eventName="contextmenu"] Optionally override the triggering event which can spawn the menu. If
|
||||
* the menu is using fixed positioning, this event must be a MouseEvent.
|
||||
* @property {ContextMenuCallback} [onOpen] A function to call when the context menu is opened.
|
||||
* @property {ContextMenuCallback} [onClose] A function to call when the context menu is closed.
|
||||
* @property {boolean} [fixed=false] If true, the context menu is given a fixed position rather than being
|
||||
* injected into the target.
|
||||
* @property {boolean} [jQuery=true] If true, callbacks will be passed jQuery objects instead of HTMLElement
|
||||
* instances.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef ContextMenuRenderOptions
|
||||
* @property {Event} [event] The event that triggered the context menu opening.
|
||||
* @property {boolean} [animate=true] Animate the context menu opening.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A subclass of ContextMenu.
|
||||
* @extends {foundry.applications.ux.ContextMenu}
|
||||
*/
|
||||
export default class DHContextMenu extends foundry.applications.ux.ContextMenu {
|
||||
/**
|
||||
* @param {HTMLElement|jQuery} container - The HTML element that contains the context menu targets.
|
||||
* @param {string} selector - A CSS selector which activates the context menu.
|
||||
* @param {ContextMenuEntry[]} menuItems - An Array of entries to display in the menu
|
||||
* @param {ContextMenuOptions} [options] - Additional options to configure the context menu.
|
||||
*/
|
||||
constructor(container, selector, menuItems, options) {
|
||||
super(container, selector, menuItems, options);
|
||||
|
||||
/** @deprecated since v13 until v15 */
|
||||
this.#jQuery = options.jQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to pass jQuery objects or HTMLElement instances to callback.
|
||||
* @type {boolean}
|
||||
*/
|
||||
#jQuery;
|
||||
|
||||
/**@inheritdoc */
|
||||
activateListeners(menu) {
|
||||
menu.addEventListener('click', this.#onClickItem.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle click events on context menu items.
|
||||
* @param {PointerEvent} event The click event
|
||||
*/
|
||||
#onClickItem(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const element = event.target.closest('.context-item');
|
||||
if (!element) return;
|
||||
const item = this.menuItems.find(i => i.element === element);
|
||||
item?.callback(this.#jQuery ? $(this.target) : this.target, event);
|
||||
this.close();
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Trigger a context menu event in response to a normal click on a additional options button.
|
||||
* @param {PointerEvent} event
|
||||
|
|
@ -6,11 +99,8 @@ export default class DHContextMenu extends foundry.applications.ux.ContextMenu {
|
|||
static triggerContextMenu(event, altSelector) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const selector = altSelector ?? '[data-item-uuid]';
|
||||
if (ui.context?.selector === selector) return;
|
||||
|
||||
const { clientX, clientY } = event;
|
||||
const selector = altSelector ?? '[data-item-uuid]';
|
||||
const target = event.target.closest(selector) ?? event.currentTarget.closest(selector);
|
||||
target?.dispatchEvent(
|
||||
new PointerEvent('contextmenu', {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
export { default as DhMeasuredTemplate } from './measuredTemplate.mjs';
|
||||
export { default as DhRuler } from './ruler.mjs';
|
||||
export { default as DhRegion } from './region.mjs';
|
||||
export { default as DhRegionLayer } from './regionLayer.mjs';
|
||||
export { default as DhTemplateLayer } from './templateLayer.mjs';
|
||||
export { default as DhTokenPlaceable } from './token.mjs';
|
||||
export { default as DhTokenRuler } from './tokenRuler.mjs';
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
import DhMeasuredTemplate from './measuredTemplate.mjs';
|
||||
|
||||
export default class DhRegion extends foundry.canvas.placeables.Region {
|
||||
/**@inheritdoc */
|
||||
_formatMeasuredDistance(distance) {
|
||||
const range = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules).rangeMeasurement;
|
||||
if (!range.enabled) return super._formatMeasuredDistance(distance);
|
||||
|
||||
const { distance: resultDistance, units } = DhMeasuredTemplate.getRangeLabels(distance, range);
|
||||
return `${resultDistance} ${units}`;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,155 +0,0 @@
|
|||
export default class DhRegionLayer extends foundry.canvas.layers.RegionLayer {
|
||||
static prepareSceneControls() {
|
||||
const sc = foundry.applications.ui.SceneControls;
|
||||
const { tools, ...rest } = super.prepareSceneControls();
|
||||
|
||||
return {
|
||||
...rest,
|
||||
tools: {
|
||||
select: tools.select,
|
||||
templateMode: tools.templateMode,
|
||||
rectangle: tools.rectangle,
|
||||
circle: tools.circle,
|
||||
ellipse: tools.ellipse,
|
||||
cone: tools.cone,
|
||||
inFront: {
|
||||
name: 'inFront',
|
||||
order: 7,
|
||||
title: 'CONTROLS.inFront',
|
||||
icon: 'fa-solid fa-eye',
|
||||
toolclip: {
|
||||
src: 'toolclips/tools/measure-cone.webm',
|
||||
heading: 'CONTROLS.inFront',
|
||||
items: sc.buildToolclipItems(['create', 'move', 'edit', 'hide', 'delete', 'rotate'])
|
||||
}
|
||||
},
|
||||
ring: { ...tools.ring, order: 8 },
|
||||
line: { ...tools.line, order: 9 },
|
||||
emanation: { ...tools.emanation, order: 10 },
|
||||
polygon: { ...tools.polygon, order: 11 },
|
||||
hole: { ...tools.hole, order: 12 },
|
||||
snap: { ...tools.snap, order: 13 },
|
||||
clear: { ...tools.clear, order: 14 }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
_isCreationToolActive() {
|
||||
return this.active && (game.activeTool === 'inFront' || game.activeTool in foundry.data.BaseShapeData.TYPES);
|
||||
}
|
||||
|
||||
_createDragShapeData(event) {
|
||||
const hole = ui.controls.controls[this.options.name].tools.hole?.active ?? false;
|
||||
if (game.activeTool === 'inFront') return { type: 'cone', x: 0, y: 0, radius: 0, angle: 180, hole };
|
||||
|
||||
const shape = super._createDragShapeData(event);
|
||||
const token =
|
||||
shape?.type === 'emanation' && shape.base?.type === 'token'
|
||||
? this.#findTokenInBounds(event.interactionData.origin)
|
||||
: null;
|
||||
if (token) {
|
||||
shape.base.width = token.width;
|
||||
shape.base.height = token.height;
|
||||
event.interactionData.origin = token.getCenterPoint();
|
||||
}
|
||||
return shape;
|
||||
}
|
||||
|
||||
async placeRegion(data, options = {}) {
|
||||
const preConfirm = data => {
|
||||
const shape = data.document.shapes[0];
|
||||
const isEmanation = shape.type === 'emanation';
|
||||
if (isEmanation) {
|
||||
const token = this.#findTokenInBounds(shape.base.origin);
|
||||
if (!token) return options.preConfirm?.(data) ?? true;
|
||||
const shapeData = shape.toObject();
|
||||
data.document.updateSource({
|
||||
shapes: [
|
||||
{
|
||||
...shapeData,
|
||||
base: {
|
||||
...shapeData.base,
|
||||
height: token.height,
|
||||
width: token.width,
|
||||
x: token.x,
|
||||
y: token.y
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
return options?.preConfirm?.(data) ?? true;
|
||||
};
|
||||
|
||||
return await super.placeRegion(data, { ...options, preConfirm });
|
||||
}
|
||||
|
||||
/** Searches for token at origin point, returning null if there are no tokens or multiple overlapping tokens */
|
||||
#findTokenInBounds(origin) {
|
||||
const { x, y } = origin;
|
||||
const gridSize = canvas.grid.size;
|
||||
const inBounds = canvas.scene.tokens.filter(t => {
|
||||
return x.between(t.x, t.x + t.width * gridSize) && y.between(t.y, t.y + t.height * gridSize);
|
||||
});
|
||||
return inBounds.length === 1 ? inBounds[0] : null;
|
||||
}
|
||||
|
||||
static getTemplateShape({ type, angle, range, direction } = {}) {
|
||||
const { line, rectangle, inFront, cone, circle, emanation } = CONFIG.DH.GENERAL.templateTypes;
|
||||
|
||||
/* Length calculation */
|
||||
const { grid, distance } = CONFIG.Scene.documentClass.schema.fields.grid.fields;
|
||||
const sceneGridSize = canvas.scene?.grid.size ?? grid.size.initial;
|
||||
const sceneGridDistance = canvas.scene?.grid.distance ?? distance.getInitialValue();
|
||||
const dimensionConstant = sceneGridSize / sceneGridDistance;
|
||||
|
||||
const settings = canvas.scene?.rangeSettings;
|
||||
const rangeNumber = Number(range);
|
||||
const length = (!Number.isNaN(rangeNumber) ? rangeNumber : settings ? settings[range] : 0) * dimensionConstant;
|
||||
/*----*/
|
||||
|
||||
const shapeData = {
|
||||
...canvas.mousePosition,
|
||||
type: type,
|
||||
direction: direction ?? 0
|
||||
};
|
||||
|
||||
switch (type) {
|
||||
case rectangle.id:
|
||||
shapeData.width = length;
|
||||
shapeData.height = length;
|
||||
break;
|
||||
case line.id:
|
||||
shapeData.length = length;
|
||||
shapeData.width = 5 * dimensionConstant;
|
||||
break;
|
||||
case cone.id:
|
||||
shapeData.angle = angle ?? CONFIG.MeasuredTemplate.defaults.angle;
|
||||
shapeData.radius = length;
|
||||
break;
|
||||
case inFront.id:
|
||||
shapeData.angle = '180';
|
||||
shapeData.radius = length;
|
||||
shapeData.type = cone.id;
|
||||
break;
|
||||
case circle.id:
|
||||
shapeData.radius = length;
|
||||
break;
|
||||
case emanation.id:
|
||||
shapeData.radius = length;
|
||||
shapeData.base = {
|
||||
type: 'token',
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 1,
|
||||
height: 1,
|
||||
shape: game.canvas.grid.isHexagonal ? CONST.TOKEN_SHAPES.ELLIPSE_1 : CONST.TOKEN_SHAPES.RECTANGLE_1
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
return shapeData;
|
||||
}
|
||||
}
|
||||
116
module/canvas/placeables/templateLayer.mjs
Normal file
116
module/canvas/placeables/templateLayer.mjs
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
export default class DhTemplateLayer extends foundry.canvas.layers.TemplateLayer {
|
||||
static prepareSceneControls() {
|
||||
const sc = foundry.applications.ui.SceneControls;
|
||||
return {
|
||||
name: 'templates',
|
||||
order: 2,
|
||||
title: 'CONTROLS.GroupMeasure',
|
||||
icon: 'fa-solid fa-ruler-combined',
|
||||
visible: game.user.can('TEMPLATE_CREATE'),
|
||||
onChange: (event, active) => {
|
||||
if (active) canvas.templates.activate();
|
||||
},
|
||||
onToolChange: () => canvas.templates.setAllRenderFlags({ refreshState: true }),
|
||||
tools: {
|
||||
circle: {
|
||||
name: 'circle',
|
||||
order: 1,
|
||||
title: 'CONTROLS.MeasureCircle',
|
||||
icon: 'fa-regular fa-circle',
|
||||
toolclip: {
|
||||
src: 'toolclips/tools/measure-circle.webm',
|
||||
heading: 'CONTROLS.MeasureCircle',
|
||||
items: sc.buildToolclipItems(['create', 'move', 'edit', 'hide', 'delete'])
|
||||
}
|
||||
},
|
||||
cone: {
|
||||
name: 'cone',
|
||||
order: 2,
|
||||
title: 'CONTROLS.MeasureCone',
|
||||
icon: 'fa-solid fa-angle-left',
|
||||
toolclip: {
|
||||
src: 'toolclips/tools/measure-cone.webm',
|
||||
heading: 'CONTROLS.MeasureCone',
|
||||
items: sc.buildToolclipItems(['create', 'move', 'edit', 'hide', 'delete', 'rotate'])
|
||||
}
|
||||
},
|
||||
inFront: {
|
||||
name: 'inFront',
|
||||
order: 3,
|
||||
title: 'CONTROLS.inFront',
|
||||
icon: 'fa-solid fa-eye',
|
||||
toolclip: {
|
||||
src: 'toolclips/tools/measure-cone.webm',
|
||||
heading: 'CONTROLS.inFront',
|
||||
items: sc.buildToolclipItems(['create', 'move', 'edit', 'hide', 'delete', 'rotate'])
|
||||
}
|
||||
},
|
||||
rect: {
|
||||
name: 'rect',
|
||||
order: 4,
|
||||
title: 'CONTROLS.MeasureRect',
|
||||
icon: 'fa-regular fa-square',
|
||||
toolclip: {
|
||||
src: 'toolclips/tools/measure-rect.webm',
|
||||
heading: 'CONTROLS.MeasureRect',
|
||||
items: sc.buildToolclipItems(['create', 'move', 'edit', 'hide', 'delete', 'rotate'])
|
||||
}
|
||||
},
|
||||
ray: {
|
||||
name: 'ray',
|
||||
order: 5,
|
||||
title: 'CONTROLS.MeasureRay',
|
||||
icon: 'fa-solid fa-up-down',
|
||||
toolclip: {
|
||||
src: 'toolclips/tools/measure-ray.webm',
|
||||
heading: 'CONTROLS.MeasureRay',
|
||||
items: sc.buildToolclipItems(['create', 'move', 'edit', 'hide', 'delete', 'rotate'])
|
||||
}
|
||||
},
|
||||
clear: {
|
||||
name: 'clear',
|
||||
order: 6,
|
||||
title: 'CONTROLS.MeasureClear',
|
||||
icon: 'fa-solid fa-trash',
|
||||
visible: game.user.isGM,
|
||||
onChange: () => canvas.templates.deleteAll(),
|
||||
button: true
|
||||
}
|
||||
},
|
||||
activeTool: 'circle'
|
||||
};
|
||||
}
|
||||
|
||||
_onDragLeftStart(event) {
|
||||
const interaction = event.interactionData;
|
||||
|
||||
// Snap the origin to the grid
|
||||
if (!event.shiftKey) interaction.origin = this.getSnappedPoint(interaction.origin);
|
||||
|
||||
// Create a pending MeasuredTemplateDocument
|
||||
const tool = game.activeTool === 'inFront' ? 'cone' : game.activeTool;
|
||||
const previewData = {
|
||||
user: game.user.id,
|
||||
t: tool,
|
||||
x: interaction.origin.x,
|
||||
y: interaction.origin.y,
|
||||
sort: Math.max(this.getMaxSort() + 1, 0),
|
||||
distance: 1,
|
||||
direction: 0,
|
||||
fillColor: game.user.color || '#FF0000',
|
||||
hidden: event.altKey
|
||||
};
|
||||
const defaults = CONFIG.MeasuredTemplate.defaults;
|
||||
if (game.activeTool === 'cone') previewData.angle = defaults.angle;
|
||||
else if (game.activeTool === 'inFront') previewData.angle = 180;
|
||||
else if (game.activeTool === 'ray') previewData.width = defaults.width * canvas.dimensions.distance;
|
||||
const cls = foundry.utils.getDocumentClass('MeasuredTemplate');
|
||||
const doc = new cls(previewData, { parent: canvas.scene });
|
||||
|
||||
// Create a preview MeasuredTemplate object
|
||||
const template = new this.constructor.placeableClass(doc);
|
||||
doc._object = template;
|
||||
interaction.preview = this.preview.addChild(template);
|
||||
template.draw();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
import { getIconVisibleActiveEffects } from '../../helpers/utils.mjs';
|
||||
import DhMeasuredTemplate from './measuredTemplate.mjs';
|
||||
|
||||
export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
|
||||
|
|
@ -10,36 +9,6 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
|
|||
this.previewHelp ||= this.addChild(this.#drawPreviewHelp());
|
||||
}
|
||||
|
||||
/**@inheritdoc */
|
||||
_refreshTurnMarker() {
|
||||
// Should a Turn Marker be active?
|
||||
const { turnMarker } = this.document;
|
||||
const markersEnabled =
|
||||
CONFIG.Combat.settings.turnMarker.enabled && turnMarker.mode !== CONST.TOKEN_TURN_MARKER_MODES.DISABLED;
|
||||
const spotlighted = game.settings
|
||||
.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.SpotlightTracker)
|
||||
.spotlightedTokens.has(this.document.uuid);
|
||||
|
||||
const turnIsSet = typeof game.combat?.turn === 'number';
|
||||
const isTurn = game.combat?.combatant?.tokenId === this.id;
|
||||
const markerActive = markersEnabled && turnIsSet ? isTurn : spotlighted;
|
||||
|
||||
// Activate a Turn Marker
|
||||
if (markerActive) {
|
||||
if (!this.turnMarker)
|
||||
this.turnMarker = this.addChildAt(new foundry.canvas.placeables.tokens.TokenTurnMarker(this), 0);
|
||||
canvas.tokens.turnMarkers.add(this);
|
||||
this.turnMarker.draw();
|
||||
}
|
||||
|
||||
// Remove a Turn Marker
|
||||
else if (this.turnMarker) {
|
||||
canvas.tokens.turnMarkers.delete(this);
|
||||
this.turnMarker.destroy();
|
||||
this.turnMarker = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
async _drawEffects() {
|
||||
this.effects.renderable = false;
|
||||
|
|
@ -51,7 +20,7 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
|
|||
this.effects.overlay = null;
|
||||
|
||||
// Categorize effects
|
||||
const activeEffects = getIconVisibleActiveEffects(Array.from(this.actor?.allApplicableEffects() ?? []));
|
||||
const activeEffects = this.actor?.getActiveEffects() ?? [];
|
||||
const overlayEffect = activeEffects.findLast(e => e.img && e.getFlag?.('core', 'overlay'));
|
||||
|
||||
// Draw effects
|
||||
|
|
@ -60,8 +29,8 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
|
|||
if (!effect.img) continue;
|
||||
const promise =
|
||||
effect === overlayEffect
|
||||
? this._drawOverlay(effect.img, effect.tint, effect)
|
||||
: this._drawEffect(effect.img, effect.tint, effect);
|
||||
? this._drawOverlay(effect.img, effect.tint)
|
||||
: this._drawEffect(effect.img, effect.tint);
|
||||
promises.push(
|
||||
promise.then(e => {
|
||||
if (e) e.zIndex = i;
|
||||
|
|
@ -75,39 +44,6 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
|
|||
this.renderFlags.set({ refreshEffects: true });
|
||||
}
|
||||
|
||||
/**@inheritdoc */
|
||||
async _drawEffect(src, tint, effect) {
|
||||
if (!src) return;
|
||||
const tex = await foundry.canvas.loadTexture(src, { fallback: 'icons/svg/hazard.svg' });
|
||||
const icon = new PIXI.Sprite(tex);
|
||||
icon.tint = tint ?? 0xffffff;
|
||||
|
||||
if (effect.system.stacking?.value > 1) {
|
||||
const stackOverlay = new PIXI.Text(effect.system.stacking.value, {
|
||||
fill: '#f3c267',
|
||||
stroke: '#000000',
|
||||
fontSize: 96,
|
||||
strokeThickness: 4
|
||||
});
|
||||
const nrDigits = Math.floor(Math.log10(effect.system.stacking.value)) + 1;
|
||||
stackOverlay.y = -8;
|
||||
/* This does not account for 1:s being much less wide than other digits. I don't think it's desired however as it makes it look jumpy */
|
||||
stackOverlay.x = icon.width - 8 - nrDigits * 56;
|
||||
stackOverlay.anchor.set(0, 0);
|
||||
|
||||
icon.addChild(stackOverlay);
|
||||
}
|
||||
|
||||
return this.effects.addChild(icon);
|
||||
}
|
||||
|
||||
async _drawOverlay(src, tint, effect) {
|
||||
const icon = await this._drawEffect(src, tint, effect);
|
||||
if (icon) icon.alpha = 0.8;
|
||||
this.effects.overlay = icon ?? null;
|
||||
return icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the distance from this token to another token object.
|
||||
* This value is corrected to handle alternate token sizes and other grid types
|
||||
|
|
@ -155,15 +91,15 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
|
|||
const targetEdge = this.#getEdgeBoundary(targetBounds, originPoint, targetPoint);
|
||||
const adjustedOriginPoint = originEdge
|
||||
? canvas.grid.getTopLeftPoint({
|
||||
x: originEdge.x + Math.sign(originPoint.x - originEdge.x),
|
||||
y: originEdge.y + Math.sign(originPoint.y - originEdge.y)
|
||||
})
|
||||
x: originEdge.x + Math.sign(originPoint.x - originEdge.x),
|
||||
y: originEdge.y + Math.sign(originPoint.y - originEdge.y)
|
||||
})
|
||||
: originPoint;
|
||||
const adjustDestinationPoint = targetEdge
|
||||
? canvas.grid.getTopLeftPoint({
|
||||
x: targetEdge.x + Math.sign(targetPoint.x - targetEdge.x),
|
||||
y: targetEdge.y + Math.sign(targetPoint.y - targetEdge.y)
|
||||
})
|
||||
x: targetEdge.x + Math.sign(targetPoint.x - targetEdge.x),
|
||||
y: targetEdge.y + Math.sign(targetPoint.y - targetEdge.y)
|
||||
})
|
||||
: targetPoint;
|
||||
const distance = canvas.grid.measurePath([
|
||||
{ ...adjustedOriginPoint, elevation: 0 },
|
||||
|
|
@ -249,6 +185,9 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
|
|||
|
||||
/** @inheritDoc */
|
||||
_drawBar(number, bar, data) {
|
||||
const val = Number(data.value);
|
||||
const pct = Math.clamp(val, 0, data.max) / data.max;
|
||||
|
||||
// Determine sizing
|
||||
const { width, height } = this.document.getSize();
|
||||
const s = canvas.dimensions.uiScale;
|
||||
|
|
@ -256,19 +195,17 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
|
|||
const bh = 8 * (this.document.height >= 2 ? 1.5 : 1) * s;
|
||||
|
||||
// Determine the color to use
|
||||
const Color = foundry.utils.Color;
|
||||
const fillColor = number === 0 ? Color.fromRGB([1, 0, 0]) : Color.fromString('#0032b1');
|
||||
const emptyColor = Color.fromRGB([0, 0, 0]);
|
||||
const fillColor =
|
||||
number === 0 ? foundry.utils.Color.fromRGB([1, 0, 0]) : foundry.utils.Color.fromString('#0032b1');
|
||||
|
||||
// Draw the bar (accounting floating point numbers from bar animations)
|
||||
const widthUnit = bw / Math.ceil(data.max);
|
||||
// Draw the bar
|
||||
const widthUnit = bw / data.max;
|
||||
bar.clear().lineStyle(s, 0x000000, 1.0);
|
||||
const sections = [...Array(Math.ceil(data.max)).keys()];
|
||||
for (const mark of sections) {
|
||||
const sections = [...Array(data.max).keys()];
|
||||
for (let mark of sections) {
|
||||
const x = mark * widthUnit;
|
||||
const marked = mark < Math.ceil(data.value);
|
||||
const remainder = mark === Math.ceil(data.value) - 1 ? data.value % 1 : 0;
|
||||
const color = !marked ? emptyColor : remainder ? emptyColor.mix(fillColor, remainder) : fillColor;
|
||||
const marked = mark + 1 <= data.value;
|
||||
const color = marked ? fillColor : foundry.utils.Color.fromRGB([0, 0, 0]);
|
||||
if (mark === 0 || mark === sections.length - 1) {
|
||||
bar.beginFill(color, marked ? 1.0 : 0.5).drawRect(x, 0, widthUnit, bh, 2 * s); // Would like drawRoundedRect, but it's very troublsome with the corners. Leaving for now.
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1 +1,16 @@
|
|||
export default class DhTokenLayer extends foundry.canvas.layers.TokenLayer {}
|
||||
export default class DhTokenLayer extends foundry.canvas.layers.TokenLayer {
|
||||
async _createPreview(createData, options) {
|
||||
if (options.actor) {
|
||||
const tokenSizes = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes;
|
||||
if (options.actor?.system.metadata.usesSize) {
|
||||
const tokenSize = tokenSizes[options.actor.system.size];
|
||||
if (tokenSize && options.actor.system.size !== CONFIG.DH.ACTOR.tokenSize.custom.id) {
|
||||
createData.width = tokenSize;
|
||||
createData.height = tokenSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return super._createPreview(createData, options);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -115,10 +115,3 @@ export const advantageState = {
|
|||
value: 1
|
||||
}
|
||||
};
|
||||
|
||||
export const areaTypes = {
|
||||
placed: {
|
||||
id: 'placed',
|
||||
label: 'Placed Area'
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -70,40 +70,10 @@ export const range = {
|
|||
}
|
||||
};
|
||||
|
||||
export const groupAttackRange = {
|
||||
melee: range.melee,
|
||||
veryClose: range.veryClose,
|
||||
close: range.close,
|
||||
far: range.far,
|
||||
veryFar: range.veryFar
|
||||
};
|
||||
|
||||
/* circle|cone|rect|ray used to be CONST.MEASURED_TEMPLATE_TYPES. Hardcoded for now */
|
||||
export const templateTypes = {
|
||||
circle: {
|
||||
id: 'circle',
|
||||
label: 'Circle'
|
||||
},
|
||||
cone: {
|
||||
id: 'cone',
|
||||
label: 'Cone'
|
||||
},
|
||||
rectangle: {
|
||||
id: 'rectangle',
|
||||
label: 'Rectangle'
|
||||
},
|
||||
line: {
|
||||
id: 'line',
|
||||
label: 'Line'
|
||||
},
|
||||
emanation: {
|
||||
id: 'emanation',
|
||||
label: 'Emanation'
|
||||
},
|
||||
inFront: {
|
||||
id: 'inFront',
|
||||
label: 'In Front'
|
||||
}
|
||||
...CONST.MEASURED_TEMPLATE_TYPES,
|
||||
EMANATION: 'emanation',
|
||||
INFRONT: 'inFront'
|
||||
};
|
||||
|
||||
export const rangeInclusion = {
|
||||
|
|
@ -271,8 +241,8 @@ export const defaultRestOptions = {
|
|||
type: 'friendly'
|
||||
},
|
||||
damage: {
|
||||
parts: {
|
||||
hitPoints: {
|
||||
parts: [
|
||||
{
|
||||
applyTo: healingTypes.hitPoints.id,
|
||||
value: {
|
||||
custom: {
|
||||
|
|
@ -281,7 +251,7 @@ export const defaultRestOptions = {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -305,8 +275,8 @@ export const defaultRestOptions = {
|
|||
type: 'self'
|
||||
},
|
||||
damage: {
|
||||
parts: {
|
||||
stress: {
|
||||
parts: [
|
||||
{
|
||||
applyTo: healingTypes.stress.id,
|
||||
value: {
|
||||
custom: {
|
||||
|
|
@ -315,7 +285,7 @@ export const defaultRestOptions = {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -340,8 +310,8 @@ export const defaultRestOptions = {
|
|||
type: 'friendly'
|
||||
},
|
||||
damage: {
|
||||
parts: {
|
||||
armor: {
|
||||
parts: [
|
||||
{
|
||||
applyTo: healingTypes.armor.id,
|
||||
value: {
|
||||
custom: {
|
||||
|
|
@ -350,7 +320,7 @@ export const defaultRestOptions = {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -374,8 +344,8 @@ export const defaultRestOptions = {
|
|||
type: 'self'
|
||||
},
|
||||
damage: {
|
||||
parts: {
|
||||
hope: {
|
||||
parts: [
|
||||
{
|
||||
applyTo: healingTypes.hope.id,
|
||||
value: {
|
||||
custom: {
|
||||
|
|
@ -384,7 +354,7 @@ export const defaultRestOptions = {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
prepareWithFriends: {
|
||||
|
|
@ -398,8 +368,8 @@ export const defaultRestOptions = {
|
|||
type: 'self'
|
||||
},
|
||||
damage: {
|
||||
parts: {
|
||||
hope: {
|
||||
parts: [
|
||||
{
|
||||
applyTo: healingTypes.hope.id,
|
||||
value: {
|
||||
custom: {
|
||||
|
|
@ -408,7 +378,7 @@ export const defaultRestOptions = {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -435,8 +405,8 @@ export const defaultRestOptions = {
|
|||
type: 'friendly'
|
||||
},
|
||||
damage: {
|
||||
parts: {
|
||||
hitPoints: {
|
||||
parts: [
|
||||
{
|
||||
applyTo: healingTypes.hitPoints.id,
|
||||
value: {
|
||||
custom: {
|
||||
|
|
@ -445,7 +415,7 @@ export const defaultRestOptions = {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -469,8 +439,8 @@ export const defaultRestOptions = {
|
|||
type: 'self'
|
||||
},
|
||||
damage: {
|
||||
parts: {
|
||||
stress: {
|
||||
parts: [
|
||||
{
|
||||
applyTo: healingTypes.stress.id,
|
||||
value: {
|
||||
custom: {
|
||||
|
|
@ -479,7 +449,7 @@ export const defaultRestOptions = {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -504,17 +474,17 @@ export const defaultRestOptions = {
|
|||
type: 'friendly'
|
||||
},
|
||||
damage: {
|
||||
parts: {
|
||||
armor: {
|
||||
parts: [
|
||||
{
|
||||
applyTo: healingTypes.armor.id,
|
||||
value: {
|
||||
custom: {
|
||||
enabled: true,
|
||||
formula: '@system.armorScore.max'
|
||||
formula: '@system.armorScore'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -538,8 +508,8 @@ export const defaultRestOptions = {
|
|||
type: 'self'
|
||||
},
|
||||
damage: {
|
||||
parts: {
|
||||
hope: {
|
||||
parts: [
|
||||
{
|
||||
applyTo: healingTypes.hope.id,
|
||||
value: {
|
||||
custom: {
|
||||
|
|
@ -548,7 +518,7 @@ export const defaultRestOptions = {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
prepareWithFriends: {
|
||||
|
|
@ -562,8 +532,8 @@ export const defaultRestOptions = {
|
|||
type: 'self'
|
||||
},
|
||||
damage: {
|
||||
parts: {
|
||||
hope: {
|
||||
parts: [
|
||||
{
|
||||
applyTo: healingTypes.hope.id,
|
||||
value: {
|
||||
custom: {
|
||||
|
|
@ -572,7 +542,7 @@ export const defaultRestOptions = {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -734,14 +704,14 @@ const getDiceSoNiceSFX = sfxOptions => {
|
|||
if (sfxOptions.critical && criticalAnimationData.class) {
|
||||
return {
|
||||
specialEffect: criticalAnimationData.class,
|
||||
options: { ...criticalAnimationData.options }
|
||||
options: {}
|
||||
};
|
||||
}
|
||||
|
||||
if (sfxOptions.higher && sfxOptions.data.higher) {
|
||||
return {
|
||||
specialEffect: sfxOptions.data.higher.class,
|
||||
options: { ...sfxOptions.data.higher.options }
|
||||
options: {}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -984,144 +954,3 @@ export const sceneRangeMeasurementSetting = {
|
|||
label: 'DAGGERHEART.CONFIG.SceneRangeMeasurementTypes.custom'
|
||||
}
|
||||
};
|
||||
|
||||
export const tagTeamRollTypes = {
|
||||
trait: {
|
||||
id: 'trait',
|
||||
label: 'DAGGERHEART.CONFIG.TagTeamRollTypes.trait'
|
||||
},
|
||||
ability: {
|
||||
id: 'ability',
|
||||
label: 'DAGGERHEART.CONFIG.TagTeamRollTypes.ability'
|
||||
},
|
||||
damageAbility: {
|
||||
id: 'damageAbility',
|
||||
label: 'DAGGERHEART.CONFIG.TagTeamRollTypes.damageAbility'
|
||||
}
|
||||
};
|
||||
|
||||
export const baseActiveEffectModes = {
|
||||
custom: {
|
||||
id: 'custom',
|
||||
priority: 0,
|
||||
label: 'EFFECT.CHANGES.TYPES.custom'
|
||||
},
|
||||
multiply: {
|
||||
id: 'multiply',
|
||||
priority: 10,
|
||||
label: 'EFFECT.CHANGES.TYPES.multiply'
|
||||
},
|
||||
add: {
|
||||
id: 'add',
|
||||
priority: 20,
|
||||
label: 'EFFECT.CHANGES.TYPES.add'
|
||||
},
|
||||
subtract: {
|
||||
id: 'subtract',
|
||||
priority: 20,
|
||||
label: 'EFFECT.CHANGES.TYPES.subtract'
|
||||
},
|
||||
downgrade: {
|
||||
id: 'downgrade',
|
||||
priority: 30,
|
||||
label: 'EFFECT.CHANGES.TYPES.downgrade'
|
||||
},
|
||||
upgrade: {
|
||||
id: 'upgrade',
|
||||
priority: 40,
|
||||
label: 'EFFECT.CHANGES.TYPES.upgrade'
|
||||
},
|
||||
override: {
|
||||
id: 'override',
|
||||
priority: 50,
|
||||
label: 'EFFECT.CHANGES.TYPES.override'
|
||||
}
|
||||
};
|
||||
|
||||
export const activeEffectModes = {
|
||||
armor: {
|
||||
id: 'armor',
|
||||
priority: 20,
|
||||
label: 'TYPES.ActiveEffect.armor'
|
||||
},
|
||||
...baseActiveEffectModes
|
||||
};
|
||||
|
||||
export const activeEffectArmorInteraction = {
|
||||
none: { id: 'none', label: 'DAGGERHEART.CONFIG.ArmorInteraction.none.label' },
|
||||
active: { id: 'active', label: 'DAGGERHEART.CONFIG.ArmorInteraction.active.label' },
|
||||
inactive: { id: 'inactive', label: 'DAGGERHEART.CONFIG.ArmorInteraction.inactive.label' }
|
||||
};
|
||||
|
||||
export const activeEffectDurations = {
|
||||
temporary: {
|
||||
id: 'temporary',
|
||||
label: 'DAGGERHEART.CONFIG.ActiveEffectDuration.temporary'
|
||||
},
|
||||
act: {
|
||||
id: 'act',
|
||||
label: 'DAGGERHEART.CONFIG.ActiveEffectDuration.act'
|
||||
},
|
||||
scene: {
|
||||
id: 'scene',
|
||||
label: 'DAGGERHEART.CONFIG.ActiveEffectDuration.scene'
|
||||
},
|
||||
shortRest: {
|
||||
id: 'shortRest',
|
||||
label: 'DAGGERHEART.CONFIG.ActiveEffectDuration.shortRest'
|
||||
},
|
||||
longRest: {
|
||||
id: 'longRest',
|
||||
label: 'DAGGERHEART.CONFIG.ActiveEffectDuration.longRest'
|
||||
},
|
||||
session: {
|
||||
id: 'session',
|
||||
label: 'DAGGERHEART.CONFIG.ActiveEffectDuration.session'
|
||||
},
|
||||
custom: {
|
||||
id: 'custom',
|
||||
label: 'DAGGERHEART.CONFIG.ActiveEffectDuration.custom'
|
||||
}
|
||||
};
|
||||
|
||||
export const fallAndCollisionDamage = {
|
||||
veryClose: {
|
||||
id: 'veryClose',
|
||||
label: 'DAGGERHEART.CONFIG.fallAndCollision.veryClose.label',
|
||||
chatTitle: 'DAGGERHEART.CONFIG.fallAndCollision.veryClose.chatTitle',
|
||||
damageFormula: '1d10 + 3'
|
||||
},
|
||||
close: {
|
||||
id: 'veryClose',
|
||||
label: 'DAGGERHEART.CONFIG.fallAndCollision.close.label',
|
||||
chatTitle: 'DAGGERHEART.CONFIG.fallAndCollision.close.chatTitle',
|
||||
damageFormula: '1d20 + 5'
|
||||
},
|
||||
far: {
|
||||
id: 'veryClose',
|
||||
label: 'DAGGERHEART.CONFIG.fallAndCollision.far.label',
|
||||
chatTitle: 'DAGGERHEART.CONFIG.fallAndCollision.far.chatTitle',
|
||||
damageFormula: '1d100 + 15'
|
||||
},
|
||||
collision: {
|
||||
id: 'veryClose',
|
||||
label: 'DAGGERHEART.CONFIG.fallAndCollision.collision.label',
|
||||
chatTitle: 'DAGGERHEART.CONFIG.fallAndCollision.collision.chatTitle',
|
||||
damageFormula: '1d20 + 5'
|
||||
}
|
||||
};
|
||||
|
||||
export const simpleDispositions = {
|
||||
[-1]: {
|
||||
id: -1,
|
||||
label: 'TOKEN.DISPOSITION.HOSTILE'
|
||||
},
|
||||
[0]: {
|
||||
id: 0,
|
||||
label: 'TOKEN.DISPOSITION.NEUTRAL'
|
||||
},
|
||||
[1]: {
|
||||
id: 1,
|
||||
label: 'TOKEN.DISPOSITION.FRIENDLY'
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
export const hooksConfig = {
|
||||
effectDisplayToggle: 'DHEffectDisplayToggle',
|
||||
lockedTooltipDismissed: 'DHLockedTooltipDismissed',
|
||||
tagTeamStart: 'DHTagTeamRollStart',
|
||||
groupRollStart: 'DHGroupRollStart'
|
||||
lockedTooltipDismissed: 'DHLockedTooltipDismissed'
|
||||
};
|
||||
|
|
|
|||
|
|
@ -70,65 +70,17 @@ export const typeConfig = {
|
|||
}
|
||||
]
|
||||
},
|
||||
environments: {
|
||||
columns: [
|
||||
{
|
||||
key: 'system.tier',
|
||||
label: 'DAGGERHEART.GENERAL.Tiers.singular'
|
||||
},
|
||||
{
|
||||
key: 'system.type',
|
||||
label: 'DAGGERHEART.GENERAL.type',
|
||||
format: type => {
|
||||
if (!type) return '-';
|
||||
|
||||
return CONFIG.DH.ACTOR.environmentTypes[type].label;
|
||||
}
|
||||
}
|
||||
],
|
||||
filters: [
|
||||
{
|
||||
key: 'system.tier',
|
||||
label: 'DAGGERHEART.GENERAL.Tiers.singular',
|
||||
field: 'system.api.models.actors.DhEnvironment.schema.fields.tier'
|
||||
},
|
||||
{
|
||||
key: 'system.type',
|
||||
label: 'DAGGERHEART.GENERAL.type',
|
||||
field: 'system.api.models.actors.DhEnvironment.schema.fields.type'
|
||||
},
|
||||
{
|
||||
key: 'system.difficulty',
|
||||
name: 'difficulty.min',
|
||||
label: 'DAGGERHEART.UI.ItemBrowser.difficultyMin',
|
||||
field: 'system.api.models.actors.DhEnvironment.schema.fields.difficulty',
|
||||
operator: 'gte'
|
||||
},
|
||||
{
|
||||
key: 'system.difficulty',
|
||||
name: 'difficulty.max',
|
||||
label: 'DAGGERHEART.UI.ItemBrowser.difficultyMax',
|
||||
field: 'system.api.models.actors.DhEnvironment.schema.fields.difficulty',
|
||||
operator: 'lte'
|
||||
}
|
||||
]
|
||||
},
|
||||
items: {
|
||||
columns: [
|
||||
{
|
||||
key: 'type',
|
||||
label: 'DAGGERHEART.GENERAL.type',
|
||||
format: type => (type ? `TYPES.Item.${type}` : '-')
|
||||
format: type => type ? `TYPES.Item.${type}` : '-'
|
||||
},
|
||||
{
|
||||
key: 'system.secondary',
|
||||
label: 'DAGGERHEART.UI.ItemBrowser.subtype',
|
||||
format: isSecondary =>
|
||||
isSecondary
|
||||
? 'DAGGERHEART.ITEMS.Weapon.secondaryWeapon.short'
|
||||
: isSecondary === false
|
||||
? 'DAGGERHEART.ITEMS.Weapon.primaryWeapon.short'
|
||||
: '-'
|
||||
format: isSecondary => (isSecondary ? 'DAGGERHEART.ITEMS.Weapon.secondaryWeapon.short' : isSecondary === false ? 'DAGGERHEART.ITEMS.Weapon.primaryWeapon.short' : '-')
|
||||
},
|
||||
{
|
||||
key: 'system.tier',
|
||||
|
|
@ -220,8 +172,8 @@ export const typeConfig = {
|
|||
key: 'system.secondary',
|
||||
label: 'DAGGERHEART.UI.ItemBrowser.subtype',
|
||||
choices: [
|
||||
{ value: false, label: 'DAGGERHEART.ITEMS.Weapon.primaryWeapon.full' },
|
||||
{ value: true, label: 'DAGGERHEART.ITEMS.Weapon.secondaryWeapon.full' }
|
||||
{ value: false, label: 'DAGGERHEART.ITEMS.Weapon.primaryWeapon.short' },
|
||||
{ value: true, label: 'DAGGERHEART.ITEMS.Weapon.secondaryWeapon.short' }
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -308,12 +260,12 @@ export const typeConfig = {
|
|||
{
|
||||
key: 'system.type',
|
||||
label: 'DAGGERHEART.GENERAL.type',
|
||||
format: type => (type ? `DAGGERHEART.CONFIG.DomainCardTypes.${type}` : '-')
|
||||
format: type => type ? `DAGGERHEART.CONFIG.DomainCardTypes.${type}` : '-'
|
||||
},
|
||||
{
|
||||
key: 'system.domain',
|
||||
label: 'DAGGERHEART.GENERAL.Domain.single',
|
||||
format: domain => (domain ? CONFIG.DH.DOMAIN.allDomains()[domain].label : '-')
|
||||
format: domain => domain ? CONFIG.DH.DOMAIN.allDomains()[domain].label : '-'
|
||||
},
|
||||
{
|
||||
key: 'system.level',
|
||||
|
|
@ -426,8 +378,7 @@ export const typeConfig = {
|
|||
{
|
||||
key: 'system.linkedClass',
|
||||
label: 'TYPES.Item.class',
|
||||
format: linkedClass =>
|
||||
foundry.utils.fromUuidSync(linkedClass)?.name ?? 'DAGGERHEART.UI.ItemBrowser.missing'
|
||||
format: linkedClass => linkedClass?.name ?? 'DAGGERHEART.UI.ItemBrowser.missing'
|
||||
},
|
||||
{
|
||||
key: 'system.spellcastingTrait',
|
||||
|
|
@ -437,20 +388,15 @@ export const typeConfig = {
|
|||
],
|
||||
filters: [
|
||||
{
|
||||
key: 'system.linkedClass',
|
||||
key: 'system.linkedClass.uuid',
|
||||
label: 'TYPES.Item.class',
|
||||
choices: async items => {
|
||||
const list = [];
|
||||
for (const item of items.filter(item => item.system.linkedClass)) {
|
||||
const linkedClass = await foundry.utils.fromUuid(item.system.linkedClass);
|
||||
if (linkedClass) {
|
||||
list.push({
|
||||
value: linkedClass.uuid,
|
||||
label: linkedClass.name
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
|
@ -604,8 +550,7 @@ export const compendiumConfig = {
|
|||
id: 'environments',
|
||||
keys: ['environments'],
|
||||
label: 'DAGGERHEART.UI.ItemBrowser.folders.environments',
|
||||
type: ['environment'],
|
||||
listType: 'environments'
|
||||
type: ['environment']
|
||||
},
|
||||
beastforms: {
|
||||
id: 'beastforms',
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ export const armorFeatures = {
|
|||
type: 'hostile'
|
||||
},
|
||||
damage: {
|
||||
parts: {
|
||||
stress: {
|
||||
parts: [
|
||||
{
|
||||
applyTo: 'stress',
|
||||
value: {
|
||||
custom: {
|
||||
|
|
@ -24,7 +24,7 @@ export const armorFeatures = {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -453,7 +453,7 @@ export const allArmorFeatures = () => {
|
|||
const feature = homebrewFeatures[key];
|
||||
const actions = feature.actions.map(action => ({
|
||||
...action,
|
||||
effects: action.effects?.map(effect => feature.effects.find(x => x.id === effect._id)) ?? [],
|
||||
effects: action.effects?.map(effect => feature.effects.find(x => x.id === effect._id))??[],
|
||||
type: action.type
|
||||
}));
|
||||
const actionEffects = actions.flatMap(a => a.effects);
|
||||
|
|
@ -489,18 +489,15 @@ export const weaponFeatures = {
|
|||
description: 'DAGGERHEART.CONFIG.WeaponFeature.barrier.effects.barrier.description',
|
||||
img: 'icons/skills/melee/shield-block-bash-blue.webp',
|
||||
changes: [
|
||||
{
|
||||
key: 'system.armorScore',
|
||||
mode: 2,
|
||||
value: 'ITEM.@system.tier + 1'
|
||||
},
|
||||
{
|
||||
key: 'system.evasion',
|
||||
mode: 2,
|
||||
value: '-1'
|
||||
},
|
||||
{
|
||||
key: 'Armor',
|
||||
type: 'armor',
|
||||
typeData: {
|
||||
type: 'armor',
|
||||
max: 'ITEM.@system.tier + 1'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -735,8 +732,8 @@ export const weaponFeatures = {
|
|||
type: 'hostile'
|
||||
},
|
||||
damage: {
|
||||
parts: {
|
||||
stress: {
|
||||
parts: [
|
||||
{
|
||||
applyTo: 'stress',
|
||||
value: {
|
||||
custom: {
|
||||
|
|
@ -745,7 +742,7 @@ export const weaponFeatures = {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -792,6 +789,11 @@ export const weaponFeatures = {
|
|||
description: 'DAGGERHEART.CONFIG.WeaponFeature.doubleDuty.effects.doubleDuty.description',
|
||||
img: 'icons/skills/melee/sword-shield-stylized-white.webp',
|
||||
changes: [
|
||||
{
|
||||
key: 'system.armorScore',
|
||||
mode: 2,
|
||||
value: '1'
|
||||
},
|
||||
{
|
||||
key: 'system.bonuses.damage.primaryWeapon.bonus',
|
||||
mode: 2,
|
||||
|
|
@ -806,22 +808,6 @@ export const weaponFeatures = {
|
|||
type: 'withinRange'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.doubleDuty.effects.doubleDuty.name',
|
||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.doubleDuty.effects.doubleDuty.description',
|
||||
img: 'icons/skills/melee/sword-shield-stylized-white.webp',
|
||||
changes: [
|
||||
{
|
||||
key: 'Armor',
|
||||
type: 'armor',
|
||||
value: 0,
|
||||
typeData: {
|
||||
type: 'armor',
|
||||
max: 1
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -928,8 +914,8 @@ export const weaponFeatures = {
|
|||
type: 'self'
|
||||
},
|
||||
damage: {
|
||||
parts: {
|
||||
hitPoints: {
|
||||
parts: [
|
||||
{
|
||||
applyTo: 'hitPoints',
|
||||
value: {
|
||||
custom: {
|
||||
|
|
@ -938,7 +924,7 @@ export const weaponFeatures = {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -1205,13 +1191,9 @@ export const weaponFeatures = {
|
|||
img: 'icons/skills/melee/shield-block-gray-orange.webp',
|
||||
changes: [
|
||||
{
|
||||
key: 'Armor',
|
||||
type: 'armor',
|
||||
value: 0,
|
||||
typeData: {
|
||||
type: 'armor',
|
||||
max: 'ITEM.@system.tier'
|
||||
}
|
||||
key: 'system.armorScore',
|
||||
mode: 2,
|
||||
value: 'ITEM.@system.tier'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1407,7 +1389,7 @@ export const allWeaponFeatures = () => {
|
|||
|
||||
const actions = feature.actions.map(action => ({
|
||||
...action,
|
||||
effects: action.effects?.map(effect => feature.effects.find(x => x.id === effect._id)) ?? [],
|
||||
effects: action.effects?.map(effect => feature.effects.find(x => x.id === effect._id))??[],
|
||||
type: action.type
|
||||
}));
|
||||
const actionEffects = actions.flatMap(a => a.effects);
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ const companionBaseResources = Object.freeze({
|
|||
max: 3,
|
||||
reverse: true,
|
||||
label: 'DAGGERHEART.GENERAL.stress'
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const character = {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
export const keybindings = {
|
||||
spotlight: 'DHSpotlight',
|
||||
partySheet: 'DHPartySheet'
|
||||
spotlight: 'DHSpotlight'
|
||||
};
|
||||
|
||||
export const menu = {
|
||||
|
|
@ -39,10 +38,9 @@ export const gameSettings = {
|
|||
LevelTiers: 'LevelTiers',
|
||||
Countdowns: 'Countdowns',
|
||||
LastMigrationVersion: 'LastMigrationVersion',
|
||||
TagTeamRoll: 'TagTeamRoll',
|
||||
SpotlightRequestQueue: 'SpotlightRequestQueue',
|
||||
CompendiumBrowserSettings: 'CompendiumBrowserSettings',
|
||||
SpotlightTracker: 'SpotlightTracker',
|
||||
ActiveParty: 'ActiveParty'
|
||||
CompendiumBrowserSettings: 'CompendiumBrowserSettings'
|
||||
};
|
||||
|
||||
export const actionAutomationChoices = {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
export { default as DhCombat } from './combat.mjs';
|
||||
export { default as DhCombatant } from './combatant.mjs';
|
||||
export { default as DhTagTeamRoll } from './tagTeamRoll.mjs';
|
||||
export { default as DhRollTable } from './rollTable.mjs';
|
||||
export { default as RegisteredTriggers } from './registeredTriggers.mjs';
|
||||
export { default as CompendiumBrowserSettings } from './compendiumBrowserSettings.mjs';
|
||||
export { default as TagTeamData } from './tagTeamData.mjs';
|
||||
export { default as GroupRollData } from './groupRollData.mjs';
|
||||
export { default as SpotlightTracker } from './spotlightTracker.mjs';
|
||||
|
||||
export * as countdowns from './countdowns.mjs';
|
||||
export * as actions from './action/_module.mjs';
|
||||
|
|
@ -15,4 +13,3 @@ export * as chatMessages from './chat-message/_modules.mjs';
|
|||
export * as fields from './fields/_module.mjs';
|
||||
export * as items from './item/_module.mjs';
|
||||
export * as scenes from './scene/_module.mjs';
|
||||
export * as regionBehaviors from './regionBehavior/_module.mjs';
|
||||
|
|
|
|||
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