mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-04-21 15:03:37 +02:00
Compare commits
64 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
191 changed files with 5290 additions and 2158 deletions
|
|
@ -1,3 +1,5 @@
|
|||
[*]
|
||||
indent_size = 4
|
||||
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:
|
||||
version: ${{steps.get_version.outputs.version-without-v}}
|
||||
url: https://github.com/${{github.repository}}
|
||||
manifest: https://raw.githubusercontent.com/${{github.repository}}/main/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
|
||||
|
||||
# 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:
|
||||
|
||||
- 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**!
|
||||
|
||||
🐸🛠️
|
||||
Thank you for your understanding and support.
|
||||
|
|
|
|||
|
|
@ -343,7 +343,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 => {
|
||||
if (!token) return;
|
||||
|
||||
const rangeMeasurement = game.settings.get(
|
||||
CONFIG.DH.id,
|
||||
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'
|
||||
}
|
||||
}
|
||||
]);
|
||||
121
lang/en.json
121
lang/en.json
|
|
@ -74,9 +74,7 @@
|
|||
"name": "Summon",
|
||||
"tooltip": "Create tokens in the 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.",
|
||||
"chatMessageTitle": "Test2",
|
||||
"chatMessageHeaderTitle": "Summoning"
|
||||
"invalidDrop": "You can only drop Actor entities to summon."
|
||||
},
|
||||
"transform": {
|
||||
"name": "Transform",
|
||||
|
|
@ -115,7 +113,9 @@
|
|||
"deleteTriggerTitle": "Delete Trigger",
|
||||
"deleteTriggerContent": "Are you sure you want to delete the {trigger} trigger?",
|
||||
"advantageState": "Advantage State",
|
||||
"damageOnSave": "Damage on Save"
|
||||
"damageOnSave": "Damage on Save",
|
||||
"useDefaultItemValues": "Use default Item values",
|
||||
"itemDamageIsUsed": "Item Damage Is Used"
|
||||
},
|
||||
"RollField": {
|
||||
"diceRolling": {
|
||||
|
|
@ -130,7 +130,8 @@
|
|||
"attackModifier": "Attack Modifier",
|
||||
"attackName": "Attack Name",
|
||||
"criticalThreshold": "Critical Threshold",
|
||||
"includeBase": { "label": "Include Item Damage" },
|
||||
"includeBase": { "label": "Use Item Damage" },
|
||||
"groupAttack": { "label": "Group Attack" },
|
||||
"multiplier": "Multiplier",
|
||||
"saveHint": "Set a default Trait to enable Reaction Roll. It can be changed later in Reaction Roll Dialog.",
|
||||
"resultBased": {
|
||||
|
|
@ -209,7 +210,13 @@
|
|||
"type": { "label": "Type" }
|
||||
},
|
||||
"hordeDamage": "Horde Damage",
|
||||
"horderHp": "Horde/HP"
|
||||
"horderHp": "Horde/HP",
|
||||
"adversaryReactionRoll": {
|
||||
"headerTitle": "Adversary Reaction Roll"
|
||||
}
|
||||
},
|
||||
"Base": {
|
||||
"CannotAddType": "Cannot add {itemType} items to {actorType} actors."
|
||||
},
|
||||
"Character": {
|
||||
"advantageSources": {
|
||||
|
|
@ -234,6 +241,8 @@
|
|||
},
|
||||
"defaultHopeDice": "Default Hope Dice",
|
||||
"defaultFearDice": "Default Fear Dice",
|
||||
"defaultAdvantageDice": "Default Advantage Dice",
|
||||
"defaultDisadvantageDice": "Default Disadvantage Dice",
|
||||
"disadvantageSources": {
|
||||
"label": "Disadvantage Sources",
|
||||
"hint": "Add single words or short text as reminders and hints of what a character has disadvantage on."
|
||||
|
|
@ -318,6 +327,22 @@
|
|||
}
|
||||
},
|
||||
"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": {
|
||||
|
|
@ -353,7 +378,7 @@
|
|||
"selectSecondaryWeapon": "Select Secondary Weapon",
|
||||
"selectSubclass": "Select Subclass",
|
||||
"setupSkipTitle": "Skipping Character Setup",
|
||||
"setupSkipContent": "You are skipping the Character Setup by adding this manually. The character setup is the blinking 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",
|
||||
"story": "Story",
|
||||
"storyExplanation": "Select which background and connection prompts you want to copy into your character's background.",
|
||||
|
|
@ -451,6 +476,10 @@
|
|||
"defaultOwnershipTooltip": "The default player ownership of countdowns",
|
||||
"hideNewCountdowns": "Hide New Countdowns"
|
||||
},
|
||||
"CreateItemDialog": {
|
||||
"createItem": "Create Item",
|
||||
"browseCompendium": "Browse Compendium"
|
||||
},
|
||||
"DaggerheartMenu": {
|
||||
"title": "GM Tools",
|
||||
"refreshFeatures": "Refresh Features",
|
||||
|
|
@ -665,6 +694,12 @@
|
|||
"noPlayers": "No players to assign ownership to",
|
||||
"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": {
|
||||
"title": "Reaction Roll: {trait}"
|
||||
},
|
||||
|
|
@ -690,7 +725,7 @@
|
|||
"FIELDS": {
|
||||
"initiator": {
|
||||
"memberId": { "label": "Initiating Character" },
|
||||
"cost": { "label": "Initiation Cost" }
|
||||
"cost": { "label": "Hope Cost" }
|
||||
}
|
||||
},
|
||||
"leaderTitle": "Initiating Character",
|
||||
|
|
@ -717,6 +752,17 @@
|
|||
"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": {
|
||||
"actorSizeUsed": "Actor size is set, determining the dimensions"
|
||||
}
|
||||
|
|
@ -737,6 +783,11 @@
|
|||
"session": "Next Session",
|
||||
"custom": "Custom"
|
||||
},
|
||||
"ActionAutomationChoices": {
|
||||
"never": "Never",
|
||||
"showDialog": "Show Dialog Only",
|
||||
"always": "Always"
|
||||
},
|
||||
"AdversaryTrait": {
|
||||
"relentless": {
|
||||
"name": "Relentless",
|
||||
|
|
@ -1266,6 +1317,11 @@
|
|||
"on": "On",
|
||||
"onWithToggle": "On With Toggle"
|
||||
},
|
||||
"SceneRangeMeasurementTypes": {
|
||||
"disable": "Disable Daggerheart Range Measurement",
|
||||
"default": "Default",
|
||||
"custom": "Custom"
|
||||
},
|
||||
"SelectAction": {
|
||||
"selectType": "Select Action Type",
|
||||
"selectAction": "Action Selection"
|
||||
|
|
@ -1965,6 +2021,10 @@
|
|||
"hint": "Multiply any damage dealt to you by this number"
|
||||
}
|
||||
},
|
||||
"Battlepoints": {
|
||||
"full": "Battlepoints",
|
||||
"short": "BP"
|
||||
},
|
||||
"Bonuses": {
|
||||
"rest": {
|
||||
"downtimeAction": "Downtime Action",
|
||||
|
|
@ -2377,8 +2437,10 @@
|
|||
"multiclass": "Multiclass",
|
||||
"newCategory": "New Category",
|
||||
"newThing": "New {thing}",
|
||||
"next": "Next",
|
||||
"none": "None",
|
||||
"noTarget": "No current target",
|
||||
"optionalThing": "Optional {thing}",
|
||||
"partner": "Partner",
|
||||
"player": {
|
||||
"single": "Player",
|
||||
|
|
@ -2405,9 +2467,11 @@
|
|||
"rollDamage": "Roll Damage",
|
||||
"rollWith": "{roll} Roll",
|
||||
"save": "Save",
|
||||
"saveSettings": "Save Settings",
|
||||
"scalable": "Scalable",
|
||||
"scars": "Scars",
|
||||
"situationalBonus": "Situational Bonus",
|
||||
"searchPlaceholder": "Search...",
|
||||
"spent": "Spent",
|
||||
"step": "Step",
|
||||
"stress": "Stress",
|
||||
|
|
@ -2558,8 +2622,14 @@
|
|||
},
|
||||
"Weapon": {
|
||||
"weaponType": "Weapon Type",
|
||||
"primaryWeapon": "Primary Weapon",
|
||||
"secondaryWeapon": "Secondary Weapon"
|
||||
"primaryWeapon": {
|
||||
"full": "Primary Weapon",
|
||||
"short": "Primary"
|
||||
},
|
||||
"secondaryWeapon": {
|
||||
"full": "Secondary Weapon",
|
||||
"short": "Secondary"
|
||||
}
|
||||
}
|
||||
},
|
||||
"MACROS": {
|
||||
|
|
@ -2813,6 +2883,10 @@
|
|||
}
|
||||
},
|
||||
"Keybindings": {
|
||||
"partySheet": {
|
||||
"name": "Toggle Party Sheet",
|
||||
"hint": "Open or close the active party's sheet"
|
||||
},
|
||||
"spotlight": {
|
||||
"name": "Spotlight Combatant",
|
||||
"hint": "Move the spotlight to a hovered or selected token that's present in an active encounter"
|
||||
|
|
@ -2857,6 +2931,7 @@
|
|||
"system": "Dice Preset",
|
||||
"font": "Font",
|
||||
"critical": "Duality Critical Animation",
|
||||
"muted": "Muted",
|
||||
"diceAppearance": "Dice Appearance",
|
||||
"animations": "Animations",
|
||||
"defaultAnimations": "Set Animations As Player Defaults",
|
||||
|
|
@ -2965,18 +3040,6 @@
|
|||
"immunityTo": "Immunity: {immunities}"
|
||||
},
|
||||
"featureTitle": "Class Feature",
|
||||
"groupRoll": {
|
||||
"title": "Group Roll",
|
||||
"leader": "Leader",
|
||||
"partyTeam": "Party Team",
|
||||
"team": "Team",
|
||||
"selectLeader": "Select a Leader",
|
||||
"selectMember": "Select a Member",
|
||||
"rerollTitle": "Reroll Group Roll",
|
||||
"rerollContent": "Are you sure you want to reroll your {trait} roll?",
|
||||
"rerollTooltip": "Reroll",
|
||||
"wholePartySelected": "The whole party is selected"
|
||||
},
|
||||
"healingRoll": {
|
||||
"title": "Heal - {damage}",
|
||||
"heal": "Heal",
|
||||
|
|
@ -2994,6 +3057,9 @@
|
|||
"resourceRoll": {
|
||||
"playerMessage": "{user} rerolled their {name}"
|
||||
},
|
||||
"saveRoll": {
|
||||
"reactionRollAllTargets": "Reaction Roll All Targets"
|
||||
},
|
||||
"tagTeam": {
|
||||
"title": "Tag Team",
|
||||
"membersTitle": "Members"
|
||||
|
|
@ -3022,9 +3088,9 @@
|
|||
},
|
||||
"ItemBrowser": {
|
||||
"title": "Daggerheart Compendium Browser",
|
||||
"windowTitle": "Compendium Browser",
|
||||
"hint": "Select a Folder in sidebar to start browsing through the compendium",
|
||||
"browserSettings": "Browser Settings",
|
||||
"searchPlaceholder": "Search...",
|
||||
"columnName": "Name",
|
||||
"tooltipFilters": "Filters",
|
||||
"tooltipErase": "Erase",
|
||||
|
|
@ -3060,7 +3126,7 @@
|
|||
"weapons": "Weapons",
|
||||
"armors": "Armors",
|
||||
"consumables": "Consumables",
|
||||
"loots": "Loots"
|
||||
"loots": "Loot"
|
||||
}
|
||||
},
|
||||
"Notifications": {
|
||||
|
|
@ -3142,7 +3208,8 @@
|
|||
"tokenActorsMissing": "[{names}] missing Actors",
|
||||
"domainTouchRequirement": "This domain card requires {nr} {domain} cards in the loadout to be used",
|
||||
"knowTheTide": "Know The Tide gained a token",
|
||||
"lackingItemTransferPermission": "User {user} lacks owner permission needed to transfer items to {target}"
|
||||
"lackingItemTransferPermission": "User {user} lacks owner permission needed to transfer items to {target}",
|
||||
"noTokenTargeted": "No token is targeted"
|
||||
},
|
||||
"Progress": {
|
||||
"migrationLabel": "Performing system migration. Please wait and do not close Foundry."
|
||||
|
|
@ -3154,6 +3221,9 @@
|
|||
"companion": "Level {level} - {partner}",
|
||||
"companionNoPartner": "No Partner",
|
||||
"duplicateToNewTier": "Duplicate to New Tier",
|
||||
"activateParty": "Make Active Party",
|
||||
"partyIsActive": "Active",
|
||||
"createAdversary": "Create Adversary",
|
||||
"pickTierTitle": "Pick a new tier for this adversary"
|
||||
},
|
||||
"daggerheartMenu": {
|
||||
|
|
@ -3165,6 +3235,7 @@
|
|||
"Tooltip": {
|
||||
"disableEffect": "Disable Effect",
|
||||
"enableEffect": "Enable Effect",
|
||||
"edit": "Edit",
|
||||
"openItemWorld": "Open Item World",
|
||||
"openActorWorld": "Open Actor World",
|
||||
"sendToChat": "Send to Chat",
|
||||
|
|
|
|||
|
|
@ -11,7 +11,10 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
|||
this.character = character;
|
||||
|
||||
this.setup = {
|
||||
traits: this.character.system.traits,
|
||||
traits: Object.keys(this.character.system.traits).reduce((acc, key) => {
|
||||
acc[key] = { value: null };
|
||||
return acc;
|
||||
}, {}),
|
||||
ancestryName: {
|
||||
primary: '',
|
||||
secondary: ''
|
||||
|
|
@ -377,8 +380,10 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
|||
];
|
||||
return Object.values(this.setup.traits).reduce((acc, x) => {
|
||||
const index = traitCompareArray.indexOf(x.value);
|
||||
if (index === -1) return acc;
|
||||
|
||||
traitCompareArray.splice(index, 1);
|
||||
acc += index !== -1;
|
||||
acc += 1;
|
||||
return acc;
|
||||
}, 0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export { default as OwnershipSelection } from './ownershipSelection.mjs';
|
|||
export { default as RerollDamageDialog } from './rerollDamageDialog.mjs';
|
||||
export { default as ResourceDiceDialog } from './resourceDiceDialog.mjs';
|
||||
export { default as ActionSelectionDialog } from './actionSelectionDialog.mjs';
|
||||
export { default as GroupRollDialog } from './group-roll-dialog.mjs';
|
||||
export { default as TagTeamDialog } from './tagTeamDialog.mjs';
|
||||
export { default as GroupRollDialog } from './groupRollDialog.mjs';
|
||||
export { default as RiskItAllDialog } from './riskItAllDialog.mjs';
|
||||
export { default as CompendiumBrowserSettingsDialog } from './CompendiumBrowserSettings.mjs';
|
||||
|
|
|
|||
|
|
@ -123,6 +123,10 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
context.advantage = this.config.roll?.advantage;
|
||||
context.disadvantage = this.config.roll?.disadvantage;
|
||||
context.diceOptions = CONFIG.DH.GENERAL.diceTypes;
|
||||
context.dieFaces = CONFIG.DH.GENERAL.dieFaces.reduce((acc, face) => {
|
||||
acc[face] = `d${face}`;
|
||||
return acc;
|
||||
}, {});
|
||||
context.isLite = this.config.roll?.lite;
|
||||
context.extraFormula = this.config.extraFormula;
|
||||
context.formula = this.roll.constructFormula(this.config);
|
||||
|
|
@ -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 (rest.roll?.dice) {
|
||||
Object.entries(rest.roll.dice).forEach(([key, value]) => {
|
||||
this.roll[key] = value;
|
||||
});
|
||||
this.roll = foundry.utils.mergeObject(this.roll, rest.roll.dice);
|
||||
}
|
||||
if (rest.hasOwnProperty('trait')) {
|
||||
this.config.roll.trait = rest.trait;
|
||||
|
|
@ -173,6 +175,15 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
this.disadvantage = advantage === -1;
|
||||
|
||||
this.config.roll.advantage = this.config.roll.advantage === advantage ? 0 : advantage;
|
||||
|
||||
if (this.config.roll.advantage === 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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
|
|||
},
|
||||
actions: {
|
||||
toggleSelectedEffect: this.toggleSelectedEffect,
|
||||
updateGroupAttack: this.updateGroupAttack,
|
||||
toggleCritical: this.toggleCritical,
|
||||
submitRoll: this.submitRoll
|
||||
},
|
||||
|
|
@ -64,15 +65,40 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
|
|||
context.hasSelectedEffects = Boolean(Object.keys(this.selectedEffects).length);
|
||||
context.selectedEffects = this.selectedEffects;
|
||||
|
||||
context.damageOptions = this.config.damageOptions;
|
||||
context.rangeOptions = CONFIG.DH.GENERAL.groupAttackRange;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
static updateRollConfiguration(_event, _, formData) {
|
||||
const { ...rest } = foundry.utils.expandObject(formData.object);
|
||||
foundry.utils.mergeObject(this.config.roll, rest.roll);
|
||||
foundry.utils.mergeObject(this.config.modifiers, rest.modifiers);
|
||||
this.config.selectedMessageMode = rest.selectedMessageMode;
|
||||
const data = foundry.utils.expandObject(formData.object);
|
||||
foundry.utils.mergeObject(this.config.roll, data.roll);
|
||||
foundry.utils.mergeObject(this.config.modifiers, data.modifiers);
|
||||
this.config.selectedMessageMode = data.selectedMessageMode;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -259,8 +259,9 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
|||
const resetValue = increasing
|
||||
? 0
|
||||
: 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;
|
||||
|
||||
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;
|
||||
const homebrewKey = CONFIG.DH.SETTINGS.gameSettings.Homebrew;
|
||||
const currencySetting = game.settings.get(CONFIG.DH.id, homebrewKey).currency?.[currency] ?? null;
|
||||
const max = item?.system.quantity ?? originActor.system.gold[currency] ?? 0;
|
||||
|
||||
return {
|
||||
originActor,
|
||||
targetActor,
|
||||
itemImage: item?.img,
|
||||
currencyIcon: currencySetting?.icon,
|
||||
max: item?.system.quantity ?? originActor.system.gold[currency] ?? 0,
|
||||
max,
|
||||
initial: targetActor.system.metadata.quantifiable?.includes(item.type) ? max : 1,
|
||||
title: item?.name ?? currencySetting?.label
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
owned: member.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER)
|
||||
}));
|
||||
|
||||
this.initiator = null;
|
||||
this.initiator = { cost: 3 };
|
||||
this.openForAllPlayers = true;
|
||||
|
||||
this.tabGroups.application = Object.keys(party.system.tagTeam.members).length
|
||||
|
|
@ -366,8 +366,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
let rollIsSelected = false;
|
||||
for (const member of Object.values(members)) {
|
||||
const rollFinished = Boolean(member.rollData);
|
||||
const damageFinished =
|
||||
member.rollData?.options?.hasDamage !== undefined ? member.rollData.options.damage : true;
|
||||
const damageFinished = member.rollData?.options?.hasDamage ? Boolean(member.rollData.options.damage) : true;
|
||||
|
||||
rollsAreFinished = rollsAreFinished && rollFinished && damageFinished;
|
||||
rollIsSelected = rollIsSelected || member.selected;
|
||||
|
|
|
|||
|
|
@ -122,15 +122,14 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
|||
|
||||
async toggleClowncar(actors) {
|
||||
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;
|
||||
if (activeTokens.length > 0) {
|
||||
for (let token of activeTokens) {
|
||||
await token.document.update(
|
||||
{ x: actorX, y: actorY, alpha: 0 },
|
||||
{ animation: { duration: animationDuration } }
|
||||
);
|
||||
setTimeout(() => token.document.delete(), animationDuration);
|
||||
await token.update({ x: actorX, y: actorY, alpha: 0 }, { animation: { duration: animationDuration } });
|
||||
setTimeout(() => token.delete(), animationDuration);
|
||||
}
|
||||
} else {
|
||||
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());
|
||||
}
|
||||
|
||||
const viewedLevel = game.scenes.get(game.user.viewedScene).levels.get(game.user.viewedLevel);
|
||||
const elevation = this.actor.token?.elevation ?? viewedLevel.elevation.bottom;
|
||||
|
||||
const newTokens = await activeScene.createEmbeddedDocuments(
|
||||
'Token',
|
||||
tokenData.map(tokenData => ({
|
||||
...tokenData,
|
||||
alpha: 0,
|
||||
level: viewedLevel,
|
||||
elevation: elevation,
|
||||
x: actorX,
|
||||
y: actorY
|
||||
}))
|
||||
|
|
|
|||
|
|
@ -118,8 +118,13 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App
|
|||
break;
|
||||
case 'footer':
|
||||
partContext.buttons = [
|
||||
{ type: 'button', action: 'reset', icon: 'fa-solid fa-arrow-rotate-left', label: 'Reset' },
|
||||
{ type: 'submit', icon: 'fa-solid fa-floppy-disk', label: 'Save Changes' }
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -217,8 +217,8 @@ export default class AdversarySheet extends DHBaseActorSheet {
|
|||
static #reactionRoll(event) {
|
||||
const config = {
|
||||
event,
|
||||
title: `Reaction Roll: ${this.actor.name}`,
|
||||
headerTitle: 'Adversary Reaction Roll',
|
||||
title: game.i18n.localize('DAGGERHEART.GENERAL.reactionRoll'),
|
||||
headerTitle: game.i18n.localize('DAGGERHEART.ACTORS.Adversary.adversaryReactionRoll.headerTitle'),
|
||||
roll: {
|
||||
type: 'trait'
|
||||
},
|
||||
|
|
|
|||
|
|
@ -12,8 +12,6 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
static DEFAULT_OPTIONS = {
|
||||
classes: ['character'],
|
||||
position: { width: 850, height: 800 },
|
||||
/* Foundry adds disabled to all buttons and inputs if editPermission is missing. This is not desired. */
|
||||
editPermission: CONST.DOCUMENT_OWNERSHIP_LEVELS.OBSERVER,
|
||||
actions: {
|
||||
toggleVault: CharacterSheet.#toggleVault,
|
||||
rollAttribute: CharacterSheet.#rollAttribute,
|
||||
|
|
@ -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"]',
|
||||
options: {
|
||||
parentClassHooks: false,
|
||||
|
|
@ -170,6 +168,16 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
return applicationOptions;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
_toggleDisabled(disabled) {
|
||||
// Overriden to only disable text inputs by default.
|
||||
// Everything else is done by checking @root.editable in the sheet
|
||||
const form = this.form;
|
||||
for (const input of form.querySelectorAll('input:not([type=search]), .editor.prosemirror')) {
|
||||
input.disabled = disabled;
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
async _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[]} */
|
||||
const options = [
|
||||
{
|
||||
name: 'toLoadout',
|
||||
label: 'toLoadout',
|
||||
icon: 'fa-solid fa-arrow-up',
|
||||
condition: target => {
|
||||
visible: target => {
|
||||
const doc = getDocFromElementSync(target);
|
||||
return doc && doc.system.inVault;
|
||||
return doc?.isOwner && doc.system.inVault;
|
||||
},
|
||||
callback: async 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',
|
||||
condition: target => {
|
||||
visible: target => {
|
||||
const doc = getDocFromElementSync(target);
|
||||
return doc && doc.system.inVault;
|
||||
return doc?.isOwner && doc.system.inVault;
|
||||
},
|
||||
callback: async (target, event) => {
|
||||
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',
|
||||
condition: target => {
|
||||
visible: 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 })
|
||||
}
|
||||
].map(option => ({
|
||||
...option,
|
||||
name: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.name}`,
|
||||
label: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.label}`,
|
||||
icon: `<i class="${option.icon}"></i>`
|
||||
}));
|
||||
|
||||
|
|
@ -391,29 +399,29 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
* @this {CharacterSheet}
|
||||
* @protected
|
||||
*/
|
||||
static #getEquipamentContextOptions() {
|
||||
static #getEquipmentContextOptions() {
|
||||
const options = [
|
||||
{
|
||||
name: 'equip',
|
||||
label: 'equip',
|
||||
icon: 'fa-solid fa-hands',
|
||||
condition: target => {
|
||||
visible: 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)
|
||||
},
|
||||
{
|
||||
name: 'unequip',
|
||||
label: 'unequip',
|
||||
icon: 'fa-solid fa-hands',
|
||||
condition: target => {
|
||||
visible: 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)
|
||||
}
|
||||
].map(option => ({
|
||||
...option,
|
||||
name: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.name}`,
|
||||
label: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.label}`,
|
||||
icon: `<i class="${option.icon}"></i>`
|
||||
}));
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
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 FilterMenu from '../../ux/filter-menu.mjs';
|
||||
import DaggerheartMenu from '../../sidebar/tabs/daggerheartMenu.mjs';
|
||||
import { socketEvent } from '../../../systemRegistration/socket.mjs';
|
||||
import GroupRollDialog from '../../dialogs/group-roll-dialog.mjs';
|
||||
import DhpActor from '../../../documents/actor.mjs';
|
||||
|
||||
export default class Party extends DHBaseActorSheet {
|
||||
|
|
@ -18,13 +17,14 @@ export default class Party extends DHBaseActorSheet {
|
|||
static DEFAULT_OPTIONS = {
|
||||
classes: ['party'],
|
||||
position: {
|
||||
width: 550,
|
||||
width: 600,
|
||||
height: 900
|
||||
},
|
||||
window: {
|
||||
resizable: true
|
||||
},
|
||||
actions: {
|
||||
openDocument: Party.#openDocument,
|
||||
deletePartyMember: Party.#deletePartyMember,
|
||||
deleteItem: Party.#deleteItem,
|
||||
toggleHope: Party.#toggleHope,
|
||||
|
|
@ -45,10 +45,6 @@ export default class Party extends DHBaseActorSheet {
|
|||
header: { template: 'systems/daggerheart/templates/sheets/actors/party/header.hbs' },
|
||||
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
|
||||
partyMembers: { template: 'systems/daggerheart/templates/sheets/actors/party/party-members.hbs' },
|
||||
resources: {
|
||||
template: 'systems/daggerheart/templates/sheets/actors/party/resources.hbs',
|
||||
scrollable: ['']
|
||||
},
|
||||
/* NOT YET IMPLEMENTED */
|
||||
// projects: {
|
||||
// template: 'systems/daggerheart/templates/sheets/actors/party/projects.hbs',
|
||||
|
|
@ -66,7 +62,6 @@ export default class Party extends DHBaseActorSheet {
|
|||
primary: {
|
||||
tabs: [
|
||||
{ id: 'partyMembers' },
|
||||
{ id: 'resources' },
|
||||
/* NOT YET IMPLEMENTED */
|
||||
// { id: 'projects' },
|
||||
{ id: 'inventory' },
|
||||
|
|
@ -96,6 +91,8 @@ export default class Party extends DHBaseActorSheet {
|
|||
case 'header':
|
||||
await this._prepareHeaderContext(context, options);
|
||||
break;
|
||||
case 'partyMembers':
|
||||
await this._prepareMembersContext(context, options);
|
||||
case 'notes':
|
||||
await this._prepareNotesContext(context, options);
|
||||
break;
|
||||
|
|
@ -119,6 +116,60 @@ export default class Party extends DHBaseActorSheet {
|
|||
relativeTo: this.document
|
||||
});
|
||||
context.tagTeamActive = Boolean(this.document.system.tagTeam.initiator);
|
||||
context.groupRollActive = Boolean(this.document.system.groupRoll.leader);
|
||||
}
|
||||
|
||||
async _prepareMembersContext(context, _options) {
|
||||
context.partyMembers = [];
|
||||
const traits = ['agility', 'strength', 'finesse', 'instinct', 'presence', 'knowledge'];
|
||||
for (const actor of this.document.system.partyMembers) {
|
||||
const weapons = [];
|
||||
if (actor.type === 'character') {
|
||||
if (actor.system.usedUnarmed) {
|
||||
weapons.push(actor.system.usedUnarmed);
|
||||
}
|
||||
const equipped = actor.items.filter(i => i.system.equipped && i.type === 'weapon');
|
||||
weapons.push(...sortBy(equipped, i => (i.system.secondary ? 1 : 0)));
|
||||
}
|
||||
|
||||
context.partyMembers.push({
|
||||
uuid: actor.uuid,
|
||||
img: actor.img,
|
||||
name: actor.name,
|
||||
subtitle: (() => {
|
||||
if (!['character', 'companion'].includes(actor.type)) {
|
||||
return game.i18n.format(`TYPES.Actor.${actor.type}`);
|
||||
}
|
||||
|
||||
const { value: classItem, subclass } = actor.system.class ?? {};
|
||||
const partner = actor.system.partner;
|
||||
const ancestry = actor.system.ancestry;
|
||||
const community = actor.system.community;
|
||||
if (partner || (classItem && subclass && ancestry && community)) {
|
||||
return game.i18n.format(`DAGGERHEART.ACTORS.Party.Subtitle.${actor.type}`, {
|
||||
class: classItem?.name,
|
||||
subclass: subclass?.name,
|
||||
partner: partner?.name,
|
||||
ancestry: ancestry?.name,
|
||||
community: community?.name
|
||||
});
|
||||
}
|
||||
})(),
|
||||
type: actor.type,
|
||||
resources: actor.system.resources,
|
||||
armorScore: actor.system.armorScore,
|
||||
damageThresholds: actor.system.damageThresholds,
|
||||
evasion: actor.system.evasion,
|
||||
difficulty: actor.system.difficulty,
|
||||
traits: actor.system.traits
|
||||
? traits.map(t => ({
|
||||
label: game.i18n.localize(`DAGGERHEART.CONFIG.Traits.${t}.short`),
|
||||
value: actor.system.traits[t].value
|
||||
}))
|
||||
: null,
|
||||
weapons
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -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.
|
||||
* @type {ApplicationClickAction}
|
||||
|
|
@ -190,7 +247,7 @@ export default class Party extends DHBaseActorSheet {
|
|||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
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 inputValue = Number.parseInt(target.dataset.value);
|
||||
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`),
|
||||
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']
|
||||
});
|
||||
|
||||
|
|
@ -261,9 +318,7 @@ export default class Party extends DHBaseActorSheet {
|
|||
}
|
||||
|
||||
static async #groupRoll(_params) {
|
||||
new GroupRollDialog(
|
||||
this.document.system.partyMembers.filter(x => Party.DICE_ROLL_ACTOR_TYPES.includes(x.type))
|
||||
).render({ force: true });
|
||||
new game.system.api.applications.dialogs.GroupRollDialog(this.document).render({ force: true });
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
|
@ -425,25 +480,23 @@ export default class Party extends DHBaseActorSheet {
|
|||
}
|
||||
|
||||
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) {
|
||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||
window: {
|
||||
title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', {
|
||||
type: game.i18n.localize('TYPES.Actor.adversary'),
|
||||
title: game.i18n.format('DAGGERHEART.ACTORS.Party.RemoveConfirmation.title', {
|
||||
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;
|
||||
}
|
||||
|
||||
const currentMembers = this.document.system.partyMembers.map(x => x.uuid);
|
||||
const newMemberdList = currentMembers.filter(uuid => uuid !== doc.uuid);
|
||||
await this.document.update({ 'system.partyMembers': newMemberdList });
|
||||
const newMembersList = currentMembers.filter(uuid => uuid !== doc.uuid);
|
||||
await this.document.update({ 'system.partyMembers': newMembersList });
|
||||
}
|
||||
|
||||
static async #deleteItem(event, target) {
|
||||
|
|
|
|||
|
|
@ -418,18 +418,18 @@ export default function DHApplicationMixin(Base) {
|
|||
/**@type {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} */
|
||||
const options = [
|
||||
{
|
||||
name: 'disableEffect',
|
||||
label: 'disableEffect',
|
||||
icon: 'fa-solid fa-lightbulb',
|
||||
condition: element => {
|
||||
visible: element => {
|
||||
const target = element.closest('[data-item-uuid]');
|
||||
return !target.dataset.disabled && target.dataset.itemType !== 'beastform';
|
||||
},
|
||||
callback: async target => (await getDocFromElement(target)).update({ disabled: true })
|
||||
},
|
||||
{
|
||||
name: 'enableEffect',
|
||||
label: 'enableEffect',
|
||||
icon: 'fa-regular fa-lightbulb',
|
||||
condition: element => {
|
||||
visible: element => {
|
||||
const target = element.closest('[data-item-uuid]');
|
||||
return target.dataset.disabled && target.dataset.itemType !== 'beastform';
|
||||
},
|
||||
|
|
@ -437,7 +437,7 @@ export default function DHApplicationMixin(Base) {
|
|||
}
|
||||
].map(option => ({
|
||||
...option,
|
||||
name: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.name}`,
|
||||
label: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.label}`,
|
||||
icon: `<i class="${option.icon}"></i>`
|
||||
}));
|
||||
|
||||
|
|
@ -468,14 +468,14 @@ export default function DHApplicationMixin(Base) {
|
|||
_getContextMenuCommonOptions({ usable = false, toChat = false, deletable = true }) {
|
||||
const options = [
|
||||
{
|
||||
name: 'CONTROLS.CommonEdit',
|
||||
label: 'CONTROLS.CommonEdit',
|
||||
icon: 'fa-solid fa-pen-to-square',
|
||||
condition: target => {
|
||||
visible: target => {
|
||||
const { dataset } = target.closest('[data-item-uuid]');
|
||||
const doc = getDocFromElementSync(target);
|
||||
return (
|
||||
(!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 })
|
||||
|
|
@ -484,14 +484,14 @@ export default function DHApplicationMixin(Base) {
|
|||
|
||||
if (usable) {
|
||||
options.unshift({
|
||||
name: 'DAGGERHEART.GENERAL.damage',
|
||||
label: 'DAGGERHEART.GENERAL.damage',
|
||||
icon: 'fa-solid fa-explosion',
|
||||
condition: target => {
|
||||
visible: target => {
|
||||
const doc = getDocFromElementSync(target);
|
||||
return (
|
||||
const hasDamage =
|
||||
!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) => {
|
||||
const doc = await getDocFromElement(target),
|
||||
|
|
@ -507,11 +507,11 @@ export default function DHApplicationMixin(Base) {
|
|||
});
|
||||
|
||||
options.unshift({
|
||||
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.useItem',
|
||||
label: 'DAGGERHEART.APPLICATIONS.ContextMenu.useItem',
|
||||
icon: 'fa-solid fa-burst',
|
||||
condition: target => {
|
||||
visible: 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)
|
||||
});
|
||||
|
|
@ -519,18 +519,19 @@ export default function DHApplicationMixin(Base) {
|
|||
|
||||
if (toChat)
|
||||
options.push({
|
||||
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.sendToChat',
|
||||
label: 'DAGGERHEART.APPLICATIONS.ContextMenu.sendToChat',
|
||||
icon: 'fa-solid fa-message',
|
||||
callback: async target => (await getDocFromElement(target)).toChat(this.document.uuid)
|
||||
});
|
||||
|
||||
if (deletable)
|
||||
options.push({
|
||||
name: 'CONTROLS.CommonDelete',
|
||||
label: 'CONTROLS.CommonDelete',
|
||||
icon: 'fa-solid fa-trash',
|
||||
condition: element => {
|
||||
visible: element => {
|
||||
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) => {
|
||||
const doc = await getDocFromElement(target);
|
||||
|
|
@ -644,12 +645,12 @@ export default function DHApplicationMixin(Base) {
|
|||
buttons: [
|
||||
{
|
||||
action: 'create',
|
||||
label: 'Create Item',
|
||||
label: game.i18n.localize('DAGGERHEART.APPLICATIONS.CreateItemDialog.createItem'),
|
||||
icon: 'fa-solid fa-plus'
|
||||
},
|
||||
{
|
||||
action: 'browse',
|
||||
label: 'Browse Compendium',
|
||||
label: game.i18n.localize('DAGGERHEART.APPLICATIONS.CreateItemDialog.browseCompendium'),
|
||||
icon: 'fa-solid fa-book'
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
|||
.hideAttribution;
|
||||
|
||||
// Prepare inventory data
|
||||
if (['party', 'character'].includes(this.document.type)) {
|
||||
if (this.document.system.metadata.hasInventory) {
|
||||
context.inventory = {
|
||||
currencies: {},
|
||||
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) {
|
||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||
const originActor = item.actor;
|
||||
if (
|
||||
item.actor?.uuid === this.document.uuid ||
|
||||
!originActor ||
|
||||
!['character', 'party'].includes(this.document.type)
|
||||
) {
|
||||
if (!originActor || originActor.uuid === this.document.uuid || !this.document.system.metadata.hasInventory) {
|
||||
return super._onDropItem(event, item);
|
||||
}
|
||||
|
||||
|
|
@ -302,47 +298,79 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
|||
);
|
||||
}
|
||||
|
||||
if (item.system.metadata.isQuantifiable) {
|
||||
const actorItem = originActor.items.get(data.originId);
|
||||
const quantityTransfered = await game.system.api.applications.dialogs.ItemTransferDialog.configure({
|
||||
// Perform the actual transfer, showing a dialog when doing it
|
||||
const availableQuantity = Math.max(1, item.system.quantity);
|
||||
const actorItem = originActor.items.get(data.originId) ?? item;
|
||||
if (availableQuantity > 1) {
|
||||
const quantityTransferred = await game.system.api.applications.dialogs.ItemTransferDialog.configure({
|
||||
item,
|
||||
targetActor: this.document
|
||||
});
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
}
|
||||
return this.#transferItem(actorItem, quantityTransferred);
|
||||
} else {
|
||||
await this.document.createEmbeddedDocuments('Item', [item.toObject()]);
|
||||
await originActor.deleteEmbeddedDocuments('Item', [data.originId]);
|
||||
return this.#transferItem(actorItem, availableQuantity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to perform the actual transfer of an item to this actor, including stack/unstack logic based on target quantifiability.
|
||||
* Make sure item is the actor item before calling this method or there will be issues
|
||||
*/
|
||||
async #transferItem(item, quantity) {
|
||||
const originActor = item.actor;
|
||||
const targetActor = this.document;
|
||||
const allowStacking = targetActor.system.metadata.quantifiable?.includes(item.type);
|
||||
|
||||
const batch = [];
|
||||
|
||||
// First add/update the item to the target actor
|
||||
const existing = allowStacking ? targetActor.items.find(x => itemIsIdentical(x, item)) : null;
|
||||
if (existing) {
|
||||
batch.push({
|
||||
action: 'update',
|
||||
documentName: 'Item',
|
||||
parent: targetActor,
|
||||
updates: [{ '_id': existing.id, 'system.quantity': existing.system.quantity + quantity }]
|
||||
});
|
||||
} else {
|
||||
const itemsToCreate = [];
|
||||
if (allowStacking) {
|
||||
itemsToCreate.push(foundry.utils.mergeObject(item.toObject(true), { system: { quantity } }));
|
||||
} else {
|
||||
const createData = new Array(Math.max(1, quantity))
|
||||
.fill(0)
|
||||
.map(() => foundry.utils.mergeObject(item.toObject(), { system: { quantity: 1 } }));
|
||||
itemsToCreate.push(...createData);
|
||||
}
|
||||
batch.push({
|
||||
action: 'create',
|
||||
documentName: 'Item',
|
||||
parent: targetActor,
|
||||
data: itemsToCreate
|
||||
});
|
||||
}
|
||||
|
||||
// Remove the item from the original actor (by either deleting it, or updating its quantity)
|
||||
if (quantity >= item.system.quantity) {
|
||||
batch.push({
|
||||
action: 'delete',
|
||||
documentName: 'Item',
|
||||
parent: originActor,
|
||||
ids: [item.id]
|
||||
});
|
||||
} else {
|
||||
batch.push({
|
||||
action: 'update',
|
||||
documentName: 'Item',
|
||||
parent: originActor,
|
||||
updates: [{ '_id': item.id, 'system.quantity': item.system.quantity - quantity }]
|
||||
});
|
||||
}
|
||||
|
||||
return foundry.documents.modifyBatch(batch);
|
||||
}
|
||||
|
||||
/**
|
||||
* On dragStart on the item.
|
||||
* @param {DragEvent} event - The drag event
|
||||
|
|
|
|||
|
|
@ -31,11 +31,12 @@ export default class AncestrySheet extends DHHeritageSheet {
|
|||
if (data.type === 'ActiveEffect') return super._onDrop(event);
|
||||
|
||||
const target = event.target.closest('fieldset.drop-section');
|
||||
const typeField =
|
||||
this.document.system[target.dataset.type === 'primary' ? 'primaryFeature' : 'secondaryFeature'];
|
||||
|
||||
if (!typeField) {
|
||||
super._onDrop(event);
|
||||
if (target) {
|
||||
const typeField =
|
||||
this.document.system[target.dataset.type === 'primary' ? 'primaryFeature' : 'secondaryFeature'];
|
||||
if (!typeField) {
|
||||
super._onDrop(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ export default class FeatureSheet extends DHBaseItemSheet {
|
|||
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
||||
}
|
||||
};
|
||||
//Might be wrong location but testing out if here is okay.
|
||||
//Might be wrong location but testing out if here is okay.
|
||||
/**@override */
|
||||
async _prepareContext(options) {
|
||||
const context = await super._prepareContext(options);
|
||||
|
|
|
|||
|
|
@ -46,50 +46,67 @@ export default class DhActorDirectory extends foundry.applications.sidebar.tabs.
|
|||
|
||||
_getEntryContextOptions() {
|
||||
const options = super._getEntryContextOptions();
|
||||
options.push({
|
||||
name: 'DAGGERHEART.UI.Sidebar.actorDirectory.duplicateToNewTier',
|
||||
icon: `<i class="fa-solid fa-arrow-trend-up" inert></i>`,
|
||||
condition: li => {
|
||||
const actor = game.actors.get(li.dataset.entryId);
|
||||
return actor?.type === 'adversary' && actor.system.type !== 'social';
|
||||
},
|
||||
callback: async li => {
|
||||
const actor = game.actors.get(li.dataset.entryId);
|
||||
if (!actor) throw new Error('Unexpected missing actor');
|
||||
options.push(
|
||||
{
|
||||
label: 'DAGGERHEART.UI.Sidebar.actorDirectory.duplicateToNewTier',
|
||||
icon: `<i class="fa-solid fa-arrow-trend-up" inert></i>`,
|
||||
visible: li => {
|
||||
const actor = game.actors.get(li.dataset.entryId);
|
||||
return actor?.type === 'adversary' && actor.system.type !== 'social';
|
||||
},
|
||||
callback: async li => {
|
||||
const actor = game.actors.get(li.dataset.entryId);
|
||||
if (!actor) throw new Error('Unexpected missing actor');
|
||||
|
||||
const tiers = [1, 2, 3, 4].filter(t => t !== actor.system.tier);
|
||||
const content = document.createElement('div');
|
||||
const select = document.createElement('select');
|
||||
select.name = 'tier';
|
||||
select.append(
|
||||
...tiers.map(t => {
|
||||
const option = document.createElement('option');
|
||||
option.value = t;
|
||||
option.textContent = game.i18n.localize(`DAGGERHEART.GENERAL.Tiers.${t}`);
|
||||
return option;
|
||||
})
|
||||
);
|
||||
content.append(select);
|
||||
const tiers = [1, 2, 3, 4].filter(t => t !== actor.system.tier);
|
||||
const content = document.createElement('div');
|
||||
const select = document.createElement('select');
|
||||
select.name = 'tier';
|
||||
select.append(
|
||||
...tiers.map(t => {
|
||||
const option = document.createElement('option');
|
||||
option.value = t;
|
||||
option.textContent = game.i18n.localize(`DAGGERHEART.GENERAL.Tiers.${t}`);
|
||||
return option;
|
||||
})
|
||||
);
|
||||
content.append(select);
|
||||
|
||||
const tier = await foundry.applications.api.Dialog.input({
|
||||
classes: ['dh-style', 'dialog'],
|
||||
window: { title: 'DAGGERHEART.UI.Sidebar.actorDirectory.pickTierTitle' },
|
||||
content,
|
||||
ok: {
|
||||
label: 'Create Adversary',
|
||||
callback: (event, button, dialog) => Number(button.form.elements.tier.value)
|
||||
const tier = await foundry.applications.api.Dialog.input({
|
||||
classes: ['dh-style', 'dialog'],
|
||||
window: { title: 'DAGGERHEART.UI.Sidebar.actorDirectory.pickTierTitle' },
|
||||
content,
|
||||
ok: {
|
||||
label: 'DAGGERHEART.UI.Sidebar.actorDirectory.createAdversary',
|
||||
callback: (event, button, dialog) => Number(button.form.elements.tier.value)
|
||||
}
|
||||
});
|
||||
|
||||
if (tier === actor.system.tier) {
|
||||
ui.notifications.warn('This actor is already at this tier');
|
||||
} else if (tier) {
|
||||
const source = actor.system.adjustForTier(tier);
|
||||
await Actor.create(source);
|
||||
ui.notifications.info(`Tier ${tier} ${actor.name} created`);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
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) {
|
||||
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`);
|
||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.ActiveParty, actor.id);
|
||||
ui.actors.render();
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
return options;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
import { abilities } from '../../config/actorConfig.mjs';
|
||||
import { enrichedDualityRoll } from '../../enrichers/DualityRollEnricher.mjs';
|
||||
import { enrichedFateRoll, getFateTypeData } from '../../enrichers/FateRollEnricher.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 {
|
||||
constructor(options) {
|
||||
|
|
@ -105,23 +103,10 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
_getEntryContextOptions() {
|
||||
return [
|
||||
...super._getEntryContextOptions(),
|
||||
// {
|
||||
// name: 'Reroll',
|
||||
// icon: '<i class="fa-solid fa-dice"></i>',
|
||||
// condition: li => {
|
||||
// const message = game.messages.get(li.dataset.messageId);
|
||||
|
||||
// return (game.user.isGM || message.isAuthor) && message.rolls.length > 0;
|
||||
// },
|
||||
// callback: li => {
|
||||
// const message = game.messages.get(li.dataset.messageId);
|
||||
// new game.system.api.applications.dialogs.RerollDialog(message).render({ force: true });
|
||||
// }
|
||||
// },
|
||||
{
|
||||
name: game.i18n.localize('DAGGERHEART.UI.ChatLog.rerollDamage'),
|
||||
label: 'DAGGERHEART.UI.ChatLog.rerollDamage',
|
||||
icon: '<i class="fa-solid fa-dice"></i>',
|
||||
condition: li => {
|
||||
visible: li => {
|
||||
const message = game.messages.get(li.dataset.messageId);
|
||||
const hasRolledDamage = message.system.hasDamage
|
||||
? 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 =>
|
||||
element.addEventListener('click', event => this.rerollEvent(event, message))
|
||||
);
|
||||
html.querySelectorAll('.group-roll-button').forEach(element =>
|
||||
element.addEventListener('click', event => this.groupRollButton(event, message))
|
||||
);
|
||||
html.querySelectorAll('.group-roll-reroll').forEach(element =>
|
||||
element.addEventListener('click', event => this.groupRollReroll(event, message))
|
||||
);
|
||||
html.querySelectorAll('.group-roll-success').forEach(element =>
|
||||
element.addEventListener('click', event => this.groupRollSuccessEvent(event, message))
|
||||
);
|
||||
html.querySelectorAll('.group-roll-header-expand-section').forEach(element =>
|
||||
element.addEventListener('click', this.groupRollExpandSection)
|
||||
);
|
||||
html.querySelectorAll('.risk-it-all-button').forEach(element =>
|
||||
element.addEventListener('click', event => this.riskItAllClearStressAndHitPoints(event, data))
|
||||
);
|
||||
|
|
@ -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) {
|
||||
const resourceValue = event.target.dataset.resourceValue;
|
||||
const actor = game.actors.get(event.target.dataset.actorId);
|
||||
|
|
|
|||
|
|
@ -84,15 +84,15 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
|||
_getCombatContextOptions() {
|
||||
return [
|
||||
{
|
||||
name: 'COMBAT.ClearMovementHistories',
|
||||
label: 'COMBAT.ClearMovementHistories',
|
||||
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()
|
||||
},
|
||||
{
|
||||
name: 'COMBAT.Delete',
|
||||
label: 'COMBAT.Delete',
|
||||
icon: '<i class="fa-solid fa-trash"></i>',
|
||||
condition: () => game.user.isGM && !!this.viewed,
|
||||
visible: () => game.user.isGM && !!this.viewed,
|
||||
callback: () => this.viewed.endCombat()
|
||||
}
|
||||
];
|
||||
|
|
|
|||
|
|
@ -137,6 +137,8 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
|||
}
|
||||
|
||||
static #getPlayerOwnership(user, setting, countdown) {
|
||||
if (user.isGM) return CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER;
|
||||
|
||||
const playerOwnership = countdown.ownership[user.id];
|
||||
return playerOwnership === undefined || playerOwnership === CONST.DOCUMENT_OWNERSHIP_LEVELS.INHERIT
|
||||
? setting.defaultOwnership
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV
|
|||
tag: 'div',
|
||||
window: {
|
||||
frame: true,
|
||||
title: 'Fear',
|
||||
title: 'DAGGERHEART.GENERAL.fear',
|
||||
positioned: true,
|
||||
resizable: true,
|
||||
minimizable: false
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
tag: 'div',
|
||||
window: {
|
||||
frame: true,
|
||||
title: 'Compendium Browser',
|
||||
title: 'DAGGERHEART.UI.ItemBrowser.windowTitle',
|
||||
icon: 'fa-solid fa-book-atlas',
|
||||
positioned: true,
|
||||
resizable: true
|
||||
|
|
@ -207,8 +207,23 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
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 = {
|
||||
path: folderId?.split('.') ?? [],
|
||||
path: splitPath,
|
||||
pathLabels: pathLabels,
|
||||
data: {
|
||||
...folderData,
|
||||
columns: columns
|
||||
|
|
@ -568,7 +583,9 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
const { itemUuid } = event.target.closest('[data-item-uuid]').dataset,
|
||||
item = await foundry.utils.fromUuid(itemUuid),
|
||||
dragData = item.toDragData();
|
||||
|
||||
event.dataTransfer.setData('text/plain', JSON.stringify(dragData));
|
||||
event.dataTransfer.setDragImage(event.target.querySelector('img'), 0, 0);
|
||||
}
|
||||
|
||||
_canDragStart() {
|
||||
|
|
|
|||
|
|
@ -188,7 +188,7 @@ export default class FilterMenu extends foundry.applications.ux.ContextMenu {
|
|||
}));
|
||||
|
||||
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),
|
||||
filter: {
|
||||
field: 'system.damage.type',
|
||||
|
|
|
|||
|
|
@ -1,16 +1 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
export default class DhTokenLayer extends foundry.canvas.layers.TokenLayer {}
|
||||
|
|
|
|||
|
|
@ -70,6 +70,14 @@ export const range = {
|
|||
}
|
||||
};
|
||||
|
||||
export const groupAttackRange = {
|
||||
melee: range.melee,
|
||||
veryClose: range.veryClose,
|
||||
close: range.close,
|
||||
far: range.far,
|
||||
veryFar: range.veryFar
|
||||
};
|
||||
|
||||
/* circle|cone|rect|ray used to be CONST.MEASURED_TEMPLATE_TYPES. Hardcoded for now */
|
||||
export const templateTypes = {
|
||||
CIRCLE: 'circle',
|
||||
|
|
@ -484,7 +492,7 @@ export const defaultRestOptions = {
|
|||
value: {
|
||||
custom: {
|
||||
enabled: true,
|
||||
formula: '@system.armorScore'
|
||||
formula: '@system.armorScore.max'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -708,14 +716,14 @@ const getDiceSoNiceSFX = sfxOptions => {
|
|||
if (sfxOptions.critical && criticalAnimationData.class) {
|
||||
return {
|
||||
specialEffect: criticalAnimationData.class,
|
||||
options: {}
|
||||
options: { ...criticalAnimationData.options }
|
||||
};
|
||||
}
|
||||
|
||||
if (sfxOptions.higher && sfxOptions.data.higher) {
|
||||
return {
|
||||
specialEffect: sfxOptions.data.higher.class,
|
||||
options: {}
|
||||
options: { ...sfxOptions.data.higher.options }
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -947,15 +955,15 @@ export const countdownAppMode = {
|
|||
export const sceneRangeMeasurementSetting = {
|
||||
disable: {
|
||||
id: 'disable',
|
||||
label: 'Disable Daggerheart Range Measurement'
|
||||
label: 'DAGGERHEART.CONFIG.SceneRangeMeasurementTypes.disable'
|
||||
},
|
||||
default: {
|
||||
id: 'default',
|
||||
label: 'Default'
|
||||
label: 'DAGGERHEART.CONFIG.SceneRangeMeasurementTypes.default'
|
||||
},
|
||||
custom: {
|
||||
id: 'custom',
|
||||
label: 'Custom'
|
||||
label: 'DAGGERHEART.CONFIG.SceneRangeMeasurementTypes.custom'
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
export const hooksConfig = {
|
||||
effectDisplayToggle: 'DHEffectDisplayToggle',
|
||||
lockedTooltipDismissed: 'DHLockedTooltipDismissed',
|
||||
tagTeamStart: 'DHTagTeamRollStart'
|
||||
tagTeamStart: 'DHTagTeamRollStart',
|
||||
groupRollStart: 'DHGroupRollStart'
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,7 +7,12 @@ export const typeConfig = {
|
|||
},
|
||||
{
|
||||
key: 'system.type',
|
||||
label: 'DAGGERHEART.GENERAL.type'
|
||||
label: 'DAGGERHEART.GENERAL.type',
|
||||
format: type => {
|
||||
if (!type) return '-';
|
||||
|
||||
return CONFIG.DH.ACTOR.allAdversaryTypes()[type].label;
|
||||
}
|
||||
}
|
||||
],
|
||||
filters: [
|
||||
|
|
@ -69,12 +74,18 @@ export const typeConfig = {
|
|||
columns: [
|
||||
{
|
||||
key: 'type',
|
||||
label: 'DAGGERHEART.GENERAL.type'
|
||||
label: 'DAGGERHEART.GENERAL.type',
|
||||
format: type => (type ? `TYPES.Item.${type}` : '-')
|
||||
},
|
||||
{
|
||||
key: 'system.secondary',
|
||||
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',
|
||||
|
|
@ -94,8 +105,8 @@ export const typeConfig = {
|
|||
key: 'system.secondary',
|
||||
label: 'DAGGERHEART.UI.ItemBrowser.subtype',
|
||||
choices: [
|
||||
{ value: false, label: 'DAGGERHEART.ITEMS.Weapon.primaryWeapon' },
|
||||
{ value: true, label: 'DAGGERHEART.ITEMS.Weapon.secondaryWeapon' }
|
||||
{ value: false, label: 'DAGGERHEART.ITEMS.Weapon.primaryWeapon.full' },
|
||||
{ value: true, label: 'DAGGERHEART.ITEMS.Weapon.secondaryWeapon.full' }
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -253,11 +264,13 @@ export const typeConfig = {
|
|||
columns: [
|
||||
{
|
||||
key: 'system.type',
|
||||
label: 'DAGGERHEART.GENERAL.type'
|
||||
label: 'DAGGERHEART.GENERAL.type',
|
||||
format: type => (type ? `DAGGERHEART.CONFIG.DomainCardTypes.${type}` : '-')
|
||||
},
|
||||
{
|
||||
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',
|
||||
|
|
@ -318,7 +331,14 @@ export const typeConfig = {
|
|||
},
|
||||
{
|
||||
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: [
|
||||
|
|
@ -362,18 +382,19 @@ export const typeConfig = {
|
|||
columns: [
|
||||
{
|
||||
key: 'system.linkedClass',
|
||||
label: 'Class',
|
||||
label: 'TYPES.Item.class',
|
||||
format: linkedClass => linkedClass?.name ?? 'DAGGERHEART.UI.ItemBrowser.missing'
|
||||
},
|
||||
{
|
||||
key: 'system.spellcastingTrait',
|
||||
label: 'DAGGERHEART.ITEMS.Subclass.spellcastingTrait'
|
||||
label: 'DAGGERHEART.ITEMS.Subclass.spellcastingTrait',
|
||||
format: trait => (trait ? `DAGGERHEART.CONFIG.Traits.${trait}.name` : '-')
|
||||
}
|
||||
],
|
||||
filters: [
|
||||
{
|
||||
key: 'system.linkedClass.uuid',
|
||||
label: 'Class',
|
||||
label: 'TYPES.Item.class',
|
||||
choices: items => {
|
||||
const list = items
|
||||
.filter(item => item.system.linkedClass)
|
||||
|
|
@ -397,7 +418,8 @@ export const typeConfig = {
|
|||
},
|
||||
{
|
||||
key: 'system.mainTrait',
|
||||
label: 'DAGGERHEART.GENERAL.Trait.single'
|
||||
label: 'DAGGERHEART.GENERAL.Trait.single',
|
||||
format: trait => (trait ? `DAGGERHEART.CONFIG.Traits.${trait}.name` : '-')
|
||||
}
|
||||
],
|
||||
filters: [
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
export const keybindings = {
|
||||
spotlight: 'DHSpotlight'
|
||||
spotlight: 'DHSpotlight',
|
||||
partySheet: 'DHPartySheet'
|
||||
};
|
||||
|
||||
export const menu = {
|
||||
|
|
@ -40,24 +41,21 @@ export const gameSettings = {
|
|||
LastMigrationVersion: 'LastMigrationVersion',
|
||||
SpotlightRequestQueue: 'SpotlightRequestQueue',
|
||||
CompendiumBrowserSettings: 'CompendiumBrowserSettings',
|
||||
SpotlightTracker: 'SpotlightTracker'
|
||||
SpotlightTracker: 'SpotlightTracker',
|
||||
ActiveParty: 'ActiveParty'
|
||||
};
|
||||
|
||||
export const actionAutomationChoices = {
|
||||
never: {
|
||||
id: 'never',
|
||||
label: 'Never'
|
||||
label: 'DAGGERHEART.CONFIG.ActionAutomationChoices.never'
|
||||
},
|
||||
showDialog: {
|
||||
id: 'showDialog',
|
||||
label: 'Show Dialog only'
|
||||
label: 'DAGGERHEART.CONFIG.ActionAutomationChoices.showDialog'
|
||||
},
|
||||
// npcOnly: {
|
||||
// id: "npcOnly",
|
||||
// label: "Always for non-characters"
|
||||
// },
|
||||
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 CompendiumBrowserSettings } from './compendiumBrowserSettings.mjs';
|
||||
export { default as TagTeamData } from './tagTeamData.mjs';
|
||||
export { default as GroupRollData } from './groupRollData.mjs';
|
||||
export { default as SpotlightTracker } from './spotlightTracker.mjs';
|
||||
|
||||
export * as countdowns from './countdowns.mjs';
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export default class DHAttackAction extends DHDamageAction {
|
|||
if (!!this.item?.system?.attack) {
|
||||
if (this.damage.includeBase) {
|
||||
const baseDamage = this.getParentDamage();
|
||||
this.damage.parts.unshift(new DHDamageData(baseDamage));
|
||||
this.damage.parts.hitPoints = new DHDamageData(baseDamage);
|
||||
}
|
||||
if (this.roll.useDefault) {
|
||||
this.roll.trait = this.item.system.attack.roll.trait;
|
||||
|
|
@ -51,7 +51,7 @@ export default class DHAttackAction extends DHDamageAction {
|
|||
async 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;
|
||||
await updateCountdowns(CONFIG.DH.GENERAL.countdownProgressionTypes.characterAttack.id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,6 +110,11 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
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.
|
||||
*/
|
||||
|
|
@ -143,6 +148,12 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
: null;
|
||||
}
|
||||
|
||||
/** Returns true if the action is usable */
|
||||
get usable() {
|
||||
const actor = this.actor;
|
||||
return this.isOwner && actor?.type === 'character';
|
||||
}
|
||||
|
||||
static getRollType(parent) {
|
||||
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);
|
||||
return config;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,7 +44,8 @@ export default class ArmorChange extends foundry.abstract.DataModel {
|
|||
label: 'Armor',
|
||||
defaultPriority: 20,
|
||||
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(
|
||||
actor,
|
||||
{
|
||||
|
|
@ -81,7 +82,7 @@ export default class ArmorChange extends foundry.abstract.DataModel {
|
|||
{
|
||||
...change,
|
||||
key: 'system.damageThresholds.major',
|
||||
type: CONFIG.DH.GENERAL.activeEffectModes.override.id,
|
||||
type: CONFIG.DH.GENERAL.activeEffectModes.add.id,
|
||||
priority: 50,
|
||||
value: major
|
||||
},
|
||||
|
|
@ -95,7 +96,7 @@ export default class ArmorChange extends foundry.abstract.DataModel {
|
|||
{
|
||||
...change,
|
||||
key: 'system.damageThresholds.severe',
|
||||
type: CONFIG.DH.GENERAL.activeEffectModes.override.id,
|
||||
type: CONFIG.DH.GENERAL.activeEffectModes.add.id,
|
||||
priority: 50,
|
||||
value: severe
|
||||
},
|
||||
|
|
@ -110,6 +111,8 @@ export default class ArmorChange extends foundry.abstract.DataModel {
|
|||
};
|
||||
|
||||
get isSuppressed() {
|
||||
if (!this.parent.parent?.actor) return false;
|
||||
|
||||
switch (this.value.interaction) {
|
||||
case CONFIG.DH.GENERAL.activeEffectArmorInteraction.active.id:
|
||||
return !this.parent.parent?.actor.system.armor;
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ export default class DhpAdversary extends DhCreature {
|
|||
}
|
||||
|
||||
isItemValid(source) {
|
||||
return source.type === 'feature';
|
||||
return super.isItemValid(source) || source.type === 'feature';
|
||||
}
|
||||
|
||||
async _preUpdate(changes, options, user) {
|
||||
|
|
|
|||
|
|
@ -107,7 +107,8 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
|
|||
hasResistances: true,
|
||||
hasAttribution: false,
|
||||
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
|
||||
* @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 { commonActorRules } from './base.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 DHCharacterSettings from '../../applications/sheets-configs/character-settings.mjs';
|
||||
import { getArmorSources } from '../../helpers/utils.mjs';
|
||||
|
|
@ -18,7 +18,9 @@ export default class DhCharacter extends DhCreature {
|
|||
label: 'TYPES.Actor.character',
|
||||
type: 'character',
|
||||
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 })
|
||||
})
|
||||
),
|
||||
gold: new fields.SchemaField({
|
||||
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 })
|
||||
}),
|
||||
gold: new GoldField(),
|
||||
scars: new fields.NumberField({ initial: 0, integer: true, label: 'DAGGERHEART.GENERAL.scars' }),
|
||||
biography: new fields.SchemaField({
|
||||
background: new fields.HTMLField(),
|
||||
|
|
@ -153,7 +150,6 @@ export default class DhCharacter extends DhCreature {
|
|||
shortMoves: new fields.NumberField({
|
||||
required: true,
|
||||
integer: true,
|
||||
min: 0,
|
||||
initial: 0,
|
||||
label: 'DAGGERHEART.GENERAL.Bonuses.rest.shortRest.shortRestMoves.label',
|
||||
hint: 'DAGGERHEART.GENERAL.Bonuses.rest.shortRest.shortRestMoves.hint'
|
||||
|
|
@ -161,7 +157,6 @@ export default class DhCharacter extends DhCreature {
|
|||
longMoves: new fields.NumberField({
|
||||
required: true,
|
||||
integer: true,
|
||||
min: 0,
|
||||
initial: 0,
|
||||
label: 'DAGGERHEART.GENERAL.Bonuses.rest.shortRest.longRestMoves.label',
|
||||
hint: 'DAGGERHEART.GENERAL.Bonuses.rest.shortRest.longRestMoves.hint'
|
||||
|
|
@ -171,7 +166,6 @@ export default class DhCharacter extends DhCreature {
|
|||
shortMoves: new fields.NumberField({
|
||||
required: true,
|
||||
integer: true,
|
||||
min: 0,
|
||||
initial: 0,
|
||||
label: 'DAGGERHEART.GENERAL.Bonuses.rest.longRest.shortRestMoves.label',
|
||||
hint: 'DAGGERHEART.GENERAL.Bonuses.rest.longRest.shortRestMoves.hint'
|
||||
|
|
@ -179,7 +173,6 @@ export default class DhCharacter extends DhCreature {
|
|||
longMoves: new fields.NumberField({
|
||||
required: true,
|
||||
integer: true,
|
||||
min: 0,
|
||||
initial: 0,
|
||||
label: 'DAGGERHEART.GENERAL.Bonuses.rest.longRest.longRestMoves.label',
|
||||
hint: 'DAGGERHEART.GENERAL.Bonuses.rest.longRest.longRestMoves.hint'
|
||||
|
|
@ -293,6 +286,22 @@ export default class DhCharacter extends DhCreature {
|
|||
guaranteedCritical: new fields.BooleanField({
|
||||
label: 'DAGGERHEART.ACTORS.Character.roll.guaranteedCritical.label',
|
||||
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;
|
||||
}
|
||||
|
||||
/* All items are valid on characters */
|
||||
isItemValid() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
isItemAvailable(item) {
|
||||
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 = {
|
||||
major: this.armor
|
||||
? this.armor.system.baseThresholds.major + this.levelData.level.current
|
||||
: this.levelData.level.current,
|
||||
severe: this.armor
|
||||
? 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;
|
||||
|
|
|
|||
|
|
@ -61,6 +61,24 @@ export default class DhCompanion extends DhCreature {
|
|||
initial: false,
|
||||
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({
|
||||
|
|
@ -118,10 +136,6 @@ export default class DhCompanion extends DhCreature {
|
|||
return this.levelupChoicesLeft > 0;
|
||||
}
|
||||
|
||||
isItemValid() {
|
||||
return false;
|
||||
}
|
||||
|
||||
prepareBaseData() {
|
||||
super.prepareBaseData();
|
||||
this.attack.roll.bonus = this.partner?.system?.spellcastModifier ?? 0;
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ export default class DhEnvironment extends BaseDataActor {
|
|||
}
|
||||
|
||||
isItemValid(source) {
|
||||
return source.type === 'feature';
|
||||
return super.isItemValid(source) || source.type === 'feature';
|
||||
}
|
||||
|
||||
_onUpdate(changes, options, userId) {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,18 @@
|
|||
import BaseDataActor from './base.mjs';
|
||||
import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs';
|
||||
import TagTeamData from '../tagTeamData.mjs';
|
||||
import GroupRollData from '../groupRollData.mjs';
|
||||
import { GoldField } from '../fields/actorField.mjs';
|
||||
|
||||
export default class DhParty extends BaseDataActor {
|
||||
/** @inheritdoc */
|
||||
static get metadata() {
|
||||
return foundry.utils.mergeObject(super.metadata, {
|
||||
hasInventory: true,
|
||||
quantifiable: ['weapon', 'armor', 'loot', 'consumable']
|
||||
});
|
||||
}
|
||||
|
||||
/**@inheritdoc */
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
|
|
@ -10,16 +20,16 @@ export default class DhParty extends BaseDataActor {
|
|||
...super.defineSchema(),
|
||||
partyMembers: new ForeignDocumentUUIDArrayField({ type: 'Actor' }, { prune: true }),
|
||||
notes: new fields.HTMLField(),
|
||||
gold: new fields.SchemaField({
|
||||
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 })
|
||||
}),
|
||||
tagTeam: new fields.EmbeddedDataField(TagTeamData)
|
||||
gold: new GoldField(),
|
||||
tagTeam: new fields.EmbeddedDataField(TagTeamData),
|
||||
groupRoll: new fields.EmbeddedDataField(GroupRollData)
|
||||
};
|
||||
}
|
||||
|
||||
get active() {
|
||||
return game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.ActiveParty) === this.parent.id;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**@inheritdoc */
|
||||
|
|
@ -27,10 +37,6 @@ export default class DhParty extends BaseDataActor {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
isItemValid(source) {
|
||||
return ['weapon', 'armor', 'consumable', 'loot'].includes(source.type);
|
||||
}
|
||||
|
||||
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) {
|
||||
super._onDelete(options, userId);
|
||||
|
||||
|
|
@ -49,5 +65,11 @@ export default class DhParty extends BaseDataActor {
|
|||
for (const member of this.partyMembers) {
|
||||
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 DHActorRoll from './actorRoll.mjs';
|
||||
import DHGroupRoll from './groupRoll.mjs';
|
||||
import DHSystemMessage from './systemMessage.mjs';
|
||||
|
||||
export const config = {
|
||||
|
|
@ -9,6 +8,5 @@ export const config = {
|
|||
damageRoll: DHActorRoll,
|
||||
dualityRoll: DHActorRoll,
|
||||
fateRoll: DHActorRoll,
|
||||
groupRoll: DHGroupRoll,
|
||||
systemMessage: DHSystemMessage
|
||||
};
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
|||
action: new fields.StringField()
|
||||
}),
|
||||
damage: new fields.ObjectField(),
|
||||
damageOptions: new fields.ObjectField(),
|
||||
costs: new fields.ArrayField(new fields.ObjectField()),
|
||||
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;
|
||||
|
||||
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)),
|
||||
defaultOwnership: new fields.NumberField({
|
||||
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 {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
|
|
|
|||
|
|
@ -57,6 +57,10 @@ export default class CountdownField extends fields.ArrayField {
|
|||
|
||||
data.countdowns[foundry.utils.randomID()] = {
|
||||
...countdown,
|
||||
ownership: game.users.reduce((acc, curr) => {
|
||||
if (!curr.isGM) acc[curr.id] = countdown.defaultOwnership;
|
||||
return acc;
|
||||
}, {}),
|
||||
progress: {
|
||||
...countdown.progress,
|
||||
current: countdownStart,
|
||||
|
|
|
|||
|
|
@ -18,7 +18,12 @@ export default class DamageField extends fields.SchemaField {
|
|||
initial: false,
|
||||
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);
|
||||
}
|
||||
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
@ -299,7 +320,7 @@ export class DHDamageData extends DHResourceData {
|
|||
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';
|
||||
|
||||
const fields = foundry.data.fields;
|
||||
|
|
@ -36,20 +37,23 @@ export default class DHSummonField extends fields.ArrayField {
|
|||
const rolls = [];
|
||||
const summonData = [];
|
||||
for (const summon of this.summon) {
|
||||
let count = summon.count;
|
||||
const roll = new Roll(summon.count);
|
||||
if (!roll.isDeterministic) {
|
||||
await roll.evaluate();
|
||||
if (game.modules.get('dice-so-nice')?.active) rolls.push(roll);
|
||||
count = roll.total;
|
||||
}
|
||||
const roll = new Roll(itemAbleRollParse(summon.count, this.actor, this.item));
|
||||
await roll.evaluate();
|
||||
const count = roll.total;
|
||||
if (!roll.isDeterministic && game.modules.get('dice-so-nice')?.active) rolls.push(roll);
|
||||
|
||||
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. */
|
||||
summon.rolledCount = count;
|
||||
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)));
|
||||
|
|
@ -58,32 +62,22 @@ export default class DHSummonField extends fields.ArrayField {
|
|||
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 */
|
||||
static getWorldActor(baseActor) {
|
||||
/* 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 async getWorldActor(baseActor) {
|
||||
const dataType = game.system.api.data.actors[`Dh${baseActor.type.capitalize()}`];
|
||||
if (baseActor.inCompendium && dataType && baseActor.img === dataType.DEFAULT_ICON) {
|
||||
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;
|
||||
}
|
||||
|
||||
static async handleSummon(summonData, actionActor, summonIndex = 0) {
|
||||
const summon = summonData[summonIndex];
|
||||
const result = await CONFIG.ux.TokenManager.createPreviewAsync(summon.actor, {
|
||||
name: `${summon.actor.prototypeToken.name}${summon.count > 1 ? ` (${summon.count}x)` : ''}`
|
||||
});
|
||||
static async handleSummon(summonData, actionActor) {
|
||||
await CONFIG.ux.TokenManager.createTokensWithPreview(summonData, { elevation: actionActor.token?.elevation });
|
||||
|
||||
if (!result) 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);
|
||||
return actionActor.sheet?.maximize();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,13 +89,13 @@ class ResourcesField extends fields.TypedObjectField {
|
|||
*/
|
||||
_getField(path) {
|
||||
if (path.length === 0) return this;
|
||||
const first = path.shift();
|
||||
if (first === this.element.name) return this.element_getField(path);
|
||||
const name = path.pop();
|
||||
if (name === this.element.name) return this.element_getField(path);
|
||||
|
||||
const resources = CONFIG.DH.RESOURCE[this.actorType].all;
|
||||
if (first in resources) {
|
||||
if (name in resources) {
|
||||
const field = this.element._getField(path);
|
||||
field.label = resources[first].label;
|
||||
field.label = resources[name].label;
|
||||
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} type - The system type that this data model represents.
|
||||
* @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
|
||||
*/
|
||||
|
||||
|
|
@ -24,7 +23,6 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
|||
type: 'base',
|
||||
hasDescription: false,
|
||||
hasResource: false,
|
||||
isQuantifiable: false,
|
||||
isInventoryItem: false,
|
||||
hasActions: false,
|
||||
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 });
|
||||
|
||||
if (this.metadata.hasActions) schema.actions = new ActionsField();
|
||||
|
|
@ -110,6 +108,8 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
|||
}
|
||||
|
||||
get actionsList() {
|
||||
// No actions on non-characters
|
||||
if (this.actor && this.actor.type !== 'character') return [];
|
||||
return this.actions;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ export default class DHConsumable extends BaseDataItem {
|
|||
label: 'TYPES.Item.consumable',
|
||||
type: 'consumable',
|
||||
hasDescription: true,
|
||||
isQuantifiable: true,
|
||||
isInventoryItem: true,
|
||||
hasActions: true
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ export default class DHLoot extends BaseDataItem {
|
|||
label: 'TYPES.Item.loot',
|
||||
type: 'loot',
|
||||
hasDescription: true,
|
||||
isQuantifiable: true,
|
||||
isInventoryItem: true,
|
||||
hasActions: true
|
||||
});
|
||||
|
|
|
|||
|
|
@ -99,7 +99,9 @@ export default class DHWeapon extends AttachableItem {
|
|||
/* -------------------------------------------- */
|
||||
|
||||
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() {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ export default class DhAppearance extends foundry.abstract.DataModel {
|
|||
initial: null,
|
||||
blank: true,
|
||||
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,
|
||||
choices: CONFIG.DH.GENERAL.tagTeamRollTypes,
|
||||
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 }),
|
||||
rollData: new fields.JSONField({ nullable: true, initial: null }),
|
||||
|
|
|
|||
|
|
@ -144,6 +144,7 @@ export default class DamageRoll extends DHRoll {
|
|||
constructFormula(config) {
|
||||
this.options.isCritical = config.isCritical;
|
||||
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.terms = Roll.parse(part.roll.formula, config.data);
|
||||
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);
|
||||
if (total > 0) {
|
||||
part.roll.terms.push(...this.formatModifier(total));
|
||||
|
|
|
|||
|
|
@ -256,7 +256,7 @@ export default class DHRoll extends Roll {
|
|||
if (!roll.terms[i].isDeterministic) continue;
|
||||
const termTotal = roll.terms[i].total;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -272,7 +272,7 @@ export default class DHRoll extends Roll {
|
|||
const changeKeys = this.getActionChangeKeys();
|
||||
return (
|
||||
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] = {
|
||||
id: effect.id,
|
||||
name: effect.name,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
import DualityDie from './dualityDie.mjs';
|
||||
import HopeDie from './hopeDie.mjs';
|
||||
import FearDie from './fearDie.mjs';
|
||||
import AdvantageDie from './advantageDie.mjs';
|
||||
import DisadvantageDie from './disadvantageDie.mjs';
|
||||
|
||||
export const diceTypes = {
|
||||
DualityDie,
|
||||
HopeDie,
|
||||
FearDie,
|
||||
AdvantageDie,
|
||||
DisadvantageDie
|
||||
};
|
||||
|
|
|
|||
|
|
@ -43,9 +43,10 @@ export default class DualityDie extends foundry.dice.terms.Die {
|
|||
options: { appearance: {} }
|
||||
};
|
||||
|
||||
const preset = await getDiceSoNicePreset(diceSoNice[key], faces);
|
||||
diceSoNiceRoll.dice[0].options.appearance = preset.appearance;
|
||||
diceSoNiceRoll.dice[0].options.modelFile = preset.modelFile;
|
||||
const diceAppearance = await this.getDiceSoNiceAppearance(options.liveRoll.roll);
|
||||
diceSoNiceRoll.dice[0].options.appearance = diceAppearance.appearance;
|
||||
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);
|
||||
} else {
|
||||
|
|
@ -59,4 +60,11 @@ export default class DualityDie extends foundry.dice.terms.Die {
|
|||
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';
|
||||
|
||||
export default class DualityRoll extends D20Roll {
|
||||
_advantageFaces = 6;
|
||||
_advantageNumber = 1;
|
||||
_rallyIndex;
|
||||
|
||||
|
|
@ -11,6 +10,11 @@ export default class DualityRoll extends D20Roll {
|
|||
super(formula, data, options);
|
||||
this.rallyChoices = this.setRallyChoices();
|
||||
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';
|
||||
|
|
@ -24,7 +28,7 @@ export default class DualityRoll extends D20Roll {
|
|||
}
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
|
|
@ -34,7 +38,7 @@ export default class DualityRoll extends D20Roll {
|
|||
}
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
get advantageFaces() {
|
||||
return this._advantageFaces;
|
||||
}
|
||||
|
||||
set advantageFaces(faces) {
|
||||
this._advantageFaces = this.getFaces(faces);
|
||||
}
|
||||
|
||||
get advantageNumber() {
|
||||
return this._advantageNumber;
|
||||
}
|
||||
|
|
@ -68,8 +64,8 @@ export default class DualityRoll extends D20Roll {
|
|||
}
|
||||
|
||||
get extraDice() {
|
||||
const { DualityDie, AdvantageDie, DisadvantageDie } = game.system.api.dice.diceTypes;
|
||||
return this.dice.filter(x => ![DualityDie, AdvantageDie, DisadvantageDie].some(die => x instanceof die));
|
||||
const { HopeDie, FearDie, AdvantageDie, DisadvantageDie } = game.system.api.dice.diceTypes;
|
||||
return this.dice.filter(x => ![HopeDie, FearDie, AdvantageDie, DisadvantageDie].some(die => x instanceof die));
|
||||
}
|
||||
|
||||
setRallyChoices() {
|
||||
|
|
@ -125,8 +121,8 @@ export default class DualityRoll extends D20Roll {
|
|||
|
||||
/** @inheritDoc */
|
||||
static fromData(data) {
|
||||
data.terms[0].class = 'DualityDie';
|
||||
data.terms[2].class = 'DualityDie';
|
||||
data.terms[0].class = 'HopeDie';
|
||||
data.terms[2].class = 'FearDie';
|
||||
if (data.options.roll.advantage?.type && data.terms[4]?.faces) {
|
||||
data.terms[4].class = data.options.roll.advantage.type === 1 ? 'AdvantageDie' : 'DisadvantageDie';
|
||||
}
|
||||
|
|
@ -135,18 +131,18 @@ export default class DualityRoll extends D20Roll {
|
|||
|
||||
createBaseDice() {
|
||||
if (
|
||||
this.dice[0] instanceof game.system.api.dice.diceTypes.DualityDie &&
|
||||
this.dice[1] 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.FearDie
|
||||
) {
|
||||
this.terms = [this.terms[0], this.terms[1], this.terms[2]];
|
||||
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
|
||||
});
|
||||
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
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -200,7 +200,6 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
|
|||
static effectSafeEval(expression) {
|
||||
let result;
|
||||
try {
|
||||
// eslint-disable-next-line no-new-func
|
||||
const evl = new Function('sandbox', `with (sandbox) { return ${expression}}`);
|
||||
result = evl(Roll.MATH_PROXY);
|
||||
} catch (err) {
|
||||
|
|
|
|||
|
|
@ -113,11 +113,13 @@ export default class DhpActor extends Actor {
|
|||
_onUpdate(changes, options, userId) {
|
||||
super._onUpdate(changes, options, userId);
|
||||
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) {
|
||||
game.system.registeredTriggers.unregisterItemTriggers(this.items);
|
||||
} else {
|
||||
|
|
@ -130,7 +132,7 @@ export default class DhpActor extends Actor {
|
|||
_onDelete(options, userId) {
|
||||
super._onDelete(options, userId);
|
||||
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.prof = this.system.proficiency ?? 1;
|
||||
rollData.cast = this.system.spellcastModifier ?? 1;
|
||||
|
||||
return rollData;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -178,8 +178,8 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
|||
const pendingingSaves = targets.filter(t => t.saved.success === null);
|
||||
if (pendingingSaves.length) {
|
||||
const confirm = await foundry.applications.api.DialogV2.confirm({
|
||||
window: { title: 'Pending Reaction Rolls found' },
|
||||
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>`
|
||||
window: { title: game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.title') },
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,11 @@
|
|||
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. */
|
||||
_initialize() {
|
||||
super._initialize();
|
||||
|
|
|
|||
|
|
@ -31,8 +31,13 @@ export default class DHItem extends foundry.documents.Item {
|
|||
static async createDocuments(sources, operation) {
|
||||
// Ensure that items being created are valid to the actor its being added to
|
||||
const actor = operation.parent;
|
||||
sources = actor?.system?.isItemValid ? sources.filter(s => actor.system.isItemValid(s)) : sources;
|
||||
return super.createDocuments(sources, operation);
|
||||
const filtered = actor ? sources.filter(s => actor.system.isItemValid(s)) : sources;
|
||||
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;
|
||||
}
|
||||
|
||||
/** 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 */
|
||||
static async createDialog(data = {}, createOptions = {}, options = {}) {
|
||||
const { folders, types, template, context = {}, ...dialogOptions } = options;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,16 @@
|
|||
import DHToken from './token.mjs';
|
||||
|
||||
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 */
|
||||
#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 {
|
||||
#activePreview;
|
||||
#actor;
|
||||
#resolve;
|
||||
|
||||
/**
|
||||
* Create a template preview, deactivating any existing ones.
|
||||
* @param {object} data
|
||||
* Create a token previer
|
||||
* @param {Actor} actor
|
||||
* @param {object} tokenData
|
||||
*/
|
||||
async createPreview(actor, tokenData) {
|
||||
this.#actor = actor;
|
||||
const token = await canvas.tokens._createPreview(
|
||||
{
|
||||
...actor.prototypeToken,
|
||||
displayName: 50,
|
||||
...tokenData
|
||||
},
|
||||
{ renderSheet: false, actor }
|
||||
const tokenSizes = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes;
|
||||
if (actor?.system.metadata.usesSize) {
|
||||
const tokenSize = tokenSizes[actor.system.size];
|
||||
if (tokenSize && actor.system.size !== CONFIG.DH.ACTOR.tokenSize.custom.id) {
|
||||
tokenData.width = tokenSize;
|
||||
tokenData.height = tokenSize;
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
* @param {mousemove Event} event
|
||||
* Creates new tokens on the canvas by placing previews.
|
||||
* @param {object} tokenData
|
||||
* @param {object} options
|
||||
*/
|
||||
#onDragMouseMove(event) {
|
||||
event.stopPropagation();
|
||||
const { moveTime, object } = this.#activePreview;
|
||||
const update = {};
|
||||
async createTokensWithPreview(tokensData, { elevation } = {}) {
|
||||
const scene = game.scenes.get(game.user.viewedScene);
|
||||
if (!scene) return;
|
||||
|
||||
const now = Date.now();
|
||||
if (now - (moveTime || 0) <= 16) return;
|
||||
this.#activePreview.moveTime = now;
|
||||
const level = scene.levels.get(game.user.viewedLevel);
|
||||
if (!level) return;
|
||||
|
||||
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));
|
||||
|
||||
object.document.updateSource(update);
|
||||
object.renderFlags.set({ refresh: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels the preview token on right-click.
|
||||
* @param {contextmenu Event} event
|
||||
*/
|
||||
#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.toObject(), x: this.#activePreview.document.x, y: this.#activePreview.document.y }
|
||||
]);
|
||||
|
||||
this.#activePreview = undefined;
|
||||
if (this.#resolve && result.length) this.#resolve(result[0]);
|
||||
await canvas.scene.createEmbeddedDocuments(
|
||||
'Token',
|
||||
previewTokens.map(x => ({
|
||||
...x.toObject(),
|
||||
name: tokenData.actor.prototypeToken.name,
|
||||
displayName: tokenData.actor.prototypeToken.displayName,
|
||||
flags: tokenData.actor.prototypeToken.flags
|
||||
})),
|
||||
{ controlObject: true, parent: canvas.scene }
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -118,13 +118,6 @@ const getTemplateDistance = range => {
|
|||
const rangeNumber = Number(range);
|
||||
if (!Number.isNaN(rangeNumber)) return rangeNumber;
|
||||
|
||||
const { custom } = CONFIG.DH.GENERAL.sceneRangeMeasurementSetting;
|
||||
const sceneMeasurements = canvas.scene?.flags.daggerheart?.rangeMeasurement;
|
||||
const globalMeasurements = game.settings.get(
|
||||
CONFIG.DH.id,
|
||||
CONFIG.DH.SETTINGS.gameSettings.variantRules
|
||||
).rangeMeasurement;
|
||||
|
||||
const settings = sceneMeasurements?.setting === custom.id ? sceneMeasurements : globalMeasurements;
|
||||
return settings[range];
|
||||
const settings = canvas.scene?.rangeSettings;
|
||||
return settings ? settings[range] : 0;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -189,7 +189,13 @@ export const getDeleteKeys = (property, innerProperty, innerPropertyDefaultValue
|
|||
|
||||
// Fix on Foundry native formula replacement for DH
|
||||
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 => {
|
||||
return { term: type, default: 1 };
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ export const preloadHandlebarsTemplates = async function () {
|
|||
'templates/generic/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/gold.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/resource-section/resource-section.hbs',
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { defaultRestOptions } from '../config/generalConfig.mjs';
|
||||
import { RefreshType, socketEvent } from './socket.mjs';
|
||||
|
||||
export async function runMigrations() {
|
||||
|
|
@ -341,6 +342,18 @@ export async function runMigrations() {
|
|||
|
||||
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
|
||||
|
||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LastMigrationVersion, lastMigrationVersion);
|
||||
|
|
|
|||
|
|
@ -52,6 +52,27 @@ export const registerKeyBindings = () => {
|
|||
reservedModifiers: [],
|
||||
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 = () => {
|
||||
|
|
@ -189,4 +210,11 @@ const registerNonConfigSettings = () => {
|
|||
config: false,
|
||||
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:
|
||||
Hooks.callAll(CONFIG.DH.HOOKS.hooksConfig.tagTeamStart, data);
|
||||
break;
|
||||
case socketEvent.GroupRollStart:
|
||||
Hooks.callAll(CONFIG.DH.HOOKS.hooksConfig.groupRollStart, data);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -26,7 +28,8 @@ export const socketEvent = {
|
|||
Refresh: 'DhRefresh',
|
||||
DhpFearUpdate: 'DhFearUpdate',
|
||||
DowntimeTrigger: 'DowntimeTrigger',
|
||||
TagTeamStart: 'DhTagTeamStart'
|
||||
TagTeamStart: 'DhTagTeamStart',
|
||||
GroupRollStart: 'DhGroupRollStart'
|
||||
};
|
||||
|
||||
export const GMUpdateEvent = {
|
||||
|
|
@ -41,6 +44,7 @@ export const GMUpdateEvent = {
|
|||
export const RefreshType = {
|
||||
Countdown: 'DhCoundownRefresh',
|
||||
TagTeamRoll: 'DhTagTeamRollRefresh',
|
||||
GroupRoll: 'DhGroupRollRefresh',
|
||||
EffectsDisplay: 'DhEffectsDisplayRefresh',
|
||||
Scene: 'DhSceneRefresh',
|
||||
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",
|
||||
"pullYMLtoLDBBuild": "node ./tools/pullYMLtoLDB.mjs --build",
|
||||
"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": {
|
||||
"@foundryvtt/foundryvtt-cli": "^1.0.2",
|
||||
"@rollup/plugin-commonjs": "^25.0.7",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"concurrently": "^8.2.2",
|
||||
"eslint": "^10.2.1",
|
||||
"eslint-plugin-prettier": "^5.5.5",
|
||||
"globals": "^17.5.0",
|
||||
"husky": "^9.1.5",
|
||||
"lint-staged": "^15.2.10",
|
||||
"postcss": "^8.4.32",
|
||||
|
|
|
|||
|
|
@ -131,12 +131,9 @@
|
|||
"src": "systems/daggerheart/assets/icons/documents/actors/dragon-head.svg",
|
||||
"anchorX": 0.5,
|
||||
"anchorY": 0.5,
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"fit": "contain",
|
||||
"scaleX": 1,
|
||||
"scaleY": 1,
|
||||
"rotation": 0,
|
||||
"tint": "#ffffff",
|
||||
"alphaThreshold": 0.75
|
||||
},
|
||||
|
|
@ -187,7 +184,7 @@
|
|||
"saturation": 0,
|
||||
"contrast": 0
|
||||
},
|
||||
"detectionModes": [],
|
||||
"detectionModes": {},
|
||||
"occludable": {
|
||||
"radius": 0
|
||||
},
|
||||
|
|
@ -213,7 +210,8 @@
|
|||
"flags": {},
|
||||
"randomImg": false,
|
||||
"appendNumber": false,
|
||||
"prependAdjective": false
|
||||
"prependAdjective": false,
|
||||
"depth": 1
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
|
|
@ -249,33 +247,95 @@
|
|||
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.</p>",
|
||||
"resource": null,
|
||||
"actions": {
|
||||
"vgguNWz8vG8aoLXR": {
|
||||
"type": "effect",
|
||||
"_id": "vgguNWz8vG8aoLXR",
|
||||
"SrNyZgPvCXMpbCLG": {
|
||||
"type": "attack",
|
||||
"_id": "SrNyZgPvCXMpbCLG",
|
||||
"systemPath": "actions",
|
||||
"baseAction": false,
|
||||
"description": "",
|
||||
"chatDisplay": true,
|
||||
"originItem": {
|
||||
"type": "itemCollection"
|
||||
},
|
||||
"actionType": "action",
|
||||
"triggers": [],
|
||||
"cost": [
|
||||
{
|
||||
"scalable": false,
|
||||
"key": "fear",
|
||||
"value": 1,
|
||||
"step": null
|
||||
"itemId": null,
|
||||
"step": null,
|
||||
"consumeOnSuccess": false
|
||||
}
|
||||
],
|
||||
"uses": {
|
||||
"value": null,
|
||||
"max": "",
|
||||
"recovery": null
|
||||
"recovery": null,
|
||||
"consumeOnSuccess": false
|
||||
},
|
||||
"damage": {
|
||||
"parts": {
|
||||
"hitPoints": {
|
||||
"applyTo": "hitPoints",
|
||||
"resultBased": false,
|
||||
"value": {
|
||||
"multiplier": "flat",
|
||||
"flatMultiplier": 1,
|
||||
"dice": "d6",
|
||||
"bonus": null,
|
||||
"custom": {
|
||||
"enabled": true,
|
||||
"formula": "4"
|
||||
}
|
||||
},
|
||||
"valueAlt": {
|
||||
"multiplier": "flat",
|
||||
"flatMultiplier": 1,
|
||||
"dice": "d6",
|
||||
"bonus": null,
|
||||
"custom": {
|
||||
"enabled": false,
|
||||
"formula": ""
|
||||
}
|
||||
},
|
||||
"base": false,
|
||||
"type": [
|
||||
"physical"
|
||||
]
|
||||
}
|
||||
},
|
||||
"includeBase": false,
|
||||
"direct": false,
|
||||
"groupAttack": "close"
|
||||
},
|
||||
"effects": [],
|
||||
"target": {
|
||||
"type": "any",
|
||||
"amount": null
|
||||
},
|
||||
"effects": [],
|
||||
"roll": {
|
||||
"type": "attack",
|
||||
"trait": null,
|
||||
"difficulty": null,
|
||||
"bonus": null,
|
||||
"advState": "neutral",
|
||||
"diceRolling": {
|
||||
"multiplier": "prof",
|
||||
"flatMultiplier": 1,
|
||||
"dice": "d6",
|
||||
"compare": null,
|
||||
"treshold": null
|
||||
},
|
||||
"useDefault": false
|
||||
},
|
||||
"save": {
|
||||
"trait": null,
|
||||
"difficulty": null,
|
||||
"damageMod": "none"
|
||||
},
|
||||
"name": "Spend Fear",
|
||||
"img": "icons/magic/unholy/orb-hands-pink.webp",
|
||||
"range": ""
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -125,12 +125,9 @@
|
|||
"src": "systems/daggerheart/assets/icons/documents/actors/dragon-head.svg",
|
||||
"anchorX": 0.5,
|
||||
"anchorY": 0.5,
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"fit": "contain",
|
||||
"scaleX": 1,
|
||||
"scaleY": 1,
|
||||
"rotation": 0,
|
||||
"tint": "#ffffff",
|
||||
"alphaThreshold": 0.75
|
||||
},
|
||||
|
|
@ -181,7 +178,7 @@
|
|||
"saturation": 0,
|
||||
"contrast": 0
|
||||
},
|
||||
"detectionModes": [],
|
||||
"detectionModes": {},
|
||||
"occludable": {
|
||||
"radius": 0
|
||||
},
|
||||
|
|
@ -207,7 +204,8 @@
|
|||
"flags": {},
|
||||
"randomImg": false,
|
||||
"appendNumber": false,
|
||||
"prependAdjective": false
|
||||
"prependAdjective": false,
|
||||
"depth": 1
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
|
|
@ -242,33 +240,95 @@
|
|||
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.</p>",
|
||||
"resource": null,
|
||||
"actions": {
|
||||
"cbAvPSIhwBMBTI3D": {
|
||||
"type": "effect",
|
||||
"_id": "cbAvPSIhwBMBTI3D",
|
||||
"FCeTuf71gCzRiO5N": {
|
||||
"type": "attack",
|
||||
"_id": "FCeTuf71gCzRiO5N",
|
||||
"systemPath": "actions",
|
||||
"baseAction": false,
|
||||
"description": "",
|
||||
"chatDisplay": true,
|
||||
"originItem": {
|
||||
"type": "itemCollection"
|
||||
},
|
||||
"actionType": "action",
|
||||
"triggers": [],
|
||||
"cost": [
|
||||
{
|
||||
"scalable": false,
|
||||
"key": "fear",
|
||||
"value": 1,
|
||||
"step": null
|
||||
"itemId": null,
|
||||
"step": null,
|
||||
"consumeOnSuccess": false
|
||||
}
|
||||
],
|
||||
"uses": {
|
||||
"value": null,
|
||||
"max": "",
|
||||
"recovery": null
|
||||
"recovery": null,
|
||||
"consumeOnSuccess": false
|
||||
},
|
||||
"damage": {
|
||||
"parts": {
|
||||
"hitPoints": {
|
||||
"applyTo": "hitPoints",
|
||||
"resultBased": false,
|
||||
"value": {
|
||||
"multiplier": "flat",
|
||||
"flatMultiplier": 1,
|
||||
"dice": "d6",
|
||||
"bonus": null,
|
||||
"custom": {
|
||||
"enabled": true,
|
||||
"formula": "6"
|
||||
}
|
||||
},
|
||||
"valueAlt": {
|
||||
"multiplier": "flat",
|
||||
"flatMultiplier": 1,
|
||||
"dice": "d6",
|
||||
"bonus": null,
|
||||
"custom": {
|
||||
"enabled": false,
|
||||
"formula": ""
|
||||
}
|
||||
},
|
||||
"base": false,
|
||||
"type": [
|
||||
"physical"
|
||||
]
|
||||
}
|
||||
},
|
||||
"includeBase": false,
|
||||
"direct": false,
|
||||
"groupAttack": "close"
|
||||
},
|
||||
"effects": [],
|
||||
"target": {
|
||||
"type": "any",
|
||||
"amount": null
|
||||
},
|
||||
"effects": [],
|
||||
"roll": {
|
||||
"type": "attack",
|
||||
"trait": null,
|
||||
"difficulty": null,
|
||||
"bonus": null,
|
||||
"advState": "neutral",
|
||||
"diceRolling": {
|
||||
"multiplier": "prof",
|
||||
"flatMultiplier": 1,
|
||||
"dice": "d6",
|
||||
"compare": null,
|
||||
"treshold": null
|
||||
},
|
||||
"useDefault": false
|
||||
},
|
||||
"save": {
|
||||
"trait": null,
|
||||
"difficulty": null,
|
||||
"damageMod": "none"
|
||||
},
|
||||
"name": "Spend Fear",
|
||||
"img": "icons/creatures/abilities/tail-strike-bone-orange.webp",
|
||||
"range": ""
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -125,12 +125,9 @@
|
|||
"src": "systems/daggerheart/assets/icons/documents/actors/dragon-head.svg",
|
||||
"anchorX": 0.5,
|
||||
"anchorY": 0.5,
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"fit": "contain",
|
||||
"scaleX": 1,
|
||||
"scaleY": 1,
|
||||
"rotation": 0,
|
||||
"tint": "#ffffff",
|
||||
"alphaThreshold": 0.75
|
||||
},
|
||||
|
|
@ -181,7 +178,7 @@
|
|||
"saturation": 0,
|
||||
"contrast": 0
|
||||
},
|
||||
"detectionModes": [],
|
||||
"detectionModes": {},
|
||||
"occludable": {
|
||||
"radius": 0
|
||||
},
|
||||
|
|
@ -207,7 +204,8 @@
|
|||
"flags": {},
|
||||
"randomImg": false,
|
||||
"appendNumber": false,
|
||||
"prependAdjective": false
|
||||
"prependAdjective": false,
|
||||
"depth": 1
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
|
|
@ -242,33 +240,95 @@
|
|||
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all Cult @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.</p>",
|
||||
"resource": null,
|
||||
"actions": {
|
||||
"EH1preaTWBD4rOvx": {
|
||||
"type": "effect",
|
||||
"_id": "EH1preaTWBD4rOvx",
|
||||
"4M2MvVzEgIQEQHBS": {
|
||||
"type": "attack",
|
||||
"_id": "4M2MvVzEgIQEQHBS",
|
||||
"systemPath": "actions",
|
||||
"baseAction": false,
|
||||
"description": "",
|
||||
"chatDisplay": true,
|
||||
"originItem": {
|
||||
"type": "itemCollection"
|
||||
},
|
||||
"actionType": "action",
|
||||
"triggers": [],
|
||||
"cost": [
|
||||
{
|
||||
"scalable": false,
|
||||
"key": "fear",
|
||||
"value": 1,
|
||||
"step": null
|
||||
"itemId": null,
|
||||
"step": null,
|
||||
"consumeOnSuccess": false
|
||||
}
|
||||
],
|
||||
"uses": {
|
||||
"value": null,
|
||||
"max": "",
|
||||
"recovery": null
|
||||
"recovery": null,
|
||||
"consumeOnSuccess": false
|
||||
},
|
||||
"damage": {
|
||||
"parts": {
|
||||
"hitPoints": {
|
||||
"applyTo": "hitPoints",
|
||||
"resultBased": false,
|
||||
"value": {
|
||||
"multiplier": "flat",
|
||||
"flatMultiplier": 1,
|
||||
"dice": "d6",
|
||||
"bonus": null,
|
||||
"custom": {
|
||||
"enabled": true,
|
||||
"formula": "5"
|
||||
}
|
||||
},
|
||||
"valueAlt": {
|
||||
"multiplier": "flat",
|
||||
"flatMultiplier": 1,
|
||||
"dice": "d6",
|
||||
"bonus": null,
|
||||
"custom": {
|
||||
"enabled": false,
|
||||
"formula": ""
|
||||
}
|
||||
},
|
||||
"base": false,
|
||||
"type": [
|
||||
"physical"
|
||||
]
|
||||
}
|
||||
},
|
||||
"includeBase": false,
|
||||
"direct": false,
|
||||
"groupAttack": "close"
|
||||
},
|
||||
"effects": [],
|
||||
"target": {
|
||||
"type": "self",
|
||||
"type": "any",
|
||||
"amount": null
|
||||
},
|
||||
"effects": [],
|
||||
"roll": {
|
||||
"type": null,
|
||||
"trait": null,
|
||||
"difficulty": null,
|
||||
"bonus": null,
|
||||
"advState": "neutral",
|
||||
"diceRolling": {
|
||||
"multiplier": "prof",
|
||||
"flatMultiplier": 1,
|
||||
"dice": "d6",
|
||||
"compare": null,
|
||||
"treshold": null
|
||||
},
|
||||
"useDefault": false
|
||||
},
|
||||
"save": {
|
||||
"trait": null,
|
||||
"difficulty": null,
|
||||
"damageMod": "none"
|
||||
},
|
||||
"name": "Spend Fear",
|
||||
"img": "icons/creatures/abilities/tail-strike-bone-orange.webp",
|
||||
"range": ""
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -125,12 +125,9 @@
|
|||
"src": "systems/daggerheart/assets/icons/documents/actors/dragon-head.svg",
|
||||
"anchorX": 0.5,
|
||||
"anchorY": 0.5,
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"fit": "contain",
|
||||
"scaleX": 1,
|
||||
"scaleY": 1,
|
||||
"rotation": 0,
|
||||
"tint": "#ffffff",
|
||||
"alphaThreshold": 0.75
|
||||
},
|
||||
|
|
@ -181,7 +178,7 @@
|
|||
"saturation": 0,
|
||||
"contrast": 0
|
||||
},
|
||||
"detectionModes": [],
|
||||
"detectionModes": {},
|
||||
"occludable": {
|
||||
"radius": 0
|
||||
},
|
||||
|
|
@ -207,7 +204,8 @@
|
|||
"flags": {},
|
||||
"randomImg": false,
|
||||
"appendNumber": false,
|
||||
"prependAdjective": false
|
||||
"prependAdjective": false,
|
||||
"depth": 1
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
|
|
@ -242,33 +240,95 @@
|
|||
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.</p>",
|
||||
"resource": null,
|
||||
"actions": {
|
||||
"vXHZVb0Y7Hqu3uso": {
|
||||
"type": "effect",
|
||||
"_id": "vXHZVb0Y7Hqu3uso",
|
||||
"S3dYxRclyhYINRi8": {
|
||||
"type": "attack",
|
||||
"_id": "S3dYxRclyhYINRi8",
|
||||
"systemPath": "actions",
|
||||
"baseAction": false,
|
||||
"description": "",
|
||||
"chatDisplay": true,
|
||||
"originItem": {
|
||||
"type": "itemCollection"
|
||||
},
|
||||
"actionType": "action",
|
||||
"triggers": [],
|
||||
"cost": [
|
||||
{
|
||||
"scalable": false,
|
||||
"key": "fear",
|
||||
"value": 1,
|
||||
"step": null
|
||||
"itemId": null,
|
||||
"step": null,
|
||||
"consumeOnSuccess": false
|
||||
}
|
||||
],
|
||||
"uses": {
|
||||
"value": null,
|
||||
"max": "",
|
||||
"recovery": null
|
||||
"recovery": null,
|
||||
"consumeOnSuccess": false
|
||||
},
|
||||
"damage": {
|
||||
"parts": {
|
||||
"hitPoints": {
|
||||
"applyTo": "hitPoints",
|
||||
"resultBased": false,
|
||||
"value": {
|
||||
"multiplier": "flat",
|
||||
"flatMultiplier": 1,
|
||||
"dice": "d6",
|
||||
"bonus": null,
|
||||
"custom": {
|
||||
"enabled": true,
|
||||
"formula": "5"
|
||||
}
|
||||
},
|
||||
"valueAlt": {
|
||||
"multiplier": "flat",
|
||||
"flatMultiplier": 1,
|
||||
"dice": "d6",
|
||||
"bonus": null,
|
||||
"custom": {
|
||||
"enabled": false,
|
||||
"formula": ""
|
||||
}
|
||||
},
|
||||
"base": false,
|
||||
"type": [
|
||||
"physical"
|
||||
]
|
||||
}
|
||||
},
|
||||
"includeBase": false,
|
||||
"direct": false,
|
||||
"groupAttack": "close"
|
||||
},
|
||||
"effects": [],
|
||||
"target": {
|
||||
"type": "self",
|
||||
"type": "any",
|
||||
"amount": null
|
||||
},
|
||||
"effects": [],
|
||||
"roll": {
|
||||
"type": "attack",
|
||||
"trait": null,
|
||||
"difficulty": null,
|
||||
"bonus": null,
|
||||
"advState": "neutral",
|
||||
"diceRolling": {
|
||||
"multiplier": "prof",
|
||||
"flatMultiplier": 1,
|
||||
"dice": "d6",
|
||||
"compare": null,
|
||||
"treshold": null
|
||||
},
|
||||
"useDefault": false
|
||||
},
|
||||
"save": {
|
||||
"trait": null,
|
||||
"difficulty": null,
|
||||
"damageMod": "none"
|
||||
},
|
||||
"name": "Spend Fear",
|
||||
"img": "icons/magic/unholy/orb-hands-pink.webp",
|
||||
"range": ""
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -125,12 +125,9 @@
|
|||
"src": "systems/daggerheart/assets/icons/documents/actors/dragon-head.svg",
|
||||
"anchorX": 0.5,
|
||||
"anchorY": 0.5,
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"fit": "contain",
|
||||
"scaleX": 1,
|
||||
"scaleY": 1,
|
||||
"rotation": 0,
|
||||
"tint": "#ffffff",
|
||||
"alphaThreshold": 0.75
|
||||
},
|
||||
|
|
@ -181,7 +178,7 @@
|
|||
"saturation": 0,
|
||||
"contrast": 0
|
||||
},
|
||||
"detectionModes": [],
|
||||
"detectionModes": {},
|
||||
"occludable": {
|
||||
"radius": 0
|
||||
},
|
||||
|
|
@ -207,7 +204,8 @@
|
|||
"flags": {},
|
||||
"randomImg": false,
|
||||
"appendNumber": false,
|
||||
"prependAdjective": false
|
||||
"prependAdjective": false,
|
||||
"depth": 1
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
|
|
@ -320,33 +318,95 @@
|
|||
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.</p>",
|
||||
"resource": null,
|
||||
"actions": {
|
||||
"QHNRSEQmqOcaoXq4": {
|
||||
"type": "effect",
|
||||
"_id": "QHNRSEQmqOcaoXq4",
|
||||
"G0DVft7h55pBnwJA": {
|
||||
"type": "attack",
|
||||
"_id": "G0DVft7h55pBnwJA",
|
||||
"systemPath": "actions",
|
||||
"baseAction": false,
|
||||
"description": "",
|
||||
"chatDisplay": true,
|
||||
"originItem": {
|
||||
"type": "itemCollection"
|
||||
},
|
||||
"actionType": "action",
|
||||
"triggers": [],
|
||||
"cost": [
|
||||
{
|
||||
"scalable": false,
|
||||
"key": "fear",
|
||||
"value": 1,
|
||||
"step": null
|
||||
"itemId": null,
|
||||
"step": null,
|
||||
"consumeOnSuccess": false
|
||||
}
|
||||
],
|
||||
"uses": {
|
||||
"value": null,
|
||||
"max": "",
|
||||
"recovery": null
|
||||
"recovery": null,
|
||||
"consumeOnSuccess": false
|
||||
},
|
||||
"damage": {
|
||||
"parts": {
|
||||
"hitPoints": {
|
||||
"applyTo": "hitPoints",
|
||||
"resultBased": false,
|
||||
"value": {
|
||||
"multiplier": "flat",
|
||||
"flatMultiplier": 1,
|
||||
"dice": "d6",
|
||||
"bonus": null,
|
||||
"custom": {
|
||||
"enabled": true,
|
||||
"formula": "12"
|
||||
}
|
||||
},
|
||||
"valueAlt": {
|
||||
"multiplier": "flat",
|
||||
"flatMultiplier": 1,
|
||||
"dice": "d6",
|
||||
"bonus": null,
|
||||
"custom": {
|
||||
"enabled": false,
|
||||
"formula": ""
|
||||
}
|
||||
},
|
||||
"base": false,
|
||||
"type": [
|
||||
"physical"
|
||||
]
|
||||
}
|
||||
},
|
||||
"includeBase": false,
|
||||
"direct": false,
|
||||
"groupAttack": "close"
|
||||
},
|
||||
"effects": [],
|
||||
"target": {
|
||||
"type": "self",
|
||||
"type": "any",
|
||||
"amount": null
|
||||
},
|
||||
"effects": [],
|
||||
"roll": {
|
||||
"type": "attack",
|
||||
"trait": null,
|
||||
"difficulty": null,
|
||||
"bonus": null,
|
||||
"advState": "neutral",
|
||||
"diceRolling": {
|
||||
"multiplier": "prof",
|
||||
"flatMultiplier": 1,
|
||||
"dice": "d6",
|
||||
"compare": null,
|
||||
"treshold": null
|
||||
},
|
||||
"useDefault": false
|
||||
},
|
||||
"save": {
|
||||
"trait": null,
|
||||
"difficulty": null,
|
||||
"damageMod": "none"
|
||||
},
|
||||
"name": "Spend Fear",
|
||||
"img": "icons/magic/unholy/orb-hands-pink.webp",
|
||||
"range": ""
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -174,12 +174,9 @@
|
|||
"src": "systems/daggerheart/assets/icons/documents/actors/dragon-head.svg",
|
||||
"anchorX": 0.5,
|
||||
"anchorY": 0.5,
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"fit": "contain",
|
||||
"scaleX": 1,
|
||||
"scaleY": 1,
|
||||
"rotation": 0,
|
||||
"tint": "#ffffff",
|
||||
"alphaThreshold": 0.75
|
||||
},
|
||||
|
|
@ -230,7 +227,7 @@
|
|||
"saturation": 0,
|
||||
"contrast": 0
|
||||
},
|
||||
"detectionModes": [],
|
||||
"detectionModes": {},
|
||||
"occludable": {
|
||||
"radius": 0
|
||||
},
|
||||
|
|
@ -256,7 +253,8 @@
|
|||
"flags": {},
|
||||
"randomImg": false,
|
||||
"appendNumber": false,
|
||||
"prependAdjective": false
|
||||
"prependAdjective": false,
|
||||
"depth": 1
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
|
|
@ -496,34 +494,42 @@
|
|||
"description": "<p><strong>Spend a Fear</strong> to summon a number of @UUID[Compendium.daggerheart.adversaries.Actor.OsLG2BjaEdTZUJU9]{Fallen Shock Troops} equal to twice the number of PCs. The Shock Troops appear at Far range.</p>",
|
||||
"resource": null,
|
||||
"actions": {
|
||||
"hGMzqw00JTlYfHYy": {
|
||||
"type": "effect",
|
||||
"_id": "hGMzqw00JTlYfHYy",
|
||||
"SrU7qbh8LcOgfozT": {
|
||||
"type": "summon",
|
||||
"_id": "SrU7qbh8LcOgfozT",
|
||||
"systemPath": "actions",
|
||||
"baseAction": false,
|
||||
"description": "",
|
||||
"chatDisplay": true,
|
||||
"originItem": {
|
||||
"type": "itemCollection"
|
||||
},
|
||||
"actionType": "action",
|
||||
"triggers": [],
|
||||
"cost": [
|
||||
{
|
||||
"scalable": false,
|
||||
"key": "fear",
|
||||
"value": 1,
|
||||
"step": null
|
||||
"itemId": null,
|
||||
"step": null,
|
||||
"consumeOnSuccess": false
|
||||
}
|
||||
],
|
||||
"uses": {
|
||||
"value": null,
|
||||
"max": "",
|
||||
"recovery": null
|
||||
},
|
||||
"effects": [],
|
||||
"target": {
|
||||
"type": "self",
|
||||
"amount": null
|
||||
"recovery": null,
|
||||
"consumeOnSuccess": false
|
||||
},
|
||||
"summon": [
|
||||
{
|
||||
"actorUUID": "Compendium.daggerheart.adversaries.Actor.OsLG2BjaEdTZUJU9",
|
||||
"count": "@partySize*2"
|
||||
}
|
||||
],
|
||||
"name": "Spend Fear",
|
||||
"img": "icons/magic/death/undead-skeleton-worn-blue.webp",
|
||||
"range": ""
|
||||
"range": "far"
|
||||
}
|
||||
},
|
||||
"originItemType": null,
|
||||
|
|
|
|||
|
|
@ -131,12 +131,9 @@
|
|||
"src": "systems/daggerheart/assets/icons/documents/actors/dragon-head.svg",
|
||||
"anchorX": 0.5,
|
||||
"anchorY": 0.5,
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"fit": "contain",
|
||||
"scaleX": 1,
|
||||
"scaleY": 1,
|
||||
"rotation": 0,
|
||||
"tint": "#ffffff",
|
||||
"alphaThreshold": 0.75
|
||||
},
|
||||
|
|
@ -187,7 +184,7 @@
|
|||
"saturation": 0,
|
||||
"contrast": 0
|
||||
},
|
||||
"detectionModes": [],
|
||||
"detectionModes": {},
|
||||
"occludable": {
|
||||
"radius": 0
|
||||
},
|
||||
|
|
@ -213,7 +210,8 @@
|
|||
"flags": {},
|
||||
"randomImg": false,
|
||||
"appendNumber": false,
|
||||
"prependAdjective": false
|
||||
"prependAdjective": false,
|
||||
"depth": 1
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
|
|
@ -272,8 +270,38 @@
|
|||
"recovery": null
|
||||
},
|
||||
"damage": {
|
||||
"parts": {},
|
||||
"includeBase": false
|
||||
"parts": {
|
||||
"hitPoints": {
|
||||
"applyTo": "hitPoints",
|
||||
"resultBased": false,
|
||||
"value": {
|
||||
"multiplier": "flat",
|
||||
"flatMultiplier": 1,
|
||||
"dice": "d6",
|
||||
"bonus": null,
|
||||
"custom": {
|
||||
"enabled": true,
|
||||
"formula": "1"
|
||||
}
|
||||
},
|
||||
"valueAlt": {
|
||||
"multiplier": "flat",
|
||||
"flatMultiplier": 1,
|
||||
"dice": "d6",
|
||||
"bonus": null,
|
||||
"custom": {
|
||||
"enabled": false,
|
||||
"formula": ""
|
||||
}
|
||||
},
|
||||
"base": false,
|
||||
"type": [
|
||||
"physical"
|
||||
]
|
||||
}
|
||||
},
|
||||
"includeBase": false,
|
||||
"groupAttack": "close"
|
||||
},
|
||||
"target": {
|
||||
"type": "any",
|
||||
|
|
@ -300,7 +328,7 @@
|
|||
"difficulty": null,
|
||||
"damageMod": "none"
|
||||
},
|
||||
"name": "Attack",
|
||||
"name": "Spend Fear",
|
||||
"img": "icons/creatures/abilities/tail-strike-bone-orange.webp",
|
||||
"range": ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -125,12 +125,9 @@
|
|||
"src": "systems/daggerheart/assets/icons/documents/actors/dragon-head.svg",
|
||||
"anchorX": 0.5,
|
||||
"anchorY": 0.5,
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"fit": "contain",
|
||||
"scaleX": 1,
|
||||
"scaleY": 1,
|
||||
"rotation": 0,
|
||||
"tint": "#ffffff",
|
||||
"alphaThreshold": 0.75
|
||||
},
|
||||
|
|
@ -181,7 +178,7 @@
|
|||
"saturation": 0,
|
||||
"contrast": 0
|
||||
},
|
||||
"detectionModes": [],
|
||||
"detectionModes": {},
|
||||
"occludable": {
|
||||
"radius": 0
|
||||
},
|
||||
|
|
@ -207,7 +204,8 @@
|
|||
"flags": {},
|
||||
"randomImg": false,
|
||||
"appendNumber": false,
|
||||
"prependAdjective": false
|
||||
"prependAdjective": false,
|
||||
"depth": 1
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
|
|
@ -242,35 +240,95 @@
|
|||
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.</p>",
|
||||
"resource": null,
|
||||
"actions": {
|
||||
"DjbPQowW1OdBD9Zn": {
|
||||
"type": "effect",
|
||||
"_id": "DjbPQowW1OdBD9Zn",
|
||||
"wez1xgy9vScux9wi": {
|
||||
"type": "attack",
|
||||
"_id": "wez1xgy9vScux9wi",
|
||||
"systemPath": "actions",
|
||||
"baseAction": false,
|
||||
"description": "",
|
||||
"chatDisplay": true,
|
||||
"originItem": {
|
||||
"type": "itemCollection"
|
||||
},
|
||||
"actionType": "action",
|
||||
"triggers": [],
|
||||
"cost": [
|
||||
{
|
||||
"consumeOnSuccess": false,
|
||||
"scalable": false,
|
||||
"key": "fear",
|
||||
"value": 1,
|
||||
"keyIsID": false,
|
||||
"step": null,
|
||||
"consumeOnSuccess": false
|
||||
"itemId": null,
|
||||
"step": null
|
||||
}
|
||||
],
|
||||
"uses": {
|
||||
"value": null,
|
||||
"max": "",
|
||||
"recovery": null
|
||||
"recovery": null,
|
||||
"consumeOnSuccess": false
|
||||
},
|
||||
"damage": {
|
||||
"parts": {
|
||||
"hitPoints": {
|
||||
"applyTo": "hitPoints",
|
||||
"resultBased": false,
|
||||
"value": {
|
||||
"multiplier": "flat",
|
||||
"flatMultiplier": 1,
|
||||
"dice": "d6",
|
||||
"bonus": null,
|
||||
"custom": {
|
||||
"enabled": true,
|
||||
"formula": "5"
|
||||
}
|
||||
},
|
||||
"valueAlt": {
|
||||
"multiplier": "flat",
|
||||
"flatMultiplier": 1,
|
||||
"dice": "d6",
|
||||
"bonus": null,
|
||||
"custom": {
|
||||
"enabled": false,
|
||||
"formula": ""
|
||||
}
|
||||
},
|
||||
"base": false,
|
||||
"type": [
|
||||
"physical"
|
||||
]
|
||||
}
|
||||
},
|
||||
"includeBase": false,
|
||||
"direct": false,
|
||||
"groupAttack": "close"
|
||||
},
|
||||
"effects": [],
|
||||
"target": {
|
||||
"type": "self",
|
||||
"type": "any",
|
||||
"amount": null
|
||||
},
|
||||
"effects": [],
|
||||
"roll": {
|
||||
"type": "attack",
|
||||
"trait": null,
|
||||
"difficulty": null,
|
||||
"bonus": null,
|
||||
"advState": "neutral",
|
||||
"diceRolling": {
|
||||
"multiplier": "prof",
|
||||
"flatMultiplier": 1,
|
||||
"dice": "d6",
|
||||
"compare": null,
|
||||
"treshold": null
|
||||
},
|
||||
"useDefault": false
|
||||
},
|
||||
"save": {
|
||||
"trait": null,
|
||||
"difficulty": null,
|
||||
"damageMod": "none"
|
||||
},
|
||||
"name": "Spend Fear",
|
||||
"img": "icons/creatures/abilities/tail-strike-bone-orange.webp",
|
||||
"range": ""
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -125,12 +125,9 @@
|
|||
"src": "systems/daggerheart/assets/icons/documents/actors/dragon-head.svg",
|
||||
"anchorX": 0.5,
|
||||
"anchorY": 0.5,
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"fit": "contain",
|
||||
"scaleX": 1,
|
||||
"scaleY": 1,
|
||||
"rotation": 0,
|
||||
"tint": "#ffffff",
|
||||
"alphaThreshold": 0.75
|
||||
},
|
||||
|
|
@ -181,7 +178,7 @@
|
|||
"saturation": 0,
|
||||
"contrast": 0
|
||||
},
|
||||
"detectionModes": [],
|
||||
"detectionModes": {},
|
||||
"occludable": {
|
||||
"radius": 0
|
||||
},
|
||||
|
|
@ -207,7 +204,8 @@
|
|||
"flags": {},
|
||||
"randomImg": false,
|
||||
"appendNumber": false,
|
||||
"prependAdjective": false
|
||||
"prependAdjective": false,
|
||||
"depth": 1
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
|
|
@ -297,33 +295,95 @@
|
|||
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.</p>",
|
||||
"resource": null,
|
||||
"actions": {
|
||||
"eo7J0v1B5zPHul1M": {
|
||||
"type": "effect",
|
||||
"_id": "eo7J0v1B5zPHul1M",
|
||||
"irZGPKPpGLA6sP2y": {
|
||||
"type": "attack",
|
||||
"_id": "irZGPKPpGLA6sP2y",
|
||||
"systemPath": "actions",
|
||||
"baseAction": false,
|
||||
"description": "",
|
||||
"chatDisplay": true,
|
||||
"originItem": {
|
||||
"type": "itemCollection"
|
||||
},
|
||||
"actionType": "action",
|
||||
"triggers": [],
|
||||
"cost": [
|
||||
{
|
||||
"scalable": false,
|
||||
"key": "fear",
|
||||
"value": 1,
|
||||
"step": null
|
||||
"itemId": null,
|
||||
"step": null,
|
||||
"consumeOnSuccess": false
|
||||
}
|
||||
],
|
||||
"uses": {
|
||||
"value": null,
|
||||
"max": "",
|
||||
"recovery": null
|
||||
"recovery": null,
|
||||
"consumeOnSuccess": false
|
||||
},
|
||||
"damage": {
|
||||
"parts": {
|
||||
"hitPoints": {
|
||||
"applyTo": "hitPoints",
|
||||
"resultBased": false,
|
||||
"value": {
|
||||
"multiplier": "flat",
|
||||
"flatMultiplier": 1,
|
||||
"dice": "d6",
|
||||
"bonus": null,
|
||||
"custom": {
|
||||
"enabled": true,
|
||||
"formula": "10"
|
||||
}
|
||||
},
|
||||
"valueAlt": {
|
||||
"multiplier": "flat",
|
||||
"flatMultiplier": 1,
|
||||
"dice": "d6",
|
||||
"bonus": null,
|
||||
"custom": {
|
||||
"enabled": false,
|
||||
"formula": ""
|
||||
}
|
||||
},
|
||||
"base": false,
|
||||
"type": [
|
||||
"physical"
|
||||
]
|
||||
}
|
||||
},
|
||||
"includeBase": false,
|
||||
"direct": false,
|
||||
"groupAttack": "close"
|
||||
},
|
||||
"effects": [],
|
||||
"target": {
|
||||
"type": "self",
|
||||
"type": "any",
|
||||
"amount": null
|
||||
},
|
||||
"effects": [],
|
||||
"roll": {
|
||||
"type": "attack",
|
||||
"trait": null,
|
||||
"difficulty": null,
|
||||
"bonus": null,
|
||||
"advState": "neutral",
|
||||
"diceRolling": {
|
||||
"multiplier": "prof",
|
||||
"flatMultiplier": 1,
|
||||
"dice": "d6",
|
||||
"compare": null,
|
||||
"treshold": null
|
||||
},
|
||||
"useDefault": false
|
||||
},
|
||||
"save": {
|
||||
"trait": null,
|
||||
"difficulty": null,
|
||||
"damageMod": "none"
|
||||
},
|
||||
"name": "Spend Fear",
|
||||
"img": "icons/creatures/abilities/tail-strike-bone-orange.webp",
|
||||
"range": ""
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -131,12 +131,9 @@
|
|||
"src": "systems/daggerheart/assets/icons/documents/actors/dragon-head.svg",
|
||||
"anchorX": 0.5,
|
||||
"anchorY": 0.5,
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"fit": "contain",
|
||||
"scaleX": 1,
|
||||
"scaleY": 1,
|
||||
"rotation": 0,
|
||||
"tint": "#ffffff",
|
||||
"alphaThreshold": 0.75
|
||||
},
|
||||
|
|
@ -187,7 +184,7 @@
|
|||
"saturation": 0,
|
||||
"contrast": 0
|
||||
},
|
||||
"detectionModes": [],
|
||||
"detectionModes": {},
|
||||
"occludable": {
|
||||
"radius": 0
|
||||
},
|
||||
|
|
@ -213,7 +210,8 @@
|
|||
"flags": {},
|
||||
"randomImg": false,
|
||||
"appendNumber": false,
|
||||
"prependAdjective": false
|
||||
"prependAdjective": false,
|
||||
"depth": 1
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
|
|
@ -251,33 +249,95 @@
|
|||
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all @Lookup[@name] within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.</p>",
|
||||
"resource": null,
|
||||
"actions": {
|
||||
"aoQDb2m32NDxE6ZP": {
|
||||
"type": "effect",
|
||||
"_id": "aoQDb2m32NDxE6ZP",
|
||||
"ferZO3BuiP9zU46m": {
|
||||
"type": "attack",
|
||||
"_id": "ferZO3BuiP9zU46m",
|
||||
"systemPath": "actions",
|
||||
"baseAction": false,
|
||||
"description": "",
|
||||
"chatDisplay": true,
|
||||
"originItem": {
|
||||
"type": "itemCollection"
|
||||
},
|
||||
"actionType": "action",
|
||||
"triggers": [],
|
||||
"cost": [
|
||||
{
|
||||
"consumeOnSuccess": false,
|
||||
"scalable": false,
|
||||
"key": "fear",
|
||||
"value": 1,
|
||||
"itemId": null,
|
||||
"step": null
|
||||
}
|
||||
],
|
||||
"uses": {
|
||||
"value": null,
|
||||
"max": "",
|
||||
"recovery": null
|
||||
"recovery": null,
|
||||
"consumeOnSuccess": false
|
||||
},
|
||||
"damage": {
|
||||
"parts": {
|
||||
"hitPoints": {
|
||||
"applyTo": "hitPoints",
|
||||
"resultBased": false,
|
||||
"value": {
|
||||
"multiplier": "flat",
|
||||
"flatMultiplier": 1,
|
||||
"dice": "d6",
|
||||
"bonus": null,
|
||||
"custom": {
|
||||
"enabled": true,
|
||||
"formula": "2"
|
||||
}
|
||||
},
|
||||
"valueAlt": {
|
||||
"multiplier": "flat",
|
||||
"flatMultiplier": 1,
|
||||
"dice": "d6",
|
||||
"bonus": null,
|
||||
"custom": {
|
||||
"enabled": false,
|
||||
"formula": ""
|
||||
}
|
||||
},
|
||||
"base": false,
|
||||
"type": [
|
||||
"physical"
|
||||
]
|
||||
}
|
||||
},
|
||||
"includeBase": false,
|
||||
"direct": false,
|
||||
"groupAttack": "close"
|
||||
},
|
||||
"effects": [],
|
||||
"target": {
|
||||
"type": "any",
|
||||
"amount": null
|
||||
},
|
||||
"effects": [],
|
||||
"roll": {
|
||||
"type": "attack",
|
||||
"trait": null,
|
||||
"difficulty": null,
|
||||
"bonus": null,
|
||||
"advState": "neutral",
|
||||
"diceRolling": {
|
||||
"multiplier": "prof",
|
||||
"flatMultiplier": 1,
|
||||
"dice": "d6",
|
||||
"compare": null,
|
||||
"treshold": null
|
||||
},
|
||||
"useDefault": false
|
||||
},
|
||||
"save": {
|
||||
"trait": null,
|
||||
"difficulty": null,
|
||||
"damageMod": "none"
|
||||
},
|
||||
"name": "Spend Fear",
|
||||
"img": "icons/creatures/abilities/tail-strike-bone-orange.webp",
|
||||
"range": ""
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -125,12 +125,9 @@
|
|||
"src": "systems/daggerheart/assets/icons/documents/actors/dragon-head.svg",
|
||||
"anchorX": 0.5,
|
||||
"anchorY": 0.5,
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"fit": "contain",
|
||||
"scaleX": 1,
|
||||
"scaleY": 1,
|
||||
"rotation": 0,
|
||||
"tint": "#ffffff",
|
||||
"alphaThreshold": 0.75
|
||||
},
|
||||
|
|
@ -181,7 +178,7 @@
|
|||
"saturation": 0,
|
||||
"contrast": 0
|
||||
},
|
||||
"detectionModes": [],
|
||||
"detectionModes": {},
|
||||
"occludable": {
|
||||
"radius": 0
|
||||
},
|
||||
|
|
@ -207,7 +204,8 @@
|
|||
"flags": {},
|
||||
"randomImg": false,
|
||||
"appendNumber": false,
|
||||
"prependAdjective": false
|
||||
"prependAdjective": false,
|
||||
"depth": 1
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
|
|
@ -245,33 +243,95 @@
|
|||
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.</p>",
|
||||
"resource": null,
|
||||
"actions": {
|
||||
"xTMNAHcoErKuR6TZ": {
|
||||
"type": "effect",
|
||||
"_id": "xTMNAHcoErKuR6TZ",
|
||||
"xFlhxnQWmVvDqQ55": {
|
||||
"type": "attack",
|
||||
"_id": "xFlhxnQWmVvDqQ55",
|
||||
"systemPath": "actions",
|
||||
"baseAction": false,
|
||||
"description": "",
|
||||
"chatDisplay": true,
|
||||
"originItem": {
|
||||
"type": "itemCollection"
|
||||
},
|
||||
"actionType": "action",
|
||||
"triggers": [],
|
||||
"cost": [
|
||||
{
|
||||
"scalable": false,
|
||||
"key": "fear",
|
||||
"value": 1,
|
||||
"step": null
|
||||
"itemId": null,
|
||||
"step": null,
|
||||
"consumeOnSuccess": false
|
||||
}
|
||||
],
|
||||
"uses": {
|
||||
"value": null,
|
||||
"max": "",
|
||||
"recovery": null
|
||||
"recovery": null,
|
||||
"consumeOnSuccess": false
|
||||
},
|
||||
"damage": {
|
||||
"parts": {
|
||||
"hitPoints": {
|
||||
"applyTo": "hitPoints",
|
||||
"resultBased": false,
|
||||
"value": {
|
||||
"multiplier": "flat",
|
||||
"flatMultiplier": 1,
|
||||
"dice": "d6",
|
||||
"bonus": null,
|
||||
"custom": {
|
||||
"enabled": true,
|
||||
"formula": "4"
|
||||
}
|
||||
},
|
||||
"valueAlt": {
|
||||
"multiplier": "flat",
|
||||
"flatMultiplier": 1,
|
||||
"dice": "d6",
|
||||
"bonus": null,
|
||||
"custom": {
|
||||
"enabled": false,
|
||||
"formula": ""
|
||||
}
|
||||
},
|
||||
"base": false,
|
||||
"type": [
|
||||
"physical"
|
||||
]
|
||||
}
|
||||
},
|
||||
"includeBase": false,
|
||||
"direct": false,
|
||||
"groupAttack": "close"
|
||||
},
|
||||
"effects": [],
|
||||
"target": {
|
||||
"type": "self",
|
||||
"type": "any",
|
||||
"amount": null
|
||||
},
|
||||
"effects": [],
|
||||
"roll": {
|
||||
"type": "attack",
|
||||
"trait": null,
|
||||
"difficulty": null,
|
||||
"bonus": null,
|
||||
"advState": "neutral",
|
||||
"diceRolling": {
|
||||
"multiplier": "prof",
|
||||
"flatMultiplier": 1,
|
||||
"dice": "d6",
|
||||
"compare": null,
|
||||
"treshold": null
|
||||
},
|
||||
"useDefault": false
|
||||
},
|
||||
"save": {
|
||||
"trait": null,
|
||||
"difficulty": null,
|
||||
"damageMod": "none"
|
||||
},
|
||||
"name": "Spend Fear",
|
||||
"img": "icons/creatures/abilities/tail-strike-bone-orange.webp",
|
||||
"range": ""
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -125,12 +125,9 @@
|
|||
"src": "systems/daggerheart/assets/icons/documents/actors/dragon-head.svg",
|
||||
"anchorX": 0.5,
|
||||
"anchorY": 0.5,
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"fit": "contain",
|
||||
"scaleX": 1,
|
||||
"scaleY": 1,
|
||||
"rotation": 0,
|
||||
"tint": "#ffffff",
|
||||
"alphaThreshold": 0.75
|
||||
},
|
||||
|
|
@ -181,7 +178,7 @@
|
|||
"saturation": 0,
|
||||
"contrast": 0
|
||||
},
|
||||
"detectionModes": [],
|
||||
"detectionModes": {},
|
||||
"occludable": {
|
||||
"radius": 0
|
||||
},
|
||||
|
|
@ -207,7 +204,8 @@
|
|||
"flags": {},
|
||||
"randomImg": false,
|
||||
"appendNumber": false,
|
||||
"prependAdjective": false
|
||||
"prependAdjective": false,
|
||||
"depth": 1
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
|
|
@ -242,33 +240,95 @@
|
|||
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.</p>",
|
||||
"resource": null,
|
||||
"actions": {
|
||||
"tvQetauskZoHDR5y": {
|
||||
"type": "effect",
|
||||
"_id": "tvQetauskZoHDR5y",
|
||||
"6VKv71tPUIGGIfkZ": {
|
||||
"type": "attack",
|
||||
"_id": "6VKv71tPUIGGIfkZ",
|
||||
"systemPath": "actions",
|
||||
"baseAction": false,
|
||||
"description": "",
|
||||
"chatDisplay": true,
|
||||
"originItem": {
|
||||
"type": "itemCollection"
|
||||
},
|
||||
"actionType": "action",
|
||||
"triggers": [],
|
||||
"cost": [
|
||||
{
|
||||
"consumeOnSuccess": false,
|
||||
"scalable": false,
|
||||
"key": "fear",
|
||||
"value": 1,
|
||||
"itemId": null,
|
||||
"step": null
|
||||
}
|
||||
],
|
||||
"uses": {
|
||||
"value": null,
|
||||
"max": "",
|
||||
"recovery": null
|
||||
"recovery": null,
|
||||
"consumeOnSuccess": false
|
||||
},
|
||||
"damage": {
|
||||
"parts": {
|
||||
"hitPoints": {
|
||||
"applyTo": "hitPoints",
|
||||
"resultBased": false,
|
||||
"value": {
|
||||
"multiplier": "flat",
|
||||
"flatMultiplier": 1,
|
||||
"dice": "d6",
|
||||
"bonus": null,
|
||||
"custom": {
|
||||
"enabled": true,
|
||||
"formula": "11"
|
||||
}
|
||||
},
|
||||
"valueAlt": {
|
||||
"multiplier": "flat",
|
||||
"flatMultiplier": 1,
|
||||
"dice": "d6",
|
||||
"bonus": null,
|
||||
"custom": {
|
||||
"enabled": false,
|
||||
"formula": ""
|
||||
}
|
||||
},
|
||||
"base": false,
|
||||
"type": [
|
||||
"physical"
|
||||
]
|
||||
}
|
||||
},
|
||||
"includeBase": false,
|
||||
"direct": false,
|
||||
"groupAttack": "close"
|
||||
},
|
||||
"effects": [],
|
||||
"target": {
|
||||
"type": "self",
|
||||
"type": "any",
|
||||
"amount": null
|
||||
},
|
||||
"effects": [],
|
||||
"roll": {
|
||||
"type": "attack",
|
||||
"trait": null,
|
||||
"difficulty": null,
|
||||
"bonus": null,
|
||||
"advState": "neutral",
|
||||
"diceRolling": {
|
||||
"multiplier": "prof",
|
||||
"flatMultiplier": 1,
|
||||
"dice": "d6",
|
||||
"compare": null,
|
||||
"treshold": null
|
||||
},
|
||||
"useDefault": false
|
||||
},
|
||||
"save": {
|
||||
"trait": null,
|
||||
"difficulty": null,
|
||||
"damageMod": "none"
|
||||
},
|
||||
"name": "Spend Fear",
|
||||
"img": "icons/creatures/abilities/tail-strike-bone-orange.webp",
|
||||
"range": ""
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -125,12 +125,9 @@
|
|||
"src": "systems/daggerheart/assets/icons/documents/actors/dragon-head.svg",
|
||||
"anchorX": 0.5,
|
||||
"anchorY": 0.5,
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"fit": "contain",
|
||||
"scaleX": 1,
|
||||
"scaleY": 1,
|
||||
"rotation": 0,
|
||||
"tint": "#ffffff",
|
||||
"alphaThreshold": 0.75
|
||||
},
|
||||
|
|
@ -181,7 +178,7 @@
|
|||
"saturation": 0,
|
||||
"contrast": 0
|
||||
},
|
||||
"detectionModes": [],
|
||||
"detectionModes": {},
|
||||
"occludable": {
|
||||
"radius": 0
|
||||
},
|
||||
|
|
@ -207,7 +204,8 @@
|
|||
"flags": {},
|
||||
"randomImg": false,
|
||||
"appendNumber": false,
|
||||
"prependAdjective": false
|
||||
"prependAdjective": false,
|
||||
"depth": 1
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
|
|
@ -245,33 +243,95 @@
|
|||
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.</p>",
|
||||
"resource": null,
|
||||
"actions": {
|
||||
"DJBNtd3hWjwsjPwq": {
|
||||
"type": "effect",
|
||||
"_id": "DJBNtd3hWjwsjPwq",
|
||||
"8wRrAWHU0xHW4zuE": {
|
||||
"type": "attack",
|
||||
"_id": "8wRrAWHU0xHW4zuE",
|
||||
"systemPath": "actions",
|
||||
"baseAction": false,
|
||||
"description": "",
|
||||
"chatDisplay": true,
|
||||
"originItem": {
|
||||
"type": "itemCollection"
|
||||
},
|
||||
"actionType": "action",
|
||||
"triggers": [],
|
||||
"cost": [
|
||||
{
|
||||
"scalable": false,
|
||||
"key": "fear",
|
||||
"value": 1,
|
||||
"step": null
|
||||
"itemId": null,
|
||||
"step": null,
|
||||
"consumeOnSuccess": false
|
||||
}
|
||||
],
|
||||
"uses": {
|
||||
"value": null,
|
||||
"max": "",
|
||||
"recovery": null
|
||||
"recovery": null,
|
||||
"consumeOnSuccess": false
|
||||
},
|
||||
"damage": {
|
||||
"parts": {
|
||||
"hitPoints": {
|
||||
"applyTo": "hitPoints",
|
||||
"resultBased": false,
|
||||
"value": {
|
||||
"multiplier": "flat",
|
||||
"flatMultiplier": 1,
|
||||
"dice": "d6",
|
||||
"bonus": null,
|
||||
"custom": {
|
||||
"enabled": true,
|
||||
"formula": "2"
|
||||
}
|
||||
},
|
||||
"valueAlt": {
|
||||
"multiplier": "flat",
|
||||
"flatMultiplier": 1,
|
||||
"dice": "d6",
|
||||
"bonus": null,
|
||||
"custom": {
|
||||
"enabled": false,
|
||||
"formula": ""
|
||||
}
|
||||
},
|
||||
"base": false,
|
||||
"type": [
|
||||
"physical"
|
||||
]
|
||||
}
|
||||
},
|
||||
"includeBase": false,
|
||||
"direct": false,
|
||||
"groupAttack": "close"
|
||||
},
|
||||
"effects": [],
|
||||
"target": {
|
||||
"type": "self",
|
||||
"type": "any",
|
||||
"amount": null
|
||||
},
|
||||
"effects": [],
|
||||
"roll": {
|
||||
"type": "attack",
|
||||
"trait": null,
|
||||
"difficulty": null,
|
||||
"bonus": null,
|
||||
"advState": "neutral",
|
||||
"diceRolling": {
|
||||
"multiplier": "prof",
|
||||
"flatMultiplier": 1,
|
||||
"dice": "d6",
|
||||
"compare": null,
|
||||
"treshold": null
|
||||
},
|
||||
"useDefault": false
|
||||
},
|
||||
"save": {
|
||||
"trait": null,
|
||||
"difficulty": null,
|
||||
"damageMod": "none"
|
||||
},
|
||||
"name": "Spend Fear",
|
||||
"img": "icons/creatures/abilities/tail-strike-bone-orange.webp",
|
||||
"range": ""
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -125,12 +125,9 @@
|
|||
"src": "systems/daggerheart/assets/icons/documents/actors/dragon-head.svg",
|
||||
"anchorX": 0.5,
|
||||
"anchorY": 0.5,
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"fit": "contain",
|
||||
"scaleX": 1,
|
||||
"scaleY": 1,
|
||||
"rotation": 0,
|
||||
"tint": "#ffffff",
|
||||
"alphaThreshold": 0.75
|
||||
},
|
||||
|
|
@ -181,7 +178,7 @@
|
|||
"saturation": 0,
|
||||
"contrast": 0
|
||||
},
|
||||
"detectionModes": [],
|
||||
"detectionModes": {},
|
||||
"occludable": {
|
||||
"radius": 0
|
||||
},
|
||||
|
|
@ -207,7 +204,8 @@
|
|||
"flags": {},
|
||||
"randomImg": false,
|
||||
"appendNumber": false,
|
||||
"prependAdjective": false
|
||||
"prependAdjective": false,
|
||||
"depth": 1
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
|
|
@ -245,33 +243,95 @@
|
|||
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.</p>",
|
||||
"resource": null,
|
||||
"actions": {
|
||||
"ghgFZskDiizJDjcn": {
|
||||
"type": "effect",
|
||||
"_id": "ghgFZskDiizJDjcn",
|
||||
"K3pF2DBnR9zJ90W8": {
|
||||
"type": "attack",
|
||||
"_id": "K3pF2DBnR9zJ90W8",
|
||||
"systemPath": "actions",
|
||||
"baseAction": false,
|
||||
"description": "",
|
||||
"chatDisplay": true,
|
||||
"originItem": {
|
||||
"type": "itemCollection"
|
||||
},
|
||||
"actionType": "action",
|
||||
"triggers": [],
|
||||
"cost": [
|
||||
{
|
||||
"consumeOnSuccess": false,
|
||||
"scalable": false,
|
||||
"key": "fear",
|
||||
"value": 1,
|
||||
"itemId": null,
|
||||
"step": null
|
||||
}
|
||||
],
|
||||
"uses": {
|
||||
"value": null,
|
||||
"max": "",
|
||||
"recovery": null
|
||||
"recovery": null,
|
||||
"consumeOnSuccess": false
|
||||
},
|
||||
"damage": {
|
||||
"parts": {
|
||||
"hitPoints": {
|
||||
"applyTo": "hitPoints",
|
||||
"resultBased": false,
|
||||
"value": {
|
||||
"multiplier": "flat",
|
||||
"flatMultiplier": 1,
|
||||
"dice": "d6",
|
||||
"bonus": null,
|
||||
"custom": {
|
||||
"enabled": true,
|
||||
"formula": "3"
|
||||
}
|
||||
},
|
||||
"valueAlt": {
|
||||
"multiplier": "flat",
|
||||
"flatMultiplier": 1,
|
||||
"dice": "d6",
|
||||
"bonus": null,
|
||||
"custom": {
|
||||
"enabled": false,
|
||||
"formula": ""
|
||||
}
|
||||
},
|
||||
"base": false,
|
||||
"type": [
|
||||
"physical"
|
||||
]
|
||||
}
|
||||
},
|
||||
"includeBase": false,
|
||||
"direct": false,
|
||||
"groupAttack": "close"
|
||||
},
|
||||
"effects": [],
|
||||
"target": {
|
||||
"type": "self",
|
||||
"type": "any",
|
||||
"amount": null
|
||||
},
|
||||
"effects": [],
|
||||
"roll": {
|
||||
"type": "attack",
|
||||
"trait": null,
|
||||
"difficulty": null,
|
||||
"bonus": null,
|
||||
"advState": "neutral",
|
||||
"diceRolling": {
|
||||
"multiplier": "prof",
|
||||
"flatMultiplier": 1,
|
||||
"dice": "d6",
|
||||
"compare": null,
|
||||
"treshold": null
|
||||
},
|
||||
"useDefault": false
|
||||
},
|
||||
"save": {
|
||||
"trait": null,
|
||||
"difficulty": null,
|
||||
"damageMod": "none"
|
||||
},
|
||||
"name": "Spend Fear",
|
||||
"img": "icons/creatures/abilities/tail-strike-bone-orange.webp",
|
||||
"range": ""
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -125,12 +125,9 @@
|
|||
"src": "systems/daggerheart/assets/icons/documents/actors/dragon-head.svg",
|
||||
"anchorX": 0.5,
|
||||
"anchorY": 0.5,
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"fit": "contain",
|
||||
"scaleX": 1,
|
||||
"scaleY": 1,
|
||||
"rotation": 0,
|
||||
"tint": "#ffffff",
|
||||
"alphaThreshold": 0.75
|
||||
},
|
||||
|
|
@ -181,7 +178,7 @@
|
|||
"saturation": 0,
|
||||
"contrast": 0
|
||||
},
|
||||
"detectionModes": [],
|
||||
"detectionModes": {},
|
||||
"occludable": {
|
||||
"radius": 0
|
||||
},
|
||||
|
|
@ -207,7 +204,8 @@
|
|||
"flags": {},
|
||||
"randomImg": false,
|
||||
"appendNumber": false,
|
||||
"prependAdjective": false
|
||||
"prependAdjective": false,
|
||||
"depth": 1
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
|
|
@ -245,33 +243,95 @@
|
|||
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.</p>",
|
||||
"resource": null,
|
||||
"actions": {
|
||||
"Sz55uB8xkoNytLwJ": {
|
||||
"type": "effect",
|
||||
"_id": "Sz55uB8xkoNytLwJ",
|
||||
"6rdwJKwsSCO4R0Ty": {
|
||||
"type": "attack",
|
||||
"_id": "6rdwJKwsSCO4R0Ty",
|
||||
"systemPath": "actions",
|
||||
"baseAction": false,
|
||||
"description": "",
|
||||
"chatDisplay": true,
|
||||
"originItem": {
|
||||
"type": "itemCollection"
|
||||
},
|
||||
"actionType": "action",
|
||||
"triggers": [],
|
||||
"cost": [
|
||||
{
|
||||
"scalable": false,
|
||||
"key": "fear",
|
||||
"value": 1,
|
||||
"step": null
|
||||
"itemId": null,
|
||||
"step": null,
|
||||
"consumeOnSuccess": false
|
||||
}
|
||||
],
|
||||
"uses": {
|
||||
"value": null,
|
||||
"max": "",
|
||||
"recovery": null
|
||||
"recovery": null,
|
||||
"consumeOnSuccess": false
|
||||
},
|
||||
"damage": {
|
||||
"parts": {
|
||||
"hitPoints": {
|
||||
"applyTo": "hitPoints",
|
||||
"resultBased": false,
|
||||
"value": {
|
||||
"multiplier": "flat",
|
||||
"flatMultiplier": 1,
|
||||
"dice": "d6",
|
||||
"bonus": null,
|
||||
"custom": {
|
||||
"enabled": true,
|
||||
"formula": "1"
|
||||
}
|
||||
},
|
||||
"valueAlt": {
|
||||
"multiplier": "flat",
|
||||
"flatMultiplier": 1,
|
||||
"dice": "d6",
|
||||
"bonus": null,
|
||||
"custom": {
|
||||
"enabled": false,
|
||||
"formula": ""
|
||||
}
|
||||
},
|
||||
"base": false,
|
||||
"type": [
|
||||
"physical"
|
||||
]
|
||||
}
|
||||
},
|
||||
"includeBase": false,
|
||||
"direct": false,
|
||||
"groupAttack": "close"
|
||||
},
|
||||
"effects": [],
|
||||
"target": {
|
||||
"type": "self",
|
||||
"type": "any",
|
||||
"amount": null
|
||||
},
|
||||
"effects": [],
|
||||
"roll": {
|
||||
"type": "attack",
|
||||
"trait": null,
|
||||
"difficulty": null,
|
||||
"bonus": null,
|
||||
"advState": "neutral",
|
||||
"diceRolling": {
|
||||
"multiplier": "prof",
|
||||
"flatMultiplier": 1,
|
||||
"dice": "d6",
|
||||
"compare": null,
|
||||
"treshold": null
|
||||
},
|
||||
"useDefault": false
|
||||
},
|
||||
"save": {
|
||||
"trait": null,
|
||||
"difficulty": null,
|
||||
"damageMod": "none"
|
||||
},
|
||||
"name": "Spend Fear",
|
||||
"img": "icons/creatures/abilities/tail-strike-bone-orange.webp",
|
||||
"range": ""
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -164,12 +164,9 @@
|
|||
"src": "systems/daggerheart/assets/icons/documents/actors/dragon-head.svg",
|
||||
"anchorX": 0.5,
|
||||
"anchorY": 0.5,
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"fit": "contain",
|
||||
"scaleX": 1,
|
||||
"scaleY": 1,
|
||||
"rotation": 0,
|
||||
"tint": "#ffffff",
|
||||
"alphaThreshold": 0.75
|
||||
},
|
||||
|
|
@ -220,7 +217,7 @@
|
|||
"saturation": 0,
|
||||
"contrast": 0
|
||||
},
|
||||
"detectionModes": [],
|
||||
"detectionModes": {},
|
||||
"occludable": {
|
||||
"radius": 0
|
||||
},
|
||||
|
|
@ -246,7 +243,8 @@
|
|||
"flags": {},
|
||||
"randomImg": false,
|
||||
"appendNumber": false,
|
||||
"prependAdjective": false
|
||||
"prependAdjective": false,
|
||||
"depth": 1
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
|
|
@ -284,33 +282,95 @@
|
|||
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.</p>",
|
||||
"resource": null,
|
||||
"actions": {
|
||||
"ZC5pKIb9N82vgMWu": {
|
||||
"type": "effect",
|
||||
"_id": "ZC5pKIb9N82vgMWu",
|
||||
"V58Ry90tvIjvfDTZ": {
|
||||
"type": "attack",
|
||||
"_id": "V58Ry90tvIjvfDTZ",
|
||||
"systemPath": "actions",
|
||||
"baseAction": false,
|
||||
"description": "",
|
||||
"chatDisplay": true,
|
||||
"originItem": {
|
||||
"type": "itemCollection"
|
||||
},
|
||||
"actionType": "action",
|
||||
"triggers": [],
|
||||
"cost": [
|
||||
{
|
||||
"consumeOnSuccess": false,
|
||||
"scalable": false,
|
||||
"key": "fear",
|
||||
"value": 1,
|
||||
"itemId": null,
|
||||
"step": null
|
||||
}
|
||||
],
|
||||
"uses": {
|
||||
"value": null,
|
||||
"max": "",
|
||||
"recovery": null
|
||||
"recovery": null,
|
||||
"consumeOnSuccess": false
|
||||
},
|
||||
"damage": {
|
||||
"parts": {
|
||||
"hitPoints": {
|
||||
"applyTo": "hitPoints",
|
||||
"resultBased": false,
|
||||
"value": {
|
||||
"multiplier": "flat",
|
||||
"flatMultiplier": 1,
|
||||
"dice": "d6",
|
||||
"bonus": null,
|
||||
"custom": {
|
||||
"enabled": true,
|
||||
"formula": "2"
|
||||
}
|
||||
},
|
||||
"valueAlt": {
|
||||
"multiplier": "flat",
|
||||
"flatMultiplier": 1,
|
||||
"dice": "d6",
|
||||
"bonus": null,
|
||||
"custom": {
|
||||
"enabled": false,
|
||||
"formula": ""
|
||||
}
|
||||
},
|
||||
"base": false,
|
||||
"type": [
|
||||
"physical"
|
||||
]
|
||||
}
|
||||
},
|
||||
"includeBase": false,
|
||||
"direct": false,
|
||||
"groupAttack": "close"
|
||||
},
|
||||
"effects": [],
|
||||
"target": {
|
||||
"type": "self",
|
||||
"type": "any",
|
||||
"amount": null
|
||||
},
|
||||
"effects": [],
|
||||
"roll": {
|
||||
"type": "attack",
|
||||
"trait": null,
|
||||
"difficulty": null,
|
||||
"bonus": null,
|
||||
"advState": "neutral",
|
||||
"diceRolling": {
|
||||
"multiplier": "prof",
|
||||
"flatMultiplier": 1,
|
||||
"dice": "d6",
|
||||
"compare": null,
|
||||
"treshold": null
|
||||
},
|
||||
"useDefault": false
|
||||
},
|
||||
"save": {
|
||||
"trait": null,
|
||||
"difficulty": null,
|
||||
"damageMod": "none"
|
||||
},
|
||||
"name": "Spend Fear",
|
||||
"img": "icons/creatures/abilities/tail-strike-bone-orange.webp",
|
||||
"range": ""
|
||||
}
|
||||
},
|
||||
|
|
|
|||
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