Date: Tue, 26 Aug 2025 14:42:07 +0200
Subject: [PATCH 6/9] added note to translations (#1093)
* added note to translations
* clarified note
---
CONTRIBUTING.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index b6f4097e..80c01bce 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -17,6 +17,10 @@ We welcome contributions of all kinds:
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
From dc54fdc096bd08d2176fc1cd9c48309386dce281 Mon Sep 17 00:00:00 2001
From: WBHarry <89362246+WBHarry@users.noreply.github.com>
Date: Tue, 26 Aug 2025 20:30:27 +0200
Subject: [PATCH 7/9] [Fix] 1091 - Improve Homebrew Domain Container (#1095)
* Simple fix for now
* Corrected to 2 rows of height
* Corrected scrollbar
---
styles/less/ui/settings/homebrew-settings/domains.less | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/styles/less/ui/settings/homebrew-settings/domains.less b/styles/less/ui/settings/homebrew-settings/domains.less
index e0e0dcd7..893f58be 100644
--- a/styles/less/ui/settings/homebrew-settings/domains.less
+++ b/styles/less/ui/settings/homebrew-settings/domains.less
@@ -54,6 +54,10 @@
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr;
gap: 8px;
+ max-height: 184px;
+ overflow: auto;
+ scrollbar-width: thin;
+ scrollbar-color: light-dark(#18162e, #f3c267) transparent;
.domain-container {
position: relative;
From 8e5dd2237085cb43fd08b65586e3f21a0b5f94ff Mon Sep 17 00:00:00 2001
From: WBHarry <89362246+WBHarry@users.noreply.github.com>
Date: Wed, 27 Aug 2025 02:12:48 +0200
Subject: [PATCH 8/9] [Fix] 900 - Compendium Self Targetting (#1077)
* Fixed classes and subclasses
* Went over DomainCards
* Everything except de-facto-items
* Fixed items
* Fixed merge conflicts
---
module/config/itemConfig.mjs | 3 +
.../feature_Retract_UFR67BUOhNGLFyg9.json | 10 +--
.../feature_Wings_WquAjoOcso8lwySW.json | 10 +--
...eature_Armored_Shell_nDQZdIF2epKlhauX.json | 10 +--
.../feature_Rampage_8upqfcZvi7b5hRLE.json | 70 +++++++++++++---
.../feature_Takedown_0ey4kM9ssj2otHvb.json | 23 +++---
...ature_Frontline_Tank_YS1g7YdWwOaS629x.json | 10 +--
...eature_Hold_Them_Off_2Cyb9ZeuAesf5Sb3.json | 10 +--
.../feature_No_Mercy_njj2C3tMDeCHHOoh.json | 10 +--
...ature_Ranger_s_Focus_ncLx2P8BOUtrAD38.json | 60 ++++++++++++--
...eature_Rogue_s_Dodge_hVaaPIjxoextIgSL.json | 16 ++--
...feature_Sneak_Attack_5QqpEwmwkPfZHpMW.json | 36 +--------
...inCard_Bold_Presence_tdsL00yTSLNgZWs6.json | 18 ++---
...inCard_Book_of_Ronin_SZMNR3uGNinJcN4N.json | 10 +--
...inCard_Book_of_Sitil_eq8VNqYMRHhF9xw9.json | 10 +--
...nCard_Book_of_Yarrow_J1ovx2FpNDvPq1o6.json | 8 +-
...inCard_Breaking_Blow_8UANBgSdhMZ0sqfO.json | 8 +-
...nCard_Cloaking_Blast_Zhw7PtK8nMPlsOqD.json | 10 +--
...inCard_Conjure_Swarm_rZPH0BY8Sznc9sFG.json | 10 +--
...ainCard_Deadly_Focus_xxZOXC4tiZQ6kg1e.json | 36 ++++-----
...Card_Force_of_Nature_LzVpMkD5I4QeaIHf.json | 10 +--
.../domainCard_Frenzy_MMl7abdGRLl7TJLO.json | 10 +--
...omainCard_Full_Surge_SgvjJfMyubZowPxS.json | 18 ++---
...inCard_Hold_the_Line_kdFoLo3KXwn4LqTG.json | 26 +++---
...ard_Hypnotic_Shimmer_2ZeuCGVatQdPOVC6.json | 8 +-
...Card_Lead_by_Example_YWCRplmtwpCjpq5i.json | 8 +-
...rd_Overwhelming_Aura_iEBLySZD9z8CLdz7.json | 10 +--
.../domainCard_Rage_Up_GRL0cvs96vrTDckZ.json | 20 ++---
...nCard_Rain_of_Blades_Ucenef6JpjQxwXni.json | 8 +-
...nCard_Salvation_Beam_4uAFGp3LxiC07woC.json | 8 +-
.../domainCard_Smite_U1uWJE94HZVudujz.json | 10 +--
..._Specter_of_the_Dark_iQhgqmLwhcSTYnvr.json | 10 +--
...omainCard_Thorn_Skin_oUipGK84E2KjoKqh.json | 10 +--
...ard_Uncanny_Disguise_TV56wSysbU5xAlOa.json | 10 +--
...inCard_Unleash_Chaos_o62i0QdbUDIiAhSq.json | 10 +--
...Card_Vanishing_Dodge_GBMIElIpk4cvk1Bd.json | 8 +-
...inCard_Veil_of_Night_gV4L5ZZmfPrEbIDh.json | 10 +--
.../folders_Splendor_TL1TutmbeCVJ06nR.json | 8 +-
.../folders_Valor_yPVeShe47ETIqs9q.json | 8 +-
...mable_Armor_Stitcher_VlbsCjvvLNfTzNXb.json | 10 +--
...umable_Attune_Potion_JGD3M9hBHtVAA8XP.json | 8 +-
...e_Blood_of_the_Yorgi_pDGzmczoTlKGmKgd.json | 10 +--
...mable_Bolster_Potion_FOPQNqXbiVO0ilYL.json | 8 +-
...consumable_Death_Tea_xDnJeF1grkmKck8Q.json | 10 +--
...able_Dragonbloom_Tea_wM18PWWW2Ami4fBG.json | 10 +--
...nsumable_Featherbone_DpxEMpwfasEBpORU.json | 64 +++++++++++++--
...onsumable_Gill_Salve_Nvbb9mze6o5D0AEg.json | 64 +++++++++++++--
.../loot_Glamour_Stone_Pj17cvdJ1XG1jv6I.json | 68 +++++++++++++---
...loot_Piercing_Arrows_I63LTFD6GXHgyGpR.json | 8 +-
...loot_Premium_Bedroll_QGYPNBIufpBguwjC.json | 10 +--
...t_Ring_of_Resistance_aUqRifqR5JXXa1dN.json | 79 +++++++++++++++++--
...loot_Ring_of_Silence_K1ysGnTpNyxPu5Au.json | 43 +++++-----
...ture_Act_of_Reprisal_k7vvMJtEcxMWUUrW.json | 8 +-
.../feature_Adrenaline_uByM34yQlw38yf1V.json | 37 +--------
...eature_Battle_Bonded_hWsKyed1vfILg0I8.json | 54 ++++++++++++-
...feature_Elementalist_dPcqKN5NeDkjB1HW.json | 28 ++++---
...ure_Elusive_Predator_Cjtc43V3IzAmfIFG.json | 54 ++++++++++++-
...re_Ruthless_Predator_Qny2J3R35bvC0Cey.json | 10 +--
...ature_Shadow_Stepper_hAwTXjhyphiE3aeW.json | 10 +--
...eature_Vanishing_Act_iyIg1VLwO8C6jvFZ.json | 18 ++---
60 files changed, 786 insertions(+), 413 deletions(-)
diff --git a/module/config/itemConfig.mjs b/module/config/itemConfig.mjs
index cee4bc52..7e9f75ea 100644
--- a/module/config/itemConfig.mjs
+++ b/module/config/itemConfig.mjs
@@ -865,6 +865,9 @@ export const weaponFeatures = {
name: 'DAGGERHEART.CONFIG.WeaponFeature.greedy.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.greedy.description',
img: 'icons/commodities/currency/coins-crown-stack-gold.webp',
+ target: {
+ type: 'self'
+ },
// Should cost handful of gold,
effects: [
{
diff --git a/src/packs/ancestries/feature_Retract_UFR67BUOhNGLFyg9.json b/src/packs/ancestries/feature_Retract_UFR67BUOhNGLFyg9.json
index 59e33ce4..0e3e6555 100644
--- a/src/packs/ancestries/feature_Retract_UFR67BUOhNGLFyg9.json
+++ b/src/packs/ancestries/feature_Retract_UFR67BUOhNGLFyg9.json
@@ -23,12 +23,12 @@
},
"effects": [],
"target": {
- "type": "any",
+ "type": "self",
"amount": null
},
"name": "Mark Stress",
"img": "icons/magic/defensive/shield-barrier-flaming-diamond-teal.webp",
- "range": ""
+ "range": "self"
}
},
"originItemType": null,
@@ -111,10 +111,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1753996513763,
- "modifiedTime": 1755394440308,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756040910699,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_key": "!items!UFR67BUOhNGLFyg9"
}
diff --git a/src/packs/ancestries/feature_Wings_WquAjoOcso8lwySW.json b/src/packs/ancestries/feature_Wings_WquAjoOcso8lwySW.json
index 7bc73dc6..87aa36a9 100644
--- a/src/packs/ancestries/feature_Wings_WquAjoOcso8lwySW.json
+++ b/src/packs/ancestries/feature_Wings_WquAjoOcso8lwySW.json
@@ -31,12 +31,12 @@
},
"effects": [],
"target": {
- "type": "any",
+ "type": "self",
"amount": null
},
"name": "Mark Stress",
"img": "icons/creatures/abilities/wing-batlike-white-blue.webp",
- "range": ""
+ "range": "self"
}
},
"originItemType": null,
@@ -113,10 +113,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1753994723305,
- "modifiedTime": 1755394368487,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756040981649,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_key": "!items!WquAjoOcso8lwySW"
}
diff --git a/src/packs/beastforms/feature_Armored_Shell_nDQZdIF2epKlhauX.json b/src/packs/beastforms/feature_Armored_Shell_nDQZdIF2epKlhauX.json
index b4d711a5..26534190 100644
--- a/src/packs/beastforms/feature_Armored_Shell_nDQZdIF2epKlhauX.json
+++ b/src/packs/beastforms/feature_Armored_Shell_nDQZdIF2epKlhauX.json
@@ -24,7 +24,7 @@
],
"uses": {
"value": null,
- "max": null,
+ "max": "",
"recovery": null
},
"effects": [
@@ -39,7 +39,7 @@
},
"name": "Retract",
"img": "icons/creatures/reptiles/turtle-shell-glowing-green.webp",
- "range": ""
+ "range": "self"
}
},
"originItemType": null,
@@ -149,10 +149,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1753580983699,
- "modifiedTime": 1755395623887,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756041050697,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_id": "nDQZdIF2epKlhauX",
"sort": 2200000,
diff --git a/src/packs/beastforms/feature_Rampage_8upqfcZvi7b5hRLE.json b/src/packs/beastforms/feature_Rampage_8upqfcZvi7b5hRLE.json
index 3ac9731d..e9b157ba 100644
--- a/src/packs/beastforms/feature_Rampage_8upqfcZvi7b5hRLE.json
+++ b/src/packs/beastforms/feature_Rampage_8upqfcZvi7b5hRLE.json
@@ -24,22 +24,22 @@
],
"uses": {
"value": null,
- "max": null,
+ "max": "",
"recovery": null
},
"effects": [
{
- "_id": "5TX0hHFKZHvBeWne",
+ "_id": "A7l4JEBC1FFQajsN",
"onSave": false
}
],
"target": {
- "type": null,
+ "type": "self",
"amount": null
},
- "name": "Gain Proficiency",
+ "name": "Mark Stress",
"img": "icons/creatures/abilities/bear-roar-bite-brown.webp",
- "range": ""
+ "range": "self"
}
},
"originItemType": null,
@@ -51,7 +51,59 @@
"artist": ""
}
},
- "effects": [],
+ "effects": [
+ {
+ "name": "Rampage",
+ "img": "icons/creatures/claws/claw-bear-paw-swipe-red.webp",
+ "origin": "Compendium.daggerheart.beastforms.Item.8upqfcZvi7b5hRLE",
+ "transfer": false,
+ "_id": "A7l4JEBC1FFQajsN",
+ "type": "base",
+ "system": {
+ "rangeDependence": {
+ "enabled": false,
+ "type": "withinRange",
+ "target": "hostile",
+ "range": "melee"
+ }
+ },
+ "changes": [
+ {
+ "key": "system.proficiency",
+ "mode": 2,
+ "value": "1",
+ "priority": null
+ }
+ ],
+ "disabled": false,
+ "duration": {
+ "startTime": null,
+ "combat": null,
+ "seconds": null,
+ "rounds": null,
+ "turns": null,
+ "startRound": null,
+ "startTurn": null
+ },
+ "description": "Before you make an attack roll, you can mark a Stress to gain a +1 bonus to your Proficiency for that attack.
",
+ "tint": "#ffffff",
+ "statuses": [],
+ "sort": 0,
+ "flags": {},
+ "_stats": {
+ "compendiumSource": null,
+ "duplicateSource": null,
+ "exportSource": null,
+ "coreVersion": "13.347",
+ "systemId": "daggerheart",
+ "systemVersion": "1.1.0",
+ "createdTime": 1756041194412,
+ "modifiedTime": 1756041218646,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
+ },
+ "_key": "!items.effects!8upqfcZvi7b5hRLE.A7l4JEBC1FFQajsN"
+ }
+ ],
"folder": "uU8bIoZvXge0rLaU",
"ownership": {
"default": 0,
@@ -64,10 +116,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1753582591417,
- "modifiedTime": 1755395649724,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756041228102,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_id": "8upqfcZvi7b5hRLE",
"sort": 2000000,
diff --git a/src/packs/beastforms/feature_Takedown_0ey4kM9ssj2otHvb.json b/src/packs/beastforms/feature_Takedown_0ey4kM9ssj2otHvb.json
index a1a14198..f31ed781 100644
--- a/src/packs/beastforms/feature_Takedown_0ey4kM9ssj2otHvb.json
+++ b/src/packs/beastforms/feature_Takedown_0ey4kM9ssj2otHvb.json
@@ -24,7 +24,7 @@
],
"uses": {
"value": null,
- "max": null,
+ "max": "",
"recovery": null
},
"damage": {
@@ -58,7 +58,7 @@
"includeBase": false
},
"target": {
- "type": "any",
+ "type": "self",
"amount": null
},
"effects": [],
@@ -84,7 +84,7 @@
},
"name": "Attack",
"img": "icons/creatures/abilities/paw-print-orange.webp",
- "range": ""
+ "range": "self"
}
},
"originItemType": null,
@@ -101,7 +101,7 @@
"name": "Takedown",
"img": "icons/creatures/abilities/paw-print-orange.webp",
"origin": "Item.okjlLUwEdNOKeUk3",
- "transfer": false,
+ "transfer": true,
"_id": "hE6ciIusvKEtUQ8U",
"type": "base",
"system": {},
@@ -113,7 +113,7 @@
"priority": null
}
],
- "disabled": false,
+ "disabled": true,
"duration": {
"startTime": null,
"combat": null,
@@ -123,7 +123,7 @@
"startRound": null,
"startTurn": null
},
- "description": "",
+ "description": "You gain a +2 bonus to your Proficiency for this attack and the target must mark a Stress.
",
"tint": "#ffffff",
"statuses": [],
"sort": 0,
@@ -132,10 +132,11 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
- "coreVersion": "13.346",
+ "coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
- "lastModifiedBy": null
+ "lastModifiedBy": "vUIbuan0U50nfKBE",
+ "modifiedTime": 1756041368153
},
"_key": "!items.effects!0ey4kM9ssj2otHvb.hE6ciIusvKEtUQ8U"
}
@@ -152,10 +153,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1753621786000,
- "modifiedTime": 1755395696040,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756041242273,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_id": "0ey4kM9ssj2otHvb",
"sort": 600000,
diff --git a/src/packs/classes/feature_Frontline_Tank_YS1g7YdWwOaS629x.json b/src/packs/classes/feature_Frontline_Tank_YS1g7YdWwOaS629x.json
index a0b56f5f..701f8eb9 100644
--- a/src/packs/classes/feature_Frontline_Tank_YS1g7YdWwOaS629x.json
+++ b/src/packs/classes/feature_Frontline_Tank_YS1g7YdWwOaS629x.json
@@ -60,7 +60,7 @@
"includeBase": false
},
"target": {
- "type": "any",
+ "type": "self",
"amount": null
},
"effects": [],
@@ -81,7 +81,7 @@
},
"name": "Spend Hope",
"img": "icons/magic/defensive/shield-barrier-flaming-pentagon-orange.webp",
- "range": ""
+ "range": "self"
}
},
"originItemType": null,
@@ -105,10 +105,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1754246687097,
- "modifiedTime": 1755391259920,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756034159296,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_key": "!items!YS1g7YdWwOaS629x"
}
diff --git a/src/packs/classes/feature_Hold_Them_Off_2Cyb9ZeuAesf5Sb3.json b/src/packs/classes/feature_Hold_Them_Off_2Cyb9ZeuAesf5Sb3.json
index f2e99b8f..3a640f48 100644
--- a/src/packs/classes/feature_Hold_Them_Off_2Cyb9ZeuAesf5Sb3.json
+++ b/src/packs/classes/feature_Hold_Them_Off_2Cyb9ZeuAesf5Sb3.json
@@ -5,14 +5,14 @@
"_id": "2Cyb9ZeuAesf5Sb3",
"img": "icons/magic/defensive/barrier-shield-dome-deflect-teal.webp",
"system": {
- "description": "Spend 3 Hope when you succeed on an attack with a weapon to use that same roll against two additional adversaries within range of the attack.
",
+ "description": "Spend 3 Hope when you succeed on an attack with a weapon to use that same roll against two additional adversaries within range of the attack.
",
"resource": null,
"actions": {
"yhVUna5biFAN0o2Y": {
"type": "effect",
"_id": "yhVUna5biFAN0o2Y",
"systemPath": "actions",
- "description": "Spend 3 Hope when you succeed on an attack with a weapon to use that same roll against two additional adversaries within range of the attack.
",
+ "description": "Spend 3 Hope when you succeed on an attack with a weapon to use that same roll against two additional adversaries within range of the attack.
",
"chatDisplay": true,
"actionType": "action",
"cost": [
@@ -60,10 +60,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1754268431889,
- "modifiedTime": 1755391281371,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756034191317,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_key": "!items!2Cyb9ZeuAesf5Sb3"
}
diff --git a/src/packs/classes/feature_No_Mercy_njj2C3tMDeCHHOoh.json b/src/packs/classes/feature_No_Mercy_njj2C3tMDeCHHOoh.json
index b131fcdf..7d689fe3 100644
--- a/src/packs/classes/feature_No_Mercy_njj2C3tMDeCHHOoh.json
+++ b/src/packs/classes/feature_No_Mercy_njj2C3tMDeCHHOoh.json
@@ -38,12 +38,12 @@
}
],
"target": {
- "type": "any",
+ "type": "self",
"amount": null
},
"name": "Spend Hope",
"img": "icons/magic/holy/barrier-shield-winged-blue.webp",
- "range": ""
+ "range": "self"
}
},
"originItemType": null,
@@ -119,10 +119,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1754257270096,
- "modifiedTime": 1755391439746,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756034406042,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_key": "!items!njj2C3tMDeCHHOoh"
}
diff --git a/src/packs/classes/feature_Ranger_s_Focus_ncLx2P8BOUtrAD38.json b/src/packs/classes/feature_Ranger_s_Focus_ncLx2P8BOUtrAD38.json
index 4f798663..d3d10bc0 100644
--- a/src/packs/classes/feature_Ranger_s_Focus_ncLx2P8BOUtrAD38.json
+++ b/src/packs/classes/feature_Ranger_s_Focus_ncLx2P8BOUtrAD38.json
@@ -29,7 +29,12 @@
"max": "",
"recovery": null
},
- "effects": [],
+ "effects": [
+ {
+ "_id": "SXi2dQWqpwY9fap4",
+ "onSave": false
+ }
+ ],
"target": {
"type": "any",
"amount": null
@@ -47,7 +52,52 @@
"artist": ""
}
},
- "effects": [],
+ "effects": [
+ {
+ "name": "Ranger's Focus",
+ "img": "icons/magic/perception/eye-ringed-green.webp",
+ "origin": "Compendium.daggerheart.classes.Item.ncLx2P8BOUtrAD38",
+ "transfer": false,
+ "_id": "SXi2dQWqpwY9fap4",
+ "type": "base",
+ "system": {
+ "rangeDependence": {
+ "enabled": false,
+ "type": "withinRange",
+ "target": "hostile",
+ "range": "melee"
+ }
+ },
+ "changes": [],
+ "disabled": false,
+ "duration": {
+ "startTime": null,
+ "combat": null,
+ "seconds": null,
+ "rounds": null,
+ "turns": null,
+ "startRound": null,
+ "startTurn": null
+ },
+ "description": "Until this feature ends or the ranger makes a different creature their Focus, they gain the following benefits against this adversary:
They know precisely what direction they are in.
When they deal damage to them, the adverasry must mark a Stress.
When they fail an attack against them, they can end their Ranger’s Focus feature to reroll their Duality Dice.
",
+ "tint": "#ffffff",
+ "statuses": [],
+ "sort": 0,
+ "flags": {},
+ "_stats": {
+ "compendiumSource": null,
+ "duplicateSource": null,
+ "exportSource": null,
+ "coreVersion": "13.347",
+ "systemId": "daggerheart",
+ "systemVersion": "1.1.0",
+ "createdTime": 1756034238996,
+ "modifiedTime": 1756034332923,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
+ },
+ "_key": "!items.effects!ncLx2P8BOUtrAD38.SXi2dQWqpwY9fap4"
+ }
+ ],
"sort": 0,
"ownership": {
"default": 0,
@@ -60,10 +110,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1754268505051,
- "modifiedTime": 1755391289388,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756034239047,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_key": "!items!ncLx2P8BOUtrAD38"
}
diff --git a/src/packs/classes/feature_Rogue_s_Dodge_hVaaPIjxoextIgSL.json b/src/packs/classes/feature_Rogue_s_Dodge_hVaaPIjxoextIgSL.json
index 244f46c5..fcc178d0 100644
--- a/src/packs/classes/feature_Rogue_s_Dodge_hVaaPIjxoextIgSL.json
+++ b/src/packs/classes/feature_Rogue_s_Dodge_hVaaPIjxoextIgSL.json
@@ -36,7 +36,7 @@
}
],
"target": {
- "type": "any",
+ "type": "self",
"amount": null
},
"name": "Spend Hope",
@@ -76,7 +76,7 @@
"priority": null
}
],
- "disabled": true,
+ "disabled": false,
"duration": {
"startTime": null,
"combat": null,
@@ -95,12 +95,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
- "coreVersion": "13.346",
+ "coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1754323997794,
- "modifiedTime": 1754351805065,
- "lastModifiedBy": "MQSznptE5yLT7kj8"
+ "modifiedTime": 1756034385415,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_key": "!items.effects!hVaaPIjxoextIgSL.hhVjBro2osGDTT5g"
}
@@ -117,10 +117,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1754323951411,
- "modifiedTime": 1755391319440,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756034357629,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_key": "!items!hVaaPIjxoextIgSL"
}
diff --git a/src/packs/classes/feature_Sneak_Attack_5QqpEwmwkPfZHpMW.json b/src/packs/classes/feature_Sneak_Attack_5QqpEwmwkPfZHpMW.json
index eb637edd..8984606c 100644
--- a/src/packs/classes/feature_Sneak_Attack_5QqpEwmwkPfZHpMW.json
+++ b/src/packs/classes/feature_Sneak_Attack_5QqpEwmwkPfZHpMW.json
@@ -7,35 +7,7 @@
"system": {
"description": "When you succeed on an attack while Cloaked or while an ally is within Melee range of your target, add a number of d6s equal to your tier to your damage roll.
Level 1 -> Tier 1
Levels 2–4 -> Tier 2
Levels 5–7 -> Tier 3
Levels 8–10 -> Tier 4
",
"resource": null,
- "actions": {
- "SfctrIW5KjH4nq5G": {
- "type": "effect",
- "_id": "SfctrIW5KjH4nq5G",
- "systemPath": "actions",
- "description": "When you succeed on an attack while Cloaked or while an ally is within Melee range of your target, add a number of d6s equal to your tier to your damage roll.
Level 1 -> Tier 1
Levels 2–4 -> Tier 2
Levels 5–7 -> Tier 3
Levels 8–10 -> Tier 4
",
- "chatDisplay": true,
- "actionType": "action",
- "cost": [],
- "uses": {
- "value": null,
- "max": "",
- "recovery": null
- },
- "effects": [
- {
- "_id": "380jFzw756qSy5ae",
- "onSave": false
- }
- ],
- "target": {
- "type": "any",
- "amount": null
- },
- "name": "Use Sneak Attack",
- "img": "icons/skills/melee/strike-dagger-skull-white.webp",
- "range": ""
- }
- },
+ "actions": {},
"originItemType": null,
"originId": null,
"attribution": {
@@ -115,10 +87,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1754324216454,
- "modifiedTime": 1755391313725,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756034698108,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_key": "!items!5QqpEwmwkPfZHpMW"
}
diff --git a/src/packs/domains/domainCard_Bold_Presence_tdsL00yTSLNgZWs6.json b/src/packs/domains/domainCard_Bold_Presence_tdsL00yTSLNgZWs6.json
index 164ffb48..c83c1fde 100644
--- a/src/packs/domains/domainCard_Bold_Presence_tdsL00yTSLNgZWs6.json
+++ b/src/packs/domains/domainCard_Bold_Presence_tdsL00yTSLNgZWs6.json
@@ -43,7 +43,7 @@
},
"name": "Spend Hope",
"img": "icons/magic/holy/barrier-shield-winged-blue.webp",
- "range": ""
+ "range": "self"
},
"OdRTXMOXhZFiZHES": {
"type": "effect",
@@ -81,10 +81,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1753922784418,
- "modifiedTime": 1755429846358,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756038980509,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_id": "tdsL00yTSLNgZWs6",
"sort": 3400000,
@@ -93,7 +93,7 @@
"name": "Bold Presence",
"img": "icons/magic/holy/barrier-shield-winged-blue.webp",
"origin": "Compendium.daggerheart.domains.Item.tdsL00yTSLNgZWs6",
- "transfer": true,
+ "transfer": false,
"_id": "2XEYhuAcRGTtqvED",
"type": "base",
"system": {
@@ -112,7 +112,7 @@
"priority": null
}
],
- "disabled": true,
+ "disabled": false,
"duration": {
"startTime": null,
"combat": null,
@@ -131,12 +131,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
- "coreVersion": "13.346",
+ "coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1754241531511,
- "modifiedTime": 1754241605959,
- "lastModifiedBy": "MQSznptE5yLT7kj8"
+ "modifiedTime": 1756038989398,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_key": "!items.effects!tdsL00yTSLNgZWs6.2XEYhuAcRGTtqvED"
}
diff --git a/src/packs/domains/domainCard_Book_of_Ronin_SZMNR3uGNinJcN4N.json b/src/packs/domains/domainCard_Book_of_Ronin_SZMNR3uGNinJcN4N.json
index b6f8bfcc..862d5da9 100644
--- a/src/packs/domains/domainCard_Book_of_Ronin_SZMNR3uGNinJcN4N.json
+++ b/src/packs/domains/domainCard_Book_of_Ronin_SZMNR3uGNinJcN4N.json
@@ -28,7 +28,7 @@
"includeBase": false
},
"target": {
- "type": "any",
+ "type": "self",
"amount": null
},
"effects": [
@@ -59,7 +59,7 @@
},
"name": "Transform",
"img": "icons/containers/barrels/barrel-reinforced-cherry-brown.webp",
- "range": ""
+ "range": "self"
},
"h7i4ZuDYuYLnjze6": {
"type": "attack",
@@ -126,10 +126,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1753922784424,
- "modifiedTime": 1755429102985,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756038121293,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_id": "SZMNR3uGNinJcN4N",
"sort": 3400000,
diff --git a/src/packs/domains/domainCard_Book_of_Sitil_eq8VNqYMRHhF9xw9.json b/src/packs/domains/domainCard_Book_of_Sitil_eq8VNqYMRHhF9xw9.json
index 4ee7c45c..3c60a08d 100644
--- a/src/packs/domains/domainCard_Book_of_Sitil_eq8VNqYMRHhF9xw9.json
+++ b/src/packs/domains/domainCard_Book_of_Sitil_eq8VNqYMRHhF9xw9.json
@@ -30,12 +30,12 @@
}
],
"target": {
- "type": "any",
+ "type": "self",
"amount": null
},
"name": "Adjust Appearance",
"img": "icons/magic/defensive/shield-barrier-blades-teal.webp",
- "range": ""
+ "range": "self"
},
"wBQkw3P4Esj6kOx2": {
"type": "effect",
@@ -132,10 +132,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1753922784424,
- "modifiedTime": 1755428993189,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756037990822,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_id": "eq8VNqYMRHhF9xw9",
"sort": 3400000,
diff --git a/src/packs/domains/domainCard_Book_of_Yarrow_J1ovx2FpNDvPq1o6.json b/src/packs/domains/domainCard_Book_of_Yarrow_J1ovx2FpNDvPq1o6.json
index 8cef1c1e..8d91c1ee 100644
--- a/src/packs/domains/domainCard_Book_of_Yarrow_J1ovx2FpNDvPq1o6.json
+++ b/src/packs/domains/domainCard_Book_of_Yarrow_J1ovx2FpNDvPq1o6.json
@@ -89,7 +89,7 @@
},
"name": "Magic Immunity",
"img": "icons/magic/defensive/barrier-shield-dome-deflect-teal.webp",
- "range": ""
+ "range": "self"
}
},
"attribution": {
@@ -105,10 +105,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1753922784429,
- "modifiedTime": 1755429115570,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756038143042,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_id": "J1ovx2FpNDvPq1o6",
"sort": 3400000,
diff --git a/src/packs/domains/domainCard_Breaking_Blow_8UANBgSdhMZ0sqfO.json b/src/packs/domains/domainCard_Breaking_Blow_8UANBgSdhMZ0sqfO.json
index 567d7d57..3781989d 100644
--- a/src/packs/domains/domainCard_Breaking_Blow_8UANBgSdhMZ0sqfO.json
+++ b/src/packs/domains/domainCard_Breaking_Blow_8UANBgSdhMZ0sqfO.json
@@ -41,7 +41,7 @@
],
"target": {
"type": "any",
- "amount": null
+ "amount": 1
},
"name": "Mark Stress",
"img": "icons/skills/wounds/bone-broken-knee-beam.webp",
@@ -61,10 +61,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1753922784431,
- "modifiedTime": 1755428345979,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756037935304,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_id": "8UANBgSdhMZ0sqfO",
"sort": 3400000,
diff --git a/src/packs/domains/domainCard_Cloaking_Blast_Zhw7PtK8nMPlsOqD.json b/src/packs/domains/domainCard_Cloaking_Blast_Zhw7PtK8nMPlsOqD.json
index f19833c5..5314de9e 100644
--- a/src/packs/domains/domainCard_Cloaking_Blast_Zhw7PtK8nMPlsOqD.json
+++ b/src/packs/domains/domainCard_Cloaking_Blast_Zhw7PtK8nMPlsOqD.json
@@ -40,12 +40,12 @@
}
],
"target": {
- "type": "any",
+ "type": "self",
"amount": null
},
"name": "Become Cloaked",
"img": "icons/magic/perception/shadow-stealth-eyes-purple.webp",
- "range": ""
+ "range": "self"
}
},
"attribution": {
@@ -61,10 +61,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1753922784435,
- "modifiedTime": 1755428020354,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756037373705,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_id": "Zhw7PtK8nMPlsOqD",
"sort": 3400000,
diff --git a/src/packs/domains/domainCard_Conjure_Swarm_rZPH0BY8Sznc9sFG.json b/src/packs/domains/domainCard_Conjure_Swarm_rZPH0BY8Sznc9sFG.json
index 9d243ade..82d68f9c 100644
--- a/src/packs/domains/domainCard_Conjure_Swarm_rZPH0BY8Sznc9sFG.json
+++ b/src/packs/domains/domainCard_Conjure_Swarm_rZPH0BY8Sznc9sFG.json
@@ -36,7 +36,7 @@
"includeBase": false
},
"target": {
- "type": "any",
+ "type": "self",
"amount": null
},
"effects": [
@@ -67,7 +67,7 @@
},
"name": "Tekaira's Armored Beetles: Stress",
"img": "icons/creatures/invertebrates/wasp-swarm-attack.webp",
- "range": ""
+ "range": "self"
},
"533qzPIjcccpiMey": {
"type": "effect",
@@ -195,10 +195,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1753922784438,
- "modifiedTime": 1755429481288,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756038512929,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_id": "rZPH0BY8Sznc9sFG",
"sort": 3400000,
diff --git a/src/packs/domains/domainCard_Deadly_Focus_xxZOXC4tiZQ6kg1e.json b/src/packs/domains/domainCard_Deadly_Focus_xxZOXC4tiZQ6kg1e.json
index df82f1aa..9246896d 100644
--- a/src/packs/domains/domainCard_Deadly_Focus_xxZOXC4tiZQ6kg1e.json
+++ b/src/packs/domains/domainCard_Deadly_Focus_xxZOXC4tiZQ6kg1e.json
@@ -26,17 +26,17 @@
},
"effects": [
{
- "_id": "6sR46Hd554DiLHy4",
+ "_id": "TFWiAUDCfGaax5MU",
"onSave": false
}
],
"target": {
- "type": "any",
- "amount": null
+ "type": "self",
+ "amount": 1
},
"name": "Focus",
"img": "icons/skills/targeting/crosshair-pointed-orange.webp",
- "range": ""
+ "range": "self"
}
},
"attribution": {
@@ -52,20 +52,20 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1753922784443,
- "modifiedTime": 1755428127375,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756037604544,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_id": "xxZOXC4tiZQ6kg1e",
"sort": 3400000,
"effects": [
{
"name": "Deadly Focus",
- "img": "systems/daggerheart/assets/icons/domains/domain-card/blade.png",
+ "img": "icons/skills/targeting/crosshair-pointed-orange.webp",
"origin": "Compendium.daggerheart.domains.Item.xxZOXC4tiZQ6kg1e",
- "transfer": true,
- "_id": "6sR46Hd554DiLHy4",
+ "transfer": false,
+ "_id": "TFWiAUDCfGaax5MU",
"type": "base",
"system": {
"rangeDependence": {
@@ -83,7 +83,7 @@
"priority": null
}
],
- "disabled": true,
+ "disabled": false,
"duration": {
"startTime": null,
"combat": null,
@@ -93,7 +93,7 @@
"startRound": null,
"startTurn": null
},
- "description": "",
+ "description": "Until you attack another creature, you defeat the target, or the battle ends, gain a +1 bonus to your Proficiency.
",
"tint": "#ffffff",
"statuses": [],
"sort": 0,
@@ -102,14 +102,14 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
- "coreVersion": "13.346",
+ "coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "0.0.1",
- "createdTime": 1754244951545,
- "modifiedTime": 1754304242570,
- "lastModifiedBy": "MQSznptE5yLT7kj8"
+ "systemVersion": "1.1.0",
+ "createdTime": 1756037548255,
+ "modifiedTime": 1756037588317,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
- "_key": "!items.effects!xxZOXC4tiZQ6kg1e.6sR46Hd554DiLHy4"
+ "_key": "!items.effects!xxZOXC4tiZQ6kg1e.TFWiAUDCfGaax5MU"
}
],
"ownership": {
diff --git a/src/packs/domains/domainCard_Force_of_Nature_LzVpMkD5I4QeaIHf.json b/src/packs/domains/domainCard_Force_of_Nature_LzVpMkD5I4QeaIHf.json
index ff9b1ebf..9aefa1bf 100644
--- a/src/packs/domains/domainCard_Force_of_Nature_LzVpMkD5I4QeaIHf.json
+++ b/src/packs/domains/domainCard_Force_of_Nature_LzVpMkD5I4QeaIHf.json
@@ -38,12 +38,12 @@
}
],
"target": {
- "type": "any",
+ "type": "self",
"amount": null
},
"name": "Transform",
"img": "icons/magic/nature/elemental-plant-humanoid.webp",
- "range": ""
+ "range": "self"
},
"1rLoYS90AZizJujS": {
"type": "effect",
@@ -89,10 +89,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1753922784456,
- "modifiedTime": 1755429633230,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756038760003,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_id": "LzVpMkD5I4QeaIHf",
"sort": 3400000,
diff --git a/src/packs/domains/domainCard_Frenzy_MMl7abdGRLl7TJLO.json b/src/packs/domains/domainCard_Frenzy_MMl7abdGRLl7TJLO.json
index 87dddb15..c6323551 100644
--- a/src/packs/domains/domainCard_Frenzy_MMl7abdGRLl7TJLO.json
+++ b/src/packs/domains/domainCard_Frenzy_MMl7abdGRLl7TJLO.json
@@ -31,12 +31,12 @@
}
],
"target": {
- "type": "any",
+ "type": "self",
"amount": null
},
"name": "Frenzy",
"img": "icons/magic/fire/projectile-wave-yellow.webp",
- "range": ""
+ "range": "self"
}
},
"attribution": {
@@ -52,10 +52,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1753922784459,
- "modifiedTime": 1755428186983,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756037770198,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_id": "MMl7abdGRLl7TJLO",
"sort": 3400000,
diff --git a/src/packs/domains/domainCard_Full_Surge_SgvjJfMyubZowPxS.json b/src/packs/domains/domainCard_Full_Surge_SgvjJfMyubZowPxS.json
index 15ffb49e..b8ac3547 100644
--- a/src/packs/domains/domainCard_Full_Surge_SgvjJfMyubZowPxS.json
+++ b/src/packs/domains/domainCard_Full_Surge_SgvjJfMyubZowPxS.json
@@ -43,7 +43,7 @@
},
"name": "Mark Stress",
"img": "icons/magic/control/buff-flight-wings-runes-purple.webp",
- "range": ""
+ "range": "self"
}
},
"attribution": {
@@ -59,10 +59,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1753922784460,
- "modifiedTime": 1755429931329,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756039042961,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_id": "SgvjJfMyubZowPxS",
"sort": 3400000,
@@ -71,7 +71,7 @@
"name": "Full Surge",
"img": "icons/magic/symbols/ring-circle-smoke-blue.webp",
"origin": "Compendium.daggerheart.domains.Item.SgvjJfMyubZowPxS",
- "transfer": true,
+ "transfer": false,
"_id": "H5q5iYImr69TfZcp",
"type": "base",
"system": {
@@ -120,7 +120,7 @@
"priority": null
}
],
- "disabled": true,
+ "disabled": false,
"duration": {
"startTime": null,
"combat": null,
@@ -139,12 +139,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
- "coreVersion": "13.346",
+ "coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1754242182511,
- "modifiedTime": 1754242229676,
- "lastModifiedBy": "MQSznptE5yLT7kj8"
+ "modifiedTime": 1756039048089,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_key": "!items.effects!SgvjJfMyubZowPxS.H5q5iYImr69TfZcp"
}
diff --git a/src/packs/domains/domainCard_Hold_the_Line_kdFoLo3KXwn4LqTG.json b/src/packs/domains/domainCard_Hold_the_Line_kdFoLo3KXwn4LqTG.json
index b572243f..6ea5b5c2 100644
--- a/src/packs/domains/domainCard_Hold_the_Line_kdFoLo3KXwn4LqTG.json
+++ b/src/packs/domains/domainCard_Hold_the_Line_kdFoLo3KXwn4LqTG.json
@@ -43,7 +43,7 @@
},
"name": "Spend Hope",
"img": "icons/magic/holy/barrier-shield-winged-blue.webp",
- "range": ""
+ "range": "self"
},
"SKG6Gu0uJZxtYTnz": {
"type": "effect",
@@ -87,10 +87,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1753922784468,
- "modifiedTime": 1755429945423,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756039072992,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_id": "kdFoLo3KXwn4LqTG",
"sort": 3400000,
@@ -99,7 +99,7 @@
"name": "Hold the Line",
"img": "icons/magic/defensive/shield-barrier-blue.webp",
"origin": "Compendium.daggerheart.domains.Item.kdFoLo3KXwn4LqTG",
- "transfer": true,
+ "transfer": false,
"_id": "y82y3nUStyL8DIJL",
"type": "base",
"system": {
@@ -111,7 +111,7 @@
}
},
"changes": [],
- "disabled": true,
+ "disabled": false,
"duration": {
"startTime": null,
"combat": null,
@@ -130,18 +130,18 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
- "coreVersion": "13.346",
+ "coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1754242295342,
- "modifiedTime": 1754242434181,
- "lastModifiedBy": "MQSznptE5yLT7kj8"
+ "modifiedTime": 1756039077113,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_key": "!items.effects!kdFoLo3KXwn4LqTG.y82y3nUStyL8DIJL"
},
{
"name": "Hold the Line",
- "img": "systems/daggerheart/assets/icons/domains/domain-card/valor.png",
+ "img": "icons/skills/melee/shield-block-bash-blue.webp",
"origin": "Compendium.daggerheart.domains.Item.kdFoLo3KXwn4LqTG",
"transfer": false,
"_id": "WTYg0b8nE1XbnMiA",
@@ -176,12 +176,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
- "coreVersion": "13.346",
+ "coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1754242400483,
- "modifiedTime": 1754242409060,
- "lastModifiedBy": "MQSznptE5yLT7kj8"
+ "modifiedTime": 1756039151398,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_key": "!items.effects!kdFoLo3KXwn4LqTG.WTYg0b8nE1XbnMiA"
}
diff --git a/src/packs/domains/domainCard_Hypnotic_Shimmer_2ZeuCGVatQdPOVC6.json b/src/packs/domains/domainCard_Hypnotic_Shimmer_2ZeuCGVatQdPOVC6.json
index 7579ac02..d7ad18c6 100644
--- a/src/packs/domains/domainCard_Hypnotic_Shimmer_2ZeuCGVatQdPOVC6.json
+++ b/src/packs/domains/domainCard_Hypnotic_Shimmer_2ZeuCGVatQdPOVC6.json
@@ -14,7 +14,7 @@
"type": "attack",
"_id": "kLMAuyZktmohOSXa",
"systemPath": "actions",
- "description": "Make a Spellcast Roll against all adversaries in front of you within Close range. Once per rest on a success, create an illusion of flashing colors and lights that temporarily Stuns targets you succeed against and forces them to mark a Stress. While Stunned , they can’t use reactions and can’t take any other actions until they clear this condition.
",
+ "description": "Make a Spellcast Roll against all adversaries in front of you within Close range. Once per rest on a success, create an illusion of flashing colors and lights that temporarily Stuns targets you succeed against and forces them to mark a Stress. While Stunned , they can’t use reactions and can’t take any other actions until they clear this condition.
@Template[type:infront|range:c]
",
"chatDisplay": true,
"actionType": "action",
"cost": [],
@@ -102,10 +102,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1753922784471,
- "modifiedTime": 1755429167606,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756038232979,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_id": "2ZeuCGVatQdPOVC6",
"sort": 3400000,
diff --git a/src/packs/domains/domainCard_Lead_by_Example_YWCRplmtwpCjpq5i.json b/src/packs/domains/domainCard_Lead_by_Example_YWCRplmtwpCjpq5i.json
index fa6f3c10..2566febc 100644
--- a/src/packs/domains/domainCard_Lead_by_Example_YWCRplmtwpCjpq5i.json
+++ b/src/packs/domains/domainCard_Lead_by_Example_YWCRplmtwpCjpq5i.json
@@ -39,7 +39,7 @@
],
"target": {
"type": "any",
- "amount": null
+ "amount": 1
},
"name": "Mark Stress",
"img": "icons/magic/control/buff-flight-wings-runes-purple.webp",
@@ -59,10 +59,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1753922784475,
- "modifiedTime": 1755429951881,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756039181179,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_id": "YWCRplmtwpCjpq5i",
"sort": 3400000,
diff --git a/src/packs/domains/domainCard_Overwhelming_Aura_iEBLySZD9z8CLdz7.json b/src/packs/domains/domainCard_Overwhelming_Aura_iEBLySZD9z8CLdz7.json
index 72a79735..e3dee02a 100644
--- a/src/packs/domains/domainCard_Overwhelming_Aura_iEBLySZD9z8CLdz7.json
+++ b/src/packs/domains/domainCard_Overwhelming_Aura_iEBLySZD9z8CLdz7.json
@@ -38,7 +38,7 @@
"includeBase": false
},
"target": {
- "type": "any",
+ "type": "self",
"amount": null
},
"effects": [
@@ -69,7 +69,7 @@
},
"name": "Cast",
"img": "icons/magic/holy/angel-winged-humanoid-blue.webp",
- "range": ""
+ "range": "self"
}
},
"attribution": {
@@ -85,10 +85,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1753922784491,
- "modifiedTime": 1755429784343,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756038903709,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_id": "iEBLySZD9z8CLdz7",
"sort": 3400000,
diff --git a/src/packs/domains/domainCard_Rage_Up_GRL0cvs96vrTDckZ.json b/src/packs/domains/domainCard_Rage_Up_GRL0cvs96vrTDckZ.json
index d9d67737..140e570d 100644
--- a/src/packs/domains/domainCard_Rage_Up_GRL0cvs96vrTDckZ.json
+++ b/src/packs/domains/domainCard_Rage_Up_GRL0cvs96vrTDckZ.json
@@ -14,7 +14,7 @@
"type": "effect",
"_id": "LmjwPg03xLnoGTHm",
"systemPath": "actions",
- "description": "",
+ "description": "You can mark a Stress to gain a bonus to your damage roll equal to twice your Strength.
",
"chatDisplay": true,
"actionType": "action",
"cost": [
@@ -22,8 +22,8 @@
"scalable": false,
"key": "stress",
"value": 1,
- "step": 1,
"keyIsID": false,
+ "step": null,
"consumeOnSuccess": false
}
],
@@ -40,18 +40,18 @@
}
],
"target": {
- "type": "any",
+ "type": "self",
"amount": null
},
"name": "Mark 1 Stress",
"img": "icons/magic/control/silhouette-aura-energy.webp",
- "range": ""
+ "range": "self"
},
"fKY9NcYBwCFwMsgV": {
"type": "effect",
"_id": "fKY9NcYBwCFwMsgV",
"systemPath": "actions",
- "description": "",
+ "description": "You can mark a Stress to gain a bonus to your damage roll equal to twice your Strength.
",
"chatDisplay": true,
"actionType": "action",
"cost": [
@@ -77,12 +77,12 @@
}
],
"target": {
- "type": "any",
+ "type": "self",
"amount": null
},
"name": "Mark 2 Stress",
"img": "icons/magic/control/silhouette-aura-energy.webp",
- "range": ""
+ "range": "self"
}
},
"attribution": {
@@ -98,10 +98,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1753922784496,
- "modifiedTime": 1755428155116,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756037744719,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_id": "GRL0cvs96vrTDckZ",
"sort": 3400000,
diff --git a/src/packs/domains/domainCard_Rain_of_Blades_Ucenef6JpjQxwXni.json b/src/packs/domains/domainCard_Rain_of_Blades_Ucenef6JpjQxwXni.json
index 630960f4..03337fa2 100644
--- a/src/packs/domains/domainCard_Rain_of_Blades_Ucenef6JpjQxwXni.json
+++ b/src/packs/domains/domainCard_Rain_of_Blades_Ucenef6JpjQxwXni.json
@@ -14,7 +14,7 @@
"type": "attack",
"_id": "WTnjKQs2uI1TuF9r",
"systemPath": "actions",
- "description": "Spend a Hope to make a Spellcast Roll and conjure throwing blades that strike out at all targets within Very Close range. Targets you succeed against take d8+2 magic damage using your Proficiency .
",
+ "description": "Spend a Hope to make a Spellcast Roll and conjure throwing blades that strike out at all targets within Very Close range. Targets you succeed against take d8+2 magic damage using your Proficiency .
@Template[type:emanation|range:vc]
",
"chatDisplay": true,
"actionType": "action",
"cost": [
@@ -154,10 +154,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1753922784497,
- "modifiedTime": 1755429293162,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756038335767,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_id": "Ucenef6JpjQxwXni",
"sort": 3400000,
diff --git a/src/packs/domains/domainCard_Salvation_Beam_4uAFGp3LxiC07woC.json b/src/packs/domains/domainCard_Salvation_Beam_4uAFGp3LxiC07woC.json
index baaba0a6..c8efc258 100644
--- a/src/packs/domains/domainCard_Salvation_Beam_4uAFGp3LxiC07woC.json
+++ b/src/packs/domains/domainCard_Salvation_Beam_4uAFGp3LxiC07woC.json
@@ -14,7 +14,7 @@
"type": "healing",
"_id": "dmnB4ZMSk8lsB8Lg",
"systemPath": "actions",
- "description": "Make a Spellcast Roll (16) . On a success, mark any number of Stress to target a line of allies within Far range. You can clear Hit Points on the targets equal to the number of Stress marked, divided among them however you’d like.
",
+ "description": "Make a Spellcast Roll (16) . On a success, mark any number of Stress to target a line of allies within Far range. You can clear Hit Points on the targets equal to the number of Stress marked, divided among them however you’d like.
@Template[type:ray|range:f]
",
"chatDisplay": true,
"actionType": "action",
"cost": [
@@ -101,10 +101,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1753922784508,
- "modifiedTime": 1755429792958,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756038941872,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_id": "4uAFGp3LxiC07woC",
"sort": 3400000,
diff --git a/src/packs/domains/domainCard_Smite_U1uWJE94HZVudujz.json b/src/packs/domains/domainCard_Smite_U1uWJE94HZVudujz.json
index 245db4a2..7b966db2 100644
--- a/src/packs/domains/domainCard_Smite_U1uWJE94HZVudujz.json
+++ b/src/packs/domains/domainCard_Smite_U1uWJE94HZVudujz.json
@@ -40,12 +40,12 @@
}
],
"target": {
- "type": "any",
+ "type": "self",
"amount": null
},
"name": "Charge Smite",
"img": "icons/skills/melee/sword-winged-holy-orange.webp",
- "range": ""
+ "range": "self"
}
},
"attribution": {
@@ -61,10 +61,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1753922784519,
- "modifiedTime": 1755429734196,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756038851464,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_id": "U1uWJE94HZVudujz",
"sort": 3400000,
diff --git a/src/packs/domains/domainCard_Specter_of_the_Dark_iQhgqmLwhcSTYnvr.json b/src/packs/domains/domainCard_Specter_of_the_Dark_iQhgqmLwhcSTYnvr.json
index 02d43c56..a7af630e 100644
--- a/src/packs/domains/domainCard_Specter_of_the_Dark_iQhgqmLwhcSTYnvr.json
+++ b/src/packs/domains/domainCard_Specter_of_the_Dark_iQhgqmLwhcSTYnvr.json
@@ -38,12 +38,12 @@
}
],
"target": {
- "type": "any",
+ "type": "self",
"amount": null
},
"name": "Become Spectral",
"img": "icons/magic/unholy/silhouette-light-fire-blue.webp",
- "range": ""
+ "range": "self"
}
},
"attribution": {
@@ -59,10 +59,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1753922784520,
- "modifiedTime": 1755429431607,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756038467712,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_id": "iQhgqmLwhcSTYnvr",
"sort": 3400000,
diff --git a/src/packs/domains/domainCard_Thorn_Skin_oUipGK84E2KjoKqh.json b/src/packs/domains/domainCard_Thorn_Skin_oUipGK84E2KjoKqh.json
index 6c56de38..41103c18 100644
--- a/src/packs/domains/domainCard_Thorn_Skin_oUipGK84E2KjoKqh.json
+++ b/src/packs/domains/domainCard_Thorn_Skin_oUipGK84E2KjoKqh.json
@@ -33,12 +33,12 @@
},
"effects": [],
"target": {
- "type": "any",
+ "type": "self",
"amount": null
},
"name": "Spend Hope",
"img": "icons/magic/nature/thorns-hand-glow-green.webp",
- "range": ""
+ "range": "self"
},
"IVAyyVf8gqFWB7bP": {
"type": "effect",
@@ -92,10 +92,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1753922784531,
- "modifiedTime": 1755429528391,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756038664007,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_id": "oUipGK84E2KjoKqh",
"sort": 3400000,
diff --git a/src/packs/domains/domainCard_Uncanny_Disguise_TV56wSysbU5xAlOa.json b/src/packs/domains/domainCard_Uncanny_Disguise_TV56wSysbU5xAlOa.json
index 1d0456ce..be34704d 100644
--- a/src/packs/domains/domainCard_Uncanny_Disguise_TV56wSysbU5xAlOa.json
+++ b/src/packs/domains/domainCard_Uncanny_Disguise_TV56wSysbU5xAlOa.json
@@ -28,7 +28,7 @@
"includeBase": false
},
"target": {
- "type": "any",
+ "type": "self",
"amount": null
},
"effects": [
@@ -39,7 +39,7 @@
],
"name": "Don Facade",
"img": "icons/magic/control/debuff-energy-hold-pink.webp",
- "range": ""
+ "range": "self"
},
"OoNND7VcWoBQdtFK": {
"type": "effect",
@@ -91,10 +91,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1753922784537,
- "modifiedTime": 1755429298602,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756038348939,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_id": "TV56wSysbU5xAlOa",
"sort": 3400000,
diff --git a/src/packs/domains/domainCard_Unleash_Chaos_o62i0QdbUDIiAhSq.json b/src/packs/domains/domainCard_Unleash_Chaos_o62i0QdbUDIiAhSq.json
index 8137b243..757aca62 100644
--- a/src/packs/domains/domainCard_Unleash_Chaos_o62i0QdbUDIiAhSq.json
+++ b/src/packs/domains/domainCard_Unleash_Chaos_o62i0QdbUDIiAhSq.json
@@ -126,12 +126,12 @@
},
"effects": [],
"target": {
- "type": "any",
+ "type": "self",
"amount": null
},
"name": "Replenish Tokens",
"img": "icons/commodities/gems/gem-faceted-diamond-blue.webp",
- "range": ""
+ "range": "self"
}
},
"attribution": {
@@ -147,10 +147,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1753922784537,
- "modifiedTime": 1755427920648,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756037237479,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_id": "o62i0QdbUDIiAhSq",
"sort": 3400000,
diff --git a/src/packs/domains/domainCard_Vanishing_Dodge_GBMIElIpk4cvk1Bd.json b/src/packs/domains/domainCard_Vanishing_Dodge_GBMIElIpk4cvk1Bd.json
index 29ba6f90..d5284f5a 100644
--- a/src/packs/domains/domainCard_Vanishing_Dodge_GBMIElIpk4cvk1Bd.json
+++ b/src/packs/domains/domainCard_Vanishing_Dodge_GBMIElIpk4cvk1Bd.json
@@ -38,7 +38,7 @@
}
],
"target": {
- "type": "any",
+ "type": "self",
"amount": null
},
"name": "Vanish",
@@ -59,10 +59,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1753922784539,
- "modifiedTime": 1755429382850,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756038425727,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_id": "GBMIElIpk4cvk1Bd",
"sort": 3400000,
diff --git a/src/packs/domains/domainCard_Veil_of_Night_gV4L5ZZmfPrEbIDh.json b/src/packs/domains/domainCard_Veil_of_Night_gV4L5ZZmfPrEbIDh.json
index 3e0a4623..ad78023c 100644
--- a/src/packs/domains/domainCard_Veil_of_Night_gV4L5ZZmfPrEbIDh.json
+++ b/src/packs/domains/domainCard_Veil_of_Night_gV4L5ZZmfPrEbIDh.json
@@ -28,7 +28,7 @@
"includeBase": false
},
"target": {
- "type": "any",
+ "type": "self",
"amount": null
},
"effects": [
@@ -59,7 +59,7 @@
},
"name": "Cast",
"img": "icons/magic/unholy/barrier-shield-glowing-pink.webp",
- "range": ""
+ "range": "self"
}
},
"attribution": {
@@ -75,10 +75,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1753922784541,
- "modifiedTime": 1755429328234,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756038378309,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_id": "gV4L5ZZmfPrEbIDh",
"sort": 3400000,
diff --git a/src/packs/domains/folders_Splendor_TL1TutmbeCVJ06nR.json b/src/packs/domains/folders_Splendor_TL1TutmbeCVJ06nR.json
index c0870ecf..87cc7469 100644
--- a/src/packs/domains/folders_Splendor_TL1TutmbeCVJ06nR.json
+++ b/src/packs/domains/folders_Splendor_TL1TutmbeCVJ06nR.json
@@ -6,18 +6,18 @@
"sorting": "m",
"_id": "TL1TutmbeCVJ06nR",
"description": "",
- "sort": 800000,
+ "sort": 900000,
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
- "coreVersion": "13.346",
+ "coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1748717133557,
- "modifiedTime": 1752681441081,
- "lastModifiedBy": "YNJ4HgHtFrTI89mx"
+ "modifiedTime": 1756038952388,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_key": "!folders!TL1TutmbeCVJ06nR"
}
diff --git a/src/packs/domains/folders_Valor_yPVeShe47ETIqs9q.json b/src/packs/domains/folders_Valor_yPVeShe47ETIqs9q.json
index 0a65d51f..f6361e50 100644
--- a/src/packs/domains/folders_Valor_yPVeShe47ETIqs9q.json
+++ b/src/packs/domains/folders_Valor_yPVeShe47ETIqs9q.json
@@ -6,18 +6,18 @@
"sorting": "m",
"_id": "yPVeShe47ETIqs9q",
"description": "",
- "sort": 900000,
+ "sort": 800000,
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
- "coreVersion": "13.346",
+ "coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1748717138700,
- "modifiedTime": 1752681443619,
- "lastModifiedBy": "YNJ4HgHtFrTI89mx"
+ "modifiedTime": 1756038952388,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_key": "!folders!yPVeShe47ETIqs9q"
}
diff --git a/src/packs/items/consumables/consumable_Armor_Stitcher_VlbsCjvvLNfTzNXb.json b/src/packs/items/consumables/consumable_Armor_Stitcher_VlbsCjvvLNfTzNXb.json
index 62049319..4f68e0d7 100644
--- a/src/packs/items/consumables/consumable_Armor_Stitcher_VlbsCjvvLNfTzNXb.json
+++ b/src/packs/items/consumables/consumable_Armor_Stitcher_VlbsCjvvLNfTzNXb.json
@@ -30,12 +30,12 @@
},
"effects": [],
"target": {
- "type": "any",
+ "type": "self",
"amount": null
},
"name": "Stitch",
"img": "icons/skills/trades/textiles-stitching-leather-brown.webp",
- "range": ""
+ "range": "self"
}
},
"consumeOnUse": true,
@@ -59,10 +59,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1753588973384,
- "modifiedTime": 1755432710896,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756041701211,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_key": "!items!VlbsCjvvLNfTzNXb"
}
diff --git a/src/packs/items/consumables/consumable_Attune_Potion_JGD3M9hBHtVAA8XP.json b/src/packs/items/consumables/consumable_Attune_Potion_JGD3M9hBHtVAA8XP.json
index 9e316540..64db5be0 100644
--- a/src/packs/items/consumables/consumable_Attune_Potion_JGD3M9hBHtVAA8XP.json
+++ b/src/packs/items/consumables/consumable_Attune_Potion_JGD3M9hBHtVAA8XP.json
@@ -32,7 +32,7 @@
},
"name": "Drink",
"img": "icons/consumables/potions/bottle-conical-corked-purple.webp",
- "range": ""
+ "range": "self"
}
},
"consumeOnUse": true,
@@ -101,10 +101,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1753587033468,
- "modifiedTime": 1755432536588,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756041711386,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_key": "!items!JGD3M9hBHtVAA8XP"
}
diff --git a/src/packs/items/consumables/consumable_Blood_of_the_Yorgi_pDGzmczoTlKGmKgd.json b/src/packs/items/consumables/consumable_Blood_of_the_Yorgi_pDGzmczoTlKGmKgd.json
index a1b70dc6..7d7596eb 100644
--- a/src/packs/items/consumables/consumable_Blood_of_the_Yorgi_pDGzmczoTlKGmKgd.json
+++ b/src/packs/items/consumables/consumable_Blood_of_the_Yorgi_pDGzmczoTlKGmKgd.json
@@ -22,12 +22,12 @@
},
"effects": [],
"target": {
- "type": "any",
+ "type": "self",
"amount": null
},
"name": "Drink",
"img": "icons/consumables/potions/potion-tube-corked-bat-gold-red.webp",
- "range": ""
+ "range": "veryFar"
}
},
"consumeOnUse": true,
@@ -51,10 +51,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1753589954973,
- "modifiedTime": 1755432804800,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756041739719,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_key": "!items!pDGzmczoTlKGmKgd"
}
diff --git a/src/packs/items/consumables/consumable_Bolster_Potion_FOPQNqXbiVO0ilYL.json b/src/packs/items/consumables/consumable_Bolster_Potion_FOPQNqXbiVO0ilYL.json
index 7519c7ab..70d93553 100644
--- a/src/packs/items/consumables/consumable_Bolster_Potion_FOPQNqXbiVO0ilYL.json
+++ b/src/packs/items/consumables/consumable_Bolster_Potion_FOPQNqXbiVO0ilYL.json
@@ -32,7 +32,7 @@
},
"name": "Drink",
"img": "icons/consumables/potions/potion-vial-tube-yellow.webp",
- "range": ""
+ "range": "self"
}
},
"consumeOnUse": true,
@@ -101,10 +101,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1753586850134,
- "modifiedTime": 1755432522101,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756041748451,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_key": "!items!FOPQNqXbiVO0ilYL"
}
diff --git a/src/packs/items/consumables/consumable_Death_Tea_xDnJeF1grkmKck8Q.json b/src/packs/items/consumables/consumable_Death_Tea_xDnJeF1grkmKck8Q.json
index e0b8a28c..25f7a45d 100644
--- a/src/packs/items/consumables/consumable_Death_Tea_xDnJeF1grkmKck8Q.json
+++ b/src/packs/items/consumables/consumable_Death_Tea_xDnJeF1grkmKck8Q.json
@@ -27,12 +27,12 @@
}
],
"target": {
- "type": "any",
+ "type": "self",
"amount": null
},
"name": "Drink",
"img": "icons/consumables/drinks/wine-amphora-clay-gray.webp",
- "range": ""
+ "range": "self"
}
},
"consumeOnUse": true,
@@ -101,10 +101,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1753592717630,
- "modifiedTime": 1755433085832,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756041829971,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_key": "!items!xDnJeF1grkmKck8Q"
}
diff --git a/src/packs/items/consumables/consumable_Dragonbloom_Tea_wM18PWWW2Ami4fBG.json b/src/packs/items/consumables/consumable_Dragonbloom_Tea_wM18PWWW2Ami4fBG.json
index 9389ca85..ee696b5d 100644
--- a/src/packs/items/consumables/consumable_Dragonbloom_Tea_wM18PWWW2Ami4fBG.json
+++ b/src/packs/items/consumables/consumable_Dragonbloom_Tea_wM18PWWW2Ami4fBG.json
@@ -11,13 +11,13 @@
"type": "attack",
"_id": "rcHEz3ImUDwo6lPC",
"systemPath": "actions",
- "description": "",
+ "description": "You can drink this tea to unleash a fiery breath attack. Make an Instinct Roll against all adversaries in front of you within Close range. Targets you succeed against take d20 physical damage using your Proficiency.
@Template[type:emanation|range:c]
",
"chatDisplay": true,
"actionType": "action",
"cost": [],
"uses": {
"value": null,
- "max": null,
+ "max": "",
"recovery": null
},
"damage": {
@@ -100,10 +100,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1753591525829,
- "modifiedTime": 1755432974525,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756041882255,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_key": "!items!wM18PWWW2Ami4fBG"
}
diff --git a/src/packs/items/consumables/consumable_Featherbone_DpxEMpwfasEBpORU.json b/src/packs/items/consumables/consumable_Featherbone_DpxEMpwfasEBpORU.json
index 31a5b7fd..fbdb9c17 100644
--- a/src/packs/items/consumables/consumable_Featherbone_DpxEMpwfasEBpORU.json
+++ b/src/packs/items/consumables/consumable_Featherbone_DpxEMpwfasEBpORU.json
@@ -20,14 +20,19 @@
"max": "",
"recovery": null
},
- "effects": [],
+ "effects": [
+ {
+ "_id": "VfJIJBW96e45xQHY",
+ "onSave": false
+ }
+ ],
"target": {
- "type": "any",
+ "type": "self",
"amount": null
},
"name": "Use",
"img": "icons/commodities/bones/bones-stack-worn-brown.webp",
- "range": ""
+ "range": "self"
}
},
"consumeOnUse": true,
@@ -37,7 +42,52 @@
"artist": ""
}
},
- "effects": [],
+ "effects": [
+ {
+ "name": "Featherbone",
+ "img": "icons/commodities/bones/bones-stack-worn-brown.webp",
+ "origin": "Compendium.daggerheart.consumables.Item.DpxEMpwfasEBpORU",
+ "transfer": false,
+ "_id": "VfJIJBW96e45xQHY",
+ "type": "base",
+ "system": {
+ "rangeDependence": {
+ "enabled": false,
+ "type": "withinRange",
+ "target": "hostile",
+ "range": "melee"
+ }
+ },
+ "changes": [],
+ "disabled": false,
+ "duration": {
+ "startTime": null,
+ "combat": null,
+ "seconds": null,
+ "rounds": null,
+ "turns": null,
+ "startRound": null,
+ "startTurn": null
+ },
+ "description": "You can use this bone to control your falling speed for a number of minutes equal to your level.
",
+ "tint": "#ffffff",
+ "statuses": [],
+ "sort": 0,
+ "flags": {},
+ "_stats": {
+ "compendiumSource": null,
+ "duplicateSource": null,
+ "exportSource": null,
+ "coreVersion": "13.347",
+ "systemId": "daggerheart",
+ "systemVersion": "1.1.0",
+ "createdTime": 1756041997551,
+ "modifiedTime": 1756042000517,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
+ },
+ "_key": "!items.effects!DpxEMpwfasEBpORU.VfJIJBW96e45xQHY"
+ }
+ ],
"folder": null,
"sort": 0,
"ownership": {
@@ -51,10 +101,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1753590624634,
- "modifiedTime": 1755432889504,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756042004101,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_key": "!items!DpxEMpwfasEBpORU"
}
diff --git a/src/packs/items/consumables/consumable_Gill_Salve_Nvbb9mze6o5D0AEg.json b/src/packs/items/consumables/consumable_Gill_Salve_Nvbb9mze6o5D0AEg.json
index 5e770de0..dd7ce092 100644
--- a/src/packs/items/consumables/consumable_Gill_Salve_Nvbb9mze6o5D0AEg.json
+++ b/src/packs/items/consumables/consumable_Gill_Salve_Nvbb9mze6o5D0AEg.json
@@ -20,14 +20,19 @@
"max": "",
"recovery": null
},
- "effects": [],
+ "effects": [
+ {
+ "_id": "5rL9CY5GO9SJcEZq",
+ "onSave": false
+ }
+ ],
"target": {
- "type": "any",
+ "type": "self",
"amount": null
},
"name": "Apply",
"img": "icons/commodities/materials/bowl-powder-blue.webp",
- "range": ""
+ "range": "self"
}
},
"consumeOnUse": true,
@@ -37,7 +42,52 @@
"artist": ""
}
},
- "effects": [],
+ "effects": [
+ {
+ "name": "Gill Salve",
+ "img": "icons/commodities/materials/bowl-powder-blue.webp",
+ "origin": "Compendium.daggerheart.consumables.Item.Nvbb9mze6o5D0AEg",
+ "transfer": false,
+ "_id": "5rL9CY5GO9SJcEZq",
+ "type": "base",
+ "system": {
+ "rangeDependence": {
+ "enabled": false,
+ "type": "withinRange",
+ "target": "hostile",
+ "range": "melee"
+ }
+ },
+ "changes": [],
+ "disabled": false,
+ "duration": {
+ "startTime": null,
+ "combat": null,
+ "seconds": null,
+ "rounds": null,
+ "turns": null,
+ "startRound": null,
+ "startTurn": null
+ },
+ "description": "You can apply this salve to your neck to breathe underwater for a number of minutes equal to your level.
",
+ "tint": "#ffffff",
+ "statuses": [],
+ "sort": 0,
+ "flags": {},
+ "_stats": {
+ "compendiumSource": null,
+ "duplicateSource": null,
+ "exportSource": null,
+ "coreVersion": "13.347",
+ "systemId": "daggerheart",
+ "systemVersion": "1.1.0",
+ "createdTime": 1756041968799,
+ "modifiedTime": 1756041978849,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
+ },
+ "_key": "!items.effects!Nvbb9mze6o5D0AEg.5rL9CY5GO9SJcEZq"
+ }
+ ],
"folder": null,
"sort": 0,
"ownership": {
@@ -51,10 +101,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1753589241094,
- "modifiedTime": 1755432719146,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756041983578,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_key": "!items!Nvbb9mze6o5D0AEg"
}
diff --git a/src/packs/items/loot/loot_Glamour_Stone_Pj17cvdJ1XG1jv6I.json b/src/packs/items/loot/loot_Glamour_Stone_Pj17cvdJ1XG1jv6I.json
index 6b233757..fa76826b 100644
--- a/src/packs/items/loot/loot_Glamour_Stone_Pj17cvdJ1XG1jv6I.json
+++ b/src/packs/items/loot/loot_Glamour_Stone_Pj17cvdJ1XG1jv6I.json
@@ -11,7 +11,7 @@
"type": "effect",
"_id": "ATQgH12mufTOQLKs",
"systemPath": "actions",
- "description": "",
+ "description": "Activate this pebble-sized stone to memorize the appearance of someone you can see. Spend a Hope to magically recreate this guise on yourself as an illusion.
",
"chatDisplay": true,
"actionType": "action",
"cost": [
@@ -25,17 +25,22 @@
],
"uses": {
"value": null,
- "max": null,
+ "max": "",
"recovery": null
},
- "effects": [],
+ "effects": [
+ {
+ "_id": "K5SB6tfuDkdVaQYe",
+ "onSave": false
+ }
+ ],
"target": {
- "type": "any",
+ "type": "self",
"amount": null
},
"name": "Create Illusion",
"img": "icons/commodities/treasure/token-engraved-purple-glowing.webp",
- "range": ""
+ "range": "self"
}
},
"attribution": {
@@ -44,7 +49,52 @@
"artist": ""
}
},
- "effects": [],
+ "effects": [
+ {
+ "name": "Glamour Stone",
+ "img": "icons/commodities/treasure/token-engraved-purple-glowing.webp",
+ "origin": "Compendium.daggerheart.loot.Item.Pj17cvdJ1XG1jv6I",
+ "transfer": false,
+ "_id": "K5SB6tfuDkdVaQYe",
+ "type": "base",
+ "system": {
+ "rangeDependence": {
+ "enabled": false,
+ "type": "withinRange",
+ "target": "hostile",
+ "range": "melee"
+ }
+ },
+ "changes": [],
+ "disabled": false,
+ "duration": {
+ "startTime": null,
+ "combat": null,
+ "seconds": null,
+ "rounds": null,
+ "turns": null,
+ "startRound": null,
+ "startTurn": null
+ },
+ "description": "Activate this pebble-sized stone to memorize the appearance of someone you can see. Spend a Hope to magically recreate this guise on yourself as an illusion.
",
+ "tint": "#ffffff",
+ "statuses": [],
+ "sort": 0,
+ "flags": {},
+ "_stats": {
+ "compendiumSource": null,
+ "duplicateSource": null,
+ "exportSource": null,
+ "coreVersion": "13.347",
+ "systemId": "daggerheart",
+ "systemVersion": "1.1.0",
+ "createdTime": 1756042876194,
+ "modifiedTime": 1756042884422,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
+ },
+ "_key": "!items.effects!Pj17cvdJ1XG1jv6I.K5SB6tfuDkdVaQYe"
+ }
+ ],
"folder": null,
"sort": 0,
"ownership": {
@@ -58,10 +108,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1753625773657,
- "modifiedTime": 1755431995057,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756042876216,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_key": "!items!Pj17cvdJ1XG1jv6I"
}
diff --git a/src/packs/items/loot/loot_Piercing_Arrows_I63LTFD6GXHgyGpR.json b/src/packs/items/loot/loot_Piercing_Arrows_I63LTFD6GXHgyGpR.json
index 9a056431..26845282 100644
--- a/src/packs/items/loot/loot_Piercing_Arrows_I63LTFD6GXHgyGpR.json
+++ b/src/packs/items/loot/loot_Piercing_Arrows_I63LTFD6GXHgyGpR.json
@@ -11,7 +11,7 @@
"type": "effect",
"_id": "DW5AqEM0F8XaUqpn",
"systemPath": "actions",
- "description": "",
+ "description": "Three times per rest when you succeed on an attack with one of these arrows, you can add your Proficiency to the damage roll.
",
"chatDisplay": true,
"actionType": "action",
"cost": [],
@@ -108,10 +108,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1753625947079,
- "modifiedTime": 1755432020809,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756042964027,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_key": "!items!I63LTFD6GXHgyGpR"
}
diff --git a/src/packs/items/loot/loot_Premium_Bedroll_QGYPNBIufpBguwjC.json b/src/packs/items/loot/loot_Premium_Bedroll_QGYPNBIufpBguwjC.json
index f000d904..1a2b7f62 100644
--- a/src/packs/items/loot/loot_Premium_Bedroll_QGYPNBIufpBguwjC.json
+++ b/src/packs/items/loot/loot_Premium_Bedroll_QGYPNBIufpBguwjC.json
@@ -17,11 +17,11 @@
"cost": [],
"uses": {
"value": null,
- "max": null,
+ "max": "",
"recovery": null
},
"target": {
- "type": "any",
+ "type": "self",
"amount": null
},
"effects": [],
@@ -89,10 +89,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1753624827945,
- "modifiedTime": 1755431895118,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756042976231,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_key": "!items!QGYPNBIufpBguwjC"
}
diff --git a/src/packs/items/loot/loot_Ring_of_Resistance_aUqRifqR5JXXa1dN.json b/src/packs/items/loot/loot_Ring_of_Resistance_aUqRifqR5JXXa1dN.json
index 628fe395..1fa60198 100644
--- a/src/packs/items/loot/loot_Ring_of_Resistance_aUqRifqR5JXXa1dN.json
+++ b/src/packs/items/loot/loot_Ring_of_Resistance_aUqRifqR5JXXa1dN.json
@@ -11,7 +11,7 @@
"type": "effect",
"_id": "4swwe8SZeh2KyPtl",
"systemPath": "actions",
- "description": "",
+ "description": "Once per long rest, you can activate this ring after a successful attack against you to halve the damage.
",
"chatDisplay": true,
"actionType": "action",
"cost": [],
@@ -20,14 +20,19 @@
"max": 1,
"recovery": "longRest"
},
- "effects": [],
+ "effects": [
+ {
+ "_id": "Sj7uGf560T7u4rlX",
+ "onSave": false
+ }
+ ],
"target": {
- "type": "any",
+ "type": "self",
"amount": null
},
"name": "Activate",
"img": "icons/equipment/finger/ring-shield-silver.webp",
- "range": ""
+ "range": "self"
}
},
"attribution": {
@@ -36,7 +41,65 @@
"artist": ""
}
},
- "effects": [],
+ "effects": [
+ {
+ "name": "Ring of Resistance",
+ "img": "icons/equipment/finger/ring-shield-silver.webp",
+ "origin": "Compendium.daggerheart.loot.Item.aUqRifqR5JXXa1dN",
+ "transfer": false,
+ "_id": "Sj7uGf560T7u4rlX",
+ "type": "base",
+ "system": {
+ "rangeDependence": {
+ "enabled": false,
+ "type": "withinRange",
+ "target": "hostile",
+ "range": "melee"
+ }
+ },
+ "changes": [
+ {
+ "key": "system.resistance.magical.resistance",
+ "mode": 5,
+ "value": "1",
+ "priority": null
+ },
+ {
+ "key": "system.resistance.physical.resistance",
+ "mode": 5,
+ "value": "1",
+ "priority": null
+ }
+ ],
+ "disabled": false,
+ "duration": {
+ "startTime": null,
+ "combat": null,
+ "seconds": null,
+ "rounds": null,
+ "turns": null,
+ "startRound": null,
+ "startTurn": null
+ },
+ "description": "Once per long rest, you can activate this ring after a successful attack against you to halve the damage.
",
+ "tint": "#ffffff",
+ "statuses": [],
+ "sort": 0,
+ "flags": {},
+ "_stats": {
+ "compendiumSource": null,
+ "duplicateSource": null,
+ "exportSource": null,
+ "coreVersion": "13.347",
+ "systemId": "daggerheart",
+ "systemVersion": "1.1.0",
+ "createdTime": 1756043003002,
+ "modifiedTime": 1756043032046,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
+ },
+ "_key": "!items.effects!aUqRifqR5JXXa1dN.Sj7uGf560T7u4rlX"
+ }
+ ],
"folder": null,
"sort": 0,
"ownership": {
@@ -50,10 +113,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1753638222884,
- "modifiedTime": 1755432185583,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756043010729,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_key": "!items!aUqRifqR5JXXa1dN"
}
diff --git a/src/packs/items/loot/loot_Ring_of_Silence_K1ysGnTpNyxPu5Au.json b/src/packs/items/loot/loot_Ring_of_Silence_K1ysGnTpNyxPu5Au.json
index b6de3da5..7d38f5a2 100644
--- a/src/packs/items/loot/loot_Ring_of_Silence_K1ysGnTpNyxPu5Au.json
+++ b/src/packs/items/loot/loot_Ring_of_Silence_K1ysGnTpNyxPu5Au.json
@@ -11,7 +11,7 @@
"type": "effect",
"_id": "Y4mvuJ4tncrKhyjY",
"systemPath": "actions",
- "description": "",
+ "description": "Spend a Hope to activate this ring. Your footsteps are silent until your next rest.
",
"chatDisplay": true,
"actionType": "action",
"cost": [
@@ -28,14 +28,19 @@
"max": 1,
"recovery": "shortRest"
},
- "effects": [],
+ "effects": [
+ {
+ "_id": "9JSLRu7amQ4iuIO7",
+ "onSave": false
+ }
+ ],
"target": {
- "type": "any",
+ "type": "self",
"amount": null
},
"name": "Activate",
"img": "icons/equipment/finger/ring-ball-purple.webp",
- "range": ""
+ "range": "self"
}
},
"attribution": {
@@ -47,9 +52,11 @@
"effects": [
{
"name": "Ring of Silence",
- "type": "base",
- "_id": "aCt3QjdeTREZAlEa",
"img": "icons/equipment/finger/ring-ball-purple.webp",
+ "origin": "Compendium.daggerheart.loot.Item.K1ysGnTpNyxPu5Au",
+ "transfer": false,
+ "_id": "9JSLRu7amQ4iuIO7",
+ "type": "base",
"system": {
"rangeDependence": {
"enabled": false,
@@ -59,7 +66,7 @@
}
},
"changes": [],
- "disabled": true,
+ "disabled": false,
"duration": {
"startTime": null,
"combat": null,
@@ -69,10 +76,8 @@
"startRound": null,
"startTurn": null
},
- "description": "Your footsteps are silent until your next rest.
",
- "origin": null,
+ "description": "Your footsteps are silent until your next rest.
",
"tint": "#ffffff",
- "transfer": true,
"statuses": [],
"sort": 0,
"flags": {},
@@ -80,14 +85,14 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
- "coreVersion": "13.346",
+ "coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "0.0.1",
- "createdTime": 1753990311274,
- "modifiedTime": 1753990345889,
- "lastModifiedBy": "MQSznptE5yLT7kj8"
+ "systemVersion": "1.1.0",
+ "createdTime": 1756043091753,
+ "modifiedTime": 1756043101742,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
- "_key": "!items.effects!K1ysGnTpNyxPu5Au.aCt3QjdeTREZAlEa"
+ "_key": "!items.effects!K1ysGnTpNyxPu5Au.9JSLRu7amQ4iuIO7"
}
],
"folder": null,
@@ -103,10 +108,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1753637775628,
- "modifiedTime": 1755432151648,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756043091776,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_key": "!items!K1ysGnTpNyxPu5Au"
}
diff --git a/src/packs/subclasses/feature_Act_of_Reprisal_k7vvMJtEcxMWUUrW.json b/src/packs/subclasses/feature_Act_of_Reprisal_k7vvMJtEcxMWUUrW.json
index 441bf12a..a73eb3d7 100644
--- a/src/packs/subclasses/feature_Act_of_Reprisal_k7vvMJtEcxMWUUrW.json
+++ b/src/packs/subclasses/feature_Act_of_Reprisal_k7vvMJtEcxMWUUrW.json
@@ -29,7 +29,7 @@
}
],
"target": {
- "type": "any",
+ "type": "self",
"amount": null
},
"name": "Mark Adversary",
@@ -98,10 +98,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1754243143650,
- "modifiedTime": 1755391854504,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756035614382,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_key": "!items!k7vvMJtEcxMWUUrW"
}
diff --git a/src/packs/subclasses/feature_Adrenaline_uByM34yQlw38yf1V.json b/src/packs/subclasses/feature_Adrenaline_uByM34yQlw38yf1V.json
index c1b11484..e15129b0 100644
--- a/src/packs/subclasses/feature_Adrenaline_uByM34yQlw38yf1V.json
+++ b/src/packs/subclasses/feature_Adrenaline_uByM34yQlw38yf1V.json
@@ -7,36 +7,7 @@
"system": {
"description": "While you're Vulnerable, add your level to your damage rolls.
",
"resource": null,
- "actions": {
- "UgffABhuobRDP7MQ": {
- "type": "effect",
- "_id": "UgffABhuobRDP7MQ",
- "systemPath": "actions",
- "description": "While you're Vulnerable, add your level to your damage rolls.
",
- "chatDisplay": true,
- "actionType": "action",
- "cost": [],
- "uses": {
- "value": null,
- "max": "",
- "recovery": null,
- "consumeOnSuccess": false
- },
- "effects": [
- {
- "_id": "HMx9uZ54mvMiH95x",
- "onSave": false
- }
- ],
- "target": {
- "type": "self",
- "amount": null
- },
- "name": "Apply",
- "img": "icons/magic/unholy/hand-marked-pink.webp",
- "range": ""
- }
- },
+ "actions": {},
"originItemType": null,
"originId": null,
"attribution": {
@@ -116,10 +87,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1754319984350,
- "modifiedTime": 1755392070263,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756035779655,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_key": "!items!uByM34yQlw38yf1V"
}
diff --git a/src/packs/subclasses/feature_Battle_Bonded_hWsKyed1vfILg0I8.json b/src/packs/subclasses/feature_Battle_Bonded_hWsKyed1vfILg0I8.json
index 259a25da..1efb24e2 100644
--- a/src/packs/subclasses/feature_Battle_Bonded_hWsKyed1vfILg0I8.json
+++ b/src/packs/subclasses/feature_Battle_Bonded_hWsKyed1vfILg0I8.json
@@ -16,7 +16,59 @@
"artist": ""
}
},
- "effects": [],
+ "effects": [
+ {
+ "name": "Battle Bonded",
+ "type": "base",
+ "system": {
+ "rangeDependence": {
+ "enabled": false,
+ "type": "withinRange",
+ "target": "hostile",
+ "range": "melee"
+ }
+ },
+ "_id": "IZhakv6EuG8DO4a3",
+ "img": "icons/creatures/mammals/humanoid-wolf-dog-blue.webp",
+ "changes": [
+ {
+ "key": "system.evasion",
+ "mode": 2,
+ "value": "2",
+ "priority": null
+ }
+ ],
+ "disabled": true,
+ "duration": {
+ "startTime": null,
+ "combat": null,
+ "seconds": null,
+ "rounds": null,
+ "turns": null,
+ "startRound": null,
+ "startTurn": null
+ },
+ "description": "When an adversary attacks you while they’re within your companion’s Melee range, you gain a +2 bonus to your Evasion against the attack.
",
+ "origin": null,
+ "tint": "#ffffff",
+ "transfer": true,
+ "statuses": [],
+ "sort": 0,
+ "flags": {},
+ "_stats": {
+ "compendiumSource": null,
+ "duplicateSource": null,
+ "exportSource": null,
+ "coreVersion": "13.347",
+ "systemId": "daggerheart",
+ "systemVersion": "1.1.0",
+ "createdTime": 1756035661033,
+ "modifiedTime": 1756035696385,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
+ },
+ "_key": "!items.effects!hWsKyed1vfILg0I8.IZhakv6EuG8DO4a3"
+ }
+ ],
"sort": 0,
"ownership": {
"default": 0,
diff --git a/src/packs/subclasses/feature_Elementalist_dPcqKN5NeDkjB1HW.json b/src/packs/subclasses/feature_Elementalist_dPcqKN5NeDkjB1HW.json
index dd2d8597..ac661a74 100644
--- a/src/packs/subclasses/feature_Elementalist_dPcqKN5NeDkjB1HW.json
+++ b/src/packs/subclasses/feature_Elementalist_dPcqKN5NeDkjB1HW.json
@@ -38,12 +38,12 @@
}
],
"target": {
- "type": "any",
+ "type": "self",
"amount": null
},
"name": "Bonus to Roll",
"img": "icons/sundries/gaming/dice-runed-tan.webp",
- "range": ""
+ "range": "self"
},
"S7HvFD3qIR3ifJRL": {
"type": "effect",
@@ -75,12 +75,12 @@
}
],
"target": {
- "type": "any",
+ "type": "self",
"amount": null
},
"name": "Bonus to Damage",
"img": "icons/sundries/gaming/dice-runed-tan.webp",
- "range": ""
+ "range": "self"
}
},
"originItemType": null,
@@ -115,7 +115,7 @@
"priority": null
}
],
- "disabled": true,
+ "disabled": false,
"duration": {
"startTime": null,
"combat": null,
@@ -134,10 +134,11 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
- "coreVersion": "13.346",
+ "coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
- "lastModifiedBy": null
+ "lastModifiedBy": "vUIbuan0U50nfKBE",
+ "modifiedTime": 1756035533156
},
"_key": "!items.effects!dPcqKN5NeDkjB1HW.EY87mY6ULfIt3XC8"
},
@@ -170,7 +171,7 @@
"priority": null
}
],
- "disabled": true,
+ "disabled": false,
"duration": {
"startTime": null,
"combat": null,
@@ -189,10 +190,11 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
- "coreVersion": "13.346",
+ "coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
- "lastModifiedBy": null
+ "lastModifiedBy": "vUIbuan0U50nfKBE",
+ "modifiedTime": 1756035536128
},
"_key": "!items.effects!dPcqKN5NeDkjB1HW.WwibpgaO6Kkks7aZ"
}
@@ -209,10 +211,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1754349507020,
- "modifiedTime": 1755392284524,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756035526712,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_key": "!items!dPcqKN5NeDkjB1HW"
}
diff --git a/src/packs/subclasses/feature_Elusive_Predator_Cjtc43V3IzAmfIFG.json b/src/packs/subclasses/feature_Elusive_Predator_Cjtc43V3IzAmfIFG.json
index fed36288..d0787ec3 100644
--- a/src/packs/subclasses/feature_Elusive_Predator_Cjtc43V3IzAmfIFG.json
+++ b/src/packs/subclasses/feature_Elusive_Predator_Cjtc43V3IzAmfIFG.json
@@ -16,7 +16,59 @@
"artist": ""
}
},
- "effects": [],
+ "effects": [
+ {
+ "name": "Elusive Predator",
+ "type": "base",
+ "system": {
+ "rangeDependence": {
+ "enabled": false,
+ "type": "withinRange",
+ "target": "hostile",
+ "range": "melee"
+ }
+ },
+ "_id": "X4llFOcAcdJLbear",
+ "img": "icons/creatures/mammals/beast-horned-scaled-glowing-orange.webp",
+ "changes": [
+ {
+ "key": "system.evasion",
+ "mode": 2,
+ "value": "2",
+ "priority": null
+ }
+ ],
+ "disabled": true,
+ "duration": {
+ "startTime": null,
+ "combat": null,
+ "seconds": null,
+ "rounds": null,
+ "turns": null,
+ "startRound": null,
+ "startTurn": null
+ },
+ "description": "When your Focus makes an attack against you, you gain a +2 bonus to your Evasion against the attack.
",
+ "origin": null,
+ "tint": "#ffffff",
+ "transfer": true,
+ "statuses": [],
+ "sort": 0,
+ "flags": {},
+ "_stats": {
+ "compendiumSource": null,
+ "duplicateSource": null,
+ "exportSource": null,
+ "coreVersion": "13.347",
+ "systemId": "daggerheart",
+ "systemVersion": "1.1.0",
+ "createdTime": 1756035709943,
+ "modifiedTime": 1756035734622,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
+ },
+ "_key": "!items.effects!Cjtc43V3IzAmfIFG.X4llFOcAcdJLbear"
+ }
+ ],
"sort": 0,
"ownership": {
"default": 0,
diff --git a/src/packs/subclasses/feature_Ruthless_Predator_Qny2J3R35bvC0Cey.json b/src/packs/subclasses/feature_Ruthless_Predator_Qny2J3R35bvC0Cey.json
index 8ac4675a..c58ad931 100644
--- a/src/packs/subclasses/feature_Ruthless_Predator_Qny2J3R35bvC0Cey.json
+++ b/src/packs/subclasses/feature_Ruthless_Predator_Qny2J3R35bvC0Cey.json
@@ -36,12 +36,12 @@
}
],
"target": {
- "type": "any",
+ "type": "self",
"amount": null
},
"name": "Mark Stress",
"img": "icons/creatures/mammals/wolf-shadow-black.webp",
- "range": ""
+ "range": "self"
}
},
"originItemType": null,
@@ -117,10 +117,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1754266926055,
- "modifiedTime": 1755391927605,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756035422344,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_key": "!items!Qny2J3R35bvC0Cey"
}
diff --git a/src/packs/subclasses/feature_Shadow_Stepper_hAwTXjhyphiE3aeW.json b/src/packs/subclasses/feature_Shadow_Stepper_hAwTXjhyphiE3aeW.json
index c46446d0..de0f54b7 100644
--- a/src/packs/subclasses/feature_Shadow_Stepper_hAwTXjhyphiE3aeW.json
+++ b/src/packs/subclasses/feature_Shadow_Stepper_hAwTXjhyphiE3aeW.json
@@ -36,12 +36,12 @@
}
],
"target": {
- "type": "any",
+ "type": "self",
"amount": null
},
"name": "Mark Stress",
"img": "icons/magic/unholy/projectile-smoke-trail-pink.webp",
- "range": ""
+ "range": "far"
}
},
"originItemType": null,
@@ -110,10 +110,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1754318976447,
- "modifiedTime": 1755392035163,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756035450401,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_key": "!items!hAwTXjhyphiE3aeW"
}
diff --git a/src/packs/subclasses/feature_Vanishing_Act_iyIg1VLwO8C6jvFZ.json b/src/packs/subclasses/feature_Vanishing_Act_iyIg1VLwO8C6jvFZ.json
index d56182e9..57ae1815 100644
--- a/src/packs/subclasses/feature_Vanishing_Act_iyIg1VLwO8C6jvFZ.json
+++ b/src/packs/subclasses/feature_Vanishing_Act_iyIg1VLwO8C6jvFZ.json
@@ -36,12 +36,12 @@
}
],
"target": {
- "type": "any",
+ "type": "self",
"amount": null
},
"name": "Mark Stress",
"img": "icons/magic/unholy/strike-hand-glow-pink.webp",
- "range": ""
+ "range": "self"
}
},
"originItemType": null,
@@ -57,7 +57,7 @@
"name": "Vanishing Act: Cloaked",
"img": "icons/magic/unholy/strike-hand-glow-pink.webp",
"origin": "Compendium.daggerheart.subclasses.Item.iyIg1VLwO8C6jvFZ",
- "transfer": true,
+ "transfer": false,
"_id": "czrwqq44sEr0uJ8O",
"type": "base",
"system": {
@@ -88,12 +88,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
- "coreVersion": "13.346",
+ "coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1754322584884,
- "modifiedTime": 1754352062188,
- "lastModifiedBy": "MQSznptE5yLT7kj8"
+ "modifiedTime": 1756035920880,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_key": "!items.effects!iyIg1VLwO8C6jvFZ.czrwqq44sEr0uJ8O"
}
@@ -110,10 +110,10 @@
"exportSource": null,
"coreVersion": "13.347",
"systemId": "daggerheart",
- "systemVersion": "1.0.5",
+ "systemVersion": "1.1.0",
"createdTime": 1754321406972,
- "modifiedTime": 1755392101482,
- "lastModifiedBy": "VZIeX2YDvX338Zvr"
+ "modifiedTime": 1756035929726,
+ "lastModifiedBy": "vUIbuan0U50nfKBE"
},
"_key": "!items!iyIg1VLwO8C6jvFZ"
}
From 1eb3ff11c090b8d78d0f461cac93ff89fb364a61 Mon Sep 17 00:00:00 2001
From: Dapoulp <74197441+Dapoulp@users.noreply.github.com>
Date: Wed, 27 Aug 2025 09:00:18 +0200
Subject: [PATCH 9/9] Task/415 action refactor part 2 (#1094)
---
lang/en.json | 37 +-
module/applications/dialogs/d20RollDialog.mjs | 54 ++-
.../settings/automationSettings.mjs | 3 +-
.../applications/sheets/actors/adversary.mjs | 4 +-
.../applications/sheets/actors/character.mjs | 26 +-
.../sheets/api/application-mixin.mjs | 14 +-
module/applications/ui/chatLog.mjs | 95 +----
module/applications/ui/fearTracker.mjs | 15 +-
module/config/settingsConfig.mjs | 19 +
module/data/action/baseAction.mjs | 383 +++++++-----------
module/data/action/beastformAction.mjs | 5 +-
module/data/action/damageAction.mjs | 60 ---
module/data/action/macroAction.mjs | 11 -
module/data/chat-message/_modules.mjs | 2 +-
.../{adversaryRoll.mjs => actorRoll.mjs} | 2 +-
module/data/fields/action/_module.mjs | 1 -
module/data/fields/action/beastformField.mjs | 99 +++++
module/data/fields/action/costField.mjs | 104 ++++-
module/data/fields/action/damageField.mjs | 142 +++++++
module/data/fields/action/effectsField.mjs | 90 ++++
module/data/fields/action/healingField.mjs | 12 -
module/data/fields/action/macroField.mjs | 22 +
module/data/fields/action/rangeField.mjs | 11 +-
module/data/fields/action/rollField.mjs | 87 ++--
module/data/fields/action/saveField.mjs | 155 +++++++
module/data/fields/action/targetField.mjs | 52 ++-
module/data/fields/action/usesField.mjs | 48 ++-
module/data/settings/Automation.mjs | 66 +++
module/dice/d20Roll.mjs | 7 +-
module/dice/damageRoll.mjs | 4 +-
module/dice/dhRoll.mjs | 11 +-
module/dice/dualityRoll.mjs | 8 +-
module/documents/actor.mjs | 2 +-
module/documents/chatMessage.mjs | 134 +++---
module/systemRegistration/socket.mjs | 10 +-
styles/less/global/elements.less | 1 +
styles/less/global/global.less | 2 +-
styles/less/ui/settings/settings.less | 2 +-
templates/actionTypes/beastform.hbs | 11 +-
templates/actionTypes/damage.hbs | 8 +-
templates/actionTypes/save.hbs | 13 +-
templates/dialogs/dice-roll/rollSelection.hbs | 18 +-
.../settings/automation-settings/roll.hbs | 22 +
.../action-settings/effect.hbs | 1 -
templates/ui/chat/parts/button-part.hbs | 6 +-
templates/ui/chat/parts/damage-part.hbs | 4 +-
templates/ui/chat/parts/target-part.hbs | 2 +-
47 files changed, 1244 insertions(+), 641 deletions(-)
rename module/data/chat-message/{adversaryRoll.mjs => actorRoll.mjs} (98%)
delete mode 100644 module/data/fields/action/healingField.mjs
create mode 100644 templates/settings/automation-settings/roll.hbs
diff --git a/lang/en.json b/lang/en.json
index 36b68ad7..d3d707d6 100755
--- a/lang/en.json
+++ b/lang/en.json
@@ -96,6 +96,7 @@
"attackName": "Attack Name",
"includeBase": { "label": "Include Item Damage" },
"multiplier": "Multiplier",
+ "saveHint": "Set a default Trait to enable Reaction Roll. It can be changed later in Reaction Roll Dialog.",
"resultBased": {
"label": "Formula based on Hope/Fear result."
},
@@ -310,7 +311,8 @@
"toLoadout": "Send to Loadout",
"toVault": "Send to Vault",
"unequip": "Unequip",
- "useItem": "Use Item"
+ "useItem": "Use Item",
+ "cancelBeastform": "Cancel Beastform"
},
"Countdown": {
"addCountdown": "Add Countdown",
@@ -1908,6 +1910,7 @@
"tier4": "tier 4",
"domains": "Domains",
"downtime": "Downtime",
+ "roll": "Roll",
"rules": "Rules",
"types": "Types"
},
@@ -1961,6 +1964,7 @@
"fear": "Fear",
"features": "Features",
"formula": "Formula",
+ "gm": "GM",
"healing": "Healing",
"healingRoll": "Healing Roll",
"hit": {
@@ -1997,6 +2001,10 @@
"none": "None",
"noTarget": "No current target",
"partner": "Partner",
+ "player": {
+ "single": "Player",
+ "plurial": "Players"
+ },
"proficiency": "Proficiency",
"quantity": "Quantity",
"range": "Range",
@@ -2022,6 +2030,7 @@
},
"title": "Title",
"total": "Total",
+ "traitModifier": "Trait Modifier",
"true": "True",
"type": "Type",
"unarmed": "Unarmed",
@@ -2204,10 +2213,35 @@
"playerCanEditSheet": {
"label": "Players Can Manually Edit Character Settings",
"hint": "Players are allowed to access the manual Character Settings and change their statistics beyond the rules."
+ },
+ "roll": {
+ "roll": {
+ "label": "Roll",
+ "hint": "Auto behavior for rolls like Attack, Spellcast, etc."
+ },
+ "damage": {
+ "label": "Damage/Healing Roll",
+ "hint": "Auto behavior for Damage & Healing rolls after the Attack/Spellcast."
+ },
+ "save": {
+ "label": "Reaction Roll",
+ "hint": "Auto behavior if a Reaction Roll is needed. Targets must be selected before the action is made"
+ },
+ "damageApply": {
+ "label": "Apply Damage/Healing",
+ "hint": "Automatically apply damages & healings. Targets must be selected before the action is made and Reaction Roll Automation must be different than Never. Bypass users permissions."
+ },
+ "effect": {
+ "label": "Apply Effects",
+ "hint": "Automatically apply effects. Targets must be selected before the action is made and Reaction Roll Automation must be different than Never. Bypass users permissions."
+ }
}
},
"defeated": {
"title": "Defeated Handling"
+ },
+ "roll": {
+ "title": "Actions"
}
},
"Homebrew": {
@@ -2407,6 +2441,7 @@
"beastformInapplicable": "A beastform can only be applied to a Character.",
"beastformAlreadyApplied": "The character already has a beastform applied!",
"noTargetsSelected": "No targets are selected.",
+ "noTargetsSelectedOrPerm": "No targets are selected or with the update permission.",
"attackTargetDoesNotExist": "The target token no longer exists",
"insufficentAdvancements": "You don't have enough advancements left.",
"noAssignedPlayerCharacter": "You have no assigned character.",
diff --git a/module/applications/dialogs/d20RollDialog.mjs b/module/applications/dialogs/d20RollDialog.mjs
index d445f24c..ed8fd6a1 100644
--- a/module/applications/dialogs/d20RollDialog.mjs
+++ b/module/applications/dialogs/d20RollDialog.mjs
@@ -1,3 +1,5 @@
+import { abilities } from "../../config/actorConfig.mjs";
+
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
export default class D20RollDialog extends HandlebarsApplicationMixin(ApplicationV2) {
@@ -7,7 +9,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
this.roll = roll;
this.config = config;
this.config.experiences = [];
- this.reactionOverride = config.roll?.type === 'reaction';
+ this.reactionOverride = config.actionType === 'reaction';
if (config.source?.action) {
this.item = config.data.parent.items.get(config.source.item) ?? config.data.parent;
@@ -20,7 +22,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
static DEFAULT_OPTIONS = {
tag: 'form',
- id: 'roll-selection',
+ // id: 'roll-selection',
classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'roll-selection'],
position: {
width: 'auto'
@@ -42,7 +44,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
};
get title() {
- return this.config.title;
+ return `${this.config.title}${this.actor ? `: ${this.actor.name}` : ''}`;
}
get actor() {
@@ -113,15 +115,21 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
context.isLite = this.config.roll?.lite;
context.extraFormula = this.config.extraFormula;
context.formula = this.roll.constructFormula(this.config);
+ if(this.actor.system.traits) context.abilities = this.getTraitModifiers();
- context.showReaction = !context.rollConfig.type && context.rollType === 'DualityRoll';
+ context.showReaction = !this.config.roll?.type && context.rollType === 'DualityRoll';
context.reactionOverride = this.reactionOverride;
}
return context;
}
+ getTraitModifiers() {
+ return Object.values(abilities).map(a => ({ id: a.id, label: `${game.i18n.localize(a.label)} (${this.actor.system.traits[a.id]?.value.signedString() ?? 0})` }))
+ }
+
static updateRollConfiguration(event, _, formData) {
const { ...rest } = foundry.utils.expandObject(formData.object);
+
this.config.selectedRollMode = rest.selectedRollMode;
if (this.config.costs) {
@@ -133,6 +141,12 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
this.roll[key] = value;
});
}
+ if(rest.hasOwnProperty("trait")) {
+ this.config.roll.trait = rest.trait;
+ this.config.title = game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
+ ability: game.i18n.localize(abilities[this.config.roll.trait]?.label)
+ });
+ }
this.config.extraFormula = rest.extraFormula;
this.render();
}
@@ -151,31 +165,29 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
this.config.experiences.indexOf(button.dataset.key) > -1
? this.config.experiences.filter(x => x !== button.dataset.key)
: [...this.config.experiences, button.dataset.key];
- if (this.config?.data?.parent?.type === 'character' || this.config?.data?.parent?.type === 'companion') {
- this.config.costs =
- this.config.costs.indexOf(this.config.costs.find(c => c.extKey === button.dataset.key)) > -1
- ? this.config.costs.filter(x => x.extKey !== button.dataset.key)
- : [
- ...this.config.costs,
- {
- extKey: button.dataset.key,
- key: 'hope',
- value: 1,
- name: this.config.data?.experiences?.[button.dataset.key]?.name
- }
- ];
- }
+ this.config.costs =
+ this.config.costs.indexOf(this.config.costs.find(c => c.extKey === button.dataset.key)) > -1
+ ? this.config.costs.filter(x => x.extKey !== button.dataset.key)
+ : [
+ ...this.config.costs,
+ {
+ extKey: button.dataset.key,
+ key: this.config?.data?.parent?.isNPC ? 'fear' : 'hope',
+ value: 1,
+ name: this.config.data?.experiences?.[button.dataset.key]?.name
+ }
+ ];
this.render();
}
static toggleReaction() {
if (this.config.roll) {
this.reactionOverride = !this.reactionOverride;
- this.config.roll.type = this.reactionOverride
+ this.config.actionType = this.reactionOverride
? CONFIG.DH.ITEM.actionTypes.reaction.id
- : this.config.roll.type === CONFIG.DH.ITEM.actionTypes.reaction.id
+ : this.config.actionType === CONFIG.DH.ITEM.actionTypes.reaction.id
? null
- : this.config.roll.type;
+ : this.config.actionType;
this.render();
}
}
diff --git a/module/applications/settings/automationSettings.mjs b/module/applications/settings/automationSettings.mjs
index 0157e016..4407897d 100644
--- a/module/applications/settings/automationSettings.mjs
+++ b/module/applications/settings/automationSettings.mjs
@@ -35,13 +35,14 @@ export default class DhAutomationSettings extends HandlebarsApplicationMixin(App
header: { template: 'systems/daggerheart/templates/settings/automation-settings/header.hbs' },
general: { template: 'systems/daggerheart/templates/settings/automation-settings/general.hbs' },
rules: { template: 'systems/daggerheart/templates/settings/automation-settings/rules.hbs' },
+ roll: { template: 'systems/daggerheart/templates/settings/automation-settings/roll.hbs' },
footer: { template: 'systems/daggerheart/templates/settings/automation-settings/footer.hbs' }
};
/** @inheritdoc */
static TABS = {
main: {
- tabs: [{ id: 'general' }, { id: 'rules' }],
+ tabs: [{ id: 'general' }, { id: 'rules' }, { id: 'roll' }],
initial: 'general',
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
}
diff --git a/module/applications/sheets/actors/adversary.mjs b/module/applications/sheets/actors/adversary.mjs
index f575a2f2..64f48d02 100644
--- a/module/applications/sheets/actors/adversary.mjs
+++ b/module/applications/sheets/actors/adversary.mjs
@@ -146,9 +146,9 @@ export default class AdversarySheet extends DHBaseActorSheet {
title: `Reaction Roll: ${this.actor.name}`,
headerTitle: 'Adversary Reaction Roll',
roll: {
- type: 'reaction'
+ type: 'trait'
},
- type: 'trait',
+ actionType: 'reaction',
hasRoll: true,
data: this.actor.getRollData()
};
diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs
index 6c2d7c95..308faee7 100644
--- a/module/applications/sheets/actors/character.mjs
+++ b/module/applications/sheets/actors/character.mjs
@@ -679,31 +679,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
})
});
- this.consumeResource(result?.costs);
- }
-
- // Remove when Action Refactor part #2 done
- async consumeResource(costs) {
- if (!costs?.length) return;
- const usefulResources = {
- ...foundry.utils.deepClone(this.actor.system.resources),
- fear: {
- value: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear),
- max: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxFear,
- reversed: false
- }
- };
- const resources = game.system.api.fields.ActionFields.CostField.getRealCosts(costs).map(c => {
- const resource = usefulResources[c.key];
- return {
- key: c.key,
- value: (c.total ?? c.value) * (resource.isReversed ? 1 : -1),
- target: resource.target,
- keyIsID: resource.keyIsID
- };
- });
-
- await this.actor.modifyResource(resources);
+ if(result) game.system.api.fields.ActionFields.CostField.execute.call(this, result);
}
//TODO: redo toggleEquipItem method
diff --git a/module/applications/sheets/api/application-mixin.mjs b/module/applications/sheets/api/application-mixin.mjs
index de9fe53d..7f338ac1 100644
--- a/module/applications/sheets/api/application-mixin.mjs
+++ b/module/applications/sheets/api/application-mixin.mjs
@@ -412,6 +412,16 @@ export default function DHApplicationMixin(Base) {
];
if (usable) {
+ options.unshift({
+ name: 'DAGGERHEART.APPLICATIONS.ContextMenu.cancelBeastform',
+ icon: 'fa-solid fa-ban',
+ condition: target => {
+ const doc = getDocFromElementSync(target);
+ return doc && doc.system?.actions?.some(a => a.type === "beastform");
+ },
+ callback: async target => game.system.api.fields.ActionFields.BeastformField.handleActiveTransformations.call(await getDocFromElement(target))
+ });
+
options.unshift({
name: 'DAGGERHEART.GENERAL.damage',
icon: 'fa-solid fa-explosion',
@@ -422,7 +432,9 @@ export default function DHApplicationMixin(Base) {
callback: async (target, event) => {
const doc = await getDocFromElement(target),
action = doc?.system?.attack ?? doc;
- return action && action.use(event, { byPassRoll: true });
+ const config = action.prepareConfig(event);
+ config.hasRoll = false;
+ return action && action.workflow.get("damage").execute(config, null, true);
}
});
diff --git a/module/applications/ui/chatLog.mjs b/module/applications/ui/chatLog.mjs
index a80974ed..21762818 100644
--- a/module/applications/ui/chatLog.mjs
+++ b/module/applications/ui/chatLog.mjs
@@ -1,5 +1,3 @@
-import { emitAsGM, GMUpdateEvent } from '../../systemRegistration/socket.mjs';
-
export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLog {
constructor(options) {
super(options);
@@ -55,21 +53,9 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
}
addChatListeners = async (app, html, data) => {
- html.querySelectorAll('.duality-action-damage').forEach(element =>
- element.addEventListener('click', event => this.onRollDamage(event, data.message))
- );
- html.querySelectorAll('.target-save').forEach(element =>
- element.addEventListener('click', event => this.onRollSave(event, data.message))
- );
- html.querySelectorAll('.roll-all-save-button').forEach(element =>
- element.addEventListener('click', event => this.onRollAllSave(event, data.message))
- );
html.querySelectorAll('.simple-roll-button').forEach(element =>
element.addEventListener('click', event => this.onRollSimple(event, data.message))
);
- html.querySelectorAll('.healing-button').forEach(element =>
- element.addEventListener('click', event => this.onHealing(event, data.message))
- );
html.querySelectorAll('.ability-use-button').forEach(element =>
element.addEventListener('click', event => this.abilityUseButton(event, data.message))
);
@@ -90,80 +76,6 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
super.close(options);
}
- async getActor(uuid) {
- return await foundry.utils.fromUuid(uuid);
- }
-
- getAction(actor, itemId, actionId) {
- const item = actor.items.get(itemId),
- action =
- actor.system.attack?._id === actionId
- ? actor.system.attack
- : item.system.attack?._id === actionId
- ? item.system.attack
- : item?.system?.actions?.get(actionId);
- return action;
- }
-
- async onRollDamage(event, message) {
- event.stopPropagation();
- const actor = await this.getActor(message.system.source.actor);
- if(!actor.isOwner) return true;
- if (message.system.source.item && message.system.source.action) {
- const action = this.getAction(actor, message.system.source.item, message.system.source.action);
- if (!action || !action?.rollDamage) return;
- await action.rollDamage(event, message);
- }
- }
-
- async onRollSave(event, message) {
- event.stopPropagation();
- const actor = await this.getActor(message.system.source.actor),
- tokenId = event.target.closest('[data-token]')?.dataset.token,
- token = game.canvas.tokens.get(tokenId);
- if (!token?.actor || !token.isOwner) return true;
- if (message.system.source.item && message.system.source.action) {
- const action = this.getAction(actor, message.system.source.item, message.system.source.action);
- if (!action || !action?.hasSave) return;
- action.rollSave(token.actor, event, message).then(result =>
- emitAsGM(
- GMUpdateEvent.UpdateSaveMessage,
- action.updateSaveMessage.bind(action, result, message, token.id),
- {
- action: action.uuid,
- message: message._id,
- token: token.id,
- result
- }
- )
- );
- }
- }
-
- async onRollAllSave(event, message) {
- event.stopPropagation();
- if (!game.user.isGM) return;
- const targets = event.target.parentElement.querySelectorAll('[data-token] .target-save');
- const actor = await this.getActor(message.system.source.actor),
- action = this.getAction(actor, message.system.source.item, message.system.source.action);
- targets.forEach(async el => {
- const tokenId = el.closest('[data-token]')?.dataset.token,
- token = game.canvas.tokens.get(tokenId);
- if (!token.actor) return;
- if (game.user === token.actor.owner) el.dispatchEvent(new PointerEvent('click', { shiftKey: true }));
- else {
- token.actor.owner
- .query('reactionRoll', {
- actionId: action.uuid,
- actorId: token.actor.uuid,
- event,
- message
- })
- .then(result => action.updateSaveMessage(result, message, token.id));
- }
- });
- }
-
async onRollSimple(event, message) {
const buttonType = event.target.dataset.type ?? 'damage',
total = message.rolls.reduce((a, c) => a + Roll.fromJSON(c).total, 0),
@@ -197,8 +109,11 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
item.system.attack?.id === event.currentTarget.id
? item.system.attack
: item.system.actions.get(event.currentTarget.id);
- if (event.currentTarget.dataset.directDamage) action.use(event, { byPassRoll: true });
- else action.use(event);
+ if (event.currentTarget.dataset.directDamage) {
+ const config = action.prepareConfig(event);
+ config.hasRoll = false;
+ action.workflow.get("damage").execute(config, null, true);
+ } else action.use(event);
}
async actionUseButton(event, message) {
diff --git a/module/applications/ui/fearTracker.mjs b/module/applications/ui/fearTracker.mjs
index ace2bbb2..a346aa66 100644
--- a/module/applications/ui/fearTracker.mjs
+++ b/module/applications/ui/fearTracker.mjs
@@ -78,7 +78,7 @@ export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV
/** @override */
async _preRender(context, options) {
- if (this.currentFear > this.maxFear)
+ if (this.currentFear > this.maxFear && game.user.isGM)
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear, this.maxFear);
}
@@ -107,18 +107,5 @@ export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV
async updateFear(value) {
return emitAsGM(GMUpdateEvent.UpdateFear, game.settings.set.bind(game.settings, CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear), value);
- /* if(!game.user.isGM)
- await game.socket.emit(`system.${CONFIG.DH.id}`, {
- action: socketEvent.GMUpdate,
- data: {
- action: GMUpdateEvent.UpdateFear,
- update: value
- }
- });
- else
- game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear, value); */
- /* if (!game.user.isGM) return;
- value = Math.max(0, Math.min(this.maxFear, value));
- await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear, value); */
}
}
diff --git a/module/config/settingsConfig.mjs b/module/config/settingsConfig.mjs
index 76234a97..0f148aeb 100644
--- a/module/config/settingsConfig.mjs
+++ b/module/config/settingsConfig.mjs
@@ -29,3 +29,22 @@ export const gameSettings = {
Countdowns: 'Countdowns',
LastMigrationVersion: 'LastMigrationVersion'
};
+
+export const actionAutomationChoices = {
+ never: {
+ id: "never",
+ label: "Never"
+ },
+ showDialog: {
+ id: "showDialog",
+ label: "Show Dialog only"
+ },
+ // npcOnly: {
+ // id: "npcOnly",
+ // label: "Always for non-characters"
+ // },
+ always: {
+ id: "always",
+ label: "Always"
+ }
+}
diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs
index ea619bcf..6e522ceb 100644
--- a/module/data/action/baseAction.mjs
+++ b/module/data/action/baseAction.mjs
@@ -1,14 +1,9 @@
import DhpActor from '../../documents/actor.mjs';
import D20RollDialog from '../../applications/dialogs/d20RollDialog.mjs';
import { ActionMixin } from '../fields/actionField.mjs';
-import { abilities } from '../../config/actorConfig.mjs';
const fields = foundry.data.fields;
-/*
- !!! I'm currently refactoring the whole Action thing, it's a WIP !!!
-*/
-
/*
ToDo
- Target Check / Target Picker
@@ -20,6 +15,7 @@ const fields = foundry.data.fields;
export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel) {
static extraSchemas = ['cost', 'uses', 'range'];
+ /** @inheritDoc */
static defineSchema() {
const schemaFields = {
_id: new fields.DocumentIdField({ initial: () => foundry.utils.randomID() }),
@@ -37,31 +33,76 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
};
this.extraSchemas.forEach(s => {
- let clsField;
- if ((clsField = this.getActionField(s))) schemaFields[s] = new clsField();
+ let clsField = this.getActionField(s);
+ if (clsField)
+ schemaFields[s] = new clsField();
});
return schemaFields;
}
+ /**
+ * Create a Map containing each Action step based on fields define in schema. Ordered by Fields order property.
+ *
+ * Each step can be called individually as long as needed config is provided.
+ * Ex: .workflow.get("damage").execute(config)
+ * @returns {Map}
+ */
+ defineWorkflow() {
+ const workflow = new Map();
+ this.constructor.extraSchemas.forEach(s => {
+ let clsField = this.constructor.getActionField(s);
+ if (clsField?.execute) {
+ workflow.set(s, { order: clsField.order, execute: clsField.execute.bind(this) } );
+ if( s === "damage" ) workflow.set("applyDamage", { order: 75, execute: clsField.applyDamage.bind(this) } );
+ }
+ });
+ return new Map([...workflow.entries()].sort(([aKey, aValue], [bKey, bValue]) => aValue.order - bValue.order));
+ }
+
+ /**
+ * Getter returning the workflow property or creating it the first time the property is called
+ */
+ get workflow() {
+ if ( this.hasOwnProperty("_workflow") ) return this._workflow;
+ const workflow = Object.freeze(this.defineWorkflow());
+ Object.defineProperty(this, "_workflow", {value: workflow, writable: false});
+ return workflow;
+ }
+
+ /**
+ * Get the Field class from ActionFields global config
+ * @param {string} name Field short name, equal to Action property
+ * @returns Action Field
+ */
static getActionField(name) {
const field = game.system.api.fields.ActionFields[`${name.capitalize()}Field`];
return fields.DataField.isPrototypeOf(field) && field;
}
+ /** @inheritDoc */
prepareData() {
this.name = this.name || game.i18n.localize(CONFIG.DH.ACTIONS.actionTypes[this.type].name);
this.img = this.img ?? this.parent?.parent?.img;
}
+ /**
+ * Get Action ID
+ */
get id() {
return this._id;
}
+ /**
+ * Return Item the action is attached too.
+ */
get item() {
return this.parent.parent;
}
+ /**
+ * Return the first Actor parent found.
+ */
get actor() {
return this.item instanceof DhpActor
? this.item
@@ -74,6 +115,11 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
return 'trait';
}
+ /**
+ * Prepare base data based on Action Type & Parent Type
+ * @param {object} parent
+ * @returns {object}
+ */
static getSourceConfig(parent) {
const updateSource = {};
if (parent?.parent?.type === 'weapon' && this === game.system.api.models.actions.actionsTypes.attack) {
@@ -96,6 +142,11 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
return updateSource;
}
+ /**
+ * Obtain a data object used to evaluate any dice rolls associated with this particular Action
+ * @param {object} [data ={}] Optional data object from previous configuration/rolls
+ * @returns {object}
+ */
getRollData(data = {}) {
if (!this.actor) return null;
const actorData = this.actor.getRollData(false);
@@ -111,19 +162,30 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
return actorData;
}
- async use(event, options = {}) {
+ /**
+ * Execute each part of the Action workflow in order, calling a specific event before and after each part.
+ * @param {object} config Config object usually created from prepareConfig method
+ */
+ async executeWorkflow(config) {
+ for(const [key, part] of this.workflow) {
+ if (Hooks.call(`${CONFIG.DH.id}.pre${key.capitalize()}Action`, this, config) === false) return;
+ if(await part.execute(config) === false) return;
+ if (Hooks.call(`${CONFIG.DH.id}.post${key.capitalize()}Action`, this, config) === false) return;
+ }
+ }
+
+ /**
+ * Main method to use the Action
+ * @param {Event} event Event from the button used to trigger the Action
+ * @returns {object}
+ */
+ async use(event) {
if (!this.actor) throw new Error("An Action can't be used outside of an Actor context.");
if (this.chatDisplay) await this.toChat();
- let { byPassRoll } = options,
- config = this.prepareConfig(event, byPassRoll);
- for (let i = 0; i < this.constructor.extraSchemas.length; i++) {
- let clsField = this.constructor.getActionField(this.constructor.extraSchemas[i]);
- if (clsField?.prepareConfig) {
- const keep = clsField.prepareConfig.call(this, config);
- if (config.isFastForward && !keep) return;
- }
- }
+
+ let config = this.prepareConfig(event);
+ if(!config) return;
if (Hooks.call(`${CONFIG.DH.id}.preUseAction`, this, config) === false) return;
@@ -132,36 +194,22 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
config = await D20RollDialog.configure(null, config);
if (!config) return;
}
-
- if (config.hasRoll) {
- const rollConfig = this.prepareRoll(config);
- config.roll = rollConfig;
- config = await this.actor.diceRoll(config);
- if (!config) return;
- }
-
- if (this.doFollowUp(config)) {
- if (this.rollDamage && this.damage.parts.length) await this.rollDamage(event, config);
- else if (this.trigger) await this.trigger(event, config);
- else if (this.hasSave || this.hasEffect) {
- const roll = new CONFIG.Dice.daggerheart.DHRoll('');
- roll._evaluated = true;
- await CONFIG.Dice.daggerheart.DHRoll.toMessage(roll, config);
- }
- }
-
- // Consume resources
- await this.consume(config);
+
+ // Execute the Action Worflow in order based of schema fields
+ await this.executeWorkflow(config);
if (Hooks.call(`${CONFIG.DH.id}.postUseAction`, this, config) === false) return;
return config;
}
- /* */
- prepareConfig(event, byPass = false) {
- const hasRoll = this.getUseHasRoll(byPass);
- return {
+ /**
+ * Create the basic config common to every action type
+ * @param {Event} event Event from the button used to trigger the Action
+ * @returns {object}
+ */
+ prepareBaseConfig(event) {
+ const config = {
event,
title: `${this.item instanceof CONFIG.Actor.documentClass ? '' : `${this.item.name}: `}${game.i18n.localize(this.name)}`,
source: {
@@ -169,238 +217,95 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
action: this._id,
actor: this.actor.uuid
},
- dialog: {
- configure: hasRoll
- },
- type: this.roll?.type ?? this.type,
- hasRoll: hasRoll,
- hasDamage: this.damage?.parts?.length && this.type !== 'healing',
- hasHealing: this.damage?.parts?.length && this.type === 'healing',
- hasEffect: !!this.effects?.length,
- isDirect: !!this.damage?.direct,
+ dialog: {},
+ actionType: this.actionType,
+ hasRoll: this.hasRoll,
+ hasDamage: this.hasDamage,
+ hasHealing: this.hasHealing,
+ hasEffect: this.hasEffect,
hasSave: this.hasSave,
+ isDirect: !!this.damage?.direct,
selectedRollMode: game.settings.get('core', 'rollMode'),
- isFastForward: event.shiftKey,
data: this.getRollData(),
- evaluate: hasRoll
+ evaluate: this.hasRoll
};
+ DHBaseAction.applyKeybindings(config);
+ return config;
}
+ /**
+ * Create the config for that action used for its workflow
+ * @param {Event} event Event from the button used to trigger the Action
+ * @returns {object}
+ */
+ prepareConfig(event) {
+ const config = this.prepareBaseConfig(event);
+ for(const clsField of Object.values(this.schema.fields)) {
+ if (clsField?.prepareConfig)
+ if(clsField.prepareConfig.call(this, config) === false) return false;
+ }
+ return config;
+ }
+
+ /**
+ * Method used to know if a configuration dialog must be shown or not when there is no roll.
+ * @param {*} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
+ * @returns {boolean}
+ */
requireConfigurationDialog(config) {
return !config.event.shiftKey && !config.hasRoll && (config.costs?.length || config.uses);
}
- prepareRoll() {
- const roll = {
- baseModifiers: this.roll.getModifier(),
- label: 'Attack',
- type: this.actionType,
- difficulty: this.roll?.difficulty,
- formula: this.roll.getFormula(),
- advantage: CONFIG.DH.ACTIONS.advantageState[this.roll.advState].value
- };
- if (this.roll?.type === 'diceSet' || !this.hasRoll) roll.lite = true;
-
- return roll;
- }
-
- doFollowUp(config) {
- return !config.hasRoll;
- }
-
+ /**
+ * Consume Action configured resources & uses.
+ * That method is only used when we want those resources to be consumed outside of the use method workflow.
+ * @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
+ * @param {boolean} successCost
+ */
async consume(config, successCost = false) {
- const actor = this.actor.system.partner ?? this.actor,
- usefulResources = {
- ...foundry.utils.deepClone(actor.system.resources),
- fear: {
- value: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear),
- max: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxFear,
- reversed: false
- }
- };
+ await this.workflow.get("cost")?.execute(config, successCost);
+ await this.workflow.get("uses")?.execute(config, successCost);
- for (var cost of config.costs) {
- if (cost.keyIsID) {
- usefulResources[cost.key] = {
- value: cost.value,
- target: this.parent.parent,
- keyIsID: true
- };
- }
- }
-
- const resources = game.system.api.fields.ActionFields.CostField.getRealCosts(config.costs)
- .filter(
- c =>
- (!successCost && (!c.consumeOnSuccess || config.roll?.success)) ||
- (successCost && c.consumeOnSuccess)
- )
- .reduce((a, c) => {
- const resource = usefulResources[c.key];
- if (resource) {
- a.push({
- key: c.key,
- value: (c.total ?? c.value) * (resource.isReversed ? 1 : -1),
- target: resource.target,
- keyIsID: resource.keyIsID
- });
- return a;
- }
- }, []);
-
- await actor.modifyResource(resources);
- if (
- config.uses?.enabled &&
- ((!successCost && (!config.uses?.consumeOnSuccess || config.roll?.success)) ||
- (successCost && config.uses?.consumeOnSuccess))
- )
- this.update({ 'uses.value': this.uses.value + 1 });
-
- if (config.roll?.success || successCost) {
+ if (config.roll && !config.roll.success && successCost) {
setTimeout(() => {
(config.message ?? config.parent).update({ 'system.successConsumed': true });
}, 50);
}
}
- /* */
- /* ROLL */
- getUseHasRoll(byPass = false) {
- return this.hasRoll && !byPass;
+ /**
+ * Set if a configuration dialog must be shown or not if a special keyboard key is pressed.
+ * @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
+ */
+ static applyKeybindings(config) {
+ config.dialog.configure ??= !(config.event.shiftKey || config.event.altKey || config.event.ctrlKey);
}
+ /**
+ * Getters to know which parts the action is composed of. A field can exist but configured to not be used.
+ * @returns {boolean} If that part is in the action.
+ */
+
get hasRoll() {
return !!this.roll?.type;
}
- get modifiers() {
- if (!this.actor) return [];
- const modifiers = [];
- /** Placeholder for specific bonuses **/
- return modifiers;
+ get hasDamage() {
+ return this.damage?.parts?.length && this.type !== 'healing'
}
- /* ROLL */
- /* SAVE */
+ get hasHealing() {
+ return this.damage?.parts?.length && this.type === 'healing'
+ }
+
get hasSave() {
return !!this.save?.trait;
}
- /* SAVE */
- /* EFFECTS */
get hasEffect() {
return this.effects?.length > 0;
}
- async applyEffects(event, data, targets) {
- targets ??= data.system.targets;
- const force = true; /* Where should this come from? */
- if (!this.effects?.length || !targets.length) return;
- let effects = this.effects;
- targets.forEach(async token => {
- if (!token.hit && !force) return;
- if (this.hasSave && token.saved.success === true) {
- effects = this.effects.filter(e => e.onSave === true);
- }
- if (!effects.length) return;
- effects.forEach(async e => {
- const actor = canvas.tokens.get(token.id)?.actor,
- effect = this.item.effects.get(e._id);
- if (!actor || !effect) return;
- await this.applyEffect(effect, actor);
- });
- });
- }
-
- async applyEffect(effect, actor) {
- const existingEffect = actor.effects.find(e => e.origin === effect.uuid);
- if (existingEffect) {
- return effect.update(
- foundry.utils.mergeObject({
- ...effect.constructor.getInitialDuration(),
- disabled: false
- })
- );
- }
-
- // Otherwise, create a new effect on the target
- const effectData = foundry.utils.mergeObject({
- ...effect.toObject(),
- disabled: false,
- transfer: false,
- origin: effect.uuid
- });
- await ActiveEffect.implementation.create(effectData, { parent: actor });
- }
- /* EFFECTS */
-
- /* SAVE */
- async rollSave(actor, event, message) {
- if (!actor) return;
- const title = actor.isNPC
- ? game.i18n.localize('DAGGERHEART.GENERAL.reactionRoll')
- : game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
- ability: game.i18n.localize(abilities[this.save.trait]?.label)
- });
- return actor.diceRoll({
- event,
- title,
- roll: {
- trait: this.save.trait,
- difficulty: this.save.difficulty ?? this.actor?.baseSaveDifficulty,
- type: 'reaction'
- },
- type: 'trait',
- hasRoll: true,
- data: actor.getRollData()
- });
- }
-
- updateSaveMessage(result, message, targetId) {
- if (!result) return;
- const updateMsg = this.updateChatMessage.bind(this, message, targetId, {
- result: result.roll.total,
- success: result.roll.success
- });
- if (game.modules.get('dice-so-nice')?.active)
- game.dice3d.waitFor3DAnimationByMessageID(result.message.id ?? result.message._id).then(() => updateMsg());
- else updateMsg();
- }
-
- static rollSaveQuery({ actionId, actorId, event, message }) {
- return new Promise(async (resolve, reject) => {
- const actor = await fromUuid(actorId),
- action = await fromUuid(actionId);
- if (!actor || !actor?.isOwner) reject();
- action.rollSave(actor, event, message).then(result => resolve(result));
- });
- }
- /* SAVE */
-
- async updateChatMessage(message, targetId, changes, chain = true) {
- setTimeout(async () => {
- const chatMessage = ui.chat.collection.get(message._id);
-
- await chatMessage.update({
- flags: {
- [game.system.id]: {
- reactionRolls: {
- [targetId]: changes
- }
- }
- }
- });
- }, 100);
- if (chain) {
- if (message.system.source.message)
- this.updateChatMessage(ui.chat.collection.get(message.system.source.message), targetId, changes, false);
- const relatedChatMessages = ui.chat.collection.filter(c => c.system.source?.message === message._id);
- relatedChatMessages.forEach(c => {
- this.updateChatMessage(c, targetId, changes, false);
- });
- }
- }
-
/**
* Generates a list of localized tags for this action.
* @returns {string[]} An array of localized tag strings.
diff --git a/module/data/action/beastformAction.mjs b/module/data/action/beastformAction.mjs
index 8c2dd31e..657cfde2 100644
--- a/module/data/action/beastformAction.mjs
+++ b/module/data/action/beastformAction.mjs
@@ -1,10 +1,9 @@
-import BeastformDialog from '../../applications/dialogs/beastformDialog.mjs';
import DHBaseAction from './baseAction.mjs';
export default class DhBeastformAction extends DHBaseAction {
static extraSchemas = [...super.extraSchemas, 'beastform'];
- async use(event, options) {
+ /* async use(event, options) {
const beastformConfig = this.prepareBeastformConfig();
const abort = await this.handleActiveTransformations();
@@ -82,5 +81,5 @@ export default class DhBeastformAction extends DHBaseAction {
beastformEffects.map(x => x.id)
);
return existingEffects;
- }
+ } */
}
diff --git a/module/data/action/damageAction.mjs b/module/data/action/damageAction.mjs
index 7deeb006..b4b3e17c 100644
--- a/module/data/action/damageAction.mjs
+++ b/module/data/action/damageAction.mjs
@@ -1,65 +1,5 @@
-import { setsEqual } from '../../helpers/utils.mjs';
import DHBaseAction from './baseAction.mjs';
export default class DHDamageAction extends DHBaseAction {
static extraSchemas = [...super.extraSchemas, 'damage', 'target', 'effects'];
-
- getFormulaValue(part, data) {
- let formulaValue = part.value;
-
- if (data.hasRoll && part.resultBased && data.roll.result.duality === -1) return part.valueAlt;
-
- const isAdversary = this.actor.type === 'adversary';
- if (isAdversary && this.actor.system.type === CONFIG.DH.ACTOR.adversaryTypes.horde.id) {
- const hasHordeDamage = this.actor.effects.find(x => x.type === 'horde');
- if (hasHordeDamage && !hasHordeDamage.disabled) return part.valueAlt;
- }
-
- return formulaValue;
- }
-
- formatFormulas(formulas, systemData) {
- const formattedFormulas = [];
- formulas.forEach(formula => {
- if (isNaN(formula.formula))
- formula.formula = Roll.replaceFormulaData(formula.formula, this.getRollData(systemData));
- const same = formattedFormulas.find(
- f => setsEqual(f.damageTypes, formula.damageTypes) && f.applyTo === formula.applyTo
- );
- if (same) same.formula += ` + ${formula.formula}`;
- else formattedFormulas.push(formula);
- });
- return formattedFormulas;
- }
-
- async rollDamage(event, data) {
- const systemData = data.system ?? data;
-
- let formulas = this.damage.parts.map(p => ({
- formula: this.getFormulaValue(p, systemData).getFormula(this.actor),
- damageTypes: p.applyTo === 'hitPoints' && !p.type.size ? new Set(['physical']) : p.type,
- applyTo: p.applyTo
- }));
-
- if (!formulas.length) return;
-
- formulas = this.formatFormulas(formulas, systemData);
-
- delete systemData.evaluate;
- const config = {
- ...systemData,
- roll: formulas,
- dialog: {},
- data: this.getRollData()
- };
- if (this.hasSave) config.onSave = this.save.damageMod;
- if (data.system) {
- config.source.message = data._id;
- config.directDamage = false;
- } else {
- config.directDamage = true;
- }
-
- return CONFIG.Dice.daggerheart.DamageRoll.build(config);
- }
}
diff --git a/module/data/action/macroAction.mjs b/module/data/action/macroAction.mjs
index 58b8eba5..b8338cd8 100644
--- a/module/data/action/macroAction.mjs
+++ b/module/data/action/macroAction.mjs
@@ -2,15 +2,4 @@ import DHBaseAction from './baseAction.mjs';
export default class DHMacroAction extends DHBaseAction {
static extraSchemas = [...super.extraSchemas, 'macro'];
-
- async trigger(event, ...args) {
- const fixUUID = !this.macro.includes('Macro.') ? `Macro.${this.macro}` : this.macro,
- macro = await fromUuid(fixUUID);
- try {
- if (!macro) throw new Error(`No macro found for the UUID: ${this.macro}.`);
- macro.execute();
- } catch (error) {
- ui.notifications.error(error);
- }
- }
}
diff --git a/module/data/chat-message/_modules.mjs b/module/data/chat-message/_modules.mjs
index a4e2c1fd..36c6ee9d 100644
--- a/module/data/chat-message/_modules.mjs
+++ b/module/data/chat-message/_modules.mjs
@@ -1,5 +1,5 @@
import DHAbilityUse from "./abilityUse.mjs";
-import DHActorRoll from "./adversaryRoll.mjs";
+import DHActorRoll from "./actorRoll.mjs";
export const config = {
abilityUse: DHAbilityUse,
diff --git a/module/data/chat-message/adversaryRoll.mjs b/module/data/chat-message/actorRoll.mjs
similarity index 98%
rename from module/data/chat-message/adversaryRoll.mjs
rename to module/data/chat-message/actorRoll.mjs
index d87bab6a..b6512fbd 100644
--- a/module/data/chat-message/adversaryRoll.mjs
+++ b/module/data/chat-message/actorRoll.mjs
@@ -95,7 +95,7 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
}
registerTargetHook() {
- if (!this.parent.isAuthor) return;
+ if (!this.parent.isAuthor || !this.hasTarget) return;
if (this.targetMode && this.parent.targetHook !== null) {
Hooks.off('targetToken', this.parent.targetHook);
return (this.parent.targetHook = null);
diff --git a/module/data/fields/action/_module.mjs b/module/data/fields/action/_module.mjs
index e6caa963..7a33e147 100644
--- a/module/data/fields/action/_module.mjs
+++ b/module/data/fields/action/_module.mjs
@@ -6,6 +6,5 @@ export { default as EffectsField } from './effectsField.mjs';
export { default as SaveField } from './saveField.mjs';
export { default as BeastformField } from './beastformField.mjs';
export { default as DamageField } from './damageField.mjs';
-export { default as HealingField } from './healingField.mjs';
export { default as RollField } from './rollField.mjs';
export { default as MacroField } from './macroField.mjs';
diff --git a/module/data/fields/action/beastformField.mjs b/module/data/fields/action/beastformField.mjs
index 832bd9f6..62c735d0 100644
--- a/module/data/fields/action/beastformField.mjs
+++ b/module/data/fields/action/beastformField.mjs
@@ -1,6 +1,13 @@
+import BeastformDialog from "../../../applications/dialogs/beastformDialog.mjs";
+
const fields = foundry.data.fields;
export default class BeastformField extends fields.SchemaField {
+ /**
+ * Action Workflow order
+ */
+ static order = 90;
+
constructor(options = {}, context = {}) {
const beastformFields = {
tierAccess: new fields.SchemaField({
@@ -27,4 +34,96 @@ export default class BeastformField extends fields.SchemaField {
};
super(beastformFields, options, context);
}
+
+ /**
+ * Beastform Transformation Action Workflow part.
+ * Must be called within Action context or similar.
+ * @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
+ */
+ static async execute(config) {
+ // Should not be useful anymore here
+ await BeastformField.handleActiveTransformations.call(this);
+
+ const { selected, evolved, hybrid } = await BeastformDialog.configure(config, this.item);
+ if (!selected) return false;
+
+ return await BeastformField.transform.call(this, selected, evolved, hybrid);
+ }
+
+ /**
+ * Update Action Workflow config object.
+ * Must be called within Action context.
+ * @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
+ */
+ prepareConfig(config) {
+ if(this.actor.effects.find(x => x.type === 'beastform')) {
+ ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.beastformAlreadyApplied'));
+ return false;
+ }
+
+ const settingsTiers = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers).tiers;
+ const actorLevel = this.actor.system.levelData.level.current;
+ const actorTier =
+ Object.values(settingsTiers).find(
+ tier => actorLevel >= tier.levels.start && actorLevel <= tier.levels.end
+ ) ?? 1;
+
+ config.tierLimit = this.beastform.tierAccess.exact ?? actorTier;
+ }
+
+ /**
+ * TODO by Harry
+ * @param {*} selectedForm
+ * @param {*} evolvedData
+ * @param {*} hybridData
+ * @returns
+ */
+ static async transform(selectedForm, evolvedData, hybridData) {
+ const formData = evolvedData?.form ? evolvedData.form.toObject() : selectedForm.toObject();
+ const beastformEffect = formData.effects.find(x => x.type === 'beastform');
+ if (!beastformEffect) {
+ ui.notifications.error('DAGGERHEART.UI.Notifications.beastformMissingEffect');
+ return false;
+ }
+
+ if (evolvedData?.form) {
+ const evolvedForm = selectedForm.effects.find(x => x.type === 'beastform');
+ if (!evolvedForm) {
+ ui.notifications.error('DAGGERHEART.UI.Notifications.beastformMissingEffect');
+ return false;
+ }
+
+ beastformEffect.changes = [...beastformEffect.changes, ...evolvedForm.changes];
+ formData.system.features = [...formData.system.features, ...selectedForm.system.features.map(x => x.uuid)];
+ }
+
+ if (selectedForm.system.beastformType === CONFIG.DH.ITEM.beastformTypes.hybrid.id) {
+ formData.system.advantageOn = Object.values(hybridData.advantages).reduce((advantages, formCategory) => {
+ Object.keys(formCategory).forEach(advantageKey => {
+ advantages[advantageKey] = formCategory[advantageKey];
+ });
+ return advantages;
+ }, {});
+ formData.system.features = [
+ ...formData.system.features,
+ ...Object.values(hybridData.features).flatMap(x => Object.keys(x))
+ ];
+ }
+
+ this.actor.createEmbeddedDocuments('Item', [formData]);
+ }
+
+ /**
+ * Remove existing beastform effect and return true if there was one
+ * @returns {boolean}
+ */
+ static async handleActiveTransformations() {
+ const beastformEffects = this.actor.effects.filter(x => x.type === 'beastform');
+ const existingEffects = beastformEffects.length > 0;
+ await this.actor.deleteEmbeddedDocuments(
+ 'ActiveEffect',
+ beastformEffects.map(x => x.id)
+ );
+ return existingEffects;
+ }
}
diff --git a/module/data/fields/action/costField.mjs b/module/data/fields/action/costField.mjs
index c224fff0..ca942909 100644
--- a/module/data/fields/action/costField.mjs
+++ b/module/data/fields/action/costField.mjs
@@ -1,6 +1,12 @@
const fields = foundry.data.fields;
export default class CostField extends fields.ArrayField {
+ /**
+ * Action Workflow order
+ */
+ static order = 150;
+
+ /** @inheritDoc */
constructor(options = {}, context = {}) {
const element = new fields.SchemaField({
key: new fields.StringField({
@@ -20,15 +26,80 @@ export default class CostField extends fields.ArrayField {
super(element, options, context);
}
- static prepareConfig(config) {
+ /**
+ * Cost Consumption Action Workflow part.
+ * Consume configured action resources.
+ * Must be called within Action context or similar.
+ * @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
+ * @param {boolean} [successCost=false] Consume only resources configured as "On Success only" if not already consumed.
+ */
+ static async execute(config, successCost = false) {
+ const actor= this.actor.system.partner ?? this.actor,
+ usefulResources = {
+ ...foundry.utils.deepClone(actor.system.resources),
+ fear: {
+ value: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear),
+ max: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxFear,
+ reversed: false
+ }
+ };
+
+ if(this.parent?.parent) {
+ for (var cost of config.costs) {
+ if (cost.keyIsID) {
+ usefulResources[cost.key] = {
+ value: cost.value,
+ target: this.parent.parent,
+ keyIsID: true
+ };
+ }
+ }
+ }
+
+ const resources = CostField.getRealCosts(config.costs)
+ .filter(
+ c =>
+ (!successCost && (!c.consumeOnSuccess || config.roll?.success)) ||
+ (successCost && c.consumeOnSuccess)
+ )
+ .reduce((a, c) => {
+ const resource = usefulResources[c.key];
+ if (resource) {
+ a.push({
+ key: c.key,
+ value: (c.total ?? c.value) * (resource.isReversed ? 1 : -1),
+ target: resource.target,
+ keyIsID: resource.keyIsID
+ });
+ return a;
+ }
+ }, []);
+
+ await actor.modifyResource(resources);
+ }
+
+ /**
+ * Update Action Workflow config object.
+ * Must be called within Action context or similar.
+ * @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
+ * @returns {boolean} Return false if fast-forwarded and no more uses.
+ */
+ prepareConfig(config) {
const costs = this.cost?.length ? foundry.utils.deepClone(this.cost) : [];
config.costs = CostField.calcCosts.call(this, costs);
const hasCost = CostField.hasCost.call(this, config.costs);
- if (config.isFastForward && !hasCost)
- return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.insufficientResources'));
- return hasCost;
+ if (config.dialog.configure === false && !hasCost) {
+ ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.insufficientResources'));
+ return hasCost;
+ }
}
+ /**
+ *
+ * Must be called within Action context.
+ * @param {*} costs
+ * @returns
+ */
static calcCosts(costs) {
const resources = CostField.getResources.call(this, costs);
return costs.map(c => {
@@ -40,13 +111,19 @@ export default class CostField extends fields.ArrayField {
c.key === 'fear'
? game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear)
: resources[c.key].isReversed
- ? resources[c.key].max
+ ? resources[c.key].max - resources[c.key].value
: resources[c.key].value;
if (c.scalable) c.maxStep = Math.floor((c.max - c.value) / c.step);
return c;
});
}
+ /**
+ * Check if the current Actor currently has all needed resources.
+ * Must be called within Action context.
+ * @param {*} costs
+ * @returns {boolean}
+ */
static hasCost(costs) {
const realCosts = CostField.getRealCosts.call(this, costs),
hasFearCost = realCosts.findIndex(c => c.key === 'fear');
@@ -73,6 +150,12 @@ export default class CostField extends fields.ArrayField {
);
}
+ /**
+ * Get all Actor resources + parent Item potential one.
+ * Must be called within Action context.
+ * @param {*} costs
+ * @returns
+ */
static getResources(costs) {
const actorResources = foundry.utils.deepClone(this.actor.system.resources);
if (this.actor.system.partner)
@@ -93,6 +176,11 @@ export default class CostField extends fields.ArrayField {
};
}
+ /**
+ *
+ * @param {*} costs
+ * @returns
+ */
static getRealCosts(costs) {
const realCosts = costs?.length ? costs.filter(c => c.enabled) : [];
let mergedCosts = [];
@@ -104,6 +192,12 @@ export default class CostField extends fields.ArrayField {
return mergedCosts;
}
+ /**
+ * Format scalable max cost, inject Action datas if it's a formula.
+ * Must be called within Action context.
+ * @param {number|string} max Configured maximum for that resource.
+ * @returns {number} The max cost value.
+ */
static formatMax(max) {
max ??= 0;
if (isNaN(max)) {
diff --git a/module/data/fields/action/damageField.mjs b/module/data/fields/action/damageField.mjs
index 4b4dee7d..db43cfb0 100644
--- a/module/data/fields/action/damageField.mjs
+++ b/module/data/fields/action/damageField.mjs
@@ -1,8 +1,15 @@
import FormulaField from '../formulaField.mjs';
+import { setsEqual } from '../../../helpers/utils.mjs';
const fields = foundry.data.fields;
export default class DamageField extends fields.SchemaField {
+ /**
+ * Action Workflow order
+ */
+ static order = 20;
+
+ /** @inheritDoc */
constructor(options, context = {}) {
const damageFields = {
parts: new fields.ArrayField(new fields.EmbeddedDataField(DHDamageData)),
@@ -14,6 +21,141 @@ export default class DamageField extends fields.SchemaField {
};
super(damageFields, options, context);
}
+
+ /**
+ * Roll Damage/Healing Action Workflow part.
+ * Must be called within Action context or similar.
+ * @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
+ * @param {string} [messageId=null] ChatMessage Id where the clicked button belong.
+ * @param {boolean} [force=false] If the method should be executed outside of Action workflow, for ChatMessage button for example.
+ */
+ static async execute(config, messageId = null, force = false) {
+ if(!this.hasDamage && !this.hasHealing) return;
+ if((this.hasRoll && DamageField.getAutomation() === CONFIG.DH.SETTINGS.actionAutomationChoices.never.id) && !force) return;
+
+ let formulas = this.damage.parts.map(p => ({
+ formula: DamageField.getFormulaValue.call(this, p, config).getFormula(this.actor),
+ damageTypes: p.applyTo === 'hitPoints' && !p.type.size ? new Set(['physical']) : p.type,
+ applyTo: p.applyTo
+ }));
+
+ if (!formulas.length) return false;
+
+ formulas = DamageField.formatFormulas.call(this, formulas, config);
+
+ const damageConfig = {
+ ...config,
+ roll: formulas,
+ dialog: {},
+ data: this.getRollData()
+ };
+ delete damageConfig.evaluate;
+
+ if(DamageField.getAutomation() === CONFIG.DH.SETTINGS.actionAutomationChoices.always.id) damageConfig.dialog.configure = false;
+ if (config.hasSave) config.onSave = damageConfig.onSave = this.save.damageMod;
+
+ damageConfig.source.message = config.message?._id ?? messageId;
+ damageConfig.directDamage = !!damageConfig.source?.message;
+
+ if(damageConfig.source?.message && game.modules.get('dice-so-nice')?.active)
+ await game.dice3d.waitFor3DAnimationByMessageID(damageConfig.source.message);
+
+ const damageResult = await CONFIG.Dice.daggerheart.DamageRoll.build(damageConfig);
+ if(!damageResult) return false;
+ config.damage = damageResult.damage;
+ config.message ??= damageConfig.message;
+ }
+
+ /**
+ * Apply Damage/Healing Action Worflow part.
+ * @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
+ * @param {*[]} targets Arrays of targets to bypass pre-selected ones.
+ * @param {boolean} force If the method should be executed outside of Action workflow, for ChatMessage button for example.
+ */
+ static async applyDamage(config, targets = null, force = false) {
+ targets ??= config.targets.filter(target => target.hit);
+ if(!config.damage || !targets?.length || (!DamageField.getApplyAutomation() && !force)) return;
+ for (let target of targets) {
+ const actor = fromUuidSync(target.actorId);
+ if(!actor) continue;
+ if (
+ !config.hasHealing &&
+ config.onSave &&
+ target.saved?.success === true
+ ) {
+ const mod = CONFIG.DH.ACTIONS.damageOnSave[config.onSave]?.mod ?? 1;
+ Object.entries(config.damage).forEach(([k, v]) => {
+ v.total = 0;
+ v.parts.forEach(part => {
+ part.total = Math.ceil(part.total * mod);
+ v.total += part.total;
+ });
+ });
+ }
+
+ if (config.hasHealing) actor.takeHealing(config.damage);
+ else actor.takeDamage(config.damage, config.isDirect);
+ }
+ }
+
+ /**
+ * Return value or valueAlt from damage part
+ * Must be called within Action context or similar.
+ * @param {object} part Damage Part
+ * @param {object} data Action getRollData
+ * @returns Formula value object
+ */
+ static getFormulaValue(part, data) {
+ let formulaValue = part.value;
+
+ if (data.hasRoll && part.resultBased && data.roll.result.duality === -1) return part.valueAlt;
+
+ const isAdversary = this.actor.type === 'adversary';
+ if (isAdversary && this.actor.system.type === CONFIG.DH.ACTOR.adversaryTypes.horde.id) {
+ const hasHordeDamage = this.actor.effects.find(x => x.type === 'horde');
+ if (hasHordeDamage && !hasHordeDamage.disabled) return part.valueAlt;
+ }
+
+ return formulaValue;
+ }
+
+ /**
+ * Prepare formulas for Damage Roll
+ * Must be called within Action context or similar.
+ * @param {object[]} formulas Array of formatted formulas object
+ * @param {object} data Action getRollData
+ * @returns
+ */
+ static formatFormulas(formulas, data) {
+ const formattedFormulas = [];
+ formulas.forEach(formula => {
+ if (isNaN(formula.formula))
+ formula.formula = Roll.replaceFormulaData(formula.formula, this.getRollData(data));
+ const same = formattedFormulas.find(
+ f => setsEqual(f.damageTypes, formula.damageTypes) && f.applyTo === formula.applyTo
+ );
+ if (same) same.formula += ` + ${formula.formula}`;
+ else formattedFormulas.push(formula);
+ });
+ return formattedFormulas;
+ }
+
+ /**
+ * Return the automation setting for execute method for current user role
+ * @returns {string} Id from settingsConfig.mjs actionAutomationChoices
+ */
+ static getAutomation() {
+ return (game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.damage.gm) || (!game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.damage.players)
+ }
+
+
+ /**
+ * Return the automation setting for applyDamage method for current user role
+ * @returns {boolean} If applyDamage should be triggered automatically
+ */
+ static getApplyAutomation() {
+ return (game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.damageApply.gm) || (!game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.damageApply.players)
+ }
}
export class DHActionDiceData extends foundry.abstract.DataModel {
diff --git a/module/data/fields/action/effectsField.mjs b/module/data/fields/action/effectsField.mjs
index ddc69d2d..3b8c5e43 100644
--- a/module/data/fields/action/effectsField.mjs
+++ b/module/data/fields/action/effectsField.mjs
@@ -1,6 +1,14 @@
+import { emitAsGM, GMUpdateEvent } from "../../../systemRegistration/socket.mjs";
+
const fields = foundry.data.fields;
export default class EffectsField extends fields.ArrayField {
+ /**
+ * Action Workflow order
+ */
+ static order = 100;
+
+ /** @inheritDoc */
constructor(options = {}, context = {}) {
const element = new fields.SchemaField({
_id: new fields.DocumentIdField(),
@@ -8,4 +16,86 @@ export default class EffectsField extends fields.ArrayField {
});
super(element, options, context);
}
+
+ /**
+ * Apply Effects Action Workflow part.
+ * Must be called within Action context or similar.
+ * @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
+ * @param {object[]} [targets=null] Array of targets to override pre-selected ones.
+ * @param {boolean} [force=false] If the method should be executed outside of Action workflow, for ChatMessage button for example.
+ */
+ static async execute(config, targets = null, force = false) {
+ if(!config.hasEffect) return;
+ let message = config.message ?? ui.chat.collection.get(config.parent?._id);
+ if(!message) {
+ const roll = new CONFIG.Dice.daggerheart.DHRoll('');
+ roll._evaluated = true;
+ message = config.message = await CONFIG.Dice.daggerheart.DHRoll.toMessage(roll, config);
+ }
+ if(EffectsField.getAutomation() || force) {
+ targets ??= (message.system?.targets ?? config.targets).filter(t => !config.hasRoll || t.hit);
+ await emitAsGM(
+ GMUpdateEvent.UpdateEffect,
+ EffectsField.applyEffects.bind(this),
+ targets,
+ this.uuid
+ );
+ // EffectsField.applyEffects.call(this, config.targets.filter(t => !config.hasRoll || t.hit));
+ }
+ }
+
+ /**
+ * Apply Action Effects to a list of Targets
+ * Must be called within Action context or similar.
+ * @param {object[]} targets Array of formatted targets
+ */
+ static async applyEffects(targets) {
+ if (!this.effects?.length || !targets?.length) return;
+ let effects = this.effects;
+ targets.forEach(async token => {
+ if (this.hasSave && token.saved.success === true)
+ effects = this.effects.filter(e => e.onSave === true);
+ if (!effects.length) return;
+ effects.forEach(async e => {
+ const actor = canvas.tokens.get(token.id)?.actor,
+ effect = this.item.effects.get(e._id);
+ if (!actor || !effect) return;
+ await EffectsField.applyEffect(effect, actor);
+ });
+ });
+ }
+
+ /**
+ * Apply an Effect to a target or enable it if already on it
+ * @param {object} effect Effect object containing ActiveEffect UUID
+ * @param {object} actor Actor Document
+ */
+ static async applyEffect(effect, actor) {
+ const existingEffect = actor.effects.find(e => e.origin === effect.uuid);
+ if (existingEffect) {
+ return effect.update(
+ foundry.utils.mergeObject({
+ ...effect.constructor.getInitialDuration(),
+ disabled: false
+ })
+ );
+ }
+
+ // Otherwise, create a new effect on the target
+ const effectData = foundry.utils.mergeObject({
+ ...effect.toObject(),
+ disabled: false,
+ transfer: false,
+ origin: effect.uuid
+ });
+ await ActiveEffect.implementation.create(effectData, { parent: actor });
+ }
+
+ /**
+ * Return the automation setting for execute method for current user role
+ * @returns {boolean} If execute should be triggered automatically
+ */
+ static getAutomation() {
+ return (game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.effect.gm) || (!game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.effect.players)
+ }
}
diff --git a/module/data/fields/action/healingField.mjs b/module/data/fields/action/healingField.mjs
deleted file mode 100644
index 98f4f5ea..00000000
--- a/module/data/fields/action/healingField.mjs
+++ /dev/null
@@ -1,12 +0,0 @@
-import { DHDamageData } from './damageField.mjs';
-
-const fields = foundry.data.fields;
-
-export default class HealingField extends fields.SchemaField {
- constructor(options, context = {}) {
- const healingFields = {
- parts: new fields.ArrayField(new fields.EmbeddedDataField(DHDamageData))
- };
- super(healingFields, options, context);
- }
-}
diff --git a/module/data/fields/action/macroField.mjs b/module/data/fields/action/macroField.mjs
index 62da0da0..222feb2a 100644
--- a/module/data/fields/action/macroField.mjs
+++ b/module/data/fields/action/macroField.mjs
@@ -1,7 +1,29 @@
const fields = foundry.data.fields;
export default class MacroField extends fields.DocumentUUIDField {
+ /**
+ * Action Workflow order
+ */
+ static order = 70;
+
+ /** @inheritDoc */
constructor(context = {}) {
super({ type: "Macro" }, context);
}
+
+ /**
+ * Macro Action Workflow part.
+ * Must be called within Action context or similar or similar.
+ * @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods. Currently not used.
+ */
+ static async execute(config) {
+ const fixUUID = !this.macro.includes('Macro.') ? `Macro.${this.macro}` : this.macro,
+ macro = await fromUuid(fixUUID);
+ try {
+ if (!macro) throw new Error(`No macro found for the UUID: ${this.macro}.`);
+ macro.execute();
+ } catch (error) {
+ ui.notifications.error(error);
+ }
+ }
}
diff --git a/module/data/fields/action/rangeField.mjs b/module/data/fields/action/rangeField.mjs
index 221f00af..1237e507 100644
--- a/module/data/fields/action/rangeField.mjs
+++ b/module/data/fields/action/rangeField.mjs
@@ -1,6 +1,8 @@
const fields = foundry.data.fields;
export default class RangeField extends fields.StringField {
+
+ /** @inheritDoc */
constructor(context = {}) {
const options = {
choices: CONFIG.DH.GENERAL.range,
@@ -11,7 +13,12 @@ export default class RangeField extends fields.StringField {
super(options, context);
}
- static prepareConfig(config) {
- return true;
+ /**
+ * Update Action Workflow config object.
+ * NOT YET IMPLEMENTED.
+ * @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
+ */
+ prepareConfig(config) {
+ return;
}
}
diff --git a/module/data/fields/action/rollField.mjs b/module/data/fields/action/rollField.mjs
index a4df2a9e..cfcfb56b 100644
--- a/module/data/fields/action/rollField.mjs
+++ b/module/data/fields/action/rollField.mjs
@@ -71,29 +71,6 @@ export class DHActionRollData extends foundry.abstract.DataModel {
const modifiers = [];
if (!this.parent?.actor) return modifiers;
switch (this.parent.actor.type) {
- case 'character':
- const spellcastingTrait =
- this.type === 'spellcast'
- ? (this.parent.actor?.system?.spellcastModifierTrait?.key ?? 'agility')
- : null;
- const trait =
- this.useDefault || !this.trait
- ? (spellcastingTrait ?? this.parent.item.system.attack?.roll?.trait ?? 'agility')
- : this.trait;
- if (
- this.type === CONFIG.DH.GENERAL.rollTypes.attack.id ||
- this.type === CONFIG.DH.GENERAL.rollTypes.trait.id
- )
- modifiers.push({
- label: `DAGGERHEART.CONFIG.Traits.${trait}.name`,
- value: this.parent.actor.system.traits[trait].value
- });
- else if (this.type === CONFIG.DH.GENERAL.rollTypes.spellcast.id)
- modifiers.push({
- label: `DAGGERHEART.CONFIG.RollTypes.spellcast.name`,
- value: this.parent.actor.system.spellcastModifier
- });
- break;
case 'companion':
case 'adversary':
if (this.type === CONFIG.DH.GENERAL.rollTypes.attack.id)
@@ -107,10 +84,74 @@ export class DHActionRollData extends foundry.abstract.DataModel {
}
return modifiers;
}
+
+ get rollTrait() {
+ if(this.parent?.actor?.type !== "character") return null;
+ switch (this.type) {
+ case CONFIG.DH.GENERAL.rollTypes.spellcast.id:
+ return this.parent.actor?.system?.spellcastModifierTrait?.key ?? 'agility';
+ case CONFIG.DH.GENERAL.rollTypes.attack.id:
+ case CONFIG.DH.GENERAL.rollTypes.trait.id:
+ return this.useDefault || !this.trait
+ ? this.parent.item.system.attack?.roll?.trait ?? 'agility'
+ : this.trait;
+ default:
+ return null;
+ }
+ }
}
export default class RollField extends fields.EmbeddedDataField {
+ /**
+ * Action Workflow order
+ */
+ static order = 10;
+
+ /** @inheritDoc */
constructor(options, context = {}) {
super(DHActionRollData, options, context);
}
+
+ /**
+ * Roll Action Workflow part.
+ * Must be called within Action context or similar.
+ * @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
+ */
+ static async execute(config) {
+ if(!config.hasRoll) return;
+ config = await this.actor.diceRoll(config);
+ if(!config) return false;
+ }
+
+ /**
+ * Update Action Workflow config object.
+ * Must be called within Action context.
+ * @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
+ */
+ prepareConfig(config) {
+ if(!config.hasRoll) return;
+
+ config.dialog.configure = RollField.getAutomation() ? !config.dialog.configure : config.dialog.configure;
+
+ const roll = {
+ baseModifiers: this.roll.getModifier(),
+ label: 'Attack',
+ type: this.roll?.type,
+ trait: this.roll?.rollTrait,
+ difficulty: this.roll?.difficulty,
+ formula: this.roll.getFormula(),
+ advantage: CONFIG.DH.ACTIONS.advantageState[this.roll.advState].value
+ };
+ if (this.roll.type === 'diceSet' || !this.hasRoll) roll.lite = true;
+
+ config.roll = roll;
+ }
+
+ /**
+ * Return the automation setting for execute method for current user role
+ * @returns {boolean} If execute should be triggered automatically
+ */
+ static getAutomation() {
+ return (game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.roll.gm) || (!game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.roll.players)
+ }
}
diff --git a/module/data/fields/action/saveField.mjs b/module/data/fields/action/saveField.mjs
index e93a82a9..0003a4d5 100644
--- a/module/data/fields/action/saveField.mjs
+++ b/module/data/fields/action/saveField.mjs
@@ -1,6 +1,14 @@
+import { abilities } from "../../../config/actorConfig.mjs";
+
const fields = foundry.data.fields;
export default class SaveField extends fields.SchemaField {
+ /**
+ * Action Workflow order
+ */
+ static order = 50;
+
+ /** @inheritDoc */
constructor(options = {}, context = {}) {
const saveFields = {
trait: new fields.StringField({
@@ -16,4 +24,151 @@ export default class SaveField extends fields.SchemaField {
};
super(saveFields, options, context);
}
+
+ /**
+ * Reaction Roll Action Workflow part.
+ * Must be called within Action context or similar.
+ * @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
+ * @param {object[]} [targets=null] Array of targets to override pre-selected ones.
+ * @param {boolean} [force=false] If the method should be executed outside of Action workflow, for ChatMessage button for example.
+ */
+ static async execute(config, targets = null, force = false) {
+ if(!config.hasSave) return;
+ let message = config.message ?? ui.chat.collection.get(config.parent?._id);
+
+ if(!message) {
+ const roll = new CONFIG.Dice.daggerheart.DHRoll('');
+ roll._evaluated = true;
+ message = config.message = await CONFIG.Dice.daggerheart.DHRoll.toMessage(roll, config);
+ }
+ if(SaveField.getAutomation() !== CONFIG.DH.SETTINGS.actionAutomationChoices.never.id || force) {
+ targets ??= config.targets.filter(t => !config.hasRoll || t.hit);
+ await SaveField.rollAllSave.call(this, targets, config.event, message);
+ } else return false;
+ }
+
+ /**
+ * Roll a Reaction Roll for all targets. Send a query to the owner if the User is not.
+ * Must be called within Action context.
+ * @param {object[]} targets Array of formatted targets.
+ * @param {Event} event Triggering event
+ * @param {ChatMessage} message The ChatMessage the triggered button comes from.
+ */
+ static async rollAllSave(targets, event, message) {
+ if(!targets) return;
+ return new Promise(resolve => {
+ const aPromise = [];
+ targets.forEach(target => {
+ aPromise.push(
+ new Promise(async subResolve => {
+ const actor = fromUuidSync(target.actorId);
+ if(actor) {
+ const rollSave = game.user === actor.owner ?
+ SaveField.rollSave.call(this, actor, event)
+ : actor.owner
+ .query('reactionRoll', {
+ actionId: this.uuid,
+ actorId: actor.uuid,
+ event,
+ message
+ });
+ const result = await rollSave;
+ await SaveField.updateSaveMessage.call(this, result, message, target.id);
+ subResolve();
+ } else subResolve();
+ })
+ )
+ });
+ Promise.all(aPromise).then(result => resolve());
+ })
+ }
+
+ /**
+ * Roll a Reaction Roll for the specified Actor against the Action difficulty.
+ * Must be called within Action context.
+ * @param {*} actor Actor document
+ * @param {Event} event Triggering event
+ * @returns {object} Actor diceRoll config result.
+ */
+ static async rollSave(actor, event) {
+ if (!actor) return;
+ const title = actor.isNPC
+ ? game.i18n.localize('DAGGERHEART.GENERAL.reactionRoll')
+ : game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
+ ability: game.i18n.localize(abilities[this.save.trait]?.label)
+ }),
+ rollConfig = {
+ event,
+ title,
+ roll: {
+ trait: this.save.trait,
+ difficulty: this.save.difficulty ?? this.actor?.baseSaveDifficulty,
+ type: 'trait'
+ },
+ actionType: 'reaction',
+ hasRoll: true,
+ data: actor.getRollData()
+ };
+ if(SaveField.getAutomation() === CONFIG.DH.SETTINGS.actionAutomationChoices.always.id) rollConfig.dialog = { configure: false };
+ return actor.diceRoll(rollConfig);
+ }
+
+ /**
+ * Update a Roll ChatMessage for a token according to his Reaction Roll result.
+ * @param {object} result Result from the Reaction Roll
+ * @param {object} message ChatMessage to update
+ * @param {string} targetId Token ID
+ */
+ static async updateSaveMessage(result, message, targetId) {
+ if (!result) return;
+ const updateMsg = async function(message, targetId, result) {
+ // setTimeout(async () => {
+ const chatMessage = ui.chat.collection.get(message._id),
+ changes = {
+ flags: {
+ [game.system.id]: {
+ reactionRolls: {
+ [targetId]:
+ {
+ result: result.roll.total,
+ success: result.roll.success
+ }
+ }
+ }
+ }
+ };
+ await chatMessage.update(changes);
+ // }, 100);
+ };
+ if (game.modules.get('dice-so-nice')?.active)
+ game.dice3d.waitFor3DAnimationByMessageID(result.message.id ?? result.message._id).then(async () => await updateMsg(message, targetId, result));
+ else await updateMsg(message, targetId, result);
+ }
+
+ /**
+ * Return the automation setting for execute method for current user role
+ * @returns {string} Id from settingsConfig.mjs actionAutomationChoices
+ */
+ static getAutomation() {
+ return (game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.save.gm) || (!game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.save.players)
+ }
+
+ /**
+ * Send a query to an Actor owner to roll a Reaction Roll then send back the result.
+ * @param {object} param0
+ * @param {string} param0.actionId Action ID
+ * @param {string} param0.actorId Actor ID
+ * @param {Event} param0.event Triggering event
+ * @param {ChatMessage} param0.message Chat Message to update
+ * @returns
+ */
+ static rollSaveQuery({ actionId, actorId, event, message }) {
+ return new Promise(async (resolve, reject) => {
+ const actor = await fromUuid(actorId),
+ action = await fromUuid(actionId);
+ if (!actor || !actor?.isOwner) reject();
+ SaveField.rollSave.call(action, actor, event, message)
+ .then(result => resolve(result));
+ });
+ }
}
diff --git a/module/data/fields/action/targetField.mjs b/module/data/fields/action/targetField.mjs
index bfb01db9..4499dcc8 100644
--- a/module/data/fields/action/targetField.mjs
+++ b/module/data/fields/action/targetField.mjs
@@ -1,6 +1,8 @@
const fields = foundry.data.fields;
export default class TargetField extends fields.SchemaField {
+
+ /** @inheritDoc */
constructor(options = {}, context = {}) {
const targetFields = {
type: new fields.StringField({
@@ -13,44 +15,70 @@ export default class TargetField extends fields.SchemaField {
super(targetFields, options, context);
}
- static prepareConfig(config) {
- if (!this.target?.type) return [];
+ /**
+ * Update Action Workflow config object.
+ * Must be called within Action context.
+ * @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
+ */
+ prepareConfig(config) {
+ if (!this.target?.type) return config.targets = [];
config.hasTarget = true;
let targets;
+ // If the Action is configured as self-targeted, set targets as the owner.
if (this.target?.type === CONFIG.DH.GENERAL.targetTypes.self.id)
targets = [this.actor.token ?? this.actor.prototypeToken];
else {
targets = Array.from(game.user.targets);
if (this.target.type !== CONFIG.DH.GENERAL.targetTypes.any.id) {
- targets = targets.filter(t => TargetField.isTargetFriendly.call(this, t));
+ targets = targets.filter(target => TargetField.isTargetFriendly(this.actor, target, this.target.type));
if (this.target.amount && targets.length > this.target.amount) targets = [];
}
}
config.targets = targets.map(t => TargetField.formatTarget.call(this, t));
const hasTargets = TargetField.checkTargets.call(this, this.target.amount, config.targets);
- if (config.isFastForward && !hasTargets)
- return ui.notifications.warn('Too many targets selected for that actions.');
- return hasTargets;
+ if (config.dialog.configure === false && !hasTargets) {
+ ui.notifications.warn('Too many targets selected for that actions.');
+ return hasTargets;
+ }
}
+ /**
+ * Check if the number of selected targets respect the amount set in the Action.
+ * NOT YET IMPLEMENTED. Will be with Target Picker.
+ * @param {number} amount Max amount of targets configured in the action.
+ * @param {*[]} targets Array of targeted tokens.
+ * @returns {boolean} If the amount of targeted tokens does not exceed action configured one.
+ */
static checkTargets(amount, targets) {
return true;
// return !amount || (targets.length > amount);
}
- static isTargetFriendly(target) {
- const actorDisposition = this.actor.token
- ? this.actor.token.disposition
- : this.actor.prototypeToken.disposition,
+ /**
+ * Compare 2 Actors disposition between each other
+ * @param {*} actor First actor document.
+ * @param {*} target Second actor document.
+ * @param {string} type Disposition id to compare (friendly/hostile).
+ * @returns {boolean} If both actors respect the provided type.
+ */
+ static isTargetFriendly(actor, target, type) {
+ const actorDisposition = actor.token
+ ? actor.token.disposition
+ : actor.prototypeToken.disposition,
targetDisposition = target.document.disposition;
return (
- (this.target.type === CONFIG.DH.GENERAL.targetTypes.friendly.id &&
+ (type === CONFIG.DH.GENERAL.targetTypes.friendly.id &&
actorDisposition === targetDisposition) ||
- (this.target.type === CONFIG.DH.GENERAL.targetTypes.hostile.id &&
+ (type === CONFIG.DH.GENERAL.targetTypes.hostile.id &&
actorDisposition + targetDisposition === 0)
);
}
+ /**
+ * Format actor to useful datas for Action roll workflow.
+ * @param {*} actor Actor object to format.
+ * @returns {*} Formatted Actor.
+ */
static formatTarget(actor) {
return {
id: actor.id,
diff --git a/module/data/fields/action/usesField.mjs b/module/data/fields/action/usesField.mjs
index 5c2bfb61..d180ddf8 100644
--- a/module/data/fields/action/usesField.mjs
+++ b/module/data/fields/action/usesField.mjs
@@ -3,6 +3,12 @@ import FormulaField from '../formulaField.mjs';
const fields = foundry.data.fields;
export default class UsesField extends fields.SchemaField {
+ /**
+ * Action Workflow order
+ */
+ static order = 160;
+
+ /** @inheritDoc */
constructor(options = {}, context = {}) {
const usesFields = {
value: new fields.NumberField({ nullable: true, initial: null }),
@@ -20,15 +26,45 @@ export default class UsesField extends fields.SchemaField {
super(usesFields, options, context);
}
- static prepareConfig(config) {
+ /**
+ * Uses Consumption Action Workflow part.
+ * Increment Action spent uses by 1.
+ * Must be called within Action context or similar or similar.
+ * @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
+ * @param {boolean} [successCost=false] Consume only resources configured as "On Success only" if not already consumed.
+ */
+ static async execute(config, successCost = false) {
+ if (
+ config.uses?.enabled &&
+ ((!successCost && (!config.uses?.consumeOnSuccess || config.roll?.success)) ||
+ (successCost && config.uses?.consumeOnSuccess))
+ )
+ this.update({ 'uses.value': this.uses.value + 1 });
+ }
+
+ /**
+ * Update Action Workflow config object.
+ * Must be called within Action context.
+ * @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
+ * @returns {boolean} Return false if fast-forwarded and no more uses.
+ */
+ prepareConfig(config) {
const uses = this.uses?.max ? foundry.utils.deepClone(this.uses) : null;
if (uses && !uses.value) uses.value = 0;
config.uses = uses;
const hasUses = UsesField.hasUses.call(this, config.uses);
- if (config.isFastForward && !hasUses) return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.actionNoUsesRemaining'));
- return hasUses;
+ if (config.dialog.configure === false && !hasUses) {
+ ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.actionNoUsesRemaining'));
+ return hasUses;
+ }
}
+ /**
+ * Prepare Uses object for Action Workflow
+ * Must be called within Action context.
+ * @param {object} uses
+ * @returns {object}
+ */
static calcUses(uses) {
if (!uses) return null;
return {
@@ -38,6 +74,12 @@ export default class UsesField extends fields.SchemaField {
};
}
+ /**
+ * Check if the Action still get atleast one unspent uses.
+ * Must be called within Action context.
+ * @param {*} uses
+ * @returns {boolean}
+ */
static hasUses(uses) {
if (!uses) return true;
let max = uses.max ?? 0;
diff --git a/module/data/settings/Automation.mjs b/module/data/settings/Automation.mjs
index e1d63669..da71e899 100644
--- a/module/data/settings/Automation.mjs
+++ b/module/data/settings/Automation.mjs
@@ -80,6 +80,72 @@ export default class DhAutomation extends foundry.abstract.DataModel {
initial: CONFIG.DH.GENERAL.defeatedConditions.defeated.id,
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.defeated.companionDefault.label'
})
+ }),
+ roll: new fields.SchemaField({
+ roll: new fields.SchemaField({
+ gm: new fields.BooleanField({
+ required: true,
+ initial: false,
+ label: 'DAGGERHEART.GENERAL.gm'
+ }),
+ players: new fields.BooleanField({
+ required: true,
+ initial: false,
+ label: 'DAGGERHEART.GENERAL.player.plurial'
+ })
+ }),
+ damage: new fields.SchemaField({
+ gm: new fields.StringField({
+ required: true,
+ initial: "never",
+ choices: CONFIG.DH.SETTINGS.actionAutomationChoices,
+ label: 'DAGGERHEART.GENERAL.gm'
+ }),
+ players: new fields.StringField({
+ required: true,
+ initial: "never",
+ choices: CONFIG.DH.SETTINGS.actionAutomationChoices,
+ label: 'DAGGERHEART.GENERAL.player.plurial'
+ })
+ }),
+ save: new fields.SchemaField({
+ gm: new fields.StringField({
+ required: true,
+ initial: "never",
+ choices: CONFIG.DH.SETTINGS.actionAutomationChoices,
+ label: 'DAGGERHEART.GENERAL.gm'
+ }),
+ players: new fields.StringField({
+ required: true,
+ initial: "never",
+ choices: CONFIG.DH.SETTINGS.actionAutomationChoices,
+ label: 'DAGGERHEART.GENERAL.player.plurial'
+ })
+ }),
+ damageApply: new fields.SchemaField({
+ gm: new fields.BooleanField({
+ required: true,
+ initial: false,
+ label: 'DAGGERHEART.GENERAL.gm'
+ }),
+ players: new fields.BooleanField({
+ required: true,
+ initial: false,
+ label: 'DAGGERHEART.GENERAL.player.plurial'
+ })
+ }),
+ effect: new fields.SchemaField({
+ gm: new fields.BooleanField({
+ required: true,
+ initial: false,
+ label: 'DAGGERHEART.GENERAL.gm'
+ }),
+ players: new fields.BooleanField({
+ required: true,
+ initial: false,
+ label: 'DAGGERHEART.GENERAL.player.plurial'
+ })
+ })
})
};
}
diff --git a/module/dice/d20Roll.mjs b/module/dice/d20Roll.mjs
index 63d84744..f0660106 100644
--- a/module/dice/d20Roll.mjs
+++ b/module/dice/d20Roll.mjs
@@ -128,7 +128,9 @@ export default class D20Roll extends DHRoll {
applyBaseBonus() {
const modifiers = foundry.utils.deepClone(this.options.roll.baseModifiers) ?? [];
- modifiers.push(...this.getBonus(`roll.${this.options.type}`, `${this.options.type?.capitalize()} Bonus`));
+ modifiers.push(
+ ...this.getBonus(`roll.${this.options.actionType}`, `${this.options.actionType?.capitalize()} Bonus`)
+ );
modifiers.push(
...this.getBonus(`roll.${this.options.roll.type}`, `${this.options.roll.type?.capitalize()} Bonus`)
);
@@ -138,7 +140,7 @@ export default class D20Roll extends DHRoll {
static postEvaluate(roll, config = {}) {
const data = super.postEvaluate(roll, config);
- data.type = config.roll?.type;
+ data.type = config.actionType;
data.difficulty = config.roll.difficulty;
if (config.targets?.length) {
config.targets.forEach(target => {
@@ -147,6 +149,7 @@ export default class D20Roll extends DHRoll {
});
data.success = config.targets.some(target => target.hit);
} else if (config.roll.difficulty) data.success = roll.isCritical || roll.total >= config.roll.difficulty;
+ config.successConsumed = data.success;
data.advantage = {
type: config.roll.advantage,
diff --git a/module/dice/damageRoll.mjs b/module/dice/damageRoll.mjs
index aa9e1d94..4d293d9d 100644
--- a/module/dice/damageRoll.mjs
+++ b/module/dice/damageRoll.mjs
@@ -9,6 +9,7 @@ export default class DamageRoll extends DHRoll {
static DefaultDialog = DamageDialog;
static async buildEvaluate(roll, config = {}, message = {}) {
+ if (config.dialog.configure === false) roll.constructFormula(config);
if (config.evaluate !== false) for (const roll of config.roll) await roll.roll.evaluate();
roll._evaluated = true;
@@ -46,9 +47,8 @@ export default class DamageRoll extends DHRoll {
);
}
await super.buildPost(roll, config, message);
- if (config.source?.message) {
+ if (config.source?.message)
chatMessage.update({ 'system.damage': config.damage });
- }
}
static unifyDamageRoll(rolls) {
diff --git a/module/dice/dhRoll.mjs b/module/dice/dhRoll.mjs
index ac340c64..0dcdd316 100644
--- a/module/dice/dhRoll.mjs
+++ b/module/dice/dhRoll.mjs
@@ -28,6 +28,7 @@ export default class DHRoll extends Roll {
static async buildConfigure(config = {}, message = {}) {
config.hooks = [...this.getHooks(), ''];
config.dialog ??= {};
+
for (const hook of config.hooks) {
if (Hooks.call(`${CONFIG.DH.id}.preRoll${hook.capitalize()}`, config, message) === false) return null;
}
@@ -45,10 +46,7 @@ export default class DHRoll extends Roll {
}
for (const hook of config.hooks) {
- if (
- Hooks.call(`${CONFIG.DH.id}.post${hook.capitalize()}RollConfiguration`, roll, config, message) === false
- )
- return [];
+ if (Hooks.call(`${CONFIG.DH.id}.post${hook.capitalize()}RollConfiguration`, roll, config, message) === false) return [];
}
return roll;
}
@@ -88,11 +86,12 @@ export default class DHRoll extends Roll {
type: this.messageType,
user: game.user.id,
title: roll.title,
- speaker: cls.getSpeaker(),
+ speaker: cls.getSpeaker({ actor: roll.data?.parent }),
sound: config.mute ? null : CONFIG.sounds.dice,
system: config,
rolls: [roll]
};
+
config.selectedRollMode ??= game.settings.get('core', 'rollMode');
if (roll._evaluated) {
@@ -226,7 +225,7 @@ export const registerRollDiceHooks = () => {
if (
!config.source?.actor ||
(game.user.isGM ? !hopeFearAutomation.gm : !hopeFearAutomation.players) ||
- config.roll.type === 'reaction'
+ config.actionType === 'reaction'
)
return;
diff --git a/module/dice/dualityRoll.mjs b/module/dice/dualityRoll.mjs
index ac1047ab..93ac231e 100644
--- a/module/dice/dualityRoll.mjs
+++ b/module/dice/dualityRoll.mjs
@@ -19,7 +19,7 @@ export default class DualityRoll extends D20Roll {
get title() {
return game.i18n.localize(
- `DAGGERHEART.GENERAL.${this.options?.roll?.type === CONFIG.DH.ITEM.actionTypes.reaction.id ? 'reactionRoll' : 'dualityRoll'}`
+ `DAGGERHEART.GENERAL.${this.options?.actionType === CONFIG.DH.ITEM.actionTypes.reaction.id ? 'reactionRoll' : 'dualityRoll'}`
);
}
@@ -153,10 +153,10 @@ export default class DualityRoll extends D20Roll {
applyBaseBonus() {
const modifiers = super.applyBaseBonus();
-
- if (this.options.roll.trait && this.data.traits[this.options.roll.trait])
+
+ if (this.options.roll.trait && this.data.traits?.[this.options.roll.trait])
modifiers.unshift({
- label: `DAGGERHEART.CONFIG.Traits.${this.options.roll.trait}.name`,
+ label: this.options.roll.type === CONFIG.DH.GENERAL.rollTypes.spellcast.id ? "DAGGERHEART.CONFIG.RollTypes.spellcast.name" : `DAGGERHEART.CONFIG.Traits.${this.options.roll.trait}.name`,
value: this.data.traits[this.options.roll.trait].value
});
diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs
index 08735a45..950ed621 100644
--- a/module/documents/actor.mjs
+++ b/module/documents/actor.mjs
@@ -14,7 +14,7 @@ export default class DhpActor extends Actor {
get owner() {
const user =
this.hasPlayerOwner && game.users.players.find(u => this.testUserPermission(u, 'OWNER') && u.active);
- if (!user) return game.user.isGM ? game.user : null;
+ if (!user) return game.users.activeGM;
return user;
}
diff --git a/module/documents/chatMessage.mjs b/module/documents/chatMessage.mjs
index c7f30e48..1a619a9c 100644
--- a/module/documents/chatMessage.mjs
+++ b/module/documents/chatMessage.mjs
@@ -1,3 +1,5 @@
+import { emitAsGM, GMUpdateEvent } from "../systemRegistration/socket.mjs";
+
export default class DhpChatMessage extends foundry.documents.ChatMessage {
targetHook = null;
@@ -102,20 +104,30 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
});
if (itemDesc && autoExpandRoll.desc) itemDesc.setAttribute('open', '');
}
-
- if (!game.user.isGM) {
- const applyButtons = html.querySelector('.apply-buttons');
+
+ if(!this.isAuthor && !this.speakerActor?.isOwner) {
+ const applyButtons = html.querySelector(".apply-buttons");
applyButtons?.remove();
- if (!this.isAuthor && !this.speakerActor?.isOwner) {
- const buttons = html.querySelectorAll('.ability-card-footer > .ability-use-button');
- buttons.forEach(b => b.remove());
- }
+ const buttons = html.querySelectorAll(".ability-card-footer > .ability-use-button");
+ buttons.forEach(b => b.remove());
}
}
addChatListeners(html) {
+ html.querySelectorAll('.duality-action-damage').forEach(element =>
+ element.addEventListener('click', this.onRollDamage.bind(this))
+ );
+
html.querySelectorAll('.damage-button').forEach(element =>
- element.addEventListener('click', this.onDamage.bind(this))
+ element.addEventListener('click', this.onApplyDamage.bind(this))
+ );
+
+ html.querySelectorAll('.target-save').forEach(element =>
+ element.addEventListener('click', this.onRollSave.bind(this))
+ );
+
+ html.querySelectorAll('.roll-all-save-button').forEach(element =>
+ element.addEventListener('click', this.onRollAllSave.bind(this))
);
html.querySelectorAll('.duality-action-effect').forEach(element =>
@@ -133,17 +145,21 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
});
}
- getTargetList() {
- const targets = this.system.hitTargets ?? [];
- return targets.map(target => game.canvas.tokens.documentCollection.find(t => t.actor?.uuid === target.actorId));
+ async onRollDamage(event) {
+ event.stopPropagation();
+ const config = foundry.utils.deepClone(this.system);
+ config.event = event;
+ this.system.action?.workflow.get("damage")?.execute(config, this._id, true);
}
- async onDamage(event) {
+ async onApplyDamage(event) {
event.stopPropagation();
- const targets = this.getTargetList();
+ const targets = this.filterPermTargets(this.system.hitTargets),
+ config = foundry.utils.deepClone(this.system);
+ config.event = event;
if (this.system.onSave) {
- const pendingingSaves = this.system.hitTargets.filter(t => t.saved.success === null);
+ 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' },
@@ -154,62 +170,62 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
}
if (targets.length === 0)
- return ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelected'));
+ return ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelectedOrPerm'));
+
+ this.consumeOnSuccess();
+ this.system.action?.workflow.get("applyDamage")?.execute(config, targets, true);
+ }
- for (let target of targets) {
- let damages = foundry.utils.deepClone(this.system.damage);
- if (
- !this.system.hasHealing &&
- this.system.onSave &&
- this.system.hitTargets.find(t => t.id === target.id)?.saved?.success === true
- ) {
- const mod = CONFIG.DH.ACTIONS.damageOnSave[this.system.onSave]?.mod ?? 1;
- Object.entries(damages).forEach(([k, v]) => {
- v.total = 0;
- v.parts.forEach(part => {
- part.total = Math.ceil(part.total * mod);
- v.total += part.total;
- });
- });
- }
-
- this.consumeOnSuccess();
- if (this.system.hasHealing) target.actor.takeHealing(damages);
- else target.actor.takeDamage(damages, this.system.isDirect);
+ async onRollSave(event) {
+ event.stopPropagation();
+ const tokenId = event.target.closest('[data-token]')?.dataset.token,
+ token = game.canvas.tokens.get(tokenId);
+ if (!token?.actor || !token.isOwner) return true;
+ if (this.system.source.item && this.system.source.action) {
+ const action = this.system.action;
+ if (!action || !action?.hasSave) return;
+ game.system.api.fields.ActionFields.SaveField.rollSave.call(action, token.actor, event).then(result =>
+ emitAsGM(
+ GMUpdateEvent.UpdateSaveMessage,
+ game.system.api.fields.ActionFields.SaveField.updateSaveMessage.bind(action, result, this, token.id),
+ {
+ action: action.uuid,
+ message: this._id,
+ token: token.id,
+ result
+ }
+ )
+ );
}
}
- getAction(actor, itemId, actionId) {
- const item = actor.items.get(itemId),
- action =
- actor.system.attack?._id === actionId
- ? actor.system.attack
- : item.system.attack?._id === actionId
- ? item.system.attack
- : item?.system?.actions?.get(actionId);
- return action;
+ async onRollAllSave(event) {
+ event.stopPropagation();
+ if (!game.user.isGM) return;
+ const targets = this.system.hitTargets,
+ config = foundry.utils.deepClone(this.system);
+ config.event = event;
+ this.system.action?.workflow.get("save")?.execute(config, targets, true);
}
async onApplyEffect(event) {
event.stopPropagation();
- const actor = await foundry.utils.fromUuid(this.system.source.actor);
- if (!actor || !game.user.isGM) return true;
- if (this.system.source.item && this.system.source.action) {
- const action = this.getAction(actor, this.system.source.item, this.system.source.action);
- if (!action || !action?.applyEffects) return;
- const targets = this.getTargetList();
- if (targets.length === 0)
- ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelected'));
- this.consumeOnSuccess();
- await action.applyEffects(event, this, targets);
- }
+ const targets = this.filterPermTargets(this.system.hitTargets),
+ config = foundry.utils.deepClone(this.system);
+ config.event = event;
+ if (targets.length === 0)
+ ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelectedOrPerm'));
+ this.consumeOnSuccess();
+ this.system.action?.workflow.get("effects")?.execute(config, targets, true);
+ }
+
+ filterPermTargets(targets) {
+ return targets.filter(t => fromUuidSync(t.actorId)?.canUserModify(game.user, "update"))
}
consumeOnSuccess() {
- if (!this.system.successConsumed && !this.targetSelection) {
- const action = this.system.action;
- if (action) action.consume(this.system, true);
- }
+ if (!this.system.successConsumed && !this.targetSelection)
+ this.system.action?.consume(this.system, true);
}
hoverTarget(event) {
diff --git a/module/systemRegistration/socket.mjs b/module/systemRegistration/socket.mjs
index b47fee85..f3f9629b 100644
--- a/module/systemRegistration/socket.mjs
+++ b/module/systemRegistration/socket.mjs
@@ -22,6 +22,7 @@ export const socketEvent = {
export const GMUpdateEvent = {
UpdateDocument: 'DhGMUpdateDocument',
+ UpdateEffect: 'DhGMUpdateEffect',
UpdateSetting: 'DhGMUpdateSetting',
UpdateFear: 'DhGMUpdateFear',
UpdateSaveMessage: 'DhGMUpdateSaveMessage'
@@ -37,9 +38,12 @@ export const registerSocketHooks = () => {
const document = data.uuid ? await fromUuid(data.uuid) : null;
switch (data.action) {
case GMUpdateEvent.UpdateDocument:
- if (document && data.update) {
+ if (document && data.update)
await document.update(data.update);
- }
+ break;
+ case GMUpdateEvent.UpdateEffect:
+ if (document && data.update)
+ await game.system.api.fields.ActionFields.EffectsField.applyEffects.call(document, data.update);
break;
case GMUpdateEvent.UpdateSetting:
await game.settings.set(CONFIG.DH.id, data.uuid, data.update);
@@ -78,7 +82,7 @@ export const registerSocketHooks = () => {
export const registerUserQueries = () => {
CONFIG.queries.armorSlot = DamageReductionDialog.armorSlotQuery;
- CONFIG.queries.reactionRoll = game.system.api.models.actions.actionsTypes.base.rollSaveQuery;
+ CONFIG.queries.reactionRoll = game.system.api.fields.ActionFields.SaveField.rollSaveQuery;
};
export const emitAsGM = async (eventName, callback, update, uuid = null) => {
diff --git a/styles/less/global/elements.less b/styles/less/global/elements.less
index 399715ca..e4a2128c 100755
--- a/styles/less/global/elements.less
+++ b/styles/less/global/elements.less
@@ -624,6 +624,7 @@
.form-group {
justify-content: end;
+ flex-wrap: nowrap;
}
.hint {
diff --git a/styles/less/global/global.less b/styles/less/global/global.less
index cf8431b5..e135846f 100644
--- a/styles/less/global/global.less
+++ b/styles/less/global/global.less
@@ -18,7 +18,7 @@
color: var(--color-form-hint);
}
- &:hover {
+ .form-group:hover {
.hint {
color: var(--color-form-hint-hover);
}
diff --git a/styles/less/ui/settings/settings.less b/styles/less/ui/settings/settings.less
index 2ac8bfb0..67fe7718 100644
--- a/styles/less/ui/settings/settings.less
+++ b/styles/less/ui/settings/settings.less
@@ -127,4 +127,4 @@
text-align: center;
}
}
-}
+}
\ No newline at end of file
diff --git a/templates/actionTypes/beastform.hbs b/templates/actionTypes/beastform.hbs
index b2710208..b9bea445 100644
--- a/templates/actionTypes/beastform.hbs
+++ b/templates/actionTypes/beastform.hbs
@@ -1,9 +1,4 @@
-
-
- {{localize "DAGGERHEART.ACTIONS.Config.beastform.label"}}
-
-
-
- {{formGroup fields.tierAccess.fields.exact value=beastform.tierAccess.exact labelAttr="label" valueAttr="key" localize=true blank=""}}
-
+
+ {{localize "DAGGERHEART.ACTIONS.Config.beastform.label"}}
+ {{formGroup fields.tierAccess.fields.exact value=source.tierAccess.exact name="beastform.tierAccess.exact" labelAttr="label" valueAttr="key" localize=true blank=""}}
\ No newline at end of file
diff --git a/templates/actionTypes/damage.hbs b/templates/actionTypes/damage.hbs
index 6a94752b..96bb361c 100644
--- a/templates/actionTypes/damage.hbs
+++ b/templates/actionTypes/damage.hbs
@@ -56,7 +56,7 @@
{{formField ../fields.valueAlt.fields.flatMultiplier value=dmg.valueAlt.flatMultiplier name=(concat ../path "damage.parts." realIndex ".valueAlt.flatMultiplier") label="DAGGERHEART.ACTIONS.Settings.multiplier" classes="inline-child" localize=true }}
- {{formField ../fields.valueAlt.fields.dice value=dmg.valueAlt.dice name=(concat ../path "damage.parts." realIndex ".valueAlt.dice") classes="inline-child"}}
+ {{formField ../fields.valueAlt.fields.dice value=dmg.valueAlt.dice name=(concat ../path "damage.parts." realIndex ".valueAlt.dice") classes="inline-child" localize=true}}
{{formField ../fields.valueAlt.fields.bonus value=dmg.valueAlt.bonus name=(concat ../path "damage.parts." realIndex ".valueAlt.bonus") localize=true classes="inline-child"}}
@@ -70,7 +70,7 @@
{{#*inline "formula"}}
{{#unless dmg.base}}
- {{formField fields.custom.fields.enabled value=source.custom.enabled name=(concat path "damage.parts." realIndex "." target ".custom.enabled") classes="checkbox"}}
+ {{formField fields.custom.fields.enabled value=source.custom.enabled name=(concat path "damage.parts." realIndex "." target ".custom.enabled") classes="checkbox" localize=true}}
{{/unless}}
{{#if source.custom.enabled}}
{{formField fields.custom.fields.formula value=source.custom.formula name=(concat path "damage.parts." realIndex "." target ".custom.formula") localize=true}}
@@ -79,8 +79,8 @@
{{#unless @root.isNPC}}
{{formField fields.multiplier value=source.multiplier name=(concat path "damage.parts." realIndex "." target ".multiplier") localize=true}}
{{/unless}}
- {{#if (eq source.multiplier 'flat')}}{{formField fields.flatMultiplier value=source.flatMultiplier name=(concat path "damage.parts." realIndex "." target ".flatMultiplier") }}{{/if}}
- {{formField fields.dice value=source.dice name=(concat path "damage.parts." realIndex "." target ".dice")}}
+ {{#if (eq source.multiplier 'flat')}}{{formField fields.flatMultiplier value=source.flatMultiplier name=(concat path "damage.parts." realIndex "." target ".flatMultiplier") localize=true }}{{/if}}
+ {{formField fields.dice value=source.dice name=(concat path "damage.parts." realIndex "." target ".dice") localize=true}}
{{formField fields.bonus value=source.bonus name=(concat path "damage.parts." realIndex "." target ".bonus") localize=true}}