mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-03-07 14:36:13 +01:00
Compare commits
33 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af5d3d4568 | ||
|
|
6b5c1ff965 | ||
|
|
42a22a49f0 | ||
|
|
da77c2a190 | ||
|
|
68decf0b57 | ||
|
|
4f0670cc35 | ||
|
|
b346ce6766 | ||
|
|
dddd0581f7 | ||
|
|
83329fac46 | ||
|
|
ee0b7b2792 | ||
|
|
4d062a6892 | ||
|
|
487c1fd9a2 | ||
|
|
3aa5cd806a | ||
|
|
2e93b79633 | ||
|
|
244dbd4902 | ||
|
|
c7aed6825a | ||
|
|
9cb5112b62 | ||
|
|
81b6f7fc51 | ||
|
|
828fffd552 | ||
|
|
fc5626ac47 | ||
|
|
2e62545aa7 | ||
|
|
b09c712dd5 | ||
|
|
ca4336bd39 | ||
|
|
77ac11c522 | ||
|
|
50311679a5 | ||
|
|
3a7bcd1b0a | ||
|
|
511e4bd644 | ||
|
|
395820513b | ||
|
|
3566ea3fd3 | ||
|
|
29d502fb97 | ||
|
|
685a25d25a | ||
|
|
dd045b3df7 | ||
|
|
0aabcec340 |
191 changed files with 949 additions and 2986 deletions
|
|
@ -1,3 +0,0 @@
|
||||||
[*]
|
|
||||||
indent_size = 4
|
|
||||||
indent_style = spaces
|
|
||||||
|
|
@ -242,41 +242,6 @@ Hooks.on('setup', () => {
|
||||||
systemEffect: true
|
systemEffect: true
|
||||||
}))
|
}))
|
||||||
];
|
];
|
||||||
|
|
||||||
const damageThresholds = ['damageThresholds.major', 'damageThresholds.severe'];
|
|
||||||
const traits = Object.keys(game.system.api.data.actors.DhCharacter.schema.fields.traits.fields).map(
|
|
||||||
trait => `traits.${trait}.value`
|
|
||||||
);
|
|
||||||
const resistance = Object.values(game.system.api.data.actors.DhCharacter.schema.fields.resistance.fields).flatMap(
|
|
||||||
type => Object.keys(type.fields).map(x => `resistance.${type.name}.${x}`)
|
|
||||||
);
|
|
||||||
const actorCommon = {
|
|
||||||
bar: ['resources.stress'],
|
|
||||||
value: [...resistance, 'advantageSources', 'disadvantageSources']
|
|
||||||
};
|
|
||||||
CONFIG.Actor.trackableAttributes = {
|
|
||||||
character: {
|
|
||||||
bar: [...actorCommon.bar, 'resources.hitPoints', 'resources.hope'],
|
|
||||||
value: [
|
|
||||||
...actorCommon.value,
|
|
||||||
...traits,
|
|
||||||
...damageThresholds,
|
|
||||||
'proficiency',
|
|
||||||
'evasion',
|
|
||||||
'armorScore',
|
|
||||||
'scars',
|
|
||||||
'levelData.level.current'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
adversary: {
|
|
||||||
bar: [...actorCommon.bar, 'resources.hitPoints'],
|
|
||||||
value: [...actorCommon.value, ...damageThresholds, 'criticalThreshold', 'difficulty']
|
|
||||||
},
|
|
||||||
companion: {
|
|
||||||
bar: [...actorCommon.bar],
|
|
||||||
value: [...actorCommon.value, 'evasion', 'levelData.level.current']
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Hooks.on('ready', async () => {
|
Hooks.on('ready', async () => {
|
||||||
|
|
@ -344,7 +309,7 @@ Hooks.on('chatMessage', (_, message) => {
|
||||||
? CONFIG.DH.ACTIONS.advantageState.disadvantage.value
|
? CONFIG.DH.ACTIONS.advantageState.disadvantage.value
|
||||||
: undefined;
|
: undefined;
|
||||||
const difficulty = rollCommand.difficulty;
|
const difficulty = rollCommand.difficulty;
|
||||||
const grantResources = rollCommand.grantResources;
|
const grantResources = Boolean(rollCommand.grantResources);
|
||||||
|
|
||||||
const target = getCommandTarget({ allowNull: true });
|
const target = getCommandTarget({ allowNull: true });
|
||||||
const title =
|
const title =
|
||||||
|
|
@ -420,7 +385,10 @@ const updateActorsRangeDependentEffects = async token => {
|
||||||
// Get required distance and special case 5 feet to test adjacency
|
// Get required distance and special case 5 feet to test adjacency
|
||||||
const required = rangeMeasurement[range];
|
const required = rangeMeasurement[range];
|
||||||
const reverse = type === CONFIG.DH.GENERAL.rangeInclusion.outsideRange.id;
|
const reverse = type === CONFIG.DH.GENERAL.rangeInclusion.outsideRange.id;
|
||||||
const inRange = userTarget.distanceTo(token.object) <= required;
|
const inRange =
|
||||||
|
required === 5
|
||||||
|
? userTarget.isAdjacentWith(token.object)
|
||||||
|
: userTarget.distanceTo(token.object) <= required;
|
||||||
if (reverse ? inRange : !inRange) {
|
if (reverse ? inRange : !inRange) {
|
||||||
enabledEffect = false;
|
enabledEffect = false;
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
129
lang/en.json
129
lang/en.json
|
|
@ -192,9 +192,6 @@
|
||||||
},
|
},
|
||||||
"age": "Age",
|
"age": "Age",
|
||||||
"backgroundQuestions": "Backgrounds",
|
"backgroundQuestions": "Backgrounds",
|
||||||
"burden": {
|
|
||||||
"ignore": { "label": "Burden: Ignore", "hint": "Ignore burden rules" }
|
|
||||||
},
|
|
||||||
"companionFeatures": "Companion Features",
|
"companionFeatures": "Companion Features",
|
||||||
"connections": "Connections",
|
"connections": "Connections",
|
||||||
"contextMenu": {
|
"contextMenu": {
|
||||||
|
|
@ -217,12 +214,6 @@
|
||||||
"maxEvasionBonus": "Max Evasion Increase",
|
"maxEvasionBonus": "Max Evasion Increase",
|
||||||
"maxHPBonus": "Max HP Increase",
|
"maxHPBonus": "Max HP Increase",
|
||||||
"pronouns": "Pronouns",
|
"pronouns": "Pronouns",
|
||||||
"roll": {
|
|
||||||
"guaranteedCritical": {
|
|
||||||
"label": "Guaranteed Critical",
|
|
||||||
"hint": "Set to 1 to always roll a critical"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"story": {
|
"story": {
|
||||||
"backgroundTitle": "Background",
|
"backgroundTitle": "Background",
|
||||||
"characteristics": "Characteristics",
|
"characteristics": "Characteristics",
|
||||||
|
|
@ -352,12 +343,6 @@
|
||||||
"requestSpotlight": "Request The Spotlight",
|
"requestSpotlight": "Request The Spotlight",
|
||||||
"openCountdowns": "Countdowns"
|
"openCountdowns": "Countdowns"
|
||||||
},
|
},
|
||||||
"CompendiumBrowserSettings": {
|
|
||||||
"title": "Enable Compendiums",
|
|
||||||
"enableSource": "Enable Source",
|
|
||||||
"disableSource": "Disable Source",
|
|
||||||
"worldCompendiums": "World Compendiums"
|
|
||||||
},
|
|
||||||
"ContextMenu": {
|
"ContextMenu": {
|
||||||
"disableEffect": "Disable Effect",
|
"disableEffect": "Disable Effect",
|
||||||
"enableEffect": "Enable Effect",
|
"enableEffect": "Enable Effect",
|
||||||
|
|
@ -458,13 +443,9 @@
|
||||||
"name": "Clear Stress"
|
"name": "Clear Stress"
|
||||||
},
|
},
|
||||||
"prepare": {
|
"prepare": {
|
||||||
"description": "Describe how you are preparing for the next day's adventure, then gain a Hope.",
|
"description": "Describe how you are preparing for the next day's adventure, then gain a Hope. If you choose to Prepare with one or more members of your party, you may each take two Hope.",
|
||||||
"name": "Prepare"
|
"name": "Prepare"
|
||||||
},
|
},
|
||||||
"prepareWithFriends": {
|
|
||||||
"description": "You prepare with one or more members of your party, and you each gain 2 Hope.",
|
|
||||||
"name": "Prepare (together)"
|
|
||||||
},
|
|
||||||
"repairArmor": {
|
"repairArmor": {
|
||||||
"description": "Describe how you spend time repairing your armor and clear all of its Armor Slots. You may also do this to an ally's armor instead.",
|
"description": "Describe how you spend time repairing your armor and clear all of its Armor Slots. You may also do this to an ally's armor instead.",
|
||||||
"name": "Repair Armor"
|
"name": "Repair Armor"
|
||||||
|
|
@ -495,11 +476,7 @@
|
||||||
},
|
},
|
||||||
"prepare": {
|
"prepare": {
|
||||||
"name": "Prepare",
|
"name": "Prepare",
|
||||||
"description": "Describe how you prepare yourself for the path ahead, then gain a Hope."
|
"description": "Describe how you prepare yourself for the path ahead, then gain a Hope. If you choose to Prepare with one or more members of your party, you each gain 2 Hope."
|
||||||
},
|
|
||||||
"prepareWithFriends": {
|
|
||||||
"name": "Prepare (together)",
|
|
||||||
"description": "You prepare with one or more members of your party, and you each gain 2 Hope."
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"refreshable": {
|
"refreshable": {
|
||||||
|
|
@ -1031,8 +1008,7 @@
|
||||||
},
|
},
|
||||||
"vulnerable": {
|
"vulnerable": {
|
||||||
"name": "Vulnerable",
|
"name": "Vulnerable",
|
||||||
"description": "While a creature is Vulnerable, all rolls targeting them have advantage.\nA creature who is already Vulnerable can’t be made to take the condition again.",
|
"description": "While a creature is Vulnerable, all rolls targeting them have advantage.\nA creature who is already Vulnerable can’t be made to take the condition again."
|
||||||
"autoAppliedByLabel": "Max Stress"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"CountdownType": {
|
"CountdownType": {
|
||||||
|
|
@ -1167,12 +1143,12 @@
|
||||||
},
|
},
|
||||||
"far": {
|
"far": {
|
||||||
"name": "Far",
|
"name": "Far",
|
||||||
"description": "means a distance where one can see the appearance of a person or object, but probably not in great detail-- across a small battlefield or down a large corridor. This is usually about 30-100 feet away. While under danger, a PC will likely have to make an Agility roll to get here safely. Anything on a battle map that is within the length of a standard piece of paper (~10-11 inches) can usually be considered far.",
|
"description": "means a distance where one can see the appearance of a person or object, but probably not in great detail-- across a small battlefield or down a large corridor. This is usually about 30-100 feet away. While under danger, a PC will likely have to make an Agility check to get here safely. Anything on a battle map that is within the length of a standard piece of paper (~10-11 inches) can usually be considered far.",
|
||||||
"short": "Far"
|
"short": "Far"
|
||||||
},
|
},
|
||||||
"veryFar": {
|
"veryFar": {
|
||||||
"name": "Very Far",
|
"name": "Very Far",
|
||||||
"description": "means a distance where you can see the shape of a person or object, but probably not make outany details-- across a large battlefield or down a long street, generally about 100-300 feet away. While under danger, a PC likely has to make an Agility roll to get here safely. Anything on a battle map that is beyond far distance, but still within sight of the characters can usually be considered very far.",
|
"description": "means a distance where you can see the shape of a person or object, but probably not make outany details-- across a large battlefield or down a long street, generally about 100-300 feet away. While under danger, a PC likely has to make an Agility check to get here safely. Anything on a battle map that is beyond far distance, but still within sight of the characters can usually be considered very far.",
|
||||||
"short": "V. Far"
|
"short": "V. Far"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -1295,7 +1271,6 @@
|
||||||
"triggerTexts": {
|
"triggerTexts": {
|
||||||
"strangePatternsContentTitle": "Matched {nr} times.",
|
"strangePatternsContentTitle": "Matched {nr} times.",
|
||||||
"strangePatternsContentSubTitle": "Increase hope and stress to a total of {nr}.",
|
"strangePatternsContentSubTitle": "Increase hope and stress to a total of {nr}.",
|
||||||
"strangePatternsActionExplanation": "Left click to increase, right click to decrease",
|
|
||||||
"ferocityContent": "Spend 2 Hope to gain {bonus} bonus Evasion until after the next attack against you?",
|
"ferocityContent": "Spend 2 Hope to gain {bonus} bonus Evasion until after the next attack against you?",
|
||||||
"ferocityEffectDescription": "Your evasion is increased by {bonus}. This bonus lasts until after the next attack made against you."
|
"ferocityEffectDescription": "Your evasion is increased by {bonus}. This bonus lasts until after the next attack made against you."
|
||||||
},
|
},
|
||||||
|
|
@ -1865,16 +1840,6 @@
|
||||||
"singular": "Adversary",
|
"singular": "Adversary",
|
||||||
"plural": "Adversaries"
|
"plural": "Adversaries"
|
||||||
},
|
},
|
||||||
"Attack": {
|
|
||||||
"hpDamageMultiplier": {
|
|
||||||
"label": "HP Damage Multiplier",
|
|
||||||
"hint": "Multiply any damage you deal by this number"
|
|
||||||
},
|
|
||||||
"hpDamageTakenMultiplier": {
|
|
||||||
"label": "HP Damage Taken Multiplier",
|
|
||||||
"hint": "Multiply any damage dealt to you by this number"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Bonuses": {
|
"Bonuses": {
|
||||||
"rest": {
|
"rest": {
|
||||||
"downtimeAction": "Downtime Action",
|
"downtimeAction": "Downtime Action",
|
||||||
|
|
@ -2059,40 +2024,16 @@
|
||||||
"reaction": "Reaction Roll"
|
"reaction": "Reaction Roll"
|
||||||
},
|
},
|
||||||
"Rules": {
|
"Rules": {
|
||||||
"conditionImmunities": {
|
|
||||||
"hidden": "Condition Immunity: Hidden",
|
|
||||||
"restrained": "Condition Immunity: Restrained",
|
|
||||||
"vulnerable": "Condition Immunity: Vulnerable"
|
|
||||||
},
|
|
||||||
"damageReduction": {
|
"damageReduction": {
|
||||||
"disabledArmor": { "label": "Disabled Armorslots" },
|
|
||||||
"increasePerArmorMark": {
|
"increasePerArmorMark": {
|
||||||
"label": "Damage Reduction per Armor Slot",
|
"label": "Damage Reduction per Armor Slot",
|
||||||
"hint": "A used armor slot normally reduces damage by one step. This value increases the number of steps damage is reduced by."
|
"hint": "A used armor slot normally reduces damage by one step. This value increases the number of steps damage is reduced by."
|
||||||
},
|
},
|
||||||
"magical": {
|
|
||||||
"label": "Daamge Reduction: Only Magical",
|
|
||||||
"hint": "Armor can only be used to reduce magical damage"
|
|
||||||
},
|
|
||||||
"maxArmorMarkedBonus": "Max Armor Used",
|
"maxArmorMarkedBonus": "Max Armor Used",
|
||||||
"maxArmorMarkedStress": {
|
"maxArmorMarkedStress": {
|
||||||
"label": "Max Armor Used With Stress",
|
"label": "Max Armor Used With Stress",
|
||||||
"hint": "If this value is set you can use up to that much stress to spend additional Armor Marks beyond your normal maximum."
|
"hint": "If this value is set you can use up to that much stress to spend additional Armor Marks beyond your normal maximum."
|
||||||
},
|
},
|
||||||
"reduceSeverity": {
|
|
||||||
"magical": {
|
|
||||||
"label": "Reduce Damage Severity: Magical",
|
|
||||||
"hint": "Lowers any magical damage received by the set amount of severity degrees"
|
|
||||||
},
|
|
||||||
"physical": {
|
|
||||||
"label": "Reduce Damage Severity: Physical",
|
|
||||||
"hint": "Lowers any physical damage received by the set amount of severity degrees"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"physical": {
|
|
||||||
"label": "Damage Reduction: Only Physical",
|
|
||||||
"hint": "Armor can only be used to reduce physical damage"
|
|
||||||
},
|
|
||||||
"stress": {
|
"stress": {
|
||||||
"any": {
|
"any": {
|
||||||
"label": "Stress Damage Reduction: Any",
|
"label": "Stress Damage Reduction: Any",
|
||||||
|
|
@ -2110,12 +2051,6 @@
|
||||||
"label": "Stress Damage Reduction: Minor",
|
"label": "Stress Damage Reduction: Minor",
|
||||||
"hint": "The cost in stress you can pay to reduce minor damage to none."
|
"hint": "The cost in stress you can pay to reduce minor damage to none."
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"thresholdImmunities": {
|
|
||||||
"minor": {
|
|
||||||
"label": "Threshold Immunities: Minor",
|
|
||||||
"hint": "Automatically ignores minor damage when set to 1"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"attack": {
|
"attack": {
|
||||||
|
|
@ -2176,6 +2111,7 @@
|
||||||
"tier4": "tier 4",
|
"tier4": "tier 4",
|
||||||
"domains": "Domains",
|
"domains": "Domains",
|
||||||
"downtime": "Downtime",
|
"downtime": "Downtime",
|
||||||
|
"itemFeatures": "Item Features",
|
||||||
"roll": "Roll",
|
"roll": "Roll",
|
||||||
"rules": "Rules",
|
"rules": "Rules",
|
||||||
"partyMembers": "Party Members",
|
"partyMembers": "Party Members",
|
||||||
|
|
@ -2184,10 +2120,7 @@
|
||||||
"questions": "Questions",
|
"questions": "Questions",
|
||||||
"configuration": "Configuration",
|
"configuration": "Configuration",
|
||||||
"base": "Base",
|
"base": "Base",
|
||||||
"triggers": "Triggers",
|
"triggers": "Triggers"
|
||||||
"deathMoves": "Deathmoves",
|
|
||||||
"sources": "Sources",
|
|
||||||
"packs": "Packs"
|
|
||||||
},
|
},
|
||||||
"Tiers": {
|
"Tiers": {
|
||||||
"singular": "Tier",
|
"singular": "Tier",
|
||||||
|
|
@ -2211,7 +2144,6 @@
|
||||||
"armorSlots": "Armor Slots",
|
"armorSlots": "Armor Slots",
|
||||||
"artistAttribution": "Artwork By: {artist}",
|
"artistAttribution": "Artwork By: {artist}",
|
||||||
"attack": "Attack",
|
"attack": "Attack",
|
||||||
"automation": "Automation",
|
|
||||||
"basics": "Basics",
|
"basics": "Basics",
|
||||||
"bonus": "Bonus",
|
"bonus": "Bonus",
|
||||||
"burden": "Burden",
|
"burden": "Burden",
|
||||||
|
|
@ -2219,7 +2151,6 @@
|
||||||
"continue": "Continue",
|
"continue": "Continue",
|
||||||
"criticalSuccess": "Critical Success",
|
"criticalSuccess": "Critical Success",
|
||||||
"criticalShort": "Critical",
|
"criticalShort": "Critical",
|
||||||
"currentLevel": "Current Level",
|
|
||||||
"custom": "Custom",
|
"custom": "Custom",
|
||||||
"d20Roll": "D20 Roll",
|
"d20Roll": "D20 Roll",
|
||||||
"damage": "Damage",
|
"damage": "Damage",
|
||||||
|
|
@ -2325,7 +2256,6 @@
|
||||||
"single": "Target",
|
"single": "Target",
|
||||||
"plural": "Targets"
|
"plural": "Targets"
|
||||||
},
|
},
|
||||||
"thingsAndThing": "{things} and {thing}",
|
|
||||||
"title": "Title",
|
"title": "Title",
|
||||||
"tokenSize": "Token Size",
|
"tokenSize": "Token Size",
|
||||||
"total": "Total",
|
"total": "Total",
|
||||||
|
|
@ -2364,8 +2294,7 @@
|
||||||
},
|
},
|
||||||
"Ancestry": {
|
"Ancestry": {
|
||||||
"primaryFeature": "Primary Feature",
|
"primaryFeature": "Primary Feature",
|
||||||
"secondaryFeature": "Secondary Feature",
|
"secondaryFeature": "Secondary Feature"
|
||||||
"featuresLabel": "Ancestry Features"
|
|
||||||
},
|
},
|
||||||
"Armor": {
|
"Armor": {
|
||||||
"baseScore": "Base Score",
|
"baseScore": "Base Score",
|
||||||
|
|
@ -2418,12 +2347,7 @@
|
||||||
"evolvedImagePlaceholder": "The image for the form selected for evolution will be used"
|
"evolvedImagePlaceholder": "The image for the form selected for evolution will be used"
|
||||||
},
|
},
|
||||||
"Class": {
|
"Class": {
|
||||||
"startingEvasionScore": "Starting Evasion Score",
|
|
||||||
"startingHitPoints": "Starting Hit Points",
|
|
||||||
"classItems": "Class Items",
|
|
||||||
"hopeFeatureLabel": "{class}'s Hope Feature",
|
|
||||||
"hopeFeatures": "Hope Features",
|
"hopeFeatures": "Hope Features",
|
||||||
"classFeature": "Class Feature",
|
|
||||||
"classFeatures": "Class Features",
|
"classFeatures": "Class Features",
|
||||||
"guide": {
|
"guide": {
|
||||||
"suggestedEquipment": "Suggested Equipments",
|
"suggestedEquipment": "Suggested Equipments",
|
||||||
|
|
@ -2436,9 +2360,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Community": {
|
|
||||||
"featuresLabel": "Community Feature"
|
|
||||||
},
|
|
||||||
"Consumable": {
|
"Consumable": {
|
||||||
"consumeOnUse": "Consume On Use",
|
"consumeOnUse": "Consume On Use",
|
||||||
"destroyOnEmpty": "Destroy On Empty"
|
"destroyOnEmpty": "Destroy On Empty"
|
||||||
|
|
@ -2454,11 +2375,7 @@
|
||||||
"masteryTitle": "Mastery"
|
"masteryTitle": "Mastery"
|
||||||
},
|
},
|
||||||
"Subclass": {
|
"Subclass": {
|
||||||
"spellcastingTrait": "Spellcasting Trait",
|
"spellcastingTrait": "Spellcasting Trait"
|
||||||
"spellcastTrait": "Spellcast Trait",
|
|
||||||
"foundationFeatures": "Foundation Features",
|
|
||||||
"specializationFeature": "Specialization Feature",
|
|
||||||
"masteryFeature": "Mastery Feature"
|
|
||||||
},
|
},
|
||||||
"Weapon": {
|
"Weapon": {
|
||||||
"weaponType": "Weapon Type",
|
"weaponType": "Weapon Type",
|
||||||
|
|
@ -2487,14 +2404,6 @@
|
||||||
"hideAttribution": {
|
"hideAttribution": {
|
||||||
"label": "Hide Attribution"
|
"label": "Hide Attribution"
|
||||||
},
|
},
|
||||||
"showTokenDistance": {
|
|
||||||
"label": "Show Token Distance on Hover",
|
|
||||||
"choices": {
|
|
||||||
"always": "Always",
|
|
||||||
"encounters": "Encounters",
|
|
||||||
"never": "Never"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"expandedTitle": "Auto-expand Descriptions",
|
"expandedTitle": "Auto-expand Descriptions",
|
||||||
"extendCharacterDescriptions": {
|
"extendCharacterDescriptions": {
|
||||||
"label": "Characters"
|
"label": "Characters"
|
||||||
|
|
@ -2557,10 +2466,6 @@
|
||||||
"gm": { "label": "GM" },
|
"gm": { "label": "GM" },
|
||||||
"players": { "label": "Players" }
|
"players": { "label": "Players" }
|
||||||
},
|
},
|
||||||
"vulnerableAutomation": {
|
|
||||||
"label": "Vulnerable Automation",
|
|
||||||
"hint": "Automatically apply the Vulnerable condition when a actor reaches max stress"
|
|
||||||
},
|
|
||||||
"countdownAutomation": {
|
"countdownAutomation": {
|
||||||
"label": "Countdown Automation",
|
"label": "Countdown Automation",
|
||||||
"hint": "Automatically progress countdowns based on their progression settings"
|
"hint": "Automatically progress countdowns based on their progression settings"
|
||||||
|
|
@ -2641,8 +2546,6 @@
|
||||||
"resetMovesTitle": "Reset {type} Downtime Moves",
|
"resetMovesTitle": "Reset {type} Downtime Moves",
|
||||||
"resetItemFeaturesTitle": "Reset {type}",
|
"resetItemFeaturesTitle": "Reset {type}",
|
||||||
"resetMovesText": "Are you sure you want to reset?",
|
"resetMovesText": "Are you sure you want to reset?",
|
||||||
"deleteItemTitle": "Delete Homebrew Item",
|
|
||||||
"deleteItemText": "Are you sure you want to delete the item?",
|
|
||||||
"FIELDS": {
|
"FIELDS": {
|
||||||
"maxFear": { "label": "Max Fear" },
|
"maxFear": { "label": "Max Fear" },
|
||||||
"maxHope": { "label": "Max Hope" },
|
"maxHope": { "label": "Max Hope" },
|
||||||
|
|
@ -2811,7 +2714,7 @@
|
||||||
"title": "Domain Card"
|
"title": "Domain Card"
|
||||||
},
|
},
|
||||||
"dualityRoll": {
|
"dualityRoll": {
|
||||||
"abilityCheckTitle": "{ability} Roll"
|
"abilityCheckTitle": "{ability} Check"
|
||||||
},
|
},
|
||||||
"effectSummary": {
|
"effectSummary": {
|
||||||
"title": "Effects Applied",
|
"title": "Effects Applied",
|
||||||
|
|
@ -2826,7 +2729,7 @@
|
||||||
"selectLeader": "Select a Leader",
|
"selectLeader": "Select a Leader",
|
||||||
"selectMember": "Select a Member",
|
"selectMember": "Select a Member",
|
||||||
"rerollTitle": "Reroll Group Roll",
|
"rerollTitle": "Reroll Group Roll",
|
||||||
"rerollContent": "Are you sure you want to reroll your {trait} roll?",
|
"rerollContent": "Are you sure you want to reroll your {trait} check?",
|
||||||
"rerollTooltip": "Reroll",
|
"rerollTooltip": "Reroll",
|
||||||
"wholePartySelected": "The whole party is selected"
|
"wholePartySelected": "The whole party is selected"
|
||||||
},
|
},
|
||||||
|
|
@ -2874,7 +2777,6 @@
|
||||||
"ItemBrowser": {
|
"ItemBrowser": {
|
||||||
"title": "Daggerheart Compendium Browser",
|
"title": "Daggerheart Compendium Browser",
|
||||||
"hint": "Select a Folder in sidebar to start browsing through the compendium",
|
"hint": "Select a Folder in sidebar to start browsing through the compendium",
|
||||||
"browserSettings": "Browser Settings",
|
|
||||||
"searchPlaceholder": "Search...",
|
"searchPlaceholder": "Search...",
|
||||||
"columnName": "Name",
|
"columnName": "Name",
|
||||||
"tooltipFilters": "Filters",
|
"tooltipFilters": "Filters",
|
||||||
|
|
@ -2992,17 +2894,14 @@
|
||||||
"tokenActorMissing": "{name} is missing an Actor",
|
"tokenActorMissing": "{name} is missing an Actor",
|
||||||
"tokenActorsMissing": "[{names}] missing Actors",
|
"tokenActorsMissing": "[{names}] missing Actors",
|
||||||
"domainTouchRequirement": "This domain card requires {nr} {domain} cards in the loadout to be used",
|
"domainTouchRequirement": "This domain card requires {nr} {domain} cards in the loadout to be used",
|
||||||
"knowTheTide": "Know The Tide gained a token",
|
"knowTheTide": "Know The Tide gained a token"
|
||||||
"lackingItemTransferPermission": "User {user} lacks owner permission needed to transfer items to {target}"
|
|
||||||
},
|
},
|
||||||
"Sidebar": {
|
"Sidebar": {
|
||||||
"actorDirectory": {
|
"actorDirectory": {
|
||||||
"tier": "Tier {tier} {type}",
|
"tier": "Tier {tier} {type}",
|
||||||
"character": "Level {level} Character",
|
"character": "Level {level} Character",
|
||||||
"companion": "Level {level} - {partner}",
|
"companion": "Level {level} - {partner}",
|
||||||
"companionNoPartner": "No Partner",
|
"companionNoPartner": "No Partner"
|
||||||
"duplicateToNewTier": "Duplicate to New Tier",
|
|
||||||
"pickTierTitle": "Pick a new tier for this adversary"
|
|
||||||
},
|
},
|
||||||
"daggerheartMenu": {
|
"daggerheartMenu": {
|
||||||
"title": "Daggerheart Menu",
|
"title": "Daggerheart Menu",
|
||||||
|
|
@ -3034,7 +2933,7 @@
|
||||||
"rulesOn": "Rules On",
|
"rulesOn": "Rules On",
|
||||||
"rulesOff": "Rules Off",
|
"rulesOff": "Rules Off",
|
||||||
"remainingUses": "Uses refresh on {type}",
|
"remainingUses": "Uses refresh on {type}",
|
||||||
"rightClickExtend": "Right-Click to extend",
|
"rightClickExtand": "Right-Click to extand",
|
||||||
"companionPartnerLevelBlock": "The companion needs an assigned partner to level up.",
|
"companionPartnerLevelBlock": "The companion needs an assigned partner to level up.",
|
||||||
"configureAttribution": "Configure Attribution",
|
"configureAttribution": "Configure Attribution",
|
||||||
"deleteItem": "Delete Item",
|
"deleteItem": "Delete Item",
|
||||||
|
|
|
||||||
|
|
@ -1,143 +0,0 @@
|
||||||
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
|
||||||
|
|
||||||
export default class CompendiumBrowserSettings extends HandlebarsApplicationMixin(ApplicationV2) {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.browserSettings = game.settings
|
|
||||||
.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.CompendiumBrowserSettings)
|
|
||||||
.toObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
static DEFAULT_OPTIONS = {
|
|
||||||
tag: 'div',
|
|
||||||
classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'compendium-brower-settings'],
|
|
||||||
window: {
|
|
||||||
icon: 'fa-solid fa-book',
|
|
||||||
title: 'DAGGERHEART.APPLICATIONS.CompendiumBrowserSettings.title'
|
|
||||||
},
|
|
||||||
position: {
|
|
||||||
width: 500
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
toggleSource: CompendiumBrowserSettings.#toggleSource,
|
|
||||||
finish: CompendiumBrowserSettings.#finish
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
static PARTS = {
|
|
||||||
packs: {
|
|
||||||
id: 'packs',
|
|
||||||
template: 'systems/daggerheart/templates/dialogs/compendiumBrowserSettingsDialog/packs.hbs'
|
|
||||||
},
|
|
||||||
footer: { template: 'systems/daggerheart/templates/dialogs/compendiumBrowserSettingsDialog/footer.hbs' }
|
|
||||||
};
|
|
||||||
|
|
||||||
static #browserPackTypes = ['Actor', 'Item'];
|
|
||||||
|
|
||||||
_attachPartListeners(partId, htmlElement, options) {
|
|
||||||
super._attachPartListeners(partId, htmlElement, options);
|
|
||||||
|
|
||||||
for (const element of htmlElement.querySelectorAll('.pack-checkbox'))
|
|
||||||
element.addEventListener('change', this.toggleTypedPack.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**@inheritdoc */
|
|
||||||
async _prepareContext(_options) {
|
|
||||||
const context = await super._prepareContext(_options);
|
|
||||||
|
|
||||||
const excludedSourceData = this.browserSettings.excludedSources;
|
|
||||||
const excludedPackData = this.browserSettings.excludedPacks;
|
|
||||||
context.typePackCollections = game.packs.reduce((acc, pack) => {
|
|
||||||
const { type, label, packageType, packageName: basePackageName, id } = pack.metadata;
|
|
||||||
if (!CompendiumBrowserSettings.#browserPackTypes.includes(type)) return acc;
|
|
||||||
|
|
||||||
const isWorldPack = packageType === 'world';
|
|
||||||
const packageName = isWorldPack ? 'world' : basePackageName;
|
|
||||||
const sourceChecked =
|
|
||||||
!excludedSourceData[packageName] ||
|
|
||||||
!excludedSourceData[packageName].excludedDocumentTypes.includes(type);
|
|
||||||
|
|
||||||
const sourceLabel =
|
|
||||||
game.modules.get(packageName)?.title ??
|
|
||||||
(isWorldPack
|
|
||||||
? game.i18n.localize('DAGGERHEART.APPLICATIONS.CompendiumBrowserSettings.worldCompendiums')
|
|
||||||
: game.system.title);
|
|
||||||
if (!acc[type]) acc[type] = { label: game.i18n.localize(`DOCUMENT.${type}s`), sources: {} };
|
|
||||||
if (!acc[type].sources[packageName])
|
|
||||||
acc[type].sources[packageName] = { label: sourceLabel, checked: sourceChecked, packs: [] };
|
|
||||||
|
|
||||||
const checked = !excludedPackData[id] || !excludedPackData[id].excludedDocumentTypes.includes(type);
|
|
||||||
|
|
||||||
acc[type].sources[packageName].packs.push({
|
|
||||||
pack: id,
|
|
||||||
type,
|
|
||||||
label: id === game.system.id ? game.system.title : game.i18n.localize(label),
|
|
||||||
checked: checked
|
|
||||||
});
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
static #toggleSource(event, button) {
|
|
||||||
event.stopPropagation();
|
|
||||||
|
|
||||||
const { type, source } = button.dataset;
|
|
||||||
const currentlyExcluded = this.browserSettings.excludedSources[source]
|
|
||||||
? this.browserSettings.excludedSources[source].excludedDocumentTypes.includes(type)
|
|
||||||
: false;
|
|
||||||
|
|
||||||
if (!this.browserSettings.excludedSources[source])
|
|
||||||
this.browserSettings.excludedSources[source] = { excludedDocumentTypes: [] };
|
|
||||||
this.browserSettings.excludedSources[source].excludedDocumentTypes = currentlyExcluded
|
|
||||||
? this.browserSettings.excludedSources[source].excludedDocumentTypes.filter(x => x !== type)
|
|
||||||
: [...(this.browserSettings.excludedSources[source]?.excludedDocumentTypes ?? []), type];
|
|
||||||
|
|
||||||
const toggleIcon = button.querySelector('a > i');
|
|
||||||
toggleIcon.classList.toggle('fa-toggle-off');
|
|
||||||
toggleIcon.classList.toggle('fa-toggle-on');
|
|
||||||
button.closest('.source-container').querySelector('.checks-container').classList.toggle('collapsed');
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleTypedPack(event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
|
|
||||||
const { type, pack } = event.target.dataset;
|
|
||||||
const currentlyExcluded = this.browserSettings.excludedPacks[pack]
|
|
||||||
? this.browserSettings.excludedPacks[pack].excludedDocumentTypes.includes(type)
|
|
||||||
: false;
|
|
||||||
|
|
||||||
if (!this.browserSettings.excludedPacks[pack])
|
|
||||||
this.browserSettings.excludedPacks[pack] = { excludedDocumentTypes: [] };
|
|
||||||
this.browserSettings.excludedPacks[pack].excludedDocumentTypes = currentlyExcluded
|
|
||||||
? this.browserSettings.excludedPacks[pack].excludedDocumentTypes.filter(x => x !== type)
|
|
||||||
: [...(this.browserSettings.excludedPacks[pack]?.excludedDocumentTypes ?? []), type];
|
|
||||||
|
|
||||||
this.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
static async #finish() {
|
|
||||||
const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.CompendiumBrowserSettings);
|
|
||||||
await settings.updateSource(this.browserSettings);
|
|
||||||
await game.settings.set(
|
|
||||||
CONFIG.DH.id,
|
|
||||||
CONFIG.DH.SETTINGS.gameSettings.CompendiumBrowserSettings,
|
|
||||||
settings.toObject()
|
|
||||||
);
|
|
||||||
|
|
||||||
this.updated = true;
|
|
||||||
this.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
static async configure() {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
const app = new this();
|
|
||||||
app.addEventListener('close', () => resolve(app.updated), { once: true });
|
|
||||||
app.render({ force: true });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -16,4 +16,3 @@ export { default as ActionSelectionDialog } from './actionSelectionDialog.mjs';
|
||||||
export { default as GroupRollDialog } from './group-roll-dialog.mjs';
|
export { default as GroupRollDialog } from './group-roll-dialog.mjs';
|
||||||
export { default as TagTeamDialog } from './tagTeamDialog.mjs';
|
export { default as TagTeamDialog } from './tagTeamDialog.mjs';
|
||||||
export { default as RiskItAllDialog } from './riskItAllDialog.mjs';
|
export { default as RiskItAllDialog } from './riskItAllDialog.mjs';
|
||||||
export { default as CompendiumBrowserSettingsDialog } from './CompendiumBrowserSettings.mjs';
|
|
||||||
|
|
|
||||||
|
|
@ -54,11 +54,7 @@ export default class AttributionDialog extends HandlebarsApplicationMixin(Applic
|
||||||
const after = label.slice(matchIndex + search.length, label.length);
|
const after = label.slice(matchIndex + search.length, label.length);
|
||||||
|
|
||||||
const element = document.createElement('li');
|
const element = document.createElement('li');
|
||||||
element.innerHTML =
|
element.innerHTML = `${beforeText}${matchText ? `<strong>${matchText}</strong>` : ''}${after}`;
|
||||||
`${beforeText}${matchText ? `<strong>${matchText}</strong>` : ''}${after}`.replaceAll(
|
|
||||||
' ',
|
|
||||||
' '
|
|
||||||
);
|
|
||||||
if (item.hint) {
|
if (item.hint) {
|
||||||
element.dataset.tooltip = game.i18n.localize(item.hint);
|
element.dataset.tooltip = game.i18n.localize(item.hint);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -165,7 +165,6 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
}
|
}
|
||||||
if (rest.hasOwnProperty('trait')) {
|
if (rest.hasOwnProperty('trait')) {
|
||||||
this.config.roll.trait = rest.trait;
|
this.config.roll.trait = rest.trait;
|
||||||
if (!this.config.source.item)
|
|
||||||
this.config.title = game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
this.config.title = game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
||||||
ability: game.i18n.localize(abilities[this.config.roll.trait]?.label)
|
ability: game.i18n.localize(abilities[this.config.roll.trait]?.label)
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ export default class DhDeathMove extends HandlebarsApplicationMixin(ApplicationV
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleAvoidDeath(useAutomation) {
|
async handleAvoidDeath() {
|
||||||
const target = this.actor.uuid;
|
const target = this.actor.uuid;
|
||||||
const config = await enrichedFateRoll({
|
const config = await enrichedFateRoll({
|
||||||
target,
|
target,
|
||||||
|
|
@ -53,7 +53,6 @@ export default class DhDeathMove extends HandlebarsApplicationMixin(ApplicationV
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!config.roll.fate) return;
|
if (!config.roll.fate) return;
|
||||||
if (!useAutomation) return '';
|
|
||||||
|
|
||||||
let returnMessage = game.i18n.localize('DAGGERHEART.UI.Chat.deathMove.avoidScar');
|
let returnMessage = game.i18n.localize('DAGGERHEART.UI.Chat.deathMove.avoidScar');
|
||||||
if (config.roll.fate.value <= this.actor.system.levelData.level.current) {
|
if (config.roll.fate.value <= this.actor.system.levelData.level.current) {
|
||||||
|
|
@ -76,7 +75,7 @@ export default class DhDeathMove extends HandlebarsApplicationMixin(ApplicationV
|
||||||
return returnMessage;
|
return returnMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleRiskItAll(useAutomation) {
|
async handleRiskItAll() {
|
||||||
const config = await enrichedDualityRoll({
|
const config = await enrichedDualityRoll({
|
||||||
reaction: true,
|
reaction: true,
|
||||||
traitValue: null,
|
traitValue: null,
|
||||||
|
|
@ -91,7 +90,6 @@ export default class DhDeathMove extends HandlebarsApplicationMixin(ApplicationV
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!config.roll.result) return;
|
if (!config.roll.result) return;
|
||||||
if (!useAutomation) return '';
|
|
||||||
|
|
||||||
const clearAllStressAndHitpointsUpdates = [
|
const clearAllStressAndHitpointsUpdates = [
|
||||||
{ key: 'hitPoints', clear: true },
|
{ key: 'hitPoints', clear: true },
|
||||||
|
|
@ -130,9 +128,7 @@ export default class DhDeathMove extends HandlebarsApplicationMixin(ApplicationV
|
||||||
return chatMessage;
|
return chatMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleBlazeOfGlory(useAutomation) {
|
async handleBlazeOfGlory() {
|
||||||
if (!useAutomation) return '';
|
|
||||||
|
|
||||||
this.actor.createEmbeddedDocuments('ActiveEffect', [
|
this.actor.createEmbeddedDocuments('ActiveEffect', [
|
||||||
{
|
{
|
||||||
name: game.i18n.localize('DAGGERHEART.CONFIG.DeathMoves.blazeOfGlory.name'),
|
name: game.i18n.localize('DAGGERHEART.CONFIG.DeathMoves.blazeOfGlory.name'),
|
||||||
|
|
@ -164,23 +160,19 @@ export default class DhDeathMove extends HandlebarsApplicationMixin(ApplicationV
|
||||||
|
|
||||||
let result = '';
|
let result = '';
|
||||||
|
|
||||||
const deathMoveAutomation = game.settings.get(
|
|
||||||
CONFIG.DH.id,
|
|
||||||
CONFIG.DH.SETTINGS.gameSettings.Automation
|
|
||||||
).deathMoveAutomation;
|
|
||||||
if (CONFIG.DH.GENERAL.deathMoves.blazeOfGlory === this.selectedMove) {
|
if (CONFIG.DH.GENERAL.deathMoves.blazeOfGlory === this.selectedMove) {
|
||||||
result = await this.handleBlazeOfGlory(deathMoveAutomation.blazeOfGlory);
|
result = await this.handleBlazeOfGlory();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CONFIG.DH.GENERAL.deathMoves.avoidDeath === this.selectedMove) {
|
if (CONFIG.DH.GENERAL.deathMoves.avoidDeath === this.selectedMove) {
|
||||||
result = await this.handleAvoidDeath(deathMoveAutomation.avoidDeath);
|
result = await this.handleAvoidDeath();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CONFIG.DH.GENERAL.deathMoves.riskItAll === this.selectedMove) {
|
if (CONFIG.DH.GENERAL.deathMoves.riskItAll === this.selectedMove) {
|
||||||
result = await this.handleRiskItAll(deathMoveAutomation.riskItAll);
|
result = await this.handleRiskItAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result === undefined) return;
|
if (!result) return;
|
||||||
|
|
||||||
const autoExpandDescription = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance)
|
const autoExpandDescription = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance)
|
||||||
.expandRollMessage?.desc;
|
.expandRollMessage?.desc;
|
||||||
|
|
@ -200,6 +192,7 @@ export default class DhDeathMove extends HandlebarsApplicationMixin(ApplicationV
|
||||||
description: game.i18n.localize(this.selectedMove.description),
|
description: game.i18n.localize(this.selectedMove.description),
|
||||||
result: result,
|
result: result,
|
||||||
open: autoExpandDescription ? 'open' : '',
|
open: autoExpandDescription ? 'open' : '',
|
||||||
|
chevron: autoExpandDescription ? 'fa-chevron-up' : 'fa-chevron-down',
|
||||||
showRiskItAllButton: this.showRiskItAllButton,
|
showRiskItAllButton: this.showRiskItAllButton,
|
||||||
riskItAllButtonLabel: this.riskItAllButtonLabel,
|
riskItAllButtonLabel: this.riskItAllButtonLabel,
|
||||||
riskItAllHope: this.riskItAllHope
|
riskItAllHope: this.riskItAllHope
|
||||||
|
|
|
||||||
|
|
@ -196,9 +196,6 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
||||||
.filter(x => x.testUserPermission(game.user, 'LIMITED'))
|
.filter(x => x.testUserPermission(game.user, 'LIMITED'))
|
||||||
.filter(x => x.uuid !== this.actor.uuid);
|
.filter(x => x.uuid !== this.actor.uuid);
|
||||||
|
|
||||||
const autoExpandDescription = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance)
|
|
||||||
.expandRollMessage?.desc;
|
|
||||||
|
|
||||||
const cls = getDocumentClass('ChatMessage');
|
const cls = getDocumentClass('ChatMessage');
|
||||||
const msg = {
|
const msg = {
|
||||||
user: game.user.id,
|
user: game.user.id,
|
||||||
|
|
@ -219,8 +216,7 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
||||||
actor: { name: this.actor.name, img: this.actor.img },
|
actor: { name: this.actor.name, img: this.actor.img },
|
||||||
moves: moves,
|
moves: moves,
|
||||||
characters: characters,
|
characters: characters,
|
||||||
selfId: this.actor.uuid,
|
selfId: this.actor.uuid
|
||||||
open: autoExpandDescription ? 'open' : ''
|
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
flags: {
|
flags: {
|
||||||
|
|
|
||||||
|
|
@ -70,11 +70,7 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
|
||||||
element.appendChild(img);
|
element.appendChild(img);
|
||||||
|
|
||||||
const label = document.createElement('span');
|
const label = document.createElement('span');
|
||||||
label.innerHTML =
|
label.innerHTML = `${beforeText}${matchText ? `<strong>${matchText}</strong>` : ''}${after}`;
|
||||||
`${beforeText}${matchText ? `<strong>${matchText}</strong>` : ''}${after}`.replaceAll(
|
|
||||||
' ',
|
|
||||||
' '
|
|
||||||
);
|
|
||||||
element.appendChild(label);
|
element.appendChild(label);
|
||||||
|
|
||||||
return element;
|
return element;
|
||||||
|
|
@ -123,11 +119,7 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
|
||||||
element.appendChild(img);
|
element.appendChild(img);
|
||||||
|
|
||||||
const label = document.createElement('span');
|
const label = document.createElement('span');
|
||||||
label.innerHTML =
|
label.innerHTML = `${beforeText}${matchText ? `<strong>${matchText}</strong>` : ''}${after}`;
|
||||||
`${beforeText}${matchText ? `<strong>${matchText}</strong>` : ''}${after}`.replaceAll(
|
|
||||||
' ',
|
|
||||||
' '
|
|
||||||
);
|
|
||||||
element.appendChild(label);
|
element.appendChild(label);
|
||||||
|
|
||||||
return element;
|
return element;
|
||||||
|
|
|
||||||
|
|
@ -103,11 +103,7 @@ export default class DhSceneConfigSettings extends foundry.applications.sheets.S
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
async _processSubmitData(event, form, submitData, options) {
|
async _processSubmitData(event, form, submitData, options) {
|
||||||
if (!submitData.flags) submitData.flags = {};
|
submitData.flags.daggerheart = this.daggerheartFlag.toObject();
|
||||||
submitData.flags.daggerheart = foundry.utils.mergeObject(
|
|
||||||
this.daggerheartFlag.toObject(),
|
|
||||||
submitData.flags.daggerheart
|
|
||||||
);
|
|
||||||
submitData.flags.daggerheart.sceneEnvironments = submitData.flags.daggerheart.sceneEnvironments.filter(x =>
|
submitData.flags.daggerheart.sceneEnvironments = submitData.flags.daggerheart.sceneEnvironments.filter(x =>
|
||||||
foundry.utils.fromUuidSync(x)
|
foundry.utils.fromUuidSync(x)
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ export default class DhAutomationSettings extends HandlebarsApplicationMixin(App
|
||||||
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
|
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
|
||||||
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/deathMoves.hbs' },
|
rules: { template: 'systems/daggerheart/templates/settings/automation-settings/rules.hbs' },
|
||||||
roll: { template: 'systems/daggerheart/templates/settings/automation-settings/roll.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' }
|
||||||
};
|
};
|
||||||
|
|
@ -42,7 +42,7 @@ export default class DhAutomationSettings extends HandlebarsApplicationMixin(App
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
static TABS = {
|
static TABS = {
|
||||||
main: {
|
main: {
|
||||||
tabs: [{ id: 'general' }, { id: 'deathMoves' }, { id: 'roll' }],
|
tabs: [{ id: 'general' }, { id: 'rules' }, { id: 'roll' }],
|
||||||
initial: 'general',
|
initial: 'general',
|
||||||
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -103,12 +103,6 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
||||||
? { id: this.selected.adversaryType, ...this.settings.adversaryTypes[this.selected.adversaryType] }
|
? { id: this.selected.adversaryType, ...this.settings.adversaryTypes[this.selected.adversaryType] }
|
||||||
: null;
|
: null;
|
||||||
break;
|
break;
|
||||||
case 'downtime':
|
|
||||||
context.restOptions = {
|
|
||||||
shortRest: CONFIG.DH.GENERAL.defaultRestOptions.shortRest(),
|
|
||||||
longRest: CONFIG.DH.GENERAL.defaultRestOptions.longRest()
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
|
|
@ -171,8 +165,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
||||||
name: game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.newDowntimeMove'),
|
name: game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.newDowntimeMove'),
|
||||||
img: 'icons/magic/life/cross-worn-green.webp',
|
img: 'icons/magic/life/cross-worn-green.webp',
|
||||||
description: '',
|
description: '',
|
||||||
actions: [],
|
actions: []
|
||||||
effects: []
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (['armorFeatures', 'weaponFeatures'].includes(type)) {
|
} else if (['armorFeatures', 'weaponFeatures'].includes(type)) {
|
||||||
|
|
@ -187,7 +180,6 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, this.settings.toObject());
|
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -228,28 +220,16 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, this.settings.toObject());
|
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
static async removeItem(_, target) {
|
static async removeItem(_, target) {
|
||||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
|
||||||
window: {
|
|
||||||
title: game.i18n.localize(`DAGGERHEART.SETTINGS.Homebrew.deleteItemTitle`)
|
|
||||||
},
|
|
||||||
content: game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.deleteItemText')
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!confirmed) return;
|
|
||||||
|
|
||||||
const { type, id } = target.dataset;
|
const { type, id } = target.dataset;
|
||||||
const isDowntime = ['shortRest', 'longRest'].includes(type);
|
const isDowntime = ['shortRest', 'longRest'].includes(type);
|
||||||
const path = isDowntime ? `restMoves.${type}.moves` : `itemFeatures.${type}`;
|
const path = isDowntime ? `restMoves.${type}.moves` : `itemFeatures.${type}`;
|
||||||
await this.settings.updateSource({
|
await this.settings.updateSource({
|
||||||
[`${path}.-=${id}`]: null
|
[`${path}.-=${id}`]: null
|
||||||
});
|
});
|
||||||
|
|
||||||
game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, this.settings.toObject());
|
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -314,7 +314,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
||||||
const index = Number.parseInt(button.dataset.index);
|
const index = Number.parseInt(button.dataset.index);
|
||||||
const toggle = (element, codeMirror) => {
|
const toggle = (element, codeMirror) => {
|
||||||
codeMirror.classList.toggle('revealed');
|
codeMirror.classList.toggle('revealed');
|
||||||
const button = element.querySelector('.expand-trigger > i');
|
const button = element.querySelector('a > i');
|
||||||
button.classList.toggle('fa-angle-up');
|
button.classList.toggle('fa-angle-up');
|
||||||
button.classList.toggle('fa-angle-down');
|
button.classList.toggle('fa-angle-down');
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,22 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
this.changeChoices = DhActiveEffectConfig.getChangeChoices();
|
const ignoredActorKeys = ['config', 'DhEnvironment'];
|
||||||
|
this.changeChoices = Object.keys(game.system.api.models.actors).reduce((acc, key) => {
|
||||||
|
if (!ignoredActorKeys.includes(key)) {
|
||||||
|
const model = game.system.api.models.actors[key];
|
||||||
|
const attributes = CONFIG.Token.documentClass.getTrackedAttributes(model);
|
||||||
|
// As per DHToken._getTrackedAttributesFromSchema, attributes.bar have a max version as well.
|
||||||
|
const maxAttributes = attributes.bar.map(x => [...x, 'max']);
|
||||||
|
attributes.value.push(...maxAttributes);
|
||||||
|
const group = game.i18n.localize(model.metadata.label);
|
||||||
|
const choices = CONFIG.Token.documentClass
|
||||||
|
.getTrackedAttributeChoices(attributes, model)
|
||||||
|
.map(x => ({ ...x, group: group }));
|
||||||
|
acc.push(...choices);
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
|
|
@ -35,69 +50,6 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Get ChangeChoices for the changes autocomplete. Static for use in this class aswell as in settings-active-effect-config.mjs
|
|
||||||
* @returns {ChangeChoice { value: string, label: string, hint: string, group: string }[]}
|
|
||||||
*/
|
|
||||||
static getChangeChoices() {
|
|
||||||
const ignoredActorKeys = ['config', 'DhEnvironment', 'DhParty'];
|
|
||||||
|
|
||||||
const getAllLeaves = (root, group, parentPath = '') => {
|
|
||||||
const leaves = [];
|
|
||||||
const rootKey = `${parentPath ? `${parentPath}.` : ''}${root.name}`;
|
|
||||||
for (const field of Object.values(root.fields)) {
|
|
||||||
if (field instanceof foundry.data.fields.SchemaField)
|
|
||||||
leaves.push(...getAllLeaves(field, group, rootKey));
|
|
||||||
else
|
|
||||||
leaves.push({
|
|
||||||
value: `${rootKey}.${field.name}`,
|
|
||||||
label: game.i18n.localize(field.label),
|
|
||||||
hint: game.i18n.localize(field.hint),
|
|
||||||
group
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return leaves;
|
|
||||||
};
|
|
||||||
return Object.keys(game.system.api.models.actors).reduce((acc, key) => {
|
|
||||||
if (ignoredActorKeys.includes(key)) return acc;
|
|
||||||
|
|
||||||
const model = game.system.api.models.actors[key];
|
|
||||||
const group = game.i18n.localize(model.metadata.label);
|
|
||||||
const attributes = CONFIG.Token.documentClass.getTrackedAttributes(model.metadata.type);
|
|
||||||
|
|
||||||
const getTranslations = path => {
|
|
||||||
if (path === 'resources.hope.max')
|
|
||||||
return {
|
|
||||||
label: game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.FIELDS.maxHope.label'),
|
|
||||||
hint: ''
|
|
||||||
};
|
|
||||||
|
|
||||||
const field = model.schema.getField(path);
|
|
||||||
return {
|
|
||||||
label: field ? game.i18n.localize(field.label) : path,
|
|
||||||
hint: field ? game.i18n.localize(field.hint) : ''
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const bars = attributes.bar.flatMap(x => {
|
|
||||||
const joined = `${x.join('.')}.max`;
|
|
||||||
return { value: joined, ...getTranslations(joined), group };
|
|
||||||
});
|
|
||||||
const values = attributes.value.flatMap(x => {
|
|
||||||
const joined = x.join('.');
|
|
||||||
return { value: joined, ...getTranslations(joined), group };
|
|
||||||
});
|
|
||||||
|
|
||||||
const bonuses = getAllLeaves(model.schema.fields.bonuses, group);
|
|
||||||
const rules = getAllLeaves(model.schema.fields.rules, group);
|
|
||||||
|
|
||||||
acc.push(...bars, ...values, ...rules, ...bonuses);
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, []);
|
|
||||||
}
|
|
||||||
|
|
||||||
_attachPartListeners(partId, htmlElement, options) {
|
_attachPartListeners(partId, htmlElement, options) {
|
||||||
super._attachPartListeners(partId, htmlElement, options);
|
super._attachPartListeners(partId, htmlElement, options);
|
||||||
const changeChoices = this.changeChoices;
|
const changeChoices = this.changeChoices;
|
||||||
|
|
@ -116,18 +68,14 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
|
||||||
},
|
},
|
||||||
render: function (item, search) {
|
render: function (item, search) {
|
||||||
const label = game.i18n.localize(item.label);
|
const label = game.i18n.localize(item.label);
|
||||||
const matchIndex = label.toLowerCase().indexOf(search.toLowerCase());
|
const matchIndex = label.toLowerCase().indexOf(search);
|
||||||
|
|
||||||
const beforeText = label.slice(0, matchIndex);
|
const beforeText = label.slice(0, matchIndex);
|
||||||
const matchText = label.slice(matchIndex, matchIndex + search.length);
|
const matchText = label.slice(matchIndex, matchIndex + search.length);
|
||||||
const after = label.slice(matchIndex + search.length, label.length);
|
const after = label.slice(matchIndex + search.length, label.length);
|
||||||
|
|
||||||
const element = document.createElement('li');
|
const element = document.createElement('li');
|
||||||
element.innerHTML =
|
element.innerHTML = `${beforeText}${matchText ? `<strong>${matchText}</strong>` : ''}${after}`;
|
||||||
`${beforeText}${matchText ? `<strong>${matchText}</strong>` : ''}${after}`.replaceAll(
|
|
||||||
' ',
|
|
||||||
' '
|
|
||||||
);
|
|
||||||
if (item.hint) {
|
if (item.hint) {
|
||||||
element.dataset.tooltip = game.i18n.localize(item.hint);
|
element.dataset.tooltip = game.i18n.localize(item.hint);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,19 @@ export default class SettingActiveEffectConfig extends HandlebarsApplicationMixi
|
||||||
super({});
|
super({});
|
||||||
|
|
||||||
this.effect = foundry.utils.deepClone(effect);
|
this.effect = foundry.utils.deepClone(effect);
|
||||||
this.changeChoices = game.system.api.applications.sheetConfigs.ActiveEffectConfig.getChangeChoices();
|
const ignoredActorKeys = ['config', 'DhEnvironment'];
|
||||||
|
this.changeChoices = Object.keys(game.system.api.models.actors).reduce((acc, key) => {
|
||||||
|
if (!ignoredActorKeys.includes(key)) {
|
||||||
|
const model = game.system.api.models.actors[key];
|
||||||
|
const attributes = CONFIG.Token.documentClass.getTrackedAttributes(model);
|
||||||
|
const group = game.i18n.localize(model.metadata.label);
|
||||||
|
const choices = CONFIG.Token.documentClass
|
||||||
|
.getTrackedAttributeChoices(attributes, model)
|
||||||
|
.map(x => ({ ...x, group: group }));
|
||||||
|
acc.push(...choices);
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
|
|
@ -91,11 +103,7 @@ export default class SettingActiveEffectConfig extends HandlebarsApplicationMixi
|
||||||
const after = label.slice(matchIndex + search.length, label.length);
|
const after = label.slice(matchIndex + search.length, label.length);
|
||||||
|
|
||||||
const element = document.createElement('li');
|
const element = document.createElement('li');
|
||||||
element.innerHTML =
|
element.innerHTML = `${beforeText}${matchText ? `<strong>${matchText}</strong>` : ''}${after}`;
|
||||||
`${beforeText}${matchText ? `<strong>${matchText}</strong>` : ''}${after}`.replaceAll(
|
|
||||||
' ',
|
|
||||||
' '
|
|
||||||
);
|
|
||||||
if (item.hint) {
|
if (item.hint) {
|
||||||
element.dataset.tooltip = game.i18n.localize(item.hint);
|
element.dataset.tooltip = game.i18n.localize(item.hint);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -73,11 +73,9 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async updateData(_event, _element, formData) {
|
static async updateData(event, element, formData) {
|
||||||
const data = foundry.utils.expandObject(formData.object);
|
const data = foundry.utils.expandObject(formData.object);
|
||||||
await this.updateMove({
|
foundry.utils.mergeObject(this.move, data);
|
||||||
[`${this.movePath}`]: data
|
|
||||||
});
|
|
||||||
|
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
@ -137,7 +135,9 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
await this.updateMove({ [`${this.actionsPath}.${action.id}`]: action });
|
await this.settings.updateSource({ [`${this.actionsPath}.${action.id}`]: action });
|
||||||
|
this.move = foundry.utils.getProperty(this.settings, this.movePath);
|
||||||
|
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -150,12 +150,13 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App
|
||||||
await game.system.api.applications.sheetConfigs.SettingActiveEffectConfig.configure(effect);
|
await game.system.api.applications.sheetConfigs.SettingActiveEffectConfig.configure(effect);
|
||||||
if (!updatedEffect) return;
|
if (!updatedEffect) return;
|
||||||
|
|
||||||
await this.updateMove({
|
await this.settings.updateSource({
|
||||||
[`${this.movePath}.effects`]: this.move.effects.reduce((acc, effect, index) => {
|
[`${this.movePath}.effects`]: this.move.effects.reduce((acc, effect, index) => {
|
||||||
acc.push(index === effectIndex ? { ...updatedEffect, id: effect.id } : effect);
|
acc.push(index === effectIndex ? { ...updatedEffect, id: effect.id } : effect);
|
||||||
return acc;
|
return acc;
|
||||||
}, [])
|
}, [])
|
||||||
});
|
});
|
||||||
|
this.move = foundry.utils.getProperty(this.settings, this.movePath);
|
||||||
this.render();
|
this.render();
|
||||||
} else {
|
} else {
|
||||||
const action = this.move.actions.get(id);
|
const action = this.move.actions.get(id);
|
||||||
|
|
@ -170,13 +171,13 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App
|
||||||
: existingEffectIndex === -1
|
: existingEffectIndex === -1
|
||||||
? [...currentEffects, effectData]
|
? [...currentEffects, effectData]
|
||||||
: currentEffects.with(existingEffectIndex, effectData);
|
: currentEffects.with(existingEffectIndex, effectData);
|
||||||
await this.updateMove({
|
await this.settings.updateSource({
|
||||||
[`${this.movePath}.effects`]: updatedEffects
|
[`${this.movePath}.effects`]: updatedEffects
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.updateMove({ [`${this.actionsPath}.${id}`]: updatedMove });
|
await this.settings.updateSource({ [`${this.actionsPath}.${id}`]: updatedMove });
|
||||||
|
this.move = foundry.utils.getProperty(this.settings, this.movePath);
|
||||||
this.render();
|
this.render();
|
||||||
return updatedEffects;
|
return updatedEffects;
|
||||||
}).render(true);
|
}).render(true);
|
||||||
|
|
@ -198,34 +199,31 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await this.updateMove({
|
await this.settings.updateSource({
|
||||||
[this.movePath]: {
|
[this.movePath]: {
|
||||||
effects: move.effects.filter(x => x.id !== id),
|
effects: move.effects.filter(x => x.id !== id),
|
||||||
actions: move.actions
|
actions: move.actions
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await this.updateMove({ [`${this.actionsPath}.-=${target.dataset.id}`]: null });
|
await this.settings.updateSource({ [`${this.actionsPath}.-=${target.dataset.id}`]: null });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.move = foundry.utils.getProperty(this.settings, this.movePath);
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
static async addEffect() {
|
static async addEffect(_, target) {
|
||||||
const currentEffects = foundry.utils.getProperty(this.settings, `${this.movePath}.effects`);
|
const currentEffects = foundry.utils.getProperty(this.settings, `${this.movePath}.effects`);
|
||||||
|
await this.settings.updateSource({
|
||||||
await this.updateMove({
|
|
||||||
[`${this.movePath}.effects`]: [
|
[`${this.movePath}.effects`]: [
|
||||||
...currentEffects,
|
...currentEffects,
|
||||||
game.system.api.data.activeEffects.BaseEffect.getDefaultObject()
|
game.system.api.data.activeEffects.BaseEffect.getDefaultObject()
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
this.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
async updateMove(update) {
|
|
||||||
await this.settings.updateSource(update);
|
|
||||||
this.move = foundry.utils.getProperty(this.settings, this.movePath);
|
this.move = foundry.utils.getProperty(this.settings, this.movePath);
|
||||||
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
static resetMoves() {}
|
static resetMoves() {}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import DaggerheartMenu from '../../sidebar/tabs/daggerheartMenu.mjs';
|
||||||
import { socketEvent } from '../../../systemRegistration/socket.mjs';
|
import { socketEvent } from '../../../systemRegistration/socket.mjs';
|
||||||
import GroupRollDialog from '../../dialogs/group-roll-dialog.mjs';
|
import GroupRollDialog from '../../dialogs/group-roll-dialog.mjs';
|
||||||
import DhpActor from '../../../documents/actor.mjs';
|
import DhpActor from '../../../documents/actor.mjs';
|
||||||
|
import DHItem from '../../../documents/item.mjs';
|
||||||
|
|
||||||
export default class Party extends DHBaseActorSheet {
|
export default class Party extends DHBaseActorSheet {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
|
|
@ -268,6 +269,15 @@ export default class Party extends DHBaseActorSheet {
|
||||||
).render({ force: true });
|
).render({ force: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the set of ContextMenu options for Consumable and Loot.
|
||||||
|
* @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - The Array of context options passed to the ContextMenu instance
|
||||||
|
* @this {CharacterSheet}
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
static #getItemContextOptions() {
|
||||||
|
return this._getContextMenuCommonOptions.call(this, { usable: true, toChat: true });
|
||||||
|
}
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
/* Filter Tracking */
|
/* Filter Tracking */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
|
||||||
|
|
@ -431,18 +431,18 @@ export default function DHApplicationMixin(Base) {
|
||||||
{
|
{
|
||||||
name: 'disableEffect',
|
name: 'disableEffect',
|
||||||
icon: 'fa-solid fa-lightbulb',
|
icon: 'fa-solid fa-lightbulb',
|
||||||
condition: element => {
|
condition: target => {
|
||||||
const target = element.closest('[data-item-uuid]');
|
const doc = getDocFromElementSync(target);
|
||||||
return !target.dataset.disabled && target.dataset.itemType !== 'beastform';
|
return doc && !doc.disabled;
|
||||||
},
|
},
|
||||||
callback: async target => (await getDocFromElement(target)).update({ disabled: true })
|
callback: async target => (await getDocFromElement(target)).update({ disabled: true })
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'enableEffect',
|
name: 'enableEffect',
|
||||||
icon: 'fa-regular fa-lightbulb',
|
icon: 'fa-regular fa-lightbulb',
|
||||||
condition: element => {
|
condition: target => {
|
||||||
const target = element.closest('[data-item-uuid]');
|
const doc = getDocFromElementSync(target);
|
||||||
return target.dataset.disabled && target.dataset.itemType !== 'beastform';
|
return doc && doc.disabled;
|
||||||
},
|
},
|
||||||
callback: async target => (await getDocFromElement(target)).update({ disabled: false })
|
callback: async target => (await getDocFromElement(target)).update({ disabled: false })
|
||||||
}
|
}
|
||||||
|
|
@ -536,10 +536,6 @@ export default function DHApplicationMixin(Base) {
|
||||||
options.push({
|
options.push({
|
||||||
name: 'CONTROLS.CommonDelete',
|
name: 'CONTROLS.CommonDelete',
|
||||||
icon: 'fa-solid fa-trash',
|
icon: 'fa-solid fa-trash',
|
||||||
condition: element => {
|
|
||||||
const target = element.closest('[data-item-uuid]');
|
|
||||||
return target.dataset.itemType !== 'beastform';
|
|
||||||
},
|
|
||||||
callback: async (target, event) => {
|
callback: async (target, event) => {
|
||||||
const doc = await getDocFromElement(target);
|
const doc = await getDocFromElement(target);
|
||||||
if (event.shiftKey) return doc.delete();
|
if (event.shiftKey) return doc.delete();
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
||||||
],
|
],
|
||||||
dragDrop: [
|
dragDrop: [
|
||||||
{ dragSelector: '.inventory-item[data-type="attack"]', dropSelector: null },
|
{ dragSelector: '.inventory-item[data-type="attack"]', dropSelector: null },
|
||||||
{ dragSelector: '.currency[data-currency] .drag-handle', dropSelector: null }
|
{ dragSelector: ".currency[data-currency] .drag-handle", dropSelector: null }
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -92,7 +92,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
||||||
value: context.source.system.gold[key]
|
value: context.source.system.gold[key]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
context.inventory.hasCurrency = Object.values(context.inventory.currencies).some(c => c.enabled);
|
context.inventory.hasCurrency = Object.values(context.inventory.currencies).some((c) => c.enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
|
|
@ -270,9 +270,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
||||||
currency
|
currency
|
||||||
});
|
});
|
||||||
if (quantity) {
|
if (quantity) {
|
||||||
originActor.update({
|
originActor.update({ [`system.gold.${currency}`]: Math.max(0, originActor.system.gold[currency] - quantity) });
|
||||||
[`system.gold.${currency}`]: Math.max(0, originActor.system.gold[currency] - quantity)
|
|
||||||
});
|
|
||||||
this.document.update({ [`system.gold.${currency}`]: this.document.system.gold[currency] + quantity });
|
this.document.update({ [`system.gold.${currency}`]: this.document.system.gold[currency] + quantity });
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|
@ -294,15 +292,6 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
||||||
|
|
||||||
/* Handling transfer of inventoryItems */
|
/* Handling transfer of inventoryItems */
|
||||||
if (item.system.metadata.isInventoryItem) {
|
if (item.system.metadata.isInventoryItem) {
|
||||||
if (!this.document.testUserPermission(game.user, 'OWNER', { exact: true })) {
|
|
||||||
return ui.notifications.error(
|
|
||||||
game.i18n.format('DAGGERHEART.UI.Notifications.lackingItemTransferPermission', {
|
|
||||||
user: game.user.name,
|
|
||||||
target: this.document.name
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.system.metadata.isQuantifiable) {
|
if (item.system.metadata.isQuantifiable) {
|
||||||
const actorItem = originActor.items.get(data.originId);
|
const actorItem = originActor.items.get(data.originId);
|
||||||
const quantityTransfered = await game.system.api.applications.dialogs.ItemTransferDialog.configure({
|
const quantityTransfered = await game.system.api.applications.dialogs.ItemTransferDialog.configure({
|
||||||
|
|
@ -311,6 +300,14 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (quantityTransfered) {
|
if (quantityTransfered) {
|
||||||
|
if (quantityTransfered === actorItem.system.quantity) {
|
||||||
|
await originActor.deleteEmbeddedDocuments('Item', [data.originId]);
|
||||||
|
} else {
|
||||||
|
await actorItem.update({
|
||||||
|
'system.quantity': actorItem.system.quantity - quantityTransfered
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const existingItem = this.document.items.find(x => itemIsIdentical(x, item));
|
const existingItem = this.document.items.find(x => itemIsIdentical(x, item));
|
||||||
if (existingItem) {
|
if (existingItem) {
|
||||||
await existingItem.update({
|
await existingItem.update({
|
||||||
|
|
@ -328,18 +325,10 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (quantityTransfered === actorItem.system.quantity) {
|
} else {
|
||||||
await originActor.deleteEmbeddedDocuments('Item', [data.originId]);
|
await originActor.deleteEmbeddedDocuments('Item', [data.originId]);
|
||||||
} else {
|
|
||||||
await actorItem.update({
|
|
||||||
'system.quantity': actorItem.system.quantity - quantityTransfered
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await this.document.createEmbeddedDocuments('Item', [item.toObject()]);
|
await this.document.createEmbeddedDocuments('Item', [item.toObject()]);
|
||||||
await originActor.deleteEmbeddedDocuments('Item', [data.originId]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -350,7 +339,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
||||||
*/
|
*/
|
||||||
async _onDragStart(event) {
|
async _onDragStart(event) {
|
||||||
// Handle drag/dropping currencies
|
// Handle drag/dropping currencies
|
||||||
const currencyEl = event.currentTarget.closest('.currency[data-currency]');
|
const currencyEl = event.currentTarget.closest(".currency[data-currency]");
|
||||||
if (currencyEl) {
|
if (currencyEl) {
|
||||||
const currency = currencyEl.dataset.currency;
|
const currency = currencyEl.dataset.currency;
|
||||||
const data = { type: 'Currency', currency, originActor: this.document.uuid };
|
const data = { type: 'Currency', currency, originActor: this.document.uuid };
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
export default function ItemAttachmentSheet(Base) {
|
export default function ItemAttachmentSheet(Base) {
|
||||||
return class extends Base {
|
return class extends Base {
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
|
...super.DEFAULT_OPTIONS,
|
||||||
dragDrop: [
|
dragDrop: [
|
||||||
...(super.DEFAULT_OPTIONS.dragDrop || []),
|
...(super.DEFAULT_OPTIONS.dragDrop || []),
|
||||||
{ dragSelector: null, dropSelector: '.attachments-section' }
|
{ dragSelector: null, dropSelector: '.attachments-section' }
|
||||||
|
|
|
||||||
|
|
@ -43,54 +43,4 @@ export default class DhActorDirectory extends foundry.applications.sidebar.tabs.
|
||||||
event.dataTransfer.setDragImage(preview, w / 2, h / 2);
|
event.dataTransfer.setDragImage(preview, w / 2, h / 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_getEntryContextOptions() {
|
|
||||||
const options = super._getEntryContextOptions();
|
|
||||||
options.push({
|
|
||||||
name: 'DAGGERHEART.UI.Sidebar.actorDirectory.duplicateToNewTier',
|
|
||||||
icon: `<i class="fa-solid fa-arrow-trend-up" inert></i>`,
|
|
||||||
condition: li => {
|
|
||||||
const actor = game.actors.get(li.dataset.entryId);
|
|
||||||
return actor?.type === 'adversary' && actor.system.type !== 'social';
|
|
||||||
},
|
|
||||||
callback: async li => {
|
|
||||||
const actor = game.actors.get(li.dataset.entryId);
|
|
||||||
if (!actor) throw new Error('Unexpected missing actor');
|
|
||||||
|
|
||||||
const tiers = [1, 2, 3, 4].filter(t => t !== actor.system.tier);
|
|
||||||
const content = document.createElement('div');
|
|
||||||
const select = document.createElement('select');
|
|
||||||
select.name = 'tier';
|
|
||||||
select.append(
|
|
||||||
...tiers.map(t => {
|
|
||||||
const option = document.createElement('option');
|
|
||||||
option.value = t;
|
|
||||||
option.textContent = game.i18n.localize(`DAGGERHEART.GENERAL.Tiers.${t}`);
|
|
||||||
return option;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
content.append(select);
|
|
||||||
|
|
||||||
const tier = await foundry.applications.api.Dialog.input({
|
|
||||||
classes: ['dh-style', 'dialog'],
|
|
||||||
window: { title: 'DAGGERHEART.UI.Sidebar.actorDirectory.pickTierTitle' },
|
|
||||||
content,
|
|
||||||
ok: {
|
|
||||||
label: 'Create Adversary',
|
|
||||||
callback: (event, button, dialog) => Number(button.form.elements.tier.value)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (tier === actor.system.tier) {
|
|
||||||
ui.notifications.warn('This actor is already at this tier');
|
|
||||||
} else if (tier) {
|
|
||||||
const source = actor.system.adjustForTier(tier);
|
|
||||||
await Actor.create(source);
|
|
||||||
ui.notifications.info(`Tier ${tier} ${actor.name} created`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { RefreshFeatures } from '../../../helpers/utils.mjs';
|
import { refreshIsAllowed } from '../../../helpers/utils.mjs';
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin } = foundry.applications.api;
|
const { HandlebarsApplicationMixin } = foundry.applications.api;
|
||||||
const { AbstractSidebarTab } = foundry.applications.sidebar;
|
const { AbstractSidebarTab } = foundry.applications.sidebar;
|
||||||
|
|
@ -54,6 +54,73 @@ export default class DaggerheartMenu extends HandlebarsApplicationMixin(Abstract
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getRefreshables(types) {
|
||||||
|
const refreshedActors = {};
|
||||||
|
for (let actor of game.actors) {
|
||||||
|
if (['character', 'adversary'].includes(actor.type) && actor.prototypeToken.actorLink) {
|
||||||
|
const updates = {};
|
||||||
|
for (let item of actor.items) {
|
||||||
|
if (item.system.metadata?.hasResource && refreshIsAllowed(types, item.system.resource?.recovery)) {
|
||||||
|
if (!refreshedActors[actor.id])
|
||||||
|
refreshedActors[actor.id] = { name: actor.name, img: actor.img, refreshed: new Set() };
|
||||||
|
refreshedActors[actor.id].refreshed.add(
|
||||||
|
game.i18n.localize(CONFIG.DH.GENERAL.refreshTypes[item.system.resource.recovery].label)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!updates[item.id]?.system) updates[item.id] = { system: {} };
|
||||||
|
|
||||||
|
const increasing =
|
||||||
|
item.system.resource.progression === CONFIG.DH.ITEM.itemResourceProgression.increasing.id;
|
||||||
|
updates[item.id].system = {
|
||||||
|
...updates[item.id].system,
|
||||||
|
'resource.value': increasing
|
||||||
|
? 0
|
||||||
|
: Roll.replaceFormulaData(item.system.resource.max, actor.getRollData())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (item.system.metadata?.hasActions) {
|
||||||
|
const refreshTypes = new Set();
|
||||||
|
const actions = item.system.actions.filter(action => {
|
||||||
|
if (refreshIsAllowed(types, action.uses.recovery)) {
|
||||||
|
refreshTypes.add(action.uses.recovery);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
if (actions.length === 0) continue;
|
||||||
|
|
||||||
|
if (!refreshedActors[actor.id])
|
||||||
|
refreshedActors[actor.id] = { name: actor.name, img: actor.img, refreshed: new Set() };
|
||||||
|
refreshedActors[actor.id].refreshed.add(
|
||||||
|
...refreshTypes.map(type => game.i18n.localize(CONFIG.DH.GENERAL.refreshTypes[type].label))
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!updates[item.id]?.system) updates[item.id] = { system: {} };
|
||||||
|
|
||||||
|
updates[item.id].system = {
|
||||||
|
...updates[item.id].system,
|
||||||
|
...actions.reduce(
|
||||||
|
(acc, action) => {
|
||||||
|
acc.actions[action.id] = { 'uses.value': 0 };
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{ actions: updates[item.id].system.actions ?? {} }
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let key in updates) {
|
||||||
|
const update = updates[key];
|
||||||
|
await actor.items.get(key).update(update);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return refreshedActors;
|
||||||
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
/* Application Clicks Actions */
|
/* Application Clicks Actions */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
@ -66,9 +133,30 @@ export default class DaggerheartMenu extends HandlebarsApplicationMixin(Abstract
|
||||||
|
|
||||||
static async #refreshActors() {
|
static async #refreshActors() {
|
||||||
const refreshKeys = Object.keys(this.refreshSelections).filter(key => this.refreshSelections[key].selected);
|
const refreshKeys = Object.keys(this.refreshSelections).filter(key => this.refreshSelections[key].selected);
|
||||||
await RefreshFeatures(refreshKeys);
|
await this.getRefreshables(refreshKeys);
|
||||||
|
const types = refreshKeys.map(x => this.refreshSelections[x].label).join(', ');
|
||||||
|
ui.notifications.info(
|
||||||
|
game.i18n.format('DAGGERHEART.UI.Notifications.gmMenuRefresh', {
|
||||||
|
types: `[${types}]`
|
||||||
|
})
|
||||||
|
);
|
||||||
this.refreshSelections = DaggerheartMenu.defaultRefreshSelections();
|
this.refreshSelections = DaggerheartMenu.defaultRefreshSelections();
|
||||||
|
|
||||||
|
const cls = getDocumentClass('ChatMessage');
|
||||||
|
const msg = {
|
||||||
|
user: game.user.id,
|
||||||
|
content: await foundry.applications.handlebars.renderTemplate(
|
||||||
|
'systems/daggerheart/templates/ui/chat/refreshMessage.hbs',
|
||||||
|
{
|
||||||
|
types: types
|
||||||
|
}
|
||||||
|
),
|
||||||
|
title: game.i18n.localize('DAGGERHEART.UI.Chat.refreshMessage.title'),
|
||||||
|
speaker: cls.getSpeaker()
|
||||||
|
};
|
||||||
|
|
||||||
|
cls.create(msg);
|
||||||
|
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,8 @@ export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV
|
||||||
position: {
|
position: {
|
||||||
width: 222,
|
width: 222,
|
||||||
height: 222
|
height: 222
|
||||||
|
// top: "200px",
|
||||||
|
// left: "120px"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -64,7 +66,7 @@ export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV
|
||||||
max = this.maxFear,
|
max = this.maxFear,
|
||||||
percent = (current / max) * 100,
|
percent = (current / max) * 100,
|
||||||
isGM = game.user.isGM;
|
isGM = game.user.isGM;
|
||||||
|
// Return the data for rendering
|
||||||
return { display, current, max, percent, isGM };
|
return { display, current, max, percent, isGM };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import { RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -19,15 +17,6 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
this.config = CONFIG.DH.ITEMBROWSER.compendiumConfig;
|
this.config = CONFIG.DH.ITEMBROWSER.compendiumConfig;
|
||||||
this.presets = {};
|
this.presets = {};
|
||||||
this.compendiumBrowserTypeKey = 'compendiumBrowserDefault';
|
this.compendiumBrowserTypeKey = 'compendiumBrowserDefault';
|
||||||
|
|
||||||
this.setupHooks = Hooks.on(socketEvent.Refresh, ({ refreshType }) => {
|
|
||||||
if (refreshType === RefreshType.CompendiumBrowser) {
|
|
||||||
if (this.rendered) {
|
|
||||||
this.render();
|
|
||||||
this.loadItems();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
|
|
@ -46,8 +35,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
selectFolder: this.selectFolder,
|
selectFolder: this.selectFolder,
|
||||||
expandContent: this.expandContent,
|
expandContent: this.expandContent,
|
||||||
resetFilters: this.resetFilters,
|
resetFilters: this.resetFilters,
|
||||||
sortList: this.sortList,
|
sortList: this.sortList
|
||||||
openSettings: this.openSettings
|
|
||||||
},
|
},
|
||||||
position: {
|
position: {
|
||||||
left: 100,
|
left: 100,
|
||||||
|
|
@ -169,8 +157,6 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
context.formatChoices = this.formatChoices;
|
context.formatChoices = this.formatChoices;
|
||||||
context.items = this.items;
|
context.items = this.items;
|
||||||
context.presets = this.presets;
|
context.presets = this.presets;
|
||||||
context.isGM = game.user.isGM;
|
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -228,10 +214,6 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
loadItems() {
|
loadItems() {
|
||||||
let loadTimeout = this.toggleLoader(true);
|
let loadTimeout = this.toggleLoader(true);
|
||||||
|
|
||||||
const browserSettings = game.settings.get(
|
|
||||||
CONFIG.DH.id,
|
|
||||||
CONFIG.DH.SETTINGS.gameSettings.CompendiumBrowserSettings
|
|
||||||
);
|
|
||||||
const promises = [];
|
const promises = [];
|
||||||
|
|
||||||
game.packs.forEach(pack => {
|
game.packs.forEach(pack => {
|
||||||
|
|
@ -245,7 +227,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
|
|
||||||
Promise.all(promises).then(async result => {
|
Promise.all(promises).then(async result => {
|
||||||
this.items = ItemBrowser.sortBy(
|
this.items = ItemBrowser.sortBy(
|
||||||
result.flatMap(r => r).filter(r => !browserSettings.isEntryExcluded.bind(browserSettings)(r)),
|
result.flatMap(r => r),
|
||||||
'name'
|
'name'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -530,22 +512,6 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
itemListContainer.replaceChildren(...newOrder);
|
itemListContainer.replaceChildren(...newOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async openSettings() {
|
|
||||||
const settingsUpdated = await game.system.api.applications.dialogs.CompendiumBrowserSettingsDialog.configure();
|
|
||||||
if (settingsUpdated) {
|
|
||||||
if (this.rendered) {
|
|
||||||
this.render();
|
|
||||||
this.loadItems();
|
|
||||||
}
|
|
||||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
|
||||||
action: socketEvent.Refresh,
|
|
||||||
data: {
|
|
||||||
refreshType: RefreshType.CompendiumBrowser
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_createDragProcess() {
|
_createDragProcess() {
|
||||||
new foundry.applications.ux.DragDrop.implementation({
|
new foundry.applications.ux.DragDrop.implementation({
|
||||||
dragSelector: '.item-container',
|
dragSelector: '.item-container',
|
||||||
|
|
@ -605,9 +571,4 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
headerActions.append(button);
|
headerActions.append(button);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async close(options = {}) {
|
|
||||||
Hooks.off(socketEvent.Refresh, this.setupHooks);
|
|
||||||
await super.close(options);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -63,8 +63,7 @@ export default class DhSceneNavigation extends foundry.applications.ui.SceneNavi
|
||||||
if (scene.flags.daggerheart.sceneEnvironments[0] !== environment.uuid) {
|
if (scene.flags.daggerheart.sceneEnvironments[0] !== environment.uuid) {
|
||||||
const newEnvironments = scene.flags.daggerheart.sceneEnvironments;
|
const newEnvironments = scene.flags.daggerheart.sceneEnvironments;
|
||||||
const newFirst = newEnvironments.splice(
|
const newFirst = newEnvironments.splice(
|
||||||
newEnvironments.findIndex(x => x === environment.uuid),
|
newEnvironments.findIndex(x => x === environment.uuid)
|
||||||
1
|
|
||||||
)[0];
|
)[0];
|
||||||
newEnvironments.unshift(newFirst);
|
newEnvironments.unshift(newFirst);
|
||||||
emitAsGM(
|
emitAsGM(
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,8 @@ export default class DhMeasuredTemplate extends foundry.canvas.placeables.Measur
|
||||||
|
|
||||||
static getRangeLabels(distanceValue, settings) {
|
static getRangeLabels(distanceValue, settings) {
|
||||||
let result = { distance: distanceValue, units: '' };
|
let result = { distance: distanceValue, units: '' };
|
||||||
if (!settings.enabled) return result;
|
|
||||||
|
|
||||||
const sceneRangeMeasurement = canvas.scene.flags.daggerheart?.rangeMeasurement;
|
const sceneRangeMeasurement = canvas.scene.flags.daggerheart?.rangeMeasurement;
|
||||||
|
|
||||||
const { disable, custom } = CONFIG.DH.GENERAL.sceneRangeMeasurementSetting;
|
const { disable, custom } = CONFIG.DH.GENERAL.sceneRangeMeasurementSetting;
|
||||||
if (sceneRangeMeasurement?.setting === disable.id) {
|
if (sceneRangeMeasurement?.setting === disable.id) {
|
||||||
result.distance = distanceValue;
|
result.distance = distanceValue;
|
||||||
|
|
@ -28,9 +27,31 @@ export default class DhMeasuredTemplate extends foundry.canvas.placeables.Measur
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ranges = sceneRangeMeasurement?.setting === custom.id ? sceneRangeMeasurement : settings;
|
const melee = sceneRangeMeasurement?.setting === custom.id ? sceneRangeMeasurement.melee : settings.melee;
|
||||||
const distanceKey = ['melee', 'veryClose', 'close', 'far'].find(r => ranges[r] >= distanceValue);
|
const veryClose =
|
||||||
result.distance = game.i18n.localize(`DAGGERHEART.CONFIG.Range.${distanceKey ?? 'veryFar'}.name`);
|
sceneRangeMeasurement?.setting === custom.id ? sceneRangeMeasurement.veryClose : settings.veryClose;
|
||||||
|
const close = sceneRangeMeasurement?.setting === custom.id ? sceneRangeMeasurement.close : settings.close;
|
||||||
|
const far = sceneRangeMeasurement?.setting === custom.id ? sceneRangeMeasurement.far : settings.far;
|
||||||
|
if (distanceValue <= melee) {
|
||||||
|
result.distance = game.i18n.localize('DAGGERHEART.CONFIG.Range.melee.name');
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (distanceValue <= veryClose) {
|
||||||
|
result.distance = game.i18n.localize('DAGGERHEART.CONFIG.Range.veryClose.name');
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (distanceValue <= close) {
|
||||||
|
result.distance = game.i18n.localize('DAGGERHEART.CONFIG.Range.close.name');
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (distanceValue <= far) {
|
||||||
|
result.distance = game.i18n.localize('DAGGERHEART.CONFIG.Range.far.name');
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (distanceValue > far) {
|
||||||
|
result.distance = game.i18n.localize('DAGGERHEART.CONFIG.Range.veryFar.name');
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import DhMeasuredTemplate from './measuredTemplate.mjs';
|
|
||||||
|
|
||||||
export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
|
export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
async _draw(options) {
|
async _draw(options) {
|
||||||
|
|
@ -54,111 +52,30 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
|
||||||
if (this === target) return 0;
|
if (this === target) return 0;
|
||||||
|
|
||||||
const originPoint = this.center;
|
const originPoint = this.center;
|
||||||
const targetPoint = target.center;
|
const destinationPoint = target.center;
|
||||||
const thisBounds = this.bounds;
|
|
||||||
const targetBounds = target.bounds;
|
|
||||||
const adjacencyBuffer = canvas.grid.distance * 1.75; // handles diagonals with one square elevation difference
|
|
||||||
|
|
||||||
// Figure out the elevation difference.
|
|
||||||
// This intends to return "grid distance" for adjacent ones, so we add that number if not overlapping.
|
|
||||||
const sizePerUnit = canvas.grid.size / canvas.grid.distance;
|
|
||||||
const thisHeight = Math.max(thisBounds.width, thisBounds.height) / sizePerUnit;
|
|
||||||
const targetHeight = Math.max(targetBounds.width, targetBounds.height) / sizePerUnit;
|
|
||||||
const thisElevation = [this.document.elevation, this.document.elevation + thisHeight];
|
|
||||||
const targetElevation = [target.document.elevation, target.document.elevation + targetHeight];
|
|
||||||
const isSameAltitude =
|
|
||||||
thisElevation[0] < targetElevation[1] && // bottom of this must be at or below the top of target
|
|
||||||
thisElevation[1] > targetElevation[0]; // top of this must be at or above the bottom of target
|
|
||||||
const [lower, higher] = [targetElevation, thisElevation].sort((a, b) => a[1] - b[1]);
|
|
||||||
const elevation = isSameAltitude ? 0 : higher[0] - lower[1] + canvas.grid.distance;
|
|
||||||
|
|
||||||
// Compute for gridless. This version returns circular edge to edge + grid distance,
|
// Compute for gridless. This version returns circular edge to edge + grid distance,
|
||||||
// so that tokens that are touching return 5.
|
// so that tokens that are touching return 5.
|
||||||
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
|
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
|
||||||
const boundsCorrection = canvas.grid.distance / canvas.grid.size;
|
const boundsCorrection = canvas.grid.distance / canvas.grid.size;
|
||||||
const originRadius = (thisBounds.width * boundsCorrection) / 2;
|
const originRadius = (this.bounds.width * boundsCorrection) / 2;
|
||||||
const targetRadius = (targetBounds.width * boundsCorrection) / 2;
|
const targetRadius = (target.bounds.width * boundsCorrection) / 2;
|
||||||
const measuredDistance = canvas.grid.measurePath([
|
const distance = canvas.grid.measurePath([originPoint, destinationPoint]).distance;
|
||||||
{ ...originPoint, elevation: 0 },
|
return distance - originRadius - targetRadius + canvas.grid.distance;
|
||||||
{ ...targetPoint, elevation }
|
|
||||||
]).distance;
|
|
||||||
const distance = Math.floor(measuredDistance - originRadius - targetRadius + canvas.grid.distance);
|
|
||||||
return Math.min(distance, distance > adjacencyBuffer ? Infinity : canvas.grid.distance);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute what the closest grid space of each token is, then compute that distance
|
// Compute what the closest grid space of each token is, then compute that distance
|
||||||
const originEdge = this.#getEdgeBoundary(thisBounds, originPoint, targetPoint);
|
const originEdge = this.#getEdgeBoundary(this.bounds, originPoint, destinationPoint);
|
||||||
const targetEdge = this.#getEdgeBoundary(targetBounds, originPoint, targetPoint);
|
const targetEdge = this.#getEdgeBoundary(target.bounds, originPoint, destinationPoint);
|
||||||
const adjustedOriginPoint = originEdge
|
const adjustedOriginPoint = canvas.grid.getTopLeftPoint({
|
||||||
? canvas.grid.getTopLeftPoint({
|
|
||||||
x: originEdge.x + Math.sign(originPoint.x - originEdge.x),
|
x: originEdge.x + Math.sign(originPoint.x - originEdge.x),
|
||||||
y: originEdge.y + Math.sign(originPoint.y - originEdge.y)
|
y: originEdge.y + Math.sign(originPoint.y - originEdge.y)
|
||||||
})
|
});
|
||||||
: originPoint;
|
const adjustDestinationPoint = canvas.grid.getTopLeftPoint({
|
||||||
const adjustDestinationPoint = targetEdge
|
x: targetEdge.x + Math.sign(destinationPoint.x - targetEdge.x),
|
||||||
? canvas.grid.getTopLeftPoint({
|
y: targetEdge.y + Math.sign(destinationPoint.y - targetEdge.y)
|
||||||
x: targetEdge.x + Math.sign(targetPoint.x - targetEdge.x),
|
});
|
||||||
y: targetEdge.y + Math.sign(targetPoint.y - targetEdge.y)
|
return canvas.grid.measurePath([adjustedOriginPoint, adjustDestinationPoint]).distance;
|
||||||
})
|
|
||||||
: targetPoint;
|
|
||||||
const distance = canvas.grid.measurePath([
|
|
||||||
{ ...adjustedOriginPoint, elevation: 0 },
|
|
||||||
{ ...adjustDestinationPoint, elevation }
|
|
||||||
]).distance;
|
|
||||||
return Math.min(distance, distance > adjacencyBuffer ? Infinity : canvas.grid.distance);
|
|
||||||
}
|
|
||||||
|
|
||||||
_onHoverIn(event, options) {
|
|
||||||
super._onHoverIn(event, options);
|
|
||||||
|
|
||||||
// Check if the setting is enabled
|
|
||||||
const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance).showTokenDistance;
|
|
||||||
if (setting === 'never' || (setting === 'encounters' && !game.combat?.started)) return;
|
|
||||||
|
|
||||||
// Check if this token isn't invisible and is actually being hovered
|
|
||||||
const isTokenValid =
|
|
||||||
this.visible &&
|
|
||||||
this.hover &&
|
|
||||||
!this.isPreview &&
|
|
||||||
!this.document.isSecret &&
|
|
||||||
!this.controlled &&
|
|
||||||
!this.animation;
|
|
||||||
if (!isTokenValid) return;
|
|
||||||
|
|
||||||
// Ensure we have a single controlled token
|
|
||||||
const originToken = canvas.tokens.controlled[0];
|
|
||||||
if (!originToken || canvas.tokens.controlled.length > 1) return;
|
|
||||||
|
|
||||||
// Determine the actual range
|
|
||||||
const ranges = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules).rangeMeasurement;
|
|
||||||
const distanceResult = DhMeasuredTemplate.getRangeLabels(originToken.distanceTo(this), ranges);
|
|
||||||
const distanceLabel = `${distanceResult.distance} ${distanceResult.units}`.trim();
|
|
||||||
|
|
||||||
// Create the element
|
|
||||||
const element = document.createElement('div');
|
|
||||||
element.id = 'token-hover-distance';
|
|
||||||
element.classList.add('waypoint-label', 'last');
|
|
||||||
const ruler = document.createElement('i');
|
|
||||||
ruler.classList.add('fa-solid', 'fa-ruler');
|
|
||||||
element.appendChild(ruler);
|
|
||||||
const labelEl = document.createElement('span');
|
|
||||||
labelEl.classList.add('total-measurement');
|
|
||||||
labelEl.textContent = distanceLabel;
|
|
||||||
element.appendChild(labelEl);
|
|
||||||
|
|
||||||
// Position the element and add to the DOM
|
|
||||||
const center = this.getCenterPoint();
|
|
||||||
element.style.setProperty('--transformY', 'calc(-100% - 10px)');
|
|
||||||
element.style.setProperty('--position-y', `${this.y}px`);
|
|
||||||
element.style.setProperty('--position-x', `${center.x}px`);
|
|
||||||
element.style.setProperty('--ui-scale', String(canvas.dimensions.uiScale));
|
|
||||||
document.querySelector('#token-hover-distance')?.remove();
|
|
||||||
document.querySelector('#measurement').appendChild(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
_onHoverOut(...args) {
|
|
||||||
super._onHoverOut(...args);
|
|
||||||
document.querySelector('#token-hover-distance')?.remove();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the point at which a line starting at origin and ending at destination intersects the edge of the bounds */
|
/** Returns the point at which a line starting at origin and ending at destination intersects the edge of the bounds */
|
||||||
|
|
@ -183,6 +100,11 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Tests if the token is at least adjacent with another, with some leeway for diagonals */
|
||||||
|
isAdjacentWith(token) {
|
||||||
|
return this.distanceTo(token) <= canvas.grid.distance * 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
_drawBar(number, bar, data) {
|
_drawBar(number, bar, data) {
|
||||||
const val = Number(data.value);
|
const val = Number(data.value);
|
||||||
|
|
|
||||||
|
|
@ -494,275 +494,3 @@ export const subclassFeatureLabels = {
|
||||||
2: 'DAGGERHEART.ITEMS.DomainCard.specializationTitle',
|
2: 'DAGGERHEART.ITEMS.DomainCard.specializationTitle',
|
||||||
3: 'DAGGERHEART.ITEMS.DomainCard.masteryTitle'
|
3: 'DAGGERHEART.ITEMS.DomainCard.masteryTitle'
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {Object} TierData
|
|
||||||
* @property {number} difficulty
|
|
||||||
* @property {number} majorThreshold
|
|
||||||
* @property {number} severeThreshold
|
|
||||||
* @property {number} hp
|
|
||||||
* @property {number} stress
|
|
||||||
* @property {number} attack
|
|
||||||
* @property {number[]} damage
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @type {Record<string, Record<2 | 3 | 4, TierData>}
|
|
||||||
* Scaling data used to change an adversary's tier. Each rank is applied incrementally.
|
|
||||||
*/
|
|
||||||
export const adversaryScalingData = {
|
|
||||||
bruiser: {
|
|
||||||
2: {
|
|
||||||
difficulty: 2,
|
|
||||||
majorThreshold: 5,
|
|
||||||
severeThreshold: 10,
|
|
||||||
hp: 1,
|
|
||||||
stress: 2,
|
|
||||||
attack: 2,
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
difficulty: 2,
|
|
||||||
majorThreshold: 7,
|
|
||||||
severeThreshold: 15,
|
|
||||||
hp: 1,
|
|
||||||
stress: 0,
|
|
||||||
attack: 2,
|
|
||||||
},
|
|
||||||
4: {
|
|
||||||
difficulty: 2,
|
|
||||||
majorThreshold: 12,
|
|
||||||
severeThreshold: 25,
|
|
||||||
hp: 1,
|
|
||||||
stress: 0,
|
|
||||||
attack: 2,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
horde: {
|
|
||||||
2: {
|
|
||||||
difficulty: 2,
|
|
||||||
majorThreshold: 5,
|
|
||||||
severeThreshold: 8,
|
|
||||||
hp: 2,
|
|
||||||
stress: 0,
|
|
||||||
attack: 0,
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
difficulty: 2,
|
|
||||||
majorThreshold: 5,
|
|
||||||
severeThreshold: 12,
|
|
||||||
hp: 0,
|
|
||||||
stress: 1,
|
|
||||||
attack: 1,
|
|
||||||
},
|
|
||||||
4: {
|
|
||||||
difficulty: 2,
|
|
||||||
majorThreshold: 10,
|
|
||||||
severeThreshold: 15,
|
|
||||||
hp: 2,
|
|
||||||
stress: 0,
|
|
||||||
attack: 0,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
leader: {
|
|
||||||
2: {
|
|
||||||
difficulty: 2,
|
|
||||||
majorThreshold: 6,
|
|
||||||
severeThreshold: 10,
|
|
||||||
hp: 0,
|
|
||||||
stress: 0,
|
|
||||||
attack: 1,
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
difficulty: 2,
|
|
||||||
majorThreshold: 6,
|
|
||||||
severeThreshold: 15,
|
|
||||||
hp: 1,
|
|
||||||
stress: 0,
|
|
||||||
attack: 2,
|
|
||||||
},
|
|
||||||
4: {
|
|
||||||
difficulty: 2,
|
|
||||||
majorThreshold: 12,
|
|
||||||
severeThreshold: 25,
|
|
||||||
hp: 1,
|
|
||||||
stress: 1,
|
|
||||||
attack: 3,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
minion: {
|
|
||||||
2: {
|
|
||||||
difficulty: 2,
|
|
||||||
majorThreshold: 0,
|
|
||||||
severeThreshold: 0,
|
|
||||||
hp: 0,
|
|
||||||
stress: 0,
|
|
||||||
attack: 1,
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
difficulty: 2,
|
|
||||||
majorThreshold: 0,
|
|
||||||
severeThreshold: 0,
|
|
||||||
hp: 0,
|
|
||||||
stress: 1,
|
|
||||||
attack: 1,
|
|
||||||
},
|
|
||||||
4: {
|
|
||||||
difficulty: 2,
|
|
||||||
majorThreshold: 0,
|
|
||||||
severeThreshold: 0,
|
|
||||||
hp: 0,
|
|
||||||
stress: 0,
|
|
||||||
attack: 1,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ranged: {
|
|
||||||
2: {
|
|
||||||
difficulty: 2,
|
|
||||||
majorThreshold: 3,
|
|
||||||
severeThreshold: 6,
|
|
||||||
hp: 1,
|
|
||||||
stress: 0,
|
|
||||||
attack: 1,
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
difficulty: 2,
|
|
||||||
majorThreshold: 7,
|
|
||||||
severeThreshold: 14,
|
|
||||||
hp: 1,
|
|
||||||
stress: 1,
|
|
||||||
attack: 2,
|
|
||||||
},
|
|
||||||
4: {
|
|
||||||
difficulty: 2,
|
|
||||||
majorThreshold: 5,
|
|
||||||
severeThreshold: 10,
|
|
||||||
hp: 1,
|
|
||||||
stress: 1,
|
|
||||||
attack: 1,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
skulk: {
|
|
||||||
2: {
|
|
||||||
difficulty: 2,
|
|
||||||
majorThreshold: 3,
|
|
||||||
severeThreshold: 8,
|
|
||||||
hp: 1,
|
|
||||||
stress: 1,
|
|
||||||
attack: 1,
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
difficulty: 2,
|
|
||||||
majorThreshold: 8,
|
|
||||||
severeThreshold: 12,
|
|
||||||
hp: 1,
|
|
||||||
stress: 1,
|
|
||||||
attack: 1,
|
|
||||||
},
|
|
||||||
4: {
|
|
||||||
difficulty: 2,
|
|
||||||
majorThreshold: 8,
|
|
||||||
severeThreshold: 10,
|
|
||||||
hp: 1,
|
|
||||||
stress: 1,
|
|
||||||
attack: 1,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
solo: {
|
|
||||||
2: {
|
|
||||||
difficulty: 2,
|
|
||||||
majorThreshold: 5,
|
|
||||||
severeThreshold: 10,
|
|
||||||
hp: 0,
|
|
||||||
stress: 1,
|
|
||||||
attack: 2,
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
difficulty: 2,
|
|
||||||
majorThreshold: 7,
|
|
||||||
severeThreshold: 15,
|
|
||||||
hp: 2,
|
|
||||||
stress: 1,
|
|
||||||
attack: 2,
|
|
||||||
},
|
|
||||||
4: {
|
|
||||||
difficulty: 2,
|
|
||||||
majorThreshold: 12,
|
|
||||||
severeThreshold: 25,
|
|
||||||
hp: 0,
|
|
||||||
stress: 1,
|
|
||||||
attack: 3,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
standard: {
|
|
||||||
2: {
|
|
||||||
difficulty: 2,
|
|
||||||
majorThreshold: 3,
|
|
||||||
severeThreshold: 8,
|
|
||||||
hp: 0,
|
|
||||||
stress: 0,
|
|
||||||
attack: 1,
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
difficulty: 2,
|
|
||||||
majorThreshold: 7,
|
|
||||||
severeThreshold: 15,
|
|
||||||
hp: 1,
|
|
||||||
stress: 1,
|
|
||||||
attack: 1,
|
|
||||||
},
|
|
||||||
4: {
|
|
||||||
difficulty: 2,
|
|
||||||
majorThreshold: 10,
|
|
||||||
severeThreshold: 15,
|
|
||||||
hp: 0,
|
|
||||||
stress: 1,
|
|
||||||
attack: 1,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
support: {
|
|
||||||
2: {
|
|
||||||
difficulty: 2,
|
|
||||||
majorThreshold: 3,
|
|
||||||
severeThreshold: 8,
|
|
||||||
hp: 1,
|
|
||||||
stress: 1,
|
|
||||||
attack: 1,
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
difficulty: 2,
|
|
||||||
majorThreshold: 7,
|
|
||||||
severeThreshold: 12,
|
|
||||||
hp: 0,
|
|
||||||
stress: 0,
|
|
||||||
attack: 1,
|
|
||||||
},
|
|
||||||
4: {
|
|
||||||
difficulty: 2,
|
|
||||||
majorThreshold: 8,
|
|
||||||
severeThreshold: 10,
|
|
||||||
hp: 1,
|
|
||||||
stress: 1,
|
|
||||||
attack: 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scaling data used for an adversary's damage.
|
|
||||||
* Tier 4 is missing certain adversary types and therefore skews upwards.
|
|
||||||
* We manually set tier 4 data to hopefully lead to better results
|
|
||||||
*/
|
|
||||||
export const adversaryExpectedDamage = {
|
|
||||||
basic: {
|
|
||||||
1: { mean: 7.321428571428571, deviation: 1.962519002770912 },
|
|
||||||
2: { mean: 12.444444444444445, deviation: 2.0631069425529676 },
|
|
||||||
3: { mean: 15.722222222222221, deviation: 2.486565208464823 },
|
|
||||||
4: { mean: 26, deviation: 5.2 }
|
|
||||||
},
|
|
||||||
minion: {
|
|
||||||
1: { mean: 2.142857142857143, deviation: 1.0690449676496976 },
|
|
||||||
2: { mean: 5, deviation: 0.816496580927726 },
|
|
||||||
3: { mean: 6.5, deviation: 2.1213203435596424 },
|
|
||||||
4: { mean: 11, deviation: 1 }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -202,8 +202,7 @@ export const conditions = () => ({
|
||||||
id: 'vulnerable',
|
id: 'vulnerable',
|
||||||
name: 'DAGGERHEART.CONFIG.Condition.vulnerable.name',
|
name: 'DAGGERHEART.CONFIG.Condition.vulnerable.name',
|
||||||
img: 'icons/magic/control/silhouette-fall-slip-prone.webp',
|
img: 'icons/magic/control/silhouette-fall-slip-prone.webp',
|
||||||
description: 'DAGGERHEART.CONFIG.Condition.vulnerable.description',
|
description: 'DAGGERHEART.CONFIG.Condition.vulnerable.description'
|
||||||
autoApplyFlagId: 'auto-vulnerable'
|
|
||||||
},
|
},
|
||||||
hidden: {
|
hidden: {
|
||||||
id: 'hidden',
|
id: 'hidden',
|
||||||
|
|
@ -237,7 +236,6 @@ export const defaultRestOptions = {
|
||||||
actionType: 'action',
|
actionType: 'action',
|
||||||
chatDisplay: false,
|
chatDisplay: false,
|
||||||
target: {
|
target: {
|
||||||
amount: 1,
|
|
||||||
type: 'friendly'
|
type: 'friendly'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
|
|
@ -254,8 +252,7 @@ export const defaultRestOptions = {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
effects: []
|
|
||||||
},
|
},
|
||||||
clearStress: {
|
clearStress: {
|
||||||
id: 'clearStress',
|
id: 'clearStress',
|
||||||
|
|
@ -288,8 +285,7 @@ export const defaultRestOptions = {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
effects: []
|
|
||||||
},
|
},
|
||||||
repairArmor: {
|
repairArmor: {
|
||||||
id: 'repairArmor',
|
id: 'repairArmor',
|
||||||
|
|
@ -306,7 +302,6 @@ export const defaultRestOptions = {
|
||||||
actionType: 'action',
|
actionType: 'action',
|
||||||
chatDisplay: false,
|
chatDisplay: false,
|
||||||
target: {
|
target: {
|
||||||
amount: 1,
|
|
||||||
type: 'friendly'
|
type: 'friendly'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
|
|
@ -323,8 +318,7 @@ export const defaultRestOptions = {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
effects: []
|
|
||||||
},
|
},
|
||||||
prepare: {
|
prepare: {
|
||||||
id: 'prepare',
|
id: 'prepare',
|
||||||
|
|
@ -332,57 +326,7 @@ export const defaultRestOptions = {
|
||||||
icon: 'fa-solid fa-dumbbell',
|
icon: 'fa-solid fa-dumbbell',
|
||||||
img: 'icons/skills/trades/academics-merchant-scribe.webp',
|
img: 'icons/skills/trades/academics-merchant-scribe.webp',
|
||||||
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.prepare.description'),
|
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.prepare.description'),
|
||||||
actions: {
|
actions: {}
|
||||||
prepare: {
|
|
||||||
type: 'healing',
|
|
||||||
systemPath: 'restMoves.shortRest.moves.prepare.actions',
|
|
||||||
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.prepare.name'),
|
|
||||||
img: 'icons/skills/trades/academics-merchant-scribe.webp',
|
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: false,
|
|
||||||
target: {
|
|
||||||
type: 'self'
|
|
||||||
},
|
|
||||||
damage: {
|
|
||||||
parts: [
|
|
||||||
{
|
|
||||||
applyTo: healingTypes.hope.id,
|
|
||||||
value: {
|
|
||||||
custom: {
|
|
||||||
enabled: true,
|
|
||||||
formula: '1'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
prepareWithFriends: {
|
|
||||||
type: 'healing',
|
|
||||||
systemPath: 'restMoves.shortRest.moves.prepare.actions',
|
|
||||||
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.prepareWithFriends.name'),
|
|
||||||
img: 'icons/skills/trades/academics-merchant-scribe.webp',
|
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: false,
|
|
||||||
target: {
|
|
||||||
type: 'self'
|
|
||||||
},
|
|
||||||
damage: {
|
|
||||||
parts: [
|
|
||||||
{
|
|
||||||
applyTo: healingTypes.hope.id,
|
|
||||||
value: {
|
|
||||||
custom: {
|
|
||||||
enabled: true,
|
|
||||||
formula: '2'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
effects: []
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
longRest: () => ({
|
longRest: () => ({
|
||||||
|
|
@ -401,7 +345,6 @@ export const defaultRestOptions = {
|
||||||
actionType: 'action',
|
actionType: 'action',
|
||||||
chatDisplay: false,
|
chatDisplay: false,
|
||||||
target: {
|
target: {
|
||||||
amount: 1,
|
|
||||||
type: 'friendly'
|
type: 'friendly'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
|
|
@ -418,8 +361,7 @@ export const defaultRestOptions = {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
effects: []
|
|
||||||
},
|
},
|
||||||
clearStress: {
|
clearStress: {
|
||||||
id: 'clearStress',
|
id: 'clearStress',
|
||||||
|
|
@ -452,8 +394,7 @@ export const defaultRestOptions = {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
effects: []
|
|
||||||
},
|
},
|
||||||
repairArmor: {
|
repairArmor: {
|
||||||
id: 'repairArmor',
|
id: 'repairArmor',
|
||||||
|
|
@ -470,7 +411,6 @@ export const defaultRestOptions = {
|
||||||
actionType: 'action',
|
actionType: 'action',
|
||||||
chatDisplay: false,
|
chatDisplay: false,
|
||||||
target: {
|
target: {
|
||||||
amount: 1,
|
|
||||||
type: 'friendly'
|
type: 'friendly'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
|
|
@ -487,8 +427,7 @@ export const defaultRestOptions = {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
effects: []
|
|
||||||
},
|
},
|
||||||
prepare: {
|
prepare: {
|
||||||
id: 'prepare',
|
id: 'prepare',
|
||||||
|
|
@ -496,57 +435,7 @@ export const defaultRestOptions = {
|
||||||
icon: 'fa-solid fa-dumbbell',
|
icon: 'fa-solid fa-dumbbell',
|
||||||
img: 'icons/skills/trades/academics-merchant-scribe.webp',
|
img: 'icons/skills/trades/academics-merchant-scribe.webp',
|
||||||
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.prepare.description'),
|
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.prepare.description'),
|
||||||
actions: {
|
actions: {}
|
||||||
prepare: {
|
|
||||||
type: 'healing',
|
|
||||||
systemPath: 'restMoves.longRest.moves.prepare.actions',
|
|
||||||
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.prepare.name'),
|
|
||||||
img: 'icons/skills/trades/academics-merchant-scribe.webp',
|
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: false,
|
|
||||||
target: {
|
|
||||||
type: 'self'
|
|
||||||
},
|
|
||||||
damage: {
|
|
||||||
parts: [
|
|
||||||
{
|
|
||||||
applyTo: healingTypes.hope.id,
|
|
||||||
value: {
|
|
||||||
custom: {
|
|
||||||
enabled: true,
|
|
||||||
formula: '1'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
prepareWithFriends: {
|
|
||||||
type: 'healing',
|
|
||||||
systemPath: 'restMoves.longRest.moves.prepare.actions',
|
|
||||||
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.prepareWithFriends.name'),
|
|
||||||
img: 'icons/skills/trades/academics-merchant-scribe.webp',
|
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: false,
|
|
||||||
target: {
|
|
||||||
type: 'self'
|
|
||||||
},
|
|
||||||
damage: {
|
|
||||||
parts: [
|
|
||||||
{
|
|
||||||
applyTo: healingTypes.hope.id,
|
|
||||||
value: {
|
|
||||||
custom: {
|
|
||||||
enabled: true,
|
|
||||||
formula: '2'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
effects: []
|
|
||||||
},
|
},
|
||||||
workOnAProject: {
|
workOnAProject: {
|
||||||
id: 'workOnAProject',
|
id: 'workOnAProject',
|
||||||
|
|
@ -554,8 +443,7 @@ export const defaultRestOptions = {
|
||||||
icon: 'fa-solid fa-diagram-project',
|
icon: 'fa-solid fa-diagram-project',
|
||||||
img: 'icons/skills/social/thumbsup-approval-like.webp',
|
img: 'icons/skills/social/thumbsup-approval-like.webp',
|
||||||
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.workOnAProject.description'),
|
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.workOnAProject.description'),
|
||||||
actions: {},
|
actions: {}
|
||||||
effects: []
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -467,7 +467,9 @@ export const allArmorFeatures = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const orderedArmorFeatures = () => {
|
export const orderedArmorFeatures = () => {
|
||||||
const allFeatures = allArmorFeatures();
|
const homebrewFeatures = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).itemFeatures
|
||||||
|
.armorFeatures;
|
||||||
|
const allFeatures = { ...armorFeatures, ...homebrewFeatures };
|
||||||
const all = Object.keys(allFeatures).map(key => {
|
const all = Object.keys(allFeatures).map(key => {
|
||||||
const feature = allFeatures[key];
|
const feature = allFeatures[key];
|
||||||
return {
|
return {
|
||||||
|
|
@ -1402,7 +1404,9 @@ export const allWeaponFeatures = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const orderedWeaponFeatures = () => {
|
export const orderedWeaponFeatures = () => {
|
||||||
const allFeatures = allWeaponFeatures();
|
const homebrewFeatures = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).itemFeatures
|
||||||
|
.weaponFeatures;
|
||||||
|
const allFeatures = { ...weaponFeatures, ...homebrewFeatures };
|
||||||
const all = Object.keys(allFeatures).map(key => {
|
const all = Object.keys(allFeatures).map(key => {
|
||||||
const feature = allFeatures[key];
|
const feature = allFeatures[key];
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,6 @@ export const gameSettings = {
|
||||||
LastMigrationVersion: 'LastMigrationVersion',
|
LastMigrationVersion: 'LastMigrationVersion',
|
||||||
TagTeamRoll: 'TagTeamRoll',
|
TagTeamRoll: 'TagTeamRoll',
|
||||||
SpotlightRequestQueue: 'SpotlightRequestQueue',
|
SpotlightRequestQueue: 'SpotlightRequestQueue',
|
||||||
CompendiumBrowserSettings: 'CompendiumBrowserSettings'
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actionAutomationChoices = {
|
export const actionAutomationChoices = {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ export { default as DhCombatant } from './combatant.mjs';
|
||||||
export { default as DhTagTeamRoll } from './tagTeamRoll.mjs';
|
export { default as DhTagTeamRoll } from './tagTeamRoll.mjs';
|
||||||
export { default as DhRollTable } from './rollTable.mjs';
|
export { default as DhRollTable } from './rollTable.mjs';
|
||||||
export { default as RegisteredTriggers } from './registeredTriggers.mjs';
|
export { default as RegisteredTriggers } from './registeredTriggers.mjs';
|
||||||
export { default as CompendiumBrowserSettings } from './compendiumBrowserSettings.mjs';
|
|
||||||
|
|
||||||
export * as countdowns from './countdowns.mjs';
|
export * as countdowns from './countdowns.mjs';
|
||||||
export * as actions from './action/_module.mjs';
|
export * as actions from './action/_module.mjs';
|
||||||
|
|
|
||||||
|
|
@ -34,20 +34,6 @@ export default class DHAttackAction extends DHDamageAction {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
get damageFormula() {
|
|
||||||
const hitPointsPart = this.damage.parts.find(x => x.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id);
|
|
||||||
if (!hitPointsPart) return '0';
|
|
||||||
|
|
||||||
return hitPointsPart.value.getFormula();
|
|
||||||
}
|
|
||||||
|
|
||||||
get altDamageFormula() {
|
|
||||||
const hitPointsPart = this.damage.parts.find(x => x.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id);
|
|
||||||
if (!hitPointsPart) return '0';
|
|
||||||
|
|
||||||
return hitPointsPart.valueAlt.getFormula();
|
|
||||||
}
|
|
||||||
|
|
||||||
async use(event, options) {
|
async use(event, options) {
|
||||||
const result = await super.use(event, options);
|
const result = await super.use(event, options);
|
||||||
if (!result.message) return;
|
if (!result.message) return;
|
||||||
|
|
|
||||||
|
|
@ -114,24 +114,9 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
* Return Item the action is attached too.
|
* Return Item the action is attached too.
|
||||||
*/
|
*/
|
||||||
get item() {
|
get item() {
|
||||||
if (!this.parent.parent && this.systemPath)
|
|
||||||
return foundry.utils.getProperty(this.parent, this.systemPath).get(this.id);
|
|
||||||
|
|
||||||
return this.parent.parent;
|
return this.parent.parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
get applyEffects() {
|
|
||||||
if (this.item.systemPath) {
|
|
||||||
const itemEffectIds = this.item.effects.map(x => x._id);
|
|
||||||
const movePathSplit = this.item.systemPath.split('.');
|
|
||||||
movePathSplit.pop();
|
|
||||||
const move = foundry.utils.getProperty(this.parent, movePathSplit.join('.'));
|
|
||||||
return new Collection(itemEffectIds.map(id => [id, move.effects.find(x => x.id === id)]));
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.item.effects;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the first Actor parent found.
|
* Return the first Actor parent found.
|
||||||
*/
|
*/
|
||||||
|
|
@ -140,7 +125,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
? this.item
|
? this.item
|
||||||
: this.item?.parent instanceof DhpActor
|
: this.item?.parent instanceof DhpActor
|
||||||
? this.item.parent
|
? this.item.parent
|
||||||
: null;
|
: this.item?.actor;
|
||||||
}
|
}
|
||||||
|
|
||||||
static getRollType(parent) {
|
static getRollType(parent) {
|
||||||
|
|
@ -229,7 +214,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
|
|
||||||
if (Hooks.call(`${CONFIG.DH.id}.postUseAction`, this, config) === false) return;
|
if (Hooks.call(`${CONFIG.DH.id}.postUseAction`, this, config) === false) return;
|
||||||
|
|
||||||
if (this.chatDisplay && !config.actionChatMessageHandled) await this.toChat();
|
if (this.chatDisplay) await this.toChat();
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
@ -240,13 +225,9 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
* @returns {object}
|
* @returns {object}
|
||||||
*/
|
*/
|
||||||
prepareBaseConfig(event) {
|
prepareBaseConfig(event) {
|
||||||
const isActor = this.item instanceof CONFIG.Actor.documentClass;
|
|
||||||
const actionTitle = game.i18n.localize(this.name);
|
|
||||||
const itemTitle = isActor || this.item.name === actionTitle ? '' : `${this.item.name} - `;
|
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
event,
|
event,
|
||||||
title: `${itemTitle}${actionTitle}`,
|
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,
|
||||||
originItem: this.originItem,
|
originItem: this.originItem,
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,9 @@
|
||||||
import DHAdversarySettings from '../../applications/sheets-configs/adversary-settings.mjs';
|
import DHAdversarySettings from '../../applications/sheets-configs/adversary-settings.mjs';
|
||||||
import { ActionField } from '../fields/actionField.mjs';
|
import { ActionField } from '../fields/actionField.mjs';
|
||||||
import { commonActorRules } from './base.mjs';
|
import BaseDataActor, { commonActorRules } from './base.mjs';
|
||||||
import DhCreature from './creature.mjs';
|
|
||||||
import { resourceField, bonusField } from '../fields/actorField.mjs';
|
import { resourceField, bonusField } from '../fields/actorField.mjs';
|
||||||
import { calculateExpectedValue, parseTermsFromSimpleFormula } from '../../helpers/utils.mjs';
|
|
||||||
import { adversaryExpectedDamage, adversaryScalingData } from '../../config/actorConfig.mjs';
|
|
||||||
|
|
||||||
export default class DhpAdversary extends DhCreature {
|
export default class DhpAdversary extends BaseDataActor {
|
||||||
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Adversary'];
|
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Adversary'];
|
||||||
|
|
||||||
static get metadata() {
|
static get metadata() {
|
||||||
|
|
@ -43,14 +40,7 @@ export default class DhpAdversary extends DhCreature {
|
||||||
integer: true,
|
integer: true,
|
||||||
label: 'DAGGERHEART.GENERAL.hordeHp'
|
label: 'DAGGERHEART.GENERAL.hordeHp'
|
||||||
}),
|
}),
|
||||||
criticalThreshold: new fields.NumberField({
|
criticalThreshold: new fields.NumberField({ required: true, integer: true, min: 1, max: 20, initial: 20 }),
|
||||||
required: true,
|
|
||||||
integer: true,
|
|
||||||
min: 1,
|
|
||||||
max: 20,
|
|
||||||
initial: 20,
|
|
||||||
label: 'DAGGERHEART.ACTIONS.Settings.criticalThreshold'
|
|
||||||
}),
|
|
||||||
damageThresholds: new fields.SchemaField({
|
damageThresholds: new fields.SchemaField({
|
||||||
major: new fields.NumberField({
|
major: new fields.NumberField({
|
||||||
required: true,
|
required: true,
|
||||||
|
|
@ -190,10 +180,6 @@ export default class DhpAdversary extends DhCreature {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareDerivedData() {
|
|
||||||
this.attack.roll.isStandardAttack = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
_getTags() {
|
_getTags() {
|
||||||
const tags = [
|
const tags = [
|
||||||
game.i18n.localize(`DAGGERHEART.GENERAL.Tiers.${this.tier}`),
|
game.i18n.localize(`DAGGERHEART.GENERAL.Tiers.${this.tier}`),
|
||||||
|
|
@ -202,211 +188,4 @@ export default class DhpAdversary extends DhCreature {
|
||||||
];
|
];
|
||||||
return tags;
|
return tags;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns source data for this actor adjusted to a new tier, which can be used to create a new actor. */
|
|
||||||
adjustForTier(tier) {
|
|
||||||
const source = this.parent.toObject(true);
|
|
||||||
|
|
||||||
/** @type {(2 | 3 | 4)[]} */
|
|
||||||
const tiers = new Array(Math.abs(tier - this.tier))
|
|
||||||
.fill(0)
|
|
||||||
.map((_, idx) => idx + Math.min(tier, this.tier) + 1);
|
|
||||||
if (tier < this.tier) tiers.reverse();
|
|
||||||
const typeData = adversaryScalingData[source.system.type] ?? adversaryScalingData[source.system.standard];
|
|
||||||
const tierEntries = tiers.map(t => ({ tier: t, ...typeData[t] }));
|
|
||||||
|
|
||||||
// Apply simple tier changes
|
|
||||||
const scale = tier > this.tier ? 1 : -1;
|
|
||||||
for (const entry of tierEntries) {
|
|
||||||
source.system.difficulty += scale * entry.difficulty;
|
|
||||||
source.system.damageThresholds.major += scale * entry.majorThreshold;
|
|
||||||
source.system.damageThresholds.severe += scale * entry.severeThreshold;
|
|
||||||
source.system.resources.hitPoints.max += scale * entry.hp;
|
|
||||||
source.system.resources.stress.max += scale * entry.stress;
|
|
||||||
source.system.attack.roll.bonus += scale * entry.attack;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the mean and standard deviation of expected damage in the previous and new tier
|
|
||||||
// The data we have is for attack scaling, but we reuse this for action scaling later
|
|
||||||
const expectedDamageData = adversaryExpectedDamage[source.system.type] ?? adversaryExpectedDamage.basic;
|
|
||||||
const damageMeta = {
|
|
||||||
currentDamageRange: { tier: source.system.tier, ...expectedDamageData[source.system.tier] },
|
|
||||||
newDamageRange: { tier, ...expectedDamageData[tier] },
|
|
||||||
type: 'attack'
|
|
||||||
};
|
|
||||||
|
|
||||||
// Update damage of base attack
|
|
||||||
try {
|
|
||||||
this.#adjustActionDamage(source.system.attack, damageMeta);
|
|
||||||
} catch (err) {
|
|
||||||
ui.notifications.warn('Failed to convert attack damage of adversary');
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update damage of each item action, making sure to also update the description if possible
|
|
||||||
const damageRegex = /@Damage\[([^\[\]]*)\]({[^}]*})?/g;
|
|
||||||
for (const item of source.items) {
|
|
||||||
// Replace damage inlines with new formulas
|
|
||||||
for (const withDescription of [item.system, ...Object.values(item.system.actions)]) {
|
|
||||||
withDescription.description = withDescription.description.replace(damageRegex, (match, inner) => {
|
|
||||||
const { value: formula } = parseInlineParams(inner);
|
|
||||||
if (!formula || !type) return match;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const adjusted = this.#calculateAdjustedDamage(formula, { ...damageMeta, type: 'action' });
|
|
||||||
const newFormula = [
|
|
||||||
adjusted.diceQuantity ? `${adjusted.diceQuantity}d${adjusted.faces}` : null,
|
|
||||||
adjusted.bonus
|
|
||||||
]
|
|
||||||
.filter(p => !!p)
|
|
||||||
.join('+');
|
|
||||||
return match.replace(formula, newFormula);
|
|
||||||
} catch {
|
|
||||||
return match;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update damage in item actions
|
|
||||||
for (const action of Object.values(item.system.actions)) {
|
|
||||||
if (!action.damage) continue;
|
|
||||||
|
|
||||||
// Parse damage, and convert all formula matches in the descriptions to the new damage
|
|
||||||
try {
|
|
||||||
const result = this.#adjustActionDamage(action, { ...damageMeta, type: 'action' });
|
|
||||||
for (const { previousFormula, formula } of Object.values(result)) {
|
|
||||||
const oldFormulaRegexp = new RegExp(
|
|
||||||
previousFormula.replace(' ', '').replace('+', '(?:\\s)?\\+(?:\\s)?')
|
|
||||||
);
|
|
||||||
item.system.description = item.system.description.replace(oldFormulaRegexp, formula);
|
|
||||||
action.description = action.description.replace(oldFormulaRegexp, formula);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
ui.notifications.warn(`Failed to convert action damage for item ${item.name}`);
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finally set the tier of the source data, now that everything is complete
|
|
||||||
source.system.tier = tier;
|
|
||||||
return source;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a damage object to a new damage range
|
|
||||||
* @returns {{ diceQuantity: number; faces: number; bonus: number }} the adjusted result as a combined term
|
|
||||||
* @throws error if the formula is the wrong type
|
|
||||||
*/
|
|
||||||
#calculateAdjustedDamage(formula, { currentDamageRange, newDamageRange, type }) {
|
|
||||||
const terms = parseTermsFromSimpleFormula(formula);
|
|
||||||
const flatTerms = terms.filter(t => t.diceQuantity === 0);
|
|
||||||
const diceTerms = terms.filter(t => t.diceQuantity > 0);
|
|
||||||
if (flatTerms.length > 1 || diceTerms.length > 1) {
|
|
||||||
throw new Error('invalid formula for conversion');
|
|
||||||
}
|
|
||||||
const value = {
|
|
||||||
...(diceTerms[0] ?? { diceQuantity: 0, faces: 1 }),
|
|
||||||
bonus: flatTerms[0]?.bonus ?? 0
|
|
||||||
};
|
|
||||||
const previousExpected = calculateExpectedValue(value);
|
|
||||||
if (previousExpected === 0) return value; // nothing to do
|
|
||||||
|
|
||||||
const dieSizes = [4, 6, 8, 10, 12, 20];
|
|
||||||
const steps = newDamageRange.tier - currentDamageRange.tier;
|
|
||||||
const increasing = steps > 0;
|
|
||||||
const deviation = (previousExpected - currentDamageRange.mean) / currentDamageRange.deviation;
|
|
||||||
const expected = Math.max(1, newDamageRange.mean + newDamageRange.deviation * deviation);
|
|
||||||
|
|
||||||
// If this was just a flat number, convert to the expected damage and exit
|
|
||||||
if (value.diceQuantity === 0) {
|
|
||||||
value.bonus = Math.round(expected);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
const getExpectedDie = () => calculateExpectedValue({ diceQuantity: 1, faces: value.faces }) || 1;
|
|
||||||
const getBaseAverage = () => calculateExpectedValue({ ...value, bonus: 0 });
|
|
||||||
|
|
||||||
// Check the number of base overages over the expected die. In the end, if the bonus inflates too much, we add a die
|
|
||||||
const baseOverages = Math.floor(value.bonus / getExpectedDie());
|
|
||||||
|
|
||||||
// Prestep. Change number of dice for attacks, bump up/down for actions
|
|
||||||
// We never bump up to d20, though we might bump down from it
|
|
||||||
if (type === 'attack') {
|
|
||||||
const minimum = increasing ? value.diceQuantity : 0;
|
|
||||||
value.diceQuantity = Math.max(minimum, newDamageRange.tier);
|
|
||||||
} else {
|
|
||||||
const currentIdx = dieSizes.indexOf(value.faces);
|
|
||||||
value.faces = dieSizes[Math.clamp(currentIdx + steps, 0, 4)];
|
|
||||||
}
|
|
||||||
|
|
||||||
value.bonus = Math.round(expected - getBaseAverage());
|
|
||||||
|
|
||||||
// Attempt to handle negative values.
|
|
||||||
// If we can do it with only step downs, do so. Otherwise remove tier dice, and try again
|
|
||||||
if (value.bonus < 0) {
|
|
||||||
let stepsRequired = Math.ceil(Math.abs(value.bonus) / value.diceQuantity);
|
|
||||||
const currentIdx = dieSizes.indexOf(value.faces);
|
|
||||||
|
|
||||||
// If step downs alone don't suffice, change the flat modifier, then calculate steps required again
|
|
||||||
// If this isn't sufficient, the result will be slightly off. This is unlikely to happen
|
|
||||||
if (type !== 'attack' && stepsRequired > currentIdx && value.diceQuantity > 0) {
|
|
||||||
value.diceQuantity -= increasing ? 1 : Math.abs(steps);
|
|
||||||
value.bonus = Math.round(expected - getBaseAverage());
|
|
||||||
if (value.bonus >= 0) return value; // complete
|
|
||||||
}
|
|
||||||
|
|
||||||
stepsRequired = Math.ceil(Math.abs(value.bonus) / value.diceQuantity);
|
|
||||||
value.faces = dieSizes[Math.max(0, currentIdx - stepsRequired)];
|
|
||||||
value.bonus = Math.max(0, Math.round(expected - getBaseAverage()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// If value is really high, we add a number of dice based on the number of overages
|
|
||||||
// This attempts to preserve a similar amount of variance when increasing an action
|
|
||||||
const overagesToRemove = Math.floor(value.bonus / getExpectedDie()) - baseOverages;
|
|
||||||
if (type !== 'attack' && increasing && overagesToRemove > 0) {
|
|
||||||
value.diceQuantity += overagesToRemove;
|
|
||||||
value.bonus = Math.round(expected - getBaseAverage());
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates damage to reflect a specific value.
|
|
||||||
* @throws if damage structure is invalid for conversion
|
|
||||||
* @returns the converted formula and value as a simplified term
|
|
||||||
*/
|
|
||||||
#adjustActionDamage(action, damageMeta) {
|
|
||||||
// The current algorithm only returns a value if there is a single damage part
|
|
||||||
const hpDamageParts = action.damage.parts.filter(d => d.applyTo === 'hitPoints');
|
|
||||||
if (hpDamageParts.length !== 1) throw new Error('incorrect number of hp parts');
|
|
||||||
|
|
||||||
const result = {};
|
|
||||||
for (const property of ['value', 'valueAlt']) {
|
|
||||||
const data = hpDamageParts[0][property];
|
|
||||||
const previousFormula = data.custom.enabled
|
|
||||||
? data.custom.formula
|
|
||||||
: [data.flatMultiplier ? `${data.flatMultiplier}${data.dice}` : 0, data.bonus ?? 0]
|
|
||||||
.filter(p => !!p)
|
|
||||||
.join('+');
|
|
||||||
const value = this.#calculateAdjustedDamage(previousFormula, damageMeta);
|
|
||||||
const formula = [value.diceQuantity ? `${value.diceQuantity}d${value.faces}` : null, value.bonus]
|
|
||||||
.filter(p => !!p)
|
|
||||||
.join('+');
|
|
||||||
if (value.diceQuantity) {
|
|
||||||
data.custom.enabled = false;
|
|
||||||
data.bonus = value.bonus;
|
|
||||||
data.dice = `d${value.faces}`;
|
|
||||||
data.flatMultiplier = value.diceQuantity;
|
|
||||||
} else if (!value.diceQuantity) {
|
|
||||||
data.custom.enabled = true;
|
|
||||||
data.custom.formula = formula;
|
|
||||||
}
|
|
||||||
|
|
||||||
result[property] = { previousFormula, formula, value };
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,40 +29,17 @@ const resistanceField = (resistanceLabel, immunityLabel, reductionLabel) =>
|
||||||
/* Common rules applying to Characters and Adversaries */
|
/* Common rules applying to Characters and Adversaries */
|
||||||
export const commonActorRules = (extendedData = { damageReduction: {}, attack: { damage: {} } }) => ({
|
export const commonActorRules = (extendedData = { damageReduction: {}, attack: { damage: {} } }) => ({
|
||||||
conditionImmunities: new fields.SchemaField({
|
conditionImmunities: new fields.SchemaField({
|
||||||
hidden: new fields.BooleanField({
|
hidden: new fields.BooleanField({ initial: false }),
|
||||||
initial: false,
|
restrained: new fields.BooleanField({ initial: false }),
|
||||||
label: 'DAGGERHEART.GENERAL.Rules.conditionImmunities.hidden'
|
vulnerable: new fields.BooleanField({ initial: false })
|
||||||
}),
|
|
||||||
restrained: new fields.BooleanField({
|
|
||||||
initial: false,
|
|
||||||
label: 'DAGGERHEART.GENERAL.Rules.conditionImmunities.restrained'
|
|
||||||
}),
|
|
||||||
vulnerable: new fields.BooleanField({
|
|
||||||
initial: false,
|
|
||||||
label: 'DAGGERHEART.GENERAL.Rules.conditionImmunities.vulnerable'
|
|
||||||
})
|
|
||||||
}),
|
}),
|
||||||
damageReduction: new fields.SchemaField({
|
damageReduction: new fields.SchemaField({
|
||||||
thresholdImmunities: new fields.SchemaField({
|
thresholdImmunities: new fields.SchemaField({
|
||||||
minor: new fields.BooleanField({
|
minor: new fields.BooleanField({ initial: false })
|
||||||
initial: false,
|
|
||||||
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.thresholdImmunities.minor.label',
|
|
||||||
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.thresholdImmunities.minor.hint'
|
|
||||||
})
|
|
||||||
}),
|
}),
|
||||||
reduceSeverity: new fields.SchemaField({
|
reduceSeverity: new fields.SchemaField({
|
||||||
magical: new fields.NumberField({
|
magical: new fields.NumberField({ initial: 0, min: 0 }),
|
||||||
initial: 0,
|
physical: new fields.NumberField({ initial: 0, min: 0 })
|
||||||
min: 0,
|
|
||||||
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.reduceSeverity.magical.label',
|
|
||||||
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.reduceSeverity.magical.hint'
|
|
||||||
}),
|
|
||||||
physical: new fields.NumberField({
|
|
||||||
initial: 0,
|
|
||||||
min: 0,
|
|
||||||
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.reduceSeverity.physical.label',
|
|
||||||
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.reduceSeverity.physical.hint'
|
|
||||||
})
|
|
||||||
}),
|
}),
|
||||||
...(extendedData.damageReduction ?? {})
|
...(extendedData.damageReduction ?? {})
|
||||||
}),
|
}),
|
||||||
|
|
@ -72,16 +49,12 @@ export const commonActorRules = (extendedData = { damageReduction: {}, attack: {
|
||||||
hpDamageMultiplier: new fields.NumberField({
|
hpDamageMultiplier: new fields.NumberField({
|
||||||
required: true,
|
required: true,
|
||||||
nullable: false,
|
nullable: false,
|
||||||
initial: 1,
|
initial: 1
|
||||||
label: 'DAGGERHEART.GENERAL.Attack.hpDamageMultiplier.label',
|
|
||||||
hint: 'DAGGERHEART.GENERAL.Attack.hpDamageMultiplier.hint'
|
|
||||||
}),
|
}),
|
||||||
hpDamageTakenMultiplier: new fields.NumberField({
|
hpDamageTakenMultiplier: new fields.NumberField({
|
||||||
required: true,
|
required: true,
|
||||||
nullable: false,
|
nullable: false,
|
||||||
initial: 1,
|
initial: 1
|
||||||
label: 'DAGGERHEART.GENERAL.Attack.hpDamageTakenMultiplier.label',
|
|
||||||
hint: 'DAGGERHEART.GENERAL.Attack.hpDamageTakenMultiplier.hint'
|
|
||||||
}),
|
}),
|
||||||
...(extendedData.attack?.damage ?? {})
|
...(extendedData.attack?.damage ?? {})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
import { burden } from '../../config/generalConfig.mjs';
|
import { burden } from '../../config/generalConfig.mjs';
|
||||||
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
||||||
import DhLevelData from '../levelData.mjs';
|
import DhLevelData from '../levelData.mjs';
|
||||||
import { commonActorRules } from './base.mjs';
|
import BaseDataActor, { commonActorRules } from './base.mjs';
|
||||||
import DhCreature from './creature.mjs';
|
|
||||||
import { attributeField, resourceField, stressDamageReductionRule, bonusField } from '../fields/actorField.mjs';
|
import { attributeField, resourceField, stressDamageReductionRule, bonusField } from '../fields/actorField.mjs';
|
||||||
import { ActionField } from '../fields/actionField.mjs';
|
import { ActionField } from '../fields/actionField.mjs';
|
||||||
import DHCharacterSettings from '../../applications/sheets-configs/character-settings.mjs';
|
import DHCharacterSettings from '../../applications/sheets-configs/character-settings.mjs';
|
||||||
|
|
||||||
export default class DhCharacter extends DhCreature {
|
export default class DhCharacter extends BaseDataActor {
|
||||||
/**@override */
|
/**@override */
|
||||||
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Character'];
|
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Character'];
|
||||||
|
|
||||||
|
|
@ -36,18 +35,14 @@ export default class DhCharacter extends DhCreature {
|
||||||
'DAGGERHEART.ACTORS.Character.maxHPBonus'
|
'DAGGERHEART.ACTORS.Character.maxHPBonus'
|
||||||
),
|
),
|
||||||
stress: resourceField(6, 0, 'DAGGERHEART.GENERAL.stress', true),
|
stress: resourceField(6, 0, 'DAGGERHEART.GENERAL.stress', true),
|
||||||
hope: new fields.SchemaField(
|
hope: new fields.SchemaField({
|
||||||
{
|
|
||||||
value: new fields.NumberField({
|
value: new fields.NumberField({
|
||||||
initial: 2,
|
initial: 2,
|
||||||
min: 0,
|
min: 0,
|
||||||
integer: true,
|
integer: true,
|
||||||
label: 'DAGGERHEART.GENERAL.hope'
|
label: 'DAGGERHEART.GENERAL.hope'
|
||||||
}),
|
})
|
||||||
isReversed: new fields.BooleanField({ initial: false })
|
})
|
||||||
},
|
|
||||||
{ label: 'DAGGERHEART.GENERAL.hope' }
|
|
||||||
)
|
|
||||||
}),
|
}),
|
||||||
traits: new fields.SchemaField({
|
traits: new fields.SchemaField({
|
||||||
agility: attributeField('DAGGERHEART.CONFIG.Traits.agility.name'),
|
agility: attributeField('DAGGERHEART.CONFIG.Traits.agility.name'),
|
||||||
|
|
@ -132,6 +127,14 @@ export default class DhCharacter extends DhCreature {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
advantageSources: new fields.ArrayField(new fields.StringField(), {
|
||||||
|
label: 'DAGGERHEART.ACTORS.Character.advantageSources.label',
|
||||||
|
hint: 'DAGGERHEART.ACTORS.Character.advantageSources.hint'
|
||||||
|
}),
|
||||||
|
disadvantageSources: new fields.ArrayField(new fields.StringField(), {
|
||||||
|
label: 'DAGGERHEART.ACTORS.Character.disadvantageSources.label',
|
||||||
|
hint: 'DAGGERHEART.ACTORS.Character.disadvantageSources.hint'
|
||||||
|
}),
|
||||||
levelData: new fields.EmbeddedDataField(DhLevelData),
|
levelData: new fields.EmbeddedDataField(DhLevelData),
|
||||||
bonuses: new fields.SchemaField({
|
bonuses: new fields.SchemaField({
|
||||||
roll: new fields.SchemaField({
|
roll: new fields.SchemaField({
|
||||||
|
|
@ -218,16 +221,8 @@ export default class DhCharacter extends DhCreature {
|
||||||
rules: new fields.SchemaField({
|
rules: new fields.SchemaField({
|
||||||
...commonActorRules({
|
...commonActorRules({
|
||||||
damageReduction: {
|
damageReduction: {
|
||||||
magical: new fields.BooleanField({
|
magical: new fields.BooleanField({ initial: false }),
|
||||||
initial: false,
|
physical: new fields.BooleanField({ initial: false }),
|
||||||
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.magical.label',
|
|
||||||
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.magical.hint'
|
|
||||||
}),
|
|
||||||
physical: new fields.BooleanField({
|
|
||||||
initial: false,
|
|
||||||
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.physical.label',
|
|
||||||
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.physical.hint'
|
|
||||||
}),
|
|
||||||
maxArmorMarked: new fields.SchemaField({
|
maxArmorMarked: new fields.SchemaField({
|
||||||
value: new fields.NumberField({
|
value: new fields.NumberField({
|
||||||
required: true,
|
required: true,
|
||||||
|
|
@ -257,10 +252,7 @@ export default class DhCharacter extends DhCreature {
|
||||||
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.label',
|
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.label',
|
||||||
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.hint'
|
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.hint'
|
||||||
}),
|
}),
|
||||||
disabledArmor: new fields.BooleanField({
|
disabledArmor: new fields.BooleanField({ intial: false })
|
||||||
intial: false,
|
|
||||||
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.disabledArmor.label'
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
attack: {
|
attack: {
|
||||||
damage: {
|
damage: {
|
||||||
|
|
@ -308,14 +300,12 @@ export default class DhCharacter extends DhCreature {
|
||||||
label: 'DAGGERHEART.ACTORS.Character.defaultFearDice'
|
label: 'DAGGERHEART.ACTORS.Character.defaultFearDice'
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
|
runeWard: new fields.BooleanField({ initial: false }),
|
||||||
burden: new fields.SchemaField({
|
burden: new fields.SchemaField({
|
||||||
ignore: new fields.BooleanField({ label: 'DAGGERHEART.ACTORS.Character.burden.ignore.label' })
|
ignore: new fields.BooleanField()
|
||||||
}),
|
}),
|
||||||
roll: new fields.SchemaField({
|
roll: new fields.SchemaField({
|
||||||
guaranteedCritical: new fields.BooleanField({
|
guaranteedCritical: new fields.BooleanField()
|
||||||
label: 'DAGGERHEART.ACTORS.Character.roll.guaranteedCritical.label',
|
|
||||||
hint: 'DAGGERHEART.ACTORS.Character.roll.guaranteedCritical.hint'
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
@ -670,7 +660,7 @@ export default class DhCharacter extends DhCreature {
|
||||||
};
|
};
|
||||||
|
|
||||||
const globalHopeMax = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxHope;
|
const globalHopeMax = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxHope;
|
||||||
this.resources.hope.max = globalHopeMax;
|
this.resources.hope.max = globalHopeMax - this.scars;
|
||||||
this.resources.hitPoints.max += this.class.value?.system?.hitPoints ?? 0;
|
this.resources.hitPoints.max += this.class.value?.system?.hitPoints ?? 0;
|
||||||
|
|
||||||
/* Companion Related Data */
|
/* Companion Related Data */
|
||||||
|
|
@ -694,7 +684,6 @@ export default class DhCharacter extends DhCreature {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.resources.hope.max -= this.scars;
|
|
||||||
this.resources.hope.value = Math.min(baseHope, this.resources.hope.max);
|
this.resources.hope.value = Math.min(baseHope, this.resources.hope.max);
|
||||||
this.attack.roll.trait = this.rules.attack.roll.trait ?? this.attack.roll.trait;
|
this.attack.roll.trait = this.rules.attack.roll.trait ?? this.attack.roll.trait;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import DhCreature from './creature.mjs';
|
import BaseDataActor from './base.mjs';
|
||||||
import DhLevelData from '../levelData.mjs';
|
import DhLevelData from '../levelData.mjs';
|
||||||
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
||||||
import { ActionField } from '../fields/actionField.mjs';
|
import { ActionField } from '../fields/actionField.mjs';
|
||||||
|
|
@ -6,7 +6,7 @@ import { adjustDice, adjustRange } from '../../helpers/utils.mjs';
|
||||||
import DHCompanionSettings from '../../applications/sheets-configs/companion-settings.mjs';
|
import DHCompanionSettings from '../../applications/sheets-configs/companion-settings.mjs';
|
||||||
import { resourceField, bonusField } from '../fields/actorField.mjs';
|
import { resourceField, bonusField } from '../fields/actorField.mjs';
|
||||||
|
|
||||||
export default class DhCompanion extends DhCreature {
|
export default class DhCompanion extends BaseDataActor {
|
||||||
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Companion'];
|
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Companion'];
|
||||||
|
|
||||||
/**@inheritdoc */
|
/**@inheritdoc */
|
||||||
|
|
@ -53,18 +53,9 @@ export default class DhCompanion extends DhCreature {
|
||||||
),
|
),
|
||||||
rules: new fields.SchemaField({
|
rules: new fields.SchemaField({
|
||||||
conditionImmunities: new fields.SchemaField({
|
conditionImmunities: new fields.SchemaField({
|
||||||
hidden: new fields.BooleanField({
|
hidden: new fields.BooleanField({ initial: false }),
|
||||||
initial: false,
|
restrained: new fields.BooleanField({ initial: false }),
|
||||||
label: 'DAGGERHEART.GENERAL.Rules.conditionImmunities.hidden'
|
vulnerable: new fields.BooleanField({ initial: false })
|
||||||
}),
|
|
||||||
restrained: new fields.BooleanField({
|
|
||||||
initial: false,
|
|
||||||
label: 'DAGGERHEART.GENERAL.Rules.conditionImmunities.restrained'
|
|
||||||
}),
|
|
||||||
vulnerable: new fields.BooleanField({
|
|
||||||
initial: false,
|
|
||||||
label: 'DAGGERHEART.GENERAL.Rules.conditionImmunities.vulnerable'
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
attack: new ActionField({
|
attack: new ActionField({
|
||||||
|
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
import BaseDataActor from './base.mjs';
|
|
||||||
|
|
||||||
export default class DhCreature extends BaseDataActor {
|
|
||||||
/**@inheritdoc */
|
|
||||||
static defineSchema() {
|
|
||||||
const fields = foundry.data.fields;
|
|
||||||
|
|
||||||
return {
|
|
||||||
...super.defineSchema(),
|
|
||||||
advantageSources: new fields.ArrayField(new fields.StringField(), {
|
|
||||||
label: 'DAGGERHEART.ACTORS.Character.advantageSources.label',
|
|
||||||
hint: 'DAGGERHEART.ACTORS.Character.advantageSources.hint'
|
|
||||||
}),
|
|
||||||
disadvantageSources: new fields.ArrayField(new fields.StringField(), {
|
|
||||||
label: 'DAGGERHEART.ACTORS.Character.disadvantageSources.label',
|
|
||||||
hint: 'DAGGERHEART.ACTORS.Character.disadvantageSources.hint'
|
|
||||||
})
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
get isAutoVulnerableActive() {
|
|
||||||
const vulnerableAppliedByOther = this.parent.effects.some(
|
|
||||||
x => x.statuses.has('vulnerable') && !x.flags.daggerheart?.autoApplyFlagId
|
|
||||||
);
|
|
||||||
return !vulnerableAppliedByOther;
|
|
||||||
}
|
|
||||||
|
|
||||||
async _preUpdate(changes, options, userId) {
|
|
||||||
const allowed = await super._preUpdate(changes, options, userId);
|
|
||||||
if (allowed === false) return;
|
|
||||||
|
|
||||||
const automationSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation);
|
|
||||||
if (
|
|
||||||
automationSettings.vulnerableAutomation &&
|
|
||||||
this.parent.type !== 'companion' &&
|
|
||||||
changes.system?.resources?.stress?.value
|
|
||||||
) {
|
|
||||||
const { name, description, img, autoApplyFlagId } = CONFIG.DH.GENERAL.conditions().vulnerable;
|
|
||||||
const autoEffects = this.parent.effects.filter(
|
|
||||||
x => x.flags.daggerheart?.autoApplyFlagId === autoApplyFlagId
|
|
||||||
);
|
|
||||||
if (changes.system.resources.stress.value >= this.resources.stress.max) {
|
|
||||||
if (!autoEffects.length)
|
|
||||||
this.parent.createEmbeddedDocuments('ActiveEffect', [
|
|
||||||
{
|
|
||||||
name: game.i18n.localize(name),
|
|
||||||
description: game.i18n.localize(description),
|
|
||||||
img: img,
|
|
||||||
statuses: ['vulnerable'],
|
|
||||||
flags: { daggerheart: { autoApplyFlagId } }
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
} else if (this.resources.stress.value >= this.resources.stress.max) {
|
|
||||||
this.parent.deleteEmbeddedDocuments(
|
|
||||||
'ActiveEffect',
|
|
||||||
autoEffects.map(x => x.id)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -31,7 +31,6 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
||||||
static defineSchema() {
|
static defineSchema() {
|
||||||
return {
|
return {
|
||||||
title: new fields.StringField(),
|
title: new fields.StringField(),
|
||||||
actionDescription: new fields.HTMLField(),
|
|
||||||
roll: new fields.ObjectField(),
|
roll: new fields.ObjectField(),
|
||||||
targets: targetsField(),
|
targets: targetsField(),
|
||||||
hasRoll: new fields.BooleanField({ initial: false }),
|
hasRoll: new fields.BooleanField({ initial: false }),
|
||||||
|
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
export default class CompendiumBrowserSettings extends foundry.abstract.DataModel {
|
|
||||||
static defineSchema() {
|
|
||||||
const fields = foundry.data.fields;
|
|
||||||
|
|
||||||
return {
|
|
||||||
excludedSources: new fields.TypedObjectField(
|
|
||||||
new fields.SchemaField({
|
|
||||||
excludedDocumentTypes: new fields.ArrayField(
|
|
||||||
new fields.StringField({ required: true, choices: CONST.SYSTEM_SPECIFIC_COMPENDIUM_TYPES })
|
|
||||||
)
|
|
||||||
})
|
|
||||||
),
|
|
||||||
excludedPacks: new fields.TypedObjectField(
|
|
||||||
new fields.SchemaField({
|
|
||||||
excludedDocumentTypes: new fields.ArrayField(
|
|
||||||
new fields.StringField({ required: true, choices: CONST.SYSTEM_SPECIFIC_COMPENDIUM_TYPES })
|
|
||||||
)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
isEntryExcluded(item) {
|
|
||||||
const pack = game.packs.get(item.pack);
|
|
||||||
if (!pack) return false;
|
|
||||||
|
|
||||||
const packageName = pack.metadata.packageType === 'world' ? 'world' : pack.metadata.packageName;
|
|
||||||
const excludedSourceData = this.excludedSources[packageName];
|
|
||||||
if (excludedSourceData && excludedSourceData.excludedDocumentTypes.includes(pack.metadata.type)) return true;
|
|
||||||
|
|
||||||
const excludedPackData = this.excludedPacks[item.pack];
|
|
||||||
if (excludedPackData && excludedPackData.excludedDocumentTypes.includes(pack.metadata.type)) return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -68,8 +68,6 @@ export default class DamageField extends fields.SchemaField {
|
||||||
|
|
||||||
const damageResult = await CONFIG.Dice.daggerheart.DamageRoll.build(damageConfig);
|
const damageResult = await CONFIG.Dice.daggerheart.DamageRoll.build(damageConfig);
|
||||||
if (!damageResult) return false;
|
if (!damageResult) return false;
|
||||||
if (damageResult.actionChatMessageHandled) config.actionChatMessageHandled = true;
|
|
||||||
|
|
||||||
config.damage = damageResult.damage;
|
config.damage = damageResult.damage;
|
||||||
config.message ??= damageConfig.message;
|
config.message ??= damageConfig.message;
|
||||||
}
|
}
|
||||||
|
|
@ -109,8 +107,8 @@ export default class DamageField extends fields.SchemaField {
|
||||||
);
|
);
|
||||||
else {
|
else {
|
||||||
const configDamage = foundry.utils.deepClone(config.damage);
|
const configDamage = foundry.utils.deepClone(config.damage);
|
||||||
const hpDamageMultiplier = config.actionActor?.system.rules?.attack?.damage?.hpDamageMultiplier ?? 1;
|
const hpDamageMultiplier = config.actionActor?.system.rules.attack.damage.hpDamageMultiplier ?? 1;
|
||||||
const hpDamageTakenMultiplier = actor.system.rules?.attack?.damage?.hpDamageTakenMultiplier;
|
const hpDamageTakenMultiplier = actor.system.rules.attack.damage.hpDamageTakenMultiplier;
|
||||||
if (configDamage.hitPoints) {
|
if (configDamage.hitPoints) {
|
||||||
for (const part of configDamage.hitPoints.parts) {
|
for (const part of configDamage.hitPoints.parts) {
|
||||||
part.total = Math.ceil(part.total * hpDamageMultiplier * hpDamageTakenMultiplier);
|
part.total = Math.ceil(part.total * hpDamageMultiplier * hpDamageTakenMultiplier);
|
||||||
|
|
@ -165,8 +163,7 @@ export default class DamageField extends fields.SchemaField {
|
||||||
if (data.hasRoll && part.resultBased && data.roll.result.duality === -1) return part.valueAlt;
|
if (data.hasRoll && part.resultBased && data.roll.result.duality === -1) return part.valueAlt;
|
||||||
|
|
||||||
const isAdversary = this.actor.type === 'adversary';
|
const isAdversary = this.actor.type === 'adversary';
|
||||||
const isHorde = this.actor.system.type === CONFIG.DH.ACTOR.adversaryTypes.horde.id;
|
if (isAdversary && this.actor.system.type === CONFIG.DH.ACTOR.adversaryTypes.horde.id) {
|
||||||
if (isAdversary && isHorde && this.roll?.isStandardAttack) {
|
|
||||||
const hasHordeDamage = this.actor.effects.find(x => x.type === 'horde');
|
const hasHordeDamage = this.actor.effects.find(x => x.type === 'horde');
|
||||||
if (hasHordeDamage && !hasHordeDamage.disabled) return part.valueAlt;
|
if (hasHordeDamage && !hasHordeDamage.disabled) return part.valueAlt;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ export default class EffectsField extends fields.ArrayField {
|
||||||
});
|
});
|
||||||
|
|
||||||
effects.forEach(async e => {
|
effects.forEach(async e => {
|
||||||
const effect = (this.item.applyEffects ?? this.item.effects).get(e._id);
|
const effect = this.item.effects.get(e._id);
|
||||||
if (!token.actor || !effect) return;
|
if (!token.actor || !effect) return;
|
||||||
await EffectsField.applyEffect(effect, token.actor);
|
await EffectsField.applyEffect(effect, token.actor);
|
||||||
});
|
});
|
||||||
|
|
@ -96,7 +96,7 @@ export default class EffectsField extends fields.ArrayField {
|
||||||
content: await foundry.applications.handlebars.renderTemplate(
|
content: await foundry.applications.handlebars.renderTemplate(
|
||||||
'systems/daggerheart/templates/ui/chat/effectSummary.hbs',
|
'systems/daggerheart/templates/ui/chat/effectSummary.hbs',
|
||||||
{
|
{
|
||||||
effects: this.effects.map(e => (this.item.applyEffects ?? this.item.effects).get(e._id)),
|
effects: this.effects.map(e => this.item.effects.get(e._id)),
|
||||||
targets: messageTargets
|
targets: messageTargets
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -123,7 +123,7 @@ export default class EffectsField extends fields.ArrayField {
|
||||||
|
|
||||||
// Otherwise, create a new effect on the target
|
// Otherwise, create a new effect on the target
|
||||||
const effectData = foundry.utils.mergeObject({
|
const effectData = foundry.utils.mergeObject({
|
||||||
...(effect.toObject?.() ?? effect),
|
...effect.toObject(),
|
||||||
disabled: false,
|
disabled: false,
|
||||||
transfer: false,
|
transfer: false,
|
||||||
origin: effect.uuid
|
origin: effect.uuid
|
||||||
|
|
|
||||||
|
|
@ -152,9 +152,7 @@ export function ActionMixin(Base) {
|
||||||
}
|
}
|
||||||
|
|
||||||
get uuid() {
|
get uuid() {
|
||||||
const isItem = this.item instanceof game.system.api.documents.DHItem;
|
return `${this.item.uuid}.${this.documentName}.${this.id}`;
|
||||||
const isActor = this.item instanceof game.system.api.documents.DhpActor;
|
|
||||||
return isItem || isActor ? `${this.item.uuid}.${this.documentName}.${this.id}` : null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get sheet() {
|
get sheet() {
|
||||||
|
|
@ -262,9 +260,6 @@ export function ActionMixin(Base) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async toChat(origin) {
|
async toChat(origin) {
|
||||||
const autoExpandDescription = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance)
|
|
||||||
.expandRollMessage?.desc;
|
|
||||||
|
|
||||||
const cls = getDocumentClass('ChatMessage');
|
const cls = getDocumentClass('ChatMessage');
|
||||||
const systemData = {
|
const systemData = {
|
||||||
title: game.i18n.localize('DAGGERHEART.CONFIG.FeatureForm.action'),
|
title: game.i18n.localize('DAGGERHEART.CONFIG.FeatureForm.action'),
|
||||||
|
|
@ -293,7 +288,7 @@ export function ActionMixin(Base) {
|
||||||
system: systemData,
|
system: systemData,
|
||||||
content: await foundry.applications.handlebars.renderTemplate(
|
content: await foundry.applications.handlebars.renderTemplate(
|
||||||
'systems/daggerheart/templates/ui/chat/action.hbs',
|
'systems/daggerheart/templates/ui/chat/action.hbs',
|
||||||
{ ...systemData, open: autoExpandDescription ? 'open' : '' }
|
systemData
|
||||||
),
|
),
|
||||||
flags: {
|
flags: {
|
||||||
daggerheart: {
|
daggerheart: {
|
||||||
|
|
|
||||||
|
|
@ -7,20 +7,16 @@ const attributeField = label =>
|
||||||
});
|
});
|
||||||
|
|
||||||
const resourceField = (max = 0, initial = 0, label, reverse = false, maxLabel) =>
|
const resourceField = (max = 0, initial = 0, label, reverse = false, maxLabel) =>
|
||||||
new fields.SchemaField(
|
new fields.SchemaField({
|
||||||
{
|
|
||||||
value: new fields.NumberField({ initial: initial, min: 0, integer: true, label }),
|
value: new fields.NumberField({ initial: initial, min: 0, integer: true, label }),
|
||||||
max: new fields.NumberField({
|
max: new fields.NumberField({
|
||||||
initial: max,
|
initial: max,
|
||||||
integer: true,
|
integer: true,
|
||||||
label:
|
label:
|
||||||
maxLabel ??
|
maxLabel ?? game.i18n.format('DAGGERHEART.GENERAL.maxWithThing', { thing: game.i18n.localize(label) })
|
||||||
game.i18n.format('DAGGERHEART.GENERAL.maxWithThing', { thing: game.i18n.localize(label) })
|
|
||||||
}),
|
}),
|
||||||
isReversed: new fields.BooleanField({ initial: reverse })
|
isReversed: new fields.BooleanField({ initial: reverse })
|
||||||
},
|
});
|
||||||
{ label }
|
|
||||||
);
|
|
||||||
|
|
||||||
const stressDamageReductionRule = localizationPath =>
|
const stressDamageReductionRule = localizationPath =>
|
||||||
new fields.SchemaField({
|
new fields.SchemaField({
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import BaseDataItem from './base.mjs';
|
import BaseDataItem from './base.mjs';
|
||||||
import ItemLinkFields from '../../data/fields/itemLinkFields.mjs';
|
import ItemLinkFields from '../../data/fields/itemLinkFields.mjs';
|
||||||
import { getFeaturesHTMLData } from '../../helpers/utils.mjs';
|
|
||||||
|
|
||||||
export default class DHAncestry extends BaseDataItem {
|
export default class DHAncestry extends BaseDataItem {
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
|
|
@ -20,6 +19,7 @@ export default class DHAncestry extends BaseDataItem {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**@override */
|
/**@override */
|
||||||
|
|
@ -42,18 +42,4 @@ export default class DHAncestry extends BaseDataItem {
|
||||||
get secondaryFeature() {
|
get secondaryFeature() {
|
||||||
return this.features.find(x => x.type === CONFIG.DH.ITEM.featureSubTypes.secondary)?.item;
|
return this.features.find(x => x.type === CONFIG.DH.ITEM.featureSubTypes.secondary)?.item;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**@inheritdoc */
|
|
||||||
async getDescriptionData() {
|
|
||||||
const baseDescription = this.description;
|
|
||||||
const features = await getFeaturesHTMLData(this.features);
|
|
||||||
|
|
||||||
if (!features.length) return { prefix: null, value: baseDescription, suffix: null };
|
|
||||||
const suffix = await foundry.applications.handlebars.renderTemplate(
|
|
||||||
'systems/daggerheart/templates/sheets/items/description.hbs',
|
|
||||||
{ label: 'DAGGERHEART.ITEMS.Ancestry.featuresLabel', features }
|
|
||||||
);
|
|
||||||
|
|
||||||
return { prefix: null, value: baseDescription, suffix };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,9 @@ export default class DHArmor extends AttachableItem {
|
||||||
armorFeatures: new fields.ArrayField(
|
armorFeatures: new fields.ArrayField(
|
||||||
new fields.SchemaField({
|
new fields.SchemaField({
|
||||||
value: new fields.StringField({
|
value: new fields.StringField({
|
||||||
required: true
|
required: true,
|
||||||
|
choices: CONFIG.DH.ITEM.allArmorFeatures,
|
||||||
|
blank: true
|
||||||
}),
|
}),
|
||||||
effectIds: new fields.ArrayField(new fields.StringField({ required: true })),
|
effectIds: new fields.ArrayField(new fields.StringField({ required: true })),
|
||||||
actionIds: new fields.ArrayField(new fields.StringField({ required: true }))
|
actionIds: new fields.ArrayField(new fields.StringField({ required: true }))
|
||||||
|
|
@ -56,11 +58,12 @@ export default class DHArmor extends AttachableItem {
|
||||||
async getDescriptionData() {
|
async getDescriptionData() {
|
||||||
const baseDescription = this.description;
|
const baseDescription = this.description;
|
||||||
const allFeatures = CONFIG.DH.ITEM.allArmorFeatures();
|
const allFeatures = CONFIG.DH.ITEM.allArmorFeatures();
|
||||||
const features = this.armorFeatures.map(x => allFeatures[x.value]).filter(x => x);
|
const features = this.armorFeatures.map(x => allFeatures[x.value]);
|
||||||
|
if (!features.length) return { prefix: null, value: baseDescription, suffix: null };
|
||||||
|
|
||||||
const prefix = await foundry.applications.handlebars.renderTemplate(
|
const prefix = await foundry.applications.handlebars.renderTemplate(
|
||||||
'systems/daggerheart/templates/sheets/items/armor/description.hbs',
|
'systems/daggerheart/templates/sheets/items/armor/description.hbs',
|
||||||
{ item: this.parent, features }
|
{ features }
|
||||||
);
|
);
|
||||||
|
|
||||||
return { prefix, value: baseDescription, suffix: null };
|
return { prefix, value: baseDescription, suffix: null };
|
||||||
|
|
|
||||||
|
|
@ -253,20 +253,4 @@ export default class DHBeastform extends BaseDataItem {
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_onCreate(_data, _options, userId) {
|
|
||||||
if (!this.actor && game.user.id === userId) {
|
|
||||||
const hasBeastformEffect = this.parent.effects.some(x => x.type === 'beastform');
|
|
||||||
if (!hasBeastformEffect)
|
|
||||||
this.parent.createEmbeddedDocuments('ActiveEffect', [
|
|
||||||
{
|
|
||||||
type: 'beastform',
|
|
||||||
name: game.i18n.localize('DAGGERHEART.ITEMS.Beastform.beastformEffect'),
|
|
||||||
img: 'icons/creatures/abilities/paw-print-pair-purple.webp'
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import BaseDataItem from './base.mjs';
|
||||||
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
||||||
import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs';
|
import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs';
|
||||||
import ItemLinkFields from '../fields/itemLinkFields.mjs';
|
import ItemLinkFields from '../fields/itemLinkFields.mjs';
|
||||||
import { addLinkedItemsDiff, getFeaturesHTMLData, updateLinkedItemApps } from '../../helpers/utils.mjs';
|
import { addLinkedItemsDiff, updateLinkedItemApps } from '../../helpers/utils.mjs';
|
||||||
|
|
||||||
export default class DHClass extends BaseDataItem {
|
export default class DHClass extends BaseDataItem {
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
|
|
@ -163,56 +163,4 @@ export default class DHClass extends BaseDataItem {
|
||||||
|
|
||||||
updateLinkedItemApps(options, this.parent.sheet);
|
updateLinkedItemApps(options, this.parent.sheet);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**@inheritdoc */
|
|
||||||
async getDescriptionData() {
|
|
||||||
const baseDescription = this.description;
|
|
||||||
|
|
||||||
const getDomainLabel = domain => {
|
|
||||||
const data = CONFIG.DH.DOMAIN.allDomains()[domain];
|
|
||||||
return data ? game.i18n.localize(data.label) : '';
|
|
||||||
};
|
|
||||||
let domainsLabel = '';
|
|
||||||
if (this.domains.length) {
|
|
||||||
if (this.domains.length === 1) domainsLabel = getDomainLabel(this.domains[0]);
|
|
||||||
else {
|
|
||||||
const firstDomains = this.domains
|
|
||||||
.slice(0, this.domains.length - 1)
|
|
||||||
.map(getDomainLabel)
|
|
||||||
.join(', ');
|
|
||||||
const lastDomain = getDomainLabel(this.domains[this.domains.length - 1]);
|
|
||||||
domainsLabel = game.i18n.format('DAGGERHEART.GENERAL.thingsAndThing', {
|
|
||||||
things: firstDomains,
|
|
||||||
thing: lastDomain
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const classItems = [];
|
|
||||||
for (const itemData of this.inventory.choiceB) {
|
|
||||||
const linkData = [
|
|
||||||
undefined,
|
|
||||||
'UUID', // type
|
|
||||||
itemData.uuid // target
|
|
||||||
];
|
|
||||||
const contentLink = await foundry.applications.ux.TextEditor.implementation._createContentLink(linkData);
|
|
||||||
classItems.push(contentLink.outerHTML);
|
|
||||||
}
|
|
||||||
|
|
||||||
const hopeFeatures = await getFeaturesHTMLData(this.hopeFeatures);
|
|
||||||
const classFeatures = await getFeaturesHTMLData(this.classFeatures);
|
|
||||||
|
|
||||||
const suffix = await foundry.applications.handlebars.renderTemplate(
|
|
||||||
'systems/daggerheart/templates/sheets/items/class/description.hbs',
|
|
||||||
{
|
|
||||||
class: this.parent,
|
|
||||||
domains: domainsLabel,
|
|
||||||
classItems,
|
|
||||||
hopeFeatures,
|
|
||||||
classFeatures
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return { prefix: null, value: baseDescription, suffix };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { getFeaturesHTMLData } from '../../helpers/utils.mjs';
|
|
||||||
import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs';
|
import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs';
|
||||||
import BaseDataItem from './base.mjs';
|
import BaseDataItem from './base.mjs';
|
||||||
|
|
||||||
|
|
@ -25,17 +24,4 @@ export default class DHCommunity extends BaseDataItem {
|
||||||
/**@override */
|
/**@override */
|
||||||
static DEFAULT_ICON = 'systems/daggerheart/assets/icons/documents/items/village.svg';
|
static DEFAULT_ICON = 'systems/daggerheart/assets/icons/documents/items/village.svg';
|
||||||
|
|
||||||
/**@inheritdoc */
|
|
||||||
async getDescriptionData() {
|
|
||||||
const baseDescription = this.description;
|
|
||||||
const features = await getFeaturesHTMLData(this.features);
|
|
||||||
|
|
||||||
if (!features.length) return { prefix: null, value: baseDescription, suffix: null };
|
|
||||||
const suffix = await foundry.applications.handlebars.renderTemplate(
|
|
||||||
'systems/daggerheart/templates/sheets/items/description.hbs',
|
|
||||||
{ label: 'DAGGERHEART.ITEMS.Community.featuresLabel', features }
|
|
||||||
);
|
|
||||||
|
|
||||||
return { prefix: null, value: baseDescription, suffix };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -94,10 +94,8 @@ export default class DHDomainCard extends BaseDataItem {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.actor.system.loadoutSlot.available && !this.loadoutIgnore) {
|
if (!this.actor.system.loadoutSlot.available) {
|
||||||
data.system.inVault = true;
|
data.system.inVault = true;
|
||||||
await this.updateSource({ inVault: true });
|
|
||||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.loadoutMaxReached'));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { getFeaturesHTMLData } from '../../helpers/utils.mjs';
|
|
||||||
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
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';
|
||||||
|
|
@ -90,28 +89,4 @@ export default class DHSubclass extends BaseDataItem {
|
||||||
const allowed = await super._preCreate(data, options, user);
|
const allowed = await super._preCreate(data, options, user);
|
||||||
if (allowed === false) return;
|
if (allowed === false) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**@inheritdoc */
|
|
||||||
async getDescriptionData() {
|
|
||||||
const baseDescription = this.description;
|
|
||||||
|
|
||||||
const spellcastTrait = this.spellcastingTrait
|
|
||||||
? game.i18n.localize(CONFIG.DH.ACTOR.abilities[this.spellcastingTrait].label)
|
|
||||||
: null;
|
|
||||||
const foundationFeatures = await getFeaturesHTMLData(this.foundationFeatures);
|
|
||||||
const specializationFeatures = await getFeaturesHTMLData(this.specializationFeatures);
|
|
||||||
const masteryFeatures = await getFeaturesHTMLData(this.masteryFeatures);
|
|
||||||
|
|
||||||
const suffix = await foundry.applications.handlebars.renderTemplate(
|
|
||||||
'systems/daggerheart/templates/sheets/items/subclass/description.hbs',
|
|
||||||
{
|
|
||||||
spellcastTrait,
|
|
||||||
foundationFeatures,
|
|
||||||
specializationFeatures,
|
|
||||||
masteryFeatures
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return { prefix: null, value: baseDescription, suffix };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,9 @@ export default class DHWeapon extends AttachableItem {
|
||||||
weaponFeatures: new fields.ArrayField(
|
weaponFeatures: new fields.ArrayField(
|
||||||
new fields.SchemaField({
|
new fields.SchemaField({
|
||||||
value: new fields.StringField({
|
value: new fields.StringField({
|
||||||
required: true
|
required: true,
|
||||||
|
choices: CONFIG.DH.ITEM.allWeaponFeatures,
|
||||||
|
blank: true
|
||||||
}),
|
}),
|
||||||
effectIds: new fields.ArrayField(new fields.StringField({ required: true })),
|
effectIds: new fields.ArrayField(new fields.StringField({ required: true })),
|
||||||
actionIds: new fields.ArrayField(new fields.StringField({ required: true }))
|
actionIds: new fields.ArrayField(new fields.StringField({ required: true }))
|
||||||
|
|
@ -111,26 +113,13 @@ export default class DHWeapon extends AttachableItem {
|
||||||
/**@inheritdoc */
|
/**@inheritdoc */
|
||||||
async getDescriptionData() {
|
async getDescriptionData() {
|
||||||
const baseDescription = this.description;
|
const baseDescription = this.description;
|
||||||
|
|
||||||
const tier = game.i18n.localize(`DAGGERHEART.GENERAL.Tiers.${this.tier}`);
|
|
||||||
const trait = game.i18n.localize(CONFIG.DH.ACTOR.abilities[this.attack.roll.trait].label);
|
|
||||||
const range = game.i18n.localize(`DAGGERHEART.CONFIG.Range.${this.attack.range}.name`);
|
|
||||||
const damage = Roll.replaceFormulaData(this.attack.damageFormula, this.parent.parent ?? this.parent);
|
|
||||||
const burden = game.i18n.localize(CONFIG.DH.GENERAL.burden[this.burden].label);
|
|
||||||
|
|
||||||
const allFeatures = CONFIG.DH.ITEM.allWeaponFeatures();
|
const allFeatures = CONFIG.DH.ITEM.allWeaponFeatures();
|
||||||
const features = this.weaponFeatures.map(x => allFeatures[x.value]).filter(x => x);
|
const features = this.weaponFeatures.map(x => allFeatures[x.value]);
|
||||||
|
if (!features.length) return { prefix: null, value: baseDescription, suffix: null };
|
||||||
|
|
||||||
const prefix = await foundry.applications.handlebars.renderTemplate(
|
const prefix = await foundry.applications.handlebars.renderTemplate(
|
||||||
'systems/daggerheart/templates/sheets/items/weapon/description.hbs',
|
'systems/daggerheart/templates/sheets/items/weapon/description.hbs',
|
||||||
{
|
{ features }
|
||||||
features,
|
|
||||||
tier,
|
|
||||||
trait,
|
|
||||||
range,
|
|
||||||
damage,
|
|
||||||
burden
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return { prefix, value: baseDescription, suffix: null };
|
return { prefix, value: baseDescription, suffix: null };
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,7 @@ export default class DhLevelData extends foundry.abstract.DataModel {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
level: new fields.SchemaField({
|
level: new fields.SchemaField({
|
||||||
current: new fields.NumberField({
|
current: new fields.NumberField({ required: true, integer: true, initial: 1 }),
|
||||||
required: true,
|
|
||||||
integer: true,
|
|
||||||
initial: 1,
|
|
||||||
label: 'DAGGERHEART.GENERAL.currentLevel'
|
|
||||||
}),
|
|
||||||
changed: new fields.NumberField({ required: true, integer: true, initial: 1 }),
|
changed: new fields.NumberField({ required: true, integer: true, initial: 1 }),
|
||||||
bonuses: new fields.TypedObjectField(new fields.NumberField({ integer: true, nullable: false }))
|
bonuses: new fields.TypedObjectField(new fields.NumberField({ integer: true, nullable: false }))
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ export default class RegisteredTriggers extends Map {
|
||||||
unregisterSceneEnvironmentTriggers(flagSystemData) {
|
unregisterSceneEnvironmentTriggers(flagSystemData) {
|
||||||
const sceneData = new game.system.api.data.scenes.DHScene(flagSystemData);
|
const sceneData = new game.system.api.data.scenes.DHScene(flagSystemData);
|
||||||
for (const environment of sceneData.sceneEnvironments) {
|
for (const environment of sceneData.sceneEnvironments) {
|
||||||
if (!environment || environment.pack) continue;
|
if (environment.pack) continue;
|
||||||
this.unregisterItemTriggers(environment.system.features);
|
this.unregisterItemTriggers(environment.system.features);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,30 +37,11 @@ export default class DhAppearance extends foundry.abstract.DataModel {
|
||||||
extendEnvironmentDescriptions: new BooleanField(),
|
extendEnvironmentDescriptions: new BooleanField(),
|
||||||
extendItemDescriptions: new BooleanField(),
|
extendItemDescriptions: new BooleanField(),
|
||||||
expandRollMessage: new SchemaField({
|
expandRollMessage: new SchemaField({
|
||||||
desc: new BooleanField({ initial: true }),
|
desc: new BooleanField(),
|
||||||
roll: new BooleanField(),
|
roll: new BooleanField(),
|
||||||
damage: new BooleanField(),
|
damage: new BooleanField(),
|
||||||
target: new BooleanField()
|
target: new BooleanField()
|
||||||
}),
|
}),
|
||||||
showTokenDistance: new StringField({
|
|
||||||
required: true,
|
|
||||||
choices: {
|
|
||||||
always: {
|
|
||||||
value: 'always',
|
|
||||||
label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.showTokenDistance.choices.always'
|
|
||||||
},
|
|
||||||
encounters: {
|
|
||||||
value: 'encounters',
|
|
||||||
label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.showTokenDistance.choices.encounters'
|
|
||||||
},
|
|
||||||
never: {
|
|
||||||
value: 'never',
|
|
||||||
label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.showTokenDistance.choices.never'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
nullable: false,
|
|
||||||
initial: 'always'
|
|
||||||
}),
|
|
||||||
hideAttribution: new BooleanField(),
|
hideAttribution: new BooleanField(),
|
||||||
showGenericStatusEffects: new BooleanField({ initial: true })
|
showGenericStatusEffects: new BooleanField({ initial: true })
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,6 @@ export default class DhAutomation extends foundry.abstract.DataModel {
|
||||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.hopeFear.players.label'
|
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.hopeFear.players.label'
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
vulnerableAutomation: new fields.BooleanField({
|
|
||||||
initial: true,
|
|
||||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.vulnerableAutomation.label'
|
|
||||||
}),
|
|
||||||
countdownAutomation: new fields.BooleanField({
|
countdownAutomation: new fields.BooleanField({
|
||||||
required: true,
|
required: true,
|
||||||
initial: true,
|
initial: true,
|
||||||
|
|
@ -59,23 +55,6 @@ export default class DhAutomation extends foundry.abstract.DataModel {
|
||||||
initial: true,
|
initial: true,
|
||||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.resourceScrollTexts.label'
|
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.resourceScrollTexts.label'
|
||||||
}),
|
}),
|
||||||
deathMoveAutomation: new fields.SchemaField({
|
|
||||||
avoidDeath: new fields.BooleanField({
|
|
||||||
required: true,
|
|
||||||
initial: true,
|
|
||||||
label: 'DAGGERHEART.CONFIG.DeathMoves.avoidDeath.name'
|
|
||||||
}),
|
|
||||||
riskItAll: new fields.BooleanField({
|
|
||||||
required: true,
|
|
||||||
initial: true,
|
|
||||||
label: 'DAGGERHEART.CONFIG.DeathMoves.riskItAll.name'
|
|
||||||
}),
|
|
||||||
blazeOfGlory: new fields.BooleanField({
|
|
||||||
required: true,
|
|
||||||
initial: true,
|
|
||||||
label: 'DAGGERHEART.CONFIG.DeathMoves.blazeOfGlory.name'
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
defeated: new fields.SchemaField({
|
defeated: new fields.SchemaField({
|
||||||
enabled: new fields.BooleanField({
|
enabled: new fields.BooleanField({
|
||||||
required: true,
|
required: true,
|
||||||
|
|
|
||||||
|
|
@ -12,20 +12,6 @@ const currencyField = (initial, label, icon) =>
|
||||||
icon: new foundry.data.fields.StringField({ required: true, nullable: false, blank: true, initial: icon })
|
icon: new foundry.data.fields.StringField({ required: true, nullable: false, blank: true, initial: icon })
|
||||||
});
|
});
|
||||||
|
|
||||||
const restMoveField = () =>
|
|
||||||
new foundry.data.fields.SchemaField({
|
|
||||||
name: new foundry.data.fields.StringField({ required: true }),
|
|
||||||
icon: new foundry.data.fields.StringField({ required: true }),
|
|
||||||
img: new foundry.data.fields.FilePathField({
|
|
||||||
initial: 'icons/magic/life/cross-worn-green.webp',
|
|
||||||
categories: ['IMAGE'],
|
|
||||||
base64: false
|
|
||||||
}),
|
|
||||||
description: new foundry.data.fields.HTMLField(),
|
|
||||||
actions: new ActionsField(),
|
|
||||||
effects: new foundry.data.fields.ArrayField(new foundry.data.fields.ObjectField())
|
|
||||||
});
|
|
||||||
|
|
||||||
export default class DhHomebrew extends foundry.abstract.DataModel {
|
export default class DhHomebrew extends foundry.abstract.DataModel {
|
||||||
static defineSchema() {
|
static defineSchema() {
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
|
|
@ -119,11 +105,37 @@ export default class DhHomebrew extends foundry.abstract.DataModel {
|
||||||
restMoves: new fields.SchemaField({
|
restMoves: new fields.SchemaField({
|
||||||
longRest: new fields.SchemaField({
|
longRest: new fields.SchemaField({
|
||||||
nrChoices: new fields.NumberField({ required: true, integer: true, min: 1, initial: 2 }),
|
nrChoices: new fields.NumberField({ required: true, integer: true, min: 1, initial: 2 }),
|
||||||
moves: new fields.TypedObjectField(restMoveField(), { initial: defaultRestOptions.longRest() })
|
moves: new fields.TypedObjectField(
|
||||||
|
new fields.SchemaField({
|
||||||
|
name: new fields.StringField({ required: true }),
|
||||||
|
icon: new fields.StringField({ required: true }),
|
||||||
|
img: new fields.FilePathField({
|
||||||
|
initial: 'icons/magic/life/cross-worn-green.webp',
|
||||||
|
categories: ['IMAGE'],
|
||||||
|
base64: false
|
||||||
|
}),
|
||||||
|
description: new fields.HTMLField(),
|
||||||
|
actions: new ActionsField()
|
||||||
|
}),
|
||||||
|
{ initial: defaultRestOptions.longRest() }
|
||||||
|
)
|
||||||
}),
|
}),
|
||||||
shortRest: new fields.SchemaField({
|
shortRest: new fields.SchemaField({
|
||||||
nrChoices: new fields.NumberField({ required: true, integer: true, min: 1, initial: 2 }),
|
nrChoices: new fields.NumberField({ required: true, integer: true, min: 1, initial: 2 }),
|
||||||
moves: new fields.TypedObjectField(restMoveField(), { initial: defaultRestOptions.shortRest() })
|
moves: new fields.TypedObjectField(
|
||||||
|
new fields.SchemaField({
|
||||||
|
name: new fields.StringField({ required: true }),
|
||||||
|
icon: new fields.StringField({ required: true }),
|
||||||
|
img: new fields.FilePathField({
|
||||||
|
initial: 'icons/magic/life/cross-worn-green.webp',
|
||||||
|
categories: ['IMAGE'],
|
||||||
|
base64: false
|
||||||
|
}),
|
||||||
|
description: new fields.HTMLField(),
|
||||||
|
actions: new ActionsField()
|
||||||
|
}),
|
||||||
|
{ initial: defaultRestOptions.shortRest() }
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
domains: new fields.TypedObjectField(
|
domains: new fields.TypedObjectField(
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import DamageDialog from '../applications/dialogs/damageDialog.mjs';
|
import DamageDialog from '../applications/dialogs/damageDialog.mjs';
|
||||||
import { parseRallyDice } from '../helpers/utils.mjs';
|
|
||||||
import { RefreshType, socketEvent } from '../systemRegistration/socket.mjs';
|
import { RefreshType, socketEvent } from '../systemRegistration/socket.mjs';
|
||||||
import DHRoll from './dhRoll.mjs';
|
import DHRoll from './dhRoll.mjs';
|
||||||
|
|
||||||
|
|
@ -34,7 +33,7 @@ export default class DamageRoll extends DHRoll {
|
||||||
static async buildPost(roll, config, message) {
|
static async buildPost(roll, config, message) {
|
||||||
const chatMessage = config.source?.message
|
const chatMessage = config.source?.message
|
||||||
? ui.chat.collection.get(config.source.message)
|
? ui.chat.collection.get(config.source.message)
|
||||||
: getDocumentClass('ChatMessage').applyRollMode({}, config.rollMode ?? CONST.DICE_ROLL_MODES.PUBLIC);
|
: getDocumentClass('ChatMessage').applyRollMode({}, config.rollMode);
|
||||||
if (game.modules.get('dice-so-nice')?.active) {
|
if (game.modules.get('dice-so-nice')?.active) {
|
||||||
const pool = foundry.dice.terms.PoolTerm.fromRolls(
|
const pool = foundry.dice.terms.PoolTerm.fromRolls(
|
||||||
Object.values(config.damage).flatMap(r => r.parts.map(p => p.roll))
|
Object.values(config.damage).flatMap(r => r.parts.map(p => p.roll))
|
||||||
|
|
@ -47,14 +46,9 @@ export default class DamageRoll extends DHRoll {
|
||||||
chatMessage.whisper?.length > 0 ? chatMessage.whisper : null,
|
chatMessage.whisper?.length > 0 ? chatMessage.whisper : null,
|
||||||
chatMessage.blind
|
chatMessage.blind
|
||||||
);
|
);
|
||||||
config.mute = true;
|
|
||||||
}
|
}
|
||||||
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 });
|
|
||||||
|
|
||||||
if (!game.modules.get('dice-so-nice')?.active) foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static unifyDamageRoll(rolls) {
|
static unifyDamageRoll(rolls) {
|
||||||
|
|
@ -198,7 +192,7 @@ export default class DamageRoll extends DHRoll {
|
||||||
// Bardic Rally
|
// Bardic Rally
|
||||||
const rallyChoices = config.data?.parent?.appliedEffects.reduce((a, c) => {
|
const rallyChoices = config.data?.parent?.appliedEffects.reduce((a, c) => {
|
||||||
const change = c.changes.find(ch => ch.key === 'system.bonuses.rally');
|
const change = c.changes.find(ch => ch.key === 'system.bonuses.rally');
|
||||||
if (change) a.push({ value: c.id, label: parseRallyDice(change.value, c) });
|
if (change) a.push({ value: c.id, label: change.value });
|
||||||
return a;
|
return a;
|
||||||
}, []);
|
}, []);
|
||||||
if (rallyChoices.length) {
|
if (rallyChoices.length) {
|
||||||
|
|
|
||||||
|
|
@ -96,19 +96,6 @@ export default class DHRoll extends Roll {
|
||||||
}
|
}
|
||||||
|
|
||||||
static async toMessage(roll, config) {
|
static async toMessage(roll, config) {
|
||||||
const item = config.data.parent?.items?.get?.(config.source.item) ?? null;
|
|
||||||
const action = item ? item.system.actions.get(config.source.action) : null;
|
|
||||||
let actionDescription = null;
|
|
||||||
if (action?.chatDisplay) {
|
|
||||||
actionDescription = action
|
|
||||||
? await foundry.applications.ux.TextEditor.implementation.enrichHTML(action.description, {
|
|
||||||
relativeTo: config.data,
|
|
||||||
rollData: config.data.getRollData?.() ?? {}
|
|
||||||
})
|
|
||||||
: null;
|
|
||||||
config.actionChatMessageHandled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cls = getDocumentClass('ChatMessage'),
|
const cls = getDocumentClass('ChatMessage'),
|
||||||
msgData = {
|
msgData = {
|
||||||
type: this.messageType,
|
type: this.messageType,
|
||||||
|
|
@ -116,7 +103,7 @@ export default class DHRoll extends Roll {
|
||||||
title: roll.title,
|
title: roll.title,
|
||||||
speaker: cls.getSpeaker({ actor: roll.data?.parent }),
|
speaker: cls.getSpeaker({ actor: roll.data?.parent }),
|
||||||
sound: config.mute ? null : CONFIG.sounds.dice,
|
sound: config.mute ? null : CONFIG.sounds.dice,
|
||||||
system: { ...config, actionDescription },
|
system: config,
|
||||||
rolls: [roll]
|
rolls: [roll]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs';
|
import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs';
|
||||||
import D20Roll from './d20Roll.mjs';
|
import D20Roll from './d20Roll.mjs';
|
||||||
import { parseRallyDice, setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs';
|
import { setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs';
|
||||||
import { getDiceSoNicePresets } from '../config/generalConfig.mjs';
|
import { getDiceSoNicePresets } from '../config/generalConfig.mjs';
|
||||||
import { ResourceUpdateMap } from '../data/action/baseAction.mjs';
|
import { ResourceUpdateMap } from '../data/action/baseAction.mjs';
|
||||||
|
|
||||||
|
|
@ -68,7 +68,7 @@ export default class DualityRoll extends D20Roll {
|
||||||
setRallyChoices() {
|
setRallyChoices() {
|
||||||
return this.data?.parent?.appliedEffects.reduce((a, c) => {
|
return this.data?.parent?.appliedEffects.reduce((a, c) => {
|
||||||
const change = c.changes.find(ch => ch.key === 'system.bonuses.rally');
|
const change = c.changes.find(ch => ch.key === 'system.bonuses.rally');
|
||||||
if (change) a.push({ value: c.id, label: parseRallyDice(change.value, c) });
|
if (change) a.push({ value: c.id, label: change.value });
|
||||||
return a;
|
return a;
|
||||||
}, []);
|
}, []);
|
||||||
}
|
}
|
||||||
|
|
@ -409,9 +409,7 @@ export default class DualityRoll extends D20Roll {
|
||||||
difficulty: message.system.roll.difficulty ? Number(message.system.roll.difficulty) : null
|
difficulty: message.system.roll.difficulty ? Number(message.system.roll.difficulty) : null
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
newRoll.extra = newRoll.extra.slice(2);
|
||||||
const extraIndex = newRoll.advantage ? 3 : 2;
|
|
||||||
newRoll.extra = newRoll.extra.slice(extraIndex);
|
|
||||||
|
|
||||||
const tagTeamSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
|
const tagTeamSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,15 +61,14 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
|
||||||
update.img = 'icons/magic/life/heart-cross-blue.webp';
|
update.img = 'icons/magic/life/heart-cross-blue.webp';
|
||||||
}
|
}
|
||||||
|
|
||||||
const statuses = Object.keys(data.statuses ?? {});
|
|
||||||
const immuneStatuses =
|
const immuneStatuses =
|
||||||
statuses.filter(
|
data.statuses?.filter(
|
||||||
status =>
|
status =>
|
||||||
this.parent.system.rules?.conditionImmunities &&
|
this.parent.system.rules?.conditionImmunities &&
|
||||||
this.parent.system.rules.conditionImmunities[status]
|
this.parent.system.rules.conditionImmunities[status]
|
||||||
) ?? [];
|
) ?? [];
|
||||||
if (immuneStatuses.length > 0) {
|
if (immuneStatuses.length > 0) {
|
||||||
update.statuses = statuses.filter(x => !immuneStatuses.includes(x));
|
update.statuses = data.statuses.filter(x => !immuneStatuses.includes(x));
|
||||||
const conditions = CONFIG.DH.GENERAL.conditions();
|
const conditions = CONFIG.DH.GENERAL.conditions();
|
||||||
const scrollingTexts = immuneStatuses.map(status => ({
|
const scrollingTexts = immuneStatuses.map(status => ({
|
||||||
text: game.i18n.format('DAGGERHEART.ACTIVEEFFECT.immuneStatusText', {
|
text: game.i18n.format('DAGGERHEART.ACTIVEEFFECT.immuneStatusText', {
|
||||||
|
|
@ -114,11 +113,6 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
|
||||||
super.applyField(model, change, field);
|
super.applyField(model, change, field);
|
||||||
}
|
}
|
||||||
|
|
||||||
_applyLegacy(actor, change, changes) {
|
|
||||||
change.value = DhActiveEffect.getChangeValue(actor, change, change.effect);
|
|
||||||
super._applyLegacy(actor, change, changes);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** */
|
/** */
|
||||||
static getChangeValue(model, change, effect) {
|
static getChangeValue(model, change, effect) {
|
||||||
let value = change.value;
|
let value = change.value;
|
||||||
|
|
|
||||||
|
|
@ -934,23 +934,10 @@ export default class DhpActor extends Actor {
|
||||||
|
|
||||||
/** Get active effects */
|
/** Get active effects */
|
||||||
getActiveEffects() {
|
getActiveEffects() {
|
||||||
const conditions = CONFIG.DH.GENERAL.conditions();
|
|
||||||
const statusMap = new Map(foundry.CONFIG.statusEffects.map(status => [status.id, status]));
|
const statusMap = new Map(foundry.CONFIG.statusEffects.map(status => [status.id, status]));
|
||||||
const autoVulnerableActive = this.system.isAutoVulnerableActive;
|
|
||||||
return this.effects
|
return this.effects
|
||||||
.filter(x => !x.disabled)
|
.filter(x => !x.disabled)
|
||||||
.reduce((acc, effect) => {
|
.reduce((acc, effect) => {
|
||||||
/* Could be generalized if needed. Currently just related to Vulnerable */
|
|
||||||
const isAutoVulnerableEffect =
|
|
||||||
effect.flags.daggerheart?.autoApplyFlagId === conditions.vulnerable.autoApplyFlagId;
|
|
||||||
if (isAutoVulnerableEffect) {
|
|
||||||
if (!autoVulnerableActive) return acc;
|
|
||||||
|
|
||||||
effect.appliedBy = game.i18n.localize('DAGGERHEART.CONFIG.Condition.vulnerable.autoAppliedByLabel');
|
|
||||||
effect.isLockedCondition = true;
|
|
||||||
effect.condition = 'vulnerable';
|
|
||||||
}
|
|
||||||
|
|
||||||
acc.push(effect);
|
acc.push(effect);
|
||||||
|
|
||||||
const currentStatusActiveEffects = acc.filter(
|
const currentStatusActiveEffects = acc.filter(
|
||||||
|
|
|
||||||
|
|
@ -110,8 +110,6 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
||||||
} else if (s.classList.contains('damage-section'))
|
} else if (s.classList.contains('damage-section'))
|
||||||
s.classList.toggle('expanded', autoExpandRoll.damage);
|
s.classList.toggle('expanded', autoExpandRoll.damage);
|
||||||
else if (s.classList.contains('target-section')) s.classList.toggle('expanded', autoExpandRoll.target);
|
else if (s.classList.contains('target-section')) s.classList.toggle('expanded', autoExpandRoll.target);
|
||||||
else if (s.classList.contains('description-section'))
|
|
||||||
s.classList.toggle('expanded', autoExpandRoll.desc);
|
|
||||||
});
|
});
|
||||||
if (itemDesc && autoExpandRoll.desc) itemDesc.setAttribute('open', '');
|
if (itemDesc && autoExpandRoll.desc) itemDesc.setAttribute('open', '');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -185,10 +185,7 @@ export default class DHItem extends foundry.documents.Item {
|
||||||
tags: this._getTags()
|
tags: this._getTags()
|
||||||
},
|
},
|
||||||
actions: item.system.actionsList,
|
actions: item.system.actionsList,
|
||||||
description: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.system.description, {
|
description: this.system.description
|
||||||
relativeTo: this.parent,
|
|
||||||
rollData: this.parent?.getRollData() ?? {}
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const msg = {
|
const msg = {
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,78 @@
|
||||||
export default class DHToken extends CONFIG.Token.documentClass {
|
export default class DHToken extends CONFIG.Token.documentClass {
|
||||||
/**@inheritdoc */
|
/**
|
||||||
static getTrackedAttributeChoices(attributes, typeKey) {
|
* Inspect the Actor data model and identify the set of attributes which could be used for a Token Bar.
|
||||||
|
* @param {object} attributes The tracked attributes which can be chosen from
|
||||||
|
* @returns {object} A nested object of attribute choices to display
|
||||||
|
*/
|
||||||
|
static getTrackedAttributeChoices(attributes, model) {
|
||||||
attributes = attributes || this.getTrackedAttributes();
|
attributes = attributes || this.getTrackedAttributes();
|
||||||
const barGroup = game.i18n.localize('TOKEN.BarAttributes');
|
const barGroup = game.i18n.localize('TOKEN.BarAttributes');
|
||||||
const valueGroup = game.i18n.localize('TOKEN.BarValues');
|
const valueGroup = game.i18n.localize('TOKEN.BarValues');
|
||||||
const actorModel = typeKey ? game.system.api.data.actors[`Dh${typeKey.capitalize()}`] : null;
|
|
||||||
const getLabel = path => {
|
|
||||||
const label = actorModel?.schema.getField(path)?.label;
|
|
||||||
return label ? game.i18n.localize(label) : path;
|
|
||||||
};
|
|
||||||
|
|
||||||
const bars = attributes.bar.map(v => {
|
const bars = attributes.bar.map(v => {
|
||||||
const a = v.join('.');
|
const a = v.join('.');
|
||||||
return { group: barGroup, value: a, label: getLabel(a) };
|
const modelLabel = model ? game.i18n.localize(model.schema.getField(`${a}.value`).label) : null;
|
||||||
|
return { group: barGroup, value: a, label: modelLabel ? modelLabel : a };
|
||||||
});
|
});
|
||||||
bars.sort((a, b) => a.value.compare(b.value));
|
bars.sort((a, b) => a.label.compare(b.label));
|
||||||
|
|
||||||
const values = attributes.value.map(v => {
|
const invalidAttributes = [
|
||||||
|
'gold',
|
||||||
|
'levelData',
|
||||||
|
'actions',
|
||||||
|
'biography',
|
||||||
|
'class',
|
||||||
|
'multiclass',
|
||||||
|
'companion',
|
||||||
|
'notes',
|
||||||
|
'partner',
|
||||||
|
'description',
|
||||||
|
'impulses',
|
||||||
|
'tier',
|
||||||
|
'type'
|
||||||
|
];
|
||||||
|
const values = attributes.value.reduce((acc, v) => {
|
||||||
const a = v.join('.');
|
const a = v.join('.');
|
||||||
return { group: valueGroup, value: a, label: getLabel(a) };
|
if (invalidAttributes.some(x => a.startsWith(x))) return acc;
|
||||||
});
|
|
||||||
|
const field = model ? model.schema.getField(a) : null;
|
||||||
|
const modelLabel = field ? game.i18n.localize(field.label) : null;
|
||||||
|
const hint = field ? game.i18n.localize(field.hint) : null;
|
||||||
|
acc.push({ group: valueGroup, value: a, label: modelLabel ? modelLabel : a, hint: hint });
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
values.sort((a, b) => a.label.compare(b.label));
|
||||||
|
|
||||||
values.sort((a, b) => a.value.compare(b.value));
|
|
||||||
return bars.concat(values);
|
return bars.concat(values);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static _getTrackedAttributesFromSchema(schema, _path = []) {
|
||||||
|
const attributes = { bar: [], value: [] };
|
||||||
|
for (const [name, field] of Object.entries(schema.fields)) {
|
||||||
|
const p = _path.concat([name]);
|
||||||
|
if (field instanceof foundry.data.fields.NumberField) attributes.value.push(p);
|
||||||
|
if (field instanceof foundry.data.fields.BooleanField && field.options.isAttributeChoice)
|
||||||
|
attributes.value.push(p);
|
||||||
|
if (field instanceof foundry.data.fields.StringField) attributes.value.push(p);
|
||||||
|
if (field instanceof foundry.data.fields.ArrayField) attributes.value.push(p);
|
||||||
|
const isSchema = field instanceof foundry.data.fields.SchemaField;
|
||||||
|
const isModel = field instanceof foundry.data.fields.EmbeddedDataField;
|
||||||
|
|
||||||
|
if (isSchema || isModel) {
|
||||||
|
const schema = isModel ? field.model.schema : field;
|
||||||
|
const isBar = schema.has && schema.has('value') && schema.has('max');
|
||||||
|
if (isBar) attributes.bar.push(p);
|
||||||
|
else {
|
||||||
|
const inner = this.getTrackedAttributes(schema, p);
|
||||||
|
attributes.bar.push(...inner.bar);
|
||||||
|
attributes.value.push(...inner.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
|
|
||||||
_shouldRecordMovementHistory() {
|
_shouldRecordMovementHistory() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -221,7 +269,7 @@ export default class DHToken extends CONFIG.Token.documentClass {
|
||||||
|
|
||||||
// Hexagon symmetry
|
// Hexagon symmetry
|
||||||
if (columns) {
|
if (columns) {
|
||||||
const rowData = DHToken.#getHexagonalShape(height, width, shape, false);
|
const rowData = BaseToken.#getHexagonalShape(height, width, shape, false);
|
||||||
if (!rowData) return null;
|
if (!rowData) return null;
|
||||||
|
|
||||||
// Transpose the offsets/points of the shape in row orientation
|
// Transpose the offsets/points of the shape in row orientation
|
||||||
|
|
|
||||||
|
|
@ -86,22 +86,19 @@ export const enrichedDualityRoll = async (
|
||||||
{ reaction, traitValue, target, difficulty, title, label, advantage, grantResources, customConfig },
|
{ reaction, traitValue, target, difficulty, title, label, advantage, grantResources, customConfig },
|
||||||
event
|
event
|
||||||
) => {
|
) => {
|
||||||
const shouldGrantResources = grantResources === undefined ? true : grantResources;
|
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
event: event ?? {},
|
event: event ?? {},
|
||||||
title: title,
|
title: title,
|
||||||
headerTitle: label,
|
headerTitle: label,
|
||||||
actionType: reaction ? 'reaction' : null,
|
|
||||||
roll: {
|
roll: {
|
||||||
trait: traitValue && target ? traitValue : null,
|
trait: traitValue && target ? traitValue : null,
|
||||||
difficulty: difficulty,
|
difficulty: difficulty,
|
||||||
advantage
|
advantage,
|
||||||
// type: reaction ? 'reaction' : null //not needed really but keeping it for troubleshooting
|
type: reaction ? 'reaction' : null
|
||||||
},
|
},
|
||||||
skips: {
|
skips: {
|
||||||
resources: !shouldGrantResources,
|
resources: !grantResources,
|
||||||
triggers: !shouldGrantResources
|
triggers: !grantResources
|
||||||
},
|
},
|
||||||
type: 'trait',
|
type: 'trait',
|
||||||
hasRoll: true,
|
hasRoll: true,
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ export default function DhTemplateEnricher(match, _options) {
|
||||||
const params = parseInlineParams(match[1]);
|
const params = parseInlineParams(match[1]);
|
||||||
const { type, angle = CONFIG.MeasuredTemplate.defaults.angle, inline = false } = params;
|
const { type, angle = CONFIG.MeasuredTemplate.defaults.angle, inline = false } = params;
|
||||||
const direction = Number(params.direction) || 0;
|
const direction = Number(params.direction) || 0;
|
||||||
params.range = params.range?.toLowerCase();
|
|
||||||
const range =
|
const range =
|
||||||
params.range && Number.isNaN(Number(params.range))
|
params.range && Number.isNaN(Number(params.range))
|
||||||
? Object.values(CONFIG.DH.GENERAL.templateRanges).find(
|
? Object.values(CONFIG.DH.GENERAL.templateRanges).find(
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ export function rollCommandToJSON(text) {
|
||||||
const flavor = flavorMatch ? flavorMatch[1] : null;
|
const flavor = flavorMatch ? flavorMatch[1] : null;
|
||||||
|
|
||||||
// Match key="quoted string" OR key=unquotedValue
|
// Match key="quoted string" OR key=unquotedValue
|
||||||
const PAIR_RE = /(\w+)\s*=\s*("(?:[^"\\]|\\.)*"|[^\]\}\s]+)/g; //updated regex to allow escaped quotes in quoted strings and avoid matching closing brackets/braces
|
const PAIR_RE = /(\w+)=("(?:[^"\\]|\\.)*"|\S+)/g;
|
||||||
const result = {};
|
const result = {};
|
||||||
for (const [, key, raw] of text.matchAll(PAIR_RE)) {
|
for (const [, key, raw] of text.matchAll(PAIR_RE)) {
|
||||||
let value;
|
let value;
|
||||||
|
|
@ -119,8 +119,8 @@ export const tagifyElement = (element, baseOptions, onChange, tagifyOptions = {}
|
||||||
}),
|
}),
|
||||||
maxTags: typeof maxTags === 'function' ? maxTags() : maxTags,
|
maxTags: typeof maxTags === 'function' ? maxTags() : maxTags,
|
||||||
dropdown: {
|
dropdown: {
|
||||||
searchKeys: ['value', 'name'],
|
|
||||||
mapValueTo: 'name',
|
mapValueTo: 'name',
|
||||||
|
searchKeys: ['value'],
|
||||||
enabled: 0,
|
enabled: 0,
|
||||||
maxItems: 100,
|
maxItems: 100,
|
||||||
closeOnSelect: true,
|
closeOnSelect: true,
|
||||||
|
|
@ -472,7 +472,7 @@ export function refreshIsAllowed(allowedTypes, typeToCheck) {
|
||||||
case CONFIG.DH.GENERAL.refreshTypes.scene.id:
|
case CONFIG.DH.GENERAL.refreshTypes.scene.id:
|
||||||
case CONFIG.DH.GENERAL.refreshTypes.session.id:
|
case CONFIG.DH.GENERAL.refreshTypes.session.id:
|
||||||
case CONFIG.DH.GENERAL.refreshTypes.longRest.id:
|
case CONFIG.DH.GENERAL.refreshTypes.longRest.id:
|
||||||
return allowedTypes.includes?.(typeToCheck) ?? allowedTypes.has(typeToCheck);
|
return allowedTypes.includes(typeToCheck);
|
||||||
case CONFIG.DH.GENERAL.refreshTypes.shortRest.id:
|
case CONFIG.DH.GENERAL.refreshTypes.shortRest.id:
|
||||||
return allowedTypes.some(
|
return allowedTypes.some(
|
||||||
x =>
|
x =>
|
||||||
|
|
@ -495,183 +495,3 @@ export function htmlToText(html) {
|
||||||
|
|
||||||
return tempDivElement.textContent || tempDivElement.innerText || '';
|
return tempDivElement.textContent || tempDivElement.innerText || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getFeaturesHTMLData(features) {
|
|
||||||
const result = [];
|
|
||||||
for (const feature of features) {
|
|
||||||
if (feature) {
|
|
||||||
const base = feature.item ?? feature;
|
|
||||||
const item = base.system ? base : await foundry.utils.fromUuid(base.uuid);
|
|
||||||
const itemDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
|
|
||||||
item.system.description
|
|
||||||
);
|
|
||||||
result.push({ label: item.name, description: itemDescription });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given a simple flavor-less formula with only +/- operators, returns a list of damage partial terms.
|
|
||||||
* All subtracted terms become negative terms.
|
|
||||||
* If there are no dice, it returns 0d1 for that term.
|
|
||||||
*/
|
|
||||||
export function parseTermsFromSimpleFormula(formula) {
|
|
||||||
const roll = formula instanceof Roll ? formula : new Roll(formula);
|
|
||||||
|
|
||||||
// Parse from right to left so that when we hit an operator, we already have the term.
|
|
||||||
return roll.terms.reduceRight((result, term) => {
|
|
||||||
// Ignore + terms, we assume + by default
|
|
||||||
if (term.expression === ' + ') return result;
|
|
||||||
|
|
||||||
// - terms modify the last term we parsed
|
|
||||||
if (term.expression === ' - ') {
|
|
||||||
const termToModify = result[0];
|
|
||||||
if (termToModify) {
|
|
||||||
if (termToModify.bonus) termToModify.bonus *= -1;
|
|
||||||
if (termToModify.dice) termToModify.dice *= -1;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
result.unshift({
|
|
||||||
bonus: term instanceof foundry.dice.terms.NumericTerm ? term.number : 0,
|
|
||||||
diceQuantity: term instanceof foundry.dice.terms.Die ? term.number : 0,
|
|
||||||
faces: term.faces ?? 1
|
|
||||||
});
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}, []);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the expectede value from a formula or the results of parseTermsFromSimpleFormula.
|
|
||||||
* @returns {number} the average result of rolling the given dice
|
|
||||||
*/
|
|
||||||
export function calculateExpectedValue(formulaOrTerms) {
|
|
||||||
const terms = Array.isArray(formulaOrTerms)
|
|
||||||
? formulaOrTerms
|
|
||||||
: typeof formulaOrTerms === 'string'
|
|
||||||
? parseTermsFromSimpleFormula(formulaOrTerms)
|
|
||||||
: [formulaOrTerms];
|
|
||||||
return terms.reduce((r, t) => r + (t.bonus ?? 0) + (t.diceQuantity ? (t.diceQuantity * (t.faces + 1)) / 2 : 0), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parseRallyDice(value, effect) {
|
|
||||||
const legacyStartsWithPrefix = value.toLowerCase().startsWith('d');
|
|
||||||
const workingValue = legacyStartsWithPrefix ? value.slice(1) : value;
|
|
||||||
const dataParsedValue = itemAbleRollParse(workingValue, effect.parent);
|
|
||||||
|
|
||||||
return `d${game.system.api.documents.DhActiveEffect.effectSafeEval(dataParsedValue)}`;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Refreshes character and/or adversary resources.
|
|
||||||
* @param { string[] } refreshTypes Which type of features to refresh using IDs from CONFIG.DH.GENERAL.refreshTypes
|
|
||||||
* @param { string[] = ['character', 'adversary'] } actorTypes Which actor types should refresh their features. Defaults to character and adversary.
|
|
||||||
* @param { boolean = true } sendRefreshMessage If a chat message should be created detailing the refresh
|
|
||||||
* @return { Actor[] } The actors that had their features refreshed
|
|
||||||
*/
|
|
||||||
export async function RefreshFeatures(
|
|
||||||
refreshTypes = [],
|
|
||||||
actorTypes = ['character', 'adversary'],
|
|
||||||
sendNotificationMessage = true,
|
|
||||||
sendRefreshMessage = true
|
|
||||||
) {
|
|
||||||
const refreshedActors = {};
|
|
||||||
for (let actor of game.actors) {
|
|
||||||
if (actorTypes.includes(actor.type) && actor.prototypeToken.actorLink) {
|
|
||||||
const updates = {};
|
|
||||||
for (let item of actor.items) {
|
|
||||||
if (
|
|
||||||
item.system.metadata?.hasResource &&
|
|
||||||
refreshIsAllowed(refreshTypes, item.system.resource?.recovery)
|
|
||||||
) {
|
|
||||||
if (!refreshedActors[actor.id])
|
|
||||||
refreshedActors[actor.id] = { name: actor.name, img: actor.img, refreshed: new Set() };
|
|
||||||
refreshedActors[actor.id].refreshed.add(
|
|
||||||
game.i18n.localize(CONFIG.DH.GENERAL.refreshTypes[item.system.resource.recovery].label)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!updates[item.id]?.system) updates[item.id] = { system: {} };
|
|
||||||
|
|
||||||
const increasing =
|
|
||||||
item.system.resource.progression === CONFIG.DH.ITEM.itemResourceProgression.increasing.id;
|
|
||||||
updates[item.id].system = {
|
|
||||||
...updates[item.id].system,
|
|
||||||
'resource.value': increasing
|
|
||||||
? 0
|
|
||||||
: game.system.api.documents.DhActiveEffect.effectSafeEval(
|
|
||||||
Roll.replaceFormulaData(item.system.resource.max, actor.getRollData())
|
|
||||||
)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (item.system.metadata?.hasActions) {
|
|
||||||
const usedTypes = new Set();
|
|
||||||
const actions = item.system.actions.filter(action => {
|
|
||||||
if (refreshIsAllowed(refreshTypes, action.uses.recovery)) {
|
|
||||||
usedTypes.add(action.uses.recovery);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
if (actions.length === 0) continue;
|
|
||||||
|
|
||||||
if (!refreshedActors[actor.id])
|
|
||||||
refreshedActors[actor.id] = { name: actor.name, img: actor.img, refreshed: new Set() };
|
|
||||||
refreshedActors[actor.id].refreshed.add(
|
|
||||||
...usedTypes.map(type => game.i18n.localize(CONFIG.DH.GENERAL.refreshTypes[type].label))
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!updates[item.id]?.system) updates[item.id] = { system: {} };
|
|
||||||
|
|
||||||
updates[item.id].system = {
|
|
||||||
...updates[item.id].system,
|
|
||||||
...actions.reduce(
|
|
||||||
(acc, action) => {
|
|
||||||
acc.actions[action.id] = { 'uses.value': 0 };
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
{ actions: updates[item.id].system.actions ?? {} }
|
|
||||||
)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let key in updates) {
|
|
||||||
const update = updates[key];
|
|
||||||
await actor.items.get(key).update(update);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const types = refreshTypes.map(x => game.i18n.localize(CONFIG.DH.GENERAL.refreshTypes[x].label)).join(', ');
|
|
||||||
|
|
||||||
if (sendNotificationMessage) {
|
|
||||||
ui.notifications.info(
|
|
||||||
game.i18n.format('DAGGERHEART.UI.Notifications.gmMenuRefresh', {
|
|
||||||
types: `[${types}]`
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sendRefreshMessage) {
|
|
||||||
const cls = getDocumentClass('ChatMessage');
|
|
||||||
const msg = {
|
|
||||||
user: game.user.id,
|
|
||||||
content: await foundry.applications.handlebars.renderTemplate(
|
|
||||||
'systems/daggerheart/templates/ui/chat/refreshMessage.hbs',
|
|
||||||
{
|
|
||||||
types: types
|
|
||||||
}
|
|
||||||
),
|
|
||||||
title: game.i18n.localize('DAGGERHEART.UI.Chat.refreshMessage.title'),
|
|
||||||
speaker: cls.getSpeaker()
|
|
||||||
};
|
|
||||||
|
|
||||||
cls.create(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
return refreshedActors;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,6 @@ export const preloadHandlebarsTemplates = async function () {
|
||||||
'systems/daggerheart/templates/dialogs/downtime/activities.hbs',
|
'systems/daggerheart/templates/dialogs/downtime/activities.hbs',
|
||||||
'systems/daggerheart/templates/dialogs/dice-roll/costSelection.hbs',
|
'systems/daggerheart/templates/dialogs/dice-roll/costSelection.hbs',
|
||||||
'systems/daggerheart/templates/ui/chat/parts/roll-part.hbs',
|
'systems/daggerheart/templates/ui/chat/parts/roll-part.hbs',
|
||||||
'systems/daggerheart/templates/ui/chat/parts/description-part.hbs',
|
|
||||||
'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',
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import {
|
||||||
DhHomebrewSettings,
|
DhHomebrewSettings,
|
||||||
DhVariantRuleSettings
|
DhVariantRuleSettings
|
||||||
} from '../applications/settings/_module.mjs';
|
} from '../applications/settings/_module.mjs';
|
||||||
import { CompendiumBrowserSettings, DhTagTeamRoll } from '../data/_module.mjs';
|
import { DhTagTeamRoll } from '../data/_module.mjs';
|
||||||
|
|
||||||
export const registerDHSettings = () => {
|
export const registerDHSettings = () => {
|
||||||
registerMenuSettings();
|
registerMenuSettings();
|
||||||
|
|
@ -126,7 +126,7 @@ const registerNonConfigSettings = () => {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 0,
|
default: 0,
|
||||||
onChange: () => {
|
onChange: () => {
|
||||||
if (ui.resources) ui.resources.render();
|
if (ui.resources) ui.resources.render({ force: true });
|
||||||
ui.combat.render({ force: true });
|
ui.combat.render({ force: true });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -142,12 +142,6 @@ const registerNonConfigSettings = () => {
|
||||||
config: false,
|
config: false,
|
||||||
type: DhTagTeamRoll
|
type: DhTagTeamRoll
|
||||||
});
|
});
|
||||||
|
|
||||||
game.settings.register(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.CompendiumBrowserSettings, {
|
|
||||||
scope: 'client',
|
|
||||||
config: false,
|
|
||||||
type: CompendiumBrowserSettings
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -38,8 +38,7 @@ export const RefreshType = {
|
||||||
Countdown: 'DhCoundownRefresh',
|
Countdown: 'DhCoundownRefresh',
|
||||||
TagTeamRoll: 'DhTagTeamRollRefresh',
|
TagTeamRoll: 'DhTagTeamRollRefresh',
|
||||||
EffectsDisplay: 'DhEffectsDisplayRefresh',
|
EffectsDisplay: 'DhEffectsDisplayRefresh',
|
||||||
Scene: 'DhSceneRefresh',
|
Scene: 'DhSceneRefresh'
|
||||||
CompendiumBrowser: 'DhCompendiumBrowserRefresh'
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const registerSocketHooks = () => {
|
export const registerSocketHooks = () => {
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@
|
||||||
"enabled": false
|
"enabled": false
|
||||||
},
|
},
|
||||||
"flatMultiplier": 3,
|
"flatMultiplier": 3,
|
||||||
"dice": "d20",
|
"dice": "d10",
|
||||||
"bonus": null,
|
"bonus": null,
|
||||||
"multiplier": "flat"
|
"multiplier": "flat"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -246,7 +246,7 @@
|
||||||
"name": "Group Attack",
|
"name": "Group Attack",
|
||||||
"type": "feature",
|
"type": "feature",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.</p>",
|
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal 4 physical damage each. Combine this damage.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"vgguNWz8vG8aoLXR": {
|
"vgguNWz8vG8aoLXR": {
|
||||||
|
|
|
||||||
|
|
@ -218,10 +218,10 @@
|
||||||
},
|
},
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"name": "Horde",
|
"name": "Horde (1d6+3)",
|
||||||
"type": "feature",
|
"type": "feature",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p>When the @Lookup[@name] has marked half or more of their HP, their standard attack deals <strong>@Lookup[@system.attack.altDamageFormula]</strong> physical damage instead.</p>",
|
"description": "<p>When the @Lookup[@name] has marked half or more of their HP, their standard attack deals <strong>1d6+3</strong> physical damage instead.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {},
|
"actions": {},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
|
|
|
||||||
|
|
@ -239,7 +239,7 @@
|
||||||
"name": "Group Attack",
|
"name": "Group Attack",
|
||||||
"type": "feature",
|
"type": "feature",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.</p>",
|
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal 6 physical damage each. Combine this damage.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"cbAvPSIhwBMBTI3D": {
|
"cbAvPSIhwBMBTI3D": {
|
||||||
|
|
|
||||||
|
|
@ -239,7 +239,7 @@
|
||||||
"name": "Group Attack",
|
"name": "Group Attack",
|
||||||
"type": "feature",
|
"type": "feature",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all Cult @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.</p>",
|
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all Cult @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal 5 physical damage each. Combine this damage.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"EH1preaTWBD4rOvx": {
|
"EH1preaTWBD4rOvx": {
|
||||||
|
|
|
||||||
|
|
@ -224,10 +224,10 @@
|
||||||
},
|
},
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"name": "Horde",
|
"name": "Horde (2d4+1)",
|
||||||
"type": "feature",
|
"type": "feature",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p>When the @Lookup[@name] has marked half or more of their HP, their standard attack deals <strong>@Lookup[@system.attack.altDamageFormula]</strong> physical damage instead.</p>",
|
"description": "<p>When the @Lookup[@name] has marked half or more of their HP, their standard attack deals <strong>2d4+1</strong> physical damage instead.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {},
|
"actions": {},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
|
|
|
||||||
|
|
@ -218,10 +218,10 @@
|
||||||
},
|
},
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"name": "Horde",
|
"name": "Horde (2d4+1)",
|
||||||
"type": "feature",
|
"type": "feature",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p>When the @Lookup[@name] have marked half or more of their HP, their standard attack deals <strong>@Lookup[@system.attack.altDamageFormula]</strong> physical damage instead.</p>",
|
"description": "<p>When the @Lookup[@name] have marked half or more of their HP, their standard attack deals <strong>2d4+1</strong> physical damage instead.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {},
|
"actions": {},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
|
|
|
||||||
|
|
@ -239,7 +239,7 @@
|
||||||
"name": "Group Attack",
|
"name": "Group Attack",
|
||||||
"type": "feature",
|
"type": "feature",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.</p>",
|
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal 5 physical damage each. Combine this damage.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"vXHZVb0Y7Hqu3uso": {
|
"vXHZVb0Y7Hqu3uso": {
|
||||||
|
|
|
||||||
|
|
@ -317,7 +317,7 @@
|
||||||
"name": "Group Attack",
|
"name": "Group Attack",
|
||||||
"type": "feature",
|
"type": "feature",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.</p>",
|
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal 12 physical damage each. Combine this damage.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"QHNRSEQmqOcaoXq4": {
|
"QHNRSEQmqOcaoXq4": {
|
||||||
|
|
|
||||||
|
|
@ -229,7 +229,7 @@
|
||||||
"_id": "9RduwBLYcBaiouYk",
|
"_id": "9RduwBLYcBaiouYk",
|
||||||
"img": "icons/creatures/magical/humanoid-silhouette-aliens-green.webp",
|
"img": "icons/creatures/magical/humanoid-silhouette-aliens-green.webp",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p>When the @Lookup[@name] have marked half or more of their HP, their standard attack deals <strong>@Lookup[@system.attack.altDamageFormula]</strong> physical damage instead.</p>",
|
"description": "<p>When the @Lookup[@name] have marked half or more of their HP, their standard attack deals <strong>1d4+1</strong> physical damage instead.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {},
|
"actions": {},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
|
|
|
||||||
|
|
@ -248,7 +248,7 @@
|
||||||
"_id": "fsaBlCjTdq1jM23G",
|
"_id": "fsaBlCjTdq1jM23G",
|
||||||
"img": "icons/creatures/abilities/tail-strike-bone-orange.webp",
|
"img": "icons/creatures/abilities/tail-strike-bone-orange.webp",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.</p>",
|
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal 1 physical damage each. Combine this damage.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"q8chow47nQLR9qeF": {
|
"q8chow47nQLR9qeF": {
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@
|
||||||
"max": 1
|
"max": 1
|
||||||
},
|
},
|
||||||
"stress": {
|
"stress": {
|
||||||
"max": 2
|
"max": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"attack": {
|
"attack": {
|
||||||
|
|
@ -239,7 +239,7 @@
|
||||||
"name": "Group Attack",
|
"name": "Group Attack",
|
||||||
"type": "feature",
|
"type": "feature",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.</p>",
|
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal 5 physical damage each. Combine this damage.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"DjbPQowW1OdBD9Zn": {
|
"DjbPQowW1OdBD9Zn": {
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@
|
||||||
"max": 1
|
"max": 1
|
||||||
},
|
},
|
||||||
"stress": {
|
"stress": {
|
||||||
"max": 2
|
"max": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"attack": {
|
"attack": {
|
||||||
|
|
@ -294,7 +294,7 @@
|
||||||
"name": "Group Attack",
|
"name": "Group Attack",
|
||||||
"type": "feature",
|
"type": "feature",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.</p>",
|
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal 10 physical damage each. Combine this damage.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"eo7J0v1B5zPHul1M": {
|
"eo7J0v1B5zPHul1M": {
|
||||||
|
|
|
||||||
|
|
@ -248,7 +248,7 @@
|
||||||
"_id": "1k5TmQIAunM7Bv32",
|
"_id": "1k5TmQIAunM7Bv32",
|
||||||
"img": "icons/creatures/abilities/tail-strike-bone-orange.webp",
|
"img": "icons/creatures/abilities/tail-strike-bone-orange.webp",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all @Lookup[@name] within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.</p>",
|
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all @Lookup[@name] within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal 2 physical damage each. Combine this damage.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"aoQDb2m32NDxE6ZP": {
|
"aoQDb2m32NDxE6ZP": {
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@
|
||||||
"reduction": 0
|
"reduction": 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": "ranged",
|
"type": "standard",
|
||||||
"notes": "",
|
"notes": "",
|
||||||
"hordeHp": 1,
|
"hordeHp": 1,
|
||||||
"experiences": {
|
"experiences": {
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,6 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"type": "attack",
|
"type": "attack",
|
||||||
"range": "melee",
|
|
||||||
"chatDisplay": false
|
"chatDisplay": false
|
||||||
},
|
},
|
||||||
"attribution": {
|
"attribution": {
|
||||||
|
|
|
||||||
|
|
@ -242,7 +242,7 @@
|
||||||
"_id": "K08WlZwGqzEo4idT",
|
"_id": "K08WlZwGqzEo4idT",
|
||||||
"img": "icons/creatures/abilities/tail-strike-bone-orange.webp",
|
"img": "icons/creatures/abilities/tail-strike-bone-orange.webp",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.</p>",
|
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal 4 physical damage each. Combine this damage.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"xTMNAHcoErKuR6TZ": {
|
"xTMNAHcoErKuR6TZ": {
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@
|
||||||
"reduction": 0
|
"reduction": 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": "bruiser",
|
"type": "standard",
|
||||||
"notes": "",
|
"notes": "",
|
||||||
"hordeHp": 1,
|
"hordeHp": 1,
|
||||||
"experiences": {},
|
"experiences": {},
|
||||||
|
|
@ -66,12 +66,12 @@
|
||||||
"tier": 3,
|
"tier": 3,
|
||||||
"description": "<p>A sturdy animate old-growth tree.</p>",
|
"description": "<p>A sturdy animate old-growth tree.</p>",
|
||||||
"attack": {
|
"attack": {
|
||||||
"name": "Branch",
|
"name": "Attack",
|
||||||
"roll": {
|
"roll": {
|
||||||
"type": "attack",
|
"type": "attack",
|
||||||
"bonus": 2
|
"bonus": 2
|
||||||
},
|
},
|
||||||
"range": "veryClose",
|
"range": "close",
|
||||||
"damage": {
|
"damage": {
|
||||||
"parts": [
|
"parts": [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@
|
||||||
},
|
},
|
||||||
"img": "icons/creatures/claws/claw-talons-yellow-red.webp",
|
"img": "icons/creatures/claws/claw-talons-yellow-red.webp",
|
||||||
"type": "attack",
|
"type": "attack",
|
||||||
"range": "veryClose",
|
"range": "melee",
|
||||||
"chatDisplay": false
|
"chatDisplay": false
|
||||||
},
|
},
|
||||||
"attribution": {
|
"attribution": {
|
||||||
|
|
@ -239,7 +239,7 @@
|
||||||
"name": "Group Attack",
|
"name": "Group Attack",
|
||||||
"type": "feature",
|
"type": "feature",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.</p>",
|
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal 11 physical damage each. Combine this damage.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"tvQetauskZoHDR5y": {
|
"tvQetauskZoHDR5y": {
|
||||||
|
|
|
||||||
|
|
@ -229,7 +229,7 @@
|
||||||
"_id": "Q7DRbWjHl64CNwag",
|
"_id": "Q7DRbWjHl64CNwag",
|
||||||
"img": "icons/creatures/magical/humanoid-silhouette-aliens-green.webp",
|
"img": "icons/creatures/magical/humanoid-silhouette-aliens-green.webp",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p>When the @Lookup[@name] have marked half or more of their HP, their standard attack deals <strong>@Lookup[@system.attack.altDamageFormula]</strong> physical damage instead.</p>",
|
"description": "<p>When the @Lookup[@name] have marked half or more of their HP, their standard attack deals <strong>1d4+1</strong> physical damage instead.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {},
|
"actions": {},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
|
|
|
||||||
|
|
@ -242,7 +242,7 @@
|
||||||
"_id": "R9vrwFNl5BD1YXJo",
|
"_id": "R9vrwFNl5BD1YXJo",
|
||||||
"img": "icons/creatures/abilities/tail-strike-bone-orange.webp",
|
"img": "icons/creatures/abilities/tail-strike-bone-orange.webp",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.</p>",
|
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal 2 physical damage each. Combine this damage.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"DJBNtd3hWjwsjPwq": {
|
"DJBNtd3hWjwsjPwq": {
|
||||||
|
|
|
||||||
|
|
@ -242,7 +242,7 @@
|
||||||
"_id": "CQZQiEiRH70Br5Ge",
|
"_id": "CQZQiEiRH70Br5Ge",
|
||||||
"img": "icons/creatures/abilities/tail-strike-bone-orange.webp",
|
"img": "icons/creatures/abilities/tail-strike-bone-orange.webp",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.</p>",
|
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal 3 physical damage each. Combine this damage.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"ghgFZskDiizJDjcn": {
|
"ghgFZskDiizJDjcn": {
|
||||||
|
|
|
||||||
|
|
@ -242,7 +242,7 @@
|
||||||
"_id": "wl9KKEpVWDBu62hU",
|
"_id": "wl9KKEpVWDBu62hU",
|
||||||
"img": "icons/creatures/abilities/tail-strike-bone-orange.webp",
|
"img": "icons/creatures/abilities/tail-strike-bone-orange.webp",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.</p>",
|
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal 1 physical damage each. Combine this damage.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"Sz55uB8xkoNytLwJ": {
|
"Sz55uB8xkoNytLwJ": {
|
||||||
|
|
|
||||||
|
|
@ -223,7 +223,7 @@
|
||||||
"_id": "9Zuu892SO5NmtI4w",
|
"_id": "9Zuu892SO5NmtI4w",
|
||||||
"img": "icons/creatures/magical/humanoid-silhouette-aliens-green.webp",
|
"img": "icons/creatures/magical/humanoid-silhouette-aliens-green.webp",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p>When the @Lookup[@name] has marked half or more of their HP, their standard attack deals <strong>@Lookup[@system.attack.altDamageFormula]</strong> physical damage instead.</p>",
|
"description": "<p>When the @Lookup[@name] has marked half or more of their HP, their standard attack deals <strong>1d4+1</strong> physical damage instead.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {},
|
"actions": {},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
|
|
|
||||||
|
|
@ -254,12 +254,12 @@
|
||||||
},
|
},
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"name": "Horde",
|
"name": "Horde (1d4+2)",
|
||||||
"type": "feature",
|
"type": "feature",
|
||||||
"_id": "4dSzqtYvH385r9Ng",
|
"_id": "4dSzqtYvH385r9Ng",
|
||||||
"img": "icons/creatures/magical/humanoid-silhouette-aliens-green.webp",
|
"img": "icons/creatures/magical/humanoid-silhouette-aliens-green.webp",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p>When the @Lookup[@name] has marked half or more of their HP, their standard attack deals <strong>@Lookup[@system.attack.altDamageFormula]</strong> physical damage instead.</p>",
|
"description": "<p>When the @Lookup[@name] has marked half or more of their HP, their standard attack deals <strong>1d4+2</strong> physical damage instead.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {},
|
"actions": {},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
|
|
|
||||||
|
|
@ -281,7 +281,7 @@
|
||||||
"_id": "WiobzuyvJ46zfsOv",
|
"_id": "WiobzuyvJ46zfsOv",
|
||||||
"img": "icons/creatures/abilities/tail-strike-bone-orange.webp",
|
"img": "icons/creatures/abilities/tail-strike-bone-orange.webp",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.</p>",
|
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal 2 physical damage each. Combine this damage.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"ZC5pKIb9N82vgMWu": {
|
"ZC5pKIb9N82vgMWu": {
|
||||||
|
|
|
||||||
|
|
@ -97,8 +97,8 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"type": "attack",
|
"type": "attack",
|
||||||
"range": "melee",
|
"chatDisplay": false,
|
||||||
"chatDisplay": false
|
"range": ""
|
||||||
},
|
},
|
||||||
"attribution": {
|
"attribution": {
|
||||||
"source": "Daggerheart SRD",
|
"source": "Daggerheart SRD",
|
||||||
|
|
@ -239,7 +239,7 @@
|
||||||
"name": "Group Attack",
|
"name": "Group Attack",
|
||||||
"type": "feature",
|
"type": "feature",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal @Lookup[@system.attack.damageFormula] physical damage each. Combine this damage.</p>",
|
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal 8 physical damage each. Combine this damage.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"euP8VA4wvfsCpwN1": {
|
"euP8VA4wvfsCpwN1": {
|
||||||
|
|
|
||||||
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