diff --git a/.editorconfig b/.editorconfig
index 8bbc2b52..6cfef2fc 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,3 +1,5 @@
[*]
indent_size = 4
indent_style = spaces
+[*.yml]
+indent_size = 2
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 00000000..fd9b922e
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -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
\ No newline at end of file
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index e245c7fa..553a1a17 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -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
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index b9099005..261c26bd 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -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.
diff --git a/daggerheart.mjs b/daggerheart.mjs
index 240d8704..7593d935 100644
--- a/daggerheart.mjs
+++ b/daggerheart.mjs
@@ -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
diff --git a/eslint.config.mjs b/eslint.config.mjs
new file mode 100644
index 00000000..ce2bb86f
--- /dev/null
+++ b/eslint.config.mjs
@@ -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'
+ }
+ }
+]);
diff --git a/lang/en.json b/lang/en.json
index d19dfb58..a824d379 100755
--- a/lang/en.json
+++ b/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",
diff --git a/module/applications/characterCreation/characterCreation.mjs b/module/applications/characterCreation/characterCreation.mjs
index e6c0f299..936bb79d 100644
--- a/module/applications/characterCreation/characterCreation.mjs
+++ b/module/applications/characterCreation/characterCreation.mjs
@@ -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);
}
diff --git a/module/applications/dialogs/_module.mjs b/module/applications/dialogs/_module.mjs
index a479100a..c866f1cd 100644
--- a/module/applications/dialogs/_module.mjs
+++ b/module/applications/dialogs/_module.mjs
@@ -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';
diff --git a/module/applications/dialogs/d20RollDialog.mjs b/module/applications/dialogs/d20RollDialog.mjs
index 64fa168a..067aa473 100644
--- a/module/applications/dialogs/d20RollDialog.mjs
+++ b/module/applications/dialogs/d20RollDialog.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();
}
diff --git a/module/applications/dialogs/damageDialog.mjs b/module/applications/dialogs/damageDialog.mjs
index 97f1c538..46d3d41f 100644
--- a/module/applications/dialogs/damageDialog.mjs
+++ b/module/applications/dialogs/damageDialog.mjs
@@ -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();
}
diff --git a/module/applications/dialogs/downtime.mjs b/module/applications/dialogs/downtime.mjs
index 3475dee7..989e4625 100644
--- a/module/applications/dialogs/downtime.mjs
+++ b/module/applications/dialogs/downtime.mjs
@@ -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 });
}
diff --git a/module/applications/dialogs/group-roll-dialog.mjs b/module/applications/dialogs/group-roll-dialog.mjs
deleted file mode 100644
index 8a3c43d6..00000000
--- a/module/applications/dialogs/group-roll-dialog.mjs
+++ /dev/null
@@ -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 ? `${matchText} ` : ''}${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 ? `${matchText} ` : ''}${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();
- }
-}
diff --git a/module/applications/dialogs/groupRollDialog.mjs b/module/applications/dialogs/groupRollDialog.mjs
new file mode 100644
index 00000000..a47dd0a8
--- /dev/null
+++ b/module/applications/dialogs/groupRollDialog.mjs
@@ -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', '
');
+ initializationPart.insertAdjacentHTML(
+ 'afterend',
+ `${game.i18n.localize('DAGGERHEART.APPLICATIONS.GroupRollSelect.aidingCharacters')}
`
+ );
+
+ 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 });
+ }
+}
diff --git a/module/applications/dialogs/itemTransfer.mjs b/module/applications/dialogs/itemTransfer.mjs
index ad3cf103..42e3a727 100644
--- a/module/applications/dialogs/itemTransfer.mjs
+++ b/module/applications/dialogs/itemTransfer.mjs
@@ -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
};
}
diff --git a/module/applications/dialogs/tagTeamDialog.mjs b/module/applications/dialogs/tagTeamDialog.mjs
index 5236afb8..6e1654b0 100644
--- a/module/applications/dialogs/tagTeamDialog.mjs
+++ b/module/applications/dialogs/tagTeamDialog.mjs
@@ -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;
diff --git a/module/applications/hud/tokenHUD.mjs b/module/applications/hud/tokenHUD.mjs
index 77caaaff..943f3506 100644
--- a/module/applications/hud/tokenHUD.mjs
+++ b/module/applications/hud/tokenHUD.mjs
@@ -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
}))
diff --git a/module/applications/settings/appearanceSettings.mjs b/module/applications/settings/appearanceSettings.mjs
index 151648e1..9de9e752 100644
--- a/module/applications/settings/appearanceSettings.mjs
+++ b/module/applications/settings/appearanceSettings.mjs
@@ -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;
}
diff --git a/module/applications/sheets/actors/adversary.mjs b/module/applications/sheets/actors/adversary.mjs
index d8a3df29..04be3efb 100644
--- a/module/applications/sheets/actors/adversary.mjs
+++ b/module/applications/sheets/actors/adversary.mjs
@@ -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'
},
diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs
index f2686fdd..c59dd64e 100644
--- a/module/applications/sheets/actors/character.mjs
+++ b/module/applications/sheets/actors/character.mjs
@@ -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: ` `
}));
@@ -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: ` `
}));
diff --git a/module/applications/sheets/actors/party.mjs b/module/applications/sheets/actors/party.mjs
index c5e77112..a7eeccdf 100644
--- a/module/applications/sheets/actors/party.mjs
+++ b/module/applications/sheets/actors/party.mjs
@@ -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) {
diff --git a/module/applications/sheets/api/application-mixin.mjs b/module/applications/sheets/api/application-mixin.mjs
index 64f62405..5faa5d5c 100644
--- a/module/applications/sheets/api/application-mixin.mjs
+++ b/module/applications/sheets/api/application-mixin.mjs
@@ -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: ` `
}));
@@ -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'
}
]
diff --git a/module/applications/sheets/api/base-actor.mjs b/module/applications/sheets/api/base-actor.mjs
index 4a550d72..e23a4426 100644
--- a/module/applications/sheets/api/base-actor.mjs
+++ b/module/applications/sheets/api/base-actor.mjs
@@ -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
diff --git a/module/applications/sheets/items/ancestry.mjs b/module/applications/sheets/items/ancestry.mjs
index 8c0a5620..cf040e02 100644
--- a/module/applications/sheets/items/ancestry.mjs
+++ b/module/applications/sheets/items/ancestry.mjs
@@ -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);
+ }
}
}
}
diff --git a/module/applications/sheets/items/feature.mjs b/module/applications/sheets/items/feature.mjs
index 6ff98ca7..7f9028d5 100644
--- a/module/applications/sheets/items/feature.mjs
+++ b/module/applications/sheets/items/feature.mjs
@@ -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);
diff --git a/module/applications/sidebar/tabs/actorDirectory.mjs b/module/applications/sidebar/tabs/actorDirectory.mjs
index 9d8f16e1..89da1426 100644
--- a/module/applications/sidebar/tabs/actorDirectory.mjs
+++ b/module/applications/sidebar/tabs/actorDirectory.mjs
@@ -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: ` `,
- 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: ` `,
+ 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: ` `,
+ 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;
}
diff --git a/module/applications/ui/chatLog.mjs b/module/applications/ui/chatLog.mjs
index 8cbacb09..34b25591 100644
--- a/module/applications/ui/chatLog.mjs
+++ b/module/applications/ui/chatLog.mjs
@@ -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: ' ',
- // 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: ' ',
- 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);
diff --git a/module/applications/ui/combatTracker.mjs b/module/applications/ui/combatTracker.mjs
index 1043e128..fb19a17e 100644
--- a/module/applications/ui/combatTracker.mjs
+++ b/module/applications/ui/combatTracker.mjs
@@ -84,15 +84,15 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
_getCombatContextOptions() {
return [
{
- name: 'COMBAT.ClearMovementHistories',
+ label: 'COMBAT.ClearMovementHistories',
icon: ' ',
- 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: ' ',
- condition: () => game.user.isGM && !!this.viewed,
+ visible: () => game.user.isGM && !!this.viewed,
callback: () => this.viewed.endCombat()
}
];
diff --git a/module/applications/ui/countdowns.mjs b/module/applications/ui/countdowns.mjs
index 0f83a05f..79a59a07 100644
--- a/module/applications/ui/countdowns.mjs
+++ b/module/applications/ui/countdowns.mjs
@@ -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
diff --git a/module/applications/ui/fearTracker.mjs b/module/applications/ui/fearTracker.mjs
index 82dda215..4e5e1132 100644
--- a/module/applications/ui/fearTracker.mjs
+++ b/module/applications/ui/fearTracker.mjs
@@ -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
diff --git a/module/applications/ui/itemBrowser.mjs b/module/applications/ui/itemBrowser.mjs
index 2d2e8cdc..67a16f6a 100644
--- a/module/applications/ui/itemBrowser.mjs
+++ b/module/applications/ui/itemBrowser.mjs
@@ -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() {
diff --git a/module/applications/ux/filter-menu.mjs b/module/applications/ux/filter-menu.mjs
index 065d08f9..791c0e1f 100644
--- a/module/applications/ux/filter-menu.mjs
+++ b/module/applications/ux/filter-menu.mjs
@@ -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',
diff --git a/module/canvas/tokens.mjs b/module/canvas/tokens.mjs
index 9813cd48..9ca140e0 100644
--- a/module/canvas/tokens.mjs
+++ b/module/canvas/tokens.mjs
@@ -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 {}
diff --git a/module/config/generalConfig.mjs b/module/config/generalConfig.mjs
index f3484e43..58a9dff9 100644
--- a/module/config/generalConfig.mjs
+++ b/module/config/generalConfig.mjs
@@ -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'
}
};
diff --git a/module/config/hooksConfig.mjs b/module/config/hooksConfig.mjs
index 8d04be6d..c0930d90 100644
--- a/module/config/hooksConfig.mjs
+++ b/module/config/hooksConfig.mjs
@@ -1,5 +1,6 @@
export const hooksConfig = {
effectDisplayToggle: 'DHEffectDisplayToggle',
lockedTooltipDismissed: 'DHLockedTooltipDismissed',
- tagTeamStart: 'DHTagTeamRollStart'
+ tagTeamStart: 'DHTagTeamRollStart',
+ groupRollStart: 'DHGroupRollStart'
};
diff --git a/module/config/itemBrowserConfig.mjs b/module/config/itemBrowserConfig.mjs
index 3b10409c..5c0a219b 100644
--- a/module/config/itemBrowserConfig.mjs
+++ b/module/config/itemBrowserConfig.mjs
@@ -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: [
diff --git a/module/config/settingsConfig.mjs b/module/config/settingsConfig.mjs
index de4d96be..50841084 100644
--- a/module/config/settingsConfig.mjs
+++ b/module/config/settingsConfig.mjs
@@ -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'
}
};
diff --git a/module/data/_module.mjs b/module/data/_module.mjs
index 0e7e295e..cd691ee1 100644
--- a/module/data/_module.mjs
+++ b/module/data/_module.mjs
@@ -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';
diff --git a/module/data/action/attackAction.mjs b/module/data/action/attackAction.mjs
index a2d47309..f54ea282 100644
--- a/module/data/action/attackAction.mjs
+++ b/module/data/action/attackAction.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);
}
diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs
index 1f75d382..23f4776a 100644
--- a/module/data/action/baseAction.mjs
+++ b/module/data/action/baseAction.mjs
@@ -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;
}
diff --git a/module/data/activeEffect/changeTypes/armor.mjs b/module/data/activeEffect/changeTypes/armor.mjs
index 713ef03d..217ff9dd 100644
--- a/module/data/activeEffect/changeTypes/armor.mjs
+++ b/module/data/activeEffect/changeTypes/armor.mjs
@@ -82,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
},
@@ -96,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
},
@@ -111,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;
diff --git a/module/data/actor/adversary.mjs b/module/data/actor/adversary.mjs
index 73673896..d30e090a 100644
--- a/module/data/actor/adversary.mjs
+++ b/module/data/actor/adversary.mjs
@@ -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) {
diff --git a/module/data/actor/base.mjs b/module/data/actor/base.mjs
index 89ba5db2..13ff0a38 100644
--- a/module/data/actor/base.mjs
+++ b/module/data/actor/base.mjs
@@ -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.
diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs
index 2878ad0c..1bb3560f 100644
--- a/module/data/actor/character.mjs
+++ b/module/data/actor/character.mjs
@@ -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;
diff --git a/module/data/actor/companion.mjs b/module/data/actor/companion.mjs
index 81d0aa8a..538031fb 100644
--- a/module/data/actor/companion.mjs
+++ b/module/data/actor/companion.mjs
@@ -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;
diff --git a/module/data/actor/environment.mjs b/module/data/actor/environment.mjs
index e06f038c..e037e004 100644
--- a/module/data/actor/environment.mjs
+++ b/module/data/actor/environment.mjs
@@ -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) {
diff --git a/module/data/actor/party.mjs b/module/data/actor/party.mjs
index 2c797803..93596cda 100644
--- a/module/data/actor/party.mjs
+++ b/module/data/actor/party.mjs
@@ -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);
}
}
diff --git a/module/data/chat-message/_modules.mjs b/module/data/chat-message/_modules.mjs
index c671de31..450d1ba2 100644
--- a/module/data/chat-message/_modules.mjs
+++ b/module/data/chat-message/_modules.mjs
@@ -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
};
diff --git a/module/data/chat-message/actorRoll.mjs b/module/data/chat-message/actorRoll.mjs
index 89f34949..eaa1cdc2 100644
--- a/module/data/chat-message/actorRoll.mjs
+++ b/module/data/chat-message/actorRoll.mjs
@@ -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 })
};
diff --git a/module/data/chat-message/groupRoll.mjs b/module/data/chat-message/groupRoll.mjs
deleted file mode 100644
index a5308323..00000000
--- a/module/data/chat-message/groupRoll.mjs
+++ /dev/null
@@ -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;
- }
-}
diff --git a/module/data/countdowns.mjs b/module/data/countdowns.mjs
index b944bf73..7d27197d 100644
--- a/module/data/countdowns.mjs
+++ b/module/data/countdowns.mjs
@@ -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;
diff --git a/module/data/fields/action/countdownField.mjs b/module/data/fields/action/countdownField.mjs
index f49e71ad..719ca749 100644
--- a/module/data/fields/action/countdownField.mjs
+++ b/module/data/fields/action/countdownField.mjs
@@ -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,
diff --git a/module/data/fields/action/damageField.mjs b/module/data/fields/action/damageField.mjs
index 5d40a470..30a5ad7c 100644
--- a/module/data/fields/action/damageField.mjs
+++ b/module/data/fields/action/damageField.mjs
@@ -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')
}
)
};
diff --git a/module/data/fields/action/summonField.mjs b/module/data/fields/action/summonField.mjs
index dce6414c..ec7881f7 100644
--- a/module/data/fields/action/summonField.mjs
+++ b/module/data/fields/action/summonField.mjs
@@ -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();
}
}
diff --git a/module/data/fields/actorField.mjs b/module/data/fields/actorField.mjs
index 7a57aa46..d6b58675 100644
--- a/module/data/fields/actorField.mjs
+++ b/module/data/fields/actorField.mjs
@@ -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 };
diff --git a/module/data/groupRollData.mjs b/module/data/groupRollData.mjs
new file mode 100644
index 00000000..78a06b13
--- /dev/null
+++ b/module/data/groupRollData.mjs
@@ -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;
+ }
+}
diff --git a/module/data/item/base.mjs b/module/data/item/base.mjs
index 21a11149..cc198dc4 100644
--- a/module/data/item/base.mjs
+++ b/module/data/item/base.mjs
@@ -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;
}
diff --git a/module/data/item/consumable.mjs b/module/data/item/consumable.mjs
index ab527967..e83a1a53 100644
--- a/module/data/item/consumable.mjs
+++ b/module/data/item/consumable.mjs
@@ -7,7 +7,6 @@ export default class DHConsumable extends BaseDataItem {
label: 'TYPES.Item.consumable',
type: 'consumable',
hasDescription: true,
- isQuantifiable: true,
isInventoryItem: true,
hasActions: true
});
diff --git a/module/data/item/loot.mjs b/module/data/item/loot.mjs
index cdb0855e..d4092934 100644
--- a/module/data/item/loot.mjs
+++ b/module/data/item/loot.mjs
@@ -7,7 +7,6 @@ export default class DHLoot extends BaseDataItem {
label: 'TYPES.Item.loot',
type: 'loot',
hasDescription: true,
- isQuantifiable: true,
isInventoryItem: true,
hasActions: true
});
diff --git a/module/data/item/weapon.mjs b/module/data/item/weapon.mjs
index 9335037c..75e6dc8e 100644
--- a/module/data/item/weapon.mjs
+++ b/module/data/item/weapon.mjs
@@ -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() {
diff --git a/module/data/settings/Appearance.mjs b/module/data/settings/Appearance.mjs
index 9da3afac..4db27be0 100644
--- a/module/data/settings/Appearance.mjs
+++ b/module/data/settings/Appearance.mjs
@@ -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()
})
});
diff --git a/module/data/tagTeamData.mjs b/module/data/tagTeamData.mjs
index 25158606..640c2f6c 100644
--- a/module/data/tagTeamData.mjs
+++ b/module/data/tagTeamData.mjs
@@ -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 }),
diff --git a/module/dice/damageRoll.mjs b/module/dice/damageRoll.mjs
index 1d680a1b..98fd8401 100644
--- a/module/dice/damageRoll.mjs
+++ b/module/dice/damageRoll.mjs
@@ -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));
diff --git a/module/dice/dualityRoll.mjs b/module/dice/dualityRoll.mjs
index f9a06d37..5e03a680 100644
--- a/module/dice/dualityRoll.mjs
+++ b/module/dice/dualityRoll.mjs
@@ -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';
@@ -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;
}
diff --git a/module/documents/activeEffect.mjs b/module/documents/activeEffect.mjs
index 8ec7a751..227e2613 100644
--- a/module/documents/activeEffect.mjs
+++ b/module/documents/activeEffect.mjs
@@ -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) {
diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs
index 3e3dfde4..db249033 100644
--- a/module/documents/actor.mjs
+++ b/module/documents/actor.mjs
@@ -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;
}
diff --git a/module/documents/chatMessage.mjs b/module/documents/chatMessage.mjs
index 307677bb..ad64336f 100644
--- a/module/documents/chatMessage.mjs
+++ b/module/documents/chatMessage.mjs
@@ -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: `Some Tokens still need to roll their Reaction Roll.
Are you sure you want to continue ?
Undone reaction rolls will be considered as failed
`
+ window: { title: game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.title') },
+ content: `${game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.unfinishedRolls')}
${game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.confirmation')}
${game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.warning')}
`
});
if (!confirm) return;
}
diff --git a/module/documents/collections/actorCollection.mjs b/module/documents/collections/actorCollection.mjs
index a3714b30..115dd694 100644
--- a/module/documents/collections/actorCollection.mjs
+++ b/module/documents/collections/actorCollection.mjs
@@ -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();
diff --git a/module/documents/item.mjs b/module/documents/item.mjs
index d1a618c7..8ece56fa 100644
--- a/module/documents/item.mjs
+++ b/module/documents/item.mjs
@@ -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;
diff --git a/module/documents/scene.mjs b/module/documents/scene.mjs
index 1c2faa34..59b8091f 100644
--- a/module/documents/scene.mjs
+++ b/module/documents/scene.mjs
@@ -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();
diff --git a/module/documents/tokenManager.mjs b/module/documents/tokenManager.mjs
index f766a677..3ccff4e2 100644
--- a/module/documents/tokenManager.mjs
+++ b/module/documents/tokenManager.mjs
@@ -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 }
+ );
+ }
}
}
diff --git a/module/enrichers/TemplateEnricher.mjs b/module/enrichers/TemplateEnricher.mjs
index 1a075518..cd0e7d9c 100644
--- a/module/enrichers/TemplateEnricher.mjs
+++ b/module/enrichers/TemplateEnricher.mjs
@@ -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;
};
diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs
index 131f94b7..4527da1a 100644
--- a/module/helpers/utils.mjs
+++ b/module/helpers/utils.mjs
@@ -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 };
});
diff --git a/module/systemRegistration/handlebars.mjs b/module/systemRegistration/handlebars.mjs
index 63e591c6..ebd65fa2 100644
--- a/module/systemRegistration/handlebars.mjs
+++ b/module/systemRegistration/handlebars.mjs
@@ -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',
diff --git a/module/systemRegistration/migrations.mjs b/module/systemRegistration/migrations.mjs
index 458ee6ef..b4c446b2 100644
--- a/module/systemRegistration/migrations.mjs
+++ b/module/systemRegistration/migrations.mjs
@@ -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);
diff --git a/module/systemRegistration/settings.mjs b/module/systemRegistration/settings.mjs
index 63611cda..41cab011 100644
--- a/module/systemRegistration/settings.mjs
+++ b/module/systemRegistration/settings.mjs
@@ -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
+ });
};
diff --git a/module/systemRegistration/socket.mjs b/module/systemRegistration/socket.mjs
index fb152959..8fed346d 100644
--- a/module/systemRegistration/socket.mjs
+++ b/module/systemRegistration/socket.mjs
@@ -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'
diff --git a/package-lock.json b/package-lock.json
index 864d027c..b8fef1cd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,6 +16,9 @@
"@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",
@@ -32,6 +35,152 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.9.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
+ "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.2",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
+ "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/config-array": {
+ "version": "0.23.5",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz",
+ "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/object-schema": "^3.0.5",
+ "debug": "^4.3.1",
+ "minimatch": "^10.2.4"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ }
+ },
+ "node_modules/@eslint/config-array/node_modules/balanced-match": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/@eslint/config-array/node_modules/brace-expansion": {
+ "version": "5.0.5",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
+ "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/@eslint/config-array/node_modules/minimatch": {
+ "version": "10.2.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
+ "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "brace-expansion": "^5.0.5"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@eslint/config-helpers": {
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.5.tgz",
+ "integrity": "sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^1.2.1"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ }
+ },
+ "node_modules/@eslint/core": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz",
+ "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/json-schema": "^7.0.15"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ }
+ },
+ "node_modules/@eslint/object-schema": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz",
+ "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ }
+ },
+ "node_modules/@eslint/plugin-kit": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.1.tgz",
+ "integrity": "sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^1.2.1",
+ "levn": "^0.4.1"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ }
+ },
"node_modules/@foundryvtt/foundryvtt-cli": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@foundryvtt/foundryvtt-cli/-/foundryvtt-cli-1.1.0.tgz",
@@ -72,12 +221,91 @@
"node": ">=10.13.0"
}
},
+ "node_modules/@humanfs/core": {
+ "version": "0.19.2",
+ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
+ "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/types": "^0.15.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node": {
+ "version": "0.16.8",
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz",
+ "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/core": "^0.19.2",
+ "@humanfs/types": "^0.15.0",
+ "@humanwhocodes/retry": "^0.4.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/types": {
+ "version": "0.15.0",
+ "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz",
+ "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/retry": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
+ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
"dev": true
},
+ "node_modules/@pkgr/core": {
+ "version": "0.2.9",
+ "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz",
+ "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/pkgr"
+ }
+ },
"node_modules/@rollup/plugin-commonjs": {
"version": "25.0.8",
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.8.tgz",
@@ -415,11 +643,25 @@
"node": ">=10.13.0"
}
},
+ "node_modules/@types/esrecurse": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz",
+ "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="
},
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/resolve": {
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
@@ -458,6 +700,46 @@
"node": ">=12"
}
},
+ "node_modules/acorn": {
+ "version": "8.16.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
+ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.14.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
+ "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
"node_modules/ansi-colors": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz",
@@ -1417,6 +1699,13 @@
}
}
},
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/deepmerge": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
@@ -1622,6 +1911,190 @@
"node": ">=6"
}
},
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "10.2.1",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.2.1.tgz",
+ "integrity": "sha512-wiyGaKsDgqXvF40P8mDwiUp/KQjE1FdrIEJsM8PZ3XCiniTMXS3OHWWUe5FI5agoCnr8x4xPrTDZuxsBlNHl+Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.8.0",
+ "@eslint-community/regexpp": "^4.12.2",
+ "@eslint/config-array": "^0.23.5",
+ "@eslint/config-helpers": "^0.5.5",
+ "@eslint/core": "^1.2.1",
+ "@eslint/plugin-kit": "^0.7.1",
+ "@humanfs/node": "^0.16.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@humanwhocodes/retry": "^0.4.2",
+ "@types/estree": "^1.0.6",
+ "ajv": "^6.14.0",
+ "cross-spawn": "^7.0.6",
+ "debug": "^4.3.2",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^9.1.2",
+ "eslint-visitor-keys": "^5.0.1",
+ "espree": "^11.2.0",
+ "esquery": "^1.7.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^8.0.0",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "minimatch": "^10.2.4",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "jiti": "*"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-plugin-prettier": {
+ "version": "5.5.5",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.5.tgz",
+ "integrity": "sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prettier-linter-helpers": "^1.0.1",
+ "synckit": "^0.11.12"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint-plugin-prettier"
+ },
+ "peerDependencies": {
+ "@types/eslint": ">=8.0.0",
+ "eslint": ">=8.0.0",
+ "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0",
+ "prettier": ">=3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/eslint": {
+ "optional": true
+ },
+ "eslint-config-prettier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "9.1.2",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz",
+ "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "@types/esrecurse": "^4.3.1",
+ "@types/estree": "^1.0.8",
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
+ "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint/node_modules/balanced-match": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/eslint/node_modules/brace-expansion": {
+ "version": "5.0.5",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
+ "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/eslint/node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/eslint/node_modules/minimatch": {
+ "version": "10.2.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
+ "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "brace-expansion": "^5.0.5"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/esm": {
"version": "3.2.25",
"resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz",
@@ -1631,12 +2104,76 @@
"node": ">=6"
}
},
+ "node_modules/espree": {
+ "version": "11.2.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz",
+ "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.16.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^5.0.1"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
+ "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
"node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"dev": true
},
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/eventemitter3": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
@@ -1694,11 +2231,32 @@
"node": ">=0.10.0"
}
},
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-diff": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
+ "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
"node_modules/fast-fifo": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
"integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="
},
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/fast-levenshtein": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-3.0.0.tgz",
@@ -1723,6 +2281,19 @@
"reusify": "^1.0.4"
}
},
+ "node_modules/file-entry-cache": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@@ -1734,6 +2305,23 @@
"node": ">=8"
}
},
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/findup-sync": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-5.0.0.tgz",
@@ -1771,6 +2359,27 @@
"node": ">= 10.13.0"
}
},
+ "node_modules/flat-cache": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.4"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
+ "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/for-each": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
@@ -2033,6 +2642,19 @@
"which": "bin/which"
}
},
+ "node_modules/globals": {
+ "version": "17.5.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-17.5.0.tgz",
+ "integrity": "sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/glogg": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/glogg/-/glogg-2.2.0.tgz",
@@ -2381,6 +3003,16 @@
}
]
},
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
"node_modules/image-size": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz",
@@ -2423,6 +3055,16 @@
"node": ">=8"
}
},
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
"node_modules/inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
@@ -2769,6 +3411,37 @@
"js-yaml": "bin/js-yaml.js"
}
},
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
"node_modules/last-run": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/last-run/-/last-run-2.0.0.tgz",
@@ -2832,6 +3505,20 @@
"node": ">=12"
}
},
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
"node_modules/lie": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
@@ -2932,6 +3619,22 @@
"lie": "3.1.1"
}
},
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
@@ -3202,6 +3905,13 @@
"integrity": "sha512-hmEVtAGYzVQpCKdbQea4skABsdXW4RUh5t5mJ2zzqowJS2OyXZTU1KhDVFhx+NlWZ4ap9mqR9TcDO3LTTttd+g==",
"dev": true
},
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/nedb-promises": {
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/nedb-promises/-/nedb-promises-6.2.3.tgz",
@@ -3370,6 +4080,31 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/optionator/node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/p-finally": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
@@ -3379,6 +4114,38 @@
"node": ">=4"
}
},
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/p-queue": {
"version": "6.6.2",
"resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz",
@@ -3442,6 +4209,16 @@
"node": ">=0.10.0"
}
},
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
@@ -4140,6 +4917,16 @@
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"dev": true
},
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
"node_modules/prettier": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
@@ -4155,6 +4942,19 @@
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
+ "node_modules/prettier-linter-helpers": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz",
+ "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-diff": "^1.1.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
"node_modules/promise.series": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/promise.series/-/promise.series-0.2.0.tgz",
@@ -4181,6 +4981,16 @@
"integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==",
"optional": true
},
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -4950,6 +5760,22 @@
"node": ">= 10"
}
},
+ "node_modules/synckit": {
+ "version": "0.11.12",
+ "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz",
+ "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@pkgr/core": "^0.2.9"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/synckit"
+ }
+ },
"node_modules/teex": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz",
@@ -5010,6 +5836,19 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
},
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
"node_modules/unc-path-regex": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz",
@@ -5070,6 +5909,16 @@
"browserslist": ">= 4.21.0"
}
},
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
"node_modules/util": {
"version": "0.12.5",
"resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",
@@ -5222,6 +6071,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/wrap-ansi": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz",
@@ -5313,6 +6172,19 @@
"engines": {
"node": ">=12"
}
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
}
}
}
diff --git a/package.json b/package.json
index 183d2de2..28c83549 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/src/packs/adversaries/adversary_Apprentice_Assassin_vNIbYQ4YSzNf0WPE.json b/src/packs/adversaries/adversary_Apprentice_Assassin_vNIbYQ4YSzNf0WPE.json
index 4c63297d..12ec35ae 100644
--- a/src/packs/adversaries/adversary_Apprentice_Assassin_vNIbYQ4YSzNf0WPE.json
+++ b/src/packs/adversaries/adversary_Apprentice_Assassin_vNIbYQ4YSzNf0WPE.json
@@ -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": "Spend a Fear 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.
",
"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": ""
}
},
diff --git a/src/packs/adversaries/adversary_Conscript_99TqczuQipBmaB8i.json b/src/packs/adversaries/adversary_Conscript_99TqczuQipBmaB8i.json
index 5cbc1f82..38e7ceab 100644
--- a/src/packs/adversaries/adversary_Conscript_99TqczuQipBmaB8i.json
+++ b/src/packs/adversaries/adversary_Conscript_99TqczuQipBmaB8i.json
@@ -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": "Spend a Fear 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.
",
"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": ""
}
},
diff --git a/src/packs/adversaries/adversary_Cult_Initiate_zx99sOGTXicP4SSD.json b/src/packs/adversaries/adversary_Cult_Initiate_zx99sOGTXicP4SSD.json
index 4f04a85a..db26605d 100644
--- a/src/packs/adversaries/adversary_Cult_Initiate_zx99sOGTXicP4SSD.json
+++ b/src/packs/adversaries/adversary_Cult_Initiate_zx99sOGTXicP4SSD.json
@@ -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": "Spend a Fear 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.
",
"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": ""
}
},
diff --git a/src/packs/adversaries/adversary_Elemental_Spark_P7h54ZePFPHpYwvB.json b/src/packs/adversaries/adversary_Elemental_Spark_P7h54ZePFPHpYwvB.json
index 2c2633ea..5b8fa7b2 100644
--- a/src/packs/adversaries/adversary_Elemental_Spark_P7h54ZePFPHpYwvB.json
+++ b/src/packs/adversaries/adversary_Elemental_Spark_P7h54ZePFPHpYwvB.json
@@ -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": "Spend a Fear 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.
",
"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": ""
}
},
diff --git a/src/packs/adversaries/adversary_Fallen_Shock_Troop_OsLG2BjaEdTZUJU9.json b/src/packs/adversaries/adversary_Fallen_Shock_Troop_OsLG2BjaEdTZUJU9.json
index 8c0d7b95..484e161a 100644
--- a/src/packs/adversaries/adversary_Fallen_Shock_Troop_OsLG2BjaEdTZUJU9.json
+++ b/src/packs/adversaries/adversary_Fallen_Shock_Troop_OsLG2BjaEdTZUJU9.json
@@ -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": "Spend a Fear 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.
",
"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": ""
}
},
diff --git a/src/packs/adversaries/adversary_Fallen_Warlord__Undefeated_Champion_RXkZTwBRi4dJ3JE5.json b/src/packs/adversaries/adversary_Fallen_Warlord__Undefeated_Champion_RXkZTwBRi4dJ3JE5.json
index 5ad77ab0..63d9ca2c 100644
--- a/src/packs/adversaries/adversary_Fallen_Warlord__Undefeated_Champion_RXkZTwBRi4dJ3JE5.json
+++ b/src/packs/adversaries/adversary_Fallen_Warlord__Undefeated_Champion_RXkZTwBRi4dJ3JE5.json
@@ -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": "Spend a Fear 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.
",
"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,
diff --git a/src/packs/adversaries/adversary_Giant_Rat_4PfLnaCrOcMdb4dK.json b/src/packs/adversaries/adversary_Giant_Rat_4PfLnaCrOcMdb4dK.json
index 822ee035..746806d9 100644
--- a/src/packs/adversaries/adversary_Giant_Rat_4PfLnaCrOcMdb4dK.json
+++ b/src/packs/adversaries/adversary_Giant_Rat_4PfLnaCrOcMdb4dK.json
@@ -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": ""
}
diff --git a/src/packs/adversaries/adversary_Giant_Recruit_5s8wSvpyC5rxY5aD.json b/src/packs/adversaries/adversary_Giant_Recruit_5s8wSvpyC5rxY5aD.json
index 376ebace..6611496f 100644
--- a/src/packs/adversaries/adversary_Giant_Recruit_5s8wSvpyC5rxY5aD.json
+++ b/src/packs/adversaries/adversary_Giant_Recruit_5s8wSvpyC5rxY5aD.json
@@ -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": "Spend a Fear 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.
",
"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": ""
}
},
diff --git a/src/packs/adversaries/adversary_Hallowed_Soldier_VENwg7xEFcYObjmT.json b/src/packs/adversaries/adversary_Hallowed_Soldier_VENwg7xEFcYObjmT.json
index 6a131c86..3e1ab854 100644
--- a/src/packs/adversaries/adversary_Hallowed_Soldier_VENwg7xEFcYObjmT.json
+++ b/src/packs/adversaries/adversary_Hallowed_Soldier_VENwg7xEFcYObjmT.json
@@ -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": "Spend a Fear 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.
",
"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": ""
}
},
diff --git a/src/packs/adversaries/adversary_Jagged_Knife_Lackey_C0OMQqV7pN6t7ouR.json b/src/packs/adversaries/adversary_Jagged_Knife_Lackey_C0OMQqV7pN6t7ouR.json
index cfcdea8b..076318c6 100644
--- a/src/packs/adversaries/adversary_Jagged_Knife_Lackey_C0OMQqV7pN6t7ouR.json
+++ b/src/packs/adversaries/adversary_Jagged_Knife_Lackey_C0OMQqV7pN6t7ouR.json
@@ -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": "Spend a Fear 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.
",
"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": ""
}
},
diff --git a/src/packs/adversaries/adversary_Minor_Treant_G62k4oSkhkoXEs2D.json b/src/packs/adversaries/adversary_Minor_Treant_G62k4oSkhkoXEs2D.json
index b2217e66..163556e9 100644
--- a/src/packs/adversaries/adversary_Minor_Treant_G62k4oSkhkoXEs2D.json
+++ b/src/packs/adversaries/adversary_Minor_Treant_G62k4oSkhkoXEs2D.json
@@ -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": "Spend a Fear 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.
",
"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": ""
}
},
diff --git a/src/packs/adversaries/adversary_Outer_Realms_Thrall_moJhHgKqTKPS2WYS.json b/src/packs/adversaries/adversary_Outer_Realms_Thrall_moJhHgKqTKPS2WYS.json
index 5a7a605a..0bb3a44c 100644
--- a/src/packs/adversaries/adversary_Outer_Realms_Thrall_moJhHgKqTKPS2WYS.json
+++ b/src/packs/adversaries/adversary_Outer_Realms_Thrall_moJhHgKqTKPS2WYS.json
@@ -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": "Spend a Fear 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.
",
"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": ""
}
},
diff --git a/src/packs/adversaries/adversary_Rotted_Zombie_gP3fWTLzSFnpA8EJ.json b/src/packs/adversaries/adversary_Rotted_Zombie_gP3fWTLzSFnpA8EJ.json
index 6755d27f..f1c7f470 100644
--- a/src/packs/adversaries/adversary_Rotted_Zombie_gP3fWTLzSFnpA8EJ.json
+++ b/src/packs/adversaries/adversary_Rotted_Zombie_gP3fWTLzSFnpA8EJ.json
@@ -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": "Spend a Fear 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.
",
"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": ""
}
},
diff --git a/src/packs/adversaries/adversary_Sellsword_bgreCaQ6ap2DVpCr.json b/src/packs/adversaries/adversary_Sellsword_bgreCaQ6ap2DVpCr.json
index ed6d7775..46bed122 100644
--- a/src/packs/adversaries/adversary_Sellsword_bgreCaQ6ap2DVpCr.json
+++ b/src/packs/adversaries/adversary_Sellsword_bgreCaQ6ap2DVpCr.json
@@ -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": "Spend a Fear 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.
",
"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": ""
}
},
diff --git a/src/packs/adversaries/adversary_Skeleton_Dredge_6l1a3Fazq8BoKIcc.json b/src/packs/adversaries/adversary_Skeleton_Dredge_6l1a3Fazq8BoKIcc.json
index e4cbab5e..1a82abb8 100644
--- a/src/packs/adversaries/adversary_Skeleton_Dredge_6l1a3Fazq8BoKIcc.json
+++ b/src/packs/adversaries/adversary_Skeleton_Dredge_6l1a3Fazq8BoKIcc.json
@@ -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": "Spend a Fear 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.
",
"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": ""
}
},
diff --git a/src/packs/adversaries/adversary_Tangle_Bramble_XcAGOSmtCFLT1unN.json b/src/packs/adversaries/adversary_Tangle_Bramble_XcAGOSmtCFLT1unN.json
index c36502de..d635b2ca 100644
--- a/src/packs/adversaries/adversary_Tangle_Bramble_XcAGOSmtCFLT1unN.json
+++ b/src/packs/adversaries/adversary_Tangle_Bramble_XcAGOSmtCFLT1unN.json
@@ -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": "Spend a Fear 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.
",
"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": ""
}
},
diff --git a/src/packs/adversaries/adversary_Treant_Sapling_o63nS0k3wHu6EgKP.json b/src/packs/adversaries/adversary_Treant_Sapling_o63nS0k3wHu6EgKP.json
index c6c11d36..c03a1b52 100644
--- a/src/packs/adversaries/adversary_Treant_Sapling_o63nS0k3wHu6EgKP.json
+++ b/src/packs/adversaries/adversary_Treant_Sapling_o63nS0k3wHu6EgKP.json
@@ -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": "Spend a Fear 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.
",
"resource": null,
"actions": {
- "euP8VA4wvfsCpwN1": {
- "type": "effect",
- "_id": "euP8VA4wvfsCpwN1",
+ "Itubbr63irPJcbXG": {
+ "type": "attack",
+ "_id": "Itubbr63irPJcbXG",
"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": "8"
+ }
+ },
+ "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": ""
}
},
diff --git a/src/packs/ancestries/feature_Retract_UFR67BUOhNGLFyg9.json b/src/packs/ancestries/feature_Retract_UFR67BUOhNGLFyg9.json
index b17cd7da..eb9696b2 100644
--- a/src/packs/ancestries/feature_Retract_UFR67BUOhNGLFyg9.json
+++ b/src/packs/ancestries/feature_Retract_UFR67BUOhNGLFyg9.json
@@ -68,31 +68,33 @@
"type": "withinRange",
"target": "hostile",
"range": "melee"
+ },
+ "changes": [
+ {
+ "key": "system.resistance.physical.resistance",
+ "type": "override",
+ "value": 1,
+ "priority": null,
+ "phase": "initial"
+ },
+ {
+ "key": "system.disadvantageSources",
+ "type": "add",
+ "value": "Action rolls",
+ "priority": null,
+ "phase": "initial"
+ }
+ ],
+ "duration": {
+ "type": ""
}
},
- "changes": [
- {
- "key": "system.resistance.physical.resistance",
- "mode": 5,
- "value": "1",
- "priority": null
- },
- {
- "key": "system.disadvantageSources",
- "mode": 2,
- "value": "Retract",
- "priority": null
- }
- ],
"disabled": true,
"duration": {
- "startTime": null,
- "combat": null,
- "seconds": null,
- "rounds": null,
- "turns": null,
- "startRound": null,
- "startTurn": null
+ "value": null,
+ "units": "seconds",
+ "expiry": null,
+ "expired": false
},
"description": "While in your shell, you have resistance to physical damage, you have disadvantage on action rolls, and you canβt move.
",
"tint": "#ffffff",
@@ -102,6 +104,16 @@
"_stats": {
"compendiumSource": null
},
+ "start": {
+ "time": 0,
+ "combat": null,
+ "combatant": null,
+ "initiative": null,
+ "round": null,
+ "turn": null
+ },
+ "showIcon": 1,
+ "folder": null,
"_key": "!items.effects!UFR67BUOhNGLFyg9.3V4FPoyjJUnFP9WS"
}
],
diff --git a/src/packs/communities/feature_Low_Light_Living_aMla3xQuCHEwORGD.json b/src/packs/communities/feature_Low_Light_Living_aMla3xQuCHEwORGD.json
index f1ed3ace..c95d9132 100644
--- a/src/packs/communities/feature_Low_Light_Living_aMla3xQuCHEwORGD.json
+++ b/src/packs/communities/feature_Low_Light_Living_aMla3xQuCHEwORGD.json
@@ -29,39 +29,28 @@
"type": "withinRange",
"target": "hostile",
"range": "melee"
+ },
+ "changes": [
+ {
+ "key": "system.advantageSources",
+ "type": "add",
+ "value": "Rolls to hide, investigate, or perceive details in low light",
+ "priority": null,
+ "phase": "initial"
+ }
+ ],
+ "duration": {
+ "type": ""
}
},
- "changes": [
- {
- "key": "system.advantageSources",
- "mode": 2,
- "value": "In an area with low light or heavy shadow: hide, investigate, or perceive",
- "priority": null
- },
- {
- "key": "system.advantageSources",
- "mode": 2,
- "value": "",
- "priority": null
- },
- {
- "key": "",
- "mode": 2,
- "value": "",
- "priority": null
- }
- ],
"disabled": false,
"duration": {
- "startTime": null,
- "combat": null,
- "seconds": null,
- "rounds": null,
- "turns": null,
- "startRound": null,
- "startTurn": null
+ "value": null,
+ "units": "seconds",
+ "expiry": null,
+ "expired": false
},
- "description": "",
+ "description": "When youβre in an area with low light or heavy shadow, you have advantage on rolls to hide, investigate, or perceive details within that area.
",
"origin": null,
"tint": "#ffffff",
"transfer": true,
@@ -71,6 +60,16 @@
"_stats": {
"compendiumSource": null
},
+ "start": {
+ "time": 0,
+ "combat": null,
+ "combatant": null,
+ "initiative": null,
+ "round": null,
+ "turn": null
+ },
+ "showIcon": 1,
+ "folder": null,
"_key": "!items.effects!aMla3xQuCHEwORGD.pCp32u7UwqxCI4WW"
}
],
diff --git a/src/packs/domains/domainCard_Bare_Bones_l5D9kq901JDESaXw.json b/src/packs/domains/domainCard_Bare_Bones_l5D9kq901JDESaXw.json
index 098f5f4c..7956d6eb 100644
--- a/src/packs/domains/domainCard_Bare_Bones_l5D9kq901JDESaXw.json
+++ b/src/packs/domains/domainCard_Bare_Bones_l5D9kq901JDESaXw.json
@@ -4,7 +4,7 @@
"type": "domainCard",
"folder": "QpOL7jPbMBzH96qR",
"system": {
- "description": "When you choose not to equip armor, you have a base Armor Score of 3 + your Strength and use the following as your base damage thresholds:
Tier 1: 9/19Tier 2: 11/24Tier 3: 13/31Tier 4: 15/38Equip the below armor to use Bare Bones.
@UUID[Compendium.daggerheart.armors.Item.ITAjcigTcUw5pMCN]{Bare Bones}
",
+ "description": "When you choose not to equip armor, you have a base Armor Score of 3 + your Strength and use the following as your base damage thresholds:
Tier 1: 9/19
Tier 2: 11/24
Tier 3: 13/31
Tier 4: 15/38
",
"domain": "valor",
"recallCost": 0,
"level": 1,
@@ -28,22 +28,33 @@
{
"type": "armor",
"phase": "initial",
- "priority": 20,
"value": {
+ "current": 0,
"max": "3 + @system.traits.strength.value",
"interaction": "inactive",
"damageThresholds": {
- "major": "9 + (@tier - 1) * 5 + max(0, (@tier -2) * 2 )",
+ "major": "9 + (@tier - 1) * 2",
"severe": "19 + (@tier - 1) * 5 + max(0, (@tier -2) * 2 )"
}
- }
+ },
+ "priority": 20
}
- ]
+ ],
+ "duration": {
+ "type": ""
+ }
},
"_id": "FCsgz7Tdsw6QUzBs",
"img": "icons/magic/control/buff-strength-muscle-damage-orange.webp",
"disabled": false,
- "start": null,
+ "start": {
+ "time": 0,
+ "combat": null,
+ "combatant": null,
+ "initiative": null,
+ "round": null,
+ "turn": null
+ },
"duration": {
"value": null,
"units": "seconds",
diff --git a/src/packs/domains/domainCard_Battle_Cry_Ef1JsUG50LIoKx2F.json b/src/packs/domains/domainCard_Battle_Cry_Ef1JsUG50LIoKx2F.json
index c9ae6071..1fde286d 100644
--- a/src/packs/domains/domainCard_Battle_Cry_Ef1JsUG50LIoKx2F.json
+++ b/src/packs/domains/domainCard_Battle_Cry_Ef1JsUG50LIoKx2F.json
@@ -132,27 +132,29 @@
"type": "withinRange",
"target": "hostile",
"range": "melee"
+ },
+ "changes": [
+ {
+ "key": "system.advantageSources",
+ "type": "add",
+ "value": "On Attacks",
+ "priority": null,
+ "phase": "initial"
+ }
+ ],
+ "duration": {
+ "type": "temporary",
+ "description": "Until you or an ally rolls a failure with Fear.
"
}
},
- "changes": [
- {
- "key": "system.advantageSources",
- "mode": 2,
- "value": "1",
- "priority": null
- }
- ],
"disabled": false,
"duration": {
- "startTime": null,
- "combat": null,
- "seconds": null,
- "rounds": null,
- "turns": null,
- "startRound": null,
- "startTurn": null
+ "value": null,
+ "units": "seconds",
+ "expiry": null,
+ "expired": false
},
- "description": "",
+ "description": "You gain advantage on attack rolls until you or an ally rolls a failure with Fear.
",
"tint": "#ffffff",
"statuses": [],
"sort": 0,
@@ -160,6 +162,16 @@
"_stats": {
"compendiumSource": null
},
+ "start": {
+ "time": 0,
+ "combat": null,
+ "combatant": null,
+ "initiative": null,
+ "round": null,
+ "turn": null
+ },
+ "showIcon": 1,
+ "folder": null,
"_key": "!items.effects!Ef1JsUG50LIoKx2F.s7ma4TNgAvt0ZgEW"
}
],
diff --git a/src/packs/environments/environment_Abandoned_Grove_pGEdzdLkqYtBhxnG.json b/src/packs/environments/environment_Abandoned_Grove_pGEdzdLkqYtBhxnG.json
index 75fc932f..0cab16a2 100644
--- a/src/packs/environments/environment_Abandoned_Grove_pGEdzdLkqYtBhxnG.json
+++ b/src/packs/environments/environment_Abandoned_Grove_pGEdzdLkqYtBhxnG.json
@@ -52,12 +52,9 @@
"src": "systems/daggerheart/assets/icons/documents/actors/forest.svg",
"anchorX": 0.5,
"anchorY": 0.5,
- "offsetX": 0,
- "offsetY": 0,
"fit": "contain",
"scaleX": 1,
"scaleY": 1,
- "rotation": 0,
"tint": "#ffffff",
"alphaThreshold": 0.75
},
@@ -108,7 +105,7 @@
"saturation": 0,
"contrast": 0
},
- "detectionModes": [],
+ "detectionModes": {},
"occludable": {
"radius": 0
},
@@ -134,7 +131,8 @@
"flags": {},
"randomImg": false,
"appendNumber": false,
- "prependAdjective": false
+ "prependAdjective": false,
+ "depth": 1
},
"items": [
{
@@ -323,7 +321,42 @@
"system": {
"description": "A @UUID[Compendium.daggerheart.adversaries.Actor.8yUj2Mzvnifhxegm]{Young Dryad}, two @UUID[Compendium.daggerheart.adversaries.Actor.VtFBt9XBE0WrGGxP]{Sylvan Soldiers}, and a number of @UUID[Compendium.daggerheart.adversaries.Actor.G62k4oSkhkoXEs2D]{Minor Treants} equal to the number of PCs appear to confront the party for their intrusion.
What are the grove guardians concealing? What threat to the forest could the PCs confront to appease the Dryad?
",
"resource": null,
- "actions": {},
+ "actions": {
+ "TPm6rpKA4mbili82": {
+ "type": "summon",
+ "_id": "TPm6rpKA4mbili82",
+ "systemPath": "actions",
+ "baseAction": false,
+ "description": "",
+ "chatDisplay": true,
+ "originItem": {
+ "type": "itemCollection"
+ },
+ "actionType": "action",
+ "triggers": [],
+ "cost": [],
+ "uses": {
+ "value": null,
+ "max": null,
+ "recovery": null,
+ "consumeOnSuccess": false
+ },
+ "summon": [
+ {
+ "actorUUID": "Compendium.daggerheart.adversaries.Actor.8yUj2Mzvnifhxegm",
+ "count": "1"
+ },
+ {
+ "actorUUID": "Compendium.daggerheart.adversaries.Actor.VtFBt9XBE0WrGGxP",
+ "count": "2"
+ },
+ {
+ "actorUUID": "Compendium.daggerheart.adversaries.Actor.G62k4oSkhkoXEs2D",
+ "count": "@partySize"
+ }
+ ]
+ }
+ },
"originItemType": null,
"originId": null,
"featureForm": "action"
diff --git a/styles/less/dialog/character-creation/selections-container.less b/styles/less/dialog/character-creation/selections-container.less
index f9569fca..2bbac484 100644
--- a/styles/less/dialog/character-creation/selections-container.less
+++ b/styles/less/dialog/character-creation/selections-container.less
@@ -175,6 +175,11 @@
opacity: 0.2;
}
+ &.no-horizontal-padding {
+ padding-left: 0;
+ padding-right: 0;
+ }
+
legend {
margin-left: auto;
margin-right: auto;
@@ -250,6 +255,11 @@
height: 65px;
background: url(../assets/svg/trait-shield.svg) no-repeat;
background-size: 100%;
+ padding-top: 3px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ row-gap: 3px;
div {
filter: drop-shadow(0 0 3px black);
@@ -278,6 +288,15 @@
flex-direction: column;
gap: 5px;
+ &.separated {
+ border-bottom: 2px solid;
+ padding-bottom: 8px;
+ }
+
+ .form-group {
+ padding: 0 0.75rem;
+ }
+
.experience-inner-container {
position: relative;
display: flex;
diff --git a/styles/less/dialog/damage-selection/sheet.less b/styles/less/dialog/damage-selection/sheet.less
index 0f765748..9f8cfc8a 100644
--- a/styles/less/dialog/damage-selection/sheet.less
+++ b/styles/less/dialog/damage-selection/sheet.less
@@ -21,7 +21,7 @@
gap: 4px;
.critical-chip {
flex: 0;
-
+
display: flex;
align-items: center;
border-radius: 5px;
@@ -41,6 +41,26 @@
}
}
+ .group-attack-container {
+ margin: 0;
+
+ .group-attack-inner-container {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+
+ > * {
+ flex: 1;
+ }
+
+ .group-attack-tools {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ }
+ }
+ }
+
.damage-section-controls {
display: flex;
align-items: center;
diff --git a/styles/less/dialog/group-roll-dialog/initialization.less b/styles/less/dialog/group-roll-dialog/initialization.less
new file mode 100644
index 00000000..b2e7e021
--- /dev/null
+++ b/styles/less/dialog/group-roll-dialog/initialization.less
@@ -0,0 +1,79 @@
+.theme-light .daggerheart.dialog.dh-style.views.group-roll-dialog {
+ .initialization-container .members-container .member-container {
+ .member-name {
+ background-image: url('../assets/parchments/dh-parchment-light.png');
+ }
+ }
+}
+
+.daggerheart.dialog.dh-style.views.group-roll-dialog {
+ .initialization-container {
+ h2 {
+ text-align: center;
+ }
+
+ .members-container {
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr 1fr;
+ gap: 8px;
+
+ .member-container {
+ position: relative;
+ display: flex;
+ justify-content: center;
+
+ &.inactive {
+ opacity: 0.4;
+ }
+
+ .member-name {
+ position: absolute;
+ padding: 0 2px;
+ border: 1px solid;
+ border-radius: 6px;
+ margin-top: 4px;
+ color: light-dark(@dark, @beige);
+ background-image: url('../assets/parchments/dh-parchment-dark.png');
+ text-align: center;
+ }
+
+ img {
+ border-radius: 6px;
+ border: 1px solid light-dark(@dark-blue, @golden);
+ }
+ }
+ }
+
+ .main-roll {
+ margin-top: 8px;
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 8px;
+
+ &.inactive {
+ opacity: 0.4;
+ }
+ }
+
+ footer {
+ margin-top: 8px;
+ display: flex;
+ gap: 8px;
+
+ button {
+ flex: 1;
+ }
+
+ .finish-tools {
+ flex: none;
+ display: flex;
+ align-items: center;
+ gap: 4px;
+
+ &.inactive {
+ opacity: 0.4;
+ }
+ }
+ }
+ }
+}
diff --git a/styles/less/dialog/group-roll-dialog/leader.less b/styles/less/dialog/group-roll-dialog/leader.less
new file mode 100644
index 00000000..b3fa3a3b
--- /dev/null
+++ b/styles/less/dialog/group-roll-dialog/leader.less
@@ -0,0 +1,35 @@
+.daggerheart.dialog.dh-style.views.group-roll-dialog {
+ .main-character-outer-container {
+ &.inactive {
+ opacity: 0.3;
+ pointer-events: none;
+ }
+
+ .main-character-container {
+ .character-info {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ width: 100%;
+ height: 64px;
+
+ img {
+ height: 64px;
+ border-radius: 6px;
+ border: 1px solid light-dark(@dark-blue, @golden);
+ }
+
+ .character-data {
+ padding-left: 0.75rem;
+ flex: 1;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ text-align: left;
+ font-size: var(--font-size-18);
+ }
+ }
+ }
+ }
+}
diff --git a/styles/less/dialog/group-roll-dialog/sheet.less b/styles/less/dialog/group-roll-dialog/sheet.less
new file mode 100644
index 00000000..70afc1fe
--- /dev/null
+++ b/styles/less/dialog/group-roll-dialog/sheet.less
@@ -0,0 +1,265 @@
+.daggerheart.dialog.dh-style.views.group-roll-dialog {
+ .team-container {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 16px;
+ margin-bottom: 16px;
+
+ .team-member-container {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ gap: 8px;
+ flex: 1;
+
+ &.inactive {
+ opacity: 0.3;
+ pointer-events: none;
+ }
+
+ .data-container {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ width: 100%;
+ }
+
+ .member-info {
+ display: flex;
+ align-items: start;
+ width: 100%;
+
+ img {
+ height: 64px;
+ border-radius: 6px;
+ border: 1px solid light-dark(@dark-blue, @golden);
+ }
+
+ .member-data {
+ padding-left: 0.75rem;
+ flex: 1;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ text-align: left;
+ font-size: var(--font-size-18);
+ }
+ }
+ }
+ }
+
+ .roll-container {
+ display: flex;
+ flex-direction: column;
+ }
+
+ .roll-title {
+ font-size: var(--font-size-20);
+ font-weight: bold;
+ color: light-dark(@dark-blue, @golden);
+ text-align: center;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+
+ &.hope,
+ &.fear,
+ &.critical {
+ color: var(--text-color);
+ }
+
+ &.hope {
+ --text-color: @golden;
+ }
+
+ &.fear {
+ --text-color: @chat-blue;
+ }
+
+ &.critical {
+ --text-color: @chat-purple;
+ }
+
+ &::before,
+ &::after {
+ color: var(--text-color);
+ content: '';
+ flex: 1;
+ height: 2px;
+ }
+
+ &::before {
+ background: linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, light-dark(@dark-blue, @golden) 100%);
+ }
+
+ &::after {
+ background: linear-gradient(90deg, light-dark(@dark-blue, @golden) 0%, rgba(0, 0, 0, 0) 100%);
+ }
+ }
+
+ .roll-tools {
+ display: flex;
+ gap: 4px;
+ align-items: center;
+
+ img {
+ height: 16px;
+ }
+
+ a {
+ display: flex;
+ font-size: 16px;
+
+ &:hover {
+ text-shadow: none;
+ filter: drop-shadow(0 0 8px var(--golden));
+ }
+ }
+ }
+
+ .roll-data {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 4px;
+
+ &.hope {
+ --text-color: @golden;
+ --bg-color: @golden-40;
+ }
+
+ &.fear {
+ --text-color: @chat-blue;
+ --bg-color: @chat-blue-40;
+ }
+
+ &.critical {
+ --text-color: @chat-purple;
+ --bg-color: @chat-purple-40;
+ }
+
+ .duality-label {
+ color: var(--text-color);
+ font-size: var(--font-size-20);
+ font-weight: bold;
+ text-align: center;
+
+ .unused-damage {
+ text-decoration: line-through;
+ }
+ }
+
+ .roll-dice-container {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-wrap: wrap;
+ gap: 8px;
+
+ .roll-dice {
+ position: relative;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ .dice-label {
+ position: absolute;
+ color: white;
+ font-size: 1rem;
+ paint-order: stroke fill;
+ -webkit-text-stroke: 2px black;
+ }
+
+ img {
+ height: 32px;
+ }
+ }
+
+ .roll-operator {
+ font-size: var(--font-size-24);
+ }
+
+ .roll-value {
+ font-size: 18px;
+ }
+ }
+
+ .roll-total {
+ background: var(--bg-color);
+ color: var(--text-color);
+ border-radius: 4px;
+ padding: 3px;
+ }
+ }
+
+ .roll-success-container {
+ display: flex;
+ align-items: center;
+ justify-content: space-around;
+
+ .roll-success-tools {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ color: light-dark(@dark-blue, @golden);
+
+ i {
+ font-size: 24px;
+ }
+ }
+
+ .roll-success-modifier {
+ display: flex;
+ align-items: center;
+ justify-content: right;
+ gap: 2px;
+ font-size: var(--font-size-20);
+ padding: 0px 4px;
+
+ &.success {
+ background: @green-10;
+ color: @green;
+ }
+
+ &.failure {
+ background: @red-10;
+ color: @red;
+ }
+ }
+ }
+
+ .section-title {
+ font-size: var(--font-size-18);
+ font-weight: bold;
+ }
+
+ .group-roll-results {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 4px;
+ font-size: var(--font-size-20);
+
+ .group-roll-container {
+ display: flex;
+ align-items: center;
+ gap: 2px;
+ }
+ }
+
+ .finish-container {
+ margin-top: 16px;
+ gap: 16px;
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr;
+
+ .finish-button {
+ grid-column: span 2;
+ }
+ }
+
+ .hint {
+ text-align: center;
+ }
+}
diff --git a/styles/less/dialog/index.less b/styles/less/dialog/index.less
index 73738eaa..947142ff 100644
--- a/styles/less/dialog/index.less
+++ b/styles/less/dialog/index.less
@@ -36,6 +36,10 @@
@import './tag-team-dialog/initialization.less';
@import './tag-team-dialog/sheet.less';
+@import './group-roll-dialog/initialization.less';
+@import './group-roll-dialog/leader.less';
+@import './group-roll-dialog/sheet.less';
+
@import './image-select/sheet.less';
@import './item-transfer/sheet.less';
diff --git a/styles/less/dialog/tag-team-dialog/initialization.less b/styles/less/dialog/tag-team-dialog/initialization.less
index 30676f82..8557d231 100644
--- a/styles/less/dialog/tag-team-dialog/initialization.less
+++ b/styles/less/dialog/tag-team-dialog/initialization.less
@@ -1,3 +1,11 @@
+.theme-light .daggerheart.dialog.dh-style.views.tag-team-dialog {
+ .initialization-container .members-container .member-container {
+ .member-name {
+ background-image: url('../assets/parchments/dh-parchment-light.png');
+ }
+ }
+}
+
.daggerheart.dialog.dh-style.views.tag-team-dialog {
.initialization-container {
h2 {
@@ -20,6 +28,18 @@
.member-name {
position: absolute;
+ padding: 0 2px;
+ border: 1px solid;
+ border-radius: 6px;
+ margin-top: 4px;
+ color: light-dark(@dark, @beige);
+ background-image: url('../assets/parchments/dh-parchment-dark.png');
+ text-align: center;
+ }
+
+ img {
+ border-radius: 6px;
+ border: 1px solid light-dark(@dark-blue, @golden);
}
}
}
diff --git a/styles/less/global/elements.less b/styles/less/global/elements.less
index 793c8164..c5bca1da 100755
--- a/styles/less/global/elements.less
+++ b/styles/less/global/elements.less
@@ -419,11 +419,19 @@
width: fit-content;
display: flex;
align-items: center;
+
.form-fields {
height: 32px;
align-content: center;
}
}
+
+ &.select {
+ width: fit-content;
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ }
}
.scalable-input {
diff --git a/styles/less/global/prose-mirror.less b/styles/less/global/prose-mirror.less
index 3523dc89..8a663e28 100644
--- a/styles/less/global/prose-mirror.less
+++ b/styles/less/global/prose-mirror.less
@@ -25,7 +25,7 @@
}
h4 {
font-size: var(--font-size-16);
- color: @beige;
+ color: light-dark(@dark, @beige);
font-weight: 600;
}
diff --git a/styles/less/sheets-settings/character-settings/sheet.less b/styles/less/sheets-settings/character-settings/sheet.less
index f7f16df4..eab29436 100644
--- a/styles/less/sheets-settings/character-settings/sheet.less
+++ b/styles/less/sheets-settings/character-settings/sheet.less
@@ -27,10 +27,11 @@
height: 65px;
background: url(../assets/svg/trait-shield.svg) no-repeat;
background-size: 100%;
- padding-top: 4px;
+ padding-top: 3px;
display: flex;
flex-direction: column;
align-items: center;
+ row-gap: 3px;
span {
font-size: var(--font-size-10);
diff --git a/styles/less/sheets/actors/actor-sheet-shared.less b/styles/less/sheets/actors/actor-sheet-shared.less
index 23db088a..bd82ef83 100644
--- a/styles/less/sheets/actors/actor-sheet-shared.less
+++ b/styles/less/sheets/actors/actor-sheet-shared.less
@@ -37,6 +37,59 @@
color: light-dark(@chat-blue-bg, @beige-50);
}
+ .tab.inventory {
+ .search-section {
+ display: flex;
+ gap: 10px;
+ align-items: center;
+ }
+ .search-bar {
+ position: relative;
+ color: light-dark(@dark-blue-50, @beige-50);
+ width: 100%;
+ padding-top: 5px;
+
+ input {
+ border-radius: 50px;
+ background: light-dark(@dark-blue-10, @golden-10);
+ border: none;
+ outline: 2px solid transparent;
+ transition: all 0.3s ease;
+ padding: 0 20px;
+
+ &:hover {
+ outline: 2px solid light-dark(@dark, @golden);
+ }
+
+ &::-webkit-search-cancel-button {
+ -webkit-appearance: none;
+ display: none;
+ }
+ }
+
+ .icon {
+ align-content: center;
+ height: 32px;
+ position: absolute;
+ right: 20px;
+ font-size: 16px;
+ z-index: 1;
+ color: light-dark(@dark-blue-50, @beige-50);
+ }
+ }
+
+ .gold-section {
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr 1fr;
+ gap: 10px;
+ padding: 10px 10px 0;
+
+ .input {
+ color: light-dark(@dark, @beige);
+ }
+ }
+ }
+
&.limited {
&.character,
&.adversary,
diff --git a/styles/less/sheets/actors/character/inventory.less b/styles/less/sheets/actors/character/inventory.less
index b555aa3d..12f63753 100644
--- a/styles/less/sheets/actors/character/inventory.less
+++ b/styles/less/sheets/actors/character/inventory.less
@@ -3,47 +3,6 @@
.application.sheet.daggerheart.actor.dh-style.character {
.tab.inventory {
- .search-section {
- display: flex;
- gap: 10px;
- align-items: center;
-
- .search-bar {
- position: relative;
- color: light-dark(@dark-blue-50, @beige-50);
- width: 100%;
- padding-top: 5px;
-
- input {
- border-radius: 50px;
- background: light-dark(@dark-blue-10, @golden-10);
- border: none;
- outline: 2px solid transparent;
- transition: all 0.3s ease;
- padding: 0 20px;
-
- &:hover {
- outline: 2px solid light-dark(@dark, @golden);
- }
-
- &::-webkit-search-cancel-button {
- -webkit-appearance: none;
- display: none;
- }
- }
-
- .icon {
- align-content: center;
- height: 32px;
- position: absolute;
- right: 20px;
- font-size: var(--font-size-16);
- z-index: 1;
- color: light-dark(@dark-blue-50, @beige-50);
- }
- }
- }
-
.items-section {
display: flex;
flex-direction: column;
@@ -55,16 +14,5 @@
scrollbar-width: thin;
scrollbar-color: light-dark(@dark-blue, @golden) transparent;
}
-
- .currency-section {
- display: grid;
- grid-template-columns: 1fr 1fr 1fr 1fr;
- gap: 10px;
- padding: 10px 10px 0;
-
- .input {
- color: light-dark(@dark, @beige);
- }
- }
}
}
diff --git a/styles/less/sheets/actors/character/sheet.less b/styles/less/sheets/actors/character/sheet.less
index ee6580fd..68792c99 100644
--- a/styles/less/sheets/actors/character/sheet.less
+++ b/styles/less/sheets/actors/character/sheet.less
@@ -11,21 +11,6 @@
padding-bottom: 0;
overflow-x: auto;
- &.viewMode {
- button:not(.btn-toggle-view),
- input:not(.search),
- .controls,
- .character-sidebar-sheet,
- .img-portait,
- .name-row,
- .hope-section,
- .downtime-section,
- .character-traits,
- .card-list {
- pointer-events: none;
- }
- }
-
.character-sidebar-sheet {
grid-row: 1 / span 2;
grid-column: 1;
diff --git a/styles/less/sheets/actors/character/sidebar.less b/styles/less/sheets/actors/character/sidebar.less
index e7027163..b159a8e8 100644
--- a/styles/less/sheets/actors/character/sidebar.less
+++ b/styles/less/sheets/actors/character/sidebar.less
@@ -316,9 +316,9 @@
border-radius: 3px;
background: light-dark(@dark-blue, @golden);
clip-path: none;
- cursor: pointer;
display: flex;
align-items: center;
+ justify-content: center;
gap: 4px;
border: 1px solid transparent;
transition: all 0.3s ease;
diff --git a/styles/less/sheets/actors/party/inventory.less b/styles/less/sheets/actors/party/inventory.less
index 2dcc97d8..ac59e1de 100644
--- a/styles/less/sheets/actors/party/inventory.less
+++ b/styles/less/sheets/actors/party/inventory.less
@@ -3,51 +3,6 @@
.application.sheet.daggerheart.actor.dh-style.party {
.tab.inventory {
- .search-section {
- display: flex;
- gap: 10px;
- align-items: center;
-
- .search-bar {
- position: relative;
- color: light-dark(@dark-blue-50, @beige-50);
- width: 100%;
- padding-top: 5px;
-
- input {
- border-radius: 50px;
- background: light-dark(@dark-blue-10, @golden-10);
- border: none;
- outline: 2px solid transparent;
- transition: all 0.3s ease;
- padding: 0 20px;
-
- &:hover {
- outline: 2px solid light-dark(@dark, @golden);
- }
-
- &:placeholder {
- color: light-dark(@dark-blue-50, @beige-50);
- }
-
- &::-webkit-search-cancel-button {
- -webkit-appearance: none;
- display: none;
- }
- }
-
- .icon {
- align-content: center;
- height: 32px;
- position: absolute;
- right: 20px;
- font-size: 16px;
- z-index: 1;
- color: light-dark(@dark-blue-50, @beige-50);
- }
- }
- }
-
.items-section {
display: flex;
flex-direction: column;
@@ -59,16 +14,5 @@
scrollbar-width: thin;
scrollbar-color: light-dark(@dark-blue, @golden) transparent;
}
-
- .currency-section {
- display: grid;
- grid-template-columns: 1fr 1fr 1fr 1fr;
- gap: 10px;
- padding: 10px 10px 0;
-
- .input {
- color: light-dark(@dark, @beige);
- }
- }
}
}
diff --git a/styles/less/sheets/actors/party/party-members.less b/styles/less/sheets/actors/party/party-members.less
index a433ae34..2e2f4cf8 100644
--- a/styles/less/sheets/actors/party/party-members.less
+++ b/styles/less/sheets/actors/party/party-members.less
@@ -1,28 +1,294 @@
@import '../../../utils/colors.less';
@import '../../../utils/fonts.less';
+@import '../../../utils/mixin.less';
-.application.sheet.daggerheart.actor.dh-style.party {
- .tab.partyMembers {
- max-height: 400px;
- overflow: auto;
+body.game:is(.performance-low, .noblur) {
+ .application.sheet.daggerheart.actor.dh-style.party .tab.resources .actors-list .actor-resources {
+ background: light-dark(@dark-blue, @dark-golden);
- .actors-list {
- display: flex;
- flex-direction: column;
- gap: 10px;
- align-items: center;
- width: 100%;
- }
- .actors-dragger {
- display: flex;
- align-items: center;
- justify-content: center;
- box-sizing: border-box;
- width: 100%;
- height: 40px;
- border: 1px dashed light-dark(@dark-blue-50, @beige-50);
- border-radius: 3px;
- color: light-dark(@dark-blue-50, @beige-50);
+ .actor-name {
+ background: light-dark(@dark-blue, @dark-golden);
}
}
}
+
+.application.sheet.daggerheart.actor.dh-style.party .tab.partyMembers {
+ overflow: auto;
+
+ .actors-list {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ align-items: stretch;
+ width: 100%;
+
+ .actor-resources {
+ display: grid;
+ grid-template:
+ "img header" min-content
+ "img body" 1fr
+ / 7.5rem 1fr;
+ gap: 6px;
+ column-gap: 12px;
+ padding: 6px;
+ background-color: light-dark(@dark-blue-10, @golden-10);
+
+ .actor-img-frame {
+ grid-area: img;
+ width: 7.375rem;
+ height: 7.375rem;
+ position: relative;
+
+ .actor-img {
+ object-fit: cover;
+ object-position: top center;
+ border-radius: 6px;
+ width: 100%;
+ height: 100%;
+ }
+
+ .equipped-weapons {
+ position: absolute;
+ top: -2px;
+ left: -3px;
+ display: flex;
+ flex-direction: column;
+ gap: 1px;
+ img {
+ border-radius: 50%;
+ width: 24px;
+ height: 24px;
+ border: 1px solid light-dark(@dark-blue, @golden);
+ object-fit: cover;
+ }
+ }
+
+ .evasion {
+ position: absolute;
+ top: 1px;
+ right: 1px;
+ width: 1.75rem;
+ height: 1.75rem;
+ background: url('../assets/svg/trait-shield.svg') no-repeat;
+ background-size: 100%;
+ color: var(--color-light-1);
+ font-size: var(--font-size-14);
+ font-weight: 700;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ .threshold-section {
+ position: absolute;
+ left: 0;
+ right: 0;
+ bottom: -2px;
+ margin: auto;
+
+ display: flex;
+ gap: 4px;
+ background-color: light-dark(var(--color-light-1), @dark-blue);
+ color: light-dark(@dark-blue, @golden);
+ padding: 4px 6px;
+ border: 1px solid light-dark(@dark-blue, @golden);
+ border-radius: 3px;
+ align-items: baseline;
+ width: fit-content;
+
+ h4 {
+ font-weight: bold;
+ text-transform: uppercase;
+ white-space: nowrap;
+
+ &.threshold-label {
+ font-size: var(--font-size-10);
+ color: light-dark(@dark-blue, @golden);
+ }
+
+ &.threshold-value {
+ font-size: var(--font-size-11);
+ color: light-dark(@dark, @beige);
+ }
+ }
+ }
+ }
+
+ header {
+ grid-area: header;
+ display: grid;
+ grid-template:
+ "name hope" min-content
+ "subtitle subtitle" min-content
+ / 1fr min-content;
+
+ .actor-name {
+ width: 100%;
+ z-index: 1;
+ font-size: var(--font-size-20);
+ color: light-dark(@dark-blue, @golden);
+ font-weight: bold;
+ }
+
+ .delete-icon {
+ font-size: 0.75em;
+ }
+
+ .subtitle {
+ grid-area: subtitle;
+ font-size: var(--font-size-14);
+ }
+
+ .hope-section {
+ display: flex;
+ background-color: light-dark(transparent, @dark-blue);
+ color: light-dark(@dark-blue, @golden);
+ padding: 3px 6px;
+ border: 1px solid light-dark(@dark-blue, @golden);
+ border-radius: 3px;
+ align-items: center;
+ width: fit-content;
+ margin-left: auto;
+
+ h4 {
+ font-size: var(--font-size-12);
+ font-weight: bold;
+ text-transform: uppercase;
+ color: light-dark(@dark-blue, @golden);
+ margin-right: 3px;
+ }
+
+ .hope-value {
+ display: flex;
+ cursor: pointer;
+ font-size: var(--font-size-12);
+ margin-left: 1px;
+ }
+ }
+ }
+
+ .body {
+ grid-area: body;
+ display: flex;
+ align-items: start;
+ justify-content: space-between;
+ }
+
+ .resources {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+
+ .slot-section {
+ display: flex;
+ flex-direction: row;
+ align-items: stretch;
+
+ .slot-label {
+ display: flex;
+ align-items: center;
+ color: light-dark(@beige, @dark-blue);
+ background: light-dark(@dark-blue, @golden);
+ padding: 0 4px;
+ width: fit-content;
+ font-weight: bold;
+ border-radius: 6px 0px 0px 6px;
+ font-size: var(--font-size-12);
+ white-space: nowrap;
+
+ .label {
+ padding-right: 2px;
+ }
+
+ .value {
+ font-variant-numeric: tabular-nums;
+ .current {
+ display: inline-block;
+ text-align: end;
+ width: 2ch;
+ }
+ .max {
+ display: inline-block;
+ text-align: start;
+ width: 2ch;
+ }
+ }
+ }
+
+ .slot-bar {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 4px;
+
+ background-color: light-dark(@dark-blue-10, @dark-blue);
+ color: light-dark(@dark-blue, @golden);
+ padding: 2px 5px;
+ border: 1px solid light-dark(@dark-blue, @golden);
+ border-radius: 0 6px 6px 0;
+ width: fit-content;
+ min-height: 22px;
+
+ .armor-slot {
+ cursor: pointer;
+ transition: all 0.3s ease;
+ font-size: var(--font-size-12);
+
+ .fa-shield-halved {
+ color: light-dark(@dark-blue-40, @golden-40);
+ }
+ }
+
+ .slot {
+ width: 16px;
+ height: 10px;
+ border: 1px solid light-dark(@dark-blue, @golden);
+ background: light-dark(@dark-blue-10, @golden-10);
+ border-radius: 3px;
+ transition: all 0.3s ease;
+ cursor: pointer;
+
+ &.filled {
+ background: light-dark(@dark-blue, @golden);
+ }
+ }
+ }
+ }
+ }
+
+ .traits {
+ background-color: light-dark(@dark-blue-10, @dark-blue);
+ border: 1px solid light-dark(@dark-blue, @golden);
+ border-radius: 6px;
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ font-size: var(--font-size-12);
+ padding: 3px 4px;
+ gap: 3px 7px;
+ .trait {
+ display: flex;
+ justify-content: space-between;
+ gap: 3px;
+ .label {
+ color: light-dark(@dark-blue, @golden);
+ }
+ .value {
+ font-weight: 600;
+ }
+ }
+ }
+ }
+ }
+
+ .actors-dragger {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-sizing: border-box;
+ width: 100%;
+ height: 40px;
+ border: 1px dashed light-dark(@dark-blue-50, @beige-50);
+ border-radius: 3px;
+ color: light-dark(@dark-blue-50, @beige-50);
+ }
+}
diff --git a/styles/less/sheets/actors/party/resources.less b/styles/less/sheets/actors/party/resources.less
deleted file mode 100644
index 68628295..00000000
--- a/styles/less/sheets/actors/party/resources.less
+++ /dev/null
@@ -1,216 +0,0 @@
-@import '../../../utils/colors.less';
-@import '../../../utils/fonts.less';
-@import '../../../utils/mixin.less';
-
-body.game:is(.performance-low, .noblur) {
- .application.sheet.daggerheart.actor.dh-style.party .tab.resources .actors-list .actor-resources {
- background: light-dark(@dark-blue, @dark-golden);
-
- .actor-name {
- background: light-dark(@dark-blue, @dark-golden);
- }
- }
-}
-
-.application.sheet.daggerheart.actor.dh-style.party {
- .tab.resources {
- overflow: auto;
-
- .actors-list {
- display: flex;
- flex-direction: row;
- flex-wrap: wrap;
- gap: 10px;
- align-items: center;
- width: 100%;
- justify-content: center;
-
- .actor-resources {
- display: flex;
- flex-direction: column;
- position: relative;
- background: light-dark(@dark-blue-40, @dark-golden-40);
- border-radius: 6px;
- border: 1px solid light-dark(@dark-blue, @golden);
- width: 230px;
- height: -webkit-fill-available;
-
- .actor-name {
- position: absolute;
- top: 0px;
- background: light-dark(@dark-blue-90, @dark-golden-80);
- backdrop-filter: blur(6.5px);
- border-radius: 6px 6px 0px 0px;
- text-align: center;
- width: 100%;
- z-index: 1;
- font-size: var(--font-size-20);
- color: light-dark(@beige, @golden);
- font-weight: bold;
- padding: 5px 0;
- }
-
- .actor-img {
- height: 150px;
- object-fit: cover;
- object-position: top center;
- border-radius: 6px 6px 0px 0px;
- mask-image: linear-gradient(180deg, black 88%, transparent 100%);
- }
-
- .resources {
- display: flex;
- flex-direction: column;
- gap: 10px;
- align-items: center;
- margin: 10px;
-
- .slot-section {
- display: flex;
- flex-direction: column;
- align-items: center;
-
- .slot-bar {
- display: flex;
- flex-wrap: wrap;
- gap: 5px;
- width: 239px;
-
- background-color: light-dark(@dark-blue-10, @dark-blue);
- color: light-dark(@dark-blue, @golden);
- padding: 5px;
- border: 1px solid light-dark(@dark-blue, @golden);
- border-radius: 6px;
- width: fit-content;
-
- .armor-slot {
- cursor: pointer;
- transition: all 0.3s ease;
- font-size: var(--font-size-12);
-
- .fa-shield-halved {
- color: light-dark(@dark-blue-40, @golden-40);
- }
- }
-
- .slot {
- width: 20px;
- height: 10px;
- border: 1px solid light-dark(@dark-blue, @golden);
- background: light-dark(@dark-blue-10, @golden-10);
- border-radius: 3px;
- transition: all 0.3s ease;
- cursor: pointer;
-
- &.filled {
- background: light-dark(@dark-blue, @golden);
- }
- }
- }
- .slot-label {
- display: flex;
- align-items: center;
- color: light-dark(@beige, @dark-blue);
- background: light-dark(@dark-blue, @golden);
- padding: 0 5px;
- width: fit-content;
- font-weight: bold;
- border-radius: 0px 0px 5px 5px;
- font-size: var(--font-size-12);
-
- .label {
- padding-right: 5px;
- }
-
- .value {
- padding-left: 6px;
- border-left: 1px solid light-dark(@beige, @dark-golden);
- }
- }
- }
-
- .hope-section {
- position: relative;
- display: flex;
- gap: 10px;
- background-color: light-dark(transparent, @dark-blue);
- color: light-dark(@dark-blue, @golden);
- padding: 5px 10px;
- border: 1px solid light-dark(@dark-blue, @golden);
- border-radius: 3px;
- align-items: center;
- width: fit-content;
-
- h4 {
- font-size: var(--font-size-12);
- font-weight: bold;
- text-transform: uppercase;
- color: light-dark(@dark-blue, @golden);
- }
-
- .hope-value {
- display: flex;
- cursor: pointer;
- font-size: var(--font-size-12);
- }
- }
-
- .stat-section {
- position: relative;
- display: flex;
- gap: 10px;
- background-color: light-dark(transparent, @dark-blue);
- color: light-dark(@dark-blue, @golden);
- padding: 5px 10px;
- border: 1px solid light-dark(@dark-blue, @golden);
- border-radius: 3px;
- align-items: center;
- width: fit-content;
-
- h4 {
- font-size: var(--font-size-12);
- font-weight: bold;
- text-transform: uppercase;
- color: light-dark(@dark-blue, @golden);
- }
- }
-
- .threshold-section {
- display: flex;
- align-self: center;
- gap: 10px;
- background-color: light-dark(transparent, @dark-blue);
- color: light-dark(@dark-blue, @golden);
- padding: 5px 10px;
- border: 1px solid light-dark(@dark-blue, @golden);
- border-radius: 3px;
- align-items: center;
- width: fit-content;
-
- h4 {
- font-size: var(--font-size-12);
- font-weight: bold;
- text-transform: uppercase;
- color: light-dark(@dark-blue, @golden);
-
- &.threshold-value {
- color: light-dark(@dark, @beige);
- }
- }
- }
- }
- }
- }
- .actors-dragger {
- display: flex;
- align-items: center;
- justify-content: center;
- box-sizing: border-box;
- width: 100%;
- height: 40px;
- border: 1px dashed light-dark(@dark-blue-50, @beige-50);
- border-radius: 3px;
- color: light-dark(@dark-blue-50, @beige-50);
- }
- }
-}
diff --git a/styles/less/sheets/index.less b/styles/less/sheets/index.less
index e5ffbf3e..7d595614 100644
--- a/styles/less/sheets/index.less
+++ b/styles/less/sheets/index.less
@@ -31,7 +31,6 @@
@import './actors/party/party-members.less';
@import './actors/party/sheet.less';
@import './actors/party/inventory.less';
-@import './actors/party/resources.less';
@import './items/beastform.less';
@import './items/class.less';
diff --git a/styles/less/ui/combat-sidebar/encounter-controls.less b/styles/less/ui/combat-sidebar/encounter-controls.less
index 16a8e11a..66f265e0 100644
--- a/styles/less/ui/combat-sidebar/encounter-controls.less
+++ b/styles/less/ui/combat-sidebar/encounter-controls.less
@@ -10,6 +10,7 @@
.encounter-battlepoints {
display: flex;
cursor: help;
+ color: light-dark(@dark, @beige);
}
.inner-controls {
diff --git a/styles/less/ui/settings/appearance-settings/diceSoNice.less b/styles/less/ui/settings/appearance-settings/diceSoNice.less
index 079bebc5..a4846596 100644
--- a/styles/less/ui/settings/appearance-settings/diceSoNice.less
+++ b/styles/less/ui/settings/appearance-settings/diceSoNice.less
@@ -68,5 +68,29 @@
text-align: center;
white-space: nowrap;
}
+
+ color-picker {
+ gap: 4px;
+ background: inherit;
+ }
+ }
+
+ .animation-container {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+
+ .animation-inner-container {
+ display: flex;
+ align-items: center;
+ justify-content: right;
+ gap: 8px;
+
+ .animation-control {
+ display: flex;
+ align-items: center;
+ gap: 2px;
+ }
+ }
}
}
diff --git a/system.json b/system.json
index 28d849b3..babdde26 100644
--- a/system.json
+++ b/system.json
@@ -2,12 +2,15 @@
"id": "daggerheart",
"title": "Daggerheart",
"description": "An unofficial implementation of the Daggerheart system",
- "version": "2.0.3",
+ "version": "2.1.2",
"compatibility": {
"minimum": "14.359",
- "verified": "14.359",
+ "verified": "14.360",
"maximum": "14"
},
+ "url": "https://github.com/Foundryborne/daggerheart",
+ "manifest": "https://raw.githubusercontent.com/Foundryborne/daggerheart/v14/system.json",
+ "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.1.2/system.zip",
"authors": [
{
"name": "WBHarry"
@@ -290,14 +293,10 @@
"damageRoll": {},
"abilityUse": {},
"tagTeam": {},
- "groupRoll": {},
"systemMessage": {}
}
},
"background": "systems/daggerheart/assets/logos/FoundrybornBackgroundLogo.png",
"primaryTokenAttribute": "resources.hitPoints",
- "secondaryTokenAttribute": "resources.stress",
- "url": "https://github.com/Foundryborne/daggerheart",
- "manifest": "https://raw.githubusercontent.com/Foundryborne/daggerheart/main/system.json",
- "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.0.3/system.zip"
+ "secondaryTokenAttribute": "resources.stress"
}
diff --git a/templates/actionTypes/damage.hbs b/templates/actionTypes/damage.hbs
index 9e7c2884..454d0413 100644
--- a/templates/actionTypes/damage.hbs
+++ b/templates/actionTypes/damage.hbs
@@ -8,17 +8,20 @@
{{/if}}
{{#unless (eq path 'system.attack.')}} {{/unless}}
-
- {{localize "Cancel"}}
+ {{localize "COMMON.Cancel"}}
diff --git a/templates/dialogs/dice-roll/damageSelection.hbs b/templates/dialogs/dice-roll/damageSelection.hbs
index b2d1a895..7bcd7063 100644
--- a/templates/dialogs/dice-roll/damageSelection.hbs
+++ b/templates/dialogs/dice-roll/damageSelection.hbs
@@ -35,13 +35,31 @@
-
+
{{localize "DAGGERHEART.GENERAL.criticalShort"}}
{{/each}}
+
+ {{#if damageOptions.groupAttack}}
+
+ {{localize "DAGGERHEART.ACTIONS.Settings.groupAttack.label"}}
+
+
+
+
+
+
+ {{selectOptions rangeOptions selected=damageOptions.groupAttack.range localize=true}}
+
+
+
+
+
+ {{/if}}
+
{{#unless (empty @root.modifiers)}}
{{localize "DAGGERHEART.GENERAL.Modifier.plural"}}
diff --git a/templates/dialogs/dice-roll/rollSelection.hbs b/templates/dialogs/dice-roll/rollSelection.hbs
index 64a3cdcb..2c1a21b6 100644
--- a/templates/dialogs/dice-roll/rollSelection.hbs
+++ b/templates/dialogs/dice-roll/rollSelection.hbs
@@ -157,8 +157,8 @@
{{add this 1}}
{{/times}}
-
- {{selectOptions diceOptions selected=(concat 'd' @root.roll.advantageFaces)}}
+
+ {{selectOptions dieFaces selected=@root.roll.advantageFaces}}
{{#if abilities}}
@@ -175,7 +175,7 @@
{{/if}}
{{#if (eq @root.rollType 'DualityRoll')}}{{localize "DAGGERHEART.GENERAL.situationalBonus"}} {{/if}}
-
+
{{/if}}
{{/unless}}
diff --git a/templates/dialogs/downtime/downtime.hbs b/templates/dialogs/downtime/downtime.hbs
index 9744ffea..edaa81c1 100644
--- a/templates/dialogs/downtime/downtime.hbs
+++ b/templates/dialogs/downtime/downtime.hbs
@@ -28,7 +28,7 @@
- {{localize "Cancel"}}
+ {{localize "COMMON.Cancel"}}
{{localize "DAGGERHEART.APPLICATIONS.Downtime.takeDowntime"}}
\ No newline at end of file
diff --git a/templates/dialogs/groupRollDialog/footer.hbs b/templates/dialogs/groupRollDialog/footer.hbs
new file mode 100644
index 00000000..e401966b
--- /dev/null
+++ b/templates/dialogs/groupRollDialog/footer.hbs
@@ -0,0 +1,6 @@
+
+
+ {{localize "COMMON.Cancel"}}
+ {{localize "DAGGERHEART.APPLICATIONS.GroupRollSelect.finishGroupRoll"}}
+
+
\ No newline at end of file
diff --git a/templates/dialogs/groupRollDialog/groupRoll.hbs b/templates/dialogs/groupRollDialog/groupRoll.hbs
new file mode 100644
index 00000000..edf1c980
--- /dev/null
+++ b/templates/dialogs/groupRollDialog/groupRoll.hbs
@@ -0,0 +1,20 @@
+
+
+ {{localize "DAGGERHEART.GENERAL.result.single"}}
+
+
+ {{#if hasRolled}}
{{groupRoll.total}} {{groupRoll.totalLabel}} {{/if}}
+
+ {{#if groupRoll.leaderTotal includeZero=true}}{{groupRoll.leaderTotal}}{{else}}{{localize "DAGGERHEART.APPLICATIONS.GroupRollSelect.leaderRoll"}}{{/if}}
+ {{#each groupRoll.modifiers as |modifier|}}
+ {{#if (gte modifier 0)}}+{{else}}-{{/if}}
+ {{positive modifier}}
+ {{/each}}
+ {{#unless groupRoll.modifiers.length}}
+ +
+ {{localize "DAGGERHEART.GENERAL.Modifier.plural"}}
+ {{/unless}}
+
+
+
+
\ No newline at end of file
diff --git a/templates/dialogs/groupRollDialog/groupRollMember.hbs b/templates/dialogs/groupRollDialog/groupRollMember.hbs
new file mode 100644
index 00000000..acf8e8f1
--- /dev/null
+++ b/templates/dialogs/groupRollDialog/groupRollMember.hbs
@@ -0,0 +1,85 @@
+{{#with (lookup members partId)}}
+
+
+
+
+
+
+ {{#if readyToRoll}}
+
+
+ {{localize "DAGGERHEART.GENERAL.roll"}}
+
+
+
+ {{#if roll}}
+
+
{{roll.total}} {{localize "DAGGERHEART.GENERAL.withThing" thing=roll.totalLabel}}
+
+
+ {{roll.dHope.total}}
+
+
+
+
+
+ {{roll.dFear.total}}
+
+
+ {{#if roll.advantage.type}}
+
{{#if (eq roll.advantage.type 1)}}+{{else}}-{{/if}}
+
+ {{roll.advantage.value}}
+
+
+ {{/if}}
+
{{#if (gte roll.modifierTotal 0)}}+{{else}}-{{/if}}
+
{{positive roll.modifierTotal}}
+
+
+ {{else}}
+
{{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.makeYourRoll"}}
+ {{/if}}
+
+ {{/if}}
+ {{#if hasRolled}}
+
+ {{#if ../isGM}}
+
+ {{/if}}
+
+ {{localize "DAGGERHEART.GENERAL.Modifier.single"}}{{#if successfull}} + 1{{else if (isNullish successfull)}} + ?{{else}} - 1{{/if}}
+
+
+ {{/if}}
+
+
+{{/with}}
\ No newline at end of file
diff --git a/templates/dialogs/groupRollDialog/initialization.hbs b/templates/dialogs/groupRollDialog/initialization.hbs
new file mode 100644
index 00000000..a520b8bd
--- /dev/null
+++ b/templates/dialogs/groupRollDialog/initialization.hbs
@@ -0,0 +1,32 @@
+
\ No newline at end of file
diff --git a/templates/dialogs/groupRollDialog/leader.hbs b/templates/dialogs/groupRollDialog/leader.hbs
new file mode 100644
index 00000000..3d5db3f7
--- /dev/null
+++ b/templates/dialogs/groupRollDialog/leader.hbs
@@ -0,0 +1,73 @@
+
+ {{#with leader}}
+
+
{{localize "DAGGERHEART.APPLICATIONS.GroupRollSelect.leader"}}
+
+
+
+
+
+
+
+
+ {{#if readyToRoll}}
+
+
+ {{localize "DAGGERHEART.GENERAL.roll"}}
+
+
+
+ {{#if roll}}
+
+
{{roll.total}} {{localize "DAGGERHEART.GENERAL.withThing" thing=roll.totalLabel}}
+
+
+ {{roll.dHope.total}}
+
+
+
+
+
+ {{roll.dFear.total}}
+
+
+ {{#if roll.advantage.type}}
+
{{#if (eq roll.advantage.type 1)}}+{{else}}-{{/if}}
+
+ {{roll.advantage.value}}
+
+
+ {{/if}}
+
{{#if (gte roll.modifierTotal 0)}}+{{else}}-{{/if}}
+
{{positive roll.modifierTotal}}
+
+
+ {{else}}
+
{{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.makeYourRoll"}}
+ {{/if}}
+
+ {{/if}}
+
+
+ {{/with}}
+
\ No newline at end of file
diff --git a/templates/dialogs/image-select/footer.hbs b/templates/dialogs/image-select/footer.hbs
index cd7d3d1a..58a60cc4 100644
--- a/templates/dialogs/image-select/footer.hbs
+++ b/templates/dialogs/image-select/footer.hbs
@@ -1,4 +1,4 @@
\ No newline at end of file
diff --git a/templates/dialogs/item-transfer.hbs b/templates/dialogs/item-transfer.hbs
index 0e7df3dc..63972ed8 100644
--- a/templates/dialogs/item-transfer.hbs
+++ b/templates/dialogs/item-transfer.hbs
@@ -14,7 +14,7 @@
diff --git a/templates/dialogs/multiclassChoice.hbs b/templates/dialogs/multiclassChoice.hbs
index 3c89ff1a..55365939 100644
--- a/templates/dialogs/multiclassChoice.hbs
+++ b/templates/dialogs/multiclassChoice.hbs
@@ -16,7 +16,7 @@
- {{localize "Cancel"}}
+ {{localize "COMMON.Cancel"}}
{{localize "DAGGERHEART.GENERAL.multiclass"}}
diff --git a/templates/dialogs/tagTeamDialog/initialization.hbs b/templates/dialogs/tagTeamDialog/initialization.hbs
index d25e8f6c..7ccdf566 100644
--- a/templates/dialogs/tagTeamDialog/initialization.hbs
+++ b/templates/dialogs/tagTeamDialog/initialization.hbs
@@ -1,5 +1,4 @@
- {{partId}}
{{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.selectParticipants"}}
{{#each memberSelection as |member|}}
diff --git a/templates/levelup/tabs/footer.hbs b/templates/levelup/tabs/footer.hbs
index 2ee7a316..d487e657 100644
--- a/templates/levelup/tabs/footer.hbs
+++ b/templates/levelup/tabs/footer.hbs
@@ -20,7 +20,7 @@
{{/if}}
{{#unless levelupAuto}}
{{/unless}}
diff --git a/templates/settings/appearance-settings/diceSoNice.hbs b/templates/settings/appearance-settings/diceSoNice.hbs
index afe7dd5a..6cd4e52e 100644
--- a/templates/settings/appearance-settings/diceSoNice.hbs
+++ b/templates/settings/appearance-settings/diceSoNice.hbs
@@ -9,9 +9,16 @@
{{/if}}
diff --git a/templates/settings/appearance-settings/diceSoNiceTab.hbs b/templates/settings/appearance-settings/diceSoNiceTab.hbs
index a15b63ec..89b587af 100644
--- a/templates/settings/appearance-settings/diceSoNiceTab.hbs
+++ b/templates/settings/appearance-settings/diceSoNiceTab.hbs
@@ -42,9 +42,15 @@
{{#if animations}}
{{localize "DAGGERHEART.SETTINGS.Menu.appearance.diceSoNice.animations"}}
-
+
{{localize "DAGGERHEART.CONFIG.DaggerheartDiceAnimationEvents.higher.name"}}
- {{formInput fields.sfx.fields.higher.fields.class value=values.sfx.higher.class blank="" localize=true}}
+
+ {{formInput fields.sfx.fields.higher.fields.class value=values.sfx.higher.class blank="" localize=true}}
+
+ {{localize "DAGGERHEART.SETTINGS.Menu.appearance.diceSoNice.muted"}}
+ {{formInput fields.sfx.fields.higher.fields.options.fields.muteSound value=values.sfx.higher.options.muteSound localize=true}}
+
+
{{/if}}
diff --git a/templates/settings/automation-settings/footer.hbs b/templates/settings/automation-settings/footer.hbs
index 54939c17..14ff5bb5 100644
--- a/templates/settings/automation-settings/footer.hbs
+++ b/templates/settings/automation-settings/footer.hbs
@@ -1,10 +1,10 @@
\ No newline at end of file
diff --git a/templates/settings/downtime-config/actions.hbs b/templates/settings/downtime-config/actions.hbs
index d197f983..feb05302 100644
--- a/templates/settings/downtime-config/actions.hbs
+++ b/templates/settings/downtime-config/actions.hbs
@@ -8,7 +8,7 @@
{{#each move.actions as |action|}}
- {{> "systems/daggerheart/templates/settings/components/settings-item-line.hbs" id=action.id }}
+ {{> "systems/daggerheart/templates/settings/components/settings-item-line.hbs" id=action.id type="action" }}
{{/each}}
diff --git a/templates/settings/downtime-config/footer.hbs b/templates/settings/downtime-config/footer.hbs
index 5e5f31dd..199aea15 100644
--- a/templates/settings/downtime-config/footer.hbs
+++ b/templates/settings/downtime-config/footer.hbs
@@ -1,4 +1,4 @@
\ No newline at end of file
diff --git a/templates/settings/homebrew-settings/footer.hbs b/templates/settings/homebrew-settings/footer.hbs
index 954572de..fc84fde7 100644
--- a/templates/settings/homebrew-settings/footer.hbs
+++ b/templates/settings/homebrew-settings/footer.hbs
@@ -1,10 +1,10 @@
\ No newline at end of file
diff --git a/templates/settings/metagaming-settings/footer.hbs b/templates/settings/metagaming-settings/footer.hbs
index 54939c17..14ff5bb5 100644
--- a/templates/settings/metagaming-settings/footer.hbs
+++ b/templates/settings/metagaming-settings/footer.hbs
@@ -1,10 +1,10 @@
\ No newline at end of file
diff --git a/templates/settings/variant-rules.hbs b/templates/settings/variant-rules.hbs
index df7accb3..cdaef024 100644
--- a/templates/settings/variant-rules.hbs
+++ b/templates/settings/variant-rules.hbs
@@ -32,11 +32,11 @@
diff --git a/templates/sheets-settings/action-settings/effect.hbs b/templates/sheets-settings/action-settings/effect.hbs
index 1bdd0304..567cb81c 100644
--- a/templates/sheets-settings/action-settings/effect.hbs
+++ b/templates/sheets-settings/action-settings/effect.hbs
@@ -5,7 +5,7 @@
>
{{#if fields.roll}}{{> 'systems/daggerheart/templates/actionTypes/roll.hbs' fields=fields.roll.fields source=source.roll}}{{/if}}
{{#if fields.save}}{{> 'systems/daggerheart/templates/actionTypes/save.hbs' fields=fields.save.fields source=source.save}}{{/if}}
- {{#if fields.damage}}{{> 'systems/daggerheart/templates/actionTypes/damage.hbs' fields=fields.damage.fields.parts.element.fields source=source.damage directField=fields.damage.fields.direct }}{{/if}}
+ {{#if fields.damage}}{{> 'systems/daggerheart/templates/actionTypes/damage.hbs' fields=fields.damage.fields.parts.element.fields source=source.damage baseFields=fields.damage.fields }}{{/if}}
{{#if fields.macro}}{{> 'systems/daggerheart/templates/actionTypes/macro.hbs' fields=fields.macro source=source.macro}}{{/if}}
{{#if fields.effects}}{{> 'systems/daggerheart/templates/actionTypes/effect.hbs' fields=fields.effects.element.fields source=source.effects}}{{/if}}
{{#if fields.beastform}}{{> 'systems/daggerheart/templates/actionTypes/beastform.hbs' fields=fields.beastform.fields source=source.beastform}}{{/if}}
diff --git a/templates/sheets-settings/adversary-settings/attack.hbs b/templates/sheets-settings/adversary-settings/attack.hbs
index f829338f..41960032 100644
--- a/templates/sheets-settings/adversary-settings/attack.hbs
+++ b/templates/sheets-settings/adversary-settings/attack.hbs
@@ -22,5 +22,5 @@
{{formGroup systemFields.criticalThreshold value=document._source.system.criticalThreshold label="DAGGERHEART.ACTIONS.Settings.criticalThreshold" name="system.criticalThreshold" localize=true}}
- {{> 'systems/daggerheart/templates/actionTypes/damage.hbs' fields=systemFields.attack.fields.damage.fields.parts.element.fields source=document.system.attack.damage path="system.attack." directField=systemFields.attack.fields.damage.fields.direct horde=(eq document._source.system.type 'horde')}}
+ {{> 'systems/daggerheart/templates/actionTypes/damage.hbs' fields=systemFields.attack.fields.damage.fields.parts.element.fields source=document.system.attack.damage path="system.attack." baseFields=systemFields.attack.fields.damage.fields horde=(eq document._source.system.type 'horde')}}
\ No newline at end of file
diff --git a/templates/sheets-settings/companion-settings/attack.hbs b/templates/sheets-settings/companion-settings/attack.hbs
index f99f7d8c..41451ef0 100644
--- a/templates/sheets-settings/companion-settings/attack.hbs
+++ b/templates/sheets-settings/companion-settings/attack.hbs
@@ -18,5 +18,5 @@
{{/if}}
{{/if}}
- {{> 'systems/daggerheart/templates/actionTypes/damage.hbs' fields=systemFields.attack.fields.damage.fields.parts.element.fields source=document.system.attack.damage path="system.attack." directField=systemFields.attack.fields.damage.fields.direct}}
+ {{> 'systems/daggerheart/templates/actionTypes/damage.hbs' fields=systemFields.attack.fields.damage.fields.parts.element.fields source=document.system.attack.damage path="system.attack." baseFields=systemFields.attack.fields.damage.fields}}
\ No newline at end of file
diff --git a/templates/sheets/activeEffect/settings.hbs b/templates/sheets/activeEffect/settings.hbs
index 9307ff65..09b78856 100644
--- a/templates/sheets/activeEffect/settings.hbs
+++ b/templates/sheets/activeEffect/settings.hbs
@@ -26,11 +26,11 @@
{{formGroup systemFields.duration.fields.type value=source.system.duration.type localize=true }}
-
\ No newline at end of file
diff --git a/templates/sheets/actors/character/effects.hbs b/templates/sheets/actors/character/effects.hbs
index 3355d575..a2e5a420 100644
--- a/templates/sheets/actors/character/effects.hbs
+++ b/templates/sheets/actors/character/effects.hbs
@@ -7,7 +7,7 @@
type='effect'
isGlassy=true
collection=effects.actives
- canCreate=true
+ canCreate=@root.editable
hideResources=true
}}
@@ -16,7 +16,7 @@
type='effect'
isGlassy=true
collection=effects.inactives
- canCreate=true
+ canCreate=@root.editable
hideResources=true
disabled=true
}}
diff --git a/templates/sheets/actors/character/features.hbs b/templates/sheets/actors/character/features.hbs
index 3e942468..70544683 100644
--- a/templates/sheets/actors/character/features.hbs
+++ b/templates/sheets/actors/character/features.hbs
@@ -8,8 +8,8 @@
type='feature'
actorType='character'
collection=category.values
- canCreate=true
- showActions=true
+ canCreate=@root.editable
+ showActions=@root.editable
}}
{{else if category.values}}
{{> 'daggerheart.inventory-items'
@@ -18,7 +18,7 @@
actorType='character'
collection=category.values
canCreate=false
- showActions=true
+ showActions=@root.editable
}}
{{/if}}
diff --git a/templates/sheets/actors/character/header.hbs b/templates/sheets/actors/character/header.hbs
index 06f464fa..a75b2c2f 100644
--- a/templates/sheets/actors/character/header.hbs
+++ b/templates/sheets/actors/character/header.hbs
@@ -4,17 +4,29 @@
- {{#if (or document.system.needsCharacterSetup document.system.levelData.canLevelUp)}}
-
-
-
+ {{#if @root.editable}}
+ {{#if document.system.needsCharacterSetup}}
+
+ {{localize "DAGGERHEART.APPLICATIONS.CharacterCreation.buttonTitle"}}
+
+ {{else if document.system.levelData.canLevelUp}}
+
+
+
+ {{/if}}
{{/if}}
- {{localize 'DAGGERHEART.GENERAL.level'}}
-
+ {{#unless document.system.needsCharacterSetup}}
+ {{localize 'DAGGERHEART.GENERAL.level'}}
+
+ {{/unless}}
@@ -100,12 +112,14 @@
{{/if}}
-
-
-
-
-
-
+ {{#if @root.editable}}
+
+
+
+
+
+
+ {{/if}}
diff --git a/templates/sheets/actors/character/inventory.hbs b/templates/sheets/actors/character/inventory.hbs
index a05fed35..c3ddb0ad 100644
--- a/templates/sheets/actors/character/inventory.hbs
+++ b/templates/sheets/actors/character/inventory.hbs
@@ -5,7 +5,7 @@
-
+
@@ -13,18 +13,7 @@
{{#if this.inventory.hasCurrency}}
-
- {{#each this.inventory.currencies as |currency key|}}
- {{#if currency.enabled}}
-
-
- {{localize currency.label}}
-
-
-
- {{/if}}
- {{/each}}
-
+ {{> "systems/daggerheart/templates/sheets/global/partials/gold.hbs" currencies=inventory.currencies}}
{{/if}}
@@ -33,7 +22,7 @@
type='weapon'
collection=@root.inventory.weapons
isGlassy=true
- canCreate=true
+ canCreate=@root.editable
hideResources=true
}}
{{> 'daggerheart.inventory-items'
@@ -41,7 +30,7 @@
type='armor'
collection=@root.inventory.armor
isGlassy=true
- canCreate=true
+ canCreate=@root.editable
hideResources=true
}}
{{> 'daggerheart.inventory-items'
@@ -49,15 +38,17 @@
type='consumable'
collection=@root.inventory.consumables
isGlassy=true
- canCreate=true
+ canCreate=@root.editable
+ isQuantifiable=true
}}
{{> 'daggerheart.inventory-items'
title='TYPES.Item.loot'
type='loot'
collection=@root.inventory.loot
isGlassy=true
- canCreate=true
- showActions=true
+ canCreate=@root.editable
+ showActions=@root.editable
+ isQuantifiable=true
}}
\ No newline at end of file
diff --git a/templates/sheets/actors/character/loadout.hbs b/templates/sheets/actors/character/loadout.hbs
index 0319d56f..9ba3fb04 100644
--- a/templates/sheets/actors/character/loadout.hbs
+++ b/templates/sheets/actors/character/loadout.hbs
@@ -5,7 +5,7 @@
-
+
@@ -27,7 +27,7 @@
isGlassy=true
cardView=cardView
collection=document.system.domainCards.loadout
- canCreate=true
+ canCreate=@root.editable
}}
{{> 'daggerheart.inventory-items'
title='DAGGERHEART.GENERAL.Tabs.vault'
@@ -35,7 +35,7 @@
isGlassy=true
cardView=cardView
collection=document.system.domainCards.vault
- canCreate=true
+ canCreate=@root.editable
inVault=true
}}
diff --git a/templates/sheets/actors/character/sidebar.hbs b/templates/sheets/actors/character/sidebar.hbs
index d3be4983..0142ac1d 100644
--- a/templates/sheets/actors/character/sidebar.hbs
+++ b/templates/sheets/actors/character/sidebar.hbs
@@ -45,11 +45,11 @@
{{/times}}
-
+
{{localize "DAGGERHEART.GENERAL.armorSlots"}}
{{document.system.armorScore.value}} / {{document.system.armorScore.max}}
-
+ {{#if @root.editable}} {{/if}}
@@ -64,9 +64,9 @@
value='{{document.system.armorScore.value}}'
max='{{document.system.armorScore.max}}'
>
-
+
{{localize "DAGGERHEART.GENERAL.armorSlots"}}
-
+ {{#if @root.editable}} {{/if}}
{{/if}}
diff --git a/templates/sheets/actors/companion/effects.hbs b/templates/sheets/actors/companion/effects.hbs
index cefb6e57..087e8b30 100644
--- a/templates/sheets/actors/companion/effects.hbs
+++ b/templates/sheets/actors/companion/effects.hbs
@@ -6,7 +6,7 @@
type='effect'
isGlassy=true
collection=effects.actives
- canCreate=true
+ canCreate=@root.editable
hideResources=true
}}
@@ -15,7 +15,7 @@
type='effect'
isGlassy=true
collection=effects.inactives
- canCreate=true
+ canCreate=@root.editable
hideResources=true
}}
diff --git a/templates/sheets/actors/environment/features.hbs b/templates/sheets/actors/environment/features.hbs
index 3ad36023..3fd512da 100644
--- a/templates/sheets/actors/environment/features.hbs
+++ b/templates/sheets/actors/environment/features.hbs
@@ -9,8 +9,8 @@
type='feature'
collection=@root.features
hideContextMenu=true
- canCreate=true
- showActions=true
+ canCreate=@root.editable
+ showActions=@root.editable
}}
\ No newline at end of file
diff --git a/templates/sheets/actors/party/header.hbs b/templates/sheets/actors/party/header.hbs
index f39f683f..3fdb137d 100644
--- a/templates/sheets/actors/party/header.hbs
+++ b/templates/sheets/actors/party/header.hbs
@@ -3,7 +3,6 @@
\ No newline at end of file
diff --git a/templates/sheets/actors/party/inventory.hbs b/templates/sheets/actors/party/inventory.hbs
index 92371b8d..cf056608 100644
--- a/templates/sheets/actors/party/inventory.hbs
+++ b/templates/sheets/actors/party/inventory.hbs
@@ -5,7 +5,7 @@
-
+
@@ -16,18 +16,7 @@
{{#if inventory.hasCurrency}}
-
- {{#each this.inventory.currencies as |currency key|}}
- {{#if currency.enabled}}
-
-
- {{localize currency.label}}
-
-
-
- {{/if}}
- {{/each}}
-
+ {{> "systems/daggerheart/templates/sheets/global/partials/gold.hbs" currencies=inventory.currencies}}
{{/if}}
@@ -37,9 +26,10 @@
actorType='party'
collection=@root.inventory.weapons
isGlassy=true
- canCreate=true
+ canCreate=@root.editable
hideResources=true
hideContextMenu=true
+ isQuantifiable=true
}}
{{> 'daggerheart.inventory-items'
title='TYPES.Item.armor'
@@ -47,9 +37,10 @@
actorType='party'
collection=@root.inventory.armor
isGlassy=true
- canCreate=true
+ canCreate=@root.editable
hideResources=true
hideContextMenu=true
+ isQuantifiable=true
}}
{{> 'daggerheart.inventory-items'
title='TYPES.Item.consumable'
@@ -57,8 +48,9 @@
actorType='party'
collection=@root.inventory.consumables
isGlassy=true
- canCreate=true
+ canCreate=@root.editable
hideContextMenu=true
+ isQuantifiable=true
}}
{{> 'daggerheart.inventory-items'
title='TYPES.Item.loot'
@@ -66,8 +58,9 @@
actorType='party'
collection=@root.inventory.loot
isGlassy=true
- canCreate=true
+ canCreate=@root.editable
hideContextMenu=true
+ isQuantifiable=true
}}
\ No newline at end of file
diff --git a/templates/sheets/actors/party/party-members.hbs b/templates/sheets/actors/party/party-members.hbs
index b3dd53e6..aa41aeaa 100644
--- a/templates/sheets/actors/party/party-members.hbs
+++ b/templates/sheets/actors/party/party-members.hbs
@@ -7,35 +7,162 @@
- Tag Team Roll
+ {{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.title"}}
-
+
- Group Roll
+ {{localize "DAGGERHEART.APPLICATIONS.GroupRollSelect.title"}}
+
+
+
+ {{localize "DAGGERHEART.APPLICATIONS.Downtime.shortRest.title"}}
+
+
+
+ {{localize "DAGGERHEART.APPLICATIONS.Downtime.longRest.title"}}
- {{!-- NOT YET IMPLEMENTED --}}
- {{!--
-
- Help Action
- --}}
-
- {{localize tabs.partyMembers.label}}
-
- {{#each document.system.partyMembers as |actor id|}}
- {{> 'daggerheart.inventory-item'
- item=actor
- type='character'
- isActor=true
- hideContextMenu=true
- }}
- {{/each}}
-
- {{#unless document.system.partyMembers.length}}
-
- {{localize "DAGGERHEART.GENERAL.dropActorsHere"}}
-
- {{/unless}}
-
-
\ No newline at end of file
+
+ {{#each partyMembers as |member id|}}
+
+
+
+ {{#if member.weapons}}
+
+ {{#each member.weapons as |weapon|}}
+
+ {{/each}}
+
+ {{/if}}
+ {{#if member.evasion includeZero=true}}
+
{{member.evasion}}
+ {{/if}}
+ {{#if member.difficulty includeZero=true}}
+
{{member.difficulty}}
+ {{/if}}
+ {{#unless (eq member.type 'companion')}}
+
+
{{localize "DAGGERHEART.ACTORS.Party.Thresholds.minor"}}
+ {{member.damageThresholds.major}}
+ {{localize "DAGGERHEART.ACTORS.Party.Thresholds.major"}}
+ {{member.damageThresholds.severe}}
+ {{localize "DAGGERHEART.ACTORS.Party.Thresholds.severe"}}
+
+ {{/unless}}
+
+
+
+
+ {{#unless (eq member.type 'companion') }}
+
+
+
+
+
+
+ {{member.resources.hitPoints.value}}
+ /
+ {{member.resources.hitPoints.max}}
+
+
+
+ {{#times member.resources.hitPoints.max}}
+
+
+ {{/times}}
+
+
+ {{/unless}}
+
+
+
+
+
+
+
+ {{member.resources.stress.value}}
+ /
+ {{member.resources.stress.max}}
+
+
+
+ {{#times member.resources.stress.max}}
+
+
+ {{/times}}
+
+
+
+ {{#if member.armorScore.max}}
+
+
+
+
+
+
+ {{member.armorScore.value}}
+ /
+ {{member.armorScore.max}}
+
+
+
+
+ {{/if}}
+
+ {{#if member.traits}}
+
+ {{#each member.traits as |trait|}}
+
+ {{trait.label}}
+ {{trait.value}}
+
+ {{/each}}
+
+ {{/if}}
+
+
+ {{/each}}
+
+ {{#unless document.system.partyMembers.length}}
+
+ {{localize "DAGGERHEART.GENERAL.dropActorsHere"}}
+
+ {{/unless}}
+
diff --git a/templates/sheets/actors/party/resources.hbs b/templates/sheets/actors/party/resources.hbs
deleted file mode 100644
index b53282ca..00000000
--- a/templates/sheets/actors/party/resources.hbs
+++ /dev/null
@@ -1,110 +0,0 @@
-
-
-
-
- {{localize "DAGGERHEART.APPLICATIONS.Downtime.longRest.title"}}
-
-
-
- {{localize "DAGGERHEART.APPLICATIONS.Downtime.shortRest.title"}}
-
-
-
-
- {{localize tabs.resources.label}}
-
- {{#each document.system.partyMembers as |actor id|}}
-
- {{actor.name}}
-
-
- {{#unless (eq actor.type 'companion') }}
-
-
- {{#times actor.system.resources.hitPoints.max}}
-
-
- {{/times}}
-
-
- {{localize "DAGGERHEART.GENERAL.HitPoints.short"}}
- {{actor.system.resources.hitPoints.value}} / {{actor.system.resources.hitPoints.max}}
-
-
- {{/unless}}
-
-
-
- {{#times actor.system.resources.stress.max}}
-
-
- {{/times}}
-
-
- {{localize "DAGGERHEART.GENERAL.stress"}}
- {{actor.system.resources.stress.value}} / {{actor.system.resources.stress.max}}
-
-
-
- {{#if actor.system.armorScore.max}}
-
-
-
- {{localize "DAGGERHEART.GENERAL.armorSlots"}}
- {{actor.system.armorScore.value}} / {{actor.system.armorScore.max}}
-
-
- {{/if}}
-
- {{#unless (or (eq actor.type 'companion') (eq actor.type 'adversary')) }}
-
-
{{localize "DAGGERHEART.GENERAL.hope"}}
- {{#times actor.system.resources.hope.max}}
-
- {{#if (gte actor.system.resources.hope.value (add this 1))}}
-
- {{else}}
-
- {{/if}}
-
- {{/times}}
-
- {{/unless}}
-
- {{#if (eq actor.type 'character')}}
-
-
{{localize "DAGGERHEART.GENERAL.evasion"}}: {{actor.system.evasion}}
-
- {{/if}}
-
- {{#unless (eq actor.type 'companion')}}
-
-
{{localize "DAGGERHEART.GENERAL.DamageThresholds.minor"}}
- {{actor.system.damageThresholds.major}}
- {{localize "DAGGERHEART.GENERAL.DamageThresholds.major"}}
- {{actor.system.damageThresholds.severe}}
- {{localize "DAGGERHEART.GENERAL.DamageThresholds.severe"}}
-
- {{/unless}}
-
-
- {{/each}}
-
-
-
\ No newline at end of file
diff --git a/templates/sheets/global/partials/gold.hbs b/templates/sheets/global/partials/gold.hbs
new file mode 100644
index 00000000..7aba2815
--- /dev/null
+++ b/templates/sheets/global/partials/gold.hbs
@@ -0,0 +1,12 @@
+
+ {{#each currencies as |currency key|}}
+ {{#if currency.enabled}}
+
+
+ {{localize currency.label}}
+
+
+
+ {{/if}}
+ {{/each}}
+
\ No newline at end of file
diff --git a/templates/sheets/global/partials/inventory-fieldset-items-V2.hbs b/templates/sheets/global/partials/inventory-fieldset-items-V2.hbs
index 31c8f7f5..3f58b80b 100644
--- a/templates/sheets/global/partials/inventory-fieldset-items-V2.hbs
+++ b/templates/sheets/global/partials/inventory-fieldset-items-V2.hbs
@@ -52,12 +52,11 @@ Parameters:
{{else}}
{{#each collection as |item|}}
-
{{> 'daggerheart.inventory-item'
item=item
type=../type
disabledEffect=../disabledEffect
- actorType=../actorType
+ actorType=(ifThen ../actorType ../actorType @root.document.type)
hideControls=../hideControls
hideContextMenu=../hideContextMenu
isActor=../isActor
@@ -66,6 +65,7 @@ Parameters:
showLabels=../showLabels
isAction=../isAction
hideResources=../hideResources
+ isQuantifiable=../isQuantifiable
showActions=../showActions
}}
diff --git a/templates/sheets/global/partials/inventory-item-V2.hbs b/templates/sheets/global/partials/inventory-item-V2.hbs
index 2129b969..d76f2897 100644
--- a/templates/sheets/global/partials/inventory-item-V2.hbs
+++ b/templates/sheets/global/partials/inventory-item-V2.hbs
@@ -25,11 +25,11 @@ Parameters:
>