mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-12 03:31:07 +01:00
Merged with development
This commit is contained in:
commit
19a07139ff
548 changed files with 4997 additions and 2887 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
name: Feature report
|
name: Feature request
|
||||||
about: Create a feature report for suggestions on improving the system
|
about: Create a feature request for suggestions on improving the system
|
||||||
title: "[Feature] <Insert Title here> "
|
title: "[Feature] <Insert Title here> "
|
||||||
labels: enhancement, discussion, maybe
|
labels: enhancement, discussion, maybe
|
||||||
type: feature
|
type: feature
|
||||||
|
|
@ -17,6 +17,10 @@ We welcome contributions of all kinds:
|
||||||
|
|
||||||
Please be respectful and collaborative — we’re all here to build something great together.
|
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
|
## 🧭 General Guidelines
|
||||||
|
|
@ -40,12 +44,14 @@ We encourage contributors to leave comments or open Discussions when proposing s
|
||||||
## 🧾 Issue & PR Best Practices
|
## 🧾 Issue & PR Best Practices
|
||||||
|
|
||||||
**For Issues:**
|
**For Issues:**
|
||||||
|
|
||||||
- Use clear, descriptive titles
|
- Use clear, descriptive titles
|
||||||
- Provide a concise explanation of the problem or idea
|
- Provide a concise explanation of the problem or idea
|
||||||
- Include reproduction steps or example scenarios if it's a bug
|
- Include reproduction steps or example scenarios if it's a bug
|
||||||
- Add screenshots or logs if helpful
|
- Add screenshots or logs if helpful
|
||||||
|
|
||||||
**For Pull Requests:**
|
**For Pull Requests:**
|
||||||
|
|
||||||
- Use a clear title summarizing the change
|
- Use a clear title summarizing the change
|
||||||
- Provide a brief description of what your code does and why
|
- Provide a brief description of what your code does and why
|
||||||
- Link to any related Issues
|
- Link to any related Issues
|
||||||
|
|
@ -67,6 +73,6 @@ Discussions are currently happening on GitHub — in Issues, PRs, and [GitHub Di
|
||||||
|
|
||||||
## 🤗 Thank You!
|
## 🤗 Thank You!
|
||||||
|
|
||||||
Whether you're fixing a typo or designing entire mechanics — every contribution matters. Thank you for helping bring *Daggerheart* to life in FoundryVTT through **Foundryborne**!
|
Whether you're fixing a typo or designing entire mechanics — every contribution matters. Thank you for helping bring _Daggerheart_ to life in FoundryVTT through **Foundryborne**!
|
||||||
|
|
||||||
🐸🛠️
|
🐸🛠️
|
||||||
|
|
|
||||||
|
|
@ -164,6 +164,9 @@ Hooks.on('ready', async () => {
|
||||||
if (game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.appearance).displayFear !== 'hide')
|
if (game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.appearance).displayFear !== 'hide')
|
||||||
ui.resources.render({ force: true });
|
ui.resources.render({ force: true });
|
||||||
|
|
||||||
|
if (!(ui.compendiumBrowser instanceof applications.ui.ItemBrowser))
|
||||||
|
ui.compendiumBrowser = new applications.ui.ItemBrowser();
|
||||||
|
|
||||||
registerCountdownHooks();
|
registerCountdownHooks();
|
||||||
socketRegistration.registerSocketHooks();
|
socketRegistration.registerSocketHooks();
|
||||||
registerRollDiceHooks();
|
registerRollDiceHooks();
|
||||||
|
|
@ -307,3 +310,6 @@ Hooks.on('moveToken', async (movedToken, data) => {
|
||||||
await effect.value.update({ disabled: effect.disabled });
|
await effect.value.update({ disabled: effect.disabled });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Hooks.on('renderCompendiumDirectory', (app, html) => applications.ui.ItemBrowser.injectSidebarButton(html));
|
||||||
|
Hooks.on('renderDocumentDirectory', (app, html) => applications.ui.ItemBrowser.injectSidebarButton(html));
|
||||||
|
|
|
||||||
116
lang/en.json
116
lang/en.json
|
|
@ -96,6 +96,7 @@
|
||||||
"attackName": "Attack Name",
|
"attackName": "Attack Name",
|
||||||
"includeBase": { "label": "Include Item Damage" },
|
"includeBase": { "label": "Include Item Damage" },
|
||||||
"multiplier": "Multiplier",
|
"multiplier": "Multiplier",
|
||||||
|
"saveHint": "Set a default Trait to enable Reaction Roll. It can be changed later in Reaction Roll Dialog.",
|
||||||
"resultBased": {
|
"resultBased": {
|
||||||
"label": "Formula based on Hope/Fear result."
|
"label": "Formula based on Hope/Fear result."
|
||||||
},
|
},
|
||||||
|
|
@ -169,7 +170,9 @@
|
||||||
"hint": "Add single words or short text as reminders and hints of what a character has advantage on."
|
"hint": "Add single words or short text as reminders and hints of what a character has advantage on."
|
||||||
},
|
},
|
||||||
"age": "Age",
|
"age": "Age",
|
||||||
|
"backgroundQuestions": "Backgrounds",
|
||||||
"companionFeatures": "Companion Features",
|
"companionFeatures": "Companion Features",
|
||||||
|
"connections": "Connections",
|
||||||
"contextMenu": {
|
"contextMenu": {
|
||||||
"consume": "Consume Item",
|
"consume": "Consume Item",
|
||||||
"equip": "Equip",
|
"equip": "Equip",
|
||||||
|
|
@ -266,7 +269,8 @@
|
||||||
"experience": "Experience",
|
"experience": "Experience",
|
||||||
"traits": "Traits",
|
"traits": "Traits",
|
||||||
"domainCards": "Domain Cards",
|
"domainCards": "Domain Cards",
|
||||||
"equipment": "Equipment"
|
"equipment": "Equipment",
|
||||||
|
"story": "Story"
|
||||||
},
|
},
|
||||||
"ancestryNamePlaceholder": "Your ancestry's name",
|
"ancestryNamePlaceholder": "Your ancestry's name",
|
||||||
"buttonTitle": "Character Setup",
|
"buttonTitle": "Character Setup",
|
||||||
|
|
@ -287,6 +291,7 @@
|
||||||
"selectSubclass": "Select Subclass",
|
"selectSubclass": "Select Subclass",
|
||||||
"startingItems": "Starting Items",
|
"startingItems": "Starting Items",
|
||||||
"story": "Story",
|
"story": "Story",
|
||||||
|
"storyExplanation": "Select which background and connection prompts you want to copy into your character's background.",
|
||||||
"suggestedArmor": "Suggested Armor",
|
"suggestedArmor": "Suggested Armor",
|
||||||
"suggestedPrimaryWeapon": "Suggested Primary Weapon",
|
"suggestedPrimaryWeapon": "Suggested Primary Weapon",
|
||||||
"suggestedSecondaryWeapon": "Suggested Secondary Weapon",
|
"suggestedSecondaryWeapon": "Suggested Secondary Weapon",
|
||||||
|
|
@ -310,7 +315,8 @@
|
||||||
"toLoadout": "Send to Loadout",
|
"toLoadout": "Send to Loadout",
|
||||||
"toVault": "Send to Vault",
|
"toVault": "Send to Vault",
|
||||||
"unequip": "Unequip",
|
"unequip": "Unequip",
|
||||||
"useItem": "Use Item"
|
"useItem": "Use Item",
|
||||||
|
"cancelBeastform": "Cancel Beastform"
|
||||||
},
|
},
|
||||||
"Countdown": {
|
"Countdown": {
|
||||||
"addCountdown": "Add Countdown",
|
"addCountdown": "Add Countdown",
|
||||||
|
|
@ -1908,8 +1914,10 @@
|
||||||
"tier4": "tier 4",
|
"tier4": "tier 4",
|
||||||
"domains": "Domains",
|
"domains": "Domains",
|
||||||
"downtime": "Downtime",
|
"downtime": "Downtime",
|
||||||
|
"roll": "Roll",
|
||||||
"rules": "Rules",
|
"rules": "Rules",
|
||||||
"types": "Types"
|
"types": "Types",
|
||||||
|
"questions": "Questions"
|
||||||
},
|
},
|
||||||
"Tiers": {
|
"Tiers": {
|
||||||
"singular": "Tier",
|
"singular": "Tier",
|
||||||
|
|
@ -1926,6 +1934,7 @@
|
||||||
"amount": "Amount",
|
"amount": "Amount",
|
||||||
"any": "Any",
|
"any": "Any",
|
||||||
"armor": "Armor",
|
"armor": "Armor",
|
||||||
|
"armors": "Armors",
|
||||||
"armorScore": "Armor Score",
|
"armorScore": "Armor Score",
|
||||||
"activeEffects": "Active Effects",
|
"activeEffects": "Active Effects",
|
||||||
"armorSlots": "Armor Slots",
|
"armorSlots": "Armor Slots",
|
||||||
|
|
@ -1960,6 +1969,7 @@
|
||||||
"fear": "Fear",
|
"fear": "Fear",
|
||||||
"features": "Features",
|
"features": "Features",
|
||||||
"formula": "Formula",
|
"formula": "Formula",
|
||||||
|
"gm": "GM",
|
||||||
"healing": "Healing",
|
"healing": "Healing",
|
||||||
"healingRoll": "Healing Roll",
|
"healingRoll": "Healing Roll",
|
||||||
"hit": {
|
"hit": {
|
||||||
|
|
@ -1978,6 +1988,8 @@
|
||||||
"inactiveEffects": "Inactive Effects",
|
"inactiveEffects": "Inactive Effects",
|
||||||
"inventory": "Inventory",
|
"inventory": "Inventory",
|
||||||
"itemResource": "Item Resource",
|
"itemResource": "Item Resource",
|
||||||
|
"itemQuantity": "Item Quantity",
|
||||||
|
"items": "Items",
|
||||||
"label": "Label",
|
"label": "Label",
|
||||||
"level": "Level",
|
"level": "Level",
|
||||||
"levelShort": "Lv",
|
"levelShort": "Lv",
|
||||||
|
|
@ -1989,11 +2001,16 @@
|
||||||
"plural": "Miss"
|
"plural": "Miss"
|
||||||
},
|
},
|
||||||
"maxWithThing": "Max {thing}",
|
"maxWithThing": "Max {thing}",
|
||||||
|
"missingDragDropThing": "Drop {thing} here",
|
||||||
"multiclass": "Multiclass",
|
"multiclass": "Multiclass",
|
||||||
"newCategory": "New Category",
|
"newCategory": "New Category",
|
||||||
"none": "None",
|
"none": "None",
|
||||||
"noTarget": "No current target",
|
"noTarget": "No current target",
|
||||||
"partner": "Partner",
|
"partner": "Partner",
|
||||||
|
"player": {
|
||||||
|
"single": "Player",
|
||||||
|
"plurial": "Players"
|
||||||
|
},
|
||||||
"proficiency": "Proficiency",
|
"proficiency": "Proficiency",
|
||||||
"quantity": "Quantity",
|
"quantity": "Quantity",
|
||||||
"range": "Range",
|
"range": "Range",
|
||||||
|
|
@ -2010,6 +2027,7 @@
|
||||||
"scalable": "Scalable",
|
"scalable": "Scalable",
|
||||||
"situationalBonus": "Situational Bonus",
|
"situationalBonus": "Situational Bonus",
|
||||||
"stress": "Stress",
|
"stress": "Stress",
|
||||||
|
"subclasses": "Subclasses",
|
||||||
"success": "Success",
|
"success": "Success",
|
||||||
"take": "Take",
|
"take": "Take",
|
||||||
"Target": {
|
"Target": {
|
||||||
|
|
@ -2018,6 +2036,7 @@
|
||||||
},
|
},
|
||||||
"title": "Title",
|
"title": "Title",
|
||||||
"total": "Total",
|
"total": "Total",
|
||||||
|
"traitModifier": "Trait Modifier",
|
||||||
"true": "True",
|
"true": "True",
|
||||||
"type": "Type",
|
"type": "Type",
|
||||||
"unarmed": "Unarmed",
|
"unarmed": "Unarmed",
|
||||||
|
|
@ -2027,6 +2046,7 @@
|
||||||
"used": "Used",
|
"used": "Used",
|
||||||
"uses": "Uses",
|
"uses": "Uses",
|
||||||
"value": "Value",
|
"value": "Value",
|
||||||
|
"weapons": "Weapons",
|
||||||
"withThing": "With {thing}"
|
"withThing": "With {thing}"
|
||||||
},
|
},
|
||||||
"ITEMS": {
|
"ITEMS": {
|
||||||
|
|
@ -2110,7 +2130,8 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Consumable": {
|
"Consumable": {
|
||||||
"consumeOnUse": "Consume On Use"
|
"consumeOnUse": "Consume On Use",
|
||||||
|
"destroyOnEmpty": "Destroy On Empty"
|
||||||
},
|
},
|
||||||
"DomainCard": {
|
"DomainCard": {
|
||||||
"type": "Type",
|
"type": "Type",
|
||||||
|
|
@ -2131,20 +2152,43 @@
|
||||||
"SETTINGS": {
|
"SETTINGS": {
|
||||||
"Appearance": {
|
"Appearance": {
|
||||||
"FIELDS": {
|
"FIELDS": {
|
||||||
"displayFear": { "label": "Fear Display" },
|
"displayFear": {
|
||||||
"dualityColorScheme": { "label": "Chat Style" },
|
"label": "Display Fear"
|
||||||
"hideAttribution": { "label": "Hide Attribution" },
|
},
|
||||||
|
"showGenericStatusEffects": {
|
||||||
|
"label": "Show Foundry Status Effects"
|
||||||
|
},
|
||||||
|
"hideAttribution": {
|
||||||
|
"label": "Hide Attribution"
|
||||||
|
},
|
||||||
"expandedTitle": "Auto-expand Descriptions",
|
"expandedTitle": "Auto-expand Descriptions",
|
||||||
"extendCharacterDescriptions": { "label": "Characters" },
|
"extendCharacterDescriptions": {
|
||||||
"extendAdversaryDescriptions": { "label": "Adversaries" },
|
"label": "Characters"
|
||||||
"extendEnvironmentDescriptions": { "label": "Environments" },
|
},
|
||||||
"extendItemDescriptions": { "label": "Items" },
|
"extendAdversaryDescriptions": {
|
||||||
"expandRollMessage": "Auto-expand Message Sections",
|
"label": "Adversaries"
|
||||||
"expandRollMessageDesc": { "label": "Description" },
|
},
|
||||||
"expandRollMessageRoll": { "label": "Formula" },
|
"extendEnvironmentDescriptions": {
|
||||||
"expandRollMessageDamage": { "label": "Damage/Healing" },
|
"label": "Environments"
|
||||||
"expandRollMessageTarget": { "label": "Target" },
|
},
|
||||||
"showGenericStatusEffects": { "label": "Show Foundry Status Effects" }
|
"extendItemDescriptions": {
|
||||||
|
"label": "Items"
|
||||||
|
},
|
||||||
|
"expandRollMessage": {
|
||||||
|
"title": "Auto-expand Message Sections",
|
||||||
|
"desc": {
|
||||||
|
"label": "Description"
|
||||||
|
},
|
||||||
|
"roll": {
|
||||||
|
"label": "Formula"
|
||||||
|
},
|
||||||
|
"damage": {
|
||||||
|
"label": "Damage/Healing"
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"label": "Target"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"fearDisplay": {
|
"fearDisplay": {
|
||||||
"token": "Tokens",
|
"token": "Tokens",
|
||||||
|
|
@ -2199,10 +2243,35 @@
|
||||||
"playerCanEditSheet": {
|
"playerCanEditSheet": {
|
||||||
"label": "Players Can Manually Edit Character Settings",
|
"label": "Players Can Manually Edit Character Settings",
|
||||||
"hint": "Players are allowed to access the manual Character Settings and change their statistics beyond the rules."
|
"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": {
|
"defeated": {
|
||||||
"title": "Defeated Handling"
|
"title": "Defeated Handling"
|
||||||
|
},
|
||||||
|
"roll": {
|
||||||
|
"title": "Actions"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Homebrew": {
|
"Homebrew": {
|
||||||
|
|
@ -2264,10 +2333,8 @@
|
||||||
"hint": "System ruler setup for displaying ranges in Daggerheart"
|
"hint": "System ruler setup for displaying ranges in Daggerheart"
|
||||||
},
|
},
|
||||||
"appearance": {
|
"appearance": {
|
||||||
"title": "Appearance Settings",
|
|
||||||
"label": "Appearance Settings",
|
"label": "Appearance Settings",
|
||||||
"hint": "Modify the look of various parts of the system",
|
"hint": "Modify the look of various parts of the system",
|
||||||
"name": "Appearance Settings",
|
|
||||||
"duality": "Duality Rolls",
|
"duality": "Duality Rolls",
|
||||||
"diceSoNice": {
|
"diceSoNice": {
|
||||||
"title": "Dice So Nice",
|
"title": "Dice So Nice",
|
||||||
|
|
@ -2388,6 +2455,7 @@
|
||||||
"evasionMax": "Evasion (Max)",
|
"evasionMax": "Evasion (Max)",
|
||||||
"subtype": "Subtype",
|
"subtype": "Subtype",
|
||||||
"folders": {
|
"folders": {
|
||||||
|
"characters": "Characters",
|
||||||
"adversaries": "Adversaries",
|
"adversaries": "Adversaries",
|
||||||
"ancestries": "Ancestries",
|
"ancestries": "Ancestries",
|
||||||
"equipment": "Equipment",
|
"equipment": "Equipment",
|
||||||
|
|
@ -2398,7 +2466,11 @@
|
||||||
"environments": "Environments",
|
"environments": "Environments",
|
||||||
"beastforms": "Beastforms",
|
"beastforms": "Beastforms",
|
||||||
"features": "Features",
|
"features": "Features",
|
||||||
"items": "Items"
|
"items": "Items",
|
||||||
|
"weapons": "Weapons",
|
||||||
|
"armors": "Armors",
|
||||||
|
"consumables": "Consumables",
|
||||||
|
"loots": "Loots"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Notifications": {
|
"Notifications": {
|
||||||
|
|
@ -2406,6 +2478,7 @@
|
||||||
"beastformInapplicable": "A beastform can only be applied to a Character.",
|
"beastformInapplicable": "A beastform can only be applied to a Character.",
|
||||||
"beastformAlreadyApplied": "The character already has a beastform applied!",
|
"beastformAlreadyApplied": "The character already has a beastform applied!",
|
||||||
"noTargetsSelected": "No targets are selected.",
|
"noTargetsSelected": "No targets are selected.",
|
||||||
|
"noTargetsSelectedOrPerm": "No targets are selected or with the update permission.",
|
||||||
"attackTargetDoesNotExist": "The target token no longer exists",
|
"attackTargetDoesNotExist": "The target token no longer exists",
|
||||||
"insufficentAdvancements": "You don't have enough advancements left.",
|
"insufficentAdvancements": "You don't have enough advancements left.",
|
||||||
"noAssignedPlayerCharacter": "You have no assigned character.",
|
"noAssignedPlayerCharacter": "You have no assigned character.",
|
||||||
|
|
@ -2465,7 +2538,8 @@
|
||||||
"multiclassAlreadyPresent": "You already have a class and multiclass",
|
"multiclassAlreadyPresent": "You already have a class and multiclass",
|
||||||
"subclassesAlreadyPresent": "You already have a class and multiclass subclass",
|
"subclassesAlreadyPresent": "You already have a class and multiclass subclass",
|
||||||
"noDiceSystem": "Your selected dice {system} does not have a {faces} dice",
|
"noDiceSystem": "Your selected dice {system} does not have a {faces} dice",
|
||||||
"gmMenuRefresh": "You refreshed all actions and resources {types}"
|
"gmMenuRefresh": "You refreshed all actions and resources {types}",
|
||||||
|
"subclassAlreadyLinked": "{name} is already a subclass in the class {class}. Remove it from there if you want it to be a subclass to this class."
|
||||||
},
|
},
|
||||||
"Sidebar": {
|
"Sidebar": {
|
||||||
"daggerheartMenu": {
|
"daggerheartMenu": {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { abilities } from '../../config/actorConfig.mjs';
|
import { abilities } from '../../config/actorConfig.mjs';
|
||||||
import { burden } from '../../config/generalConfig.mjs';
|
import { burden } from '../../config/generalConfig.mjs';
|
||||||
import { ItemBrowser } from '../ui/itemBrowser.mjs';
|
|
||||||
import { createEmbeddedItemsWithEffects, createEmbeddedItemWithEffects } from '../../helpers/utils.mjs';
|
import { createEmbeddedItemsWithEffects, createEmbeddedItemWithEffects } from '../../helpers/utils.mjs';
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
@ -46,8 +45,6 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
||||||
};
|
};
|
||||||
|
|
||||||
this._dragDrop = this._createDragDropHandlers();
|
this._dragDrop = this._createDragDropHandlers();
|
||||||
|
|
||||||
this.itemBrowser = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get title() {
|
get title() {
|
||||||
|
|
@ -425,26 +422,30 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
||||||
equipment = ['armor', 'weapon'];
|
equipment = ['armor', 'weapon'];
|
||||||
|
|
||||||
const presets = {
|
const presets = {
|
||||||
compendium: 'daggerheart',
|
folder: equipment.includes(type) ? `equipments.folders.${type}s` : type,
|
||||||
folder: equipment.includes(type) ? 'equipments' : type,
|
|
||||||
render: {
|
render: {
|
||||||
noFolder: true
|
noFolder: true
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (type == 'domains')
|
if (type === 'domains')
|
||||||
presets.filter = {
|
presets.filter = {
|
||||||
'level.max': { key: 'level.max', value: 1 },
|
'level.max': { key: 'level.max', value: 1 },
|
||||||
'system.domain': { key: 'system.domain', value: this.setup.class?.system.domains ?? null }
|
'system.domain': { key: 'system.domain', value: this.setup.class?.system.domains ?? null }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (type === 'subclasses')
|
||||||
|
presets.filter = {
|
||||||
|
'system.linkedClass.uuid': { key: 'system.linkedClass.uuid', value: this.setup.class?.uuid }
|
||||||
|
};
|
||||||
|
|
||||||
if (equipment.includes(type))
|
if (equipment.includes(type))
|
||||||
presets.filter = {
|
presets.filter = {
|
||||||
'system.tier': { key: 'system.tier', value: 1 },
|
'system.tier': { key: 'system.tier', value: 1 },
|
||||||
'type': { key: 'type', value: type }
|
'type': { key: 'type', value: type }
|
||||||
};
|
};
|
||||||
|
|
||||||
return (this.itemBrowser = await new ItemBrowser({ presets }).render({ force: true }));
|
ui.compendiumBrowser.open(presets);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async viewItem(_, target) {
|
static async viewItem(_, target) {
|
||||||
|
|
@ -562,7 +563,7 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
||||||
{ overwrite: true }
|
{ overwrite: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
if (this.itemBrowser) this.itemBrowser.close();
|
if (ui.compendiumBrowser) ui.compendiumBrowser.close();
|
||||||
this.close();
|
this.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { abilities } from '../../config/actorConfig.mjs';
|
||||||
|
|
||||||
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||||
|
|
||||||
export default class D20RollDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
export default class D20RollDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
|
|
@ -7,7 +9,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
this.roll = roll;
|
this.roll = roll;
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.config.experiences = [];
|
this.config.experiences = [];
|
||||||
this.reactionOverride = config.roll?.type === 'reaction';
|
this.reactionOverride = config.actionType === 'reaction';
|
||||||
|
|
||||||
if (config.source?.action) {
|
if (config.source?.action) {
|
||||||
this.item = config.data.parent.items.get(config.source.item) ?? config.data.parent;
|
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 = {
|
static DEFAULT_OPTIONS = {
|
||||||
tag: 'form',
|
tag: 'form',
|
||||||
id: 'roll-selection',
|
// id: 'roll-selection',
|
||||||
classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'roll-selection'],
|
classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'roll-selection'],
|
||||||
position: {
|
position: {
|
||||||
width: 'auto'
|
width: 'auto'
|
||||||
|
|
@ -42,7 +44,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
};
|
};
|
||||||
|
|
||||||
get title() {
|
get title() {
|
||||||
return this.config.title;
|
return `${this.config.title}${this.actor ? `: ${this.actor.name}` : ''}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
get actor() {
|
get actor() {
|
||||||
|
|
@ -81,7 +83,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
);
|
);
|
||||||
context.costs = updatedCosts.map(x => ({
|
context.costs = updatedCosts.map(x => ({
|
||||||
...x,
|
...x,
|
||||||
label: x.keyIsID
|
label: x.itemId
|
||||||
? this.action.parent.parent.name
|
? this.action.parent.parent.name
|
||||||
: game.i18n.localize(CONFIG.DH.GENERAL.abilityCosts[x.key].label)
|
: game.i18n.localize(CONFIG.DH.GENERAL.abilityCosts[x.key].label)
|
||||||
}));
|
}));
|
||||||
|
|
@ -113,15 +115,24 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
context.isLite = this.config.roll?.lite;
|
context.isLite = this.config.roll?.lite;
|
||||||
context.extraFormula = this.config.extraFormula;
|
context.extraFormula = this.config.extraFormula;
|
||||||
context.formula = this.roll.constructFormula(this.config);
|
context.formula = this.roll.constructFormula(this.config);
|
||||||
|
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;
|
context.reactionOverride = this.reactionOverride;
|
||||||
}
|
}
|
||||||
return context;
|
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) {
|
static updateRollConfiguration(event, _, formData) {
|
||||||
const { ...rest } = foundry.utils.expandObject(formData.object);
|
const { ...rest } = foundry.utils.expandObject(formData.object);
|
||||||
|
|
||||||
this.config.selectedRollMode = rest.selectedRollMode;
|
this.config.selectedRollMode = rest.selectedRollMode;
|
||||||
|
|
||||||
if (this.config.costs) {
|
if (this.config.costs) {
|
||||||
|
|
@ -133,6 +144,12 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
this.roll[key] = value;
|
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.config.extraFormula = rest.extraFormula;
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
@ -151,31 +168,29 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
this.config.experiences.indexOf(button.dataset.key) > -1
|
this.config.experiences.indexOf(button.dataset.key) > -1
|
||||||
? this.config.experiences.filter(x => x !== button.dataset.key)
|
? this.config.experiences.filter(x => x !== button.dataset.key)
|
||||||
: [...this.config.experiences, 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 =
|
this.config.costs.indexOf(this.config.costs.find(c => c.extKey === button.dataset.key)) > -1
|
||||||
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.filter(x => x.extKey !== button.dataset.key)
|
: [
|
||||||
: [
|
...this.config.costs,
|
||||||
...this.config.costs,
|
{
|
||||||
{
|
extKey: button.dataset.key,
|
||||||
extKey: button.dataset.key,
|
key: this.config?.data?.parent?.isNPC ? 'fear' : 'hope',
|
||||||
key: 'hope',
|
value: 1,
|
||||||
value: 1,
|
name: this.config.data?.experiences?.[button.dataset.key]?.name
|
||||||
name: this.config.data?.experiences?.[button.dataset.key]?.name
|
}
|
||||||
}
|
];
|
||||||
];
|
|
||||||
}
|
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
static toggleReaction() {
|
static toggleReaction() {
|
||||||
if (this.config.roll) {
|
if (this.config.roll) {
|
||||||
this.reactionOverride = !this.reactionOverride;
|
this.reactionOverride = !this.reactionOverride;
|
||||||
this.config.roll.type = this.reactionOverride
|
this.config.actionType = this.reactionOverride
|
||||||
? CONFIG.DH.ITEM.actionTypes.reaction.id
|
? 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
|
? null
|
||||||
: this.config.roll.type;
|
: this.config.actionType;
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
classes: ['daggerheart']
|
classes: ['daggerheart'],
|
||||||
|
actions: {
|
||||||
|
combat: DHTokenHUD.#onToggleCombat
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
|
|
@ -11,8 +14,14 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static #nonCombatTypes = ['environment', 'companion'];
|
||||||
|
|
||||||
async _prepareContext(options) {
|
async _prepareContext(options) {
|
||||||
const context = await super._prepareContext(options);
|
const context = await super._prepareContext(options);
|
||||||
|
|
||||||
|
context.canToggleCombat = DHTokenHUD.#nonCombatTypes.includes(this.actor.type)
|
||||||
|
? false
|
||||||
|
: context.canToggleCombat;
|
||||||
context.systemStatusEffects = Object.keys(context.statusEffects).reduce((acc, key) => {
|
context.systemStatusEffects = Object.keys(context.statusEffects).reduce((acc, key) => {
|
||||||
const effect = context.statusEffects[key];
|
const effect = context.statusEffects[key];
|
||||||
if (effect.systemEffect) acc[key] = effect;
|
if (effect.systemEffect) acc[key] = effect;
|
||||||
|
|
@ -36,6 +45,20 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async #onToggleCombat() {
|
||||||
|
const tokens = canvas.tokens.controlled
|
||||||
|
.filter(t => !t.actor || !DHTokenHUD.#nonCombatTypes.includes(t.actor.type))
|
||||||
|
.map(t => t.document);
|
||||||
|
if (!this.object.controlled) tokens.push(this.document);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (this.document.inCombat) await TokenDocument.implementation.deleteCombatants(tokens);
|
||||||
|
else await TokenDocument.implementation.createCombatants(tokens);
|
||||||
|
} catch (err) {
|
||||||
|
ui.notifications.warn(err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_getStatusEffectChoices() {
|
_getStatusEffectChoices() {
|
||||||
// Include all HUD-enabled status effects
|
// Include all HUD-enabled status effects
|
||||||
const choices = {};
|
const choices = {};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { abilities, subclassFeatureLabels } from '../../config/actorConfig.mjs';
|
import { abilities, subclassFeatureLabels } from '../../config/actorConfig.mjs';
|
||||||
import { getDeleteKeys, tagifyElement } from '../../helpers/utils.mjs';
|
import { getDeleteKeys, tagifyElement } from '../../helpers/utils.mjs';
|
||||||
import { ItemBrowser } from '../ui/itemBrowser.mjs';
|
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
||||||
|
|
@ -12,8 +11,6 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
|
|
||||||
this._dragDrop = this._createDragDropHandlers();
|
this._dragDrop = this._createDragDropHandlers();
|
||||||
this.tabGroups.primary = 'advancements';
|
this.tabGroups.primary = 'advancements';
|
||||||
|
|
||||||
this.itemBrowser = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get title() {
|
get title() {
|
||||||
|
|
@ -540,7 +537,6 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
const type = target.dataset.compendium ?? target.dataset.type;
|
const type = target.dataset.compendium ?? target.dataset.type;
|
||||||
|
|
||||||
const presets = {
|
const presets = {
|
||||||
compendium: 'daggerheart',
|
|
||||||
folder: type,
|
folder: type,
|
||||||
render: {
|
render: {
|
||||||
noFolder: true
|
noFolder: true
|
||||||
|
|
@ -559,7 +555,7 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return (this.itemBrowser = await new ItemBrowser({ presets }).render({ force: true }));
|
ui.compendiumBrowser.open(presets);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async selectPreview(_, button) {
|
static async selectPreview(_, button) {
|
||||||
|
|
@ -662,7 +658,8 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
await this.actor.levelUp(levelupData);
|
await this.actor.levelUp(levelupData);
|
||||||
if (this.itemBrowser) this.itemBrowser.close();
|
|
||||||
|
if (ui.compendiumBrowser) ui.compendiumBrowser.close();
|
||||||
this.close();
|
this.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,43 +3,48 @@ import { getDiceSoNicePreset } from '../../config/generalConfig.mjs';
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @import {ApplicationClickAction} from "@client/applications/_types.mjs"
|
||||||
|
*/
|
||||||
|
|
||||||
export default class DHAppearanceSettings extends HandlebarsApplicationMixin(ApplicationV2) {
|
export default class DHAppearanceSettings extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
constructor() {
|
/**@inheritdoc */
|
||||||
super({});
|
|
||||||
|
|
||||||
this.settings = new DhAppearance(
|
|
||||||
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance).toObject()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
get title() {
|
|
||||||
return game.i18n.localize('DAGGERHEART.SETTINGS.Menu.title');
|
|
||||||
}
|
|
||||||
|
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
tag: 'form',
|
tag: 'form',
|
||||||
id: 'daggerheart-appearance-settings',
|
id: 'daggerheart-appearance-settings',
|
||||||
classes: ['daggerheart', 'dialog', 'dh-style', 'setting'],
|
classes: ['daggerheart', 'dialog', 'dh-style', 'setting'],
|
||||||
position: { width: '600', height: 'auto' },
|
position: { width: '600', height: 'auto' },
|
||||||
window: {
|
window: {
|
||||||
|
title: 'DAGGERHEART.SETTINGS.Menu.title',
|
||||||
icon: 'fa-solid fa-gears'
|
icon: 'fa-solid fa-gears'
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
reset: this.reset,
|
reset: DHAppearanceSettings.#onReset,
|
||||||
save: this.save,
|
preview: DHAppearanceSettings.#onPreview
|
||||||
preview: this.preview
|
|
||||||
},
|
},
|
||||||
form: { handler: this.updateData, submitOnChange: true }
|
form: {
|
||||||
|
closeOnSubmit: true,
|
||||||
|
handler: DHAppearanceSettings.#onSubmit
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static PARTS = {
|
static PARTS = {
|
||||||
main: {
|
header: { template: 'systems/daggerheart/templates/settings/appearance-settings/header.hbs' },
|
||||||
template: 'systems/daggerheart/templates/settings/appearance-settings.hbs'
|
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
|
||||||
}
|
main: { template: 'systems/daggerheart/templates/settings/appearance-settings/main.hbs' },
|
||||||
|
diceSoNice: { template: 'systems/daggerheart/templates/settings/appearance-settings/diceSoNice.hbs' },
|
||||||
|
footer: { template: 'templates/generic/form-footer.hbs' }
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
static TABS = {
|
static TABS = {
|
||||||
|
general: {
|
||||||
|
tabs: [
|
||||||
|
{ id: 'main', label: 'DAGGERHEART.GENERAL.Tabs.general' },
|
||||||
|
{ id: 'diceSoNice', label: 'DAGGERHEART.SETTINGS.Menu.appearance.diceSoNice.title' }
|
||||||
|
],
|
||||||
|
initial: 'main'
|
||||||
|
},
|
||||||
diceSoNice: {
|
diceSoNice: {
|
||||||
tabs: [
|
tabs: [
|
||||||
{ id: 'hope', label: 'DAGGERHEART.GENERAL.hope' },
|
{ id: 'hope', label: 'DAGGERHEART.GENERAL.hope' },
|
||||||
|
|
@ -51,79 +56,149 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
changeTab(tab, group, options) {
|
/**@type {DhAppearance}*/
|
||||||
super.changeTab(tab, group, options);
|
setting;
|
||||||
|
|
||||||
this.render();
|
static #localized = false;
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
async _preFirstRender(_context, _options) {
|
||||||
|
await super._preFirstRender(_context, _options);
|
||||||
|
if (!DHAppearanceSettings.#localized) {
|
||||||
|
foundry.helpers.Localization.localizeDataModel(this.setting.constructor);
|
||||||
|
DHAppearanceSettings.#localized = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _prepareContext(_options) {
|
/** @inheritdoc */
|
||||||
const context = await super._prepareContext(_options);
|
_configureRenderParts(options) {
|
||||||
context.settingFields = this.settings;
|
const parts = super._configureRenderParts(options);
|
||||||
|
if (!game.modules.get('dice-so-nice')?.active) {
|
||||||
context.showDiceSoNice = game.modules.get('dice-so-nice')?.active;
|
delete parts.diceSoNice;
|
||||||
if (game.dice3d) {
|
delete parts.tabs;
|
||||||
context.diceSoNiceTextures = game.dice3d.exports.TEXTURELIST;
|
|
||||||
context.diceSoNiceColorsets = game.dice3d.exports.COLORSETS;
|
|
||||||
context.diceSoNiceMaterials = Object.keys(game.dice3d.DiceFactory.material_options).map(key => ({
|
|
||||||
key: key,
|
|
||||||
name: `DICESONICE.Material${key.capitalize()}`
|
|
||||||
}));
|
|
||||||
context.diceSoNiceSystems = [];
|
|
||||||
for (const [key, system] of game.dice3d.DiceFactory.systems.entries()) {
|
|
||||||
context.diceSoNiceSystems.push({ key, name: system.name });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return parts;
|
||||||
|
}
|
||||||
|
|
||||||
context.diceTab = {
|
/**@inheritdoc */
|
||||||
key: this.tabGroups.diceSoNice,
|
async _prepareContext(options) {
|
||||||
source: this.settings._source.diceSoNice[this.tabGroups.diceSoNice],
|
const context = await super._prepareContext(options);
|
||||||
fields: this.settings.schema.fields.diceSoNice.fields[this.tabGroups.diceSoNice].fields
|
if (options.isFirstRender)
|
||||||
};
|
this.setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance);
|
||||||
|
|
||||||
|
context.setting = this.setting;
|
||||||
|
context.fields = this.setting.schema.fields;
|
||||||
|
|
||||||
|
context.tabs = this._prepareTabs('general');
|
||||||
|
context.dsnTabs = this._prepareTabs('diceSoNice');
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async updateData(event, element, formData) {
|
/**@inheritdoc */
|
||||||
const updatedSettings = foundry.utils.expandObject(formData.object);
|
async _preparePartContext(partId, context, options) {
|
||||||
|
const partContext = await super._preparePartContext(partId, context, options);
|
||||||
await this.settings.updateSource(updatedSettings);
|
if (partId in context.tabs) partContext.tab = partContext.tabs[partId];
|
||||||
this.render();
|
switch (partId) {
|
||||||
|
case 'diceSoNice':
|
||||||
|
await this.prepareDiceSoNiceContext(partContext);
|
||||||
|
break;
|
||||||
|
case 'footer':
|
||||||
|
partContext.buttons = [
|
||||||
|
{ type: 'button', action: 'reset', icon: 'fa-solid fa-arrow-rotate-left', label: 'Reset' },
|
||||||
|
{ type: 'submit', icon: 'fa-solid fa-floppy-disk', label: 'Save Changes' }
|
||||||
|
];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return partContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async preview() {
|
/**
|
||||||
const source = this.settings._source.diceSoNice[this.tabGroups.diceSoNice];
|
* Prepare render context for the DSN part.
|
||||||
let faces = 'd12';
|
* @param {ApplicationRenderContext} context
|
||||||
switch (this.tabGroups.diceSoNice) {
|
* @returns {Promise<void>}
|
||||||
case 'advantage':
|
* @protected
|
||||||
case 'disadvantage':
|
*/
|
||||||
faces = 'd6';
|
async prepareDiceSoNiceContext(context) {
|
||||||
}
|
context.diceSoNiceTextures = Object.entries(game.dice3d.exports.TEXTURELIST).reduce(
|
||||||
const preset = await getDiceSoNicePreset(source, faces);
|
(acc, [k, v]) => ({
|
||||||
const diceSoNiceRoll = await new Roll(`1${faces}`).evaluate();
|
...acc,
|
||||||
|
[k]: v.name
|
||||||
|
}),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
context.diceSoNiceColorsets = Object.values(game.dice3d.exports.COLORSETS).reduce(
|
||||||
|
(acc, v) => ({
|
||||||
|
...acc,
|
||||||
|
[v.id]: v.description
|
||||||
|
}),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
context.diceSoNiceMaterials = Object.keys(game.dice3d.DiceFactory.material_options).reduce(
|
||||||
|
(acc, key) => ({
|
||||||
|
...acc,
|
||||||
|
[key]: `DICESONICE.Material${key.capitalize()}`
|
||||||
|
}),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
context.diceSoNiceSystems = Object.fromEntries(
|
||||||
|
[...game.dice3d.DiceFactory.systems].map(([k, v]) => [k, v.name])
|
||||||
|
);
|
||||||
|
|
||||||
|
foundry.utils.mergeObject(
|
||||||
|
context.dsnTabs,
|
||||||
|
['hope', 'fear', 'advantage', 'disadvantage'].reduce(
|
||||||
|
(acc, key) => ({
|
||||||
|
...acc,
|
||||||
|
[key]: {
|
||||||
|
values: this.setting.diceSoNice[key],
|
||||||
|
fields: this.setting.schema.getField(`diceSoNice.${key}`).fields
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit the configuration form.
|
||||||
|
* @this {DHAppearanceSettings}
|
||||||
|
* @param {SubmitEvent} event
|
||||||
|
* @param {HTMLFormElement} form
|
||||||
|
* @param {foundry.applications.ux.FormDataExtended} formData
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
static async #onSubmit(event, form, formData) {
|
||||||
|
const data = this.setting.schema.clean(foundry.utils.expandObject(formData.object));
|
||||||
|
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit the configuration form.
|
||||||
|
* @this {DHAppearanceSettings}
|
||||||
|
* @type {ApplicationClickAction}
|
||||||
|
*/
|
||||||
|
static async #onPreview(_, target) {
|
||||||
|
const formData = new foundry.applications.ux.FormDataExtended(target.closest('form'));
|
||||||
|
const { diceSoNice } = foundry.utils.expandObject(formData.object);
|
||||||
|
const { key } = target.dataset;
|
||||||
|
const faces = ['advantage', 'disadvantage'].includes(key) ? 'd6' : 'd12';
|
||||||
|
const preset = await getDiceSoNicePreset(diceSoNice[key], faces);
|
||||||
|
const diceSoNiceRoll = await new foundry.dice.Roll(`1${faces}`).evaluate();
|
||||||
diceSoNiceRoll.dice[0].options.appearance = preset.appearance;
|
diceSoNiceRoll.dice[0].options.appearance = preset.appearance;
|
||||||
diceSoNiceRoll.dice[0].options.modelFile = preset.modelFile;
|
diceSoNiceRoll.dice[0].options.modelFile = preset.modelFile;
|
||||||
|
|
||||||
await game.dice3d.showForRoll(diceSoNiceRoll, game.user, false);
|
await game.dice3d.showForRoll(diceSoNiceRoll, game.user, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async reset() {
|
/**
|
||||||
this.settings = new DhAppearance();
|
* Reset the form back to default values.
|
||||||
this.render();
|
* @this {DHAppearanceSettings}
|
||||||
}
|
* @type {ApplicationClickAction}
|
||||||
|
*/
|
||||||
static async save() {
|
static async #onReset() {
|
||||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance, this.settings.toObject());
|
this.setting = new this.setting.constructor();
|
||||||
|
this.render({ force: false });
|
||||||
this.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
_getTabs(tabs) {
|
|
||||||
for (const v of Object.values(tabs)) {
|
|
||||||
v.active = this.tabGroups[v.group] ? this.tabGroups[v.group] === v.id : v.active;
|
|
||||||
v.cssClass = v.active ? 'active' : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
return tabs;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,13 +35,14 @@ export default class DhAutomationSettings extends HandlebarsApplicationMixin(App
|
||||||
header: { template: 'systems/daggerheart/templates/settings/automation-settings/header.hbs' },
|
header: { template: 'systems/daggerheart/templates/settings/automation-settings/header.hbs' },
|
||||||
general: { template: 'systems/daggerheart/templates/settings/automation-settings/general.hbs' },
|
general: { template: 'systems/daggerheart/templates/settings/automation-settings/general.hbs' },
|
||||||
rules: { template: 'systems/daggerheart/templates/settings/automation-settings/rules.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' }
|
footer: { template: 'systems/daggerheart/templates/settings/automation-settings/footer.hbs' }
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
static TABS = {
|
static TABS = {
|
||||||
main: {
|
main: {
|
||||||
tabs: [{ id: 'general' }, { id: 'rules' }],
|
tabs: [{ id: 'general' }, { id: 'rules' }, { id: 'roll' }],
|
||||||
initial: 'general',
|
initial: 'general',
|
||||||
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -132,12 +132,19 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
|
||||||
const options = foundry.utils.deepClone(CONFIG.DH.GENERAL.abilityCosts);
|
const options = foundry.utils.deepClone(CONFIG.DH.GENERAL.abilityCosts);
|
||||||
const resource = this.action.parent.resource;
|
const resource = this.action.parent.resource;
|
||||||
if (resource) {
|
if (resource) {
|
||||||
options[this.action.parent.parent.id] = {
|
options.resource = {
|
||||||
label: 'DAGGERHEART.GENERAL.itemResource',
|
label: 'DAGGERHEART.GENERAL.itemResource',
|
||||||
group: 'Global'
|
group: 'Global'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.action.parent.metadata.isQuantifiable) {
|
||||||
|
options.quantity = {
|
||||||
|
label: 'DAGGERHEART.GENERAL.itemQuantity',
|
||||||
|
group: 'Global'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -164,13 +171,14 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
|
||||||
|
|
||||||
_prepareSubmitData(_event, formData) {
|
_prepareSubmitData(_event, formData) {
|
||||||
const submitData = foundry.utils.expandObject(formData.object);
|
const submitData = foundry.utils.expandObject(formData.object);
|
||||||
|
|
||||||
|
const itemAbilityCostKeys = Object.keys(CONFIG.DH.GENERAL.itemAbilityCosts);
|
||||||
for (const keyPath of this.constructor.CLEAN_ARRAYS) {
|
for (const keyPath of this.constructor.CLEAN_ARRAYS) {
|
||||||
const data = foundry.utils.getProperty(submitData, keyPath);
|
const data = foundry.utils.getProperty(submitData, keyPath);
|
||||||
const dataValues = data ? Object.values(data) : [];
|
const dataValues = data ? Object.values(data) : [];
|
||||||
if (keyPath === 'cost') {
|
if (keyPath === 'cost') {
|
||||||
for (var value of dataValues) {
|
for (var value of dataValues) {
|
||||||
const item = this.action.parent.parent.id === value.key;
|
value.itemId = itemAbilityCostKeys.includes(value.key) ? this.action.parent.parent.id : null;
|
||||||
value.keyIsID = Boolean(item);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -146,9 +146,9 @@ export default class AdversarySheet extends DHBaseActorSheet {
|
||||||
title: `Reaction Roll: ${this.actor.name}`,
|
title: `Reaction Roll: ${this.actor.name}`,
|
||||||
headerTitle: 'Adversary Reaction Roll',
|
headerTitle: 'Adversary Reaction Roll',
|
||||||
roll: {
|
roll: {
|
||||||
type: 'reaction'
|
type: 'trait'
|
||||||
},
|
},
|
||||||
type: 'trait',
|
actionType: 'reaction',
|
||||||
hasRoll: true,
|
hasRoll: true,
|
||||||
data: this.actor.getRollData()
|
data: this.actor.getRollData()
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import { CharacterLevelup, LevelupViewMode } from '../../levelup/_module.mjs';
|
||||||
import DhCharacterCreation from '../../characterCreation/characterCreation.mjs';
|
import DhCharacterCreation from '../../characterCreation/characterCreation.mjs';
|
||||||
import FilterMenu from '../../ux/filter-menu.mjs';
|
import FilterMenu from '../../ux/filter-menu.mjs';
|
||||||
import { getDocFromElement, getDocFromElementSync } from '../../../helpers/utils.mjs';
|
import { getDocFromElement, getDocFromElementSync } from '../../../helpers/utils.mjs';
|
||||||
import { ItemBrowser } from '../../ui/itemBrowser.mjs';
|
|
||||||
|
|
||||||
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
|
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
|
||||||
|
|
||||||
|
|
@ -15,6 +14,8 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
classes: ['character'],
|
classes: ['character'],
|
||||||
position: { width: 850, height: 800 },
|
position: { width: 850, height: 800 },
|
||||||
|
/* Foundry adds disabled to all buttons and inputs if editPermission is missing. This is not desired. */
|
||||||
|
editPermission: CONST.DOCUMENT_OWNERSHIP_LEVELS.OBSERVER,
|
||||||
actions: {
|
actions: {
|
||||||
toggleVault: CharacterSheet.#toggleVault,
|
toggleVault: CharacterSheet.#toggleVault,
|
||||||
rollAttribute: CharacterSheet.#rollAttribute,
|
rollAttribute: CharacterSheet.#rollAttribute,
|
||||||
|
|
@ -27,8 +28,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
toggleEquipItem: CharacterSheet.#toggleEquipItem,
|
toggleEquipItem: CharacterSheet.#toggleEquipItem,
|
||||||
toggleResourceDice: CharacterSheet.#toggleResourceDice,
|
toggleResourceDice: CharacterSheet.#toggleResourceDice,
|
||||||
handleResourceDice: CharacterSheet.#handleResourceDice,
|
handleResourceDice: CharacterSheet.#handleResourceDice,
|
||||||
useDowntime: this.useDowntime,
|
useDowntime: this.useDowntime
|
||||||
tempBrowser: CharacterSheet.#tempBrowser
|
|
||||||
},
|
},
|
||||||
window: {
|
window: {
|
||||||
resizable: true,
|
resizable: true,
|
||||||
|
|
@ -132,6 +132,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
});
|
});
|
||||||
htmlElement.querySelectorAll('.inventory-item-quantity').forEach(element => {
|
htmlElement.querySelectorAll('.inventory-item-quantity').forEach(element => {
|
||||||
element.addEventListener('change', this.updateItemQuantity.bind(this));
|
element.addEventListener('change', this.updateItemQuantity.bind(this));
|
||||||
|
element.addEventListener('click', e => e.stopPropagation());
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add listener for armor marks input
|
// Add listener for armor marks input
|
||||||
|
|
@ -148,6 +149,13 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
.querySelector('.level-value')
|
.querySelector('.level-value')
|
||||||
?.addEventListener('change', event => this.document.updateLevel(Number(event.currentTarget.value)));
|
?.addEventListener('change', event => this.document.updateLevel(Number(event.currentTarget.value)));
|
||||||
|
|
||||||
|
const observer = this.document.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OBSERVER, {
|
||||||
|
exact: true
|
||||||
|
});
|
||||||
|
if (observer) {
|
||||||
|
this.element.querySelector('.window-content').classList.add('viewMode');
|
||||||
|
}
|
||||||
|
|
||||||
this._createFilterMenus();
|
this._createFilterMenus();
|
||||||
this._createSearchFilter();
|
this._createSearchFilter();
|
||||||
}
|
}
|
||||||
|
|
@ -626,14 +634,22 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
const { key } = button.dataset;
|
const { key } = button.dataset;
|
||||||
|
|
||||||
const presets = {
|
const presets = {
|
||||||
compendium: 'daggerheart',
|
|
||||||
folder: key,
|
folder: key,
|
||||||
|
filter:
|
||||||
|
key === 'subclasses'
|
||||||
|
? {
|
||||||
|
'system.linkedClass.uuid': {
|
||||||
|
key: 'system.linkedClass.uuid',
|
||||||
|
value: this.document.system.class.value._stats.compendiumSource
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
render: {
|
render: {
|
||||||
noFolder: true
|
noFolder: true
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return new ItemBrowser({ presets }).render({ force: true });
|
ui.compendiumBrowser.open(presets);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -661,31 +677,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
this.consumeResource(result?.costs);
|
if (result) game.system.api.fields.ActionFields.CostField.execute.call(this, result);
|
||||||
}
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: redo toggleEquipItem method
|
//TODO: redo toggleEquipItem method
|
||||||
|
|
@ -774,13 +766,6 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Temp
|
|
||||||
*/
|
|
||||||
static async #tempBrowser(_, target) {
|
|
||||||
new ItemBrowser().render({ force: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle the roll values of resource dice.
|
* Handle the roll values of resource dice.
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ export default class DhCompanionSheet extends DHBaseActorSheet {
|
||||||
classes: ['actor', 'companion'],
|
classes: ['actor', 'companion'],
|
||||||
position: { width: 340 },
|
position: { width: 340 },
|
||||||
actions: {
|
actions: {
|
||||||
|
actionRoll: DhCompanionSheet.#actionRoll,
|
||||||
levelManagement: DhCompanionSheet.#levelManagement
|
levelManagement: DhCompanionSheet.#levelManagement
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -45,6 +46,51 @@ export default class DhCompanionSheet extends DHBaseActorSheet {
|
||||||
/* Application Clicks Actions */
|
/* Application Clicks Actions */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static async #actionRoll(event) {
|
||||||
|
const partner = this.actor.system.partner;
|
||||||
|
const config = {
|
||||||
|
event,
|
||||||
|
title: `${game.i18n.localize('DAGGERHEART.GENERAL.Roll.action')}: ${this.actor.name}`,
|
||||||
|
headerTitle: `Companion ${game.i18n.localize('DAGGERHEART.GENERAL.Roll.action')}`,
|
||||||
|
roll: {
|
||||||
|
trait: partner.system.spellcastModifierTrait?.key
|
||||||
|
},
|
||||||
|
hasRoll: true,
|
||||||
|
data: partner.getRollData()
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await partner.diceRoll(config);
|
||||||
|
this.consumeResource(result?.costs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove when Action Refactor part #2 done
|
||||||
|
async consumeResource(costs) {
|
||||||
|
if (!costs?.length) return;
|
||||||
|
|
||||||
|
const partner = this.actor.system.partner;
|
||||||
|
const usefulResources = {
|
||||||
|
...foundry.utils.deepClone(partner.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
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
await partner.modifyResource(resources);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens the companions level management window.
|
* Opens the companions level management window.
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
const { HandlebarsApplicationMixin } = foundry.applications.api;
|
const { HandlebarsApplicationMixin } = foundry.applications.api;
|
||||||
import { getDocFromElement, getDocFromElementSync, tagifyElement } from '../../../helpers/utils.mjs';
|
import { getDocFromElement, getDocFromElementSync, tagifyElement } from '../../../helpers/utils.mjs';
|
||||||
import { ItemBrowser } from '../../ui/itemBrowser.mjs';
|
|
||||||
|
|
||||||
const typeSettingsMap = {
|
const typeSettingsMap = {
|
||||||
character: 'extendCharacterDescriptions',
|
character: 'extendCharacterDescriptions',
|
||||||
|
|
@ -412,6 +411,19 @@ export default function DHApplicationMixin(Base) {
|
||||||
];
|
];
|
||||||
|
|
||||||
if (usable) {
|
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({
|
options.unshift({
|
||||||
name: 'DAGGERHEART.GENERAL.damage',
|
name: 'DAGGERHEART.GENERAL.damage',
|
||||||
icon: 'fa-solid fa-explosion',
|
icon: 'fa-solid fa-explosion',
|
||||||
|
|
@ -422,7 +434,9 @@ export default function DHApplicationMixin(Base) {
|
||||||
callback: async (target, event) => {
|
callback: async (target, event) => {
|
||||||
const doc = await getDocFromElement(target),
|
const doc = await getDocFromElement(target),
|
||||||
action = doc?.system?.attack ?? doc;
|
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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -577,28 +591,27 @@ export default function DHApplicationMixin(Base) {
|
||||||
static async #browseItem(event, target) {
|
static async #browseItem(event, target) {
|
||||||
const type = target.dataset.compendium ?? target.dataset.type;
|
const type = target.dataset.compendium ?? target.dataset.type;
|
||||||
|
|
||||||
const presets = {};
|
const presets = {
|
||||||
|
render: {
|
||||||
|
noFolder: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'loot':
|
case 'loot':
|
||||||
|
presets.folder = 'equipments.folders.loots';
|
||||||
|
break;
|
||||||
case 'consumable':
|
case 'consumable':
|
||||||
|
presets.folder = 'equipments.folders.consumables';
|
||||||
|
break;
|
||||||
case 'armor':
|
case 'armor':
|
||||||
|
presets.folder = 'equipments.folders.armors';
|
||||||
|
break;
|
||||||
case 'weapon':
|
case 'weapon':
|
||||||
presets.compendium = 'daggerheart';
|
presets.folder = 'equipments.folders.weapons';
|
||||||
presets.folder = 'equipments';
|
|
||||||
presets.render = {
|
|
||||||
noFolder: true
|
|
||||||
};
|
|
||||||
presets.filter = {
|
|
||||||
type: { key: 'type', value: type, forced: true }
|
|
||||||
};
|
|
||||||
break;
|
break;
|
||||||
case 'domainCard':
|
case 'domainCard':
|
||||||
presets.compendium = 'daggerheart';
|
|
||||||
presets.folder = 'domains';
|
presets.folder = 'domains';
|
||||||
presets.render = {
|
|
||||||
noFolder: true
|
|
||||||
};
|
|
||||||
presets.filter = {
|
presets.filter = {
|
||||||
'level.max': { key: 'level.max', value: this.document.system.levelData.level.current },
|
'level.max': { key: 'level.max', value: this.document.system.levelData.level.current },
|
||||||
'system.domain': { key: 'system.domain', value: this.document.system.domains }
|
'system.domain': { key: 'system.domain', value: this.document.system.domains }
|
||||||
|
|
@ -608,7 +621,7 @@ export default function DHApplicationMixin(Base) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ItemBrowser({ presets }).render({ force: true });
|
ui.compendiumBrowser.open(presets);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,10 @@ export default class ClassSheet extends DHBaseItemSheet {
|
||||||
template: 'systems/daggerheart/templates/sheets/items/class/settings.hbs',
|
template: 'systems/daggerheart/templates/sheets/items/class/settings.hbs',
|
||||||
scrollable: ['.settings']
|
scrollable: ['.settings']
|
||||||
},
|
},
|
||||||
|
questions: {
|
||||||
|
template: 'systems/daggerheart/templates/sheets/items/class/questions.hbs',
|
||||||
|
scrollable: ['.questions']
|
||||||
|
},
|
||||||
effects: {
|
effects: {
|
||||||
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs',
|
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs',
|
||||||
scrollable: ['.effects']
|
scrollable: ['.effects']
|
||||||
|
|
@ -55,7 +59,13 @@ export default class ClassSheet extends DHBaseItemSheet {
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
static TABS = {
|
static TABS = {
|
||||||
primary: {
|
primary: {
|
||||||
tabs: [{ id: 'description' }, { id: 'features' }, { id: 'settings' }, { id: 'effects' }],
|
tabs: [
|
||||||
|
{ id: 'description' },
|
||||||
|
{ id: 'features' },
|
||||||
|
{ id: 'settings' },
|
||||||
|
{ id: 'questions' },
|
||||||
|
{ id: 'effects' }
|
||||||
|
],
|
||||||
initial: 'description',
|
initial: 'description',
|
||||||
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
||||||
}
|
}
|
||||||
|
|
@ -119,6 +129,15 @@ export default class ClassSheet extends DHBaseItemSheet {
|
||||||
const itemType = data.data ? data.type : item.type;
|
const itemType = data.data ? data.type : item.type;
|
||||||
const target = event.target.closest('fieldset.drop-section');
|
const target = event.target.closest('fieldset.drop-section');
|
||||||
if (itemType === 'subclass') {
|
if (itemType === 'subclass') {
|
||||||
|
if (item.system.linkedClass) {
|
||||||
|
return ui.notifications.warn(
|
||||||
|
game.i18n.format('DAGGERHEART.UI.Notifications.subclassAlreadyLinked', {
|
||||||
|
name: item.name,
|
||||||
|
class: this.document.name
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await item.update({ 'system.linkedClass': this.document.uuid });
|
||||||
await this.document.update({
|
await this.document.update({
|
||||||
'system.subclasses': [...this.document.system.subclasses.map(x => x.uuid), item.uuid]
|
'system.subclasses': [...this.document.system.subclasses.map(x => x.uuid), item.uuid]
|
||||||
});
|
});
|
||||||
|
|
@ -181,6 +200,12 @@ export default class ClassSheet extends DHBaseItemSheet {
|
||||||
static async #removeItemFromCollection(_event, element) {
|
static async #removeItemFromCollection(_event, element) {
|
||||||
const { uuid, target } = element.dataset;
|
const { uuid, target } = element.dataset;
|
||||||
const prop = foundry.utils.getProperty(this.document.system, target);
|
const prop = foundry.utils.getProperty(this.document.system, target);
|
||||||
|
|
||||||
|
if (target === 'subclasses') {
|
||||||
|
const subclass = await foundry.utils.fromUuid(uuid);
|
||||||
|
await subclass.update({ 'system.linkedClass': null });
|
||||||
|
}
|
||||||
|
|
||||||
await this.document.update({ [`system.${target}`]: prop.filter(i => i.uuid !== uuid).map(x => x.uuid) });
|
await this.document.update({ [`system.${target}`]: prop.filter(i => i.uuid !== uuid).map(x => x.uuid) });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,3 +3,4 @@ export { default as DhCombatTracker } from './combatTracker.mjs';
|
||||||
export * as DhCountdowns from './countdowns.mjs';
|
export * as DhCountdowns from './countdowns.mjs';
|
||||||
export { default as DhFearTracker } from './fearTracker.mjs';
|
export { default as DhFearTracker } from './fearTracker.mjs';
|
||||||
export { default as DhHotbar } from './hotbar.mjs';
|
export { default as DhHotbar } from './hotbar.mjs';
|
||||||
|
export { ItemBrowser } from './itemBrowser.mjs';
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import { emitAsGM, GMUpdateEvent } from '../../systemRegistration/socket.mjs';
|
|
||||||
|
|
||||||
export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLog {
|
export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLog {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
@ -55,21 +53,9 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
}
|
}
|
||||||
|
|
||||||
addChatListeners = async (app, html, data) => {
|
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 =>
|
html.querySelectorAll('.simple-roll-button').forEach(element =>
|
||||||
element.addEventListener('click', event => this.onRollSimple(event, data.message))
|
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 =>
|
html.querySelectorAll('.ability-use-button').forEach(element =>
|
||||||
element.addEventListener('click', event => this.abilityUseButton(event, data.message))
|
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);
|
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) {
|
async onRollSimple(event, message) {
|
||||||
const buttonType = event.target.dataset.type ?? 'damage',
|
const buttonType = event.target.dataset.type ?? 'damage',
|
||||||
total = message.rolls.reduce((a, c) => a + Roll.fromJSON(c).total, 0),
|
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?.id === event.currentTarget.id
|
||||||
? item.system.attack
|
? item.system.attack
|
||||||
: item.system.actions.get(event.currentTarget.id);
|
: item.system.actions.get(event.currentTarget.id);
|
||||||
if (event.currentTarget.dataset.directDamage) action.use(event, { byPassRoll: true });
|
if (event.currentTarget.dataset.directDamage) {
|
||||||
else action.use(event);
|
const config = action.prepareConfig(event);
|
||||||
|
config.hasRoll = false;
|
||||||
|
action.workflow.get('damage').execute(config, null, true);
|
||||||
|
} else action.use(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
async actionUseButton(event, message) {
|
async actionUseButton(event, message) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { emitAsGM, GMUpdateEvent, socketEvent } from "../../systemRegistration/socket.mjs";
|
import { emitAsGM, GMUpdateEvent, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
||||||
|
|
@ -78,7 +78,7 @@ export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
async _preRender(context, options) {
|
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);
|
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear, this.maxFear);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -106,19 +106,10 @@ export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateFear(value) {
|
async updateFear(value) {
|
||||||
return emitAsGM(GMUpdateEvent.UpdateFear, game.settings.set.bind(game.settings, CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear), value);
|
return emitAsGM(
|
||||||
/* if(!game.user.isGM)
|
GMUpdateEvent.UpdateFear,
|
||||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
game.settings.set.bind(game.settings, CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear),
|
||||||
action: socketEvent.GMUpdate,
|
value
|
||||||
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); */
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,16 +15,13 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
this.fieldFilter = [];
|
this.fieldFilter = [];
|
||||||
this.selectedMenu = { path: [], data: null };
|
this.selectedMenu = { path: [], data: null };
|
||||||
this.config = CONFIG.DH.ITEMBROWSER.compendiumConfig;
|
this.config = CONFIG.DH.ITEMBROWSER.compendiumConfig;
|
||||||
this.presets = options.presets;
|
this.presets = {};
|
||||||
|
|
||||||
if (this.presets?.compendium && this.presets?.folder)
|
|
||||||
ItemBrowser.selectFolder.call(this, null, null, this.presets.compendium, this.presets.folder);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
id: 'itemBrowser',
|
id: 'itemBrowser',
|
||||||
classes: ['daggerheart', 'dh-style', 'dialog', 'compendium-browser'],
|
classes: ['daggerheart', 'dh-style', 'dialog', 'compendium-browser', 'loader'],
|
||||||
tag: 'div',
|
tag: 'div',
|
||||||
window: {
|
window: {
|
||||||
frame: true,
|
frame: true,
|
||||||
|
|
@ -84,17 +81,13 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @inheritDoc */
|
|
||||||
async _preFirstRender(context, options) {
|
|
||||||
if (context.presets?.render?.noFolder || context.presets?.render?.lite) options.position.width = 600;
|
|
||||||
|
|
||||||
await super._preFirstRender(context, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
async _preRender(context, options) {
|
async _preRender(context, options) {
|
||||||
if (context.presets?.render?.noFolder || context.presets?.render?.lite)
|
this.presets = options.presets ?? {};
|
||||||
options.parts.splice(options.parts.indexOf('sidebar'), 1);
|
|
||||||
|
const width = this.presets?.render?.noFolder === true || this.presets?.render?.lite === true ? 600 : 850;
|
||||||
|
if (this.rendered) this.setPosition({ width });
|
||||||
|
else options.position.width = width;
|
||||||
|
|
||||||
await super._preRender(context, options);
|
await super._preRender(context, options);
|
||||||
}
|
}
|
||||||
|
|
@ -103,22 +96,21 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
async _onRender(context, options) {
|
async _onRender(context, options) {
|
||||||
await super._onRender(context, options);
|
await super._onRender(context, options);
|
||||||
|
|
||||||
this._createSearchFilter();
|
this.element
|
||||||
this._createFilterInputs();
|
.querySelectorAll('[data-action="selectFolder"]')
|
||||||
this._createDragProcess();
|
.forEach(element =>
|
||||||
|
element.classList.toggle('is-selected', element.dataset.folderId === this.selectedMenu.path.join('.'))
|
||||||
if (context.presets?.render?.lite) this.element.classList.add('lite');
|
|
||||||
|
|
||||||
if (context.presets?.render?.noFolder) this.element.classList.add('no-folder');
|
|
||||||
|
|
||||||
if (context.presets?.render?.noFilter) this.element.classList.add('no-filter');
|
|
||||||
|
|
||||||
if (this.presets?.filter) {
|
|
||||||
Object.entries(this.presets.filter).forEach(
|
|
||||||
([k, v]) => (this.fieldFilter.find(c => c.name === k).value = v.value)
|
|
||||||
);
|
);
|
||||||
await this._onInputFilterBrowser();
|
|
||||||
}
|
this._createSearchFilter();
|
||||||
|
|
||||||
|
this.element.classList.toggle('lite', this.presets?.render?.lite === true);
|
||||||
|
this.element.classList.toggle('no-folder', this.presets?.render?.noFolder === true);
|
||||||
|
this.element.classList.toggle('no-filter', this.presets?.render?.noFilter === true);
|
||||||
|
this.element.querySelectorAll('.folder-list > [data-action="selectFolder"]').forEach(element => {
|
||||||
|
element.hidden =
|
||||||
|
this.presets.render?.folders?.length && !this.presets.render.folders.includes(element.dataset.folderId);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_attachPartListeners(partId, htmlElement, options) {
|
_attachPartListeners(partId, htmlElement, options) {
|
||||||
|
|
@ -139,19 +131,23 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
async _prepareContext(options) {
|
async _prepareContext(options) {
|
||||||
const context = await super._prepareContext(options);
|
const context = await super._prepareContext(options);
|
||||||
context.compendiums = this.getCompendiumFolders(foundry.utils.deepClone(this.config));
|
context.compendiums = this.getCompendiumFolders(foundry.utils.deepClone(this.config));
|
||||||
// context.pathTitle = this.pathTile;
|
|
||||||
context.menu = this.selectedMenu;
|
context.menu = this.selectedMenu;
|
||||||
context.formatLabel = this.formatLabel;
|
context.formatLabel = this.formatLabel;
|
||||||
context.formatChoices = this.formatChoices;
|
context.formatChoices = this.formatChoices;
|
||||||
context.fieldFilter = this.fieldFilter = this._createFieldFilter();
|
|
||||||
context.items = this.items;
|
context.items = this.items;
|
||||||
context.presets = this.presets;
|
context.presets = this.presets;
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open(presets = {}) {
|
||||||
|
this.presets = presets;
|
||||||
|
ItemBrowser.selectFolder.call(this);
|
||||||
|
}
|
||||||
|
|
||||||
getCompendiumFolders(config, parent = null, depth = 0) {
|
getCompendiumFolders(config, parent = null, depth = 0) {
|
||||||
let folders = [];
|
let folders = [];
|
||||||
Object.values(config).forEach(c => {
|
Object.values(config).forEach(c => {
|
||||||
|
// if(this.presets.render?.folders?.length && !this.presets.render.folders.includes(c.id)) return;
|
||||||
const folder = {
|
const folder = {
|
||||||
id: c.id,
|
id: c.id,
|
||||||
label: game.i18n.localize(c.label),
|
label: game.i18n.localize(c.label),
|
||||||
|
|
@ -162,16 +158,14 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
: [];
|
: [];
|
||||||
folders.push(folder);
|
folders.push(folder);
|
||||||
});
|
});
|
||||||
|
folders.sort((a, b) => a.label.localeCompare(b.label));
|
||||||
|
|
||||||
return folders;
|
return folders;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async selectFolder(_, target, compend, folder) {
|
static async selectFolder(_, target) {
|
||||||
const config = foundry.utils.deepClone(this.config),
|
const folderId = target?.dataset?.folderId ?? this.presets.folder,
|
||||||
compendium = compend ?? target.closest('[data-compendium-id]').dataset.compendiumId,
|
folderData = foundry.utils.getProperty(this.config, folderId) ?? {};
|
||||||
folderId = folder ?? target.dataset.folderId,
|
|
||||||
folderPath = `${compendium}.folders.${folderId}`,
|
|
||||||
folderData = foundry.utils.getProperty(config, folderPath);
|
|
||||||
|
|
||||||
const columns = ItemBrowser.getFolderConfig(folderData).map(col => ({
|
const columns = ItemBrowser.getFolderConfig(folderData).map(col => ({
|
||||||
...col,
|
...col,
|
||||||
|
|
@ -179,31 +173,16 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.selectedMenu = {
|
this.selectedMenu = {
|
||||||
path: folderPath.split('.'),
|
path: folderId?.split('.') ?? [],
|
||||||
data: {
|
data: {
|
||||||
...folderData,
|
...folderData,
|
||||||
columns: columns
|
columns: columns
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let items = [];
|
await this.render({ force: true, presets: this.presets });
|
||||||
for (const key of folderData.keys) {
|
|
||||||
const comp = game.packs.get(`${compendium}.${key}`);
|
|
||||||
if (!comp) return;
|
|
||||||
items = items.concat(await comp.getDocuments({ type__in: folderData.type }));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.items = ItemBrowser.sortBy(items, 'name');
|
if (this.selectedMenu?.data?.type?.length) this.loadItems();
|
||||||
|
|
||||||
if (target) {
|
|
||||||
target
|
|
||||||
.closest('.compendium-sidebar')
|
|
||||||
.querySelectorAll('[data-action="selectFolder"]')
|
|
||||||
.forEach(element => element.classList.remove('is-selected'));
|
|
||||||
target.classList.add('is-selected');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.render({ force: true });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_replaceHTML(result, content, options) {
|
_replaceHTML(result, content, options) {
|
||||||
|
|
@ -211,6 +190,76 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
super._replaceHTML(result, content, options);
|
super._replaceHTML(result, content, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadItems() {
|
||||||
|
let loadTimeout = this.toggleLoader(true);
|
||||||
|
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
game.packs.forEach(pack => {
|
||||||
|
promises.push(
|
||||||
|
new Promise(async resolve => {
|
||||||
|
const items = await pack.getDocuments({ type__in: this.selectedMenu?.data?.type });
|
||||||
|
resolve(items);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Promise.all(promises).then(async result => {
|
||||||
|
this.items = ItemBrowser.sortBy(
|
||||||
|
result.flatMap(r => r),
|
||||||
|
'name'
|
||||||
|
);
|
||||||
|
this.fieldFilter = this._createFieldFilter();
|
||||||
|
|
||||||
|
if (this.presets?.filter) {
|
||||||
|
Object.entries(this.presets.filter).forEach(([k, v]) => {
|
||||||
|
const filter = this.fieldFilter.find(c => c.name === k);
|
||||||
|
if (filter) filter.value = v.value;
|
||||||
|
});
|
||||||
|
// await this._onInputFilterBrowser();
|
||||||
|
}
|
||||||
|
|
||||||
|
const filterList = await foundry.applications.handlebars.renderTemplate(
|
||||||
|
'systems/daggerheart/templates/ui/itemBrowser/filterContainer.hbs',
|
||||||
|
{
|
||||||
|
fieldFilter: this.fieldFilter,
|
||||||
|
presets: this.presets,
|
||||||
|
formatChoices: this.formatChoices
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.element.querySelector('.filter-content .wrapper').innerHTML = filterList;
|
||||||
|
const filterContainer = this.element.querySelector('.filter-header > [data-action="expandContent"]');
|
||||||
|
if (this.fieldFilter.length === 0) filterContainer.setAttribute('disabled', '');
|
||||||
|
else filterContainer.removeAttribute('disabled');
|
||||||
|
|
||||||
|
const itemList = await foundry.applications.handlebars.renderTemplate(
|
||||||
|
'systems/daggerheart/templates/ui/itemBrowser/itemContainer.hbs',
|
||||||
|
{
|
||||||
|
items: this.items,
|
||||||
|
menu: this.selectedMenu,
|
||||||
|
formatLabel: this.formatLabel
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.element.querySelector('.item-list').innerHTML = itemList;
|
||||||
|
|
||||||
|
this._createFilterInputs();
|
||||||
|
await this._onInputFilterBrowser();
|
||||||
|
this._createDragProcess();
|
||||||
|
|
||||||
|
clearTimeout(loadTimeout);
|
||||||
|
this.toggleLoader(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleLoader(state) {
|
||||||
|
const container = this.element.querySelector('.item-list');
|
||||||
|
return setTimeout(() => {
|
||||||
|
container.classList.toggle('loader', state);
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
static expandContent(_, target) {
|
static expandContent(_, target) {
|
||||||
const parent = target.parentElement;
|
const parent = target.parentElement;
|
||||||
parent.classList.toggle('expanded');
|
parent.classList.toggle('expanded');
|
||||||
|
|
@ -243,7 +292,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
filters.forEach(f => {
|
filters.forEach(f => {
|
||||||
if (typeof f.field === 'string') f.field = foundry.utils.getProperty(game, f.field);
|
if (typeof f.field === 'string') f.field = foundry.utils.getProperty(game, f.field);
|
||||||
else if (typeof f.choices === 'function') {
|
else if (typeof f.choices === 'function') {
|
||||||
f.choices = f.choices();
|
f.choices = f.choices(this.items);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear field label so template uses our custom label parameter
|
// Clear field label so template uses our custom label parameter
|
||||||
|
|
@ -262,11 +311,8 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create and initialize search filter instances for the inventory and loadout sections.
|
* Create and initialize search filter instance.
|
||||||
*
|
*
|
||||||
* Sets up two {@link foundry.applications.ux.SearchFilter} instances:
|
|
||||||
* - One for the inventory, which filters items in the inventory grid.
|
|
||||||
* - One for the loadout, which filters items in the loadout/card grid.
|
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_createSearchFilter() {
|
_createSearchFilter() {
|
||||||
|
|
@ -331,6 +377,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
for (const li of html.querySelectorAll('.item-container')) {
|
for (const li of html.querySelectorAll('.item-container')) {
|
||||||
const itemUUID = li.dataset.itemUuid,
|
const itemUUID = li.dataset.itemUuid,
|
||||||
item = this.items.find(i => i.uuid === itemUUID);
|
item = this.items.find(i => i.uuid === itemUUID);
|
||||||
|
if (!item) continue;
|
||||||
const matchesSearch = !query || foundry.applications.ux.SearchFilter.testQuery(rgx, item.name);
|
const matchesSearch = !query || foundry.applications.ux.SearchFilter.testQuery(rgx, item.name);
|
||||||
if (matchesSearch) this.#filteredItems.browser.search.add(item.id);
|
if (matchesSearch) this.#filteredItems.browser.search.add(item.id);
|
||||||
const { input } = this.#filteredItems.browser;
|
const { input } = this.#filteredItems.browser;
|
||||||
|
|
@ -422,11 +469,13 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
|
|
||||||
const newOrder = [...itemList].reverse().sort((a, b) => {
|
const newOrder = [...itemList].reverse().sort((a, b) => {
|
||||||
const aProp = a.querySelector(`[data-item-key="${key}"]`),
|
const aProp = a.querySelector(`[data-item-key="${key}"]`),
|
||||||
bProp = b.querySelector(`[data-item-key="${key}"]`);
|
bProp = b.querySelector(`[data-item-key="${key}"]`),
|
||||||
|
aValue = isNaN(aProp.innerText) ? aProp.innerText : Number(aProp.innerText),
|
||||||
|
bValue = isNaN(bProp.innerText) ? bProp.innerText : Number(bProp.innerText);
|
||||||
if (type === 'DESC') {
|
if (type === 'DESC') {
|
||||||
return aProp.innerText < bProp.innerText ? 1 : -1;
|
return aValue < bValue ? 1 : -1;
|
||||||
} else {
|
} else {
|
||||||
return aProp.innerText > bProp.innerText ? 1 : -1;
|
return aValue > bValue ? 1 : -1;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -455,4 +504,41 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
_canDragStart() {
|
_canDragStart() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static injectSidebarButton(html) {
|
||||||
|
if (!game.user.isGM) return;
|
||||||
|
const sectionId = html.dataset.tab,
|
||||||
|
menus = {
|
||||||
|
actors: {
|
||||||
|
folder: 'adversaries',
|
||||||
|
render: {
|
||||||
|
folders: ['adversaries', 'characters', 'environments']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
items: {
|
||||||
|
folder: 'equipments',
|
||||||
|
render: {
|
||||||
|
noFolder: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
compendium: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Object.keys(menus).includes(sectionId)) {
|
||||||
|
const headerActions = html.querySelector('.header-actions');
|
||||||
|
|
||||||
|
const button = document.createElement('button');
|
||||||
|
button.type = 'button';
|
||||||
|
button.classList.add('open-compendium-browser');
|
||||||
|
button.innerHTML = `
|
||||||
|
<i class="fa-solid fa-book-atlas"></i>
|
||||||
|
${game.i18n.localize('DAGGERHEART.UI.Tooltip.compendiumBrowser')}
|
||||||
|
`;
|
||||||
|
button.addEventListener('click', event => {
|
||||||
|
ui.compendiumBrowser.open(menus[sectionId]);
|
||||||
|
});
|
||||||
|
|
||||||
|
headerActions.append(button);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -561,6 +561,19 @@ export const refreshTypes = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const itemAbilityCosts = {
|
||||||
|
resource: {
|
||||||
|
id: 'resource',
|
||||||
|
label: 'DAGGERHEART.GENERAL.resource',
|
||||||
|
group: 'Global'
|
||||||
|
},
|
||||||
|
quantity: {
|
||||||
|
id: 'quantity',
|
||||||
|
label: 'DAGGERHEART.GENERAL.quantity',
|
||||||
|
group: 'Global'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const abilityCosts = {
|
export const abilityCosts = {
|
||||||
hitPoints: {
|
hitPoints: {
|
||||||
id: 'hitPoints',
|
id: 'hitPoints',
|
||||||
|
|
@ -586,7 +599,8 @@ export const abilityCosts = {
|
||||||
id: 'fear',
|
id: 'fear',
|
||||||
label: 'Fear',
|
label: 'Fear',
|
||||||
group: 'TYPES.Actor.adversary'
|
group: 'TYPES.Actor.adversary'
|
||||||
}
|
},
|
||||||
|
resource: itemAbilityCosts.resource
|
||||||
};
|
};
|
||||||
|
|
||||||
export const countdownTypes = {
|
export const countdownTypes = {
|
||||||
|
|
|
||||||
|
|
@ -149,6 +149,102 @@ export const typeConfig = {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
weapons: {
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
key: 'system.secondary',
|
||||||
|
label: 'DAGGERHEART.UI.ItemBrowser.subtype',
|
||||||
|
format: isSecondary => (isSecondary ? 'secondary' : isSecondary === false ? 'primary' : '-')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'system.tier',
|
||||||
|
label: 'DAGGERHEART.GENERAL.Tiers.singular'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
key: 'system.secondary',
|
||||||
|
label: 'DAGGERHEART.UI.ItemBrowser.subtype',
|
||||||
|
choices: [
|
||||||
|
{ value: false, label: 'DAGGERHEART.ITEMS.Weapon.primaryWeapon' },
|
||||||
|
{ value: true, label: 'DAGGERHEART.ITEMS.Weapon.secondaryWeapon' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'system.tier',
|
||||||
|
label: 'DAGGERHEART.GENERAL.Tiers.singular',
|
||||||
|
choices: [
|
||||||
|
{ value: '1', label: '1' },
|
||||||
|
{ value: '2', label: '2' },
|
||||||
|
{ value: '3', label: '3' },
|
||||||
|
{ value: '4', label: '4' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'system.burden',
|
||||||
|
label: 'DAGGERHEART.GENERAL.burden',
|
||||||
|
field: 'system.api.models.items.DHWeapon.schema.fields.burden'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'system.attack.roll.trait',
|
||||||
|
label: 'DAGGERHEART.GENERAL.Trait.single',
|
||||||
|
field: 'system.api.models.actions.actionsTypes.attack.schema.fields.roll.fields.trait'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'system.attack.range',
|
||||||
|
label: 'DAGGERHEART.GENERAL.range',
|
||||||
|
field: 'system.api.models.actions.actionsTypes.attack.schema.fields.range'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'system.itemFeatures',
|
||||||
|
label: 'DAGGERHEART.GENERAL.features',
|
||||||
|
choices: () =>
|
||||||
|
Object.entries(CONFIG.DH.ITEM.weaponFeatures).map(([k, v]) => ({ value: k, label: v.label })),
|
||||||
|
operator: 'contains3'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
armors: {
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
key: 'system.tier',
|
||||||
|
label: 'DAGGERHEART.GENERAL.Tiers.singular'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
key: 'system.tier',
|
||||||
|
label: 'DAGGERHEART.GENERAL.Tiers.singular',
|
||||||
|
choices: [
|
||||||
|
{ value: '1', label: '1' },
|
||||||
|
{ value: '2', label: '2' },
|
||||||
|
{ value: '3', label: '3' },
|
||||||
|
{ value: '4', label: '4' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'system.baseScore',
|
||||||
|
name: 'armor.min',
|
||||||
|
label: 'DAGGERHEART.UI.ItemBrowser.armorScoreMin',
|
||||||
|
field: 'system.api.models.items.DHArmor.schema.fields.baseScore',
|
||||||
|
operator: 'gte'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'system.baseScore',
|
||||||
|
name: 'armor.max',
|
||||||
|
label: 'DAGGERHEART.UI.ItemBrowser.armorScoreMax',
|
||||||
|
field: 'system.api.models.items.DHArmor.schema.fields.baseScore',
|
||||||
|
operator: 'lte'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'system.itemFeatures',
|
||||||
|
label: 'DAGGERHEART.GENERAL.features',
|
||||||
|
choices: () =>
|
||||||
|
Object.entries(CONFIG.DH.ITEM.armorFeatures).map(([k, v]) => ({ value: k, label: v.label })),
|
||||||
|
operator: 'contains3'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
features: {
|
features: {
|
||||||
columns: [],
|
columns: [],
|
||||||
filters: []
|
filters: []
|
||||||
|
|
@ -257,7 +353,7 @@ export const typeConfig = {
|
||||||
{
|
{
|
||||||
key: 'system.domains',
|
key: 'system.domains',
|
||||||
label: 'DAGGERHEART.GENERAL.Domain.plural',
|
label: 'DAGGERHEART.GENERAL.Domain.plural',
|
||||||
choices: () => Object.values(CONFIG.DH.DOMAIN.domains).map(d => ({ value: d.id, label: d.label })),
|
choices: () => Object.values(CONFIG.DH.DOMAIN.allDomains()).map(d => ({ value: d.id, label: d.label })),
|
||||||
operator: 'contains2'
|
operator: 'contains2'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -265,11 +361,9 @@ export const typeConfig = {
|
||||||
subclasses: {
|
subclasses: {
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
key: 'id',
|
key: 'system.linkedClass',
|
||||||
label: 'TYPES.Item.class',
|
label: 'Class',
|
||||||
format: id => {
|
format: linkedClass => linkedClass.name
|
||||||
return '';
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'system.spellcastingTrait',
|
key: 'system.spellcastingTrait',
|
||||||
|
|
@ -278,6 +372,34 @@ export const typeConfig = {
|
||||||
],
|
],
|
||||||
filters: []
|
filters: []
|
||||||
},
|
},
|
||||||
|
beastforms: {
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
key: 'system.tier',
|
||||||
|
label: 'DAGGERHEART.GENERAL.Tiers.singular'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'system.mainTrait',
|
||||||
|
label: 'DAGGERHEART.GENERAL.Trait.single'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
key: 'system.linkedClass.uuid',
|
||||||
|
label: 'Class',
|
||||||
|
choices: items => {
|
||||||
|
const list = items.map(item => ({
|
||||||
|
value: item.system.linkedClass.uuid,
|
||||||
|
label: item.system.linkedClass.name
|
||||||
|
}));
|
||||||
|
return list.reduce((a, c) => {
|
||||||
|
if (!a.find(i => i.value === c.value)) a.push(c);
|
||||||
|
return a;
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
beastforms: {
|
beastforms: {
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
|
|
@ -305,109 +427,144 @@ export const typeConfig = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const compendiumConfig = {
|
export const compendiumConfig = {
|
||||||
daggerheart: {
|
characters: {
|
||||||
id: 'daggerheart',
|
id: 'characters',
|
||||||
label: 'DAGGERHEART',
|
keys: ['characters'],
|
||||||
folders: {
|
label: 'DAGGERHEART.UI.ItemBrowser.folders.characters',
|
||||||
adversaries: {
|
type: ['character']
|
||||||
id: 'adversaries',
|
// listType: 'characters'
|
||||||
keys: ['adversaries'],
|
},
|
||||||
label: 'DAGGERHEART.UI.ItemBrowser.folders.adversaries',
|
adversaries: {
|
||||||
type: ['adversary'],
|
id: 'adversaries',
|
||||||
listType: 'adversaries'
|
keys: ['adversaries'],
|
||||||
},
|
label: 'DAGGERHEART.UI.ItemBrowser.folders.adversaries',
|
||||||
ancestries: {
|
type: ['adversary'],
|
||||||
id: 'ancestries',
|
listType: 'adversaries'
|
||||||
|
},
|
||||||
|
ancestries: {
|
||||||
|
id: 'ancestries',
|
||||||
|
keys: ['ancestries'],
|
||||||
|
label: 'DAGGERHEART.UI.ItemBrowser.folders.ancestries',
|
||||||
|
type: ['ancestry']
|
||||||
|
/* folders: {
|
||||||
|
features: {
|
||||||
|
id: 'features',
|
||||||
keys: ['ancestries'],
|
keys: ['ancestries'],
|
||||||
label: 'DAGGERHEART.UI.ItemBrowser.folders.ancestries',
|
label: 'DAGGERHEART.UI.ItemBrowser.folders.features',
|
||||||
type: ['ancestry'],
|
type: ['feature']
|
||||||
folders: {
|
}
|
||||||
features: {
|
} */
|
||||||
id: 'features',
|
},
|
||||||
keys: ['ancestries'],
|
equipments: {
|
||||||
label: 'DAGGERHEART.UI.ItemBrowser.folders.features',
|
id: 'equipments',
|
||||||
type: ['feature']
|
keys: ['armors', 'weapons', 'consumables', 'loot'],
|
||||||
}
|
label: 'DAGGERHEART.UI.ItemBrowser.folders.equipment',
|
||||||
}
|
type: ['armor', 'weapon', 'consumable', 'loot'],
|
||||||
|
listType: 'items',
|
||||||
|
folders: {
|
||||||
|
weapons: {
|
||||||
|
id: 'weapons',
|
||||||
|
keys: ['weapons'],
|
||||||
|
label: 'DAGGERHEART.UI.ItemBrowser.folders.weapons',
|
||||||
|
type: ['weapon'],
|
||||||
|
listType: 'weapons'
|
||||||
},
|
},
|
||||||
equipments: {
|
armors: {
|
||||||
id: 'equipments',
|
id: 'armors',
|
||||||
keys: ['armors', 'weapons', 'consumables', 'loot'],
|
keys: ['armors'],
|
||||||
label: 'DAGGERHEART.UI.ItemBrowser.folders.equipment',
|
label: 'DAGGERHEART.UI.ItemBrowser.folders.armors',
|
||||||
type: ['armor', 'weapon', 'consumable', 'loot'],
|
type: ['armor'],
|
||||||
listType: 'items'
|
listType: 'armors'
|
||||||
},
|
},
|
||||||
classes: {
|
consumables: {
|
||||||
id: 'classes',
|
id: 'consumables',
|
||||||
keys: ['classes'],
|
keys: ['consumables'],
|
||||||
label: 'DAGGERHEART.UI.ItemBrowser.folders.classes',
|
label: 'DAGGERHEART.UI.ItemBrowser.folders.consumables',
|
||||||
type: ['class'],
|
type: ['consumable']
|
||||||
folders: {
|
|
||||||
features: {
|
|
||||||
id: 'features',
|
|
||||||
keys: ['classes'],
|
|
||||||
label: 'DAGGERHEART.UI.ItemBrowser.folders.features',
|
|
||||||
type: ['feature']
|
|
||||||
},
|
|
||||||
items: {
|
|
||||||
id: 'items',
|
|
||||||
keys: ['classes'],
|
|
||||||
label: 'DAGGERHEART.UI.ItemBrowser.folders.items',
|
|
||||||
type: ['armor', 'weapon', 'consumable', 'loot'],
|
|
||||||
listType: 'items'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
listType: 'classes'
|
|
||||||
},
|
},
|
||||||
subclasses: {
|
loots: {
|
||||||
id: 'subclasses',
|
id: 'loots',
|
||||||
keys: ['subclasses'],
|
keys: ['loots'],
|
||||||
label: 'DAGGERHEART.UI.ItemBrowser.folders.subclasses',
|
label: 'DAGGERHEART.UI.ItemBrowser.folders.loots',
|
||||||
type: ['subclass'],
|
type: ['loot']
|
||||||
listType: 'subclasses'
|
|
||||||
},
|
|
||||||
domains: {
|
|
||||||
id: 'domains',
|
|
||||||
keys: ['domains'],
|
|
||||||
label: 'DAGGERHEART.UI.ItemBrowser.folders.domainCards',
|
|
||||||
type: ['domainCard'],
|
|
||||||
listType: 'cards'
|
|
||||||
},
|
|
||||||
communities: {
|
|
||||||
id: 'communities',
|
|
||||||
keys: ['communities'],
|
|
||||||
label: 'DAGGERHEART.UI.ItemBrowser.folders.communities',
|
|
||||||
type: ['community'],
|
|
||||||
folders: {
|
|
||||||
features: {
|
|
||||||
id: 'features',
|
|
||||||
keys: ['communities'],
|
|
||||||
label: 'DAGGERHEART.UI.ItemBrowser.folders.features',
|
|
||||||
type: ['feature']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
environments: {
|
|
||||||
id: 'environments',
|
|
||||||
keys: ['environments'],
|
|
||||||
label: 'DAGGERHEART.UI.ItemBrowser.folders.environments',
|
|
||||||
type: ['environment']
|
|
||||||
},
|
|
||||||
beastforms: {
|
|
||||||
id: 'beastforms',
|
|
||||||
keys: ['beastforms'],
|
|
||||||
label: 'DAGGERHEART.UI.ItemBrowser.folders.beastforms',
|
|
||||||
type: ['beastform'],
|
|
||||||
listType: 'beastforms',
|
|
||||||
folders: {
|
|
||||||
features: {
|
|
||||||
id: 'features',
|
|
||||||
keys: ['beastforms'],
|
|
||||||
label: 'DAGGERHEART.UI.ItemBrowser.folders.features',
|
|
||||||
type: ['feature']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
classes: {
|
||||||
|
id: 'classes',
|
||||||
|
keys: ['classes'],
|
||||||
|
label: 'DAGGERHEART.UI.ItemBrowser.folders.classes',
|
||||||
|
type: ['class'],
|
||||||
|
/* folders: {
|
||||||
|
features: {
|
||||||
|
id: 'features',
|
||||||
|
keys: ['classes'],
|
||||||
|
label: 'DAGGERHEART.UI.ItemBrowser.folders.features',
|
||||||
|
type: ['feature']
|
||||||
|
},
|
||||||
|
items: {
|
||||||
|
id: 'items',
|
||||||
|
keys: ['classes'],
|
||||||
|
label: 'DAGGERHEART.UI.ItemBrowser.folders.items',
|
||||||
|
type: ['armor', 'weapon', 'consumable', 'loot'],
|
||||||
|
listType: 'items'
|
||||||
|
}
|
||||||
|
}, */
|
||||||
|
listType: 'classes'
|
||||||
|
},
|
||||||
|
subclasses: {
|
||||||
|
id: 'subclasses',
|
||||||
|
keys: ['subclasses'],
|
||||||
|
label: 'DAGGERHEART.UI.ItemBrowser.folders.subclasses',
|
||||||
|
type: ['subclass'],
|
||||||
|
listType: 'subclasses'
|
||||||
|
},
|
||||||
|
domains: {
|
||||||
|
id: 'domains',
|
||||||
|
keys: ['domains'],
|
||||||
|
label: 'DAGGERHEART.UI.ItemBrowser.folders.domainCards',
|
||||||
|
type: ['domainCard'],
|
||||||
|
listType: 'cards'
|
||||||
|
},
|
||||||
|
communities: {
|
||||||
|
id: 'communities',
|
||||||
|
keys: ['communities'],
|
||||||
|
label: 'DAGGERHEART.UI.ItemBrowser.folders.communities',
|
||||||
|
type: ['community']
|
||||||
|
/* folders: {
|
||||||
|
features: {
|
||||||
|
id: 'features',
|
||||||
|
keys: ['communities'],
|
||||||
|
label: 'DAGGERHEART.UI.ItemBrowser.folders.features',
|
||||||
|
type: ['feature']
|
||||||
|
}
|
||||||
|
} */
|
||||||
|
},
|
||||||
|
environments: {
|
||||||
|
id: 'environments',
|
||||||
|
keys: ['environments'],
|
||||||
|
label: 'DAGGERHEART.UI.ItemBrowser.folders.environments',
|
||||||
|
type: ['environment']
|
||||||
|
},
|
||||||
|
beastforms: {
|
||||||
|
id: 'beastforms',
|
||||||
|
keys: ['beastforms'],
|
||||||
|
label: 'DAGGERHEART.UI.ItemBrowser.folders.beastforms',
|
||||||
|
type: ['beastform'],
|
||||||
|
listType: 'beastforms'
|
||||||
|
/* folders: {
|
||||||
|
features: {
|
||||||
|
id: 'features',
|
||||||
|
keys: ['beastforms'],
|
||||||
|
label: 'DAGGERHEART.UI.ItemBrowser.folders.features',
|
||||||
|
type: ['feature']
|
||||||
|
}
|
||||||
|
} */
|
||||||
|
},
|
||||||
|
features: {
|
||||||
|
id: 'features',
|
||||||
|
keys: ['features'],
|
||||||
|
label: 'DAGGERHEART.UI.ItemBrowser.folders.features',
|
||||||
|
type: ['feature']
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -865,6 +865,9 @@ export const weaponFeatures = {
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.greedy.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.greedy.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.greedy.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.greedy.description',
|
||||||
img: 'icons/commodities/currency/coins-crown-stack-gold.webp',
|
img: 'icons/commodities/currency/coins-crown-stack-gold.webp',
|
||||||
|
target: {
|
||||||
|
type: 'self'
|
||||||
|
},
|
||||||
// Should cost handful of gold,
|
// Should cost handful of gold,
|
||||||
effects: [
|
effects: [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -29,3 +29,22 @@ export const gameSettings = {
|
||||||
Countdowns: 'Countdowns',
|
Countdowns: 'Countdowns',
|
||||||
LastMigrationVersion: 'LastMigrationVersion'
|
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'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -51,11 +51,13 @@ export default class DHAttackAction extends DHDamageAction {
|
||||||
const labels = [];
|
const labels = [];
|
||||||
const { roll, range, damage } = this;
|
const { roll, range, damage } = this;
|
||||||
|
|
||||||
if (roll.trait) labels.push(game.i18n.localize(`DAGGERHEART.CONFIG.Traits.${roll.trait}.short`))
|
if (roll.trait) labels.push(game.i18n.localize(`DAGGERHEART.CONFIG.Traits.${roll.trait}.short`));
|
||||||
if (range) labels.push(game.i18n.localize(`DAGGERHEART.CONFIG.Range.${range}.short`));
|
if (range) labels.push(game.i18n.localize(`DAGGERHEART.CONFIG.Range.${range}.short`));
|
||||||
|
|
||||||
for (const { value, type } of damage.parts) {
|
const useAltDamage = this.actor?.effects?.find(x => x.type === 'horde')?.active;
|
||||||
const str = Roll.replaceFormulaData(value.getFormula(), this.actor?.getRollData() ?? {});
|
for (const { value, valueAlt, type } of damage.parts) {
|
||||||
|
const usedValue = useAltDamage ? valueAlt : value;
|
||||||
|
const str = Roll.replaceFormulaData(usedValue.getFormula(), this.actor?.getRollData() ?? {});
|
||||||
|
|
||||||
const icons = Array.from(type)
|
const icons = Array.from(type)
|
||||||
.map(t => CONFIG.DH.GENERAL.damageTypes[t]?.icon)
|
.map(t => CONFIG.DH.GENERAL.damageTypes[t]?.icon)
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,9 @@
|
||||||
import DhpActor from '../../documents/actor.mjs';
|
import DhpActor from '../../documents/actor.mjs';
|
||||||
import D20RollDialog from '../../applications/dialogs/d20RollDialog.mjs';
|
import D20RollDialog from '../../applications/dialogs/d20RollDialog.mjs';
|
||||||
import { ActionMixin } from '../fields/actionField.mjs';
|
import { ActionMixin } from '../fields/actionField.mjs';
|
||||||
import { abilities } from '../../config/actorConfig.mjs';
|
|
||||||
|
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
/*
|
|
||||||
!!! I'm currently refactoring the whole Action thing, it's a WIP !!!
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
ToDo
|
ToDo
|
||||||
- Target Check / Target Picker
|
- Target Check / Target Picker
|
||||||
|
|
@ -20,6 +15,7 @@ const fields = foundry.data.fields;
|
||||||
export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel) {
|
export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel) {
|
||||||
static extraSchemas = ['cost', 'uses', 'range'];
|
static extraSchemas = ['cost', 'uses', 'range'];
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
static defineSchema() {
|
static defineSchema() {
|
||||||
const schemaFields = {
|
const schemaFields = {
|
||||||
_id: new fields.DocumentIdField({ initial: () => foundry.utils.randomID() }),
|
_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 => {
|
this.extraSchemas.forEach(s => {
|
||||||
let clsField;
|
let clsField = this.getActionField(s);
|
||||||
if ((clsField = this.getActionField(s))) schemaFields[s] = new clsField();
|
if (clsField) schemaFields[s] = new clsField();
|
||||||
});
|
});
|
||||||
|
|
||||||
return schemaFields;
|
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: <action>.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) {
|
static getActionField(name) {
|
||||||
const field = game.system.api.fields.ActionFields[`${name.capitalize()}Field`];
|
const field = game.system.api.fields.ActionFields[`${name.capitalize()}Field`];
|
||||||
return fields.DataField.isPrototypeOf(field) && field;
|
return fields.DataField.isPrototypeOf(field) && field;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
prepareData() {
|
prepareData() {
|
||||||
this.name = this.name || game.i18n.localize(CONFIG.DH.ACTIONS.actionTypes[this.type].name);
|
this.name = this.name || game.i18n.localize(CONFIG.DH.ACTIONS.actionTypes[this.type].name);
|
||||||
this.img = this.img ?? this.parent?.parent?.img;
|
this.img = this.img ?? this.parent?.parent?.img;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Action ID
|
||||||
|
*/
|
||||||
get id() {
|
get id() {
|
||||||
return this._id;
|
return this._id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return Item the action is attached too.
|
||||||
|
*/
|
||||||
get item() {
|
get item() {
|
||||||
return this.parent.parent;
|
return this.parent.parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the first Actor parent found.
|
||||||
|
*/
|
||||||
get actor() {
|
get actor() {
|
||||||
return this.item instanceof DhpActor
|
return this.item instanceof DhpActor
|
||||||
? this.item
|
? this.item
|
||||||
|
|
@ -74,6 +115,11 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
return 'trait';
|
return 'trait';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare base data based on Action Type & Parent Type
|
||||||
|
* @param {object} parent
|
||||||
|
* @returns {object}
|
||||||
|
*/
|
||||||
static getSourceConfig(parent) {
|
static getSourceConfig(parent) {
|
||||||
const updateSource = {};
|
const updateSource = {};
|
||||||
if (parent?.parent?.type === 'weapon' && this === game.system.api.models.actions.actionsTypes.attack) {
|
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;
|
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 = {}) {
|
getRollData(data = {}) {
|
||||||
if (!this.actor) return null;
|
if (!this.actor) return null;
|
||||||
const actorData = this.actor.getRollData(false);
|
const actorData = this.actor.getRollData(false);
|
||||||
|
|
@ -111,19 +162,30 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
return actorData;
|
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.actor) throw new Error("An Action can't be used outside of an Actor context.");
|
||||||
|
|
||||||
if (this.chatDisplay) await this.toChat();
|
if (this.chatDisplay) await this.toChat();
|
||||||
let { byPassRoll } = options,
|
|
||||||
config = this.prepareConfig(event, byPassRoll);
|
let config = this.prepareConfig(event);
|
||||||
for (let i = 0; i < this.constructor.extraSchemas.length; i++) {
|
if (!config) return;
|
||||||
let clsField = this.constructor.getActionField(this.constructor.extraSchemas[i]);
|
|
||||||
if (clsField?.prepareConfig) {
|
|
||||||
const keep = clsField.prepareConfig.call(this, config);
|
|
||||||
if (config.isFastForward && !keep) return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Hooks.call(`${CONFIG.DH.id}.preUseAction`, this, config) === false) return;
|
if (Hooks.call(`${CONFIG.DH.id}.preUseAction`, this, config) === false) return;
|
||||||
|
|
||||||
|
|
@ -133,274 +195,116 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
if (!config) return;
|
if (!config) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.hasRoll) {
|
// Execute the Action Worflow in order based of schema fields
|
||||||
const rollConfig = this.prepareRoll(config);
|
await this.executeWorkflow(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);
|
|
||||||
|
|
||||||
if (Hooks.call(`${CONFIG.DH.id}.postUseAction`, this, config) === false) return;
|
if (Hooks.call(`${CONFIG.DH.id}.postUseAction`, this, config) === false) return;
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* */
|
/**
|
||||||
prepareConfig(event, byPass = false) {
|
* Create the basic config common to every action type
|
||||||
const hasRoll = this.getUseHasRoll(byPass);
|
* @param {Event} event Event from the button used to trigger the Action
|
||||||
return {
|
* @returns {object}
|
||||||
|
*/
|
||||||
|
prepareBaseConfig(event) {
|
||||||
|
const config = {
|
||||||
event,
|
event,
|
||||||
title: `${this.item.name}: ${game.i18n.localize(this.name)}`,
|
title: `${this.item instanceof CONFIG.Actor.documentClass ? '' : `${this.item.name}: `}${game.i18n.localize(this.name)}`,
|
||||||
source: {
|
source: {
|
||||||
item: this.item._id,
|
item: this.item._id,
|
||||||
action: this._id,
|
action: this._id,
|
||||||
actor: this.actor.uuid
|
actor: this.actor.uuid
|
||||||
},
|
},
|
||||||
dialog: {
|
dialog: {},
|
||||||
configure: hasRoll
|
actionType: this.actionType,
|
||||||
},
|
hasRoll: this.hasRoll,
|
||||||
type: this.type,
|
hasDamage: this.hasDamage,
|
||||||
hasRoll: hasRoll,
|
hasHealing: this.hasHealing,
|
||||||
hasDamage: this.damage?.parts?.length && this.type !== 'healing',
|
hasEffect: this.hasEffect,
|
||||||
hasHealing: this.damage?.parts?.length && this.type === 'healing',
|
|
||||||
hasEffect: !!this.effects?.length,
|
|
||||||
isDirect: !!this.damage?.direct,
|
|
||||||
hasSave: this.hasSave,
|
hasSave: this.hasSave,
|
||||||
|
isDirect: !!this.damage?.direct,
|
||||||
selectedRollMode: game.settings.get('core', 'rollMode'),
|
selectedRollMode: game.settings.get('core', 'rollMode'),
|
||||||
isFastForward: event.shiftKey,
|
|
||||||
data: this.getRollData(),
|
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) {
|
requireConfigurationDialog(config) {
|
||||||
return !config.event.shiftKey && !config.hasRoll && (config.costs?.length || config.uses);
|
return !config.event.shiftKey && !config.hasRoll && (config.costs?.length || config.uses);
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareRoll() {
|
/**
|
||||||
const roll = {
|
* Consume Action configured resources & uses.
|
||||||
baseModifiers: this.roll.getModifier(),
|
* That method is only used when we want those resources to be consumed outside of the use method workflow.
|
||||||
label: 'Attack',
|
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
|
||||||
type: this.actionType,
|
* @param {boolean} successCost
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
async consume(config, successCost = false) {
|
async consume(config, successCost = false) {
|
||||||
const actor = this.actor.system.partner ?? this.actor,
|
await this.workflow.get('cost')?.execute(config, successCost);
|
||||||
usefulResources = {
|
await this.workflow.get('uses')?.execute(config, successCost);
|
||||||
...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
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for (var cost of config.costs) {
|
if (config.roll && !config.roll.success && successCost) {
|
||||||
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) {
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
(config.message ?? config.parent).update({ 'system.successConsumed': true });
|
(config.message ?? config.parent).update({ 'system.successConsumed': true });
|
||||||
}, 50);
|
}, 50);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* */
|
|
||||||
|
|
||||||
/* ROLL */
|
/**
|
||||||
getUseHasRoll(byPass = false) {
|
* Set if a configuration dialog must be shown or not if a special keyboard key is pressed.
|
||||||
return this.hasRoll && !byPass;
|
* @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() {
|
get hasRoll() {
|
||||||
return !!this.roll?.type;
|
return !!this.roll?.type;
|
||||||
}
|
}
|
||||||
|
|
||||||
get modifiers() {
|
get hasDamage() {
|
||||||
if (!this.actor) return [];
|
return this.damage?.parts?.length && this.type !== 'healing';
|
||||||
const modifiers = [];
|
}
|
||||||
/** Placeholder for specific bonuses **/
|
|
||||||
return modifiers;
|
get hasHealing() {
|
||||||
|
return this.damage?.parts?.length && this.type === 'healing';
|
||||||
}
|
}
|
||||||
/* ROLL */
|
|
||||||
|
|
||||||
/* SAVE */
|
|
||||||
get hasSave() {
|
get hasSave() {
|
||||||
return !!this.save?.trait;
|
return !!this.save?.trait;
|
||||||
}
|
}
|
||||||
/* SAVE */
|
|
||||||
|
|
||||||
/* EFFECTS */
|
|
||||||
get hasEffect() {
|
get hasEffect() {
|
||||||
return this.effects?.length > 0;
|
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.
|
* Generates a list of localized tags for this action.
|
||||||
* @returns {string[]} An array of localized tag strings.
|
* @returns {string[]} An array of localized tag strings.
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
import BeastformDialog from '../../applications/dialogs/beastformDialog.mjs';
|
|
||||||
import DHBaseAction from './baseAction.mjs';
|
import DHBaseAction from './baseAction.mjs';
|
||||||
|
|
||||||
export default class DhBeastformAction extends DHBaseAction {
|
export default class DhBeastformAction extends DHBaseAction {
|
||||||
static extraSchemas = [...super.extraSchemas, 'beastform'];
|
static extraSchemas = [...super.extraSchemas, 'beastform'];
|
||||||
|
|
||||||
async use(event, options) {
|
/* async use(event, options) {
|
||||||
const beastformConfig = this.prepareBeastformConfig();
|
const beastformConfig = this.prepareBeastformConfig();
|
||||||
|
|
||||||
const abort = await this.handleActiveTransformations();
|
const abort = await this.handleActiveTransformations();
|
||||||
|
|
@ -82,5 +81,5 @@ export default class DhBeastformAction extends DHBaseAction {
|
||||||
beastformEffects.map(x => x.id)
|
beastformEffects.map(x => x.id)
|
||||||
);
|
);
|
||||||
return existingEffects;
|
return existingEffects;
|
||||||
}
|
} */
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,65 +1,5 @@
|
||||||
import { setsEqual } from '../../helpers/utils.mjs';
|
|
||||||
import DHBaseAction from './baseAction.mjs';
|
import DHBaseAction from './baseAction.mjs';
|
||||||
|
|
||||||
export default class DHDamageAction extends DHBaseAction {
|
export default class DHDamageAction extends DHBaseAction {
|
||||||
static extraSchemas = [...super.extraSchemas, 'damage', 'target', 'effects'];
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,4 @@ import DHBaseAction from './baseAction.mjs';
|
||||||
|
|
||||||
export default class DHMacroAction extends DHBaseAction {
|
export default class DHMacroAction extends DHBaseAction {
|
||||||
static extraSchemas = [...super.extraSchemas, 'macro'];
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,7 @@ export default class DhpAdversary extends BaseDataActor {
|
||||||
CONFIG.DH.id,
|
CONFIG.DH.id,
|
||||||
CONFIG.DH.SETTINGS.gameSettings.Automation
|
CONFIG.DH.SETTINGS.gameSettings.Automation
|
||||||
).hordeDamage;
|
).hordeDamage;
|
||||||
if (autoHordeDamage && changes.system?.resources?.hitPoints?.value) {
|
if (autoHordeDamage && changes.system?.resources?.hitPoints?.value !== undefined) {
|
||||||
const hordeActiveEffect = this.parent.effects.find(x => x.type === 'horde');
|
const hordeActiveEffect = this.parent.effects.find(x => x.type === 'horde');
|
||||||
if (hordeActiveEffect) {
|
if (hordeActiveEffect) {
|
||||||
const halfHP = Math.ceil(this.resources.hitPoints.max / 2);
|
const halfHP = Math.ceil(this.resources.hitPoints.max / 2);
|
||||||
|
|
|
||||||
|
|
@ -130,11 +130,16 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
|
||||||
const typeForDefeated = ['character', 'adversary', 'companion'].find(x => x === this.parent.type);
|
const typeForDefeated = ['character', 'adversary', 'companion'].find(x => x === this.parent.type);
|
||||||
if (defeatedSettings.enabled && typeForDefeated) {
|
if (defeatedSettings.enabled && typeForDefeated) {
|
||||||
const resource = typeForDefeated === 'companion' ? 'stress' : 'hitPoints';
|
const resource = typeForDefeated === 'companion' ? 'stress' : 'hitPoints';
|
||||||
if (changes.system.resources[resource]) {
|
const resourceValue = changes.system.resources[resource];
|
||||||
const becameMax = changes.system.resources[resource].value === this.resources[resource].max;
|
if (
|
||||||
|
resourceValue &&
|
||||||
|
this.resources[resource].max &&
|
||||||
|
resourceValue.value !== this.resources[resource].value
|
||||||
|
) {
|
||||||
|
const becameMax = resourceValue.value === this.resources[resource].max;
|
||||||
const wasMax =
|
const wasMax =
|
||||||
this.resources[resource].value === this.resources[resource].max &&
|
this.resources[resource].value === this.resources[resource].max &&
|
||||||
this.resources[resource].value !== changes.system.resources[resource].value;
|
this.resources[resource].value !== resourceValue.value;
|
||||||
if (becameMax) {
|
if (becameMax) {
|
||||||
this.parent.toggleDefeated(true);
|
this.parent.toggleDefeated(true);
|
||||||
} else if (wasMax) {
|
} else if (wasMax) {
|
||||||
|
|
|
||||||
|
|
@ -317,7 +317,7 @@ export default class DhCharacter extends BaseDataActor {
|
||||||
}
|
}
|
||||||
|
|
||||||
get multiclass() {
|
get multiclass() {
|
||||||
const value = this.parent.items.find(x => x.type === 'Class' && x.system.isMulticlass);
|
const value = this.parent.items.find(x => x.type === 'class' && x.system.isMulticlass);
|
||||||
const subclass = this.parent.items.find(x => x.type === 'subclass' && x.system.isMulticlass);
|
const subclass = this.parent.items.find(x => x.type === 'subclass' && x.system.isMulticlass);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -443,7 +443,9 @@ export default class DhCharacter extends BaseDataActor {
|
||||||
classFeatures.push(item);
|
classFeatures.push(item);
|
||||||
} else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.subclass.id) {
|
} else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.subclass.id) {
|
||||||
if (this.class.subclass) {
|
if (this.class.subclass) {
|
||||||
const subclassState = this.class.subclass.system.featureState;
|
const prop = item.system.multiclassOrigin ? 'multiclass' : 'class';
|
||||||
|
const subclassState = this[prop].subclass?.system?.featureState;
|
||||||
|
if (!subclassState) continue;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
item.system.identifier === CONFIG.DH.ITEM.featureSubTypes.foundation ||
|
item.system.identifier === CONFIG.DH.ITEM.featureSubTypes.foundation ||
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import DHAbilityUse from "./abilityUse.mjs";
|
import DHAbilityUse from './abilityUse.mjs';
|
||||||
import DHActorRoll from "./adversaryRoll.mjs";
|
import DHActorRoll from './actorRoll.mjs';
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
abilityUse: DHAbilityUse,
|
abilityUse: DHAbilityUse,
|
||||||
adversaryRoll: DHActorRoll,
|
adversaryRoll: DHActorRoll,
|
||||||
damageRoll: DHActorRoll,
|
damageRoll: DHActorRoll,
|
||||||
dualityRoll: DHActorRoll
|
dualityRoll: DHActorRoll
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -55,9 +55,12 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
get action() {
|
get action() {
|
||||||
const actionItem = this.actionItem;
|
const actionActor = this.actionActor,
|
||||||
if (!actionItem || !this.source.action) return null;
|
actionItem = this.actionItem;
|
||||||
return actionItem.system.actionsList?.find(a => a.id === this.source.action);
|
if (!this.source.action) return null;
|
||||||
|
if (actionItem) return actionItem.system.actionsList?.find(a => a.id === this.source.action);
|
||||||
|
else if (actionActor?.system.attack?._id === this.source.action) return actionActor.system.attack;
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
get targetMode() {
|
get targetMode() {
|
||||||
|
|
@ -95,7 +98,7 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
registerTargetHook() {
|
registerTargetHook() {
|
||||||
if (!this.parent.isAuthor) return;
|
if (!this.parent.isAuthor || !this.hasTarget) return;
|
||||||
if (this.targetMode && this.parent.targetHook !== null) {
|
if (this.targetMode && this.parent.targetHook !== null) {
|
||||||
Hooks.off('targetToken', this.parent.targetHook);
|
Hooks.off('targetToken', this.parent.targetHook);
|
||||||
return (this.parent.targetHook = null);
|
return (this.parent.targetHook = null);
|
||||||
|
|
@ -113,7 +116,7 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
||||||
this.currentTargets = this.getTargetList();
|
this.currentTargets = this.getTargetList();
|
||||||
// this.registerTargetHook();
|
// this.registerTargetHook();
|
||||||
|
|
||||||
if (this.targetMode === true && this.hasRoll) {
|
if (this.hasRoll) {
|
||||||
this.targetShort = this.targets.reduce(
|
this.targetShort = this.targets.reduce(
|
||||||
(a, c) => {
|
(a, c) => {
|
||||||
if (c.hit) a.hit += 1;
|
if (c.hit) a.hit += 1;
|
||||||
|
|
@ -127,7 +130,8 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.canViewSecret = this.parent.speakerActor?.testUserPermission(game.user, 'OBSERVER');
|
this.canViewSecret = this.parent.speakerActor?.testUserPermission(game.user, 'OBSERVER');
|
||||||
this.canButtonApply = game.user.isGM;
|
this.canButtonApply = game.user.isGM; //temp
|
||||||
|
this.isGM = game.user.isGM; //temp
|
||||||
}
|
}
|
||||||
|
|
||||||
getTargetList() {
|
getTargetList() {
|
||||||
|
|
@ -6,6 +6,5 @@ export { default as EffectsField } from './effectsField.mjs';
|
||||||
export { default as SaveField } from './saveField.mjs';
|
export { default as SaveField } from './saveField.mjs';
|
||||||
export { default as BeastformField } from './beastformField.mjs';
|
export { default as BeastformField } from './beastformField.mjs';
|
||||||
export { default as DamageField } from './damageField.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 RollField } from './rollField.mjs';
|
||||||
export { default as MacroField } from './macroField.mjs';
|
export { default as MacroField } from './macroField.mjs';
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,13 @@
|
||||||
|
import BeastformDialog from '../../../applications/dialogs/beastformDialog.mjs';
|
||||||
|
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
export default class BeastformField extends fields.SchemaField {
|
export default class BeastformField extends fields.SchemaField {
|
||||||
|
/**
|
||||||
|
* Action Workflow order
|
||||||
|
*/
|
||||||
|
static order = 90;
|
||||||
|
|
||||||
constructor(options = {}, context = {}) {
|
constructor(options = {}, context = {}) {
|
||||||
const beastformFields = {
|
const beastformFields = {
|
||||||
tierAccess: new fields.SchemaField({
|
tierAccess: new fields.SchemaField({
|
||||||
|
|
@ -27,4 +34,96 @@ export default class BeastformField extends fields.SchemaField {
|
||||||
};
|
};
|
||||||
super(beastformFields, options, context);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,12 @@
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
export default class CostField extends fields.ArrayField {
|
export default class CostField extends fields.ArrayField {
|
||||||
|
/**
|
||||||
|
* Action Workflow order
|
||||||
|
*/
|
||||||
|
static order = 150;
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
constructor(options = {}, context = {}) {
|
constructor(options = {}, context = {}) {
|
||||||
const element = new fields.SchemaField({
|
const element = new fields.SchemaField({
|
||||||
key: new fields.StringField({
|
key: new fields.StringField({
|
||||||
|
|
@ -8,7 +14,7 @@ export default class CostField extends fields.ArrayField {
|
||||||
required: true,
|
required: true,
|
||||||
initial: 'hope'
|
initial: 'hope'
|
||||||
}),
|
}),
|
||||||
keyIsID: new fields.BooleanField(),
|
itemId: new fields.StringField({ nullable: true, initial: null }),
|
||||||
value: new fields.NumberField({ nullable: true, initial: 1, min: 0 }),
|
value: new fields.NumberField({ nullable: true, initial: 1, min: 0 }),
|
||||||
scalable: new fields.BooleanField({ initial: false }),
|
scalable: new fields.BooleanField({ initial: false }),
|
||||||
step: new fields.NumberField({ nullable: true, initial: null }),
|
step: new fields.NumberField({ nullable: true, initial: null }),
|
||||||
|
|
@ -20,18 +26,88 @@ export default class CostField extends fields.ArrayField {
|
||||||
super(element, options, context);
|
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.itemId) {
|
||||||
|
usefulResources[cost.key] = {
|
||||||
|
value: cost.value,
|
||||||
|
target: this.parent.parent,
|
||||||
|
itemId: cost.itemId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
itemId: resource.itemId
|
||||||
|
});
|
||||||
|
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) : [];
|
const costs = this.cost?.length ? foundry.utils.deepClone(this.cost) : [];
|
||||||
config.costs = CostField.calcCosts.call(this, costs);
|
config.costs = CostField.calcCosts.call(this, costs);
|
||||||
const hasCost = CostField.hasCost.call(this, config.costs);
|
const hasCost = CostField.hasCost.call(this, config.costs);
|
||||||
if (config.isFastForward && !hasCost)
|
if (config.dialog.configure === false && !hasCost) {
|
||||||
return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.insufficientResources'));
|
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.insufficientResources'));
|
||||||
return hasCost;
|
return hasCost;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Must be called within Action context.
|
||||||
|
* @param {*} costs
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
static calcCosts(costs) {
|
static calcCosts(costs) {
|
||||||
const resources = CostField.getResources.call(this, costs);
|
const resources = CostField.getResources.call(this, costs);
|
||||||
return costs.map(c => {
|
let filteredCosts = costs;
|
||||||
|
if (this.parent.metadata.isQuantifiable && this.parent.consumeOnUse === false) {
|
||||||
|
filteredCosts = filteredCosts.filter(c => c.key !== 'quantity');
|
||||||
|
}
|
||||||
|
|
||||||
|
return filteredCosts.map(c => {
|
||||||
c.scale = c.scale ?? 0;
|
c.scale = c.scale ?? 0;
|
||||||
c.step = c.step ?? 1;
|
c.step = c.step ?? 1;
|
||||||
c.total = c.value + c.scale * c.step;
|
c.total = c.value + c.scale * c.step;
|
||||||
|
|
@ -40,13 +116,19 @@ export default class CostField extends fields.ArrayField {
|
||||||
c.key === 'fear'
|
c.key === 'fear'
|
||||||
? game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear)
|
? game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear)
|
||||||
: resources[c.key].isReversed
|
: resources[c.key].isReversed
|
||||||
? resources[c.key].max
|
? resources[c.key].max - resources[c.key].value
|
||||||
: resources[c.key].value;
|
: resources[c.key].value;
|
||||||
if (c.scalable) c.maxStep = Math.floor((c.max - c.value) / c.step);
|
if (c.scalable) c.maxStep = Math.floor((c.max - c.value) / c.step);
|
||||||
return c;
|
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) {
|
static hasCost(costs) {
|
||||||
const realCosts = CostField.getRealCosts.call(this, costs),
|
const realCosts = CostField.getRealCosts.call(this, costs),
|
||||||
hasFearCost = realCosts.findIndex(c => c.key === 'fear');
|
hasFearCost = realCosts.findIndex(c => c.key === 'fear');
|
||||||
|
|
@ -73,17 +155,20 @@ 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) {
|
static getResources(costs) {
|
||||||
const actorResources = foundry.utils.deepClone(this.actor.system.resources);
|
const actorResources = foundry.utils.deepClone(this.actor.system.resources);
|
||||||
if (this.actor.system.partner)
|
if (this.actor.system.partner)
|
||||||
actorResources.hope = foundry.utils.deepClone(this.actor.system.partner.system.resources.hope);
|
actorResources.hope = foundry.utils.deepClone(this.actor.system.partner.system.resources.hope);
|
||||||
const itemResources = {};
|
const itemResources = {};
|
||||||
for (let itemResource of costs) {
|
for (let itemResource of costs) {
|
||||||
if (itemResource.keyIsID) {
|
if (itemResource.itemId) {
|
||||||
itemResources[itemResource.key] = {
|
itemResources[itemResource.key] = CostField.getItemIdCostResource.bind(this)(itemResource);
|
||||||
value: this.parent.resource.value ?? 0,
|
|
||||||
max: CostField.formatMax.call(this, this.parent?.resource?.max)
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -93,6 +178,45 @@ export default class CostField extends fields.ArrayField {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getItemIdCostResource(itemResource) {
|
||||||
|
switch (itemResource.key) {
|
||||||
|
case CONFIG.DH.GENERAL.itemAbilityCosts.resource.id:
|
||||||
|
return {
|
||||||
|
value: this.parent.resource.value ?? 0,
|
||||||
|
max: CostField.formatMax.call(this, this.parent?.resource?.max)
|
||||||
|
};
|
||||||
|
case CONFIG.DH.GENERAL.itemAbilityCosts.quantity.id:
|
||||||
|
return {
|
||||||
|
value: this.parent.quantity ?? 0,
|
||||||
|
max: this.parent.quantity ?? 0
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return { value: 0, max: 0 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static getItemIdCostUpdate(r) {
|
||||||
|
switch (r.key) {
|
||||||
|
case CONFIG.DH.GENERAL.itemAbilityCosts.resource.id:
|
||||||
|
return {
|
||||||
|
path: 'system.resource.value',
|
||||||
|
value: r.target.system.resource.value + r.value
|
||||||
|
};
|
||||||
|
case CONFIG.DH.GENERAL.itemAbilityCosts.quantity.id:
|
||||||
|
return {
|
||||||
|
path: 'system.quantity',
|
||||||
|
value: r.target.system.quantity + r.value
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return { path: '', value: undefined };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} costs
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
static getRealCosts(costs) {
|
static getRealCosts(costs) {
|
||||||
const realCosts = costs?.length ? costs.filter(c => c.enabled) : [];
|
const realCosts = costs?.length ? costs.filter(c => c.enabled) : [];
|
||||||
let mergedCosts = [];
|
let mergedCosts = [];
|
||||||
|
|
@ -104,6 +228,12 @@ export default class CostField extends fields.ArrayField {
|
||||||
return mergedCosts;
|
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) {
|
static formatMax(max) {
|
||||||
max ??= 0;
|
max ??= 0;
|
||||||
if (isNaN(max)) {
|
if (isNaN(max)) {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,15 @@
|
||||||
import FormulaField from '../formulaField.mjs';
|
import FormulaField from '../formulaField.mjs';
|
||||||
|
import { setsEqual } from '../../../helpers/utils.mjs';
|
||||||
|
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
export default class DamageField extends fields.SchemaField {
|
export default class DamageField extends fields.SchemaField {
|
||||||
|
/**
|
||||||
|
* Action Workflow order
|
||||||
|
*/
|
||||||
|
static order = 20;
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
constructor(options, context = {}) {
|
constructor(options, context = {}) {
|
||||||
const damageFields = {
|
const damageFields = {
|
||||||
parts: new fields.ArrayField(new fields.EmbeddedDataField(DHDamageData)),
|
parts: new fields.ArrayField(new fields.EmbeddedDataField(DHDamageData)),
|
||||||
|
|
@ -14,6 +21,152 @@ export default class DamageField extends fields.SchemaField {
|
||||||
};
|
};
|
||||||
super(damageFields, options, context);
|
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 {
|
export class DHActionDiceData extends foundry.abstract.DataModel {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,14 @@
|
||||||
|
import { emitAsGM, GMUpdateEvent } from '../../../systemRegistration/socket.mjs';
|
||||||
|
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
export default class EffectsField extends fields.ArrayField {
|
export default class EffectsField extends fields.ArrayField {
|
||||||
|
/**
|
||||||
|
* Action Workflow order
|
||||||
|
*/
|
||||||
|
static order = 100;
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
constructor(options = {}, context = {}) {
|
constructor(options = {}, context = {}) {
|
||||||
const element = new fields.SchemaField({
|
const element = new fields.SchemaField({
|
||||||
_id: new fields.DocumentIdField(),
|
_id: new fields.DocumentIdField(),
|
||||||
|
|
@ -8,4 +16,85 @@ export default class EffectsField extends fields.ArrayField {
|
||||||
});
|
});
|
||||||
super(element, options, context);
|
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)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +1,29 @@
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
export default class MacroField extends fields.DocumentUUIDField {
|
export default class MacroField extends fields.DocumentUUIDField {
|
||||||
|
/**
|
||||||
|
* Action Workflow order
|
||||||
|
*/
|
||||||
|
static order = 70;
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
constructor(context = {}) {
|
constructor(context = {}) {
|
||||||
super({ type: "Macro" }, 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,23 @@
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
export default class RangeField extends fields.StringField {
|
export default class RangeField extends fields.StringField {
|
||||||
|
/** @inheritDoc */
|
||||||
constructor(context = {}) {
|
constructor(context = {}) {
|
||||||
const options = {
|
const options = {
|
||||||
choices: CONFIG.DH.GENERAL.range,
|
choices: CONFIG.DH.GENERAL.range,
|
||||||
required: false,
|
required: false,
|
||||||
blank: true,
|
blank: true,
|
||||||
label: "DAGGERHEART.GENERAL.range"
|
label: 'DAGGERHEART.GENERAL.range'
|
||||||
};
|
};
|
||||||
super(options, context);
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,12 @@ export class DHActionRollData extends foundry.abstract.DataModel {
|
||||||
static defineSchema() {
|
static defineSchema() {
|
||||||
return {
|
return {
|
||||||
type: new fields.StringField({ nullable: true, initial: null, choices: CONFIG.DH.GENERAL.rollTypes }),
|
type: new fields.StringField({ nullable: true, initial: null, choices: CONFIG.DH.GENERAL.rollTypes }),
|
||||||
trait: new fields.StringField({ nullable: true, initial: null, choices: CONFIG.DH.ACTOR.abilities, label: "DAGGERHEART.GENERAL.Trait.single" }),
|
trait: new fields.StringField({
|
||||||
|
nullable: true,
|
||||||
|
initial: null,
|
||||||
|
choices: CONFIG.DH.ACTOR.abilities,
|
||||||
|
label: 'DAGGERHEART.GENERAL.Trait.single'
|
||||||
|
}),
|
||||||
difficulty: new fields.NumberField({ nullable: true, initial: null, integer: true, min: 0 }),
|
difficulty: new fields.NumberField({ nullable: true, initial: null, integer: true, min: 0 }),
|
||||||
bonus: new fields.NumberField({ nullable: true, initial: null, integer: true }),
|
bonus: new fields.NumberField({ nullable: true, initial: null, integer: true }),
|
||||||
advState: new fields.StringField({
|
advState: new fields.StringField({
|
||||||
|
|
@ -71,29 +76,6 @@ export class DHActionRollData extends foundry.abstract.DataModel {
|
||||||
const modifiers = [];
|
const modifiers = [];
|
||||||
if (!this.parent?.actor) return modifiers;
|
if (!this.parent?.actor) return modifiers;
|
||||||
switch (this.parent.actor.type) {
|
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 'companion':
|
||||||
case 'adversary':
|
case 'adversary':
|
||||||
if (this.type === CONFIG.DH.GENERAL.rollTypes.attack.id)
|
if (this.type === CONFIG.DH.GENERAL.rollTypes.attack.id)
|
||||||
|
|
@ -107,10 +89,79 @@ export class DHActionRollData extends foundry.abstract.DataModel {
|
||||||
}
|
}
|
||||||
return modifiers;
|
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 {
|
export default class RollField extends fields.EmbeddedDataField {
|
||||||
|
/**
|
||||||
|
* Action Workflow order
|
||||||
|
*/
|
||||||
|
static order = 10;
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
constructor(options, context = {}) {
|
constructor(options, context = {}) {
|
||||||
super(DHActionRollData, 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)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,14 @@
|
||||||
|
import { abilities } from '../../../config/actorConfig.mjs';
|
||||||
|
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
export default class SaveField extends fields.SchemaField {
|
export default class SaveField extends fields.SchemaField {
|
||||||
|
/**
|
||||||
|
* Action Workflow order
|
||||||
|
*/
|
||||||
|
static order = 50;
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
constructor(options = {}, context = {}) {
|
constructor(options = {}, context = {}) {
|
||||||
const saveFields = {
|
const saveFields = {
|
||||||
trait: new fields.StringField({
|
trait: new fields.StringField({
|
||||||
|
|
@ -16,4 +24,157 @@ export default class SaveField extends fields.SchemaField {
|
||||||
};
|
};
|
||||||
super(saveFields, options, context);
|
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));
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
export default class TargetField extends fields.SchemaField {
|
export default class TargetField extends fields.SchemaField {
|
||||||
|
/** @inheritDoc */
|
||||||
constructor(options = {}, context = {}) {
|
constructor(options = {}, context = {}) {
|
||||||
const targetFields = {
|
const targetFields = {
|
||||||
type: new fields.StringField({
|
type: new fields.StringField({
|
||||||
|
|
@ -13,44 +14,66 @@ export default class TargetField extends fields.SchemaField {
|
||||||
super(targetFields, options, context);
|
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;
|
config.hasTarget = true;
|
||||||
let targets;
|
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)
|
if (this.target?.type === CONFIG.DH.GENERAL.targetTypes.self.id)
|
||||||
targets = [this.actor.token ?? this.actor.prototypeToken];
|
targets = [this.actor.token ?? this.actor.prototypeToken];
|
||||||
else {
|
else {
|
||||||
targets = Array.from(game.user.targets);
|
targets = Array.from(game.user.targets);
|
||||||
if (this.target.type !== CONFIG.DH.GENERAL.targetTypes.any.id) {
|
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 = [];
|
if (this.target.amount && targets.length > this.target.amount) targets = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
config.targets = targets.map(t => TargetField.formatTarget.call(this, t));
|
config.targets = targets.map(t => TargetField.formatTarget.call(this, t));
|
||||||
const hasTargets = TargetField.checkTargets.call(this, this.target.amount, config.targets);
|
const hasTargets = TargetField.checkTargets.call(this, this.target.amount, config.targets);
|
||||||
if (config.isFastForward && !hasTargets)
|
if (config.dialog.configure === false && !hasTargets) {
|
||||||
return ui.notifications.warn('Too many targets selected for that actions.');
|
ui.notifications.warn('Too many targets selected for that actions.');
|
||||||
return hasTargets;
|
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) {
|
static checkTargets(amount, targets) {
|
||||||
return true;
|
return true;
|
||||||
// return !amount || (targets.length > amount);
|
// return !amount || (targets.length > amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
static isTargetFriendly(target) {
|
/**
|
||||||
const actorDisposition = this.actor.token
|
* Compare 2 Actors disposition between each other
|
||||||
? this.actor.token.disposition
|
* @param {*} actor First actor document.
|
||||||
: this.actor.prototypeToken.disposition,
|
* @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;
|
targetDisposition = target.document.disposition;
|
||||||
return (
|
return (
|
||||||
(this.target.type === CONFIG.DH.GENERAL.targetTypes.friendly.id &&
|
(type === CONFIG.DH.GENERAL.targetTypes.friendly.id && actorDisposition === targetDisposition) ||
|
||||||
actorDisposition === targetDisposition) ||
|
(type === CONFIG.DH.GENERAL.targetTypes.hostile.id && actorDisposition + targetDisposition === 0)
|
||||||
(this.target.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) {
|
static formatTarget(actor) {
|
||||||
return {
|
return {
|
||||||
id: actor.id,
|
id: actor.id,
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,12 @@ import FormulaField from '../formulaField.mjs';
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
export default class UsesField extends fields.SchemaField {
|
export default class UsesField extends fields.SchemaField {
|
||||||
|
/**
|
||||||
|
* Action Workflow order
|
||||||
|
*/
|
||||||
|
static order = 160;
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
constructor(options = {}, context = {}) {
|
constructor(options = {}, context = {}) {
|
||||||
const usesFields = {
|
const usesFields = {
|
||||||
value: new fields.NumberField({ nullable: true, initial: null }),
|
value: new fields.NumberField({ nullable: true, initial: null }),
|
||||||
|
|
@ -20,16 +26,45 @@ export default class UsesField extends fields.SchemaField {
|
||||||
super(usesFields, options, context);
|
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;
|
const uses = this.uses?.max ? foundry.utils.deepClone(this.uses) : null;
|
||||||
if (uses && !uses.value) uses.value = 0;
|
if (uses && !uses.value) uses.value = 0;
|
||||||
config.uses = uses;
|
config.uses = uses;
|
||||||
const hasUses = UsesField.hasUses.call(this, config.uses);
|
const hasUses = UsesField.hasUses.call(this, config.uses);
|
||||||
if (config.isFastForward && !hasUses)
|
if (config.dialog.configure === false && !hasUses) {
|
||||||
return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.actionNoUsesRemaining'));
|
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.actionNoUsesRemaining'));
|
||||||
return hasUses;
|
return hasUses;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare Uses object for Action Workflow
|
||||||
|
* Must be called within Action context.
|
||||||
|
* @param {object} uses
|
||||||
|
* @returns {object}
|
||||||
|
*/
|
||||||
static calcUses(uses) {
|
static calcUses(uses) {
|
||||||
if (!uses) return null;
|
if (!uses) return null;
|
||||||
return {
|
return {
|
||||||
|
|
@ -39,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) {
|
static hasUses(uses) {
|
||||||
if (!uses) return true;
|
if (!uses) return true;
|
||||||
let max = uses.max ?? 0;
|
let max = uses.max ?? 0;
|
||||||
|
|
|
||||||
|
|
@ -162,7 +162,6 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
||||||
for (let f of this.features) {
|
for (let f of this.features) {
|
||||||
const fBase = f.item ?? f;
|
const fBase = f.item ?? f;
|
||||||
const feature = fBase.system ? fBase : await foundry.utils.fromUuid(fBase.uuid);
|
const feature = fBase.system ? fBase : await foundry.utils.fromUuid(fBase.uuid);
|
||||||
const multiclass = this.isMulticlass ? 'multiclass' : null;
|
|
||||||
features.push(
|
features.push(
|
||||||
foundry.utils.mergeObject(
|
foundry.utils.mergeObject(
|
||||||
feature.toObject(),
|
feature.toObject(),
|
||||||
|
|
@ -170,7 +169,8 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
||||||
_stats: { compendiumSource: fBase.uuid },
|
_stats: { compendiumSource: fBase.uuid },
|
||||||
system: {
|
system: {
|
||||||
originItemType: this.parent.type,
|
originItemType: this.parent.type,
|
||||||
identifier: multiclass ?? (f.item ? f.type : null)
|
identifier: f.item ? f.type : null,
|
||||||
|
multiclassOrigin: this.isMulticlass
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ inplace: false }
|
{ inplace: false }
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,8 @@ export default class DHClass extends BaseDataItem {
|
||||||
suggestedSecondaryWeapon: new ForeignDocumentUUIDField({ type: 'Item' }),
|
suggestedSecondaryWeapon: new ForeignDocumentUUIDField({ type: 'Item' }),
|
||||||
suggestedArmor: new ForeignDocumentUUIDField({ type: 'Item' })
|
suggestedArmor: new ForeignDocumentUUIDField({ type: 'Item' })
|
||||||
}),
|
}),
|
||||||
|
backgroundQuestions: new fields.ArrayField(new fields.StringField(), { initial: ['', '', ''] }),
|
||||||
|
connections: new fields.ArrayField(new fields.StringField(), { initial: ['', '', ''] }),
|
||||||
isMulticlass: new fields.BooleanField({ initial: false })
|
isMulticlass: new fields.BooleanField({ initial: false })
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -96,6 +98,20 @@ export default class DHClass extends BaseDataItem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!data.system.isMulticlass) {
|
||||||
|
const addQuestions = (base, questions) => {
|
||||||
|
return `${base}${questions.map(q => `<p><strong>${q}</strong></p>`).join('<br/>')}`;
|
||||||
|
};
|
||||||
|
const backgroundQuestions = data.system.backgroundQuestions.filter(x => x);
|
||||||
|
const connections = data.system.connections.filter(x => x);
|
||||||
|
await this.actor.update({
|
||||||
|
'system.biography': {
|
||||||
|
background: addQuestions(this.actor.system.biography.background, backgroundQuestions),
|
||||||
|
connections: addQuestions(this.actor.system.biography.connections, connections)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const allowed = await super._preCreate(data, options, user);
|
const allowed = await super._preCreate(data, options, user);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import BaseDataItem from './base.mjs';
|
import BaseDataItem from './base.mjs';
|
||||||
import { ActionField } from '../fields/actionField.mjs';
|
|
||||||
|
|
||||||
export default class DHConsumable extends BaseDataItem {
|
export default class DHConsumable extends BaseDataItem {
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
|
|
@ -19,7 +18,8 @@ export default class DHConsumable extends BaseDataItem {
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
return {
|
return {
|
||||||
...super.defineSchema(),
|
...super.defineSchema(),
|
||||||
consumeOnUse: new fields.BooleanField({ initial: false })
|
consumeOnUse: new fields.BooleanField({ initial: true }),
|
||||||
|
destroyOnEmpty: new fields.BooleanField({ initial: true })
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -27,5 +27,4 @@ export default class DHConsumable extends BaseDataItem {
|
||||||
|
|
||||||
/**@override */
|
/**@override */
|
||||||
static DEFAULT_ICON = 'systems/daggerheart/assets/icons/documents/items/round-potion.svg';
|
static DEFAULT_ICON = 'systems/daggerheart/assets/icons/documents/items/round-potion.svg';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ export default class DHFeature extends BaseDataItem {
|
||||||
nullable: true,
|
nullable: true,
|
||||||
initial: null
|
initial: null
|
||||||
}),
|
}),
|
||||||
|
multiclassOrigin: new fields.BooleanField({ initial: false }),
|
||||||
identifier: new fields.StringField()
|
identifier: new fields.StringField()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
||||||
import ItemLinkFields from '../fields/itemLinkFields.mjs';
|
import ItemLinkFields from '../fields/itemLinkFields.mjs';
|
||||||
import BaseDataItem from './base.mjs';
|
import BaseDataItem from './base.mjs';
|
||||||
|
|
||||||
|
|
@ -25,7 +26,8 @@ export default class DHSubclass extends BaseDataItem {
|
||||||
}),
|
}),
|
||||||
features: new ItemLinkFields(),
|
features: new ItemLinkFields(),
|
||||||
featureState: new fields.NumberField({ required: true, initial: 1, min: 1 }),
|
featureState: new fields.NumberField({ required: true, initial: 1, min: 1 }),
|
||||||
isMulticlass: new fields.BooleanField({ initial: false })
|
isMulticlass: new fields.BooleanField({ initial: false }),
|
||||||
|
linkedClass: new ForeignDocumentUUIDField({ type: 'Item', nullable: true, initial: null })
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -50,8 +52,7 @@ export default class DHSubclass extends BaseDataItem {
|
||||||
|
|
||||||
async _preCreate(data, options, user) {
|
async _preCreate(data, options, user) {
|
||||||
if (this.actor?.type === 'character') {
|
if (this.actor?.type === 'character') {
|
||||||
const dataUuid =
|
const dataUuid = data.uuid ?? data._stats.compendiumSource ?? `Item.${data._id}`;
|
||||||
data.uuid ?? (data.folder ? `Compendium.daggerheart.subclasses.Item.${data._id}` : `Item.${data._id}`);
|
|
||||||
if (this.actor.system.class.subclass) {
|
if (this.actor.system.class.subclass) {
|
||||||
if (this.actor.system.multiclass.subclass) {
|
if (this.actor.system.multiclass.subclass) {
|
||||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.subclassesAlreadyPresent'));
|
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.subclassesAlreadyPresent'));
|
||||||
|
|
|
||||||
|
|
@ -1,100 +1,46 @@
|
||||||
import { fearDisplay } from '../../config/generalConfig.mjs';
|
|
||||||
|
|
||||||
export default class DhAppearance extends foundry.abstract.DataModel {
|
export default class DhAppearance extends foundry.abstract.DataModel {
|
||||||
|
static LOCALIZATION_PREFIXES = ['DAGGERHEART.SETTINGS.Appearance'];
|
||||||
|
|
||||||
static defineSchema() {
|
static defineSchema() {
|
||||||
const fields = foundry.data.fields;
|
const { StringField, ColorField, BooleanField, SchemaField } = foundry.data.fields;
|
||||||
|
|
||||||
|
// helper to create dice style schema
|
||||||
|
const diceStyle = ({ fg, bg, outline, edge }) =>
|
||||||
|
new SchemaField({
|
||||||
|
foreground: new ColorField({ required: true, initial: fg }),
|
||||||
|
background: new ColorField({ required: true, initial: bg }),
|
||||||
|
outline: new ColorField({ required: true, initial: outline }),
|
||||||
|
edge: new ColorField({ required: true, initial: edge }),
|
||||||
|
texture: new StringField({ initial: 'astralsea', required: true, blank: false }),
|
||||||
|
colorset: new StringField({ initial: 'inspired', required: true, blank: false }),
|
||||||
|
material: new StringField({ initial: 'metal', required: true, blank: false }),
|
||||||
|
system: new StringField({ initial: 'standard', required: true, blank: false })
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
displayFear: new fields.StringField({
|
displayFear: new StringField({
|
||||||
required: true,
|
required: true,
|
||||||
choices: fearDisplay,
|
choices: CONFIG.DH.GENERAL.fearDisplay,
|
||||||
initial: fearDisplay.token.value,
|
initial: CONFIG.DH.GENERAL.fearDisplay.token.value
|
||||||
label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.displayFear.label'
|
|
||||||
}),
|
}),
|
||||||
diceSoNice: new fields.SchemaField({
|
diceSoNice: new SchemaField({
|
||||||
hope: new fields.SchemaField({
|
hope: diceStyle({ fg: '#ffffff', bg: '#ffe760', outline: '#000000', edge: '#ffffff' }),
|
||||||
foreground: new fields.ColorField({ required: true, initial: '#ffffff' }),
|
fear: diceStyle({ fg: '#000000', bg: '#0032b1', outline: '#ffffff', edge: '#000000' }),
|
||||||
background: new fields.ColorField({ required: true, initial: '#ffe760' }),
|
advantage: diceStyle({ fg: '#ffffff', bg: '#008000', outline: '#000000', edge: '#ffffff' }),
|
||||||
outline: new fields.ColorField({ required: true, initial: '#000000' }),
|
disadvantage: diceStyle({ fg: '#000000', bg: '#b30000', outline: '#ffffff', edge: '#000000' })
|
||||||
edge: new fields.ColorField({ required: true, initial: '#ffffff' }),
|
|
||||||
texture: new fields.StringField({ initial: 'astralsea' }),
|
|
||||||
colorset: new fields.StringField({ initial: 'inspired' }),
|
|
||||||
material: new fields.StringField({ initial: 'metal' }),
|
|
||||||
system: new fields.StringField({ initial: 'standard' })
|
|
||||||
}),
|
|
||||||
fear: new fields.SchemaField({
|
|
||||||
foreground: new fields.ColorField({ required: true, initial: '#000000' }),
|
|
||||||
background: new fields.ColorField({ required: true, initial: '#0032b1' }),
|
|
||||||
outline: new fields.ColorField({ required: true, initial: '#ffffff' }),
|
|
||||||
edge: new fields.ColorField({ required: true, initial: '#000000' }),
|
|
||||||
texture: new fields.StringField({ initial: 'astralsea' }),
|
|
||||||
colorset: new fields.StringField({ initial: 'inspired' }),
|
|
||||||
material: new fields.StringField({ initial: 'metal' }),
|
|
||||||
system: new fields.StringField({ initial: 'standard' })
|
|
||||||
}),
|
|
||||||
advantage: new fields.SchemaField({
|
|
||||||
foreground: new fields.ColorField({ required: true, initial: '#ffffff' }),
|
|
||||||
background: new fields.ColorField({ required: true, initial: '#008000' }),
|
|
||||||
outline: new fields.ColorField({ required: true, initial: '#000000' }),
|
|
||||||
edge: new fields.ColorField({ required: true, initial: '#ffffff' }),
|
|
||||||
texture: new fields.StringField({ initial: 'astralsea' }),
|
|
||||||
colorset: new fields.StringField({ initial: 'inspired' }),
|
|
||||||
material: new fields.StringField({ initial: 'metal' }),
|
|
||||||
system: new fields.StringField({ initial: 'standard' })
|
|
||||||
}),
|
|
||||||
disadvantage: new fields.SchemaField({
|
|
||||||
foreground: new fields.ColorField({ required: true, initial: '#000000' }),
|
|
||||||
background: new fields.ColorField({ required: true, initial: '#b30000' }),
|
|
||||||
outline: new fields.ColorField({ required: true, initial: '#ffffff' }),
|
|
||||||
edge: new fields.ColorField({ required: true, initial: '#000000' }),
|
|
||||||
texture: new fields.StringField({ initial: 'astralsea' }),
|
|
||||||
colorset: new fields.StringField({ initial: 'inspired' }),
|
|
||||||
material: new fields.StringField({ initial: 'metal' }),
|
|
||||||
system: new fields.StringField({ initial: 'standard' })
|
|
||||||
})
|
|
||||||
}),
|
}),
|
||||||
showGenericStatusEffects: new fields.BooleanField({
|
extendCharacterDescriptions: new BooleanField(),
|
||||||
initial: true,
|
extendAdversaryDescriptions: new BooleanField(),
|
||||||
label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.showGenericStatusEffects.label'
|
extendEnvironmentDescriptions: new BooleanField(),
|
||||||
|
extendItemDescriptions: new BooleanField(),
|
||||||
|
expandRollMessage: new SchemaField({
|
||||||
|
desc: new BooleanField(),
|
||||||
|
roll: new BooleanField(),
|
||||||
|
damage: new BooleanField(),
|
||||||
|
target: new BooleanField()
|
||||||
}),
|
}),
|
||||||
extendCharacterDescriptions: new fields.BooleanField({
|
hideAttribution: new BooleanField(),
|
||||||
initial: false,
|
showGenericStatusEffects: new BooleanField({ initial: true })
|
||||||
label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.extendCharacterDescriptions.label'
|
|
||||||
}),
|
|
||||||
extendAdversaryDescriptions: new fields.BooleanField({
|
|
||||||
initial: false,
|
|
||||||
label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.extendAdversaryDescriptions.label'
|
|
||||||
}),
|
|
||||||
extendEnvironmentDescriptions: new fields.BooleanField({
|
|
||||||
initial: false,
|
|
||||||
label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.extendEnvironmentDescriptions.label'
|
|
||||||
}),
|
|
||||||
extendItemDescriptions: new fields.BooleanField({
|
|
||||||
initial: false,
|
|
||||||
label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.extendItemDescriptions.label'
|
|
||||||
}),
|
|
||||||
expandRollMessage: new fields.SchemaField({
|
|
||||||
desc: new fields.BooleanField({
|
|
||||||
initial: false,
|
|
||||||
label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.expandRollMessageDesc.label'
|
|
||||||
}),
|
|
||||||
roll: new fields.BooleanField({
|
|
||||||
initial: false,
|
|
||||||
label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.expandRollMessageRoll.label'
|
|
||||||
}),
|
|
||||||
damage: new fields.BooleanField({
|
|
||||||
initial: false,
|
|
||||||
label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.expandRollMessageDamage.label'
|
|
||||||
}),
|
|
||||||
target: new fields.BooleanField({
|
|
||||||
initial: false,
|
|
||||||
label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.expandRollMessageTarget.label'
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
hideAttribution: new fields.BooleanField({
|
|
||||||
required: true,
|
|
||||||
initial: false,
|
|
||||||
label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.hideAttribution.label'
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,72 @@ export default class DhAutomation extends foundry.abstract.DataModel {
|
||||||
initial: CONFIG.DH.GENERAL.defeatedConditions.defeated.id,
|
initial: CONFIG.DH.GENERAL.defeatedConditions.defeated.id,
|
||||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.defeated.companionDefault.label'
|
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'
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -128,7 +128,9 @@ export default class D20Roll extends DHRoll {
|
||||||
applyBaseBonus() {
|
applyBaseBonus() {
|
||||||
const modifiers = foundry.utils.deepClone(this.options.roll.baseModifiers) ?? [];
|
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(
|
modifiers.push(
|
||||||
...this.getBonus(`roll.${this.options.roll.type}`, `${this.options.roll.type?.capitalize()} Bonus`)
|
...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 = {}) {
|
static postEvaluate(roll, config = {}) {
|
||||||
const data = super.postEvaluate(roll, config);
|
const data = super.postEvaluate(roll, config);
|
||||||
data.type = config.roll?.type;
|
data.type = config.actionType;
|
||||||
data.difficulty = config.roll.difficulty;
|
data.difficulty = config.roll.difficulty;
|
||||||
if (config.targets?.length) {
|
if (config.targets?.length) {
|
||||||
config.targets.forEach(target => {
|
config.targets.forEach(target => {
|
||||||
|
|
@ -147,6 +149,7 @@ export default class D20Roll extends DHRoll {
|
||||||
});
|
});
|
||||||
data.success = config.targets.some(target => target.hit);
|
data.success = config.targets.some(target => target.hit);
|
||||||
} else if (config.roll.difficulty) data.success = roll.isCritical || roll.total >= config.roll.difficulty;
|
} else if (config.roll.difficulty) data.success = roll.isCritical || roll.total >= config.roll.difficulty;
|
||||||
|
config.successConsumed = data.success;
|
||||||
|
|
||||||
data.advantage = {
|
data.advantage = {
|
||||||
type: config.roll.advantage,
|
type: config.roll.advantage,
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ export default class DamageRoll extends DHRoll {
|
||||||
static DefaultDialog = DamageDialog;
|
static DefaultDialog = DamageDialog;
|
||||||
|
|
||||||
static async buildEvaluate(roll, config = {}, message = {}) {
|
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();
|
if (config.evaluate !== false) for (const roll of config.roll) await roll.roll.evaluate();
|
||||||
|
|
||||||
roll._evaluated = true;
|
roll._evaluated = true;
|
||||||
|
|
@ -46,9 +47,7 @@ export default class DamageRoll extends DHRoll {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
await super.buildPost(roll, config, message);
|
await super.buildPost(roll, config, message);
|
||||||
if (config.source?.message) {
|
if (config.source?.message) chatMessage.update({ 'system.damage': config.damage });
|
||||||
chatMessage.update({ 'system.damage': config.damage });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static unifyDamageRoll(rolls) {
|
static unifyDamageRoll(rolls) {
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ export default class DHRoll extends Roll {
|
||||||
static async buildConfigure(config = {}, message = {}) {
|
static async buildConfigure(config = {}, message = {}) {
|
||||||
config.hooks = [...this.getHooks(), ''];
|
config.hooks = [...this.getHooks(), ''];
|
||||||
config.dialog ??= {};
|
config.dialog ??= {};
|
||||||
|
|
||||||
for (const hook of config.hooks) {
|
for (const hook of config.hooks) {
|
||||||
if (Hooks.call(`${CONFIG.DH.id}.preRoll${hook.capitalize()}`, config, message) === false) return null;
|
if (Hooks.call(`${CONFIG.DH.id}.preRoll${hook.capitalize()}`, config, message) === false) return null;
|
||||||
}
|
}
|
||||||
|
|
@ -88,11 +89,12 @@ export default class DHRoll extends Roll {
|
||||||
type: this.messageType,
|
type: this.messageType,
|
||||||
user: game.user.id,
|
user: game.user.id,
|
||||||
title: roll.title,
|
title: roll.title,
|
||||||
speaker: cls.getSpeaker(),
|
speaker: cls.getSpeaker({ actor: roll.data?.parent }),
|
||||||
sound: config.mute ? null : CONFIG.sounds.dice,
|
sound: config.mute ? null : CONFIG.sounds.dice,
|
||||||
system: config,
|
system: config,
|
||||||
rolls: [roll]
|
rolls: [roll]
|
||||||
};
|
};
|
||||||
|
|
||||||
config.selectedRollMode ??= game.settings.get('core', 'rollMode');
|
config.selectedRollMode ??= game.settings.get('core', 'rollMode');
|
||||||
|
|
||||||
if (roll._evaluated) {
|
if (roll._evaluated) {
|
||||||
|
|
@ -226,7 +228,7 @@ export const registerRollDiceHooks = () => {
|
||||||
if (
|
if (
|
||||||
!config.source?.actor ||
|
!config.source?.actor ||
|
||||||
(game.user.isGM ? !hopeFearAutomation.gm : !hopeFearAutomation.players) ||
|
(game.user.isGM ? !hopeFearAutomation.gm : !hopeFearAutomation.players) ||
|
||||||
config.roll.type === 'reaction'
|
config.actionType === 'reaction'
|
||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ export default class DualityRoll extends D20Roll {
|
||||||
|
|
||||||
get title() {
|
get title() {
|
||||||
return game.i18n.localize(
|
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'}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -154,9 +154,12 @@ export default class DualityRoll extends D20Roll {
|
||||||
applyBaseBonus() {
|
applyBaseBonus() {
|
||||||
const modifiers = super.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({
|
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
|
value: this.data.traits[this.options.roll.trait].value
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -167,13 +167,11 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
|
||||||
|
|
||||||
if (subclass) {
|
if (subclass) {
|
||||||
const featureState = subclass.system.featureState;
|
const featureState = subclass.system.featureState;
|
||||||
const featureType = subclass
|
|
||||||
? (subclass.system.features.find(x => x.item?.uuid === this.parent.uuid)?.type ?? null)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(featureType === CONFIG.DH.ITEM.featureSubTypes.specialization && featureState < 2) ||
|
(this.parent.system.identifier === CONFIG.DH.ITEM.featureSubTypes.specialization &&
|
||||||
(featureType === CONFIG.DH.ITEM.featureSubTypes.mastery && featureState < 3)
|
featureState < 2) ||
|
||||||
|
(this.parent.system.identifier === CONFIG.DH.ITEM.featureSubTypes.mastery && featureState < 3)
|
||||||
) {
|
) {
|
||||||
this.transfer = false;
|
this.transfer = false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { emitAsGM, GMUpdateEvent } from '../systemRegistration/socket.mjs';
|
import { emitAsGM, GMUpdateEvent } from '../systemRegistration/socket.mjs';
|
||||||
import { LevelOptionType } from '../data/levelTier.mjs';
|
import { LevelOptionType } from '../data/levelTier.mjs';
|
||||||
import DHFeature from '../data/item/feature.mjs';
|
import DHFeature from '../data/item/feature.mjs';
|
||||||
import { createScrollText, damageKeyToNumber, versionCompare } from '../helpers/utils.mjs';
|
import { createScrollText, damageKeyToNumber } from '../helpers/utils.mjs';
|
||||||
import DhCompanionLevelUp from '../applications/levelup/companionLevelup.mjs';
|
import DhCompanionLevelUp from '../applications/levelup/companionLevelup.mjs';
|
||||||
|
|
||||||
export default class DhpActor extends Actor {
|
export default class DhpActor extends Actor {
|
||||||
|
|
@ -14,7 +14,7 @@ export default class DhpActor extends Actor {
|
||||||
get owner() {
|
get owner() {
|
||||||
const user =
|
const user =
|
||||||
this.hasPlayerOwner && game.users.players.find(u => this.testUserPermission(u, 'OWNER') && u.active);
|
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;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -167,10 +167,10 @@ export default class DhpActor extends Actor {
|
||||||
if (multiclass) {
|
if (multiclass) {
|
||||||
const multiclassItem = this.items.find(x => x.uuid === multiclass.itemUuid);
|
const multiclassItem = this.items.find(x => x.uuid === multiclass.itemUuid);
|
||||||
const multiclassFeatures = this.items.filter(
|
const multiclassFeatures = this.items.filter(
|
||||||
x => x.system.originItemType === 'class' && x.system.identifier === 'multiclass'
|
x => x.system.originItemType === 'class' && x.system.multiclassOrigin
|
||||||
);
|
);
|
||||||
const subclassFeatures = this.items.filter(
|
const subclassFeatures = this.items.filter(
|
||||||
x => x.system.originItemType === 'subclass' && x.system.identifier === 'multiclass'
|
x => x.system.originItemType === 'subclass' && x.system.multiclassOrigin
|
||||||
);
|
);
|
||||||
|
|
||||||
this.deleteEmbeddedDocuments(
|
this.deleteEmbeddedDocuments(
|
||||||
|
|
@ -659,13 +659,22 @@ export default class DhpActor extends Actor {
|
||||||
};
|
};
|
||||||
|
|
||||||
resources.forEach(r => {
|
resources.forEach(r => {
|
||||||
if (r.keyIsID) {
|
if (r.itemId) {
|
||||||
updates.items[r.key] = {
|
const { path, value } = game.system.api.fields.ActionFields.CostField.getItemIdCostUpdate(r);
|
||||||
target: r.target,
|
|
||||||
resources: {
|
if (
|
||||||
'system.resource.value': r.target.system.resource.value + r.value
|
r.key === 'quantity' &&
|
||||||
}
|
r.target.type === 'consumable' &&
|
||||||
};
|
value === 0 &&
|
||||||
|
r.target.system.destroyOnEmpty
|
||||||
|
) {
|
||||||
|
r.target.delete();
|
||||||
|
} else {
|
||||||
|
updates.items[r.key] = {
|
||||||
|
target: r.target,
|
||||||
|
resources: { [path]: value }
|
||||||
|
};
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
switch (r.key) {
|
switch (r.key) {
|
||||||
case 'fear':
|
case 'fear':
|
||||||
|
|
@ -782,7 +791,7 @@ export default class DhpActor extends Actor {
|
||||||
}
|
}
|
||||||
|
|
||||||
const parsedJSON = JSON.parse(json);
|
const parsedJSON = JSON.parse(json);
|
||||||
if (versionCompare(parsedJSON._stats.systemVersion, '1.1.0')) {
|
if (foundry.utils.isNewerVersion('1.1.0', parsedJSON._stats.systemVersion)) {
|
||||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||||
window: {
|
window: {
|
||||||
title: game.i18n.localize('DAGGERHEART.ACTORS.Character.InvalidOldCharacterImportTitle')
|
title: game.i18n.localize('DAGGERHEART.ACTORS.Character.InvalidOldCharacterImportTitle')
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { emitAsGM, GMUpdateEvent } from '../systemRegistration/socket.mjs';
|
||||||
|
|
||||||
export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
||||||
targetHook = null;
|
targetHook = null;
|
||||||
|
|
||||||
|
|
@ -103,19 +105,29 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
||||||
if (itemDesc && autoExpandRoll.desc) itemDesc.setAttribute('open', '');
|
if (itemDesc && autoExpandRoll.desc) itemDesc.setAttribute('open', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!game.user.isGM) {
|
if (!this.isAuthor && !this.speakerActor?.isOwner) {
|
||||||
const applyButtons = html.querySelector('.apply-buttons');
|
const applyButtons = html.querySelector('.apply-buttons');
|
||||||
applyButtons?.remove();
|
applyButtons?.remove();
|
||||||
if (!this.isAuthor && !this.speakerActor?.isOwner) {
|
const buttons = html.querySelectorAll('.ability-card-footer > .ability-use-button');
|
||||||
const buttons = html.querySelectorAll('.ability-card-footer > .ability-use-button');
|
buttons.forEach(b => b.remove());
|
||||||
buttons.forEach(b => b.remove());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addChatListeners(html) {
|
addChatListeners(html) {
|
||||||
|
html.querySelectorAll('.duality-action-damage').forEach(element =>
|
||||||
|
element.addEventListener('click', this.onRollDamage.bind(this))
|
||||||
|
);
|
||||||
|
|
||||||
html.querySelectorAll('.damage-button').forEach(element =>
|
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 =>
|
html.querySelectorAll('.duality-action-effect').forEach(element =>
|
||||||
|
|
@ -133,17 +145,21 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getTargetList() {
|
async onRollDamage(event) {
|
||||||
const targets = this.system.hitTargets ?? [];
|
event.stopPropagation();
|
||||||
return targets.map(target => game.canvas.tokens.documentCollection.find(t => t.actor?.uuid === target.actorId));
|
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();
|
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) {
|
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) {
|
if (pendingingSaves.length) {
|
||||||
const confirm = await foundry.applications.api.DialogV2.confirm({
|
const confirm = await foundry.applications.api.DialogV2.confirm({
|
||||||
window: { title: 'Pending Reaction Rolls found' },
|
window: { title: 'Pending Reaction Rolls found' },
|
||||||
|
|
@ -154,62 +170,66 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (targets.length === 0)
|
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'));
|
||||||
|
|
||||||
for (let target of targets) {
|
this.consumeOnSuccess();
|
||||||
let damages = foundry.utils.deepClone(this.system.damage);
|
this.system.action?.workflow.get('applyDamage')?.execute(config, targets, true);
|
||||||
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();
|
async onRollSave(event) {
|
||||||
if (this.system.hasHealing) target.actor.takeHealing(damages);
|
event.stopPropagation();
|
||||||
else target.actor.takeDamage(damages, this.system.isDirect);
|
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) {
|
async onRollAllSave(event) {
|
||||||
const item = actor.items.get(itemId),
|
event.stopPropagation();
|
||||||
action =
|
if (!game.user.isGM) return;
|
||||||
actor.system.attack?._id === actionId
|
const targets = this.system.hitTargets,
|
||||||
? actor.system.attack
|
config = foundry.utils.deepClone(this.system);
|
||||||
: item.system.attack?._id === actionId
|
config.event = event;
|
||||||
? item.system.attack
|
this.system.action?.workflow.get('save')?.execute(config, targets, true);
|
||||||
: item?.system?.actions?.get(actionId);
|
|
||||||
return action;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async onApplyEffect(event) {
|
async onApplyEffect(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const actor = await foundry.utils.fromUuid(this.system.source.actor);
|
const targets = this.filterPermTargets(this.system.hitTargets),
|
||||||
if (!actor || !game.user.isGM) return true;
|
config = foundry.utils.deepClone(this.system);
|
||||||
if (this.system.source.item && this.system.source.action) {
|
config.event = event;
|
||||||
const action = this.getAction(actor, this.system.source.item, this.system.source.action);
|
if (targets.length === 0)
|
||||||
if (!action || !action?.applyEffects) return;
|
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelectedOrPerm'));
|
||||||
const targets = this.getTargetList();
|
this.consumeOnSuccess();
|
||||||
if (targets.length === 0)
|
this.system.action?.workflow.get('effects')?.execute(config, targets, true);
|
||||||
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelected'));
|
}
|
||||||
this.consumeOnSuccess();
|
|
||||||
await action.applyEffects(event, this, targets);
|
filterPermTargets(targets) {
|
||||||
}
|
return targets.filter(t => fromUuidSync(t.actorId)?.canUserModify(game.user, 'update'));
|
||||||
}
|
}
|
||||||
|
|
||||||
consumeOnSuccess() {
|
consumeOnSuccess() {
|
||||||
if (!this.system.successConsumed && !this.targetSelection) {
|
if (!this.system.successConsumed && !this.targetSelection) this.system.action?.consume(this.system, true);
|
||||||
const action = this.system.action;
|
|
||||||
if (action) action.consume(this.system, true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hoverTarget(event) {
|
hoverTarget(event) {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@ export default function DhDamageEnricher(match, _options) {
|
||||||
const parts = match[1].split('|').map(x => x.trim());
|
const parts = match[1].split('|').map(x => x.trim());
|
||||||
|
|
||||||
let value = null,
|
let value = null,
|
||||||
type = null;
|
type = null,
|
||||||
|
inline = false;
|
||||||
|
|
||||||
parts.forEach(part => {
|
parts.forEach(part => {
|
||||||
const split = part.split(':').map(x => x.toLowerCase().trim());
|
const split = part.split(':').map(x => x.toLowerCase().trim());
|
||||||
|
|
@ -14,16 +15,19 @@ export default function DhDamageEnricher(match, _options) {
|
||||||
case 'type':
|
case 'type':
|
||||||
type = split[1];
|
type = split[1];
|
||||||
break;
|
break;
|
||||||
|
case 'inline':
|
||||||
|
inline = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!value || !value) return match[0];
|
if (!value || !value) return match[0];
|
||||||
|
|
||||||
return getDamageMessage(value, type, match[0]);
|
return getDamageMessage(value, type, inline, match[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDamageMessage(damage, type, defaultElement) {
|
function getDamageMessage(damage, type, inline, defaultElement) {
|
||||||
const typeIcons = type
|
const typeIcons = type
|
||||||
.replace('[', '')
|
.replace('[', '')
|
||||||
.replace(']', '')
|
.replace(']', '')
|
||||||
|
|
@ -40,7 +44,7 @@ function getDamageMessage(damage, type, defaultElement) {
|
||||||
|
|
||||||
const dualityElement = document.createElement('span');
|
const dualityElement = document.createElement('span');
|
||||||
dualityElement.innerHTML = `
|
dualityElement.innerHTML = `
|
||||||
<button class="enriched-damage-button"
|
<button type="button" class="enriched-damage-button${inline ? ' inline' : ''}"
|
||||||
data-value="${damage}"
|
data-value="${damage}"
|
||||||
data-type="${type}"
|
data-type="${type}"
|
||||||
data-tooltip="${game.i18n.localize('DAGGERHEART.GENERAL.damage')}"
|
data-tooltip="${game.i18n.localize('DAGGERHEART.GENERAL.damage')}"
|
||||||
|
|
@ -67,6 +71,11 @@ export const renderDamageButton = async event => {
|
||||||
title: game.i18n.localize('Damage Roll'),
|
title: game.i18n.localize('Damage Roll'),
|
||||||
data: { bonuses: [] },
|
data: { bonuses: [] },
|
||||||
source: {},
|
source: {},
|
||||||
|
hasDamage: true,
|
||||||
|
hasTarget: true,
|
||||||
|
targets: Array.from(game.user.targets).map(t =>
|
||||||
|
game.system.api.fields.ActionFields.TargetField.formatTarget(t)
|
||||||
|
),
|
||||||
roll: [
|
roll: [
|
||||||
{
|
{
|
||||||
formula: value,
|
formula: value,
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ function getDualityMessage(roll, flavor) {
|
||||||
|
|
||||||
const dualityElement = document.createElement('span');
|
const dualityElement = document.createElement('span');
|
||||||
dualityElement.innerHTML = `
|
dualityElement.innerHTML = `
|
||||||
<button class="duality-roll-button"
|
<button type="button" class="duality-roll-button${roll.inline ? ' inline' : ''}"
|
||||||
data-title="${label}"
|
data-title="${label}"
|
||||||
data-label="${dataLabel}"
|
data-label="${dataLabel}"
|
||||||
data-reaction="${roll.reaction ? 'true' : 'false'}"
|
data-reaction="${roll.reaction ? 'true' : 'false'}"
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@ export default function DhTemplateEnricher(match, _options) {
|
||||||
const parts = match[1].split('|').map(x => x.trim());
|
const parts = match[1].split('|').map(x => x.trim());
|
||||||
|
|
||||||
let type = null,
|
let type = null,
|
||||||
range = null;
|
range = null,
|
||||||
|
inline = false;
|
||||||
|
|
||||||
parts.forEach(part => {
|
parts.forEach(part => {
|
||||||
const split = part.split(':').map(x => x.toLowerCase().trim());
|
const split = part.split(':').map(x => x.toLowerCase().trim());
|
||||||
|
|
@ -20,6 +21,9 @@ export default function DhTemplateEnricher(match, _options) {
|
||||||
);
|
);
|
||||||
range = matchedRange?.id;
|
range = matchedRange?.id;
|
||||||
break;
|
break;
|
||||||
|
case 'inline':
|
||||||
|
inline = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -30,7 +34,7 @@ export default function DhTemplateEnricher(match, _options) {
|
||||||
|
|
||||||
const templateElement = document.createElement('span');
|
const templateElement = document.createElement('span');
|
||||||
templateElement.innerHTML = `
|
templateElement.innerHTML = `
|
||||||
<button class="measured-template-button" data-type="${type}" data-range="${range}">
|
<button type="button" class="measured-template-button${inline ? ' inline' : ''}" data-type="${type}" data-range="${range}">
|
||||||
${label} - ${game.i18n.localize(`DAGGERHEART.CONFIG.Range.${range}.name`)}
|
${label} - ${game.i18n.localize(`DAGGERHEART.CONFIG.Range.${range}.name`)}
|
||||||
</button>
|
</button>
|
||||||
`;
|
`;
|
||||||
|
|
|
||||||
|
|
@ -418,14 +418,3 @@ export async function createEmbeddedItemsWithEffects(actor, baseData) {
|
||||||
export const slugify = name => {
|
export const slugify = name => {
|
||||||
return name.toLowerCase().replaceAll(' ', '-').replaceAll('.', '');
|
return name.toLowerCase().replaceAll(' ', '-').replaceAll('.', '');
|
||||||
};
|
};
|
||||||
|
|
||||||
export const versionCompare = (current, target) => {
|
|
||||||
const currentSplit = current.split('.').map(x => Number.parseInt(x));
|
|
||||||
const targetSplit = target.split('.').map(x => Number.parseInt(x));
|
|
||||||
for (var i = 0; i < currentSplit.length; i++) {
|
|
||||||
if (currentSplit[i] < targetSplit[i]) return true;
|
|
||||||
if (currentSplit[i] > targetSplit[i]) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ export const preloadHandlebarsTemplates = async function () {
|
||||||
'systems/daggerheart/templates/ui/chat/parts/damage-part.hbs',
|
'systems/daggerheart/templates/ui/chat/parts/damage-part.hbs',
|
||||||
'systems/daggerheart/templates/ui/chat/parts/target-part.hbs',
|
'systems/daggerheart/templates/ui/chat/parts/target-part.hbs',
|
||||||
'systems/daggerheart/templates/ui/chat/parts/button-part.hbs',
|
'systems/daggerheart/templates/ui/chat/parts/button-part.hbs',
|
||||||
|
'systems/daggerheart/templates/ui/itemBrowser/itemContainer.hbs',
|
||||||
'systems/daggerheart/templates/scene/dh-config.hbs'
|
'systems/daggerheart/templates/scene/dh-config.hbs'
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,15 @@
|
||||||
import { versionCompare } from '../helpers/utils.mjs';
|
|
||||||
|
|
||||||
export async function runMigrations() {
|
export async function runMigrations() {
|
||||||
let lastMigrationVersion = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LastMigrationVersion);
|
let lastMigrationVersion = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LastMigrationVersion);
|
||||||
if (!lastMigrationVersion) lastMigrationVersion = '1.0.6';
|
if (!lastMigrationVersion) lastMigrationVersion = '1.0.6';
|
||||||
|
|
||||||
if (versionCompare(lastMigrationVersion, '1.1.0')) {
|
if (foundry.utils.isNewerVersion('1.1.0', lastMigrationVersion)) {
|
||||||
|
const lockedPacks = [];
|
||||||
const compendiumActors = [];
|
const compendiumActors = [];
|
||||||
for (let pack of game.packs) {
|
for (let pack of game.packs) {
|
||||||
|
if (pack.locked) {
|
||||||
|
lockedPacks.push(pack.collection);
|
||||||
|
await pack.configure({ locked: false });
|
||||||
|
}
|
||||||
const documents = await pack.getDocuments();
|
const documents = await pack.getDocuments();
|
||||||
compendiumActors.push(...documents.filter(x => x.type === 'character'));
|
compendiumActors.push(...documents.filter(x => x.type === 'character'));
|
||||||
}
|
}
|
||||||
|
|
@ -34,8 +37,119 @@ export async function runMigrations() {
|
||||||
actor.updateEmbeddedDocuments('Item', items);
|
actor.updateEmbeddedDocuments('Item', items);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
for (let packId of lockedPacks) {
|
||||||
|
const pack = game.packs.get(packId);
|
||||||
|
await pack.configure({ locked: true });
|
||||||
|
}
|
||||||
|
|
||||||
lastMigrationVersion = '1.1.0';
|
lastMigrationVersion = '1.1.0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (foundry.utils.isNewerVersion('1.1.1', lastMigrationVersion)) {
|
||||||
|
const lockedPacks = [];
|
||||||
|
const compendiumClasses = [];
|
||||||
|
const compendiumActors = [];
|
||||||
|
for (let pack of game.packs) {
|
||||||
|
if (pack.locked) {
|
||||||
|
lockedPacks.push(pack.collection);
|
||||||
|
await pack.configure({ locked: false });
|
||||||
|
}
|
||||||
|
const documents = await pack.getDocuments();
|
||||||
|
compendiumClasses.push(...documents.filter(x => x.type === 'class'));
|
||||||
|
compendiumActors.push(...documents.filter(x => x.type === 'character'));
|
||||||
|
}
|
||||||
|
|
||||||
|
[...compendiumActors, ...game.actors.filter(x => x.type === 'character')].forEach(char => {
|
||||||
|
const multiclass = char.items.find(x => x.type === 'class' && x.system.isMulticlass);
|
||||||
|
const multiclassSubclass =
|
||||||
|
multiclass?.system?.subclasses?.length > 0 ? multiclass.system.subclasses[0] : null;
|
||||||
|
char.items.forEach(item => {
|
||||||
|
if (item.type === 'feature' && item.system.identifier === 'multiclass') {
|
||||||
|
const base = item.system.originItemType === 'class' ? multiclass : multiclassSubclass;
|
||||||
|
if (base) {
|
||||||
|
const baseFeature = base.system.features.find(x => x.item.name === item.name);
|
||||||
|
if (baseFeature) {
|
||||||
|
item.update({
|
||||||
|
system: {
|
||||||
|
multiclassOrigin: true,
|
||||||
|
identifier: baseFeature.type
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const worldClasses = game.items.filter(x => x.type === 'class');
|
||||||
|
for (let classVal of [...compendiumClasses, ...worldClasses]) {
|
||||||
|
for (let subclass of classVal.system.subclasses) {
|
||||||
|
await subclass.update({ 'system.linkedClass': classVal.uuid });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let packId of lockedPacks) {
|
||||||
|
const pack = game.packs.get(packId);
|
||||||
|
await pack.configure({ locked: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
lastMigrationVersion = '1.1.1';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundry.utils.isNewerVersion('1.2.0', lastMigrationVersion)) {
|
||||||
|
const lockedPacks = [];
|
||||||
|
const compendiumItems = [];
|
||||||
|
for (let pack of game.packs) {
|
||||||
|
if (pack.locked) {
|
||||||
|
lockedPacks.push(pack.collection);
|
||||||
|
await pack.configure({ locked: false });
|
||||||
|
}
|
||||||
|
const documents = await pack.getDocuments();
|
||||||
|
|
||||||
|
compendiumItems.push(...documents.filter(x => x.system?.metadata?.hasActions));
|
||||||
|
compendiumItems.push(
|
||||||
|
...documents
|
||||||
|
.filter(x => x.items)
|
||||||
|
.flatMap(actor => actor.items.filter(x => x.system?.metadata?.hasActions))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const worldItems = game.items.filter(x => x.system.metadata.hasActions);
|
||||||
|
const worldActorItems = Array.from(game.actors).flatMap(actor =>
|
||||||
|
actor.items.filter(x => x.system.metadata.hasActions)
|
||||||
|
);
|
||||||
|
|
||||||
|
const validCostKeys = Object.keys(CONFIG.DH.GENERAL.abilityCosts);
|
||||||
|
for (let item of [...worldItems, ...worldActorItems, ...compendiumItems]) {
|
||||||
|
for (let action of item.system.actions) {
|
||||||
|
const resourceCostIndexes = Object.keys(action.cost).reduce(
|
||||||
|
(acc, index) => (!validCostKeys.includes(action.cost[index].key) ? [...acc, Number(index)] : acc),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
if (resourceCostIndexes.length === 0) continue;
|
||||||
|
|
||||||
|
await action.update({
|
||||||
|
cost: action.cost.map((cost, index) => {
|
||||||
|
const { keyIsID, ...rest } = cost;
|
||||||
|
if (!resourceCostIndexes.includes(index)) return { ...rest };
|
||||||
|
|
||||||
|
return {
|
||||||
|
...rest,
|
||||||
|
key: 'resource',
|
||||||
|
itemId: cost.key
|
||||||
|
};
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let packId of lockedPacks) {
|
||||||
|
const pack = game.packs.get(packId);
|
||||||
|
await pack.configure({ locked: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
lastMigrationVersion = '1.2.0';
|
||||||
|
}
|
||||||
|
|
||||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LastMigrationVersion, lastMigrationVersion);
|
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LastMigrationVersion, lastMigrationVersion);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ const registerMenus = () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
game.settings.registerMenu(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance, {
|
game.settings.registerMenu(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance, {
|
||||||
name: game.i18n.localize('DAGGERHEART.SETTINGS.Menu.appearance.title'),
|
name: game.i18n.localize('DAGGERHEART.SETTINGS.Menu.appearance.label'),
|
||||||
label: game.i18n.localize('DAGGERHEART.SETTINGS.Menu.appearance.label'),
|
label: game.i18n.localize('DAGGERHEART.SETTINGS.Menu.appearance.label'),
|
||||||
hint: game.i18n.localize('DAGGERHEART.SETTINGS.Menu.appearance.hint'),
|
hint: game.i18n.localize('DAGGERHEART.SETTINGS.Menu.appearance.hint'),
|
||||||
icon: 'fa-solid fa-palette',
|
icon: 'fa-solid fa-palette',
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ export const socketEvent = {
|
||||||
|
|
||||||
export const GMUpdateEvent = {
|
export const GMUpdateEvent = {
|
||||||
UpdateDocument: 'DhGMUpdateDocument',
|
UpdateDocument: 'DhGMUpdateDocument',
|
||||||
|
UpdateEffect: 'DhGMUpdateEffect',
|
||||||
UpdateSetting: 'DhGMUpdateSetting',
|
UpdateSetting: 'DhGMUpdateSetting',
|
||||||
UpdateFear: 'DhGMUpdateFear',
|
UpdateFear: 'DhGMUpdateFear',
|
||||||
UpdateSaveMessage: 'DhGMUpdateSaveMessage'
|
UpdateSaveMessage: 'DhGMUpdateSaveMessage'
|
||||||
|
|
@ -37,9 +38,11 @@ export const registerSocketHooks = () => {
|
||||||
const document = data.uuid ? await fromUuid(data.uuid) : null;
|
const document = data.uuid ? await fromUuid(data.uuid) : null;
|
||||||
switch (data.action) {
|
switch (data.action) {
|
||||||
case GMUpdateEvent.UpdateDocument:
|
case GMUpdateEvent.UpdateDocument:
|
||||||
if (document && data.update) {
|
if (document && data.update) await document.update(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;
|
break;
|
||||||
case GMUpdateEvent.UpdateSetting:
|
case GMUpdateEvent.UpdateSetting:
|
||||||
await game.settings.set(CONFIG.DH.id, data.uuid, data.update);
|
await game.settings.set(CONFIG.DH.id, data.uuid, data.update);
|
||||||
|
|
@ -78,7 +81,7 @@ export const registerSocketHooks = () => {
|
||||||
|
|
||||||
export const registerUserQueries = () => {
|
export const registerUserQueries = () => {
|
||||||
CONFIG.queries.armorSlot = DamageReductionDialog.armorSlotQuery;
|
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) => {
|
export const emitAsGM = async (eventName, callback, update, uuid = null) => {
|
||||||
|
|
|
||||||
|
|
@ -316,7 +316,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "stress",
|
"key": "stress",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -392,7 +392,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "fear",
|
"key": "fear",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -507,7 +506,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "stress",
|
"key": "stress",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -727,7 +725,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "stress",
|
"key": "stress",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -278,7 +278,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "fear",
|
"key": "fear",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -257,7 +257,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "stress",
|
"key": "stress",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -345,7 +344,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "stress",
|
"key": "stress",
|
||||||
"value": 2,
|
"value": 2,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -579,7 +577,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "fear",
|
"key": "fear",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -643,7 +640,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "fear",
|
"key": "fear",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -279,7 +279,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "fear",
|
"key": "fear",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -285,7 +285,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "stress",
|
"key": "stress",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -1091,7 +1090,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "stress",
|
"key": "stress",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -293,7 +293,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "stress",
|
"key": "stress",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -404,7 +404,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "stress",
|
"key": "stress",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -253,7 +253,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "fear",
|
"key": "fear",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -352,7 +351,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "stress",
|
"key": "stress",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -364,7 +364,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "stress",
|
"key": "stress",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -272,7 +272,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "fear",
|
"key": "fear",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -316,7 +316,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "stress",
|
"key": "stress",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -432,7 +431,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "stress",
|
"key": "stress",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -257,7 +257,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "stress",
|
"key": "stress",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -254,7 +254,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "stress",
|
"key": "stress",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -421,7 +420,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "fear",
|
"key": "fear",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -257,7 +257,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "fear",
|
"key": "fear",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -397,7 +396,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "stress",
|
"key": "stress",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -524,7 +522,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "fear",
|
"key": "fear",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -246,7 +246,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "stress",
|
"key": "stress",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -310,7 +309,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "stress",
|
"key": "stress",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -270,9 +270,8 @@
|
||||||
"cost": [
|
"cost": [
|
||||||
{
|
{
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "stress",
|
"key": "fear",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -286,7 +285,7 @@
|
||||||
"type": "self",
|
"type": "self",
|
||||||
"amount": null
|
"amount": null
|
||||||
},
|
},
|
||||||
"name": "Mark Stress",
|
"name": "Spend Fear",
|
||||||
"img": "icons/creatures/abilities/tail-strike-bone-orange.webp",
|
"img": "icons/creatures/abilities/tail-strike-bone-orange.webp",
|
||||||
"range": ""
|
"range": ""
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -285,7 +285,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "fear",
|
"key": "fear",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -399,7 +398,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "fear",
|
"key": "fear",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -252,7 +252,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "fear",
|
"key": "fear",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -432,7 +431,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "stress",
|
"key": "stress",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -527,7 +525,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "fear",
|
"key": "fear",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -318,7 +318,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "stress",
|
"key": "stress",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -285,7 +285,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "fear",
|
"key": "fear",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -349,7 +348,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "stress",
|
"key": "stress",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -421,7 +421,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "stress",
|
"key": "stress",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -360,7 +360,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "stress",
|
"key": "stress",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -252,7 +252,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "stress",
|
"key": "stress",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -393,7 +392,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "fear",
|
"key": "fear",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -457,7 +455,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "fear",
|
"key": "fear",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -279,7 +279,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "stress",
|
"key": "stress",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -272,7 +272,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "fear",
|
"key": "fear",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -277,7 +277,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "stress",
|
"key": "stress",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -391,7 +390,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "stress",
|
"key": "stress",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -370,7 +370,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "stress",
|
"key": "stress",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -358,7 +358,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "fear",
|
"key": "fear",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -252,7 +252,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "fear",
|
"key": "fear",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -389,7 +389,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "stress",
|
"key": "stress",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -497,7 +497,6 @@
|
||||||
"scalable": false,
|
"scalable": false,
|
||||||
"key": "fear",
|
"key": "fear",
|
||||||
"value": 1,
|
"value": 1,
|
||||||
"keyIsID": false,
|
|
||||||
"step": null
|
"step": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue