mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-04-21 23:13:39 +02:00
Compare commits
78 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3eda3c4c05 | ||
|
|
646e0debbd | ||
|
|
3cbc18f42b | ||
|
|
f850cbda76 | ||
|
|
f2ec5ef458 | ||
|
|
c683bc4352 | ||
|
|
fa04c9920f | ||
|
|
03110377e1 | ||
|
|
1fea8438ba | ||
|
|
4944722139 | ||
|
|
4b92001f97 | ||
|
|
2fde61a1d5 | ||
|
|
d9b322406d | ||
|
|
16c07d23bb | ||
|
|
91aff8b10d | ||
|
|
7e9385bc39 | ||
|
|
aa8771bf0d | ||
|
|
7d5cdeb09d | ||
|
|
8808e4646d | ||
|
|
a77d2088a0 | ||
|
|
c6335980ba | ||
|
|
1176328f62 | ||
| a62d28cd96 | |||
|
|
8d8dea81fe | ||
|
|
fb07938e54 | ||
|
|
c337338c8b | ||
|
|
f900011510 | ||
|
|
56a6613a73 | ||
|
|
e003db3ec1 | ||
|
|
66c90d69e3 | ||
|
|
a839ca0066 | ||
|
|
6ed975f5b7 | ||
|
|
e2c97a7b61 | ||
|
|
3ec013ff50 | ||
|
|
94f1fbdd9b | ||
|
|
f22b67367b | ||
|
|
8a0b1b8e22 | ||
|
|
1a57b55723 | ||
|
|
f910cf9795 | ||
|
|
6804bfe047 | ||
|
|
28d9254883 | ||
|
|
b076c2481b | ||
|
|
bdfc97bb3b | ||
|
|
e111f7c2ae | ||
|
|
bb179db758 | ||
|
|
bbc1781d01 | ||
|
|
a897037dc4 | ||
|
|
97636fa134 | ||
|
|
e7be2a7d2b | ||
|
|
9bea8d6a97 | ||
|
|
7ca420ae0e | ||
|
|
ae480157d1 | ||
|
|
b505e15eb2 | ||
|
|
087e69694c | ||
|
|
fad830580c | ||
|
|
4c2d31b2f4 | ||
|
|
67d142df3d | ||
|
|
fdfd8c5a8d | ||
|
|
dbcef140a2 | ||
|
|
90f4339898 | ||
|
|
0d7469801e | ||
|
|
70e21f34db | ||
|
|
7057504a9e | ||
|
|
331f1ebf75 | ||
|
|
f91c140d34 | ||
|
|
3a117ef117 | ||
|
|
01619ef067 | ||
|
|
622b38ac08 | ||
|
|
02cca277da | ||
|
|
3265613767 | ||
|
|
36ffe8a23a | ||
|
|
96eba49dc1 | ||
|
|
f92f9f7132 | ||
|
|
0747994686 | ||
|
|
56dc9afe8f | ||
|
|
0f1ac406df | ||
|
|
e8ac3012ad | ||
|
|
582a15be77 |
276 changed files with 7262 additions and 2985 deletions
|
|
@ -1,3 +1,5 @@
|
||||||
[*]
|
[*]
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
indent_style = spaces
|
indent_style = spaces
|
||||||
|
[*.yml]
|
||||||
|
indent_size = 2
|
||||||
|
|
|
||||||
42
.github/workflows/ci.yml
vendored
Normal file
42
.github/workflows/ci.yml
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
name: Project CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
workflow_dispatch:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node-version: [24.x]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node-version }}
|
||||||
|
|
||||||
|
- uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: 10
|
||||||
|
|
||||||
|
- name: Cache NPM Deps
|
||||||
|
id: cache-npm
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: node_modules/
|
||||||
|
key: npm-${{ hashFiles('package-lock.json') }}
|
||||||
|
|
||||||
|
- name: Install NPM Deps
|
||||||
|
if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }}
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Lint
|
||||||
|
run: npm run lint
|
||||||
2
.github/workflows/deploy.yml
vendored
2
.github/workflows/deploy.yml
vendored
|
|
@ -35,7 +35,7 @@ jobs:
|
||||||
env:
|
env:
|
||||||
version: ${{steps.get_version.outputs.version-without-v}}
|
version: ${{steps.get_version.outputs.version-without-v}}
|
||||||
url: https://github.com/${{github.repository}}
|
url: https://github.com/${{github.repository}}
|
||||||
manifest: https://github.com/${{github.repository}}/releases/latest/download/system.json
|
manifest: https://raw.githubusercontent.com/${{github.repository}}/v14/system.json
|
||||||
download: https://github.com/${{github.repository}}/releases/download/${{github.event.release.tag_name}}/system.zip
|
download: https://github.com/${{github.repository}}/releases/download/${{github.event.release.tag_name}}/system.zip
|
||||||
|
|
||||||
# Create a zip file with all files required by the module to add to the release
|
# Create a zip file with all files required by the module to add to the release
|
||||||
|
|
|
||||||
|
|
@ -1,78 +1,9 @@
|
||||||
# Contributing to Foundryborne
|
# Contributing to Daggerheart
|
||||||
|
|
||||||
Welcome! This is a community-driven project to bring [Daggerheart](https://www.daggerheart.com/) to [FoundryVTT](https://foundryvtt.com/) as a full system. We're excited to have you here and appreciate your interest in contributing.
|
Thank you for your interest in contributing to the Foundryborne project!
|
||||||
|
|
||||||
---
|
To ensure that all contributions align with our project goals and architectural standards, we ask that you **do not submit outside contributions without first receiving feedback from the development team.**
|
||||||
|
|
||||||
## 🤝 How to Contribute
|
If you have an idea or a fix you'd like to contribute, please start a discussion or open an issue first. We'd love to hear from you and collaborate on the best way to move forward!
|
||||||
|
|
||||||
We welcome contributions of all kinds:
|
Thank you for your understanding and support.
|
||||||
|
|
||||||
- Bug reports
|
|
||||||
- Feature suggestions
|
|
||||||
- Code contributions
|
|
||||||
- UI/UX mockups
|
|
||||||
- Documentation improvements
|
|
||||||
- Questions and discussions
|
|
||||||
|
|
||||||
Please be respectful and collaborative — we’re all here to build something great together.
|
|
||||||
|
|
||||||
### Community Translations
|
|
||||||
|
|
||||||
Please note that we are not accepting community translations in the main project. Instead, community translations should be published as a module.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧭 General Guidelines
|
|
||||||
|
|
||||||
- **Use GitHub Issues** to report bugs or propose features
|
|
||||||
- **Start a Discussion** for larger ideas or questions
|
|
||||||
- **Open a Pull Request** once you've confirmed your work aligns with project direction
|
|
||||||
- **Keep things modular and maintainable** — if you're not sure how to structure something, ask!
|
|
||||||
- **Orient your code on existing examples**, and feel free to suggest a standard if it makes things clearer
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🗂️ Project Structure
|
|
||||||
|
|
||||||
Please try to follow the general logic of the existing code when submitting PRs.
|
|
||||||
|
|
||||||
We encourage contributors to leave comments or open Discussions when proposing structural or organizational changes.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧾 Issue & PR Best Practices
|
|
||||||
|
|
||||||
**For Issues:**
|
|
||||||
|
|
||||||
- Use clear, descriptive titles
|
|
||||||
- Provide a concise explanation of the problem or idea
|
|
||||||
- Include reproduction steps or example scenarios if it's a bug
|
|
||||||
- Add screenshots or logs if helpful
|
|
||||||
|
|
||||||
**For Pull Requests:**
|
|
||||||
|
|
||||||
- Use a clear title summarizing the change
|
|
||||||
- Provide a brief description of what your code does and why
|
|
||||||
- Link to any related Issues
|
|
||||||
- Keep PRs focused — smaller is better
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔖 Labels and Boards
|
|
||||||
|
|
||||||
We use GitHub labels to help organize contributions. If your issue or PR relates to a specific category, feel free to tag it. There is also a GitHub Project Board to help track active work and priorities.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📣 Communication
|
|
||||||
|
|
||||||
Discussions are currently happening on GitHub — in Issues, PRs, and [GitHub Discussions](https://github.com/Foundryborne/daggerheart/discussions). You're welcome to use any of these, though we may consolidate to one in the future.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🤗 Thank You!
|
|
||||||
|
|
||||||
Whether you're fixing a typo or designing entire mechanics — every contribution matters. Thank you for helping bring _Daggerheart_ to life in FoundryVTT through **Foundryborne**!
|
|
||||||
|
|
||||||
🐸🛠️
|
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,11 @@ CONFIG.Dice.daggerheart = {
|
||||||
FateRoll: FateRoll
|
FateRoll: FateRoll
|
||||||
};
|
};
|
||||||
|
|
||||||
|
CONFIG.RegionBehavior.dataModels = {
|
||||||
|
...CONFIG.RegionBehavior.dataModels,
|
||||||
|
...data.regionBehaviors
|
||||||
|
};
|
||||||
|
|
||||||
Object.assign(CONFIG.Dice.termTypes, dice.diceTypes);
|
Object.assign(CONFIG.Dice.termTypes, dice.diceTypes);
|
||||||
|
|
||||||
CONFIG.Actor.documentClass = documents.DhpActor;
|
CONFIG.Actor.documentClass = documents.DhpActor;
|
||||||
|
|
@ -343,7 +348,20 @@ Hooks.on(CONFIG.DH.HOOKS.hooksConfig.tagTeamStart, async data => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Hooks.on(CONFIG.DH.HOOKS.hooksConfig.groupRollStart, async data => {
|
||||||
|
if (data.openForAllPlayers && data.partyId) {
|
||||||
|
const party = game.actors.get(data.partyId);
|
||||||
|
if (!party) return;
|
||||||
|
|
||||||
|
const dialog = new game.system.api.applications.dialogs.GroupRollDialog(party);
|
||||||
|
dialog.tabGroups.application = 'groupRoll';
|
||||||
|
await dialog.render({ force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const updateActorsRangeDependentEffects = async token => {
|
const updateActorsRangeDependentEffects = async token => {
|
||||||
|
if (!token) return;
|
||||||
|
|
||||||
const rangeMeasurement = game.settings.get(
|
const rangeMeasurement = game.settings.get(
|
||||||
CONFIG.DH.id,
|
CONFIG.DH.id,
|
||||||
CONFIG.DH.SETTINGS.gameSettings.variantRules
|
CONFIG.DH.SETTINGS.gameSettings.variantRules
|
||||||
|
|
|
||||||
14
eslint.config.mjs
Normal file
14
eslint.config.mjs
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import globals from 'globals';
|
||||||
|
import { defineConfig } from 'eslint/config';
|
||||||
|
import prettier from 'eslint-plugin-prettier';
|
||||||
|
|
||||||
|
export default defineConfig([
|
||||||
|
{ files: ['**/*.{js,mjs,cjs}'], languageOptions: { globals: globals.browser } },
|
||||||
|
{ plugins: { prettier } },
|
||||||
|
{
|
||||||
|
files: ['**/*.{js,mjs,cjs}'],
|
||||||
|
rules: {
|
||||||
|
'prettier/prettier': 'error'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]);
|
||||||
134
lang/en.json
134
lang/en.json
|
|
@ -74,9 +74,7 @@
|
||||||
"name": "Summon",
|
"name": "Summon",
|
||||||
"tooltip": "Create tokens in the scene.",
|
"tooltip": "Create tokens in the scene.",
|
||||||
"error": "You do not have permission to summon tokens or there is no active scene.",
|
"error": "You do not have permission to summon tokens or there is no active scene.",
|
||||||
"invalidDrop": "You can only drop Actor entities to summon.",
|
"invalidDrop": "You can only drop Actor entities to summon."
|
||||||
"chatMessageTitle": "Test2",
|
|
||||||
"chatMessageHeaderTitle": "Summoning"
|
|
||||||
},
|
},
|
||||||
"transform": {
|
"transform": {
|
||||||
"name": "Transform",
|
"name": "Transform",
|
||||||
|
|
@ -111,9 +109,18 @@
|
||||||
"customFormula": "Custom Formula",
|
"customFormula": "Custom Formula",
|
||||||
"formula": "Formula"
|
"formula": "Formula"
|
||||||
},
|
},
|
||||||
|
"area": {
|
||||||
|
"sectionTitle": "Areas",
|
||||||
|
"shape": "Shape",
|
||||||
|
"size": "Size"
|
||||||
|
},
|
||||||
"displayInChat": "Display in chat",
|
"displayInChat": "Display in chat",
|
||||||
"deleteTriggerTitle": "Delete Trigger",
|
"deleteTriggerTitle": "Delete Trigger",
|
||||||
"deleteTriggerContent": "Are you sure you want to delete the {trigger} trigger?"
|
"deleteTriggerContent": "Are you sure you want to delete the {trigger} trigger?",
|
||||||
|
"advantageState": "Advantage State",
|
||||||
|
"damageOnSave": "Damage on Save",
|
||||||
|
"useDefaultItemValues": "Use default Item values",
|
||||||
|
"itemDamageIsUsed": "Item Damage Is Used"
|
||||||
},
|
},
|
||||||
"RollField": {
|
"RollField": {
|
||||||
"diceRolling": {
|
"diceRolling": {
|
||||||
|
|
@ -125,10 +132,11 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Settings": {
|
"Settings": {
|
||||||
"attackBonus": "Attack Bonus",
|
"attackModifier": "Attack Modifier",
|
||||||
"attackName": "Attack Name",
|
"attackName": "Attack Name",
|
||||||
"criticalThreshold": "Critical Threshold",
|
"criticalThreshold": "Critical Threshold",
|
||||||
"includeBase": { "label": "Include Item Damage" },
|
"includeBase": { "label": "Use Item Damage" },
|
||||||
|
"groupAttack": { "label": "Group Attack" },
|
||||||
"multiplier": "Multiplier",
|
"multiplier": "Multiplier",
|
||||||
"saveHint": "Set a default Trait to enable Reaction Roll. It can be changed later in Reaction Roll Dialog.",
|
"saveHint": "Set a default Trait to enable Reaction Roll. It can be changed later in Reaction Roll Dialog.",
|
||||||
"resultBased": {
|
"resultBased": {
|
||||||
|
|
@ -160,7 +168,8 @@
|
||||||
"rangeDependence": {
|
"rangeDependence": {
|
||||||
"title": "Range Dependence"
|
"title": "Range Dependence"
|
||||||
},
|
},
|
||||||
"stacking": { "title": "Stacking" }
|
"stacking": { "title": "Stacking" },
|
||||||
|
"targetDispositions": "Affected Dispositions"
|
||||||
},
|
},
|
||||||
"RangeDependance": {
|
"RangeDependance": {
|
||||||
"hint": "Settings for an optional distance at which this effect should activate",
|
"hint": "Settings for an optional distance at which this effect should activate",
|
||||||
|
|
@ -207,7 +216,13 @@
|
||||||
"type": { "label": "Type" }
|
"type": { "label": "Type" }
|
||||||
},
|
},
|
||||||
"hordeDamage": "Horde Damage",
|
"hordeDamage": "Horde Damage",
|
||||||
"horderHp": "Horde/HP"
|
"horderHp": "Horde/HP",
|
||||||
|
"adversaryReactionRoll": {
|
||||||
|
"headerTitle": "Adversary Reaction Roll"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Base": {
|
||||||
|
"CannotAddType": "Cannot add {itemType} items to {actorType} actors."
|
||||||
},
|
},
|
||||||
"Character": {
|
"Character": {
|
||||||
"advantageSources": {
|
"advantageSources": {
|
||||||
|
|
@ -232,6 +247,8 @@
|
||||||
},
|
},
|
||||||
"defaultHopeDice": "Default Hope Dice",
|
"defaultHopeDice": "Default Hope Dice",
|
||||||
"defaultFearDice": "Default Fear Dice",
|
"defaultFearDice": "Default Fear Dice",
|
||||||
|
"defaultAdvantageDice": "Default Advantage Dice",
|
||||||
|
"defaultDisadvantageDice": "Default Disadvantage Dice",
|
||||||
"disadvantageSources": {
|
"disadvantageSources": {
|
||||||
"label": "Disadvantage Sources",
|
"label": "Disadvantage Sources",
|
||||||
"hint": "Add single words or short text as reminders and hints of what a character has disadvantage on."
|
"hint": "Add single words or short text as reminders and hints of what a character has disadvantage on."
|
||||||
|
|
@ -316,6 +333,22 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"newAdversary": "New Adversary"
|
"newAdversary": "New Adversary"
|
||||||
|
},
|
||||||
|
"Party": {
|
||||||
|
"Subtitle": {
|
||||||
|
"character": "{community} {ancestry} | {subclass} {class}",
|
||||||
|
"companion": "Companion of {partner}"
|
||||||
|
},
|
||||||
|
"RemoveConfirmation": {
|
||||||
|
"title": "Remove member {name}",
|
||||||
|
"text": "Are you sure you want to remove {name} from the party?"
|
||||||
|
},
|
||||||
|
"Thresholds": {
|
||||||
|
"minor": "MIN",
|
||||||
|
"major": "MAJ",
|
||||||
|
"severe": "SEV"
|
||||||
|
},
|
||||||
|
"triggerRestContent": "This will trigger a dialog to players make their downtime moves. Are you sure?"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"APPLICATIONS": {
|
"APPLICATIONS": {
|
||||||
|
|
@ -351,7 +384,7 @@
|
||||||
"selectSecondaryWeapon": "Select Secondary Weapon",
|
"selectSecondaryWeapon": "Select Secondary Weapon",
|
||||||
"selectSubclass": "Select Subclass",
|
"selectSubclass": "Select Subclass",
|
||||||
"setupSkipTitle": "Skipping Character Setup",
|
"setupSkipTitle": "Skipping Character Setup",
|
||||||
"setupSkipContent": "You are skipping the Character Setup by adding this manually. The character setup is the blinking arrows in the top-right. Are you sure you want to continue?",
|
"setupSkipContent": "You are skipping the Character Setup by adding this manually. The character setup is the blinking button in the top-right. Are you sure you want to continue?",
|
||||||
"startingItems": "Starting Items",
|
"startingItems": "Starting Items",
|
||||||
"story": "Story",
|
"story": "Story",
|
||||||
"storyExplanation": "Select which background and connection prompts you want to copy into your character's background.",
|
"storyExplanation": "Select which background and connection prompts you want to copy into your character's background.",
|
||||||
|
|
@ -449,6 +482,10 @@
|
||||||
"defaultOwnershipTooltip": "The default player ownership of countdowns",
|
"defaultOwnershipTooltip": "The default player ownership of countdowns",
|
||||||
"hideNewCountdowns": "Hide New Countdowns"
|
"hideNewCountdowns": "Hide New Countdowns"
|
||||||
},
|
},
|
||||||
|
"CreateItemDialog": {
|
||||||
|
"createItem": "Create Item",
|
||||||
|
"browseCompendium": "Browse Compendium"
|
||||||
|
},
|
||||||
"DaggerheartMenu": {
|
"DaggerheartMenu": {
|
||||||
"title": "GM Tools",
|
"title": "GM Tools",
|
||||||
"refreshFeatures": "Refresh Features",
|
"refreshFeatures": "Refresh Features",
|
||||||
|
|
@ -663,6 +700,12 @@
|
||||||
"noPlayers": "No players to assign ownership to",
|
"noPlayers": "No players to assign ownership to",
|
||||||
"default": "Default Ownership"
|
"default": "Default Ownership"
|
||||||
},
|
},
|
||||||
|
"PendingReactionsDialog": {
|
||||||
|
"title": "Pending Reaction Rolls Found",
|
||||||
|
"unfinishedRolls": "Some Tokens 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": {
|
"ReactionRoll": {
|
||||||
"title": "Reaction Roll: {trait}"
|
"title": "Reaction Roll: {trait}"
|
||||||
},
|
},
|
||||||
|
|
@ -688,7 +731,7 @@
|
||||||
"FIELDS": {
|
"FIELDS": {
|
||||||
"initiator": {
|
"initiator": {
|
||||||
"memberId": { "label": "Initiating Character" },
|
"memberId": { "label": "Initiating Character" },
|
||||||
"cost": { "label": "Initiation Cost" }
|
"cost": { "label": "Hope Cost" }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"leaderTitle": "Initiating Character",
|
"leaderTitle": "Initiating Character",
|
||||||
|
|
@ -715,6 +758,17 @@
|
||||||
"selectRoll": "Select which roll value to be used for the Tag Team"
|
"selectRoll": "Select which roll value to be used for the Tag Team"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"GroupRollSelect": {
|
||||||
|
"title": "Group Roll",
|
||||||
|
"aidingCharacters": "Aiding Characters",
|
||||||
|
"leader": "Leader",
|
||||||
|
"leaderRoll": "Leader Roll",
|
||||||
|
"openDialogForAll": "Open Dialog For All",
|
||||||
|
"startGroupRoll": "Start Group Roll",
|
||||||
|
"finishGroupRoll": "Finish Group Roll",
|
||||||
|
"cancelConfirmTitle": "Cancel Group Roll",
|
||||||
|
"cancelConfirmText": "Are you sure you want to cancel the Group Roll? This will close it for all other players too."
|
||||||
|
},
|
||||||
"TokenConfig": {
|
"TokenConfig": {
|
||||||
"actorSizeUsed": "Actor size is set, determining the dimensions"
|
"actorSizeUsed": "Actor size is set, determining the dimensions"
|
||||||
}
|
}
|
||||||
|
|
@ -735,6 +789,11 @@
|
||||||
"session": "Next Session",
|
"session": "Next Session",
|
||||||
"custom": "Custom"
|
"custom": "Custom"
|
||||||
},
|
},
|
||||||
|
"ActionAutomationChoices": {
|
||||||
|
"never": "Never",
|
||||||
|
"showDialog": "Show Dialog Only",
|
||||||
|
"always": "Always"
|
||||||
|
},
|
||||||
"AdversaryTrait": {
|
"AdversaryTrait": {
|
||||||
"relentless": {
|
"relentless": {
|
||||||
"name": "Relentless",
|
"name": "Relentless",
|
||||||
|
|
@ -1264,6 +1323,11 @@
|
||||||
"on": "On",
|
"on": "On",
|
||||||
"onWithToggle": "On With Toggle"
|
"onWithToggle": "On With Toggle"
|
||||||
},
|
},
|
||||||
|
"SceneRangeMeasurementTypes": {
|
||||||
|
"disable": "Disable Daggerheart Range Measurement",
|
||||||
|
"default": "Default",
|
||||||
|
"custom": "Custom"
|
||||||
|
},
|
||||||
"SelectAction": {
|
"SelectAction": {
|
||||||
"selectType": "Select Action Type",
|
"selectType": "Select Action Type",
|
||||||
"selectAction": "Action Selection"
|
"selectAction": "Action Selection"
|
||||||
|
|
@ -1963,6 +2027,10 @@
|
||||||
"hint": "Multiply any damage dealt to you by this number"
|
"hint": "Multiply any damage dealt to you by this number"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Battlepoints": {
|
||||||
|
"full": "Battlepoints",
|
||||||
|
"short": "BP"
|
||||||
|
},
|
||||||
"Bonuses": {
|
"Bonuses": {
|
||||||
"rest": {
|
"rest": {
|
||||||
"downtimeAction": "Downtime Action",
|
"downtimeAction": "Downtime Action",
|
||||||
|
|
@ -2373,10 +2441,13 @@
|
||||||
"maxWithThing": "Max {thing}",
|
"maxWithThing": "Max {thing}",
|
||||||
"missingDragDropThing": "Drop {thing} here",
|
"missingDragDropThing": "Drop {thing} here",
|
||||||
"multiclass": "Multiclass",
|
"multiclass": "Multiclass",
|
||||||
|
"name": "Name",
|
||||||
"newCategory": "New Category",
|
"newCategory": "New Category",
|
||||||
"newThing": "New {thing}",
|
"newThing": "New {thing}",
|
||||||
|
"next": "Next",
|
||||||
"none": "None",
|
"none": "None",
|
||||||
"noTarget": "No current target",
|
"noTarget": "No current target",
|
||||||
|
"optionalThing": "Optional {thing}",
|
||||||
"partner": "Partner",
|
"partner": "Partner",
|
||||||
"player": {
|
"player": {
|
||||||
"single": "Player",
|
"single": "Player",
|
||||||
|
|
@ -2403,9 +2474,11 @@
|
||||||
"rollDamage": "Roll Damage",
|
"rollDamage": "Roll Damage",
|
||||||
"rollWith": "{roll} Roll",
|
"rollWith": "{roll} Roll",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
|
"saveSettings": "Save Settings",
|
||||||
"scalable": "Scalable",
|
"scalable": "Scalable",
|
||||||
"scars": "Scars",
|
"scars": "Scars",
|
||||||
"situationalBonus": "Situational Bonus",
|
"situationalBonus": "Situational Bonus",
|
||||||
|
"searchPlaceholder": "Search...",
|
||||||
"spent": "Spent",
|
"spent": "Spent",
|
||||||
"step": "Step",
|
"step": "Step",
|
||||||
"stress": "Stress",
|
"stress": "Stress",
|
||||||
|
|
@ -2556,8 +2629,14 @@
|
||||||
},
|
},
|
||||||
"Weapon": {
|
"Weapon": {
|
||||||
"weaponType": "Weapon Type",
|
"weaponType": "Weapon Type",
|
||||||
"primaryWeapon": "Primary Weapon",
|
"primaryWeapon": {
|
||||||
"secondaryWeapon": "Secondary Weapon"
|
"full": "Primary Weapon",
|
||||||
|
"short": "Primary"
|
||||||
|
},
|
||||||
|
"secondaryWeapon": {
|
||||||
|
"full": "Secondary Weapon",
|
||||||
|
"short": "Secondary"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"MACROS": {
|
"MACROS": {
|
||||||
|
|
@ -2811,6 +2890,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Keybindings": {
|
"Keybindings": {
|
||||||
|
"partySheet": {
|
||||||
|
"name": "Toggle Party Sheet",
|
||||||
|
"hint": "Open or close the active party's sheet"
|
||||||
|
},
|
||||||
"spotlight": {
|
"spotlight": {
|
||||||
"name": "Spotlight Combatant",
|
"name": "Spotlight Combatant",
|
||||||
"hint": "Move the spotlight to a hovered or selected token that's present in an active encounter"
|
"hint": "Move the spotlight to a hovered or selected token that's present in an active encounter"
|
||||||
|
|
@ -2855,6 +2938,7 @@
|
||||||
"system": "Dice Preset",
|
"system": "Dice Preset",
|
||||||
"font": "Font",
|
"font": "Font",
|
||||||
"critical": "Duality Critical Animation",
|
"critical": "Duality Critical Animation",
|
||||||
|
"muted": "Muted",
|
||||||
"diceAppearance": "Dice Appearance",
|
"diceAppearance": "Dice Appearance",
|
||||||
"animations": "Animations",
|
"animations": "Animations",
|
||||||
"defaultAnimations": "Set Animations As Player Defaults",
|
"defaultAnimations": "Set Animations As Player Defaults",
|
||||||
|
|
@ -2963,18 +3047,6 @@
|
||||||
"immunityTo": "Immunity: {immunities}"
|
"immunityTo": "Immunity: {immunities}"
|
||||||
},
|
},
|
||||||
"featureTitle": "Class Feature",
|
"featureTitle": "Class Feature",
|
||||||
"groupRoll": {
|
|
||||||
"title": "Group Roll",
|
|
||||||
"leader": "Leader",
|
|
||||||
"partyTeam": "Party Team",
|
|
||||||
"team": "Team",
|
|
||||||
"selectLeader": "Select a Leader",
|
|
||||||
"selectMember": "Select a Member",
|
|
||||||
"rerollTitle": "Reroll Group Roll",
|
|
||||||
"rerollContent": "Are you sure you want to reroll your {trait} roll?",
|
|
||||||
"rerollTooltip": "Reroll",
|
|
||||||
"wholePartySelected": "The whole party is selected"
|
|
||||||
},
|
|
||||||
"healingRoll": {
|
"healingRoll": {
|
||||||
"title": "Heal - {damage}",
|
"title": "Heal - {damage}",
|
||||||
"heal": "Heal",
|
"heal": "Heal",
|
||||||
|
|
@ -2992,6 +3064,9 @@
|
||||||
"resourceRoll": {
|
"resourceRoll": {
|
||||||
"playerMessage": "{user} rerolled their {name}"
|
"playerMessage": "{user} rerolled their {name}"
|
||||||
},
|
},
|
||||||
|
"saveRoll": {
|
||||||
|
"reactionRollAllTargets": "Reaction Roll All Targets"
|
||||||
|
},
|
||||||
"tagTeam": {
|
"tagTeam": {
|
||||||
"title": "Tag Team",
|
"title": "Tag Team",
|
||||||
"membersTitle": "Members"
|
"membersTitle": "Members"
|
||||||
|
|
@ -3020,9 +3095,9 @@
|
||||||
},
|
},
|
||||||
"ItemBrowser": {
|
"ItemBrowser": {
|
||||||
"title": "Daggerheart Compendium Browser",
|
"title": "Daggerheart Compendium Browser",
|
||||||
|
"windowTitle": "Compendium Browser",
|
||||||
"hint": "Select a Folder in sidebar to start browsing through the compendium",
|
"hint": "Select a Folder in sidebar to start browsing through the compendium",
|
||||||
"browserSettings": "Browser Settings",
|
"browserSettings": "Browser Settings",
|
||||||
"searchPlaceholder": "Search...",
|
|
||||||
"columnName": "Name",
|
"columnName": "Name",
|
||||||
"tooltipFilters": "Filters",
|
"tooltipFilters": "Filters",
|
||||||
"tooltipErase": "Erase",
|
"tooltipErase": "Erase",
|
||||||
|
|
@ -3058,7 +3133,7 @@
|
||||||
"weapons": "Weapons",
|
"weapons": "Weapons",
|
||||||
"armors": "Armors",
|
"armors": "Armors",
|
||||||
"consumables": "Consumables",
|
"consumables": "Consumables",
|
||||||
"loots": "Loots"
|
"loots": "Loot"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Notifications": {
|
"Notifications": {
|
||||||
|
|
@ -3140,7 +3215,8 @@
|
||||||
"tokenActorsMissing": "[{names}] missing Actors",
|
"tokenActorsMissing": "[{names}] missing Actors",
|
||||||
"domainTouchRequirement": "This domain card requires {nr} {domain} cards in the loadout to be used",
|
"domainTouchRequirement": "This domain card requires {nr} {domain} cards in the loadout to be used",
|
||||||
"knowTheTide": "Know The Tide gained a token",
|
"knowTheTide": "Know The Tide gained a token",
|
||||||
"lackingItemTransferPermission": "User {user} lacks owner permission needed to transfer items to {target}"
|
"lackingItemTransferPermission": "User {user} lacks owner permission needed to transfer items to {target}",
|
||||||
|
"noTokenTargeted": "No token is targeted"
|
||||||
},
|
},
|
||||||
"Progress": {
|
"Progress": {
|
||||||
"migrationLabel": "Performing system migration. Please wait and do not close Foundry."
|
"migrationLabel": "Performing system migration. Please wait and do not close Foundry."
|
||||||
|
|
@ -3152,6 +3228,9 @@
|
||||||
"companion": "Level {level} - {partner}",
|
"companion": "Level {level} - {partner}",
|
||||||
"companionNoPartner": "No Partner",
|
"companionNoPartner": "No Partner",
|
||||||
"duplicateToNewTier": "Duplicate to New Tier",
|
"duplicateToNewTier": "Duplicate to New Tier",
|
||||||
|
"activateParty": "Make Active Party",
|
||||||
|
"partyIsActive": "Active",
|
||||||
|
"createAdversary": "Create Adversary",
|
||||||
"pickTierTitle": "Pick a new tier for this adversary"
|
"pickTierTitle": "Pick a new tier for this adversary"
|
||||||
},
|
},
|
||||||
"daggerheartMenu": {
|
"daggerheartMenu": {
|
||||||
|
|
@ -3163,6 +3242,7 @@
|
||||||
"Tooltip": {
|
"Tooltip": {
|
||||||
"disableEffect": "Disable Effect",
|
"disableEffect": "Disable Effect",
|
||||||
"enableEffect": "Enable Effect",
|
"enableEffect": "Enable Effect",
|
||||||
|
"edit": "Edit",
|
||||||
"openItemWorld": "Open Item World",
|
"openItemWorld": "Open Item World",
|
||||||
"openActorWorld": "Open Actor World",
|
"openActorWorld": "Open Actor World",
|
||||||
"sendToChat": "Send to Chat",
|
"sendToChat": "Send to Chat",
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,10 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
||||||
this.character = character;
|
this.character = character;
|
||||||
|
|
||||||
this.setup = {
|
this.setup = {
|
||||||
traits: this.character.system.traits,
|
traits: Object.keys(this.character.system.traits).reduce((acc, key) => {
|
||||||
|
acc[key] = { value: null };
|
||||||
|
return acc;
|
||||||
|
}, {}),
|
||||||
ancestryName: {
|
ancestryName: {
|
||||||
primary: '',
|
primary: '',
|
||||||
secondary: ''
|
secondary: ''
|
||||||
|
|
@ -377,8 +380,10 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
||||||
];
|
];
|
||||||
return Object.values(this.setup.traits).reduce((acc, x) => {
|
return Object.values(this.setup.traits).reduce((acc, x) => {
|
||||||
const index = traitCompareArray.indexOf(x.value);
|
const index = traitCompareArray.indexOf(x.value);
|
||||||
|
if (index === -1) return acc;
|
||||||
|
|
||||||
traitCompareArray.splice(index, 1);
|
traitCompareArray.splice(index, 1);
|
||||||
acc += index !== -1;
|
acc += 1;
|
||||||
return acc;
|
return acc;
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ export { default as OwnershipSelection } from './ownershipSelection.mjs';
|
||||||
export { default as RerollDamageDialog } from './rerollDamageDialog.mjs';
|
export { default as RerollDamageDialog } from './rerollDamageDialog.mjs';
|
||||||
export { default as ResourceDiceDialog } from './resourceDiceDialog.mjs';
|
export { default as ResourceDiceDialog } from './resourceDiceDialog.mjs';
|
||||||
export { default as ActionSelectionDialog } from './actionSelectionDialog.mjs';
|
export { default as ActionSelectionDialog } from './actionSelectionDialog.mjs';
|
||||||
export { default as GroupRollDialog } from './group-roll-dialog.mjs';
|
|
||||||
export { default as TagTeamDialog } from './tagTeamDialog.mjs';
|
export { default as TagTeamDialog } from './tagTeamDialog.mjs';
|
||||||
|
export { default as GroupRollDialog } from './groupRollDialog.mjs';
|
||||||
export { default as RiskItAllDialog } from './riskItAllDialog.mjs';
|
export { default as RiskItAllDialog } from './riskItAllDialog.mjs';
|
||||||
export { default as CompendiumBrowserSettingsDialog } from './CompendiumBrowserSettings.mjs';
|
export { default as CompendiumBrowserSettingsDialog } from './CompendiumBrowserSettings.mjs';
|
||||||
|
|
|
||||||
|
|
@ -72,8 +72,8 @@ export default class ActionSelectionDialog extends HandlebarsApplicationMixin(Ap
|
||||||
|
|
||||||
static async #onChooseAction(event, button) {
|
static async #onChooseAction(event, button) {
|
||||||
const { actionId } = button.dataset;
|
const { actionId } = button.dataset;
|
||||||
this.#action = this.#item.system.actionsList.find(a => a._id === actionId);
|
this.action = this.item.system.actionsList.find(a => a._id === actionId);
|
||||||
Object.defineProperty(this.#event, 'shiftKey', {
|
Object.defineProperty(this.event, 'shiftKey', {
|
||||||
get() {
|
get() {
|
||||||
return event.shiftKey;
|
return event.shiftKey;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,10 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
context.advantage = this.config.roll?.advantage;
|
context.advantage = this.config.roll?.advantage;
|
||||||
context.disadvantage = this.config.roll?.disadvantage;
|
context.disadvantage = this.config.roll?.disadvantage;
|
||||||
context.diceOptions = CONFIG.DH.GENERAL.diceTypes;
|
context.diceOptions = CONFIG.DH.GENERAL.diceTypes;
|
||||||
|
context.dieFaces = CONFIG.DH.GENERAL.dieFaces.reduce((acc, face) => {
|
||||||
|
acc[face] = `d${face}`;
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
context.isLite = this.config.roll?.lite;
|
context.isLite = this.config.roll?.lite;
|
||||||
context.extraFormula = this.config.extraFormula;
|
context.extraFormula = this.config.extraFormula;
|
||||||
context.formula = this.roll.constructFormula(this.config);
|
context.formula = this.roll.constructFormula(this.config);
|
||||||
|
|
@ -152,9 +156,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
}
|
}
|
||||||
if (this.config.uses) this.config.uses = foundry.utils.mergeObject(this.config.uses, rest.uses);
|
if (this.config.uses) this.config.uses = foundry.utils.mergeObject(this.config.uses, rest.uses);
|
||||||
if (rest.roll?.dice) {
|
if (rest.roll?.dice) {
|
||||||
Object.entries(rest.roll.dice).forEach(([key, value]) => {
|
this.roll = foundry.utils.mergeObject(this.roll, rest.roll.dice);
|
||||||
this.roll[key] = value;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if (rest.hasOwnProperty('trait')) {
|
if (rest.hasOwnProperty('trait')) {
|
||||||
this.config.roll.trait = rest.trait;
|
this.config.roll.trait = rest.trait;
|
||||||
|
|
@ -173,6 +175,15 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
this.disadvantage = advantage === -1;
|
this.disadvantage = advantage === -1;
|
||||||
|
|
||||||
this.config.roll.advantage = this.config.roll.advantage === advantage ? 0 : advantage;
|
this.config.roll.advantage = this.config.roll.advantage === advantage ? 0 : advantage;
|
||||||
|
|
||||||
|
if (this.config.roll.advantage === 1 && this.config.data.rules.roll.defaultAdvantageDice) {
|
||||||
|
const faces = Number.parseInt(this.config.data.rules.roll.defaultAdvantageDice);
|
||||||
|
this.roll.advantageFaces = Number.isNaN(faces) ? this.roll.advantageFaces : faces;
|
||||||
|
} else if (this.config.roll.advantage === -1 && this.config.data.rules.roll.defaultDisadvantageDice) {
|
||||||
|
const faces = Number.parseInt(this.config.data.rules.roll.defaultDisadvantageDice);
|
||||||
|
this.roll.advantageFaces = Number.isNaN(faces) ? this.roll.advantageFaces : faces;
|
||||||
|
}
|
||||||
|
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
toggleSelectedEffect: this.toggleSelectedEffect,
|
toggleSelectedEffect: this.toggleSelectedEffect,
|
||||||
|
updateGroupAttack: this.updateGroupAttack,
|
||||||
toggleCritical: this.toggleCritical,
|
toggleCritical: this.toggleCritical,
|
||||||
submitRoll: this.submitRoll
|
submitRoll: this.submitRoll
|
||||||
},
|
},
|
||||||
|
|
@ -64,15 +65,40 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
|
||||||
context.hasSelectedEffects = Boolean(Object.keys(this.selectedEffects).length);
|
context.hasSelectedEffects = Boolean(Object.keys(this.selectedEffects).length);
|
||||||
context.selectedEffects = this.selectedEffects;
|
context.selectedEffects = this.selectedEffects;
|
||||||
|
|
||||||
|
context.damageOptions = this.config.damageOptions;
|
||||||
|
context.rangeOptions = CONFIG.DH.GENERAL.groupAttackRange;
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
static updateRollConfiguration(_event, _, formData) {
|
static updateRollConfiguration(_event, _, formData) {
|
||||||
const { ...rest } = foundry.utils.expandObject(formData.object);
|
const data = foundry.utils.expandObject(formData.object);
|
||||||
foundry.utils.mergeObject(this.config.roll, rest.roll);
|
foundry.utils.mergeObject(this.config.roll, data.roll);
|
||||||
foundry.utils.mergeObject(this.config.modifiers, rest.modifiers);
|
foundry.utils.mergeObject(this.config.modifiers, data.modifiers);
|
||||||
this.config.selectedMessageMode = rest.selectedMessageMode;
|
this.config.selectedMessageMode = data.selectedMessageMode;
|
||||||
|
|
||||||
|
if (data.damageOptions) {
|
||||||
|
const numAttackers = data.damageOptions.groupAttack?.numAttackers;
|
||||||
|
if (typeof numAttackers !== 'number' || numAttackers % 1 !== 0) {
|
||||||
|
data.damageOptions.groupAttack.numAttackers = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
foundry.utils.mergeObject(this.config.damageOptions, data.damageOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
static updateGroupAttack() {
|
||||||
|
const targets = Array.from(game.user.targets);
|
||||||
|
if (targets.length === 0)
|
||||||
|
return ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.noTokenTargeted'));
|
||||||
|
|
||||||
|
const actorId = this.roll.data.parent.id;
|
||||||
|
const range = this.config.damageOptions.groupAttack.range;
|
||||||
|
const groupAttackTokens = game.system.api.fields.ActionFields.DamageField.getGroupAttackTokens(actorId, range);
|
||||||
|
|
||||||
|
this.config.damageOptions.groupAttack.numAttackers = groupAttackTokens.length;
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -259,8 +259,9 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
||||||
const resetValue = increasing
|
const resetValue = increasing
|
||||||
? 0
|
? 0
|
||||||
: feature.system.resource.max
|
: feature.system.resource.max
|
||||||
? Roll.replaceFormulaData(feature.system.resource.max, this.actor)
|
? new Roll(Roll.replaceFormulaData(feature.system.resource.max, this.actor)).evaluateSync().total
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
await feature.update({ 'system.resource.value': resetValue });
|
await feature.update({ 'system.resource.value': resetValue });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,204 +0,0 @@
|
||||||
import autocomplete from 'autocompleter';
|
|
||||||
import { abilities } from '../../config/actorConfig.mjs';
|
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
|
||||||
|
|
||||||
export default class GroupRollDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
|
||||||
constructor(actors) {
|
|
||||||
super();
|
|
||||||
this.actors = actors;
|
|
||||||
this.actorLeader = {};
|
|
||||||
this.actorsMembers = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
get title() {
|
|
||||||
return 'Group Roll';
|
|
||||||
}
|
|
||||||
|
|
||||||
static DEFAULT_OPTIONS = {
|
|
||||||
tag: 'form',
|
|
||||||
classes: ['daggerheart', 'views', 'dh-style', 'dialog', 'group-roll'],
|
|
||||||
position: { width: 'auto', height: 'auto' },
|
|
||||||
window: {
|
|
||||||
title: 'DAGGERHEART.UI.Chat.groupRoll.title'
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
roll: GroupRollDialog.#roll,
|
|
||||||
removeLeader: GroupRollDialog.#removeLeader,
|
|
||||||
removeMember: GroupRollDialog.#removeMember
|
|
||||||
},
|
|
||||||
form: { handler: this.updateData, submitOnChange: true, closeOnSubmit: false }
|
|
||||||
};
|
|
||||||
|
|
||||||
static PARTS = {
|
|
||||||
application: {
|
|
||||||
id: 'group-roll',
|
|
||||||
template: 'systems/daggerheart/templates/dialogs/group-roll/group-roll.hbs'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
_attachPartListeners(partId, htmlElement, options) {
|
|
||||||
super._attachPartListeners(partId, htmlElement, options);
|
|
||||||
const leaderChoices = this.actors.filter(x => this.actorsMembers.every(member => member.actor?.id !== x.id));
|
|
||||||
const memberChoices = this.actors.filter(
|
|
||||||
x => this.actorLeader?.actor?.id !== x.id && this.actorsMembers.every(member => member.actor?.id !== x.id)
|
|
||||||
);
|
|
||||||
|
|
||||||
htmlElement.querySelectorAll('.leader-change-input').forEach(element => {
|
|
||||||
autocomplete({
|
|
||||||
input: element,
|
|
||||||
fetch: function (text, update) {
|
|
||||||
if (!text) {
|
|
||||||
update(leaderChoices);
|
|
||||||
} else {
|
|
||||||
text = text.toLowerCase();
|
|
||||||
var suggestions = leaderChoices.filter(n => n.name.toLowerCase().includes(text));
|
|
||||||
update(suggestions);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
render: function (actor, search) {
|
|
||||||
const actorName = game.i18n.localize(actor.name);
|
|
||||||
const matchIndex = actorName.toLowerCase().indexOf(search);
|
|
||||||
|
|
||||||
const beforeText = actorName.slice(0, matchIndex);
|
|
||||||
const matchText = actorName.slice(matchIndex, matchIndex + search.length);
|
|
||||||
const after = actorName.slice(matchIndex + search.length, actorName.length);
|
|
||||||
const img = document.createElement('img');
|
|
||||||
img.src = actor.img;
|
|
||||||
|
|
||||||
const element = document.createElement('li');
|
|
||||||
element.appendChild(img);
|
|
||||||
|
|
||||||
const label = document.createElement('span');
|
|
||||||
label.innerHTML =
|
|
||||||
`${beforeText}${matchText ? `<strong>${matchText}</strong>` : ''}${after}`.replaceAll(
|
|
||||||
' ',
|
|
||||||
' '
|
|
||||||
);
|
|
||||||
element.appendChild(label);
|
|
||||||
|
|
||||||
return element;
|
|
||||||
},
|
|
||||||
renderGroup: function (label) {
|
|
||||||
const itemElement = document.createElement('div');
|
|
||||||
itemElement.textContent = game.i18n.localize(label);
|
|
||||||
return itemElement;
|
|
||||||
},
|
|
||||||
onSelect: actor => {
|
|
||||||
element.value = actor.uuid;
|
|
||||||
this.actorLeader = { actor: actor, trait: 'agility', difficulty: 0 };
|
|
||||||
this.render();
|
|
||||||
},
|
|
||||||
click: e => e.fetch(),
|
|
||||||
customize: function (_input, _inputRect, container) {
|
|
||||||
container.style.zIndex = foundry.applications.api.ApplicationV2._maxZ;
|
|
||||||
},
|
|
||||||
minLength: 0
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
htmlElement.querySelectorAll('.team-push-input').forEach(element => {
|
|
||||||
autocomplete({
|
|
||||||
input: element,
|
|
||||||
fetch: function (text, update) {
|
|
||||||
if (!text) {
|
|
||||||
update(memberChoices);
|
|
||||||
} else {
|
|
||||||
text = text.toLowerCase();
|
|
||||||
var suggestions = memberChoices.filter(n => n.name.toLowerCase().includes(text));
|
|
||||||
update(suggestions);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
render: function (actor, search) {
|
|
||||||
const actorName = game.i18n.localize(actor.name);
|
|
||||||
const matchIndex = actorName.toLowerCase().indexOf(search);
|
|
||||||
|
|
||||||
const beforeText = actorName.slice(0, matchIndex);
|
|
||||||
const matchText = actorName.slice(matchIndex, matchIndex + search.length);
|
|
||||||
const after = actorName.slice(matchIndex + search.length, actorName.length);
|
|
||||||
const img = document.createElement('img');
|
|
||||||
img.src = actor.img;
|
|
||||||
|
|
||||||
const element = document.createElement('li');
|
|
||||||
element.appendChild(img);
|
|
||||||
|
|
||||||
const label = document.createElement('span');
|
|
||||||
label.innerHTML =
|
|
||||||
`${beforeText}${matchText ? `<strong>${matchText}</strong>` : ''}${after}`.replaceAll(
|
|
||||||
' ',
|
|
||||||
' '
|
|
||||||
);
|
|
||||||
element.appendChild(label);
|
|
||||||
|
|
||||||
return element;
|
|
||||||
},
|
|
||||||
renderGroup: function (label) {
|
|
||||||
const itemElement = document.createElement('div');
|
|
||||||
itemElement.textContent = game.i18n.localize(label);
|
|
||||||
return itemElement;
|
|
||||||
},
|
|
||||||
onSelect: actor => {
|
|
||||||
element.value = actor.uuid;
|
|
||||||
this.actorsMembers.push({ actor: actor, trait: 'agility', difficulty: 0 });
|
|
||||||
this.render({ force: true });
|
|
||||||
},
|
|
||||||
click: e => e.fetch(),
|
|
||||||
customize: function (_input, _inputRect, container) {
|
|
||||||
container.style.zIndex = foundry.applications.api.ApplicationV2._maxZ;
|
|
||||||
},
|
|
||||||
minLength: 0
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async _prepareContext(_options) {
|
|
||||||
const context = await super._prepareContext(_options);
|
|
||||||
context.leader = this.actorLeader;
|
|
||||||
context.members = this.actorsMembers;
|
|
||||||
context.traitList = abilities;
|
|
||||||
|
|
||||||
context.allSelected = this.actorsMembers.length + (this.actorLeader?.actor ? 1 : 0) === this.actors.length;
|
|
||||||
context.rollDisabled = context.members.length === 0 || !this.actorLeader?.actor;
|
|
||||||
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
static updateData(event, _, formData) {
|
|
||||||
const { actorLeader, actorsMembers } = foundry.utils.expandObject(formData.object);
|
|
||||||
this.actorLeader = foundry.utils.mergeObject(this.actorLeader, actorLeader);
|
|
||||||
this.actorsMembers = foundry.utils.mergeObject(this.actorsMembers, actorsMembers);
|
|
||||||
this.render(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
static async #removeLeader(_, button) {
|
|
||||||
this.actorLeader = null;
|
|
||||||
this.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
static async #removeMember(_, button) {
|
|
||||||
this.actorsMembers = this.actorsMembers.filter(m => m.actor.uuid !== button.dataset.memberUuid);
|
|
||||||
this.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
static async #roll() {
|
|
||||||
const cls = getDocumentClass('ChatMessage');
|
|
||||||
const systemData = {
|
|
||||||
leader: this.actorLeader,
|
|
||||||
members: this.actorsMembers
|
|
||||||
};
|
|
||||||
const msg = {
|
|
||||||
type: 'groupRoll',
|
|
||||||
user: game.user.id,
|
|
||||||
speaker: cls.getSpeaker(),
|
|
||||||
title: game.i18n.localize('DAGGERHEART.UI.Chat.groupRoll.title'),
|
|
||||||
system: systemData,
|
|
||||||
content: await foundry.applications.handlebars.renderTemplate(
|
|
||||||
'systems/daggerheart/templates/ui/chat/groupRoll.hbs',
|
|
||||||
{ system: systemData }
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
cls.create(msg);
|
|
||||||
this.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
527
module/applications/dialogs/groupRollDialog.mjs
Normal file
527
module/applications/dialogs/groupRollDialog.mjs
Normal file
|
|
@ -0,0 +1,527 @@
|
||||||
|
import { ResourceUpdateMap } from '../../data/action/baseAction.mjs';
|
||||||
|
import { emitAsGM, 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();
|
||||||
|
|
||||||
|
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',
|
||||||
|
id: 'GroupRollDialog',
|
||||||
|
classes: ['daggerheart', 'views', 'dh-style', 'dialog', 'group-roll-dialog'],
|
||||||
|
position: { width: 550, height: 'auto' },
|
||||||
|
actions: {
|
||||||
|
toggleSelectMember: this.#toggleSelectMember,
|
||||||
|
startGroupRoll: this.#startGroupRoll,
|
||||||
|
makeRoll: this.#makeRoll,
|
||||||
|
removeRoll: this.#removeRoll,
|
||||||
|
rerollDice: this.#rerollDice,
|
||||||
|
makeLeaderRoll: this.#makeLeaderRoll,
|
||||||
|
removeLeaderRoll: this.#removeLeaderRoll,
|
||||||
|
rerollLeaderDice: this.#rerollLeaderDice,
|
||||||
|
markSuccessfull: this.#markSuccessfull,
|
||||||
|
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'
|
||||||
|
},
|
||||||
|
leader: {
|
||||||
|
id: 'leader',
|
||||||
|
template: 'systems/daggerheart/templates/dialogs/groupRollDialog/leader.hbs'
|
||||||
|
},
|
||||||
|
groupRoll: {
|
||||||
|
id: 'groupRoll',
|
||||||
|
template: 'systems/daggerheart/templates/dialogs/groupRollDialog/groupRoll.hbs'
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
id: 'footer',
|
||||||
|
template: 'systems/daggerheart/templates/dialogs/groupRollDialog/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 { initialization, leader, groupRoll, footer } = super._configureRenderParts(options);
|
||||||
|
const augmentedParts = { initialization };
|
||||||
|
for (const memberKey of Object.keys(this.party.system.groupRoll.aidingCharacters)) {
|
||||||
|
augmentedParts[memberKey] = {
|
||||||
|
id: memberKey,
|
||||||
|
template: 'systems/daggerheart/templates/dialogs/groupRollDialog/groupRollMember.hbs'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
augmentedParts.leader = leader;
|
||||||
|
augmentedParts.groupRoll = groupRoll;
|
||||||
|
augmentedParts.footer = footer;
|
||||||
|
|
||||||
|
return augmentedParts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**@inheritdoc */
|
||||||
|
async _onRender(context, options) {
|
||||||
|
await super._onRender(context, options);
|
||||||
|
|
||||||
|
if (this.element.querySelector('.team-container')) return;
|
||||||
|
|
||||||
|
if (this.tabGroups.application !== this.constructor.PARTS.initialization.id) {
|
||||||
|
const initializationPart = this.element.querySelector('.initialization-container');
|
||||||
|
initializationPart.insertAdjacentHTML('afterend', '<div class="team-container"></div>');
|
||||||
|
initializationPart.insertAdjacentHTML(
|
||||||
|
'afterend',
|
||||||
|
`<div class="section-title">${game.i18n.localize('DAGGERHEART.APPLICATIONS.GroupRollSelect.aidingCharacters')}</div>`
|
||||||
|
);
|
||||||
|
|
||||||
|
const teamContainer = this.element.querySelector('.team-container');
|
||||||
|
for (const memberContainer of this.element.querySelectorAll('.team-member-container'))
|
||||||
|
teamContainer.appendChild(memberContainer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async _prepareContext(_options) {
|
||||||
|
const context = await super._prepareContext(_options);
|
||||||
|
|
||||||
|
context.isGM = game.user.isGM;
|
||||||
|
context.isEditable = this.getIsEditable();
|
||||||
|
context.fields = this.party.system.schema.fields.groupRoll.fields;
|
||||||
|
context.data = this.party.system.groupRoll;
|
||||||
|
context.traitOptions = CONFIG.DH.ACTOR.abilities;
|
||||||
|
context.members = {};
|
||||||
|
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;
|
||||||
|
|
||||||
|
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 'leader':
|
||||||
|
partContext.leader = this.getRollCharacterData(this.party.system.groupRoll.leader);
|
||||||
|
break;
|
||||||
|
case 'groupRoll':
|
||||||
|
const leader = this.party.system.groupRoll.leader;
|
||||||
|
partContext.hasRolled =
|
||||||
|
leader?.rollData ||
|
||||||
|
Object.values(this.party.system.groupRoll?.aidingCharacters ?? {}).some(
|
||||||
|
x => x.successfull !== null
|
||||||
|
);
|
||||||
|
const { modifierTotal, modifiers } = Object.values(this.party.system.groupRoll.aidingCharacters).reduce(
|
||||||
|
(acc, curr) => {
|
||||||
|
const modifier = curr.successfull === true ? 1 : curr.successfull === 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.successfull !== 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);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...data,
|
||||||
|
roll: data.roll,
|
||||||
|
isEditable: actor.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER),
|
||||||
|
key: partId,
|
||||||
|
readyToRoll: Boolean(data.rollChoice),
|
||||||
|
hasRolled: Boolean(data.rollData)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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 emitAsGM(
|
||||||
|
GMUpdateEvent.UpdateDocument,
|
||||||
|
gmUpdate,
|
||||||
|
update,
|
||||||
|
this.party.uuid,
|
||||||
|
options.render ? { refreshType: RefreshType.GroupRoll, action: 'refresh', parts: updatingParts } : undefined
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getUpdatingParts(target) {
|
||||||
|
const { initialization, leader, groupRoll, footer } = this.constructor.PARTS;
|
||||||
|
const isInitialization = this.tabGroups.application === initialization.id;
|
||||||
|
const updatingMember = target.closest('.team-member-container')?.dataset?.memberKey;
|
||||||
|
const updatingLeader = target.closest('.main-character-outer-container');
|
||||||
|
|
||||||
|
return [
|
||||||
|
...(isInitialization ? [initialization.id] : []),
|
||||||
|
...(updatingMember ? [updatingMember] : []),
|
||||||
|
...(updatingLeader ? [leader.id] : []),
|
||||||
|
...(!isInitialization ? [groupRoll.id, footer.id] : [])
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
getIsEditable() {
|
||||||
|
return 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
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
|
||||||
|
|
||||||
|
async makeRoll(button, characterData, path) {
|
||||||
|
const actor = game.actors.find(x => x.id === characterData.id);
|
||||||
|
if (!actor) return;
|
||||||
|
|
||||||
|
const result = await actor.rollTrait(characterData.rollChoice, {
|
||||||
|
skips: {
|
||||||
|
createMessage: true,
|
||||||
|
resources: true,
|
||||||
|
triggers: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result) return;
|
||||||
|
if (!game.modules.get('dice-so-nice')?.active) foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice });
|
||||||
|
|
||||||
|
const rollData = result.messageRoll.toJSON();
|
||||||
|
delete rollData.options.messageRoll;
|
||||||
|
this.updatePartyData(
|
||||||
|
{
|
||||||
|
[path]: rollData
|
||||||
|
},
|
||||||
|
this.getUpdatingParts(button)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #makeRoll(_event, button) {
|
||||||
|
const { member } = button.dataset;
|
||||||
|
const character = this.party.system.groupRoll.aidingCharacters[member];
|
||||||
|
this.makeRoll(button, character, `system.groupRoll.aidingCharacters.${member}.rollData`);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #makeLeaderRoll(_event, button) {
|
||||||
|
const character = this.party.system.groupRoll.leader;
|
||||||
|
this.makeRoll(button, character, 'system.groupRoll.leader.rollData');
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeRoll(button, path) {
|
||||||
|
this.updatePartyData(
|
||||||
|
{
|
||||||
|
[path]: {
|
||||||
|
rollData: null,
|
||||||
|
rollChoice: null,
|
||||||
|
selected: false,
|
||||||
|
successfull: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
this.getUpdatingParts(button)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #removeRoll(_event, button) {
|
||||||
|
this.removeRoll(button, `system.groupRoll.aidingCharacters.${button.dataset.member}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #removeLeaderRoll(_event, button) {
|
||||||
|
this.removeRoll(button, 'system.groupRoll.leader');
|
||||||
|
}
|
||||||
|
|
||||||
|
async rerollDice(button, data, path) {
|
||||||
|
const { diceType } = button.dataset;
|
||||||
|
|
||||||
|
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(
|
||||||
|
{
|
||||||
|
[path]: rollData
|
||||||
|
},
|
||||||
|
this.getUpdatingParts(button)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #rerollDice(_, button) {
|
||||||
|
const { member } = button.dataset;
|
||||||
|
this.rerollDice(
|
||||||
|
button,
|
||||||
|
this.party.system.groupRoll.aidingCharacters[member],
|
||||||
|
`system.groupRoll.aidingCharacters.${member}.rollData`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #rerollLeaderDice(_, button) {
|
||||||
|
this.rerollDice(button, this.party.system.groupRoll.leader, `system.groupRoll.leader.rollData`);
|
||||||
|
}
|
||||||
|
|
||||||
|
static #markSuccessfull(_event, button) {
|
||||||
|
const previousValue = this.party.system.groupRoll.aidingCharacters[button.dataset.member].successfull;
|
||||||
|
const newValue = Boolean(button.dataset.successfull === 'true');
|
||||||
|
this.updatePartyData(
|
||||||
|
{
|
||||||
|
[`system.groupRoll.aidingCharacters.${button.dataset.member}.successfull`]:
|
||||||
|
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.successfull ? '+' : '-' }));
|
||||||
|
totalRoll.terms.push(new foundry.dice.terms.NumericTerm({ number: 1 }));
|
||||||
|
}
|
||||||
|
|
||||||
|
await totalRoll._evaluate();
|
||||||
|
|
||||||
|
const systemData = totalRoll.options;
|
||||||
|
const actor = game.actors.get(this.party.system.groupRoll.leader.id);
|
||||||
|
|
||||||
|
const cls = getDocumentClass('ChatMessage'),
|
||||||
|
msgData = {
|
||||||
|
type: 'dualityRoll',
|
||||||
|
user: game.user.id,
|
||||||
|
title: game.i18n.localize('DAGGERHEART.APPLICATIONS.GroupRollSelect.title'),
|
||||||
|
speaker: cls.getSpeaker({ actor }),
|
||||||
|
system: systemData,
|
||||||
|
rolls: [JSON.stringify(totalRoll)],
|
||||||
|
sound: null,
|
||||||
|
flags: { core: { RollTable: true } }
|
||||||
|
};
|
||||||
|
|
||||||
|
await cls.create(msgData);
|
||||||
|
|
||||||
|
const resourceMap = new ResourceUpdateMap(actor);
|
||||||
|
if (totalRoll.isCritical) {
|
||||||
|
resourceMap.addResources([
|
||||||
|
{ key: 'stress', value: -1, total: 1 },
|
||||||
|
{ key: 'hope', value: 1, total: 1 }
|
||||||
|
]);
|
||||||
|
} else if (totalRoll.withHope) {
|
||||||
|
resourceMap.addResources([{ key: 'hope', value: 1, total: 1 }]);
|
||||||
|
} else {
|
||||||
|
resourceMap.addResources([{ key: 'fear', value: 1, total: 1 }]);
|
||||||
|
}
|
||||||
|
|
||||||
|
resourceMap.updateResources();
|
||||||
|
|
||||||
|
/* Fin */
|
||||||
|
this.cancelRoll({ confirm: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -38,13 +38,15 @@ export default class ItemTransferDialog extends HandlebarsApplicationMixin(Appli
|
||||||
originActor ??= item?.actor;
|
originActor ??= item?.actor;
|
||||||
const homebrewKey = CONFIG.DH.SETTINGS.gameSettings.Homebrew;
|
const homebrewKey = CONFIG.DH.SETTINGS.gameSettings.Homebrew;
|
||||||
const currencySetting = game.settings.get(CONFIG.DH.id, homebrewKey).currency?.[currency] ?? null;
|
const currencySetting = game.settings.get(CONFIG.DH.id, homebrewKey).currency?.[currency] ?? null;
|
||||||
|
const max = item?.system.quantity ?? originActor.system.gold[currency] ?? 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
originActor,
|
originActor,
|
||||||
targetActor,
|
targetActor,
|
||||||
itemImage: item?.img,
|
itemImage: item?.img,
|
||||||
currencyIcon: currencySetting?.icon,
|
currencyIcon: currencySetting?.icon,
|
||||||
max: item?.system.quantity ?? originActor.system.gold[currency] ?? 0,
|
max,
|
||||||
|
initial: targetActor.system.metadata.quantifiable?.includes(item.type) ? max : 1,
|
||||||
title: item?.name ?? currencySetting?.label
|
title: item?.name ?? currencySetting?.label
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,11 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
...member.toObject(),
|
...member.toObject(),
|
||||||
uuid: member.uuid,
|
uuid: member.uuid,
|
||||||
id: member.id,
|
id: member.id,
|
||||||
selected: false
|
selected: false,
|
||||||
|
owned: member.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER)
|
||||||
}));
|
}));
|
||||||
this.intiator = null;
|
|
||||||
|
this.initiator = { cost: 3 };
|
||||||
this.openForAllPlayers = true;
|
this.openForAllPlayers = true;
|
||||||
|
|
||||||
this.tabGroups.application = Object.keys(party.system.tagTeam.members).length
|
this.tabGroups.application = Object.keys(party.system.tagTeam.members).length
|
||||||
|
|
@ -80,6 +82,18 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
|
|
||||||
for (const element of htmlElement.querySelectorAll('.roll-type-select'))
|
for (const element of htmlElement.querySelectorAll('.roll-type-select'))
|
||||||
element.addEventListener('change', this.updateRollType.bind(this));
|
element.addEventListener('change', this.updateRollType.bind(this));
|
||||||
|
|
||||||
|
htmlElement
|
||||||
|
.querySelector('.initiator-member-field')
|
||||||
|
?.addEventListener('input', this.updateInitiatorMemberField.bind(this));
|
||||||
|
|
||||||
|
htmlElement
|
||||||
|
.querySelector('.initiator-cost-field')
|
||||||
|
?.addEventListener('input', this.updateInitiatorCostField.bind(this));
|
||||||
|
|
||||||
|
htmlElement
|
||||||
|
.querySelector('.openforall-field')
|
||||||
|
?.addEventListener('change', this.updateOpenForAllField.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
_configureRenderParts(options) {
|
_configureRenderParts(options) {
|
||||||
|
|
@ -101,6 +115,12 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
async _onRender(context, options) {
|
async _onRender(context, options) {
|
||||||
await super._onRender(context, options);
|
await super._onRender(context, options);
|
||||||
|
|
||||||
|
// if (this.element.querySelector('.roll-selection')) {
|
||||||
|
// for (const element of this.element.querySelectorAll('.team-member-container')) {
|
||||||
|
// element.classList.add('select-padding');
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
if (this.element.querySelector('.team-container')) return;
|
if (this.element.querySelector('.team-container')) return;
|
||||||
const initializationPart = this.element.querySelector('.initialization-container');
|
const initializationPart = this.element.querySelector('.initialization-container');
|
||||||
initializationPart.insertAdjacentHTML('afterend', '<div class="team-container"></div>');
|
initializationPart.insertAdjacentHTML('afterend', '<div class="team-container"></div>');
|
||||||
|
|
@ -119,7 +139,10 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
context.members = {};
|
context.members = {};
|
||||||
context.allHaveRolled = Object.keys(this.party.system.tagTeam.members).every(key => {
|
context.allHaveRolled = Object.keys(this.party.system.tagTeam.members).every(key => {
|
||||||
const data = this.party.system.tagTeam.members[key];
|
const data = this.party.system.tagTeam.members[key];
|
||||||
return Boolean(data.rollData);
|
const hasRolled = Boolean(data.rollData);
|
||||||
|
if (!hasRolled) return false;
|
||||||
|
|
||||||
|
return !data.rollData.options.hasDamage || Boolean(data.rollData.options.damage);
|
||||||
});
|
});
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
|
|
@ -135,9 +158,12 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
const selectedMembers = partContext.memberSelection.filter(x => x.selected);
|
const selectedMembers = partContext.memberSelection.filter(x => x.selected);
|
||||||
|
|
||||||
partContext.allSelected = selectedMembers.length === 2;
|
partContext.allSelected = selectedMembers.length === 2;
|
||||||
partContext.canStartTagTeam = partContext.allSelected && this.initiator;
|
partContext.canStartTagTeam =
|
||||||
|
partContext.allSelected && this.initiator?.memberId && typeof this.initiator?.cost === 'number';
|
||||||
partContext.initiator = this.initiator;
|
partContext.initiator = this.initiator;
|
||||||
partContext.initiatorOptions = selectedMembers.map(x => ({ value: x.id, label: x.name }));
|
partContext.initiatorOptions = selectedMembers
|
||||||
|
.filter(actor => actor.owned)
|
||||||
|
.map(x => ({ value: x.id, label: x.name }));
|
||||||
partContext.initiatorDisabled = !selectedMembers.length;
|
partContext.initiatorDisabled = !selectedMembers.length;
|
||||||
partContext.openForAllPlayers = this.openForAllPlayers;
|
partContext.openForAllPlayers = this.openForAllPlayers;
|
||||||
|
|
||||||
|
|
@ -230,14 +256,15 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
}
|
}
|
||||||
|
|
||||||
static async updateData(event, _, formData) {
|
static async updateData(event, _, formData) {
|
||||||
const { initiator, openForAllPlayers, ...partyData } = foundry.utils.expandObject(formData.object);
|
const partyData = foundry.utils.expandObject(formData.object);
|
||||||
this.initiator = initiator;
|
|
||||||
this.openForAllPlayers = openForAllPlayers !== undefined ? openForAllPlayers : this.openForAllPlayers;
|
|
||||||
|
|
||||||
this.updatePartyData(partyData, this.getUpdatingParts(event.target));
|
this.updatePartyData(partyData, this.getUpdatingParts(event.target));
|
||||||
}
|
}
|
||||||
|
|
||||||
async updatePartyData(update, updatingParts, options = { render: true }) {
|
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 => {
|
const gmUpdate = async update => {
|
||||||
await this.party.update(update);
|
await this.party.update(update);
|
||||||
this.render({ parts: updatingParts });
|
this.render({ parts: updatingParts });
|
||||||
|
|
@ -348,8 +375,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
let rollIsSelected = false;
|
let rollIsSelected = false;
|
||||||
for (const member of Object.values(members)) {
|
for (const member of Object.values(members)) {
|
||||||
const rollFinished = Boolean(member.rollData);
|
const rollFinished = Boolean(member.rollData);
|
||||||
const damageFinished =
|
const damageFinished = member.rollData?.options?.hasDamage ? Boolean(member.rollData.options.damage) : true;
|
||||||
member.rollData?.options?.hasDamage !== undefined ? member.rollData.options.damage : true;
|
|
||||||
|
|
||||||
rollsAreFinished = rollsAreFinished && rollFinished && damageFinished;
|
rollsAreFinished = rollsAreFinished && rollFinished && damageFinished;
|
||||||
rollIsSelected = rollIsSelected || member.selected;
|
rollIsSelected = rollIsSelected || member.selected;
|
||||||
|
|
@ -374,6 +400,23 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateInitiatorMemberField(event) {
|
||||||
|
if (!this.initiator) this.initiator = {};
|
||||||
|
this.initiator.memberId = event.target.value;
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateInitiatorCostField(event) {
|
||||||
|
if (!this.initiator) this.initiator = {};
|
||||||
|
this.initiator.cost = event.target.value ? Number.parseInt(event.target.value) : null;
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateOpenForAllField(event) {
|
||||||
|
this.openForAllPlayers = event.target.checked;
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
static async #removeRoll(_, button) {
|
static async #removeRoll(_, button) {
|
||||||
this.updatePartyData(
|
this.updatePartyData(
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -122,15 +122,14 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
||||||
|
|
||||||
async toggleClowncar(actors) {
|
async toggleClowncar(actors) {
|
||||||
const animationDuration = 500;
|
const animationDuration = 500;
|
||||||
const activeTokens = actors.flatMap(member => member.getActiveTokens());
|
const scene = game.scenes.get(game.user.viewedScene);
|
||||||
|
/* getDependentTokens returns already removed tokens with id = null. Need to filter that until it's potentially fixed from Foundry */
|
||||||
|
const activeTokens = actors.flatMap(member => member.getDependentTokens({ scenes: scene }).filter(x => x._id));
|
||||||
const { x: actorX, y: actorY } = this.document;
|
const { x: actorX, y: actorY } = this.document;
|
||||||
if (activeTokens.length > 0) {
|
if (activeTokens.length > 0) {
|
||||||
for (let token of activeTokens) {
|
for (let token of activeTokens) {
|
||||||
await token.document.update(
|
await token.update({ x: actorX, y: actorY, alpha: 0 }, { animation: { duration: animationDuration } });
|
||||||
{ x: actorX, y: actorY, alpha: 0 },
|
setTimeout(() => token.delete(), animationDuration);
|
||||||
{ animation: { duration: animationDuration } }
|
|
||||||
);
|
|
||||||
setTimeout(() => token.document.delete(), animationDuration);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const activeScene = game.scenes.find(x => x.id === game.user.viewedScene);
|
const activeScene = game.scenes.find(x => x.id === game.user.viewedScene);
|
||||||
|
|
@ -140,11 +139,16 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
||||||
tokenData.push(data.toObject());
|
tokenData.push(data.toObject());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const viewedLevel = game.scenes.get(game.user.viewedScene).levels.get(game.user.viewedLevel);
|
||||||
|
const elevation = this.actor.token?.elevation ?? viewedLevel.elevation.bottom;
|
||||||
|
|
||||||
const newTokens = await activeScene.createEmbeddedDocuments(
|
const newTokens = await activeScene.createEmbeddedDocuments(
|
||||||
'Token',
|
'Token',
|
||||||
tokenData.map(tokenData => ({
|
tokenData.map(tokenData => ({
|
||||||
...tokenData,
|
...tokenData,
|
||||||
alpha: 0,
|
alpha: 0,
|
||||||
|
level: viewedLevel,
|
||||||
|
elevation: elevation,
|
||||||
x: actorX,
|
x: actorX,
|
||||||
y: actorY
|
y: actorY
|
||||||
}))
|
}))
|
||||||
|
|
|
||||||
|
|
@ -118,8 +118,13 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App
|
||||||
break;
|
break;
|
||||||
case 'footer':
|
case 'footer':
|
||||||
partContext.buttons = [
|
partContext.buttons = [
|
||||||
{ type: 'button', action: 'reset', icon: 'fa-solid fa-arrow-rotate-left', label: 'Reset' },
|
{
|
||||||
{ type: 'submit', icon: 'fa-solid fa-floppy-disk', label: 'Save Changes' }
|
type: 'button',
|
||||||
|
action: 'reset',
|
||||||
|
icon: 'fa-solid fa-arrow-rotate-left',
|
||||||
|
label: game.i18n.localize('SETTINGS.UI.ACTIONS.Reset')
|
||||||
|
},
|
||||||
|
{ type: 'submit', icon: 'fa-solid fa-floppy-disk', label: game.i18n.localize('EDITOR.Save') }
|
||||||
];
|
];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -264,7 +264,9 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
||||||
key = event.target.closest('[data-key]').dataset.key;
|
key = event.target.closest('[data-key]').dataset.key;
|
||||||
if (!this.action[key]) return;
|
if (!this.action[key]) return;
|
||||||
|
|
||||||
data[key].push(this.action.defaultValues[key] ?? {});
|
const value = key === 'areas' ? { name: this.action.item.name } : {};
|
||||||
|
|
||||||
|
data[key].push(this.action.defaultValues[key] ?? value);
|
||||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,16 +19,17 @@ export default class DHActionConfig extends DHActionBaseConfig {
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async addEffect(_event) {
|
static async addEffect(event) {
|
||||||
|
const { areaIndex } = event.target.dataset;
|
||||||
if (!this.action.effects) return;
|
if (!this.action.effects) return;
|
||||||
const effectData = this._addEffectData.bind(this)();
|
|
||||||
const data = this.action.toObject();
|
const data = this.action.toObject();
|
||||||
|
|
||||||
const created = await this.action.item.createEmbeddedDocuments('ActiveEffect', [
|
const created = await this.action.item.createEmbeddedDocuments('ActiveEffect', [
|
||||||
game.system.api.data.activeEffects.BaseEffect.getDefaultObject()
|
game.system.api.data.activeEffects.BaseEffect.getDefaultObject({ transfer: false })
|
||||||
]);
|
]);
|
||||||
|
|
||||||
data.effects.push({ _id: created[0]._id });
|
if (areaIndex !== undefined) data.areas[areaIndex].effects.push(created[0]._id);
|
||||||
|
else data.effects.push({ _id: created[0]._id });
|
||||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
this.action.item.effects.get(created[0]._id).sheet.render(true);
|
this.action.item.effects.get(created[0]._id).sheet.render(true);
|
||||||
}
|
}
|
||||||
|
|
@ -53,9 +54,19 @@ export default class DHActionConfig extends DHActionBaseConfig {
|
||||||
|
|
||||||
static removeEffect(event, button) {
|
static removeEffect(event, button) {
|
||||||
if (!this.action.effects) return;
|
if (!this.action.effects) return;
|
||||||
const index = button.dataset.index,
|
|
||||||
|
const { areaIndex, index } = button.dataset;
|
||||||
|
let effectId = null;
|
||||||
|
if (areaIndex !== undefined) {
|
||||||
|
effectId = this.action.areas[areaIndex].effects[index];
|
||||||
|
const data = this.action.toObject();
|
||||||
|
data.areas[areaIndex].effects.splice(index, 1);
|
||||||
|
this.constructor.updateForm.call(this, null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
|
} else {
|
||||||
effectId = this.action.effects[index]._id;
|
effectId = this.action.effects[index]._id;
|
||||||
this.constructor.removeElement.bind(this)(event, button);
|
this.constructor.removeElement.call(this, event, button);
|
||||||
|
}
|
||||||
|
|
||||||
this.action.item.deleteEmbeddedDocuments('ActiveEffect', [effectId]);
|
this.action.item.deleteEmbeddedDocuments('ActiveEffect', [effectId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,21 +31,35 @@ export default class DHActionSettingsConfig extends DHActionBaseConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
static async addEffect(_event) {
|
static async addEffect(_event) {
|
||||||
|
const { areaIndex } = event.target.dataset;
|
||||||
if (!this.action.effects) return;
|
if (!this.action.effects) return;
|
||||||
const effectData = game.system.api.data.activeEffects.BaseEffect.getDefaultObject();
|
|
||||||
|
const effectData = game.system.api.data.activeEffects.BaseEffect.getDefaultObject({ transfer: false });
|
||||||
const data = this.action.toObject();
|
const data = this.action.toObject();
|
||||||
|
|
||||||
this.sheetUpdate(data, effectData);
|
this.sheetUpdate(data, effectData);
|
||||||
this.effects = [...this.effects, effectData];
|
this.effects = [...this.effects, effectData];
|
||||||
data.effects.push({ _id: effectData.id });
|
|
||||||
|
if (areaIndex !== undefined) data.areas[areaIndex].effects.push(effectData.id);
|
||||||
|
else data.effects.push({ _id: effectData.id });
|
||||||
|
|
||||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
}
|
}
|
||||||
|
|
||||||
static removeEffect(event, button) {
|
static removeEffect(event, button) {
|
||||||
if (!this.action.effects) return;
|
if (!this.action.effects) return;
|
||||||
const index = button.dataset.index,
|
const { areaIndex, index } = button.dataset;
|
||||||
|
let effectId = null;
|
||||||
|
if (areaIndex !== undefined) {
|
||||||
|
effectId = this.action.areas[areaIndex].effects[index];
|
||||||
|
const data = this.action.toObject();
|
||||||
|
data.areas[areaIndex].effects.splice(index, 1);
|
||||||
|
this.constructor.updateForm.call(this, null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
|
} else {
|
||||||
effectId = this.action.effects[index]._id;
|
effectId = this.action.effects[index]._id;
|
||||||
this.constructor.removeElement.bind(this)(event, button);
|
this.constructor.removeElement.call(this, event, button);
|
||||||
|
}
|
||||||
|
|
||||||
this.sheetUpdate(
|
this.sheetUpdate(
|
||||||
this.action.toObject(),
|
this.action.toObject(),
|
||||||
this.effects.find(x => x.id === effectId),
|
this.effects.find(x => x.id === effectId),
|
||||||
|
|
|
||||||
|
|
@ -217,8 +217,8 @@ export default class AdversarySheet extends DHBaseActorSheet {
|
||||||
static #reactionRoll(event) {
|
static #reactionRoll(event) {
|
||||||
const config = {
|
const config = {
|
||||||
event,
|
event,
|
||||||
title: `Reaction Roll: ${this.actor.name}`,
|
title: game.i18n.localize('DAGGERHEART.GENERAL.reactionRoll'),
|
||||||
headerTitle: 'Adversary Reaction Roll',
|
headerTitle: game.i18n.localize('DAGGERHEART.ACTORS.Adversary.adversaryReactionRoll.headerTitle'),
|
||||||
roll: {
|
roll: {
|
||||||
type: 'trait'
|
type: 'trait'
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,6 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
classes: ['character'],
|
classes: ['character'],
|
||||||
position: { width: 850, height: 800 },
|
position: { width: 850, height: 800 },
|
||||||
/* Foundry adds disabled to all buttons and inputs if editPermission is missing. This is not desired. */
|
|
||||||
editPermission: CONST.DOCUMENT_OWNERSHIP_LEVELS.OBSERVER,
|
|
||||||
actions: {
|
actions: {
|
||||||
toggleVault: CharacterSheet.#toggleVault,
|
toggleVault: CharacterSheet.#toggleVault,
|
||||||
rollAttribute: CharacterSheet.#rollAttribute,
|
rollAttribute: CharacterSheet.#rollAttribute,
|
||||||
|
|
@ -68,7 +66,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
handler: CharacterSheet.#getEquipamentContextOptions,
|
handler: CharacterSheet.#getEquipmentContextOptions,
|
||||||
selector: '[data-item-uuid][data-type="armor"], [data-item-uuid][data-type="weapon"]',
|
selector: '[data-item-uuid][data-type="armor"], [data-item-uuid][data-type="weapon"]',
|
||||||
options: {
|
options: {
|
||||||
parentClassHooks: false,
|
parentClassHooks: false,
|
||||||
|
|
@ -170,6 +168,16 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
return applicationOptions;
|
return applicationOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
_toggleDisabled(disabled) {
|
||||||
|
// Overriden to only disable text inputs by default.
|
||||||
|
// Everything else is done by checking @root.editable in the sheet
|
||||||
|
const form = this.form;
|
||||||
|
for (const input of form.querySelectorAll('input:not([type=search]), .editor.prosemirror')) {
|
||||||
|
input.disabled = disabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
async _onRender(context, options) {
|
async _onRender(context, options) {
|
||||||
await super._onRender(context, options);
|
await super._onRender(context, options);
|
||||||
|
|
@ -315,11 +323,11 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
/**@type {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} */
|
/**@type {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} */
|
||||||
const options = [
|
const options = [
|
||||||
{
|
{
|
||||||
name: 'toLoadout',
|
label: 'toLoadout',
|
||||||
icon: 'fa-solid fa-arrow-up',
|
icon: 'fa-solid fa-arrow-up',
|
||||||
condition: target => {
|
visible: target => {
|
||||||
const doc = getDocFromElementSync(target);
|
const doc = getDocFromElementSync(target);
|
||||||
return doc && doc.system.inVault;
|
return doc?.isOwner && doc.system.inVault;
|
||||||
},
|
},
|
||||||
callback: async target => {
|
callback: async target => {
|
||||||
const doc = await getDocFromElement(target);
|
const doc = await getDocFromElement(target);
|
||||||
|
|
@ -329,11 +337,11 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'recall',
|
label: 'recall',
|
||||||
icon: 'fa-solid fa-bolt-lightning',
|
icon: 'fa-solid fa-bolt-lightning',
|
||||||
condition: target => {
|
visible: target => {
|
||||||
const doc = getDocFromElementSync(target);
|
const doc = getDocFromElementSync(target);
|
||||||
return doc && doc.system.inVault;
|
return doc?.isOwner && doc.system.inVault;
|
||||||
},
|
},
|
||||||
callback: async (target, event) => {
|
callback: async (target, event) => {
|
||||||
const doc = await getDocFromElement(target);
|
const doc = await getDocFromElement(target);
|
||||||
|
|
@ -368,17 +376,17 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'toVault',
|
label: 'toVault',
|
||||||
icon: 'fa-solid fa-arrow-down',
|
icon: 'fa-solid fa-arrow-down',
|
||||||
condition: target => {
|
visible: target => {
|
||||||
const doc = getDocFromElementSync(target);
|
const doc = getDocFromElementSync(target);
|
||||||
return doc && !doc.system.inVault;
|
return doc?.isOwner && !doc.system.inVault;
|
||||||
},
|
},
|
||||||
callback: async target => (await getDocFromElement(target)).update({ 'system.inVault': true })
|
callback: async target => (await getDocFromElement(target)).update({ 'system.inVault': true })
|
||||||
}
|
}
|
||||||
].map(option => ({
|
].map(option => ({
|
||||||
...option,
|
...option,
|
||||||
name: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.name}`,
|
label: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.label}`,
|
||||||
icon: `<i class="${option.icon}"></i>`
|
icon: `<i class="${option.icon}"></i>`
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
@ -391,29 +399,29 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
* @this {CharacterSheet}
|
* @this {CharacterSheet}
|
||||||
* @protected
|
* @protected
|
||||||
*/
|
*/
|
||||||
static #getEquipamentContextOptions() {
|
static #getEquipmentContextOptions() {
|
||||||
const options = [
|
const options = [
|
||||||
{
|
{
|
||||||
name: 'equip',
|
label: 'equip',
|
||||||
icon: 'fa-solid fa-hands',
|
icon: 'fa-solid fa-hands',
|
||||||
condition: target => {
|
visible: target => {
|
||||||
const doc = getDocFromElementSync(target);
|
const doc = getDocFromElementSync(target);
|
||||||
return doc && !doc.system.equipped;
|
return doc.isOwner && doc && !doc.system.equipped;
|
||||||
},
|
},
|
||||||
callback: (target, event) => CharacterSheet.#toggleEquipItem.call(this, event, target)
|
callback: (target, event) => CharacterSheet.#toggleEquipItem.call(this, event, target)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'unequip',
|
label: 'unequip',
|
||||||
icon: 'fa-solid fa-hands',
|
icon: 'fa-solid fa-hands',
|
||||||
condition: target => {
|
visible: target => {
|
||||||
const doc = getDocFromElementSync(target);
|
const doc = getDocFromElementSync(target);
|
||||||
return doc && doc.system.equipped;
|
return doc.isOwner && doc && doc.system.equipped;
|
||||||
},
|
},
|
||||||
callback: (target, event) => CharacterSheet.#toggleEquipItem.call(this, event, target)
|
callback: (target, event) => CharacterSheet.#toggleEquipItem.call(this, event, target)
|
||||||
}
|
}
|
||||||
].map(option => ({
|
].map(option => ({
|
||||||
...option,
|
...option,
|
||||||
name: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.name}`,
|
label: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.label}`,
|
||||||
icon: `<i class="${option.icon}"></i>`
|
icon: `<i class="${option.icon}"></i>`
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
import DHBaseActorSheet from '../api/base-actor.mjs';
|
import DHBaseActorSheet from '../api/base-actor.mjs';
|
||||||
import { getDocFromElement } from '../../../helpers/utils.mjs';
|
import { getDocFromElement, sortBy } from '../../../helpers/utils.mjs';
|
||||||
import { ItemBrowser } from '../../ui/itemBrowser.mjs';
|
import { ItemBrowser } from '../../ui/itemBrowser.mjs';
|
||||||
import FilterMenu from '../../ux/filter-menu.mjs';
|
import FilterMenu from '../../ux/filter-menu.mjs';
|
||||||
import DaggerheartMenu from '../../sidebar/tabs/daggerheartMenu.mjs';
|
import DaggerheartMenu from '../../sidebar/tabs/daggerheartMenu.mjs';
|
||||||
import { socketEvent } from '../../../systemRegistration/socket.mjs';
|
import { socketEvent } from '../../../systemRegistration/socket.mjs';
|
||||||
import GroupRollDialog from '../../dialogs/group-roll-dialog.mjs';
|
|
||||||
import DhpActor from '../../../documents/actor.mjs';
|
import DhpActor from '../../../documents/actor.mjs';
|
||||||
|
|
||||||
export default class Party extends DHBaseActorSheet {
|
export default class Party extends DHBaseActorSheet {
|
||||||
|
|
@ -18,13 +17,14 @@ export default class Party extends DHBaseActorSheet {
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
classes: ['party'],
|
classes: ['party'],
|
||||||
position: {
|
position: {
|
||||||
width: 550,
|
width: 600,
|
||||||
height: 900
|
height: 900
|
||||||
},
|
},
|
||||||
window: {
|
window: {
|
||||||
resizable: true
|
resizable: true
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
|
openDocument: Party.#openDocument,
|
||||||
deletePartyMember: Party.#deletePartyMember,
|
deletePartyMember: Party.#deletePartyMember,
|
||||||
deleteItem: Party.#deleteItem,
|
deleteItem: Party.#deleteItem,
|
||||||
toggleHope: Party.#toggleHope,
|
toggleHope: Party.#toggleHope,
|
||||||
|
|
@ -45,10 +45,6 @@ export default class Party extends DHBaseActorSheet {
|
||||||
header: { template: 'systems/daggerheart/templates/sheets/actors/party/header.hbs' },
|
header: { template: 'systems/daggerheart/templates/sheets/actors/party/header.hbs' },
|
||||||
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
|
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
|
||||||
partyMembers: { template: 'systems/daggerheart/templates/sheets/actors/party/party-members.hbs' },
|
partyMembers: { template: 'systems/daggerheart/templates/sheets/actors/party/party-members.hbs' },
|
||||||
resources: {
|
|
||||||
template: 'systems/daggerheart/templates/sheets/actors/party/resources.hbs',
|
|
||||||
scrollable: ['']
|
|
||||||
},
|
|
||||||
/* NOT YET IMPLEMENTED */
|
/* NOT YET IMPLEMENTED */
|
||||||
// projects: {
|
// projects: {
|
||||||
// template: 'systems/daggerheart/templates/sheets/actors/party/projects.hbs',
|
// template: 'systems/daggerheart/templates/sheets/actors/party/projects.hbs',
|
||||||
|
|
@ -66,7 +62,6 @@ export default class Party extends DHBaseActorSheet {
|
||||||
primary: {
|
primary: {
|
||||||
tabs: [
|
tabs: [
|
||||||
{ id: 'partyMembers' },
|
{ id: 'partyMembers' },
|
||||||
{ id: 'resources' },
|
|
||||||
/* NOT YET IMPLEMENTED */
|
/* NOT YET IMPLEMENTED */
|
||||||
// { id: 'projects' },
|
// { id: 'projects' },
|
||||||
{ id: 'inventory' },
|
{ id: 'inventory' },
|
||||||
|
|
@ -96,6 +91,8 @@ export default class Party extends DHBaseActorSheet {
|
||||||
case 'header':
|
case 'header':
|
||||||
await this._prepareHeaderContext(context, options);
|
await this._prepareHeaderContext(context, options);
|
||||||
break;
|
break;
|
||||||
|
case 'partyMembers':
|
||||||
|
await this._prepareMembersContext(context, options);
|
||||||
case 'notes':
|
case 'notes':
|
||||||
await this._prepareNotesContext(context, options);
|
await this._prepareNotesContext(context, options);
|
||||||
break;
|
break;
|
||||||
|
|
@ -119,6 +116,60 @@ export default class Party extends DHBaseActorSheet {
|
||||||
relativeTo: this.document
|
relativeTo: this.document
|
||||||
});
|
});
|
||||||
context.tagTeamActive = Boolean(this.document.system.tagTeam.initiator);
|
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
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -149,6 +200,12 @@ export default class Party extends DHBaseActorSheet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async #openDocument(_, target) {
|
||||||
|
const uuid = target.dataset.uuid;
|
||||||
|
const document = await foundry.utils.fromUuid(uuid);
|
||||||
|
document?.sheet?.render(true);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggles a hope resource value.
|
* Toggles a hope resource value.
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
|
|
@ -190,7 +247,7 @@ export default class Party extends DHBaseActorSheet {
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
*/
|
*/
|
||||||
static async #toggleArmorSlot(_, target) {
|
static async #toggleArmorSlot(_, target) {
|
||||||
const actor = game.actors.get(target.dataset.actorId);
|
const actor = await foundry.utils.fromUuid(target.dataset.actorId);
|
||||||
const { value, max } = actor.system.armorScore;
|
const { value, max } = actor.system.armorScore;
|
||||||
const inputValue = Number.parseInt(target.dataset.value);
|
const inputValue = Number.parseInt(target.dataset.value);
|
||||||
const newValue = value >= inputValue ? inputValue - 1 : inputValue;
|
const newValue = value >= inputValue ? inputValue - 1 : inputValue;
|
||||||
|
|
@ -231,7 +288,7 @@ export default class Party extends DHBaseActorSheet {
|
||||||
title: game.i18n.localize(`DAGGERHEART.APPLICATIONS.Downtime.${button.dataset.type}.title`),
|
title: game.i18n.localize(`DAGGERHEART.APPLICATIONS.Downtime.${button.dataset.type}.title`),
|
||||||
icon: button.dataset.type === 'shortRest' ? 'fa-solid fa-utensils' : 'fa-solid fa-bed'
|
icon: button.dataset.type === 'shortRest' ? 'fa-solid fa-utensils' : 'fa-solid fa-bed'
|
||||||
},
|
},
|
||||||
content: 'This will trigger a dialog to players make their downtime moves, are you sure?',
|
content: game.i18n.localize('DAGGERHEART.ACTORS.Party.triggerRestContent'),
|
||||||
classes: ['daggerheart', 'dialog', 'dh-style']
|
classes: ['daggerheart', 'dialog', 'dh-style']
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -261,9 +318,7 @@ export default class Party extends DHBaseActorSheet {
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #groupRoll(_params) {
|
static async #groupRoll(_params) {
|
||||||
new GroupRollDialog(
|
new game.system.api.applications.dialogs.GroupRollDialog(this.document).render({ force: true });
|
||||||
this.document.system.partyMembers.filter(x => Party.DICE_ROLL_ACTOR_TYPES.includes(x.type))
|
|
||||||
).render({ force: true });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
@ -425,25 +480,23 @@ export default class Party extends DHBaseActorSheet {
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #deletePartyMember(event, target) {
|
static async #deletePartyMember(event, target) {
|
||||||
const doc = await getDocFromElement(target.closest('.inventory-item'));
|
const doc = await foundry.utils.fromUuid(target.closest('[data-uuid]')?.dataset.uuid);
|
||||||
|
|
||||||
if (!event.shiftKey) {
|
if (!event.shiftKey) {
|
||||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||||
window: {
|
window: {
|
||||||
title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', {
|
title: game.i18n.format('DAGGERHEART.ACTORS.Party.RemoveConfirmation.title', {
|
||||||
type: game.i18n.localize('TYPES.Actor.adversary'),
|
|
||||||
name: doc.name
|
name: doc.name
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: doc.name })
|
content: game.i18n.format('DAGGERHEART.ACTORS.Party.RemoveConfirmation.text', { name: doc.name })
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!confirmed) return;
|
if (!confirmed) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentMembers = this.document.system.partyMembers.map(x => x.uuid);
|
const currentMembers = this.document.system.partyMembers.map(x => x.uuid);
|
||||||
const newMemberdList = currentMembers.filter(uuid => uuid !== doc.uuid);
|
const newMembersList = currentMembers.filter(uuid => uuid !== doc.uuid);
|
||||||
await this.document.update({ 'system.partyMembers': newMemberdList });
|
await this.document.update({ 'system.partyMembers': newMembersList });
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #deleteItem(event, target) {
|
static async #deleteItem(event, target) {
|
||||||
|
|
|
||||||
|
|
@ -418,18 +418,18 @@ export default function DHApplicationMixin(Base) {
|
||||||
/**@type {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} */
|
/**@type {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} */
|
||||||
const options = [
|
const options = [
|
||||||
{
|
{
|
||||||
name: 'disableEffect',
|
label: 'disableEffect',
|
||||||
icon: 'fa-solid fa-lightbulb',
|
icon: 'fa-solid fa-lightbulb',
|
||||||
condition: element => {
|
visible: element => {
|
||||||
const target = element.closest('[data-item-uuid]');
|
const target = element.closest('[data-item-uuid]');
|
||||||
return !target.dataset.disabled && target.dataset.itemType !== 'beastform';
|
return !target.dataset.disabled && target.dataset.itemType !== 'beastform';
|
||||||
},
|
},
|
||||||
callback: async target => (await getDocFromElement(target)).update({ disabled: true })
|
callback: async target => (await getDocFromElement(target)).update({ disabled: true })
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'enableEffect',
|
label: 'enableEffect',
|
||||||
icon: 'fa-regular fa-lightbulb',
|
icon: 'fa-regular fa-lightbulb',
|
||||||
condition: element => {
|
visible: element => {
|
||||||
const target = element.closest('[data-item-uuid]');
|
const target = element.closest('[data-item-uuid]');
|
||||||
return target.dataset.disabled && target.dataset.itemType !== 'beastform';
|
return target.dataset.disabled && target.dataset.itemType !== 'beastform';
|
||||||
},
|
},
|
||||||
|
|
@ -437,7 +437,7 @@ export default function DHApplicationMixin(Base) {
|
||||||
}
|
}
|
||||||
].map(option => ({
|
].map(option => ({
|
||||||
...option,
|
...option,
|
||||||
name: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.name}`,
|
label: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.label}`,
|
||||||
icon: `<i class="${option.icon}"></i>`
|
icon: `<i class="${option.icon}"></i>`
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
@ -468,14 +468,14 @@ export default function DHApplicationMixin(Base) {
|
||||||
_getContextMenuCommonOptions({ usable = false, toChat = false, deletable = true }) {
|
_getContextMenuCommonOptions({ usable = false, toChat = false, deletable = true }) {
|
||||||
const options = [
|
const options = [
|
||||||
{
|
{
|
||||||
name: 'CONTROLS.CommonEdit',
|
label: 'CONTROLS.CommonEdit',
|
||||||
icon: 'fa-solid fa-pen-to-square',
|
icon: 'fa-solid fa-pen-to-square',
|
||||||
condition: target => {
|
visible: target => {
|
||||||
const { dataset } = target.closest('[data-item-uuid]');
|
const { dataset } = target.closest('[data-item-uuid]');
|
||||||
const doc = getDocFromElementSync(target);
|
const doc = getDocFromElementSync(target);
|
||||||
return (
|
return (
|
||||||
(!dataset.noCompendiumEdit && !doc) ||
|
(!dataset.noCompendiumEdit && !doc) ||
|
||||||
(doc && (!doc?.hasOwnProperty('systemPath') || doc?.inCollection))
|
(doc?.isOwner && (!doc?.hasOwnProperty('systemPath') || doc?.inCollection))
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
callback: async target => (await getDocFromElement(target)).sheet.render({ force: true })
|
callback: async target => (await getDocFromElement(target)).sheet.render({ force: true })
|
||||||
|
|
@ -484,14 +484,14 @@ export default function DHApplicationMixin(Base) {
|
||||||
|
|
||||||
if (usable) {
|
if (usable) {
|
||||||
options.unshift({
|
options.unshift({
|
||||||
name: 'DAGGERHEART.GENERAL.damage',
|
label: 'DAGGERHEART.GENERAL.damage',
|
||||||
icon: 'fa-solid fa-explosion',
|
icon: 'fa-solid fa-explosion',
|
||||||
condition: target => {
|
visible: target => {
|
||||||
const doc = getDocFromElementSync(target);
|
const doc = getDocFromElementSync(target);
|
||||||
return (
|
const hasDamage =
|
||||||
!foundry.utils.isEmpty(doc?.system?.attack?.damage.parts) ||
|
!foundry.utils.isEmpty(doc?.system?.attack?.damage.parts) ||
|
||||||
!foundry.utils.isEmpty(doc?.damage?.parts)
|
!foundry.utils.isEmpty(doc?.damage?.parts);
|
||||||
);
|
return doc?.isOwner && hasDamage;
|
||||||
},
|
},
|
||||||
callback: async (target, event) => {
|
callback: async (target, event) => {
|
||||||
const doc = await getDocFromElement(target),
|
const doc = await getDocFromElement(target),
|
||||||
|
|
@ -507,11 +507,11 @@ export default function DHApplicationMixin(Base) {
|
||||||
});
|
});
|
||||||
|
|
||||||
options.unshift({
|
options.unshift({
|
||||||
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.useItem',
|
label: 'DAGGERHEART.APPLICATIONS.ContextMenu.useItem',
|
||||||
icon: 'fa-solid fa-burst',
|
icon: 'fa-solid fa-burst',
|
||||||
condition: target => {
|
visible: target => {
|
||||||
const doc = getDocFromElementSync(target);
|
const doc = getDocFromElementSync(target);
|
||||||
return doc && !(doc.type === 'domainCard' && doc.system.inVault);
|
return doc?.isOwner && !(doc.type === 'domainCard' && doc.system.inVault);
|
||||||
},
|
},
|
||||||
callback: async (target, event) => (await getDocFromElement(target)).use(event)
|
callback: async (target, event) => (await getDocFromElement(target)).use(event)
|
||||||
});
|
});
|
||||||
|
|
@ -519,18 +519,19 @@ export default function DHApplicationMixin(Base) {
|
||||||
|
|
||||||
if (toChat)
|
if (toChat)
|
||||||
options.push({
|
options.push({
|
||||||
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.sendToChat',
|
label: 'DAGGERHEART.APPLICATIONS.ContextMenu.sendToChat',
|
||||||
icon: 'fa-solid fa-message',
|
icon: 'fa-solid fa-message',
|
||||||
callback: async target => (await getDocFromElement(target)).toChat(this.document.uuid)
|
callback: async target => (await getDocFromElement(target)).toChat(this.document.uuid)
|
||||||
});
|
});
|
||||||
|
|
||||||
if (deletable)
|
if (deletable)
|
||||||
options.push({
|
options.push({
|
||||||
name: 'CONTROLS.CommonDelete',
|
label: 'CONTROLS.CommonDelete',
|
||||||
icon: 'fa-solid fa-trash',
|
icon: 'fa-solid fa-trash',
|
||||||
condition: element => {
|
visible: element => {
|
||||||
const target = element.closest('[data-item-uuid]');
|
const target = element.closest('[data-item-uuid]');
|
||||||
return target.dataset.itemType !== 'beastform';
|
const doc = getDocFromElementSync(target);
|
||||||
|
return doc?.isOwner && target.dataset.itemType !== 'beastform';
|
||||||
},
|
},
|
||||||
callback: async (target, event) => {
|
callback: async (target, event) => {
|
||||||
const doc = await getDocFromElement(target);
|
const doc = await getDocFromElement(target);
|
||||||
|
|
@ -644,12 +645,12 @@ export default function DHApplicationMixin(Base) {
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
action: 'create',
|
action: 'create',
|
||||||
label: 'Create Item',
|
label: game.i18n.localize('DAGGERHEART.APPLICATIONS.CreateItemDialog.createItem'),
|
||||||
icon: 'fa-solid fa-plus'
|
icon: 'fa-solid fa-plus'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: 'browse',
|
action: 'browse',
|
||||||
label: 'Browse Compendium',
|
label: game.i18n.localize('DAGGERHEART.APPLICATIONS.CreateItemDialog.browseCompendium'),
|
||||||
icon: 'fa-solid fa-book'
|
icon: 'fa-solid fa-book'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
||||||
.hideAttribution;
|
.hideAttribution;
|
||||||
|
|
||||||
// Prepare inventory data
|
// Prepare inventory data
|
||||||
if (['party', 'character'].includes(this.document.type)) {
|
if (this.document.system.metadata.hasInventory) {
|
||||||
context.inventory = {
|
context.inventory = {
|
||||||
currencies: {},
|
currencies: {},
|
||||||
weapons: this.document.itemTypes.weapon.sort((a, b) => a.sort - b.sort),
|
weapons: this.document.itemTypes.weapon.sort((a, b) => a.sort - b.sort),
|
||||||
|
|
@ -283,11 +283,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
||||||
async _onDropItem(event, item) {
|
async _onDropItem(event, item) {
|
||||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||||
const originActor = item.actor;
|
const originActor = item.actor;
|
||||||
if (
|
if (!originActor || originActor.uuid === this.document.uuid || !this.document.system.metadata.hasInventory) {
|
||||||
item.actor?.uuid === this.document.uuid ||
|
|
||||||
!originActor ||
|
|
||||||
!['character', 'party'].includes(this.document.type)
|
|
||||||
) {
|
|
||||||
return super._onDropItem(event, item);
|
return super._onDropItem(event, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -302,47 +298,79 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.system.metadata.isQuantifiable) {
|
// Perform the actual transfer, showing a dialog when doing it
|
||||||
const actorItem = originActor.items.get(data.originId);
|
const availableQuantity = Math.max(1, item.system.quantity);
|
||||||
const quantityTransfered = await game.system.api.applications.dialogs.ItemTransferDialog.configure({
|
const actorItem = originActor.items.get(data.originId) ?? item;
|
||||||
|
if (availableQuantity > 1) {
|
||||||
|
const quantityTransferred = await game.system.api.applications.dialogs.ItemTransferDialog.configure({
|
||||||
item,
|
item,
|
||||||
targetActor: this.document
|
targetActor: this.document
|
||||||
});
|
});
|
||||||
|
return this.#transferItem(actorItem, quantityTransferred);
|
||||||
if (quantityTransfered) {
|
|
||||||
const existingItem = this.document.items.find(x => itemIsIdentical(x, item));
|
|
||||||
if (existingItem) {
|
|
||||||
await existingItem.update({
|
|
||||||
'system.quantity': existingItem.system.quantity + quantityTransfered
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
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 {
|
} else {
|
||||||
await this.document.createEmbeddedDocuments('Item', [item.toObject()]);
|
return this.#transferItem(actorItem, availableQuantity);
|
||||||
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.
|
* On dragStart on the item.
|
||||||
* @param {DragEvent} event - The drag event
|
* @param {DragEvent} event - The drag event
|
||||||
|
|
|
||||||
|
|
@ -31,11 +31,12 @@ export default class AncestrySheet extends DHHeritageSheet {
|
||||||
if (data.type === 'ActiveEffect') return super._onDrop(event);
|
if (data.type === 'ActiveEffect') return super._onDrop(event);
|
||||||
|
|
||||||
const target = event.target.closest('fieldset.drop-section');
|
const target = event.target.closest('fieldset.drop-section');
|
||||||
const typeField =
|
if (target) {
|
||||||
this.document.system[target.dataset.type === 'primary' ? 'primaryFeature' : 'secondaryFeature'];
|
const typeField =
|
||||||
|
this.document.system[target.dataset.type === 'primary' ? 'primaryFeature' : 'secondaryFeature'];
|
||||||
if (!typeField) {
|
if (!typeField) {
|
||||||
super._onDrop(event);
|
super._onDrop(event);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ export default class FeatureSheet extends DHBaseItemSheet {
|
||||||
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
//Might be wrong location but testing out if here is okay.
|
//Might be wrong location but testing out if here is okay.
|
||||||
/**@override */
|
/**@override */
|
||||||
async _prepareContext(options) {
|
async _prepareContext(options) {
|
||||||
const context = await super._prepareContext(options);
|
const context = await super._prepareContext(options);
|
||||||
|
|
|
||||||
|
|
@ -46,50 +46,67 @@ export default class DhActorDirectory extends foundry.applications.sidebar.tabs.
|
||||||
|
|
||||||
_getEntryContextOptions() {
|
_getEntryContextOptions() {
|
||||||
const options = super._getEntryContextOptions();
|
const options = super._getEntryContextOptions();
|
||||||
options.push({
|
options.push(
|
||||||
name: 'DAGGERHEART.UI.Sidebar.actorDirectory.duplicateToNewTier',
|
{
|
||||||
icon: `<i class="fa-solid fa-arrow-trend-up" inert></i>`,
|
label: 'DAGGERHEART.UI.Sidebar.actorDirectory.duplicateToNewTier',
|
||||||
condition: li => {
|
icon: `<i class="fa-solid fa-arrow-trend-up" inert></i>`,
|
||||||
const actor = game.actors.get(li.dataset.entryId);
|
visible: li => {
|
||||||
return actor?.type === 'adversary' && actor.system.type !== 'social';
|
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);
|
callback: async li => {
|
||||||
if (!actor) throw new Error('Unexpected missing actor');
|
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 tiers = [1, 2, 3, 4].filter(t => t !== actor.system.tier);
|
||||||
const content = document.createElement('div');
|
const content = document.createElement('div');
|
||||||
const select = document.createElement('select');
|
const select = document.createElement('select');
|
||||||
select.name = 'tier';
|
select.name = 'tier';
|
||||||
select.append(
|
select.append(
|
||||||
...tiers.map(t => {
|
...tiers.map(t => {
|
||||||
const option = document.createElement('option');
|
const option = document.createElement('option');
|
||||||
option.value = t;
|
option.value = t;
|
||||||
option.textContent = game.i18n.localize(`DAGGERHEART.GENERAL.Tiers.${t}`);
|
option.textContent = game.i18n.localize(`DAGGERHEART.GENERAL.Tiers.${t}`);
|
||||||
return option;
|
return option;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
content.append(select);
|
content.append(select);
|
||||||
|
|
||||||
const tier = await foundry.applications.api.Dialog.input({
|
const tier = await foundry.applications.api.Dialog.input({
|
||||||
classes: ['dh-style', 'dialog'],
|
classes: ['dh-style', 'dialog'],
|
||||||
window: { title: 'DAGGERHEART.UI.Sidebar.actorDirectory.pickTierTitle' },
|
window: { title: 'DAGGERHEART.UI.Sidebar.actorDirectory.pickTierTitle' },
|
||||||
content,
|
content,
|
||||||
ok: {
|
ok: {
|
||||||
label: 'Create Adversary',
|
label: 'DAGGERHEART.UI.Sidebar.actorDirectory.createAdversary',
|
||||||
callback: (event, button, dialog) => Number(button.form.elements.tier.value)
|
callback: (event, button, dialog) => Number(button.form.elements.tier.value)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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`);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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');
|
||||||
|
|
||||||
if (tier === actor.system.tier) {
|
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.ActiveParty, actor.id);
|
||||||
ui.notifications.warn('This actor is already at this tier');
|
ui.actors.render();
|
||||||
} else if (tier) {
|
|
||||||
const source = actor.system.adjustForTier(tier);
|
|
||||||
await Actor.create(source);
|
|
||||||
ui.notifications.info(`Tier ${tier} ${actor.name} created`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
import { abilities } from '../../config/actorConfig.mjs';
|
|
||||||
import { enrichedDualityRoll } from '../../enrichers/DualityRollEnricher.mjs';
|
import { enrichedDualityRoll } from '../../enrichers/DualityRollEnricher.mjs';
|
||||||
import { enrichedFateRoll, getFateTypeData } from '../../enrichers/FateRollEnricher.mjs';
|
import { enrichedFateRoll, getFateTypeData } from '../../enrichers/FateRollEnricher.mjs';
|
||||||
import { getCommandTarget, rollCommandToJSON } from '../../helpers/utils.mjs';
|
import { getCommandTarget, rollCommandToJSON } from '../../helpers/utils.mjs';
|
||||||
import { emitAsGM, GMUpdateEvent } from '../../systemRegistration/socket.mjs';
|
|
||||||
|
|
||||||
export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLog {
|
export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLog {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
|
|
@ -105,23 +103,10 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
_getEntryContextOptions() {
|
_getEntryContextOptions() {
|
||||||
return [
|
return [
|
||||||
...super._getEntryContextOptions(),
|
...super._getEntryContextOptions(),
|
||||||
// {
|
|
||||||
// name: 'Reroll',
|
|
||||||
// icon: '<i class="fa-solid fa-dice"></i>',
|
|
||||||
// condition: li => {
|
|
||||||
// const message = game.messages.get(li.dataset.messageId);
|
|
||||||
|
|
||||||
// return (game.user.isGM || message.isAuthor) && message.rolls.length > 0;
|
|
||||||
// },
|
|
||||||
// callback: li => {
|
|
||||||
// const message = game.messages.get(li.dataset.messageId);
|
|
||||||
// new game.system.api.applications.dialogs.RerollDialog(message).render({ force: true });
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
name: game.i18n.localize('DAGGERHEART.UI.ChatLog.rerollDamage'),
|
label: 'DAGGERHEART.UI.ChatLog.rerollDamage',
|
||||||
icon: '<i class="fa-solid fa-dice"></i>',
|
icon: '<i class="fa-solid fa-dice"></i>',
|
||||||
condition: li => {
|
visible: li => {
|
||||||
const message = game.messages.get(li.dataset.messageId);
|
const message = game.messages.get(li.dataset.messageId);
|
||||||
const hasRolledDamage = message.system.hasDamage
|
const hasRolledDamage = message.system.hasDamage
|
||||||
? Object.keys(message.system.damage).length > 0
|
? Object.keys(message.system.damage).length > 0
|
||||||
|
|
@ -150,18 +135,6 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
html.querySelectorAll('.reroll-button').forEach(element =>
|
html.querySelectorAll('.reroll-button').forEach(element =>
|
||||||
element.addEventListener('click', event => this.rerollEvent(event, message))
|
element.addEventListener('click', event => this.rerollEvent(event, message))
|
||||||
);
|
);
|
||||||
html.querySelectorAll('.group-roll-button').forEach(element =>
|
|
||||||
element.addEventListener('click', event => this.groupRollButton(event, message))
|
|
||||||
);
|
|
||||||
html.querySelectorAll('.group-roll-reroll').forEach(element =>
|
|
||||||
element.addEventListener('click', event => this.groupRollReroll(event, message))
|
|
||||||
);
|
|
||||||
html.querySelectorAll('.group-roll-success').forEach(element =>
|
|
||||||
element.addEventListener('click', event => this.groupRollSuccessEvent(event, message))
|
|
||||||
);
|
|
||||||
html.querySelectorAll('.group-roll-header-expand-section').forEach(element =>
|
|
||||||
element.addEventListener('click', this.groupRollExpandSection)
|
|
||||||
);
|
|
||||||
html.querySelectorAll('.risk-it-all-button').forEach(element =>
|
html.querySelectorAll('.risk-it-all-button').forEach(element =>
|
||||||
element.addEventListener('click', event => this.riskItAllClearStressAndHitPoints(event, data))
|
element.addEventListener('click', event => this.riskItAllClearStressAndHitPoints(event, data))
|
||||||
);
|
);
|
||||||
|
|
@ -305,174 +278,6 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async groupRollButton(event, message) {
|
|
||||||
const path = event.currentTarget.dataset.path;
|
|
||||||
const isLeader = path === 'leader';
|
|
||||||
const { actor: actorData, trait } = foundry.utils.getProperty(message.system, path);
|
|
||||||
const actor = game.actors.get(actorData._id);
|
|
||||||
|
|
||||||
if (!actor) {
|
|
||||||
return ui.notifications.error(
|
|
||||||
game.i18n.format('DAGGERHEART.UI.Notifications.documentIsMissing', {
|
|
||||||
documentType: game.i18n.localize('TYPES.Actor.character')
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!actor.testUserPermission(game.user, 'OWNER')) {
|
|
||||||
return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.noActorOwnership'));
|
|
||||||
}
|
|
||||||
|
|
||||||
const traitLabel = game.i18n.localize(abilities[trait].label);
|
|
||||||
const config = {
|
|
||||||
event: event,
|
|
||||||
title: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${actor.name}`,
|
|
||||||
headerTitle: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
|
||||||
ability: traitLabel
|
|
||||||
}),
|
|
||||||
roll: {
|
|
||||||
trait: trait,
|
|
||||||
advantage: 0,
|
|
||||||
modifiers: [{ label: traitLabel, value: actor.system.traits[trait].value }]
|
|
||||||
},
|
|
||||||
hasRoll: true,
|
|
||||||
skips: {
|
|
||||||
createMessage: true,
|
|
||||||
resources: !isLeader,
|
|
||||||
updateCountdowns: !isLeader
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const result = await actor.diceRoll({
|
|
||||||
...config,
|
|
||||||
headerTitle: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${actor.name}`,
|
|
||||||
title: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
|
||||||
ability: traitLabel
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!result) return;
|
|
||||||
|
|
||||||
const newMessageData = foundry.utils.deepClone(message.system);
|
|
||||||
foundry.utils.setProperty(newMessageData, `${path}.result`, result.roll);
|
|
||||||
const renderData = { system: new game.system.api.models.chatMessages.config.groupRoll(newMessageData) };
|
|
||||||
|
|
||||||
const updatedContent = await foundry.applications.handlebars.renderTemplate(
|
|
||||||
'systems/daggerheart/templates/ui/chat/groupRoll.hbs',
|
|
||||||
{ ...renderData, user: game.user }
|
|
||||||
);
|
|
||||||
const mess = game.messages.get(message._id);
|
|
||||||
|
|
||||||
await emitAsGM(
|
|
||||||
GMUpdateEvent.UpdateDocument,
|
|
||||||
mess.update.bind(mess),
|
|
||||||
{
|
|
||||||
...renderData,
|
|
||||||
content: updatedContent
|
|
||||||
},
|
|
||||||
mess.uuid
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async groupRollReroll(event, message) {
|
|
||||||
const path = event.currentTarget.dataset.path;
|
|
||||||
const { actor: actorData, trait } = foundry.utils.getProperty(message.system, path);
|
|
||||||
const actor = game.actors.get(actorData._id);
|
|
||||||
|
|
||||||
if (!actor.testUserPermission(game.user, 'OWNER')) {
|
|
||||||
return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.noActorOwnership'));
|
|
||||||
}
|
|
||||||
|
|
||||||
const traitLabel = game.i18n.localize(abilities[trait].label);
|
|
||||||
|
|
||||||
const config = {
|
|
||||||
event: event,
|
|
||||||
title: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${actor.name}`,
|
|
||||||
headerTitle: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
|
||||||
ability: traitLabel
|
|
||||||
}),
|
|
||||||
roll: {
|
|
||||||
trait: trait,
|
|
||||||
advantage: 0,
|
|
||||||
modifiers: [{ label: traitLabel, value: actor.system.traits[trait].value }]
|
|
||||||
},
|
|
||||||
hasRoll: true,
|
|
||||||
skips: {
|
|
||||||
createMessage: true,
|
|
||||||
updateCountdowns: true
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const result = await actor.diceRoll({
|
|
||||||
...config,
|
|
||||||
headerTitle: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${actor.name}`,
|
|
||||||
title: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
|
||||||
ability: traitLabel
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
const newMessageData = foundry.utils.deepClone(message.system);
|
|
||||||
foundry.utils.setProperty(newMessageData, `${path}.result`, { ...result.roll, rerolled: true });
|
|
||||||
const renderData = { system: new game.system.api.models.chatMessages.config.groupRoll(newMessageData) };
|
|
||||||
|
|
||||||
const updatedContent = await foundry.applications.handlebars.renderTemplate(
|
|
||||||
'systems/daggerheart/templates/ui/chat/groupRoll.hbs',
|
|
||||||
{ ...renderData, user: game.user }
|
|
||||||
);
|
|
||||||
const mess = game.messages.get(message._id);
|
|
||||||
await emitAsGM(
|
|
||||||
GMUpdateEvent.UpdateDocument,
|
|
||||||
mess.update.bind(mess),
|
|
||||||
{
|
|
||||||
...renderData,
|
|
||||||
content: updatedContent
|
|
||||||
},
|
|
||||||
mess.uuid
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async groupRollSuccessEvent(event, message) {
|
|
||||||
if (!game.user.isGM) {
|
|
||||||
return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.gmOnly'));
|
|
||||||
}
|
|
||||||
|
|
||||||
const { path, success } = event.currentTarget.dataset;
|
|
||||||
const { actor: actorData } = foundry.utils.getProperty(message.system, path);
|
|
||||||
const actor = game.actors.get(actorData._id);
|
|
||||||
|
|
||||||
if (!actor.testUserPermission(game.user, 'OWNER')) {
|
|
||||||
return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.noActorOwnership'));
|
|
||||||
}
|
|
||||||
|
|
||||||
const newMessageData = foundry.utils.deepClone(message.system);
|
|
||||||
foundry.utils.setProperty(newMessageData, `${path}.manualSuccess`, Boolean(success));
|
|
||||||
const renderData = { system: new game.system.api.models.chatMessages.config.groupRoll(newMessageData) };
|
|
||||||
|
|
||||||
const updatedContent = await foundry.applications.handlebars.renderTemplate(
|
|
||||||
'systems/daggerheart/templates/ui/chat/groupRoll.hbs',
|
|
||||||
{ ...renderData, user: game.user }
|
|
||||||
);
|
|
||||||
const mess = game.messages.get(message._id);
|
|
||||||
await emitAsGM(
|
|
||||||
GMUpdateEvent.UpdateDocument,
|
|
||||||
mess.update.bind(mess),
|
|
||||||
{
|
|
||||||
...renderData,
|
|
||||||
content: updatedContent
|
|
||||||
},
|
|
||||||
mess.uuid
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async groupRollExpandSection(event) {
|
|
||||||
event.target
|
|
||||||
.closest('.group-roll-header-expand-section')
|
|
||||||
.querySelectorAll('i')
|
|
||||||
.forEach(element => {
|
|
||||||
element.classList.toggle('fa-angle-up');
|
|
||||||
element.classList.toggle('fa-angle-down');
|
|
||||||
});
|
|
||||||
event.target.closest('.group-roll-section').querySelector('.group-roll-content').classList.toggle('closed');
|
|
||||||
}
|
|
||||||
|
|
||||||
async riskItAllClearStressAndHitPoints(event, data) {
|
async riskItAllClearStressAndHitPoints(event, data) {
|
||||||
const resourceValue = event.target.dataset.resourceValue;
|
const resourceValue = event.target.dataset.resourceValue;
|
||||||
const actor = game.actors.get(event.target.dataset.actorId);
|
const actor = game.actors.get(event.target.dataset.actorId);
|
||||||
|
|
|
||||||
|
|
@ -84,15 +84,15 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
||||||
_getCombatContextOptions() {
|
_getCombatContextOptions() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
name: 'COMBAT.ClearMovementHistories',
|
label: 'COMBAT.ClearMovementHistories',
|
||||||
icon: '<i class="fa-solid fa-shoe-prints"></i>',
|
icon: '<i class="fa-solid fa-shoe-prints"></i>',
|
||||||
condition: () => game.user.isGM && this.viewed?.combatants.size > 0,
|
visible: () => game.user.isGM && this.viewed?.combatants.size > 0,
|
||||||
callback: () => this.viewed.clearMovementHistories()
|
callback: () => this.viewed.clearMovementHistories()
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'COMBAT.Delete',
|
label: 'COMBAT.Delete',
|
||||||
icon: '<i class="fa-solid fa-trash"></i>',
|
icon: '<i class="fa-solid fa-trash"></i>',
|
||||||
condition: () => game.user.isGM && !!this.viewed,
|
visible: () => game.user.isGM && !!this.viewed,
|
||||||
callback: () => this.viewed.endCombat()
|
callback: () => this.viewed.endCombat()
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -137,6 +137,8 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
||||||
}
|
}
|
||||||
|
|
||||||
static #getPlayerOwnership(user, setting, countdown) {
|
static #getPlayerOwnership(user, setting, countdown) {
|
||||||
|
if (user.isGM) return CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER;
|
||||||
|
|
||||||
const playerOwnership = countdown.ownership[user.id];
|
const playerOwnership = countdown.ownership[user.id];
|
||||||
return playerOwnership === undefined || playerOwnership === CONST.DOCUMENT_OWNERSHIP_LEVELS.INHERIT
|
return playerOwnership === undefined || playerOwnership === CONST.DOCUMENT_OWNERSHIP_LEVELS.INHERIT
|
||||||
? setting.defaultOwnership
|
? setting.defaultOwnership
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV
|
||||||
tag: 'div',
|
tag: 'div',
|
||||||
window: {
|
window: {
|
||||||
frame: true,
|
frame: true,
|
||||||
title: 'Fear',
|
title: 'DAGGERHEART.GENERAL.fear',
|
||||||
positioned: true,
|
positioned: true,
|
||||||
resizable: true,
|
resizable: true,
|
||||||
minimizable: false
|
minimizable: false
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
tag: 'div',
|
tag: 'div',
|
||||||
window: {
|
window: {
|
||||||
frame: true,
|
frame: true,
|
||||||
title: 'Compendium Browser',
|
title: 'DAGGERHEART.UI.ItemBrowser.windowTitle',
|
||||||
icon: 'fa-solid fa-book-atlas',
|
icon: 'fa-solid fa-book-atlas',
|
||||||
positioned: true,
|
positioned: true,
|
||||||
resizable: true
|
resizable: true
|
||||||
|
|
@ -207,8 +207,23 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
label: game.i18n.localize(col.label)
|
label: game.i18n.localize(col.label)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const splitPath = folderId?.split('.') ?? [];
|
||||||
|
const { pathLabels } = splitPath.reduce(
|
||||||
|
(acc, curr) => {
|
||||||
|
acc.currentPath = !acc.currentPath ? curr : [acc.currentPath, curr].join('.');
|
||||||
|
if (curr === 'folder') return acc;
|
||||||
|
|
||||||
|
const label = foundry.utils.getProperty(this.config, acc.currentPath)?.label;
|
||||||
|
if (label) acc.pathLabels.push(game.i18n.localize(label));
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{ pathLabels: [], currentPath: '' }
|
||||||
|
);
|
||||||
|
|
||||||
this.selectedMenu = {
|
this.selectedMenu = {
|
||||||
path: folderId?.split('.') ?? [],
|
path: splitPath,
|
||||||
|
pathLabels: pathLabels,
|
||||||
data: {
|
data: {
|
||||||
...folderData,
|
...folderData,
|
||||||
columns: columns
|
columns: columns
|
||||||
|
|
@ -568,7 +583,9 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
const { itemUuid } = event.target.closest('[data-item-uuid]').dataset,
|
const { itemUuid } = event.target.closest('[data-item-uuid]').dataset,
|
||||||
item = await foundry.utils.fromUuid(itemUuid),
|
item = await foundry.utils.fromUuid(itemUuid),
|
||||||
dragData = item.toDragData();
|
dragData = item.toDragData();
|
||||||
|
|
||||||
event.dataTransfer.setData('text/plain', JSON.stringify(dragData));
|
event.dataTransfer.setData('text/plain', JSON.stringify(dragData));
|
||||||
|
event.dataTransfer.setDragImage(event.target.querySelector('img'), 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
_canDragStart() {
|
_canDragStart() {
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,11 @@ export default class DHContextMenu extends foundry.applications.ux.ContextMenu {
|
||||||
static triggerContextMenu(event, altSelector) {
|
static triggerContextMenu(event, altSelector) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const { clientX, clientY } = event;
|
|
||||||
const selector = altSelector ?? '[data-item-uuid]';
|
const selector = altSelector ?? '[data-item-uuid]';
|
||||||
|
if (ui.context?.selector === selector) return;
|
||||||
|
|
||||||
|
const { clientX, clientY } = event;
|
||||||
const target = event.target.closest(selector) ?? event.currentTarget.closest(selector);
|
const target = event.target.closest(selector) ?? event.currentTarget.closest(selector);
|
||||||
target?.dispatchEvent(
|
target?.dispatchEvent(
|
||||||
new PointerEvent('contextmenu', {
|
new PointerEvent('contextmenu', {
|
||||||
|
|
|
||||||
|
|
@ -188,7 +188,7 @@ export default class FilterMenu extends foundry.applications.ux.ContextMenu {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const damageTypeFilter = Object.values(CONFIG.DH.GENERAL.damageTypes).map(({ id, abbreviation }) => ({
|
const damageTypeFilter = Object.values(CONFIG.DH.GENERAL.damageTypes).map(({ id, abbreviation }) => ({
|
||||||
group: 'Damage Type', //TODO localize
|
group: game.i18n.localize('DAGGERHEART.GENERAL.damageType'),
|
||||||
name: game.i18n.localize(abbreviation),
|
name: game.i18n.localize(abbreviation),
|
||||||
filter: {
|
filter: {
|
||||||
field: 'system.damage.type',
|
field: 'system.damage.type',
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ export default class DhMeasuredTemplate extends foundry.canvas.placeables.Measur
|
||||||
|
|
||||||
static getRangeLabels(distanceValue, settings) {
|
static getRangeLabels(distanceValue, settings) {
|
||||||
let result = { distance: distanceValue, units: '' };
|
let result = { distance: distanceValue, units: '' };
|
||||||
if (!settings.enabled) return result;
|
if (!settings.enabled || !canvas.scene) return result;
|
||||||
|
|
||||||
const sceneRangeMeasurement = canvas.scene.flags.daggerheart?.rangeMeasurement;
|
const sceneRangeMeasurement = canvas.scene.flags.daggerheart?.rangeMeasurement;
|
||||||
const { disable, custom } = CONFIG.DH.GENERAL.sceneRangeMeasurementSetting;
|
const { disable, custom } = CONFIG.DH.GENERAL.sceneRangeMeasurementSetting;
|
||||||
|
|
|
||||||
|
|
@ -95,4 +95,61 @@ export default class DhRegionLayer extends foundry.canvas.layers.RegionLayer {
|
||||||
});
|
});
|
||||||
return inBounds.length === 1 ? inBounds[0] : null;
|
return inBounds.length === 1 ? inBounds[0] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getTemplateShape({ type, angle, range, direction } = {}) {
|
||||||
|
const { line, rectangle, inFront, cone, circle, emanation } = CONFIG.DH.GENERAL.templateTypes;
|
||||||
|
|
||||||
|
/* Length calculation */
|
||||||
|
const { grid, distance } = CONFIG.Scene.documentClass.schema.fields.grid.fields;
|
||||||
|
const sceneGridSize = canvas.scene?.grid.size ?? grid.size.initial;
|
||||||
|
const sceneGridDistance = canvas.scene?.grid.distance ?? distance.getInitialValue();
|
||||||
|
const dimensionConstant = sceneGridSize / sceneGridDistance;
|
||||||
|
|
||||||
|
const settings = canvas.scene?.rangeSettings;
|
||||||
|
const rangeNumber = Number(range);
|
||||||
|
const length = (!Number.isNaN(rangeNumber) ? rangeNumber : settings ? settings[range] : 0) * dimensionConstant;
|
||||||
|
/*----*/
|
||||||
|
|
||||||
|
const shapeData = {
|
||||||
|
...canvas.mousePosition,
|
||||||
|
type: type,
|
||||||
|
direction: direction ?? 0
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case rectangle.id:
|
||||||
|
shapeData.width = length;
|
||||||
|
shapeData.height = length;
|
||||||
|
break;
|
||||||
|
case line.id:
|
||||||
|
shapeData.length = length;
|
||||||
|
shapeData.width = 5 * dimensionConstant;
|
||||||
|
break;
|
||||||
|
case cone.id:
|
||||||
|
shapeData.angle = angle ?? CONFIG.MeasuredTemplate.defaults.angle;
|
||||||
|
shapeData.radius = length;
|
||||||
|
break;
|
||||||
|
case inFront.id:
|
||||||
|
shapeData.angle = '180';
|
||||||
|
shapeData.radius = length;
|
||||||
|
shapeData.type = cone.id;
|
||||||
|
break;
|
||||||
|
case circle.id:
|
||||||
|
shapeData.radius = length;
|
||||||
|
break;
|
||||||
|
case emanation.id:
|
||||||
|
shapeData.radius = length;
|
||||||
|
shapeData.base = {
|
||||||
|
type: 'token',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 1,
|
||||||
|
height: 1,
|
||||||
|
shape: game.canvas.grid.isHexagonal ? CONST.TOKEN_SHAPES.ELLIPSE_1 : CONST.TOKEN_SHAPES.RECTANGLE_1
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return shapeData;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1 @@
|
||||||
export default class DhTokenLayer extends foundry.canvas.layers.TokenLayer {
|
export default class DhTokenLayer extends foundry.canvas.layers.TokenLayer {}
|
||||||
async _createPreview(createData, options) {
|
|
||||||
if (options.actor) {
|
|
||||||
const tokenSizes = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes;
|
|
||||||
if (options.actor?.system.metadata.usesSize) {
|
|
||||||
const tokenSize = tokenSizes[options.actor.system.size];
|
|
||||||
if (tokenSize && options.actor.system.size !== CONFIG.DH.ACTOR.tokenSize.custom.id) {
|
|
||||||
createData.width = tokenSize;
|
|
||||||
createData.height = tokenSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return super._createPreview(createData, options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -115,3 +115,10 @@ export const advantageState = {
|
||||||
value: 1
|
value: 1
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const areaTypes = {
|
||||||
|
placed: {
|
||||||
|
id: 'placed',
|
||||||
|
label: 'Placed Area'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -70,14 +70,40 @@ export const range = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const groupAttackRange = {
|
||||||
|
melee: range.melee,
|
||||||
|
veryClose: range.veryClose,
|
||||||
|
close: range.close,
|
||||||
|
far: range.far,
|
||||||
|
veryFar: range.veryFar
|
||||||
|
};
|
||||||
|
|
||||||
/* circle|cone|rect|ray used to be CONST.MEASURED_TEMPLATE_TYPES. Hardcoded for now */
|
/* circle|cone|rect|ray used to be CONST.MEASURED_TEMPLATE_TYPES. Hardcoded for now */
|
||||||
export const templateTypes = {
|
export const templateTypes = {
|
||||||
CIRCLE: 'circle',
|
circle: {
|
||||||
CONE: 'cone',
|
id: 'circle',
|
||||||
RECTANGLE: 'rectangle',
|
label: 'Circle'
|
||||||
LINE: 'line',
|
},
|
||||||
EMANATION: 'emanation',
|
cone: {
|
||||||
INFRONT: 'inFront'
|
id: 'cone',
|
||||||
|
label: 'Cone'
|
||||||
|
},
|
||||||
|
rectangle: {
|
||||||
|
id: 'rectangle',
|
||||||
|
label: 'Rectangle'
|
||||||
|
},
|
||||||
|
line: {
|
||||||
|
id: 'line',
|
||||||
|
label: 'Line'
|
||||||
|
},
|
||||||
|
emanation: {
|
||||||
|
id: 'emanation',
|
||||||
|
label: 'Emanation'
|
||||||
|
},
|
||||||
|
inFront: {
|
||||||
|
id: 'inFront',
|
||||||
|
label: 'In Front'
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const rangeInclusion = {
|
export const rangeInclusion = {
|
||||||
|
|
@ -484,7 +510,7 @@ export const defaultRestOptions = {
|
||||||
value: {
|
value: {
|
||||||
custom: {
|
custom: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
formula: '@system.armorScore'
|
formula: '@system.armorScore.max'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -708,14 +734,14 @@ const getDiceSoNiceSFX = sfxOptions => {
|
||||||
if (sfxOptions.critical && criticalAnimationData.class) {
|
if (sfxOptions.critical && criticalAnimationData.class) {
|
||||||
return {
|
return {
|
||||||
specialEffect: criticalAnimationData.class,
|
specialEffect: criticalAnimationData.class,
|
||||||
options: {}
|
options: { ...criticalAnimationData.options }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sfxOptions.higher && sfxOptions.data.higher) {
|
if (sfxOptions.higher && sfxOptions.data.higher) {
|
||||||
return {
|
return {
|
||||||
specialEffect: sfxOptions.data.higher.class,
|
specialEffect: sfxOptions.data.higher.class,
|
||||||
options: {}
|
options: { ...sfxOptions.data.higher.options }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -947,15 +973,15 @@ export const countdownAppMode = {
|
||||||
export const sceneRangeMeasurementSetting = {
|
export const sceneRangeMeasurementSetting = {
|
||||||
disable: {
|
disable: {
|
||||||
id: 'disable',
|
id: 'disable',
|
||||||
label: 'Disable Daggerheart Range Measurement'
|
label: 'DAGGERHEART.CONFIG.SceneRangeMeasurementTypes.disable'
|
||||||
},
|
},
|
||||||
default: {
|
default: {
|
||||||
id: 'default',
|
id: 'default',
|
||||||
label: 'Default'
|
label: 'DAGGERHEART.CONFIG.SceneRangeMeasurementTypes.default'
|
||||||
},
|
},
|
||||||
custom: {
|
custom: {
|
||||||
id: 'custom',
|
id: 'custom',
|
||||||
label: 'Custom'
|
label: 'DAGGERHEART.CONFIG.SceneRangeMeasurementTypes.custom'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1084,3 +1110,18 @@ export const fallAndCollisionDamage = {
|
||||||
damageFormula: '1d20 + 5'
|
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,5 +1,6 @@
|
||||||
export const hooksConfig = {
|
export const hooksConfig = {
|
||||||
effectDisplayToggle: 'DHEffectDisplayToggle',
|
effectDisplayToggle: 'DHEffectDisplayToggle',
|
||||||
lockedTooltipDismissed: 'DHLockedTooltipDismissed',
|
lockedTooltipDismissed: 'DHLockedTooltipDismissed',
|
||||||
tagTeamStart: 'DHTagTeamRollStart'
|
tagTeamStart: 'DHTagTeamRollStart',
|
||||||
|
groupRollStart: 'DHGroupRollStart'
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,12 @@ export const typeConfig = {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'system.type',
|
key: 'system.type',
|
||||||
label: 'DAGGERHEART.GENERAL.type'
|
label: 'DAGGERHEART.GENERAL.type',
|
||||||
|
format: type => {
|
||||||
|
if (!type) return '-';
|
||||||
|
|
||||||
|
return CONFIG.DH.ACTOR.allAdversaryTypes()[type].label;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
filters: [
|
filters: [
|
||||||
|
|
@ -69,12 +74,18 @@ export const typeConfig = {
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
key: 'type',
|
key: 'type',
|
||||||
label: 'DAGGERHEART.GENERAL.type'
|
label: 'DAGGERHEART.GENERAL.type',
|
||||||
|
format: type => (type ? `TYPES.Item.${type}` : '-')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'system.secondary',
|
key: 'system.secondary',
|
||||||
label: 'DAGGERHEART.UI.ItemBrowser.subtype',
|
label: 'DAGGERHEART.UI.ItemBrowser.subtype',
|
||||||
format: isSecondary => (isSecondary ? 'secondary' : isSecondary === false ? 'primary' : '-')
|
format: isSecondary =>
|
||||||
|
isSecondary
|
||||||
|
? 'DAGGERHEART.ITEMS.Weapon.secondaryWeapon.short'
|
||||||
|
: isSecondary === false
|
||||||
|
? 'DAGGERHEART.ITEMS.Weapon.primaryWeapon.short'
|
||||||
|
: '-'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'system.tier',
|
key: 'system.tier',
|
||||||
|
|
@ -94,8 +105,8 @@ export const typeConfig = {
|
||||||
key: 'system.secondary',
|
key: 'system.secondary',
|
||||||
label: 'DAGGERHEART.UI.ItemBrowser.subtype',
|
label: 'DAGGERHEART.UI.ItemBrowser.subtype',
|
||||||
choices: [
|
choices: [
|
||||||
{ value: false, label: 'DAGGERHEART.ITEMS.Weapon.primaryWeapon' },
|
{ value: false, label: 'DAGGERHEART.ITEMS.Weapon.primaryWeapon.full' },
|
||||||
{ value: true, label: 'DAGGERHEART.ITEMS.Weapon.secondaryWeapon' }
|
{ value: true, label: 'DAGGERHEART.ITEMS.Weapon.secondaryWeapon.full' }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -253,11 +264,13 @@ export const typeConfig = {
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
key: 'system.type',
|
key: 'system.type',
|
||||||
label: 'DAGGERHEART.GENERAL.type'
|
label: 'DAGGERHEART.GENERAL.type',
|
||||||
|
format: type => (type ? `DAGGERHEART.CONFIG.DomainCardTypes.${type}` : '-')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'system.domain',
|
key: 'system.domain',
|
||||||
label: 'DAGGERHEART.GENERAL.Domain.single'
|
label: 'DAGGERHEART.GENERAL.Domain.single',
|
||||||
|
format: domain => (domain ? CONFIG.DH.DOMAIN.allDomains()[domain].label : '-')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'system.level',
|
key: 'system.level',
|
||||||
|
|
@ -318,7 +331,14 @@ export const typeConfig = {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'system.domains',
|
key: 'system.domains',
|
||||||
label: 'DAGGERHEART.GENERAL.Domain.plural'
|
label: 'DAGGERHEART.GENERAL.Domain.plural',
|
||||||
|
format: domains => {
|
||||||
|
const config = CONFIG.DH.DOMAIN.allDomains();
|
||||||
|
return domains
|
||||||
|
.map(x => (x ? game.i18n.localize(config[x].label) : null))
|
||||||
|
.filter(x => x)
|
||||||
|
.join(', ');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
filters: [
|
filters: [
|
||||||
|
|
@ -362,18 +382,19 @@ export const typeConfig = {
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
key: 'system.linkedClass',
|
key: 'system.linkedClass',
|
||||||
label: 'Class',
|
label: 'TYPES.Item.class',
|
||||||
format: linkedClass => linkedClass?.name ?? 'DAGGERHEART.UI.ItemBrowser.missing'
|
format: linkedClass => linkedClass?.name ?? 'DAGGERHEART.UI.ItemBrowser.missing'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'system.spellcastingTrait',
|
key: 'system.spellcastingTrait',
|
||||||
label: 'DAGGERHEART.ITEMS.Subclass.spellcastingTrait'
|
label: 'DAGGERHEART.ITEMS.Subclass.spellcastingTrait',
|
||||||
|
format: trait => (trait ? `DAGGERHEART.CONFIG.Traits.${trait}.name` : '-')
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
filters: [
|
filters: [
|
||||||
{
|
{
|
||||||
key: 'system.linkedClass.uuid',
|
key: 'system.linkedClass.uuid',
|
||||||
label: 'Class',
|
label: 'TYPES.Item.class',
|
||||||
choices: items => {
|
choices: items => {
|
||||||
const list = items
|
const list = items
|
||||||
.filter(item => item.system.linkedClass)
|
.filter(item => item.system.linkedClass)
|
||||||
|
|
@ -397,7 +418,8 @@ export const typeConfig = {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'system.mainTrait',
|
key: 'system.mainTrait',
|
||||||
label: 'DAGGERHEART.GENERAL.Trait.single'
|
label: 'DAGGERHEART.GENERAL.Trait.single',
|
||||||
|
format: trait => (trait ? `DAGGERHEART.CONFIG.Traits.${trait}.name` : '-')
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
filters: [
|
filters: [
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
export const keybindings = {
|
export const keybindings = {
|
||||||
spotlight: 'DHSpotlight'
|
spotlight: 'DHSpotlight',
|
||||||
|
partySheet: 'DHPartySheet'
|
||||||
};
|
};
|
||||||
|
|
||||||
export const menu = {
|
export const menu = {
|
||||||
|
|
@ -40,24 +41,21 @@ export const gameSettings = {
|
||||||
LastMigrationVersion: 'LastMigrationVersion',
|
LastMigrationVersion: 'LastMigrationVersion',
|
||||||
SpotlightRequestQueue: 'SpotlightRequestQueue',
|
SpotlightRequestQueue: 'SpotlightRequestQueue',
|
||||||
CompendiumBrowserSettings: 'CompendiumBrowserSettings',
|
CompendiumBrowserSettings: 'CompendiumBrowserSettings',
|
||||||
SpotlightTracker: 'SpotlightTracker'
|
SpotlightTracker: 'SpotlightTracker',
|
||||||
|
ActiveParty: 'ActiveParty'
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actionAutomationChoices = {
|
export const actionAutomationChoices = {
|
||||||
never: {
|
never: {
|
||||||
id: 'never',
|
id: 'never',
|
||||||
label: 'Never'
|
label: 'DAGGERHEART.CONFIG.ActionAutomationChoices.never'
|
||||||
},
|
},
|
||||||
showDialog: {
|
showDialog: {
|
||||||
id: 'showDialog',
|
id: 'showDialog',
|
||||||
label: 'Show Dialog only'
|
label: 'DAGGERHEART.CONFIG.ActionAutomationChoices.showDialog'
|
||||||
},
|
},
|
||||||
// npcOnly: {
|
|
||||||
// id: "npcOnly",
|
|
||||||
// label: "Always for non-characters"
|
|
||||||
// },
|
|
||||||
always: {
|
always: {
|
||||||
id: 'always',
|
id: 'always',
|
||||||
label: 'Always'
|
label: 'DAGGERHEART.CONFIG.ActionAutomationChoices.always'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ export { default as DhRollTable } from './rollTable.mjs';
|
||||||
export { default as RegisteredTriggers } from './registeredTriggers.mjs';
|
export { default as RegisteredTriggers } from './registeredTriggers.mjs';
|
||||||
export { default as CompendiumBrowserSettings } from './compendiumBrowserSettings.mjs';
|
export { default as CompendiumBrowserSettings } from './compendiumBrowserSettings.mjs';
|
||||||
export { default as TagTeamData } from './tagTeamData.mjs';
|
export { default as TagTeamData } from './tagTeamData.mjs';
|
||||||
|
export { default as GroupRollData } from './groupRollData.mjs';
|
||||||
export { default as SpotlightTracker } from './spotlightTracker.mjs';
|
export { default as SpotlightTracker } from './spotlightTracker.mjs';
|
||||||
|
|
||||||
export * as countdowns from './countdowns.mjs';
|
export * as countdowns from './countdowns.mjs';
|
||||||
|
|
@ -14,3 +15,4 @@ export * as chatMessages from './chat-message/_modules.mjs';
|
||||||
export * as fields from './fields/_module.mjs';
|
export * as fields from './fields/_module.mjs';
|
||||||
export * as items from './item/_module.mjs';
|
export * as items from './item/_module.mjs';
|
||||||
export * as scenes from './scene/_module.mjs';
|
export * as scenes from './scene/_module.mjs';
|
||||||
|
export * as regionBehaviors from './regionBehavior/_module.mjs';
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ export default class DHAttackAction extends DHDamageAction {
|
||||||
if (!!this.item?.system?.attack) {
|
if (!!this.item?.system?.attack) {
|
||||||
if (this.damage.includeBase) {
|
if (this.damage.includeBase) {
|
||||||
const baseDamage = this.getParentDamage();
|
const baseDamage = this.getParentDamage();
|
||||||
this.damage.parts.unshift(new DHDamageData(baseDamage));
|
this.damage.parts.hitPoints = new DHDamageData(baseDamage);
|
||||||
}
|
}
|
||||||
if (this.roll.useDefault) {
|
if (this.roll.useDefault) {
|
||||||
this.roll.trait = this.item.system.attack.roll.trait;
|
this.roll.trait = this.item.system.attack.roll.trait;
|
||||||
|
|
@ -51,7 +51,7 @@ export default class DHAttackAction extends DHDamageAction {
|
||||||
async use(event, options) {
|
async use(event, options) {
|
||||||
const result = await super.use(event, options);
|
const result = await super.use(event, options);
|
||||||
|
|
||||||
if (result.message?.system.action.roll?.type === 'attack') {
|
if (result?.message?.system.action?.roll?.type === 'attack') {
|
||||||
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
|
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
|
||||||
await updateCountdowns(CONFIG.DH.GENERAL.countdownProgressionTypes.characterAttack.id);
|
await updateCountdowns(CONFIG.DH.GENERAL.countdownProgressionTypes.characterAttack.id);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ const fields = foundry.data.fields;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel) {
|
export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel) {
|
||||||
static extraSchemas = ['cost', 'uses', 'range'];
|
static extraSchemas = ['areas', 'cost', 'uses', 'range'];
|
||||||
|
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
static defineSchema() {
|
static defineSchema() {
|
||||||
|
|
@ -110,6 +110,11 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
return this._id;
|
return this._id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns true if the current user is the owner of the containing item */
|
||||||
|
get isOwner() {
|
||||||
|
return this.item?.isOwner ?? true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return Item the action is attached too.
|
* Return Item the action is attached too.
|
||||||
*/
|
*/
|
||||||
|
|
@ -143,6 +148,12 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns true if the action is usable */
|
||||||
|
get usable() {
|
||||||
|
const actor = this.actor;
|
||||||
|
return this.isOwner && actor?.type === 'character';
|
||||||
|
}
|
||||||
|
|
||||||
static getRollType(parent) {
|
static getRollType(parent) {
|
||||||
return 'trait';
|
return 'trait';
|
||||||
}
|
}
|
||||||
|
|
@ -280,6 +291,26 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (this.damage) {
|
||||||
|
config.isDirect = this.damage.direct;
|
||||||
|
|
||||||
|
const groupAttackTokens = this.damage.groupAttack
|
||||||
|
? game.system.api.fields.ActionFields.DamageField.getGroupAttackTokens(
|
||||||
|
this.actor.id,
|
||||||
|
this.damage.groupAttack
|
||||||
|
)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
config.damageOptions = {
|
||||||
|
groupAttack: this.damage.groupAttack
|
||||||
|
? {
|
||||||
|
numAttackers: Math.max(groupAttackTokens.length, 1),
|
||||||
|
range: this.damage.groupAttack
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
DHBaseAction.applyKeybindings(config);
|
DHBaseAction.applyKeybindings(config);
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,12 @@ export default class BaseEffect extends foundry.data.ActiveEffectTypeDataModel {
|
||||||
max: new fields.NumberField({ integer: true, label: 'DAGGERHEART.GENERAL.max' })
|
max: new fields.NumberField({ integer: true, label: 'DAGGERHEART.GENERAL.max' })
|
||||||
},
|
},
|
||||||
{ nullable: true, initial: null }
|
{ nullable: true, initial: null }
|
||||||
|
),
|
||||||
|
targetDispositions: new fields.SetField(
|
||||||
|
new fields.NumberField({
|
||||||
|
choices: CONFIG.DH.GENERAL.simpleDispositions
|
||||||
|
}),
|
||||||
|
{ label: 'DAGGERHEART.ACTIVEEFFECT.Config.targetDispositions' }
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -131,13 +137,14 @@ export default class BaseEffect extends foundry.data.ActiveEffectTypeDataModel {
|
||||||
return armorChange.getArmorData();
|
return armorChange.getArmorData();
|
||||||
}
|
}
|
||||||
|
|
||||||
static getDefaultObject() {
|
static getDefaultObject(options = { transfer: true }) {
|
||||||
return {
|
return {
|
||||||
name: 'New Effect',
|
name: 'New Effect',
|
||||||
id: foundry.utils.randomID(),
|
id: foundry.utils.randomID(),
|
||||||
disabled: false,
|
disabled: false,
|
||||||
img: 'icons/magic/life/heart-cross-blue.webp',
|
img: 'icons/magic/life/heart-cross-blue.webp',
|
||||||
description: '',
|
description: '',
|
||||||
|
transfer: options.transfer,
|
||||||
statuses: [],
|
statuses: [],
|
||||||
changes: [],
|
changes: [],
|
||||||
system: {
|
system: {
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,8 @@ export default class ArmorChange extends foundry.abstract.DataModel {
|
||||||
label: 'Armor',
|
label: 'Armor',
|
||||||
defaultPriority: 20,
|
defaultPriority: 20,
|
||||||
handler: (actor, change, _options, _field, replacementData) => {
|
handler: (actor, change, _options, _field, replacementData) => {
|
||||||
const parsedMax = itemAbleRollParse(change.value.max, actor, change.effect.parent);
|
const baseParsedMax = itemAbleRollParse(change.value.max, actor, change.effect.parent);
|
||||||
|
const parsedMax = new Roll(baseParsedMax).evaluateSync().total;
|
||||||
game.system.api.documents.DhActiveEffect.applyChange(
|
game.system.api.documents.DhActiveEffect.applyChange(
|
||||||
actor,
|
actor,
|
||||||
{
|
{
|
||||||
|
|
@ -81,7 +82,7 @@ export default class ArmorChange extends foundry.abstract.DataModel {
|
||||||
{
|
{
|
||||||
...change,
|
...change,
|
||||||
key: 'system.damageThresholds.major',
|
key: 'system.damageThresholds.major',
|
||||||
type: CONFIG.DH.GENERAL.activeEffectModes.override.id,
|
type: CONFIG.DH.GENERAL.activeEffectModes.add.id,
|
||||||
priority: 50,
|
priority: 50,
|
||||||
value: major
|
value: major
|
||||||
},
|
},
|
||||||
|
|
@ -95,7 +96,7 @@ export default class ArmorChange extends foundry.abstract.DataModel {
|
||||||
{
|
{
|
||||||
...change,
|
...change,
|
||||||
key: 'system.damageThresholds.severe',
|
key: 'system.damageThresholds.severe',
|
||||||
type: CONFIG.DH.GENERAL.activeEffectModes.override.id,
|
type: CONFIG.DH.GENERAL.activeEffectModes.add.id,
|
||||||
priority: 50,
|
priority: 50,
|
||||||
value: severe
|
value: severe
|
||||||
},
|
},
|
||||||
|
|
@ -110,6 +111,8 @@ export default class ArmorChange extends foundry.abstract.DataModel {
|
||||||
};
|
};
|
||||||
|
|
||||||
get isSuppressed() {
|
get isSuppressed() {
|
||||||
|
if (!this.parent.parent?.actor) return false;
|
||||||
|
|
||||||
switch (this.value.interaction) {
|
switch (this.value.interaction) {
|
||||||
case CONFIG.DH.GENERAL.activeEffectArmorInteraction.active.id:
|
case CONFIG.DH.GENERAL.activeEffectArmorInteraction.active.id:
|
||||||
return !this.parent.parent?.actor.system.armor;
|
return !this.parent.parent?.actor.system.armor;
|
||||||
|
|
|
||||||
|
|
@ -133,7 +133,7 @@ export default class DhpAdversary extends DhCreature {
|
||||||
}
|
}
|
||||||
|
|
||||||
isItemValid(source) {
|
isItemValid(source) {
|
||||||
return source.type === 'feature';
|
return super.isItemValid(source) || source.type === 'feature';
|
||||||
}
|
}
|
||||||
|
|
||||||
async _preUpdate(changes, options, user) {
|
async _preUpdate(changes, options, user) {
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,8 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
|
||||||
hasResistances: true,
|
hasResistances: true,
|
||||||
hasAttribution: false,
|
hasAttribution: false,
|
||||||
hasLimitedView: true,
|
hasLimitedView: true,
|
||||||
usesSize: false
|
usesSize: false,
|
||||||
|
hasInventory: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -168,6 +169,11 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
isItemValid(source) {
|
||||||
|
const inventoryTypes = ['weapon', 'armor', 'consumable', 'loot'];
|
||||||
|
return this.metadata.hasInventory && inventoryTypes.includes(source.type);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtain a data object used to evaluate any dice rolls associated with this Item Type
|
* Obtain a data object used to evaluate any dice rolls associated with this Item Type
|
||||||
* @param {object} [options] - Options which modify the getRollData method.
|
* @param {object} [options] - Options which modify the getRollData method.
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
||||||
import DhLevelData from '../levelData.mjs';
|
import DhLevelData from '../levelData.mjs';
|
||||||
import { commonActorRules } from './base.mjs';
|
import { commonActorRules } from './base.mjs';
|
||||||
import DhCreature from './creature.mjs';
|
import DhCreature from './creature.mjs';
|
||||||
import { attributeField, stressDamageReductionRule, bonusField } from '../fields/actorField.mjs';
|
import { attributeField, stressDamageReductionRule, bonusField, GoldField } from '../fields/actorField.mjs';
|
||||||
import { ActionField } from '../fields/actionField.mjs';
|
import { ActionField } from '../fields/actionField.mjs';
|
||||||
import DHCharacterSettings from '../../applications/sheets-configs/character-settings.mjs';
|
import DHCharacterSettings from '../../applications/sheets-configs/character-settings.mjs';
|
||||||
import { getArmorSources } from '../../helpers/utils.mjs';
|
import { getArmorSources } from '../../helpers/utils.mjs';
|
||||||
|
|
@ -18,7 +18,9 @@ export default class DhCharacter extends DhCreature {
|
||||||
label: 'TYPES.Actor.character',
|
label: 'TYPES.Actor.character',
|
||||||
type: 'character',
|
type: 'character',
|
||||||
settingSheet: DHCharacterSettings,
|
settingSheet: DHCharacterSettings,
|
||||||
isNPC: false
|
isNPC: false,
|
||||||
|
hasInventory: true,
|
||||||
|
quantifiable: ['loot', 'consumable']
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -62,12 +64,7 @@ export default class DhCharacter extends DhCreature {
|
||||||
core: new fields.BooleanField({ initial: false })
|
core: new fields.BooleanField({ initial: false })
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
gold: new fields.SchemaField({
|
gold: new GoldField(),
|
||||||
coins: new fields.NumberField({ initial: 0, integer: true }),
|
|
||||||
handfuls: new fields.NumberField({ initial: 1, integer: true }),
|
|
||||||
bags: new fields.NumberField({ initial: 0, integer: true }),
|
|
||||||
chests: new fields.NumberField({ initial: 0, integer: true })
|
|
||||||
}),
|
|
||||||
scars: new fields.NumberField({ initial: 0, integer: true, label: 'DAGGERHEART.GENERAL.scars' }),
|
scars: new fields.NumberField({ initial: 0, integer: true, label: 'DAGGERHEART.GENERAL.scars' }),
|
||||||
biography: new fields.SchemaField({
|
biography: new fields.SchemaField({
|
||||||
background: new fields.HTMLField(),
|
background: new fields.HTMLField(),
|
||||||
|
|
@ -153,7 +150,6 @@ export default class DhCharacter extends DhCreature {
|
||||||
shortMoves: new fields.NumberField({
|
shortMoves: new fields.NumberField({
|
||||||
required: true,
|
required: true,
|
||||||
integer: true,
|
integer: true,
|
||||||
min: 0,
|
|
||||||
initial: 0,
|
initial: 0,
|
||||||
label: 'DAGGERHEART.GENERAL.Bonuses.rest.shortRest.shortRestMoves.label',
|
label: 'DAGGERHEART.GENERAL.Bonuses.rest.shortRest.shortRestMoves.label',
|
||||||
hint: 'DAGGERHEART.GENERAL.Bonuses.rest.shortRest.shortRestMoves.hint'
|
hint: 'DAGGERHEART.GENERAL.Bonuses.rest.shortRest.shortRestMoves.hint'
|
||||||
|
|
@ -161,7 +157,6 @@ export default class DhCharacter extends DhCreature {
|
||||||
longMoves: new fields.NumberField({
|
longMoves: new fields.NumberField({
|
||||||
required: true,
|
required: true,
|
||||||
integer: true,
|
integer: true,
|
||||||
min: 0,
|
|
||||||
initial: 0,
|
initial: 0,
|
||||||
label: 'DAGGERHEART.GENERAL.Bonuses.rest.shortRest.longRestMoves.label',
|
label: 'DAGGERHEART.GENERAL.Bonuses.rest.shortRest.longRestMoves.label',
|
||||||
hint: 'DAGGERHEART.GENERAL.Bonuses.rest.shortRest.longRestMoves.hint'
|
hint: 'DAGGERHEART.GENERAL.Bonuses.rest.shortRest.longRestMoves.hint'
|
||||||
|
|
@ -171,7 +166,6 @@ export default class DhCharacter extends DhCreature {
|
||||||
shortMoves: new fields.NumberField({
|
shortMoves: new fields.NumberField({
|
||||||
required: true,
|
required: true,
|
||||||
integer: true,
|
integer: true,
|
||||||
min: 0,
|
|
||||||
initial: 0,
|
initial: 0,
|
||||||
label: 'DAGGERHEART.GENERAL.Bonuses.rest.longRest.shortRestMoves.label',
|
label: 'DAGGERHEART.GENERAL.Bonuses.rest.longRest.shortRestMoves.label',
|
||||||
hint: 'DAGGERHEART.GENERAL.Bonuses.rest.longRest.shortRestMoves.hint'
|
hint: 'DAGGERHEART.GENERAL.Bonuses.rest.longRest.shortRestMoves.hint'
|
||||||
|
|
@ -179,7 +173,6 @@ export default class DhCharacter extends DhCreature {
|
||||||
longMoves: new fields.NumberField({
|
longMoves: new fields.NumberField({
|
||||||
required: true,
|
required: true,
|
||||||
integer: true,
|
integer: true,
|
||||||
min: 0,
|
|
||||||
initial: 0,
|
initial: 0,
|
||||||
label: 'DAGGERHEART.GENERAL.Bonuses.rest.longRest.longRestMoves.label',
|
label: 'DAGGERHEART.GENERAL.Bonuses.rest.longRest.longRestMoves.label',
|
||||||
hint: 'DAGGERHEART.GENERAL.Bonuses.rest.longRest.longRestMoves.hint'
|
hint: 'DAGGERHEART.GENERAL.Bonuses.rest.longRest.longRestMoves.hint'
|
||||||
|
|
@ -293,6 +286,22 @@ export default class DhCharacter extends DhCreature {
|
||||||
guaranteedCritical: new fields.BooleanField({
|
guaranteedCritical: new fields.BooleanField({
|
||||||
label: 'DAGGERHEART.ACTORS.Character.roll.guaranteedCritical.label',
|
label: 'DAGGERHEART.ACTORS.Character.roll.guaranteedCritical.label',
|
||||||
hint: 'DAGGERHEART.ACTORS.Character.roll.guaranteedCritical.hint'
|
hint: 'DAGGERHEART.ACTORS.Character.roll.guaranteedCritical.hint'
|
||||||
|
}),
|
||||||
|
defaultAdvantageDice: new fields.NumberField({
|
||||||
|
nullable: true,
|
||||||
|
required: true,
|
||||||
|
integer: true,
|
||||||
|
choices: CONFIG.DH.GENERAL.dieFaces,
|
||||||
|
initial: null,
|
||||||
|
label: 'DAGGERHEART.ACTORS.Character.defaultAdvantageDice'
|
||||||
|
}),
|
||||||
|
defaultDisadvantageDice: new fields.NumberField({
|
||||||
|
nullable: true,
|
||||||
|
required: true,
|
||||||
|
integer: true,
|
||||||
|
choices: CONFIG.DH.GENERAL.dieFaces,
|
||||||
|
initial: null,
|
||||||
|
label: 'DAGGERHEART.ACTORS.Character.defaultDisadvantageDice'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
@ -438,6 +447,11 @@ export default class DhCharacter extends DhCreature {
|
||||||
return attack;
|
return attack;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* All items are valid on characters */
|
||||||
|
isItemValid() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
isItemAvailable(item) {
|
isItemAvailable(item) {
|
||||||
if (!super.isItemAvailable(this)) return false;
|
if (!super.isItemAvailable(this)) return false;
|
||||||
|
|
@ -736,13 +750,22 @@ export default class DhCharacter extends DhCreature {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Armor and ArmorEffects can set a Base Damage Threshold. Characters only gain level*2 bonus to severe if this is not present */
|
||||||
|
const severeThresholdMulitplier =
|
||||||
|
this.armor ||
|
||||||
|
this.parent.appliedEffects.some(x =>
|
||||||
|
x.system.changes.some(x => x.type === 'armor' && x.value.damageThresholds)
|
||||||
|
)
|
||||||
|
? 1
|
||||||
|
: 2;
|
||||||
|
|
||||||
this.damageThresholds = {
|
this.damageThresholds = {
|
||||||
major: this.armor
|
major: this.armor
|
||||||
? this.armor.system.baseThresholds.major + this.levelData.level.current
|
? this.armor.system.baseThresholds.major + this.levelData.level.current
|
||||||
: this.levelData.level.current,
|
: this.levelData.level.current,
|
||||||
severe: this.armor
|
severe: this.armor
|
||||||
? this.armor.system.baseThresholds.severe + this.levelData.level.current
|
? this.armor.system.baseThresholds.severe + this.levelData.level.current
|
||||||
: this.levelData.level.current * 2
|
: this.levelData.level.current * severeThresholdMulitplier
|
||||||
};
|
};
|
||||||
|
|
||||||
const globalHopeMax = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxHope;
|
const globalHopeMax = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxHope;
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,24 @@ export default class DhCompanion extends DhCreature {
|
||||||
initial: false,
|
initial: false,
|
||||||
label: 'DAGGERHEART.GENERAL.Rules.conditionImmunities.vulnerable'
|
label: 'DAGGERHEART.GENERAL.Rules.conditionImmunities.vulnerable'
|
||||||
})
|
})
|
||||||
|
}),
|
||||||
|
roll: new fields.SchemaField({
|
||||||
|
defaultAdvantageDice: new fields.NumberField({
|
||||||
|
nullable: true,
|
||||||
|
required: true,
|
||||||
|
integer: true,
|
||||||
|
choices: CONFIG.DH.GENERAL.dieFaces,
|
||||||
|
initial: null,
|
||||||
|
label: 'DAGGERHEART.ACTORS.Character.defaultAdvantageDice'
|
||||||
|
}),
|
||||||
|
defaultDisadvantageDice: new fields.NumberField({
|
||||||
|
nullable: true,
|
||||||
|
required: true,
|
||||||
|
integer: true,
|
||||||
|
choices: CONFIG.DH.GENERAL.dieFaces,
|
||||||
|
initial: null,
|
||||||
|
label: 'DAGGERHEART.ACTORS.Character.defaultDisadvantageDice'
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
attack: new ActionField({
|
attack: new ActionField({
|
||||||
|
|
@ -118,10 +136,6 @@ export default class DhCompanion extends DhCreature {
|
||||||
return this.levelupChoicesLeft > 0;
|
return this.levelupChoicesLeft > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
isItemValid() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
prepareBaseData() {
|
prepareBaseData() {
|
||||||
super.prepareBaseData();
|
super.prepareBaseData();
|
||||||
this.attack.roll.bonus = this.partner?.system?.spellcastModifier ?? 0;
|
this.attack.roll.bonus = this.partner?.system?.spellcastModifier ?? 0;
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ export default class DhEnvironment extends BaseDataActor {
|
||||||
}
|
}
|
||||||
|
|
||||||
isItemValid(source) {
|
isItemValid(source) {
|
||||||
return source.type === 'feature';
|
return super.isItemValid(source) || source.type === 'feature';
|
||||||
}
|
}
|
||||||
|
|
||||||
_onUpdate(changes, options, userId) {
|
_onUpdate(changes, options, userId) {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,18 @@
|
||||||
import BaseDataActor from './base.mjs';
|
import BaseDataActor from './base.mjs';
|
||||||
import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs';
|
import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs';
|
||||||
import TagTeamData from '../tagTeamData.mjs';
|
import TagTeamData from '../tagTeamData.mjs';
|
||||||
|
import GroupRollData from '../groupRollData.mjs';
|
||||||
|
import { GoldField } from '../fields/actorField.mjs';
|
||||||
|
|
||||||
export default class DhParty extends BaseDataActor {
|
export default class DhParty extends BaseDataActor {
|
||||||
|
/** @inheritdoc */
|
||||||
|
static get metadata() {
|
||||||
|
return foundry.utils.mergeObject(super.metadata, {
|
||||||
|
hasInventory: true,
|
||||||
|
quantifiable: ['weapon', 'armor', 'loot', 'consumable']
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**@inheritdoc */
|
/**@inheritdoc */
|
||||||
static defineSchema() {
|
static defineSchema() {
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
|
|
@ -10,16 +20,16 @@ export default class DhParty extends BaseDataActor {
|
||||||
...super.defineSchema(),
|
...super.defineSchema(),
|
||||||
partyMembers: new ForeignDocumentUUIDArrayField({ type: 'Actor' }, { prune: true }),
|
partyMembers: new ForeignDocumentUUIDArrayField({ type: 'Actor' }, { prune: true }),
|
||||||
notes: new fields.HTMLField(),
|
notes: new fields.HTMLField(),
|
||||||
gold: new fields.SchemaField({
|
gold: new GoldField(),
|
||||||
coins: new fields.NumberField({ initial: 0, integer: true }),
|
tagTeam: new fields.EmbeddedDataField(TagTeamData),
|
||||||
handfuls: new fields.NumberField({ initial: 1, integer: true }),
|
groupRoll: new fields.EmbeddedDataField(GroupRollData)
|
||||||
bags: new fields.NumberField({ initial: 0, integer: true }),
|
|
||||||
chests: new fields.NumberField({ initial: 0, integer: true })
|
|
||||||
}),
|
|
||||||
tagTeam: new fields.EmbeddedDataField(TagTeamData)
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get active() {
|
||||||
|
return game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.ActiveParty) === this.parent.id;
|
||||||
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**@inheritdoc */
|
/**@inheritdoc */
|
||||||
|
|
@ -27,10 +37,6 @@ export default class DhParty extends BaseDataActor {
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
isItemValid(source) {
|
|
||||||
return ['weapon', 'armor', 'consumable', 'loot'].includes(source.type);
|
|
||||||
}
|
|
||||||
|
|
||||||
prepareBaseData() {
|
prepareBaseData() {
|
||||||
super.prepareBaseData();
|
super.prepareBaseData();
|
||||||
|
|
||||||
|
|
@ -42,6 +48,16 @@ export default class DhParty extends BaseDataActor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_onCreate(data, options, userId) {
|
||||||
|
super._onCreate(data, options, userId);
|
||||||
|
|
||||||
|
if (game.user.isActiveGM && !game.actors.party) {
|
||||||
|
game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.ActiveParty, this.parent.id).then(_ => {
|
||||||
|
ui.actors.render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_onDelete(options, userId) {
|
_onDelete(options, userId) {
|
||||||
super._onDelete(options, userId);
|
super._onDelete(options, userId);
|
||||||
|
|
||||||
|
|
@ -49,5 +65,11 @@ export default class DhParty extends BaseDataActor {
|
||||||
for (const member of this.partyMembers) {
|
for (const member of this.partyMembers) {
|
||||||
member?.parties?.delete(this.parent);
|
member?.parties?.delete(this.parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If this *was* the active party, delete it. We can't use game.actors.party as this actor was already deleted
|
||||||
|
const isWorldActor = !this.parent?.parent && !this.parent.compendium;
|
||||||
|
const activePartyId = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.ActiveParty);
|
||||||
|
if (isWorldActor && this.id === activePartyId)
|
||||||
|
game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.ActiveParty, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import DHAbilityUse from './abilityUse.mjs';
|
import DHAbilityUse from './abilityUse.mjs';
|
||||||
import DHActorRoll from './actorRoll.mjs';
|
import DHActorRoll from './actorRoll.mjs';
|
||||||
import DHGroupRoll from './groupRoll.mjs';
|
|
||||||
import DHSystemMessage from './systemMessage.mjs';
|
import DHSystemMessage from './systemMessage.mjs';
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
|
|
@ -9,6 +8,5 @@ export const config = {
|
||||||
damageRoll: DHActorRoll,
|
damageRoll: DHActorRoll,
|
||||||
dualityRoll: DHActorRoll,
|
dualityRoll: DHActorRoll,
|
||||||
fateRoll: DHActorRoll,
|
fateRoll: DHActorRoll,
|
||||||
groupRoll: DHGroupRoll,
|
|
||||||
systemMessage: DHSystemMessage
|
systemMessage: DHSystemMessage
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -7,26 +7,31 @@ export default class DHAbilityUse extends foundry.abstract.TypeDataModel {
|
||||||
img: new fields.StringField({}),
|
img: new fields.StringField({}),
|
||||||
name: new fields.StringField({}),
|
name: new fields.StringField({}),
|
||||||
description: new fields.StringField({}),
|
description: new fields.StringField({}),
|
||||||
actions: new fields.ArrayField(
|
source: new fields.SchemaField({
|
||||||
new fields.ObjectField({
|
actor: new fields.StringField(),
|
||||||
name: new fields.StringField({}),
|
item: new fields.StringField(),
|
||||||
damage: new fields.SchemaField({
|
action: new fields.StringField()
|
||||||
type: new fields.StringField({}),
|
})
|
||||||
value: new fields.StringField({})
|
|
||||||
}),
|
|
||||||
healing: new fields.SchemaField({
|
|
||||||
type: new fields.StringField({}),
|
|
||||||
value: new fields.StringField({})
|
|
||||||
}),
|
|
||||||
cost: new fields.SchemaField({
|
|
||||||
type: new fields.StringField({}),
|
|
||||||
value: new fields.NumberField({})
|
|
||||||
}),
|
|
||||||
target: new fields.SchemaField({
|
|
||||||
type: new fields.StringField({ nullable: true })
|
|
||||||
})
|
|
||||||
})
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get actionActor() {
|
||||||
|
if (!this.source.actor) return null;
|
||||||
|
return fromUuidSync(this.source.actor);
|
||||||
|
}
|
||||||
|
|
||||||
|
get actionItem() {
|
||||||
|
const actionActor = this.actionActor;
|
||||||
|
if (!actionActor || !this.source.item) return null;
|
||||||
|
|
||||||
|
const item = actionActor.items.get(this.source.item);
|
||||||
|
return item ? item.system.actions?.find(a => a.id === this.source.action) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
get action() {
|
||||||
|
const { actionItem: itemAction } = this;
|
||||||
|
if (!this.source.action) return null;
|
||||||
|
if (itemAction) return itemAction;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
||||||
action: new fields.StringField()
|
action: new fields.StringField()
|
||||||
}),
|
}),
|
||||||
damage: new fields.ObjectField(),
|
damage: new fields.ObjectField(),
|
||||||
|
damageOptions: new fields.ObjectField(),
|
||||||
costs: new fields.ArrayField(new fields.ObjectField()),
|
costs: new fields.ArrayField(new fields.ObjectField()),
|
||||||
successConsumed: new fields.BooleanField({ initial: false })
|
successConsumed: new fields.BooleanField({ initial: false })
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
import { abilities } from '../../config/actorConfig.mjs';
|
|
||||||
|
|
||||||
export default class DHGroupRoll extends foundry.abstract.TypeDataModel {
|
|
||||||
static defineSchema() {
|
|
||||||
const fields = foundry.data.fields;
|
|
||||||
|
|
||||||
return {
|
|
||||||
leader: new fields.EmbeddedDataField(GroupRollMemberField),
|
|
||||||
members: new fields.ArrayField(new fields.EmbeddedDataField(GroupRollMemberField))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
get totalModifier() {
|
|
||||||
return this.members.reduce((acc, m) => {
|
|
||||||
if (m.manualSuccess === null) return acc;
|
|
||||||
|
|
||||||
return acc + (m.manualSuccess ? 1 : -1);
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class GroupRollMemberField extends foundry.abstract.DataModel {
|
|
||||||
static defineSchema() {
|
|
||||||
const fields = foundry.data.fields;
|
|
||||||
|
|
||||||
return {
|
|
||||||
actor: new fields.ObjectField(),
|
|
||||||
trait: new fields.StringField({ choices: abilities }),
|
|
||||||
difficulty: new fields.StringField(),
|
|
||||||
result: new fields.ObjectField({ nullable: true, initial: null }),
|
|
||||||
manualSuccess: new fields.BooleanField({ nullable: true, initial: null })
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Can be expanded if we handle automation of success/failure */
|
|
||||||
get success() {
|
|
||||||
return manualSuccess;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -5,10 +5,6 @@ export default class DhCountdowns extends foundry.abstract.DataModel {
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
/* Outdated and unused. Needed for migration. Remove in next minor version. (1.3) */
|
|
||||||
narrative: new fields.EmbeddedDataField(DhCountdownData),
|
|
||||||
encounter: new fields.EmbeddedDataField(DhCountdownData),
|
|
||||||
/**/
|
|
||||||
countdowns: new fields.TypedObjectField(new fields.EmbeddedDataField(DhCountdown)),
|
countdowns: new fields.TypedObjectField(new fields.EmbeddedDataField(DhCountdown)),
|
||||||
defaultOwnership: new fields.NumberField({
|
defaultOwnership: new fields.NumberField({
|
||||||
required: true,
|
required: true,
|
||||||
|
|
@ -19,122 +15,6 @@ export default class DhCountdowns extends foundry.abstract.DataModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Outdated and unused. Needed for migration. Remove in next minor version. (1.3) */
|
|
||||||
class DhCountdownData extends foundry.abstract.DataModel {
|
|
||||||
static defineSchema() {
|
|
||||||
const fields = foundry.data.fields;
|
|
||||||
return {
|
|
||||||
countdowns: new fields.TypedObjectField(new fields.EmbeddedDataField(DhOldCountdown)),
|
|
||||||
ownership: new fields.SchemaField({
|
|
||||||
default: new fields.NumberField({
|
|
||||||
required: true,
|
|
||||||
choices: Object.values(CONST.DOCUMENT_OWNERSHIP_LEVELS),
|
|
||||||
initial: CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE
|
|
||||||
}),
|
|
||||||
players: new fields.TypedObjectField(
|
|
||||||
new fields.SchemaField({
|
|
||||||
type: new fields.NumberField({
|
|
||||||
required: true,
|
|
||||||
choices: Object.values(CONST.DOCUMENT_OWNERSHIP_LEVELS),
|
|
||||||
initial: CONST.DOCUMENT_OWNERSHIP_LEVELS.INHERIT
|
|
||||||
})
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
window: new fields.SchemaField({})
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
get playerOwnership() {
|
|
||||||
return Array.from(game.users).reduce((acc, user) => {
|
|
||||||
acc[user.id] = {
|
|
||||||
value: user.isGM
|
|
||||||
? CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER
|
|
||||||
: this.ownership.players[user.id] && this.ownership.players[user.id].type !== -1
|
|
||||||
? this.ownership.players[user.id].type
|
|
||||||
: this.ownership.default,
|
|
||||||
isGM: user.isGM
|
|
||||||
};
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Outdated and unused. Needed for migration. Remove in next minor version. (1.3) */
|
|
||||||
class DhOldCountdown extends foundry.abstract.DataModel {
|
|
||||||
static defineSchema() {
|
|
||||||
const fields = foundry.data.fields;
|
|
||||||
return {
|
|
||||||
name: new fields.StringField({
|
|
||||||
required: true,
|
|
||||||
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.name.label'
|
|
||||||
}),
|
|
||||||
img: new fields.FilePathField({
|
|
||||||
categories: ['IMAGE'],
|
|
||||||
base64: false,
|
|
||||||
initial: 'icons/magic/time/hourglass-yellow-green.webp'
|
|
||||||
}),
|
|
||||||
ownership: new fields.SchemaField({
|
|
||||||
default: new fields.NumberField({
|
|
||||||
required: true,
|
|
||||||
choices: Object.values(CONST.DOCUMENT_OWNERSHIP_LEVELS),
|
|
||||||
initial: CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE
|
|
||||||
}),
|
|
||||||
players: new fields.TypedObjectField(
|
|
||||||
new fields.SchemaField({
|
|
||||||
type: new fields.NumberField({
|
|
||||||
required: true,
|
|
||||||
choices: Object.values(CONST.DOCUMENT_OWNERSHIP_LEVELS),
|
|
||||||
initial: CONST.DOCUMENT_OWNERSHIP_LEVELS.INHERIT
|
|
||||||
})
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
progress: new fields.SchemaField({
|
|
||||||
current: new fields.NumberField({
|
|
||||||
required: true,
|
|
||||||
integer: true,
|
|
||||||
initial: 1,
|
|
||||||
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.progress.current.label'
|
|
||||||
}),
|
|
||||||
max: new fields.NumberField({
|
|
||||||
required: true,
|
|
||||||
integer: true,
|
|
||||||
initial: 1,
|
|
||||||
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.progress.max.label'
|
|
||||||
}),
|
|
||||||
type: new fields.SchemaField({
|
|
||||||
value: new fields.StringField({
|
|
||||||
required: true,
|
|
||||||
choices: CONFIG.DH.GENERAL.countdownProgressionTypes,
|
|
||||||
initial: CONFIG.DH.GENERAL.countdownProgressionTypes.custom.id,
|
|
||||||
label: 'DAGGERHEART.GENERAL.type'
|
|
||||||
}),
|
|
||||||
label: new fields.StringField({
|
|
||||||
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.progress.type.label.label'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
get playerOwnership() {
|
|
||||||
return Array.from(game.users).reduce((acc, user) => {
|
|
||||||
acc[user.id] = {
|
|
||||||
value: user.isGM
|
|
||||||
? CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER
|
|
||||||
: this.ownership.players[user.id] && this.ownership.players[user.id].type !== -1
|
|
||||||
? this.ownership.players[user.id].type
|
|
||||||
: this.ownership.default,
|
|
||||||
isGM: user.isGM
|
|
||||||
};
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DhCountdown extends foundry.abstract.DataModel {
|
export class DhCountdown extends foundry.abstract.DataModel {
|
||||||
static defineSchema() {
|
static defineSchema() {
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
export { default as AreasField } from './areasField.mjs';
|
||||||
export { default as CostField } from './costField.mjs';
|
export { default as CostField } from './costField.mjs';
|
||||||
export { default as CountdownField } from './countdownField.mjs';
|
export { default as CountdownField } from './countdownField.mjs';
|
||||||
export { default as UsesField } from './usesField.mjs';
|
export { default as UsesField } from './usesField.mjs';
|
||||||
|
|
|
||||||
40
module/data/fields/action/areasField.mjs
Normal file
40
module/data/fields/action/areasField.mjs
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
|
export default class AreasField extends fields.ArrayField {
|
||||||
|
/**
|
||||||
|
* Action Workflow order
|
||||||
|
*/
|
||||||
|
static order = 150;
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
constructor(options = {}, context = {}) {
|
||||||
|
const element = new fields.SchemaField({
|
||||||
|
name: new fields.StringField({
|
||||||
|
nullable: false,
|
||||||
|
initial: 'Area',
|
||||||
|
label: 'DAGGERHEART.GENERAL.name'
|
||||||
|
}),
|
||||||
|
type: new fields.StringField({
|
||||||
|
nullable: false,
|
||||||
|
choices: CONFIG.DH.ACTIONS.areaTypes,
|
||||||
|
initial: CONFIG.DH.ACTIONS.areaTypes.placed.id,
|
||||||
|
label: 'DAGGERHEART.GENERAL.type'
|
||||||
|
}),
|
||||||
|
shape: new fields.StringField({
|
||||||
|
nullable: false,
|
||||||
|
choices: CONFIG.DH.GENERAL.templateTypes,
|
||||||
|
initial: CONFIG.DH.GENERAL.templateTypes.circle.id,
|
||||||
|
label: 'DAGGERHEART.ACTIONS.Config.area.shape'
|
||||||
|
}),
|
||||||
|
/* Could be opened up to allow numbers to be input aswell. Probably best handled via an autocomplete in that case to allow the select options but also free text */
|
||||||
|
size: new fields.StringField({
|
||||||
|
nullable: false,
|
||||||
|
choices: CONFIG.DH.GENERAL.range,
|
||||||
|
initial: CONFIG.DH.GENERAL.range.veryClose.id,
|
||||||
|
label: 'DAGGERHEART.ACTIONS.Config.area.size'
|
||||||
|
}),
|
||||||
|
effects: new fields.ArrayField(new fields.DocumentIdField())
|
||||||
|
});
|
||||||
|
super(element, options, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -57,6 +57,10 @@ export default class CountdownField extends fields.ArrayField {
|
||||||
|
|
||||||
data.countdowns[foundry.utils.randomID()] = {
|
data.countdowns[foundry.utils.randomID()] = {
|
||||||
...countdown,
|
...countdown,
|
||||||
|
ownership: game.users.reduce((acc, curr) => {
|
||||||
|
if (!curr.isGM) acc[curr.id] = countdown.defaultOwnership;
|
||||||
|
return acc;
|
||||||
|
}, {}),
|
||||||
progress: {
|
progress: {
|
||||||
...countdown.progress,
|
...countdown.progress,
|
||||||
current: countdownStart,
|
current: countdownStart,
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,12 @@ export default class DamageField extends fields.SchemaField {
|
||||||
initial: false,
|
initial: false,
|
||||||
label: 'DAGGERHEART.ACTIONS.Settings.includeBase.label'
|
label: 'DAGGERHEART.ACTIONS.Settings.includeBase.label'
|
||||||
}),
|
}),
|
||||||
direct: new fields.BooleanField({ initial: false, label: 'DAGGERHEART.CONFIG.DamageType.direct.name' })
|
direct: new fields.BooleanField({ initial: false, label: 'DAGGERHEART.CONFIG.DamageType.direct.name' }),
|
||||||
|
groupAttack: new fields.StringField({
|
||||||
|
choices: CONFIG.DH.GENERAL.groupAttackRange,
|
||||||
|
blank: true,
|
||||||
|
label: 'DAGGERHEART.ACTIONS.Settings.groupAttack.label'
|
||||||
|
})
|
||||||
};
|
};
|
||||||
super(damageFields, options, context);
|
super(damageFields, options, context);
|
||||||
}
|
}
|
||||||
|
|
@ -224,6 +229,22 @@ export default class DamageField extends fields.SchemaField {
|
||||||
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.damageApply.players)
|
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.damageApply.players)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getGroupAttackTokens(actorId, range) {
|
||||||
|
if (!canvas.scene) return [];
|
||||||
|
|
||||||
|
const targets = Array.from(game.user.targets);
|
||||||
|
const rangeSettings = canvas.scene?.rangeSettings;
|
||||||
|
if (!rangeSettings) return [];
|
||||||
|
|
||||||
|
const maxDistance = rangeSettings[range];
|
||||||
|
return canvas.scene.tokens.filter(x => {
|
||||||
|
if (x.actor?.id !== actorId) return false;
|
||||||
|
if (targets.every(target => x.object.distanceTo(target) > maxDistance)) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DHActionDiceData extends foundry.abstract.DataModel {
|
export class DHActionDiceData extends foundry.abstract.DataModel {
|
||||||
|
|
@ -232,7 +253,7 @@ export class DHActionDiceData extends foundry.abstract.DataModel {
|
||||||
return {
|
return {
|
||||||
multiplier: new fields.StringField({
|
multiplier: new fields.StringField({
|
||||||
choices: CONFIG.DH.GENERAL.multiplierTypes,
|
choices: CONFIG.DH.GENERAL.multiplierTypes,
|
||||||
initial: 'prof',
|
initial: 'flat',
|
||||||
label: 'DAGGERHEART.ACTIONS.Config.damage.multiplier',
|
label: 'DAGGERHEART.ACTIONS.Config.damage.multiplier',
|
||||||
nullable: false,
|
nullable: false,
|
||||||
required: true
|
required: true
|
||||||
|
|
@ -244,7 +265,7 @@ export class DHActionDiceData extends foundry.abstract.DataModel {
|
||||||
}),
|
}),
|
||||||
dice: new fields.StringField({
|
dice: new fields.StringField({
|
||||||
choices: CONFIG.DH.GENERAL.diceTypes,
|
choices: CONFIG.DH.GENERAL.diceTypes,
|
||||||
initial: 'd6',
|
initial: CONFIG.DH.GENERAL.diceTypes.d6,
|
||||||
label: 'DAGGERHEART.GENERAL.Dice.single',
|
label: 'DAGGERHEART.GENERAL.Dice.single',
|
||||||
nullable: false,
|
nullable: false,
|
||||||
required: true
|
required: true
|
||||||
|
|
@ -299,7 +320,7 @@ export class DHDamageData extends DHResourceData {
|
||||||
required: true
|
required: true
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
label: 'Type'
|
label: game.i18n.localize('DAGGERHEART.GENERAL.type')
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { itemAbleRollParse } from '../../../helpers/utils.mjs';
|
||||||
import FormulaField from '../formulaField.mjs';
|
import FormulaField from '../formulaField.mjs';
|
||||||
|
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
|
|
@ -36,20 +37,23 @@ export default class DHSummonField extends fields.ArrayField {
|
||||||
const rolls = [];
|
const rolls = [];
|
||||||
const summonData = [];
|
const summonData = [];
|
||||||
for (const summon of this.summon) {
|
for (const summon of this.summon) {
|
||||||
let count = summon.count;
|
const roll = new Roll(itemAbleRollParse(summon.count, this.actor, this.item));
|
||||||
const roll = new Roll(summon.count);
|
await roll.evaluate();
|
||||||
if (!roll.isDeterministic) {
|
const count = roll.total;
|
||||||
await roll.evaluate();
|
if (!roll.isDeterministic && game.modules.get('dice-so-nice')?.active) rolls.push(roll);
|
||||||
if (game.modules.get('dice-so-nice')?.active) rolls.push(roll);
|
|
||||||
count = roll.total;
|
|
||||||
}
|
|
||||||
|
|
||||||
const actor = DHSummonField.getWorldActor(await foundry.utils.fromUuid(summon.actorUUID));
|
const actor = await DHSummonField.getWorldActor(await foundry.utils.fromUuid(summon.actorUUID));
|
||||||
/* Extending summon data in memory so it's available in actionField.toChat. Think it's harmless, but ugly. Could maybe find a better way. */
|
/* Extending summon data in memory so it's available in actionField.toChat. Think it's harmless, but ugly. Could maybe find a better way. */
|
||||||
summon.rolledCount = count;
|
|
||||||
summon.actor = actor.toObject();
|
summon.actor = actor.toObject();
|
||||||
|
|
||||||
summonData.push({ actor, count: count });
|
const countNumber = Number.parseInt(count);
|
||||||
|
for (let i = 0; i < countNumber; i++) {
|
||||||
|
const remaining = countNumber - i;
|
||||||
|
summonData.push({
|
||||||
|
actor,
|
||||||
|
tokenPreviewName: `${actor.prototypeToken.name}${remaining > 1 ? ` (${remaining}x)` : ''}`
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rolls.length) await Promise.all(rolls.map(roll => game.dice3d.showForRoll(roll, game.user, true)));
|
if (rolls.length) await Promise.all(rolls.map(roll => game.dice3d.showForRoll(roll, game.user, true)));
|
||||||
|
|
@ -58,32 +62,22 @@ export default class DHSummonField extends fields.ArrayField {
|
||||||
DHSummonField.handleSummon(summonData, this.actor);
|
DHSummonField.handleSummon(summonData, this.actor);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check for any available instances of the actor present in the world if we're missing artwork in the compendium */
|
/* Check for any available instances of the actor present in the world if we're missing artwork in the compendium. If none exists, create one. */
|
||||||
static getWorldActor(baseActor) {
|
static async getWorldActor(baseActor) {
|
||||||
const dataType = game.system.api.data.actors[`Dh${baseActor.type.capitalize()}`];
|
const dataType = game.system.api.data.actors[`Dh${baseActor.type.capitalize()}`];
|
||||||
if (baseActor.inCompendium && dataType && baseActor.img === dataType.DEFAULT_ICON) {
|
if (baseActor.inCompendium && dataType && baseActor.img === dataType.DEFAULT_ICON) {
|
||||||
const worldActorCopy = game.actors.find(x => x.name === baseActor.name);
|
const worldActorCopy = game.actors.find(x => x.name === baseActor.name);
|
||||||
return worldActorCopy ?? baseActor;
|
if (worldActorCopy) return worldActorCopy;
|
||||||
|
|
||||||
|
return await game.system.api.documents.DhpActor.create(baseActor.toObject());
|
||||||
}
|
}
|
||||||
|
|
||||||
return baseActor;
|
return baseActor;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async handleSummon(summonData, actionActor, summonIndex = 0) {
|
static async handleSummon(summonData, actionActor) {
|
||||||
const summon = summonData[summonIndex];
|
await CONFIG.ux.TokenManager.createTokensWithPreview(summonData, { elevation: actionActor.token?.elevation });
|
||||||
const result = await CONFIG.ux.TokenManager.createPreviewAsync(summon.actor, {
|
|
||||||
name: `${summon.actor.prototypeToken.name}${summon.count > 1 ? ` (${summon.count}x)` : ''}`
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!result) return actionActor.sheet?.maximize();
|
return actionActor.sheet?.maximize();
|
||||||
summon.actor = result.actor;
|
|
||||||
|
|
||||||
summon.count--;
|
|
||||||
if (summon.count <= 0) {
|
|
||||||
summonIndex++;
|
|
||||||
if (summonIndex === summonData.length) return actionActor.sheet?.maximize();
|
|
||||||
}
|
|
||||||
|
|
||||||
DHSummonField.handleSummon(summonData, actionActor, summonIndex);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -281,8 +281,14 @@ export function ActionMixin(Base) {
|
||||||
name: this.name,
|
name: this.name,
|
||||||
img: this.baseAction ? this.parent.parent.img : this.img,
|
img: this.baseAction ? this.parent.parent.img : this.img,
|
||||||
tags: this.tags ? this.tags : ['Spell', 'Arcana', 'Lv 10'],
|
tags: this.tags ? this.tags : ['Spell', 'Arcana', 'Lv 10'],
|
||||||
|
areas: this.areas,
|
||||||
summon: this.summon
|
summon: this.summon
|
||||||
},
|
},
|
||||||
|
source: {
|
||||||
|
actor: this.actor.uuid,
|
||||||
|
item: this.item.id,
|
||||||
|
action: this.id
|
||||||
|
},
|
||||||
itemOrigin: this.item,
|
itemOrigin: this.item,
|
||||||
description: this.description || (this.item instanceof Item ? this.item.system.description : '')
|
description: this.description || (this.item instanceof Item ? this.item.system.description : '')
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -89,13 +89,13 @@ class ResourcesField extends fields.TypedObjectField {
|
||||||
*/
|
*/
|
||||||
_getField(path) {
|
_getField(path) {
|
||||||
if (path.length === 0) return this;
|
if (path.length === 0) return this;
|
||||||
const first = path.shift();
|
const name = path.pop();
|
||||||
if (first === this.element.name) return this.element_getField(path);
|
if (name === this.element.name) return this.element_getField(path);
|
||||||
|
|
||||||
const resources = CONFIG.DH.RESOURCE[this.actorType].all;
|
const resources = CONFIG.DH.RESOURCE[this.actorType].all;
|
||||||
if (first in resources) {
|
if (name in resources) {
|
||||||
const field = this.element._getField(path);
|
const field = this.element._getField(path);
|
||||||
field.label = resources[first].label;
|
field.label = resources[name].label;
|
||||||
return field;
|
return field;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -103,4 +103,15 @@ class ResourcesField extends fields.TypedObjectField {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { attributeField, ResourcesField, stressDamageReductionRule, bonusField };
|
class GoldField extends fields.SchemaField {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
coins: new fields.NumberField({ initial: 0, integer: true }),
|
||||||
|
handfuls: new fields.NumberField({ initial: 1, integer: true }),
|
||||||
|
bags: new fields.NumberField({ initial: 0, integer: true }),
|
||||||
|
chests: new fields.NumberField({ initial: 0, integer: true })
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { attributeField, ResourcesField, GoldField, stressDamageReductionRule, bonusField };
|
||||||
|
|
|
||||||
40
module/data/groupRollData.mjs
Normal file
40
module/data/groupRollData.mjs
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
export default class GroupRollData extends foundry.abstract.DataModel {
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
|
return {
|
||||||
|
leader: new fields.EmbeddedDataField(CharacterData, { nullable: true, initial: null }),
|
||||||
|
aidingCharacters: new fields.TypedObjectField(new fields.EmbeddedDataField(CharacterData))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
get participants() {
|
||||||
|
return {
|
||||||
|
...(this.leader ? { [this.leader.id]: this.leader } : {}),
|
||||||
|
...this.aidingCharacters
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CharacterData extends foundry.abstract.DataModel {
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: new fields.StringField({ required: true }),
|
||||||
|
name: new fields.StringField({ required: true }),
|
||||||
|
img: new fields.StringField({ required: true }),
|
||||||
|
rollChoice: new fields.StringField({
|
||||||
|
choices: CONFIG.DH.ACTOR.abilities,
|
||||||
|
initial: CONFIG.DH.ACTOR.abilities.agility.id
|
||||||
|
}),
|
||||||
|
rollData: new fields.JSONField({ nullable: true, initial: null }),
|
||||||
|
selected: new fields.BooleanField({ initial: false }),
|
||||||
|
successfull: new fields.BooleanField({ nullable: true, initial: null })
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
get roll() {
|
||||||
|
return this.rollData ? CONFIG.Dice.daggerheart.DualityRoll.fromData(this.rollData) : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
* @property {string} label - A localizable label used on application.
|
* @property {string} label - A localizable label used on application.
|
||||||
* @property {string} type - The system type that this data model represents.
|
* @property {string} type - The system type that this data model represents.
|
||||||
* @property {boolean} hasDescription - Indicates whether items of this type have description field
|
* @property {boolean} hasDescription - Indicates whether items of this type have description field
|
||||||
* @property {boolean} isQuantifiable - Indicates whether items of this type have quantity field
|
|
||||||
* @property {boolean} isInventoryItem- Indicates whether items of this type is a Inventory Item
|
* @property {boolean} isInventoryItem- Indicates whether items of this type is a Inventory Item
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
@ -24,7 +23,6 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
||||||
type: 'base',
|
type: 'base',
|
||||||
hasDescription: false,
|
hasDescription: false,
|
||||||
hasResource: false,
|
hasResource: false,
|
||||||
isQuantifiable: false,
|
|
||||||
isInventoryItem: false,
|
isInventoryItem: false,
|
||||||
hasActions: false,
|
hasActions: false,
|
||||||
hasAttribution: true
|
hasAttribution: true
|
||||||
|
|
@ -83,7 +81,7 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.metadata.isQuantifiable)
|
if (this.metadata.isInventoryItem)
|
||||||
schema.quantity = new fields.NumberField({ integer: true, initial: 1, min: 0, required: true });
|
schema.quantity = new fields.NumberField({ integer: true, initial: 1, min: 0, required: true });
|
||||||
|
|
||||||
if (this.metadata.hasActions) schema.actions = new ActionsField();
|
if (this.metadata.hasActions) schema.actions = new ActionsField();
|
||||||
|
|
@ -110,6 +108,10 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
get actionsList() {
|
get actionsList() {
|
||||||
|
// No actions on non-characters
|
||||||
|
if (this.metadata.isInventoryItem && this.actor && this.actor.type !== 'character') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
return this.actions;
|
return this.actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ export default class DHConsumable extends BaseDataItem {
|
||||||
label: 'TYPES.Item.consumable',
|
label: 'TYPES.Item.consumable',
|
||||||
type: 'consumable',
|
type: 'consumable',
|
||||||
hasDescription: true,
|
hasDescription: true,
|
||||||
isQuantifiable: true,
|
|
||||||
isInventoryItem: true,
|
isInventoryItem: true,
|
||||||
hasActions: true
|
hasActions: true
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ export default class DHLoot extends BaseDataItem {
|
||||||
label: 'TYPES.Item.loot',
|
label: 'TYPES.Item.loot',
|
||||||
type: 'loot',
|
type: 'loot',
|
||||||
hasDescription: true,
|
hasDescription: true,
|
||||||
isQuantifiable: true,
|
|
||||||
isInventoryItem: true,
|
isInventoryItem: true,
|
||||||
hasActions: true
|
hasActions: true
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,9 @@ export default class DHWeapon extends AttachableItem {
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
get actionsList() {
|
get actionsList() {
|
||||||
return [this.attack, ...this.actions];
|
// No actions on non-characters
|
||||||
|
if (this.actor && this.actor.type !== 'character') return [];
|
||||||
|
return [this.attack, ...super.actionsList];
|
||||||
}
|
}
|
||||||
|
|
||||||
get customActions() {
|
get customActions() {
|
||||||
|
|
|
||||||
1
module/data/regionBehavior/_module.mjs
Normal file
1
module/data/regionBehavior/_module.mjs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export { default as applyActiveEffect } from './applyActiveEffect.mjs';
|
||||||
40
module/data/regionBehavior/applyActiveEffect.mjs
Normal file
40
module/data/regionBehavior/applyActiveEffect.mjs
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
export default class DhApplyActiveEffect extends CONFIG.RegionBehavior.dataModels.applyActiveEffect {
|
||||||
|
static async #getApplicableEffects(token) {
|
||||||
|
const effects = await Promise.all(this.effects.map(foundry.utils.fromUuid));
|
||||||
|
return effects.filter(
|
||||||
|
effect => !effect.system.targetDispositions.size || effect.system.targetDispositions.has(token.disposition)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onTokenEnter(event) {
|
||||||
|
if (!event.user.isSelf) return;
|
||||||
|
const { token, movement } = event.data;
|
||||||
|
const actor = token.actor;
|
||||||
|
if (!actor) return;
|
||||||
|
const resumeMovement = movement ? token.pauseMovement() : undefined;
|
||||||
|
const effects = await DhApplyActiveEffect.#getApplicableEffects.bind(this)(event.data.token);
|
||||||
|
const toCreate = [];
|
||||||
|
for (const effect of effects) {
|
||||||
|
const data = effect.toObject();
|
||||||
|
delete data._id;
|
||||||
|
if (effect.compendium) {
|
||||||
|
data._stats.duplicateSource = null;
|
||||||
|
data._stats.compendiumSource = effect.uuid;
|
||||||
|
} else {
|
||||||
|
data._stats.duplicateSource = effect.uuid;
|
||||||
|
data._stats.compendiumSource = null;
|
||||||
|
}
|
||||||
|
data._stats.exportSource = null;
|
||||||
|
data.origin = this.parent.uuid;
|
||||||
|
toCreate.push(data);
|
||||||
|
}
|
||||||
|
if (toCreate.length) await actor.createEmbeddedDocuments('ActiveEffect', toCreate);
|
||||||
|
await resumeMovement?.();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static events = {
|
||||||
|
...CONFIG.RegionBehavior.dataModels.applyActiveEffect.events,
|
||||||
|
[CONST.REGION_EVENTS.TOKEN_ENTER]: this.#onTokenEnter
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,9 @@ export default class DhAppearance extends foundry.abstract.DataModel {
|
||||||
initial: null,
|
initial: null,
|
||||||
blank: true,
|
blank: true,
|
||||||
choices: CONFIG.DH.GENERAL.diceSoNiceSFXClasses
|
choices: CONFIG.DH.GENERAL.diceSoNiceSFXClasses
|
||||||
|
}),
|
||||||
|
options: new foundry.data.fields.SchemaField({
|
||||||
|
muteSound: new foundry.data.fields.BooleanField()
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ export class MemberData extends foundry.abstract.DataModel {
|
||||||
required: true,
|
required: true,
|
||||||
choices: CONFIG.DH.GENERAL.tagTeamRollTypes,
|
choices: CONFIG.DH.GENERAL.tagTeamRollTypes,
|
||||||
initial: CONFIG.DH.GENERAL.tagTeamRollTypes.trait.id,
|
initial: CONFIG.DH.GENERAL.tagTeamRollTypes.trait.id,
|
||||||
label: 'Roll Type'
|
label: game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.rollType')
|
||||||
}),
|
}),
|
||||||
rollChoice: new fields.StringField({ nullable: true, initial: null }),
|
rollChoice: new fields.StringField({ nullable: true, initial: null }),
|
||||||
rollData: new fields.JSONField({ nullable: true, initial: null }),
|
rollData: new fields.JSONField({ nullable: true, initial: null }),
|
||||||
|
|
|
||||||
|
|
@ -144,6 +144,7 @@ export default class DamageRoll extends DHRoll {
|
||||||
constructFormula(config) {
|
constructFormula(config) {
|
||||||
this.options.isCritical = config.isCritical;
|
this.options.isCritical = config.isCritical;
|
||||||
for (const [index, part] of this.options.roll.entries()) {
|
for (const [index, part] of this.options.roll.entries()) {
|
||||||
|
const isHitpointPart = part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id;
|
||||||
part.roll = new Roll(Roll.replaceFormulaData(part.formula, config.data));
|
part.roll = new Roll(Roll.replaceFormulaData(part.formula, config.data));
|
||||||
part.roll.terms = Roll.parse(part.roll.formula, config.data);
|
part.roll.terms = Roll.parse(part.roll.formula, config.data);
|
||||||
if (part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) {
|
if (part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) {
|
||||||
|
|
@ -169,7 +170,16 @@ export default class DamageRoll extends DHRoll {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.isCritical && part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) {
|
if (config.damageOptions.groupAttack?.numAttackers > 1 && isHitpointPart) {
|
||||||
|
const damageTypes = [foundry.dice.terms.Die, foundry.dice.terms.NumericTerm];
|
||||||
|
for (const term of part.roll.terms) {
|
||||||
|
if (damageTypes.some(type => term instanceof type)) {
|
||||||
|
term.number *= config.damageOptions.groupAttack.numAttackers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.isCritical && isHitpointPart) {
|
||||||
const total = part.roll.dice.reduce((acc, term) => acc + term._faces * term._number, 0);
|
const total = part.roll.dice.reduce((acc, term) => acc + term._faces * term._number, 0);
|
||||||
if (total > 0) {
|
if (total > 0) {
|
||||||
part.roll.terms.push(...this.formatModifier(total));
|
part.roll.terms.push(...this.formatModifier(total));
|
||||||
|
|
|
||||||
|
|
@ -145,6 +145,7 @@ export default class DHRoll extends Roll {
|
||||||
roll: this,
|
roll: this,
|
||||||
parent: chatData.parent,
|
parent: chatData.parent,
|
||||||
targetMode: chatData.targetMode,
|
targetMode: chatData.targetMode,
|
||||||
|
areas: chatData.action?.areas,
|
||||||
metagamingSettings
|
metagamingSettings
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -246,7 +247,7 @@ export default class DHRoll extends Roll {
|
||||||
return (this._formula = this.constructor.getFormula(this.terms));
|
return (this._formula = this.constructor.getFormula(this.terms));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate total modifiers of any rolls, including non-dh rolls.
|
* Calculate total modifiers of any rolls, including non-dh rolls.
|
||||||
* This exists because damage rolls still may receive base roll classes
|
* This exists because damage rolls still may receive base roll classes
|
||||||
*/
|
*/
|
||||||
|
|
@ -256,7 +257,7 @@ export default class DHRoll extends Roll {
|
||||||
if (!roll.terms[i].isDeterministic) continue;
|
if (!roll.terms[i].isDeterministic) continue;
|
||||||
const termTotal = roll.terms[i].total;
|
const termTotal = roll.terms[i].total;
|
||||||
if (typeof termTotal === 'number') {
|
if (typeof termTotal === 'number') {
|
||||||
const multiplier = roll.terms[i - 1]?.operator === " - " ? -1 : 1;
|
const multiplier = roll.terms[i - 1]?.operator === ' - ' ? -1 : 1;
|
||||||
modifierTotal += multiplier * termTotal;
|
modifierTotal += multiplier * termTotal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -272,7 +273,7 @@ export default class DHRoll extends Roll {
|
||||||
const changeKeys = this.getActionChangeKeys();
|
const changeKeys = this.getActionChangeKeys();
|
||||||
return (
|
return (
|
||||||
this.options.effects?.reduce((acc, effect) => {
|
this.options.effects?.reduce((acc, effect) => {
|
||||||
if (effect.system.changes.some(x => changeKeys.some(key => x.key.includes(key)))) {
|
if (effect.system.changes.some(x => changeKeys.some(key => x.key?.includes(key)))) {
|
||||||
acc[effect.id] = {
|
acc[effect.id] = {
|
||||||
id: effect.id,
|
id: effect.id,
|
||||||
name: effect.name,
|
name: effect.name,
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
import DualityDie from './dualityDie.mjs';
|
import DualityDie from './dualityDie.mjs';
|
||||||
|
import HopeDie from './hopeDie.mjs';
|
||||||
|
import FearDie from './fearDie.mjs';
|
||||||
import AdvantageDie from './advantageDie.mjs';
|
import AdvantageDie from './advantageDie.mjs';
|
||||||
import DisadvantageDie from './disadvantageDie.mjs';
|
import DisadvantageDie from './disadvantageDie.mjs';
|
||||||
|
|
||||||
export const diceTypes = {
|
export const diceTypes = {
|
||||||
DualityDie,
|
DualityDie,
|
||||||
|
HopeDie,
|
||||||
|
FearDie,
|
||||||
AdvantageDie,
|
AdvantageDie,
|
||||||
DisadvantageDie
|
DisadvantageDie
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -43,9 +43,10 @@ export default class DualityDie extends foundry.dice.terms.Die {
|
||||||
options: { appearance: {} }
|
options: { appearance: {} }
|
||||||
};
|
};
|
||||||
|
|
||||||
const preset = await getDiceSoNicePreset(diceSoNice[key], faces);
|
const diceAppearance = await this.getDiceSoNiceAppearance(options.liveRoll.roll);
|
||||||
diceSoNiceRoll.dice[0].options.appearance = preset.appearance;
|
diceSoNiceRoll.dice[0].options.appearance = diceAppearance.appearance;
|
||||||
diceSoNiceRoll.dice[0].options.modelFile = preset.modelFile;
|
diceSoNiceRoll.dice[0].options.modelFile = diceAppearance.modelFile;
|
||||||
|
diceSoNiceRoll.dice[0].results = diceSoNiceRoll.dice[0].results.filter(x => x.active);
|
||||||
|
|
||||||
await game.dice3d.showForRoll(diceSoNiceRoll, game.user, true);
|
await game.dice3d.showForRoll(diceSoNiceRoll, game.user, true);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -59,4 +60,11 @@ export default class DualityDie extends foundry.dice.terms.Die {
|
||||||
this.#updateResources(oldDuality, newDuality, options.liveRoll.actor);
|
this.#updateResources(oldDuality, newDuality, options.liveRoll.actor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overridden by extending classes HopeDie and FearDie
|
||||||
|
*/
|
||||||
|
async getDiceSoNiceAppearance() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
9
module/dice/die/fearDie.mjs
Normal file
9
module/dice/die/fearDie.mjs
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { getDiceSoNicePresets } from '../../config/generalConfig.mjs';
|
||||||
|
import DualityDie from './dualityDie.mjs';
|
||||||
|
|
||||||
|
export default class FearDie extends DualityDie {
|
||||||
|
async getDiceSoNiceAppearance(roll) {
|
||||||
|
const { fear } = await getDiceSoNicePresets(roll, this.denomination, this.denomination);
|
||||||
|
return fear;
|
||||||
|
}
|
||||||
|
}
|
||||||
9
module/dice/die/hopeDie.mjs
Normal file
9
module/dice/die/hopeDie.mjs
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { getDiceSoNicePresets } from '../../config/generalConfig.mjs';
|
||||||
|
import DualityDie from './dualityDie.mjs';
|
||||||
|
|
||||||
|
export default class HopeDie extends DualityDie {
|
||||||
|
async getDiceSoNiceAppearance(roll) {
|
||||||
|
const { hope } = await getDiceSoNicePresets(roll, this.denomination, this.denomination);
|
||||||
|
return hope;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,6 @@ import D20Roll from './d20Roll.mjs';
|
||||||
import { parseRallyDice, setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs';
|
import { parseRallyDice, setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs';
|
||||||
|
|
||||||
export default class DualityRoll extends D20Roll {
|
export default class DualityRoll extends D20Roll {
|
||||||
_advantageFaces = 6;
|
|
||||||
_advantageNumber = 1;
|
_advantageNumber = 1;
|
||||||
_rallyIndex;
|
_rallyIndex;
|
||||||
|
|
||||||
|
|
@ -11,6 +10,11 @@ export default class DualityRoll extends D20Roll {
|
||||||
super(formula, data, options);
|
super(formula, data, options);
|
||||||
this.rallyChoices = this.setRallyChoices();
|
this.rallyChoices = this.setRallyChoices();
|
||||||
this.guaranteedCritical = options.guaranteedCritical;
|
this.guaranteedCritical = options.guaranteedCritical;
|
||||||
|
|
||||||
|
const advantageFaces = data.rules?.roll?.defaultAdvantageDice
|
||||||
|
? Number.parseInt(data.rules.roll.defaultAdvantageDice)
|
||||||
|
: 6;
|
||||||
|
this.advantageFaces = Number.isNaN(advantageFaces) ? 6 : advantageFaces;
|
||||||
}
|
}
|
||||||
|
|
||||||
static messageType = 'dualityRoll';
|
static messageType = 'dualityRoll';
|
||||||
|
|
@ -24,7 +28,7 @@ export default class DualityRoll extends D20Roll {
|
||||||
}
|
}
|
||||||
|
|
||||||
get dHope() {
|
get dHope() {
|
||||||
if (!(this.dice[0] instanceof game.system.api.dice.diceTypes.DualityDie)) this.createBaseDice();
|
if (!(this.dice[0] instanceof game.system.api.dice.diceTypes.HopeDie)) this.createBaseDice();
|
||||||
return this.dice[0];
|
return this.dice[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -34,7 +38,7 @@ export default class DualityRoll extends D20Roll {
|
||||||
}
|
}
|
||||||
|
|
||||||
get dFear() {
|
get dFear() {
|
||||||
if (!(this.dice[1] instanceof game.system.api.dice.diceTypes.DualityDie)) this.createBaseDice();
|
if (!(this.dice[1] instanceof game.system.api.dice.diceTypes.FearDie)) this.createBaseDice();
|
||||||
return this.dice[1];
|
return this.dice[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -51,14 +55,6 @@ export default class DualityRoll extends D20Roll {
|
||||||
return this.dice[2] instanceof game.system.api.dice.diceTypes.DisadvantageDie ? this.dice[2] : null;
|
return this.dice[2] instanceof game.system.api.dice.diceTypes.DisadvantageDie ? this.dice[2] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
get advantageFaces() {
|
|
||||||
return this._advantageFaces;
|
|
||||||
}
|
|
||||||
|
|
||||||
set advantageFaces(faces) {
|
|
||||||
this._advantageFaces = this.getFaces(faces);
|
|
||||||
}
|
|
||||||
|
|
||||||
get advantageNumber() {
|
get advantageNumber() {
|
||||||
return this._advantageNumber;
|
return this._advantageNumber;
|
||||||
}
|
}
|
||||||
|
|
@ -68,8 +64,8 @@ export default class DualityRoll extends D20Roll {
|
||||||
}
|
}
|
||||||
|
|
||||||
get extraDice() {
|
get extraDice() {
|
||||||
const { DualityDie, AdvantageDie, DisadvantageDie } = game.system.api.dice.diceTypes;
|
const { HopeDie, FearDie, AdvantageDie, DisadvantageDie } = game.system.api.dice.diceTypes;
|
||||||
return this.dice.filter(x => ![DualityDie, AdvantageDie, DisadvantageDie].some(die => x instanceof die));
|
return this.dice.filter(x => ![HopeDie, FearDie, AdvantageDie, DisadvantageDie].some(die => x instanceof die));
|
||||||
}
|
}
|
||||||
|
|
||||||
setRallyChoices() {
|
setRallyChoices() {
|
||||||
|
|
@ -125,8 +121,8 @@ export default class DualityRoll extends D20Roll {
|
||||||
|
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
static fromData(data) {
|
static fromData(data) {
|
||||||
data.terms[0].class = 'DualityDie';
|
data.terms[0].class = 'HopeDie';
|
||||||
data.terms[2].class = 'DualityDie';
|
data.terms[2].class = 'FearDie';
|
||||||
if (data.options.roll.advantage?.type && data.terms[4]?.faces) {
|
if (data.options.roll.advantage?.type && data.terms[4]?.faces) {
|
||||||
data.terms[4].class = data.options.roll.advantage.type === 1 ? 'AdvantageDie' : 'DisadvantageDie';
|
data.terms[4].class = data.options.roll.advantage.type === 1 ? 'AdvantageDie' : 'DisadvantageDie';
|
||||||
}
|
}
|
||||||
|
|
@ -135,18 +131,18 @@ export default class DualityRoll extends D20Roll {
|
||||||
|
|
||||||
createBaseDice() {
|
createBaseDice() {
|
||||||
if (
|
if (
|
||||||
this.dice[0] instanceof game.system.api.dice.diceTypes.DualityDie &&
|
this.dice[0] instanceof game.system.api.dice.diceTypes.HopeDie &&
|
||||||
this.dice[1] instanceof game.system.api.dice.diceTypes.DualityDie
|
this.dice[1] instanceof game.system.api.dice.diceTypes.FearDie
|
||||||
) {
|
) {
|
||||||
this.terms = [this.terms[0], this.terms[1], this.terms[2]];
|
this.terms = [this.terms[0], this.terms[1], this.terms[2]];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.terms[0] = new game.system.api.dice.diceTypes.DualityDie({
|
this.terms[0] = new game.system.api.dice.diceTypes.HopeDie({
|
||||||
faces: this.data.rules.dualityRoll?.defaultHopeDice ?? 12
|
faces: this.data.rules.dualityRoll?.defaultHopeDice ?? 12
|
||||||
});
|
});
|
||||||
this.terms[1] = new foundry.dice.terms.OperatorTerm({ operator: '+' });
|
this.terms[1] = new foundry.dice.terms.OperatorTerm({ operator: '+' });
|
||||||
this.terms[2] = new game.system.api.dice.diceTypes.DualityDie({
|
this.terms[2] = new game.system.api.dice.diceTypes.FearDie({
|
||||||
faces: this.data.rules.dualityRoll?.defaultFearDice ?? 12
|
faces: this.data.rules.dualityRoll?.defaultFearDice ?? 12
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -200,7 +200,6 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
|
||||||
static effectSafeEval(expression) {
|
static effectSafeEval(expression) {
|
||||||
let result;
|
let result;
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line no-new-func
|
|
||||||
const evl = new Function('sandbox', `with (sandbox) { return ${expression}}`);
|
const evl = new Function('sandbox', `with (sandbox) { return ${expression}}`);
|
||||||
result = evl(Roll.MATH_PROXY);
|
result = evl(Roll.MATH_PROXY);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
||||||
|
|
@ -113,11 +113,13 @@ export default class DhpActor extends Actor {
|
||||||
_onUpdate(changes, options, userId) {
|
_onUpdate(changes, options, userId) {
|
||||||
super._onUpdate(changes, options, userId);
|
super._onUpdate(changes, options, userId);
|
||||||
for (const party of this.parties) {
|
for (const party of this.parties) {
|
||||||
party.render();
|
party.render({ parts: ['partyMembers'] });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _preDelete() {
|
async _preDelete(options, user) {
|
||||||
|
if ((await super._preDelete(options, user)) === false) return false;
|
||||||
|
|
||||||
if (this.prototypeToken.actorLink) {
|
if (this.prototypeToken.actorLink) {
|
||||||
game.system.registeredTriggers.unregisterItemTriggers(this.items);
|
game.system.registeredTriggers.unregisterItemTriggers(this.items);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -130,7 +132,7 @@ export default class DhpActor extends Actor {
|
||||||
_onDelete(options, userId) {
|
_onDelete(options, userId) {
|
||||||
super._onDelete(options, userId);
|
super._onDelete(options, userId);
|
||||||
for (const party of this.parties) {
|
for (const party of this.parties) {
|
||||||
party.render();
|
party.render({ parts: ['partyMembers'] });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -600,6 +602,7 @@ export default class DhpActor extends Actor {
|
||||||
rollData.system = this.system.getRollData();
|
rollData.system = this.system.getRollData();
|
||||||
rollData.prof = this.system.proficiency ?? 1;
|
rollData.prof = this.system.proficiency ?? 1;
|
||||||
rollData.cast = this.system.spellcastModifier ?? 1;
|
rollData.cast = this.system.spellcastModifier ?? 1;
|
||||||
|
|
||||||
return rollData;
|
return rollData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -137,6 +137,10 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
||||||
element.addEventListener('click', this.onApplyEffect.bind(this))
|
element.addEventListener('click', this.onApplyEffect.bind(this))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
for (const element of html.querySelectorAll('.action-areas')) {
|
||||||
|
element.addEventListener('click', this.onCreateAreas.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
html.querySelectorAll('.roll-target').forEach(element => {
|
html.querySelectorAll('.roll-target').forEach(element => {
|
||||||
element.addEventListener('mouseenter', this.hoverTarget);
|
element.addEventListener('mouseenter', this.hoverTarget);
|
||||||
element.addEventListener('mouseleave', this.unhoverTarget);
|
element.addEventListener('mouseleave', this.unhoverTarget);
|
||||||
|
|
@ -178,8 +182,8 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
||||||
const pendingingSaves = targets.filter(t => t.saved.success === null);
|
const pendingingSaves = targets.filter(t => t.saved.success === null);
|
||||||
if (pendingingSaves.length) {
|
if (pendingingSaves.length) {
|
||||||
const confirm = await foundry.applications.api.DialogV2.confirm({
|
const confirm = await foundry.applications.api.DialogV2.confirm({
|
||||||
window: { title: 'Pending Reaction Rolls found' },
|
window: { title: game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.title') },
|
||||||
content: `<p>Some Tokens still need to roll their Reaction Roll.</p><p>Are you sure you want to continue ?</p><p><i>Undone reaction rolls will be considered as failed</i></p>`
|
content: `<p>${game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.unfinishedRolls')}</p><p>${game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.confirmation')}</p><p><i>${game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.warning')}</i></p>`
|
||||||
});
|
});
|
||||||
if (!confirm) return;
|
if (!confirm) return;
|
||||||
}
|
}
|
||||||
|
|
@ -249,6 +253,54 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
||||||
this.system.action?.workflow.get('effects')?.execute(config, targets, true);
|
this.system.action?.workflow.get('effects')?.execute(config, targets, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async onCreateAreas(event) {
|
||||||
|
const createArea = async selectedArea => {
|
||||||
|
const effects = selectedArea.effects.map(effect => this.system.action.item.effects.get(effect).uuid);
|
||||||
|
const { shape: type, size: range } = selectedArea;
|
||||||
|
const shapeData = CONFIG.Canvas.layers.regions.layerClass.getTemplateShape({ type, range });
|
||||||
|
|
||||||
|
await canvas.regions.placeRegion(
|
||||||
|
{
|
||||||
|
name: selectedArea.name,
|
||||||
|
shapes: [shapeData],
|
||||||
|
restriction: { enabled: false, type: 'move', priority: 0 },
|
||||||
|
behaviors: [
|
||||||
|
{
|
||||||
|
name: game.i18n.localize('TYPES.RegionBehavior.applyActiveEffect'),
|
||||||
|
type: 'applyActiveEffect',
|
||||||
|
system: {
|
||||||
|
effects: effects
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
displayMeasurements: true,
|
||||||
|
locked: false,
|
||||||
|
ownership: { default: CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE },
|
||||||
|
visibility: CONST.REGION_VISIBILITY.ALWAYS
|
||||||
|
},
|
||||||
|
{ create: true }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.system.action.areas.length === 1) createArea(this.system.action.areas[0]);
|
||||||
|
else if (this.system.action.areas.length > 1) {
|
||||||
|
new foundry.applications.ux.ContextMenu.implementation(
|
||||||
|
event.target,
|
||||||
|
'.action-areas',
|
||||||
|
this.system.action.areas.map(area => ({
|
||||||
|
label: area.name,
|
||||||
|
onClick: () => createArea(area)
|
||||||
|
})),
|
||||||
|
{
|
||||||
|
jQuery: false,
|
||||||
|
fixed: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
CONFIG.ux.ContextMenu.triggerContextMenu(event, '.action-areas');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
filterPermTargets(targets) {
|
filterPermTargets(targets) {
|
||||||
return targets.filter(t => fromUuidSync(t.actorId)?.canUserModify(game.user, 'update'));
|
return targets.filter(t => fromUuidSync(t.actorId)?.canUserModify(game.user, 'update'));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,11 @@
|
||||||
export default class DhActorCollection extends foundry.documents.collections.Actors {
|
export default class DhActorCollection extends foundry.documents.collections.Actors {
|
||||||
|
/** @returns the active party */
|
||||||
|
get party() {
|
||||||
|
const id = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.ActiveParty);
|
||||||
|
const actor = game.actors.get(id);
|
||||||
|
return actor?.type === 'party' ? actor : null;
|
||||||
|
}
|
||||||
|
|
||||||
/** Ensure companions are initialized after all other subtypes. */
|
/** Ensure companions are initialized after all other subtypes. */
|
||||||
_initialize() {
|
_initialize() {
|
||||||
super._initialize();
|
super._initialize();
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,13 @@ export default class DHItem extends foundry.documents.Item {
|
||||||
static async createDocuments(sources, operation) {
|
static async createDocuments(sources, operation) {
|
||||||
// Ensure that items being created are valid to the actor its being added to
|
// Ensure that items being created are valid to the actor its being added to
|
||||||
const actor = operation.parent;
|
const actor = operation.parent;
|
||||||
sources = actor?.system?.isItemValid ? sources.filter(s => actor.system.isItemValid(s)) : sources;
|
const filtered = actor ? sources.filter(s => actor.system.isItemValid(s)) : sources;
|
||||||
return super.createDocuments(sources, operation);
|
if (actor && filtered.length === 0 && sources.length > 0) {
|
||||||
|
const itemType = _loc(`TYPES.Item.${sources[0].type}`);
|
||||||
|
const actorType = _loc(`TYPES.Actor.${actor.type}`);
|
||||||
|
ui.notifications.error('DAGGERHEART.ACTORS.Base.CannotAddType', { format: { itemType, actorType } });
|
||||||
|
}
|
||||||
|
return super.createDocuments(filtered, operation);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
@ -71,6 +76,13 @@ export default class DHItem extends foundry.documents.Item {
|
||||||
return this.system.metadata.isInventoryItem ?? false;
|
return this.system.metadata.isInventoryItem ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns true if the item can be used */
|
||||||
|
get usable() {
|
||||||
|
const actor = this.actor;
|
||||||
|
const actionsList = this.system.actionsList;
|
||||||
|
return this.isOwner && actor?.type === 'character' && (actionsList?.size || actionsList?.length);
|
||||||
|
}
|
||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
static async createDialog(data = {}, createOptions = {}, options = {}) {
|
static async createDialog(data = {}, createOptions = {}, options = {}) {
|
||||||
const { folders, types, template, context = {}, ...dialogOptions } = options;
|
const { folders, types, template, context = {}, ...dialogOptions } = options;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,16 @@
|
||||||
import DHToken from './token.mjs';
|
import DHToken from './token.mjs';
|
||||||
|
|
||||||
export default class DhScene extends Scene {
|
export default class DhScene extends Scene {
|
||||||
|
get rangeSettings() {
|
||||||
|
const { custom } = CONFIG.DH.GENERAL.sceneRangeMeasurementSetting;
|
||||||
|
const sceneMeasurements = this.flags.daggerheart?.rangeMeasurement;
|
||||||
|
const globalMeasurements = game.settings.get(
|
||||||
|
CONFIG.DH.id,
|
||||||
|
CONFIG.DH.SETTINGS.gameSettings.variantRules
|
||||||
|
).rangeMeasurement;
|
||||||
|
return sceneMeasurements?.setting === custom.id ? sceneMeasurements : globalMeasurements;
|
||||||
|
}
|
||||||
|
|
||||||
/** A map of `TokenDocument` IDs embedded in this scene long with new dimensions from actor size-category changes */
|
/** A map of `TokenDocument` IDs embedded in this scene long with new dimensions from actor size-category changes */
|
||||||
#sizeSyncBatch = new Map();
|
#sizeSyncBatch = new Map();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,104 +1,68 @@
|
||||||
/**
|
/**
|
||||||
* A singleton class that handles preview tokens.
|
* A singleton class that handles creating tokens.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default class DhTokenManager {
|
export default class DhTokenManager {
|
||||||
#activePreview;
|
|
||||||
#actor;
|
|
||||||
#resolve;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a template preview, deactivating any existing ones.
|
* Create a token previer
|
||||||
* @param {object} data
|
* @param {Actor} actor
|
||||||
|
* @param {object} tokenData
|
||||||
*/
|
*/
|
||||||
async createPreview(actor, tokenData) {
|
async createPreview(actor, tokenData) {
|
||||||
this.#actor = actor;
|
const tokenSizes = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes;
|
||||||
const token = await canvas.tokens._createPreview(
|
if (actor?.system.metadata.usesSize) {
|
||||||
{
|
const tokenSize = tokenSizes[actor.system.size];
|
||||||
...actor.prototypeToken,
|
if (tokenSize && actor.system.size !== CONFIG.DH.ACTOR.tokenSize.custom.id) {
|
||||||
displayName: 50,
|
tokenData.width = tokenSize;
|
||||||
...tokenData
|
tokenData.height = tokenSize;
|
||||||
},
|
}
|
||||||
{ renderSheet: false, actor }
|
}
|
||||||
|
|
||||||
|
return await canvas.tokens.placeTokens(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
...actor.prototypeToken.toObject(),
|
||||||
|
actorId: actor.id,
|
||||||
|
displayName: 50,
|
||||||
|
...tokenData
|
||||||
|
}
|
||||||
|
],
|
||||||
|
{ create: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
this.#activePreview = {
|
|
||||||
document: token.document,
|
|
||||||
object: token,
|
|
||||||
origin: { x: token.document.x, y: token.document.y }
|
|
||||||
};
|
|
||||||
|
|
||||||
this.#activePreview.events = {
|
|
||||||
contextmenu: this.#cancelTemplate.bind(this),
|
|
||||||
mousedown: this.#confirmTemplate.bind(this),
|
|
||||||
mousemove: this.#onDragMouseMove.bind(this)
|
|
||||||
};
|
|
||||||
|
|
||||||
canvas.stage.on('mousemove', this.#activePreview.events.mousemove);
|
|
||||||
canvas.stage.on('mousedown', this.#activePreview.events.mousedown);
|
|
||||||
canvas.app.view.addEventListener('contextmenu', this.#activePreview.events.contextmenu);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Currently intended for using as a preview of where to create a token. (note the flag) */
|
|
||||||
async createPreviewAsync(actor, tokenData = {}) {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
this.#resolve = resolve;
|
|
||||||
this.createPreview(actor, { ...tokenData, flags: { daggerheart: { createPlacement: true } } });
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the movement of the token preview on mousedrag.
|
* Creates new tokens on the canvas by placing previews.
|
||||||
* @param {mousemove Event} event
|
* @param {object} tokenData
|
||||||
|
* @param {object} options
|
||||||
*/
|
*/
|
||||||
#onDragMouseMove(event) {
|
async createTokensWithPreview(tokensData, { elevation } = {}) {
|
||||||
event.stopPropagation();
|
const scene = game.scenes.get(game.user.viewedScene);
|
||||||
const { moveTime, object } = this.#activePreview;
|
if (!scene) return;
|
||||||
const update = {};
|
|
||||||
|
|
||||||
const now = Date.now();
|
const level = scene.levels.get(game.user.viewedLevel);
|
||||||
if (now - (moveTime || 0) <= 16) return;
|
if (!level) return;
|
||||||
this.#activePreview.moveTime = now;
|
|
||||||
|
|
||||||
let cursor = event.getLocalPosition(canvas.templates);
|
const createElevation = elevation ?? level.elevation.bottom;
|
||||||
|
for (const tokenData of tokensData) {
|
||||||
|
const previewTokens = await this.createPreview(tokenData.actor, {
|
||||||
|
name: tokenData.tokenPreviewName,
|
||||||
|
level: game.user.viewedLevel,
|
||||||
|
elevation: createElevation,
|
||||||
|
flags: { daggerheart: { createPlacement: true } }
|
||||||
|
});
|
||||||
|
if (!previewTokens?.length) return null;
|
||||||
|
|
||||||
Object.assign(update, canvas.grid.getTopLeftPoint(cursor));
|
await canvas.scene.createEmbeddedDocuments(
|
||||||
|
'Token',
|
||||||
object.document.updateSource(update);
|
previewTokens.map(x => ({
|
||||||
object.renderFlags.set({ refresh: true });
|
...x.toObject(),
|
||||||
}
|
name: tokenData.actor.prototypeToken.name,
|
||||||
|
displayName: tokenData.actor.prototypeToken.displayName,
|
||||||
/**
|
flags: tokenData.actor.prototypeToken.flags
|
||||||
* Cancels the preview token on right-click.
|
})),
|
||||||
* @param {contextmenu Event} event
|
{ controlObject: true, parent: canvas.scene }
|
||||||
*/
|
);
|
||||||
#cancelTemplate(_event, resolved) {
|
}
|
||||||
const { mousemove, mousedown, contextmenu } = this.#activePreview.events;
|
|
||||||
this.#activePreview.object.destroy();
|
|
||||||
|
|
||||||
canvas.stage.off('mousemove', mousemove);
|
|
||||||
canvas.stage.off('mousedown', mousedown);
|
|
||||||
canvas.app.view.removeEventListener('contextmenu', contextmenu);
|
|
||||||
if (this.#resolve && !resolved) this.#resolve(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a real Actor and token at the preview location and cancels the preview.
|
|
||||||
* @param {click Event} event
|
|
||||||
*/
|
|
||||||
async #confirmTemplate(event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
this.#cancelTemplate(event, true);
|
|
||||||
|
|
||||||
const actor = this.#actor.inCompendium
|
|
||||||
? await game.system.api.documents.DhpActor.create(this.#actor.toObject())
|
|
||||||
: this.#actor;
|
|
||||||
const tokenData = await actor.getTokenDocument();
|
|
||||||
const result = await canvas.scene.createEmbeddedDocuments('Token', [
|
|
||||||
{ ...tokenData, x: this.#activePreview.document.x, y: this.#activePreview.document.y }
|
|
||||||
]);
|
|
||||||
|
|
||||||
this.#activePreview = undefined;
|
|
||||||
if (this.#resolve && result.length) this.#resolve(result[0]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ export default function DhTemplateEnricher(match, _options) {
|
||||||
)?.id
|
)?.id
|
||||||
: params.range;
|
: params.range;
|
||||||
|
|
||||||
if (!Object.values(CONFIG.DH.GENERAL.templateTypes).find(x => x === type) || !range) return match[0];
|
if (!CONFIG.DH.GENERAL.templateTypes[type] || !range) return match[0];
|
||||||
|
|
||||||
const label = game.i18n.localize(`DAGGERHEART.CONFIG.TemplateTypes.${type}`);
|
const label = game.i18n.localize(`DAGGERHEART.CONFIG.TemplateTypes.${type}`);
|
||||||
const rangeDisplay = Number.isNaN(Number(range))
|
const rangeDisplay = Number.isNaN(Number(range))
|
||||||
|
|
@ -49,8 +49,6 @@ export default function DhTemplateEnricher(match, _options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const renderMeasuredTemplate = async event => {
|
export const renderMeasuredTemplate = async event => {
|
||||||
const { LINE, RECTANGLE, INFRONT, CONE } = CONFIG.DH.GENERAL.templateTypes;
|
|
||||||
|
|
||||||
const button = event.currentTarget,
|
const button = event.currentTarget,
|
||||||
type = button.dataset.type,
|
type = button.dataset.type,
|
||||||
range = button.dataset.range,
|
range = button.dataset.range,
|
||||||
|
|
@ -59,51 +57,16 @@ export const renderMeasuredTemplate = async event => {
|
||||||
|
|
||||||
if (!type || !range || !game.canvas.scene) return;
|
if (!type || !range || !game.canvas.scene) return;
|
||||||
|
|
||||||
const usedType = type === 'inFront' ? 'cone' : type;
|
const shapeData = CONFIG.Canvas.layers.regions.layerClass.getTemplateShape({
|
||||||
const usedAngle =
|
type,
|
||||||
type === CONE ? (angle ?? CONFIG.MeasuredTemplate.defaults.angle) : type === INFRONT ? '180' : undefined;
|
angle,
|
||||||
|
range,
|
||||||
let baseDistance = range;
|
direction
|
||||||
if (Number.isNaN(Number(range))) {
|
});
|
||||||
baseDistance = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules).rangeMeasurement[
|
|
||||||
range
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
const dimensionConstant = game.scenes.active.grid.size / game.scenes.active.grid.distance;
|
|
||||||
|
|
||||||
baseDistance *= dimensionConstant;
|
|
||||||
|
|
||||||
const length = baseDistance;
|
|
||||||
const radius = length;
|
|
||||||
|
|
||||||
const shapeWidth = type === LINE ? 5 * dimensionConstant : type === RECTANGLE ? length : undefined;
|
|
||||||
|
|
||||||
const { width, height } = game.canvas.scene.dimensions;
|
|
||||||
const shapeData = {
|
|
||||||
x: width / 2,
|
|
||||||
y: height / 2,
|
|
||||||
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
|
|
||||||
},
|
|
||||||
t: usedType,
|
|
||||||
length: length,
|
|
||||||
width: shapeWidth,
|
|
||||||
height: length,
|
|
||||||
angle: usedAngle,
|
|
||||||
radius: radius,
|
|
||||||
direction: direction,
|
|
||||||
type: usedType
|
|
||||||
};
|
|
||||||
|
|
||||||
await canvas.regions.placeRegion(
|
await canvas.regions.placeRegion(
|
||||||
{
|
{
|
||||||
name: usedType.capitalize(),
|
name: type.capitalize(),
|
||||||
shapes: [shapeData],
|
shapes: [shapeData],
|
||||||
restriction: { enabled: false, type: 'move', priority: 0 },
|
restriction: { enabled: false, type: 'move', priority: 0 },
|
||||||
behaviors: [],
|
behaviors: [],
|
||||||
|
|
|
||||||
|
|
@ -189,7 +189,13 @@ export const getDeleteKeys = (property, innerProperty, innerPropertyDefaultValue
|
||||||
|
|
||||||
// Fix on Foundry native formula replacement for DH
|
// Fix on Foundry native formula replacement for DH
|
||||||
const nativeReplaceFormulaData = Roll.replaceFormulaData;
|
const nativeReplaceFormulaData = Roll.replaceFormulaData;
|
||||||
Roll.replaceFormulaData = function (formula, data = {}, { missing, warn = false } = {}) {
|
Roll.replaceFormulaData = function (formula, baseData = {}, { missing, warn = false } = {}) {
|
||||||
|
/* Inserting global data */
|
||||||
|
const data = {
|
||||||
|
...baseData,
|
||||||
|
partySize: game.actors?.party?.system.partyMembers.length ?? 0
|
||||||
|
};
|
||||||
|
|
||||||
const terms = Object.keys(CONFIG.DH.GENERAL.multiplierTypes).map(type => {
|
const terms = Object.keys(CONFIG.DH.GENERAL.multiplierTypes).map(type => {
|
||||||
return { term: type, default: 1 };
|
return { term: type, default: 1 };
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ export const preloadHandlebarsTemplates = async function () {
|
||||||
'templates/generic/tab-navigation.hbs',
|
'templates/generic/tab-navigation.hbs',
|
||||||
'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs',
|
'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs',
|
||||||
'systems/daggerheart/templates/sheets/global/partials/action-item.hbs',
|
'systems/daggerheart/templates/sheets/global/partials/action-item.hbs',
|
||||||
|
'systems/daggerheart/templates/sheets/global/partials/gold.hbs',
|
||||||
'systems/daggerheart/templates/sheets/global/partials/domain-card-item.hbs',
|
'systems/daggerheart/templates/sheets/global/partials/domain-card-item.hbs',
|
||||||
'systems/daggerheart/templates/sheets/global/partials/item-resource.hbs',
|
'systems/daggerheart/templates/sheets/global/partials/item-resource.hbs',
|
||||||
'systems/daggerheart/templates/sheets/global/partials/resource-section/resource-section.hbs',
|
'systems/daggerheart/templates/sheets/global/partials/resource-section/resource-section.hbs',
|
||||||
|
|
@ -28,6 +29,7 @@ export const preloadHandlebarsTemplates = async function () {
|
||||||
'systems/daggerheart/templates/actionTypes/uses.hbs',
|
'systems/daggerheart/templates/actionTypes/uses.hbs',
|
||||||
'systems/daggerheart/templates/actionTypes/roll.hbs',
|
'systems/daggerheart/templates/actionTypes/roll.hbs',
|
||||||
'systems/daggerheart/templates/actionTypes/save.hbs',
|
'systems/daggerheart/templates/actionTypes/save.hbs',
|
||||||
|
'systems/daggerheart/templates/actionTypes/areas.hbs',
|
||||||
'systems/daggerheart/templates/actionTypes/cost.hbs',
|
'systems/daggerheart/templates/actionTypes/cost.hbs',
|
||||||
'systems/daggerheart/templates/actionTypes/range-target.hbs',
|
'systems/daggerheart/templates/actionTypes/range-target.hbs',
|
||||||
'systems/daggerheart/templates/actionTypes/effect.hbs',
|
'systems/daggerheart/templates/actionTypes/effect.hbs',
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { defaultRestOptions } from '../config/generalConfig.mjs';
|
||||||
import { RefreshType, socketEvent } from './socket.mjs';
|
import { RefreshType, socketEvent } from './socket.mjs';
|
||||||
|
|
||||||
export async function runMigrations() {
|
export async function runMigrations() {
|
||||||
|
|
@ -341,6 +342,18 @@ export async function runMigrations() {
|
||||||
|
|
||||||
lastMigrationVersion = '2.0.0';
|
lastMigrationVersion = '2.0.0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (foundry.utils.isNewerVersion('2.1.0', lastMigrationVersion)) {
|
||||||
|
const downtimeMoves = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew);
|
||||||
|
if (downtimeMoves.restMoves.longRest.moves.repairArmor) {
|
||||||
|
await downtimeMoves.updateSource({
|
||||||
|
'restMoves.longRest.moves.repairArmor': defaultRestOptions.longRest().repairArmor
|
||||||
|
});
|
||||||
|
game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, downtimeMoves.toObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
lastMigrationVersion = '2.1.0';
|
||||||
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LastMigrationVersion, lastMigrationVersion);
|
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LastMigrationVersion, lastMigrationVersion);
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,27 @@ export const registerKeyBindings = () => {
|
||||||
reservedModifiers: [],
|
reservedModifiers: [],
|
||||||
precedence: CONST.KEYBINDING_PRECEDENCE.NORMAL
|
precedence: CONST.KEYBINDING_PRECEDENCE.NORMAL
|
||||||
});
|
});
|
||||||
|
|
||||||
|
game.keybindings.register(CONFIG.DH.id, CONFIG.DH.SETTINGS.keybindings.partySheet, {
|
||||||
|
name: _loc('DAGGERHEART.SETTINGS.Keybindings.partySheet.name'),
|
||||||
|
hint: _loc('DAGGERHEART.SETTINGS.Keybindings.partySheet.hint'),
|
||||||
|
editable: [{ key: 'KeyP' }],
|
||||||
|
onDown: () => {
|
||||||
|
const controlled = canvas.ready ? canvas.tokens.controlled : [];
|
||||||
|
const selectedParty = controlled.find(c => c.actor?.type === 'party')?.actor;
|
||||||
|
const party = selectedParty ?? game.actors.party;
|
||||||
|
if (!party) return;
|
||||||
|
|
||||||
|
const sheet = party.sheet;
|
||||||
|
if (!sheet.rendered) {
|
||||||
|
sheet.render(true);
|
||||||
|
} else if (sheet.minimized) {
|
||||||
|
sheet.maximize();
|
||||||
|
} else {
|
||||||
|
sheet.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const registerMenuSettings = () => {
|
const registerMenuSettings = () => {
|
||||||
|
|
@ -189,4 +210,11 @@ const registerNonConfigSettings = () => {
|
||||||
config: false,
|
config: false,
|
||||||
type: SpotlightTracker
|
type: SpotlightTracker
|
||||||
});
|
});
|
||||||
|
|
||||||
|
game.settings.register(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.ActiveParty, {
|
||||||
|
scope: 'world',
|
||||||
|
config: false,
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@ export function handleSocketEvent({ action = null, data = {} } = {}) {
|
||||||
case socketEvent.TagTeamStart:
|
case socketEvent.TagTeamStart:
|
||||||
Hooks.callAll(CONFIG.DH.HOOKS.hooksConfig.tagTeamStart, data);
|
Hooks.callAll(CONFIG.DH.HOOKS.hooksConfig.tagTeamStart, data);
|
||||||
break;
|
break;
|
||||||
|
case socketEvent.GroupRollStart:
|
||||||
|
Hooks.callAll(CONFIG.DH.HOOKS.hooksConfig.groupRollStart, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -26,7 +28,8 @@ export const socketEvent = {
|
||||||
Refresh: 'DhRefresh',
|
Refresh: 'DhRefresh',
|
||||||
DhpFearUpdate: 'DhFearUpdate',
|
DhpFearUpdate: 'DhFearUpdate',
|
||||||
DowntimeTrigger: 'DowntimeTrigger',
|
DowntimeTrigger: 'DowntimeTrigger',
|
||||||
TagTeamStart: 'DhTagTeamStart'
|
TagTeamStart: 'DhTagTeamStart',
|
||||||
|
GroupRollStart: 'DhGroupRollStart'
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GMUpdateEvent = {
|
export const GMUpdateEvent = {
|
||||||
|
|
@ -41,6 +44,7 @@ export const GMUpdateEvent = {
|
||||||
export const RefreshType = {
|
export const RefreshType = {
|
||||||
Countdown: 'DhCoundownRefresh',
|
Countdown: 'DhCoundownRefresh',
|
||||||
TagTeamRoll: 'DhTagTeamRollRefresh',
|
TagTeamRoll: 'DhTagTeamRollRefresh',
|
||||||
|
GroupRoll: 'DhGroupRollRefresh',
|
||||||
EffectsDisplay: 'DhEffectsDisplayRefresh',
|
EffectsDisplay: 'DhEffectsDisplayRefresh',
|
||||||
Scene: 'DhSceneRefresh',
|
Scene: 'DhSceneRefresh',
|
||||||
CompendiumBrowser: 'DhCompendiumBrowserRefresh'
|
CompendiumBrowser: 'DhCompendiumBrowserRefresh'
|
||||||
|
|
|
||||||
872
package-lock.json
generated
872
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -17,13 +17,18 @@
|
||||||
"pullYMLtoLDB": "node ./tools/pullYMLtoLDB.mjs",
|
"pullYMLtoLDB": "node ./tools/pullYMLtoLDB.mjs",
|
||||||
"pullYMLtoLDBBuild": "node ./tools/pullYMLtoLDB.mjs --build",
|
"pullYMLtoLDBBuild": "node ./tools/pullYMLtoLDB.mjs --build",
|
||||||
"createSymlink": "node ./tools/create-symlink.mjs",
|
"createSymlink": "node ./tools/create-symlink.mjs",
|
||||||
"setup:dev": "node ./tools/dev-setup.mjs"
|
"setup:dev": "node ./tools/dev-setup.mjs",
|
||||||
|
"lint": "eslint",
|
||||||
|
"lint:fix": "eslint --fix"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@foundryvtt/foundryvtt-cli": "^1.0.2",
|
"@foundryvtt/foundryvtt-cli": "^1.0.2",
|
||||||
"@rollup/plugin-commonjs": "^25.0.7",
|
"@rollup/plugin-commonjs": "^25.0.7",
|
||||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||||
"concurrently": "^8.2.2",
|
"concurrently": "^8.2.2",
|
||||||
|
"eslint": "^10.2.1",
|
||||||
|
"eslint-plugin-prettier": "^5.5.5",
|
||||||
|
"globals": "^17.5.0",
|
||||||
"husky": "^9.1.5",
|
"husky": "^9.1.5",
|
||||||
"lint-staged": "^15.2.10",
|
"lint-staged": "^15.2.10",
|
||||||
"postcss": "^8.4.32",
|
"postcss": "^8.4.32",
|
||||||
|
|
|
||||||
|
|
@ -169,12 +169,9 @@
|
||||||
"src": "systems/daggerheart/assets/icons/documents/actors/dragon-head.svg",
|
"src": "systems/daggerheart/assets/icons/documents/actors/dragon-head.svg",
|
||||||
"anchorX": 0.5,
|
"anchorX": 0.5,
|
||||||
"anchorY": 0.5,
|
"anchorY": 0.5,
|
||||||
"offsetX": 0,
|
|
||||||
"offsetY": 0,
|
|
||||||
"fit": "contain",
|
"fit": "contain",
|
||||||
"scaleX": 1,
|
"scaleX": 1,
|
||||||
"scaleY": 1,
|
"scaleY": 1,
|
||||||
"rotation": 0,
|
|
||||||
"tint": "#ffffff",
|
"tint": "#ffffff",
|
||||||
"alphaThreshold": 0.75
|
"alphaThreshold": 0.75
|
||||||
},
|
},
|
||||||
|
|
@ -225,7 +222,7 @@
|
||||||
"saturation": 0,
|
"saturation": 0,
|
||||||
"contrast": 0
|
"contrast": 0
|
||||||
},
|
},
|
||||||
"detectionModes": [],
|
"detectionModes": {},
|
||||||
"occludable": {
|
"occludable": {
|
||||||
"radius": 0
|
"radius": 0
|
||||||
},
|
},
|
||||||
|
|
@ -251,7 +248,8 @@
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"randomImg": false,
|
"randomImg": false,
|
||||||
"appendNumber": false,
|
"appendNumber": false,
|
||||||
"prependAdjective": false
|
"prependAdjective": false,
|
||||||
|
"depth": 1
|
||||||
},
|
},
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
|
|
@ -319,7 +317,7 @@
|
||||||
"_id": "ctXYwil2D1zfsekT",
|
"_id": "ctXYwil2D1zfsekT",
|
||||||
"img": "icons/magic/earth/barrier-stone-explosion-red.webp",
|
"img": "icons/magic/earth/barrier-stone-explosion-red.webp",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p><strong>Mark a Stress</strong> to have the @Lookup[@name] burst out of the ground. All creatures within Very Close range must succeed on an Agility Reaction Roll or be knocked over, making them Vulnerable until they next act.</p><p>@Template[type:emanation|range:vc]</p>",
|
"description": "<p><strong>Mark a Stress</strong> to have the @Lookup[@name] burst out of the ground. All creatures within Very Close range must succeed on an Agility Reaction Roll or be knocked over, making them Vulnerable until they next act.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"4ppSeiTdbqnMzWAs": {
|
"4ppSeiTdbqnMzWAs": {
|
||||||
|
|
@ -344,7 +342,8 @@
|
||||||
},
|
},
|
||||||
"damage": {
|
"damage": {
|
||||||
"parts": {},
|
"parts": {},
|
||||||
"includeBase": false
|
"includeBase": false,
|
||||||
|
"groupAttack": ""
|
||||||
},
|
},
|
||||||
"target": {
|
"target": {
|
||||||
"type": "any",
|
"type": "any",
|
||||||
|
|
@ -378,7 +377,16 @@
|
||||||
},
|
},
|
||||||
"name": "Roll Save",
|
"name": "Roll Save",
|
||||||
"img": "icons/magic/earth/barrier-stone-explosion-red.webp",
|
"img": "icons/magic/earth/barrier-stone-explosion-red.webp",
|
||||||
"range": "veryClose"
|
"range": "veryClose",
|
||||||
|
"areas": [
|
||||||
|
{
|
||||||
|
"name": "Earth Eruption",
|
||||||
|
"type": "placed",
|
||||||
|
"shape": "emanation",
|
||||||
|
"size": "veryClose",
|
||||||
|
"effects": []
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
|
|
@ -581,7 +589,7 @@
|
||||||
"_id": "aNIVT5LKhwLyjKpI",
|
"_id": "aNIVT5LKhwLyjKpI",
|
||||||
"img": "icons/magic/acid/dissolve-drip-droplet-smoke.webp",
|
"img": "icons/magic/acid/dissolve-drip-droplet-smoke.webp",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p>When the @Lookup[@name] takes Severe damage, all creatures within Close range are bathed in their acidic blood, taking <strong>1d10</strong> physical damage. This splash covers the ground within Very Close range with blood, and all creatures other than the @Lookup[@name] who move through it take <strong>1d6</strong> physical damage.</p><p>@Template[type:emanation|range:c]</p>",
|
"description": "<p>When the @Lookup[@name] takes Severe damage, all creatures within Close range are bathed in their acidic blood, taking <strong>1d10</strong> physical damage. This splash covers the ground within Very Close range with blood, and all creatures other than the @Lookup[@name] who move through it take <strong>1d6</strong> physical damage.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"XbtTzOBvlTaxOKTy": {
|
"XbtTzOBvlTaxOKTy": {
|
||||||
|
|
@ -627,7 +635,8 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"includeBase": false
|
"includeBase": false,
|
||||||
|
"groupAttack": ""
|
||||||
},
|
},
|
||||||
"target": {
|
"target": {
|
||||||
"type": "any",
|
"type": "any",
|
||||||
|
|
@ -636,7 +645,23 @@
|
||||||
"effects": [],
|
"effects": [],
|
||||||
"name": "Splash",
|
"name": "Splash",
|
||||||
"img": "icons/magic/acid/dissolve-drip-droplet-smoke.webp",
|
"img": "icons/magic/acid/dissolve-drip-droplet-smoke.webp",
|
||||||
"range": "close"
|
"range": "close",
|
||||||
|
"areas": [
|
||||||
|
{
|
||||||
|
"name": "Acid Bath: Damage Area",
|
||||||
|
"type": "placed",
|
||||||
|
"shape": "emanation",
|
||||||
|
"size": "close",
|
||||||
|
"effects": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Acid Ground",
|
||||||
|
"type": "placed",
|
||||||
|
"shape": "emanation",
|
||||||
|
"size": "veryClose",
|
||||||
|
"effects": []
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"xpcp1ECTWF20kxve": {
|
"xpcp1ECTWF20kxve": {
|
||||||
"type": "damage",
|
"type": "damage",
|
||||||
|
|
@ -690,7 +715,8 @@
|
||||||
"effects": [],
|
"effects": [],
|
||||||
"name": "Acid Ground",
|
"name": "Acid Ground",
|
||||||
"img": "icons/magic/acid/dissolve-pool-bubbles.webp",
|
"img": "icons/magic/acid/dissolve-pool-bubbles.webp",
|
||||||
"range": ""
|
"range": "",
|
||||||
|
"areas": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
|
|
|
||||||
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