mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-17 07:36:26 +01:00
Merge branch 'main' into bug/103-enrich-htmlfield-content-before-its-used-in-applications
This commit is contained in:
commit
f56c2482cb
551 changed files with 2831 additions and 14203 deletions
2
.github/workflows/deploy.yml
vendored
2
.github/workflows/deploy.yml
vendored
|
|
@ -37,7 +37,7 @@ jobs:
|
|||
download: https://github.com/${{github.repository}}/releases/download/${{github.event.release.tag_name}}/system.zip
|
||||
|
||||
# Create a zip file with all files required by the module to add to the release
|
||||
- run: zip -r ./system.zip system.json README.md LICENSE build/daggerheart.js assets/ templates/ styles/daggerheart.css packs/ lang/
|
||||
- run: zip -r ./system.zip system.json README.md LICENSE build/daggerheart.js build/tagify.css styles/daggerheart.css assets/ templates/ packs/ lang/
|
||||
|
||||
# Create a release for this specific version
|
||||
- name: Update Release with Files
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import * as documents from './module/documents/_module.mjs';
|
|||
import RegisterHandlebarsHelpers from './module/helpers/handlebarsHelper.mjs';
|
||||
import { DhDualityRollEnricher, DhTemplateEnricher } from './module/enrichers/_module.mjs';
|
||||
import { getCommandTarget, rollCommandToJSON } from './module/helpers/utils.mjs';
|
||||
import { NarrativeCountdowns, registerCountdownApplicationHooks } from './module/applications/ui/countdowns.mjs';
|
||||
import { NarrativeCountdowns } from './module/applications/ui/countdowns.mjs';
|
||||
import { DualityRollColor } from './module/data/settings/Appearance.mjs';
|
||||
import { DHRoll, DualityRoll, D20Roll, DamageRoll, DualityDie } from './module/dice/_module.mjs';
|
||||
import { renderDualityButton } from './module/enrichers/DualityRollEnricher.mjs';
|
||||
|
|
@ -19,6 +19,7 @@ import {
|
|||
import { placeables } from './module/canvas/_module.mjs';
|
||||
import { registerRollDiceHooks } from './module/dice/dhRoll.mjs';
|
||||
import { registerDHActorHooks } from './module/documents/actor.mjs';
|
||||
import './node_modules/@yaireo/tagify/dist/tagify.css';
|
||||
|
||||
Hooks.once('init', () => {
|
||||
CONFIG.DH = SYSTEM;
|
||||
|
|
@ -42,7 +43,7 @@ Hooks.once('init', () => {
|
|||
);
|
||||
|
||||
CONFIG.statusEffects = [
|
||||
...CONFIG.statusEffects,
|
||||
...CONFIG.statusEffects.filter(x => !['dead', 'unconscious'].includes(x.id)),
|
||||
...Object.values(SYSTEM.GENERAL.conditions).map(x => ({
|
||||
...x,
|
||||
name: game.i18n.localize(x.name),
|
||||
|
|
@ -61,6 +62,14 @@ Hooks.once('init', () => {
|
|||
CONFIG.Dice.rolls = [...CONFIG.Dice.rolls, ...[DHRoll, DualityRoll, D20Roll, DamageRoll]];
|
||||
CONFIG.MeasuredTemplate.objectClass = placeables.DhMeasuredTemplate;
|
||||
|
||||
const { DocumentSheetConfig } = foundry.applications.apps;
|
||||
CONFIG.Token.documentClass = documents.DhToken;
|
||||
CONFIG.Token.prototypeSheetClass = applications.sheetConfigs.DhPrototypeTokenConfig;
|
||||
DocumentSheetConfig.unregisterSheet(TokenDocument, 'core', foundry.applications.sheets.TokenConfig);
|
||||
DocumentSheetConfig.registerSheet(TokenDocument, 'dnd5e', applications.sheetConfigs.DhTokenConfig, {
|
||||
makeDefault: true
|
||||
});
|
||||
|
||||
CONFIG.Item.documentClass = documents.DHItem;
|
||||
|
||||
//Registering the Item DataModel
|
||||
|
|
@ -98,12 +107,12 @@ Hooks.once('init', () => {
|
|||
CONFIG.ActiveEffect.documentClass = documents.DhActiveEffect;
|
||||
CONFIG.ActiveEffect.dataModels = models.activeEffects.config;
|
||||
|
||||
foundry.applications.apps.DocumentSheetConfig.unregisterSheet(
|
||||
DocumentSheetConfig.unregisterSheet(
|
||||
CONFIG.ActiveEffect.documentClass,
|
||||
'core',
|
||||
foundry.applications.sheets.ActiveEffectConfig
|
||||
);
|
||||
foundry.applications.apps.DocumentSheetConfig.registerSheet(
|
||||
DocumentSheetConfig.registerSheet(
|
||||
CONFIG.ActiveEffect.documentClass,
|
||||
SYSTEM.id,
|
||||
applications.sheetConfigs.ActiveEffectConfig,
|
||||
|
|
@ -127,9 +136,11 @@ Hooks.once('init', () => {
|
|||
|
||||
CONFIG.Canvas.rulerClass = placeables.DhRuler;
|
||||
CONFIG.Canvas.layers.templates.layerClass = placeables.DhTemplateLayer;
|
||||
CONFIG.Token.objectClass = placeables.DhTokenPlaceable;
|
||||
CONFIG.Combat.documentClass = documents.DhpCombat;
|
||||
CONFIG.ui.combat = applications.ui.DhCombatTracker;
|
||||
CONFIG.ui.chat = applications.ui.DhChatLog;
|
||||
CONFIG.ui.hotbar = applications.ui.DhHotbar;
|
||||
CONFIG.Token.rulerClass = placeables.DhTokenRuler;
|
||||
|
||||
CONFIG.ui.resources = applications.ui.DhFearTracker;
|
||||
|
|
@ -160,7 +171,6 @@ Hooks.on('ready', () => {
|
|||
|
||||
registerCountdownHooks();
|
||||
socketRegistration.registerSocketHooks();
|
||||
registerCountdownApplicationHooks();
|
||||
registerRollDiceHooks();
|
||||
registerDHActorHooks();
|
||||
});
|
||||
|
|
|
|||
200
lang/en.json
200
lang/en.json
|
|
@ -99,6 +99,7 @@
|
|||
"tier": { "label": "Tier" },
|
||||
"type": { "label": "Type" }
|
||||
},
|
||||
"hordeDamage": "Horde Damage",
|
||||
"horderHp": "Horde/HP"
|
||||
},
|
||||
"Character": {
|
||||
|
|
@ -249,8 +250,9 @@
|
|||
"title": "{actor} - Death Move"
|
||||
},
|
||||
"Downtime": {
|
||||
"downtimeHeader": "Downtime Moves ({current}/{max})",
|
||||
"longRest": {
|
||||
"title": "Long Rest",
|
||||
"moves": "Long Rest Moves ({current}/{max})",
|
||||
"clearStress": {
|
||||
"description": "Describe how you blow off steam or pull yourself together, and clear all marked Stress.",
|
||||
"name": "Clear Stress"
|
||||
|
|
@ -267,7 +269,6 @@
|
|||
"description": "Describe how you patch yourself up and remove all marked Hit Points. You may also do this on an ally instead.",
|
||||
"name": "Tend to Wounds"
|
||||
},
|
||||
"title": "Long Rest",
|
||||
"workOnAProject": {
|
||||
"description": "Establish or continue work on a project.",
|
||||
"name": "Work on a Project"
|
||||
|
|
@ -275,6 +276,7 @@
|
|||
},
|
||||
"shortRest": {
|
||||
"title": "Short Rest",
|
||||
"moves": "Short Rest Moves ({current}/{max})",
|
||||
"tendToWounds": {
|
||||
"name": "Tend to Wounds",
|
||||
"description": "Describe how you hastily patch yourself up, then clear a number of Hit Points equal to 1d4 + your tier. You can do this to an ally instead."
|
||||
|
|
@ -291,7 +293,8 @@
|
|||
"name": "Prepare",
|
||||
"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."
|
||||
}
|
||||
}
|
||||
},
|
||||
"takeDowntime": "Take Downtime"
|
||||
},
|
||||
"HUD": {
|
||||
"tokenHUD": {
|
||||
|
|
@ -407,7 +410,11 @@
|
|||
"rerollDice": "Reroll Dice"
|
||||
}
|
||||
},
|
||||
|
||||
"CLASS": {
|
||||
"Feature": {
|
||||
"rallyDice": "Bardic Rally Dice"
|
||||
}
|
||||
},
|
||||
"CONFIG": {
|
||||
"ActionType": {
|
||||
"passive": "Passive",
|
||||
|
|
@ -560,9 +567,9 @@
|
|||
"twoHanded": "Two-Handed"
|
||||
},
|
||||
"Condition": {
|
||||
"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."
|
||||
"dead": {
|
||||
"name": "Dead",
|
||||
"description": "The character is dead"
|
||||
},
|
||||
"hidden": {
|
||||
"name": "Hidden",
|
||||
|
|
@ -571,6 +578,14 @@
|
|||
"restrained": {
|
||||
"name": "Restrained",
|
||||
"description": "When an effect makes a creature Restrained, it means they cannot move until this condition is cleared.\nThey can still take actions from their current position."
|
||||
},
|
||||
"unconcious": {
|
||||
"name": "Unconcious",
|
||||
"description": "Your character can’t move or act while unconscious, they can’t be targeted by an attack."
|
||||
},
|
||||
"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."
|
||||
}
|
||||
},
|
||||
"CountdownType": {
|
||||
|
|
@ -687,11 +702,11 @@
|
|||
}
|
||||
},
|
||||
"RollTypes": {
|
||||
"ability": {
|
||||
"name": "Ability"
|
||||
"trait": {
|
||||
"name": "Trait"
|
||||
},
|
||||
"weapon": {
|
||||
"name": "Weapon"
|
||||
"attack": {
|
||||
"name": "Attack"
|
||||
},
|
||||
"spellcast": {
|
||||
"name": "SpellCast"
|
||||
|
|
@ -763,7 +778,7 @@
|
|||
"WeaponFeature": {
|
||||
"barrier": {
|
||||
"name": "Barrier",
|
||||
"description": "+{armorScore} to Armor Score; -1 to Evasion"
|
||||
"description": "Gain your character's Tier + 1 to Armor Score; -1 to Evasion"
|
||||
},
|
||||
"bonded": {
|
||||
"name": "Bonded",
|
||||
|
|
@ -879,7 +894,7 @@
|
|||
},
|
||||
"paired": {
|
||||
"name": "Paired",
|
||||
"description": "+{bonusDamage} to primary weapon damage to targets within Melee range"
|
||||
"description": "Add your character's Tier + 1 to primary weapon damage against targets within Melee range"
|
||||
},
|
||||
"parry": {
|
||||
"name": "Parry",
|
||||
|
|
@ -899,7 +914,7 @@
|
|||
},
|
||||
"protective": {
|
||||
"name": "Protective",
|
||||
"description": "+{tier} to Armor Score"
|
||||
"description": "Add your character's Tier to your Armor Score"
|
||||
},
|
||||
"quick": {
|
||||
"name": "Quick",
|
||||
|
|
@ -948,10 +963,6 @@
|
|||
"timebending": {
|
||||
"name": "Timebending",
|
||||
"description": "You can choose the target of your attack after making your attack roll."
|
||||
},
|
||||
"versatile": {
|
||||
"name": "Versatile",
|
||||
"description": "This weapon can also be used with these statistics—{characterTrait}, {range}, {damage}."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -1000,6 +1011,30 @@
|
|||
"singular": "Adversary",
|
||||
"plural": "Adversaries"
|
||||
},
|
||||
"Bonuses": {
|
||||
"rest": {
|
||||
"shortRest": {
|
||||
"shortRestMoves": {
|
||||
"label": "Short Rest: Bonus Short Rest Moves",
|
||||
"hint": "The number of extra Short Rest Moves the character can take during a Short Rest."
|
||||
},
|
||||
"longRestMoves": {
|
||||
"label": "Short Rest: Bonus Long Rest Moves",
|
||||
"hint": "The number of extra Long Rest Moves the character can take during a Short Rest."
|
||||
}
|
||||
},
|
||||
"longRest": {
|
||||
"shortRestMoves": {
|
||||
"label": "Long Rest: Bonus Short Rest Moves",
|
||||
"hint": "The number of extra Short Rest Moves the character can take during a Long Rest."
|
||||
},
|
||||
"longRestMoves": {
|
||||
"label": "Long Rest: Bonus Long Rest Moves",
|
||||
"hint": "The number of extra Long Rest Moves the character can take during a Long Rest."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Character": {
|
||||
"singular": "Character",
|
||||
"plural": "Characters"
|
||||
|
|
@ -1012,23 +1047,36 @@
|
|||
"severe": "Severe",
|
||||
"major": "Major",
|
||||
"minor": "Minor",
|
||||
"none": "None"
|
||||
"none": "None",
|
||||
"allDamage": "All Damage",
|
||||
"physicalDamage": "Physical Damage",
|
||||
"magicalDamage": "Magical Damage",
|
||||
"primaryWeapon": "Primary Weapon Damage",
|
||||
"secondaryWeapon": "Secondary Weapon Damage"
|
||||
},
|
||||
"DamageResistance": {
|
||||
"none": "None",
|
||||
"resistance": "Resistance",
|
||||
"immunity": "Immunity"
|
||||
"immunity": "Immunity",
|
||||
"physicalReduction": "Physical Damage Reduction",
|
||||
"magicalReduction": "Magical Damage Reduction"
|
||||
},
|
||||
"DamageThresholds": {
|
||||
"title": "Damage Thresholds",
|
||||
"minor": "Minor",
|
||||
"major": "Major",
|
||||
"severe": "Severe"
|
||||
"severe": "Severe",
|
||||
"majorThreshold": "Major Damage Threshold",
|
||||
"severeThreshold": "Severe Damage Threshold"
|
||||
},
|
||||
"Dice": {
|
||||
"single": "Die",
|
||||
"plural": "Dice"
|
||||
},
|
||||
"Difficulty": {
|
||||
"all": "Difficulty: all",
|
||||
"reaction": "Difficulty: reaction"
|
||||
},
|
||||
"Disadvantage": {
|
||||
"full": "Disadvantage",
|
||||
"short": "Dis"
|
||||
|
|
@ -1038,39 +1086,39 @@
|
|||
"plural": "Domains",
|
||||
"arcana": {
|
||||
"label": "Arcana",
|
||||
"Description": "This is the domain of the innate or instinctual use of magic. Those who walk this path tap into the raw, enigmatic forces of the realms to manipulate both the elements and their own energy. Arcana offers wielders a volatile power, but it is incredibly potent when correctly channeled."
|
||||
"description": "This is the domain of the innate or instinctual use of magic. Those who walk this path tap into the raw, enigmatic forces of the realms to manipulate both the elements and their own energy. Arcana offers wielders a volatile power, but it is incredibly potent when correctly channeled."
|
||||
},
|
||||
"blade": {
|
||||
"label": "Blade",
|
||||
"Description": "This is the domain of those who dedicate their lives to the mastery of weapons. Whether by blade, bow, or perhaps a more specialized arm, those who follow this path have the skill to cut short the lives of others. Blade requires study and dedication from its followers, in exchange for inexorable power over death."
|
||||
"description": "This is the domain of those who dedicate their lives to the mastery of weapons. Whether by blade, bow, or perhaps a more specialized arm, those who follow this path have the skill to cut short the lives of others. Blade requires study and dedication from its followers, in exchange for inexorable power over death."
|
||||
},
|
||||
"bone": {
|
||||
"label": "Bone",
|
||||
"Description": "This is the domain of mastery of swiftness and tactical mastery. Practitioners of this domain have an uncanny control over their own physical abilities, and an eye for predicting the behaviors of others in combat. Bone grants its adherents unparalleled understanding of bodies and their movements in exchange for diligent training."
|
||||
"description": "This is the domain of mastery of swiftness and tactical mastery. Practitioners of this domain have an uncanny control over their own physical abilities, and an eye for predicting the behaviors of others in combat. Bone grants its adherents unparalleled understanding of bodies and their movements in exchange for diligent training."
|
||||
},
|
||||
"codex": {
|
||||
"label": "Codex",
|
||||
"Description": "This is the domain of intensive magical study. Those who seek magical knowledge turn to the recipes of power recorded in books, on scrolls, etched into walls, or tattooed on bodies. Codex offers a commanding and versatile understanding of magic to those devotees who are willing to seek beyond the common knowledge."
|
||||
"description": "This is the domain of intensive magical study. Those who seek magical knowledge turn to the recipes of power recorded in books, on scrolls, etched into walls, or tattooed on bodies. Codex offers a commanding and versatile understanding of magic to those devotees who are willing to seek beyond the common knowledge."
|
||||
},
|
||||
"grace": {
|
||||
"label": "Grace",
|
||||
"Description": "This is the domain of charisma. Through rapturous storytelling, clever charm, or a shroud of lies, those who channel this power define the realities of their adversaries, bending perception to their will. Grace offers its wielders raw magnetism and mastery over language."
|
||||
"description": "This is the domain of charisma. Through rapturous storytelling, clever charm, or a shroud of lies, those who channel this power define the realities of their adversaries, bending perception to their will. Grace offers its wielders raw magnetism and mastery over language."
|
||||
},
|
||||
"midnight": {
|
||||
"label": "Midnight",
|
||||
"Description": "This is the domain of shadows and secrecy. Whether by clever tricks, or cloak of night those who channel these forces are practiced in that art of obscurity and there is nothing hidden they cannot reach. Midnight offers practitioners the incredible power to control and create enigmas."
|
||||
"description": "This is the domain of shadows and secrecy. Whether by clever tricks, or cloak of night those who channel these forces are practiced in that art of obscurity and there is nothing hidden they cannot reach. Midnight offers practitioners the incredible power to control and create enigmas."
|
||||
},
|
||||
"sage": {
|
||||
"label": "Sage",
|
||||
"Description": "This is the domain of the natural world. Those who walk this path tap into the unfettered power of the earth and its creatures to unleash raw magic. Sage grants its adherents the vitality of a blooming flower and ferocity of a hungry predator."
|
||||
"description": "This is the domain of the natural world. Those who walk this path tap into the unfettered power of the earth and its creatures to unleash raw magic. Sage grants its adherents the vitality of a blooming flower and ferocity of a hungry predator."
|
||||
},
|
||||
"splendor": {
|
||||
"label": "Splendor",
|
||||
"Description": "This is the domain of life. Through this magic, followers gain the ability to heal, though such power also grants the wielder some control over death. Splendor offers its disciples the magnificent ability to both give and end life."
|
||||
"description": "This is the domain of life. Through this magic, followers gain the ability to heal, though such power also grants the wielder some control over death. Splendor offers its disciples the magnificent ability to both give and end life."
|
||||
},
|
||||
"valor": {
|
||||
"label": "Valor",
|
||||
"Description": "This is the domain of protection. Whether through attack or defense, those who choose this discipline channel formidable strength to protect their allies in battle. Valor offers great power to those who raise their shield in defense of others."
|
||||
"description": "This is the domain of protection. Whether through attack or defense, those who choose this discipline channel formidable strength to protect their allies in battle. Valor offers great power to those who raise their shield in defense of others."
|
||||
}
|
||||
},
|
||||
"Effect": {
|
||||
|
|
@ -1081,10 +1129,18 @@
|
|||
"single": "Experience",
|
||||
"plural": "Experiences"
|
||||
},
|
||||
"Healing": {
|
||||
"healingAmount": "Healing Amount"
|
||||
},
|
||||
"Neutral": {
|
||||
"full": "None",
|
||||
"short": "no"
|
||||
},
|
||||
"Range": {
|
||||
"other": "Range Increase: Other",
|
||||
"spell": "Range Increase: Spell",
|
||||
"weapon": "Range Increase: Weapon"
|
||||
},
|
||||
"RefreshType": {
|
||||
"session": "Session",
|
||||
"shortrest": "Short Rest",
|
||||
|
|
@ -1094,6 +1150,42 @@
|
|||
"single": "Resource",
|
||||
"plural": "Resources"
|
||||
},
|
||||
"Roll": {
|
||||
"attack": "Attack Roll",
|
||||
"primaryWeaponAttack": "Primary Weapon Attack Roll",
|
||||
"secondaryWeaponAttack": "Secondary Weapon Attack Roll",
|
||||
"spellcast": "Spellcast Roll",
|
||||
"trait": "Trait Roll",
|
||||
"action": "Action Roll",
|
||||
"reaction": "Reaction Roll"
|
||||
},
|
||||
"Rules": {
|
||||
"damageReduction": {
|
||||
"increasePerArmorMark": {
|
||||
"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."
|
||||
},
|
||||
"maxArmorMarkedBonus": "Max Armor Used",
|
||||
"maxArmorMarkedStress": {
|
||||
"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."
|
||||
},
|
||||
"stress": {
|
||||
"severe": {
|
||||
"label": "Stress Damage Reduction: Severe",
|
||||
"hint": "The cost in stress you can pay to reduce severe damage down to major."
|
||||
},
|
||||
"major": {
|
||||
"label": "Stress Damage Reduction: Major",
|
||||
"hint": "The cost in stress you can pay to reduce major damage down to minor."
|
||||
},
|
||||
"minor": {
|
||||
"label": "Stress Damage Reduction: Minor",
|
||||
"hint": "The cost in stress you can pay to reduce minor damage to none."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Tabs": {
|
||||
"details": "Details",
|
||||
"attack": "Attack",
|
||||
|
|
@ -1138,7 +1230,9 @@
|
|||
"single": "Trait",
|
||||
"plural": "Traits"
|
||||
},
|
||||
"armorScore": "Armor Score",
|
||||
"activeEffects": "Active Effects",
|
||||
"armorSlots": "Armor Slots",
|
||||
"attack": "Attack",
|
||||
"basics": "Basics",
|
||||
"bonus": "Bonus",
|
||||
|
|
@ -1148,14 +1242,24 @@
|
|||
"damage": "Damage",
|
||||
"damageType": "Damage Type",
|
||||
"description": "Description",
|
||||
"difficulty": "Difficulty",
|
||||
"duality": "Duality",
|
||||
"dualityRoll": "Duality Roll",
|
||||
"enabled": "Enabled",
|
||||
"evasion": "Evasion",
|
||||
"experience": {
|
||||
"single": "Experience",
|
||||
"plural": "Experiences"
|
||||
},
|
||||
"fear": "Fear",
|
||||
"features": "Features",
|
||||
"hitPoints": "Hit Points",
|
||||
"hitPoints": {
|
||||
"single": "Hit Point",
|
||||
"plural": "Hit Points",
|
||||
"short": "HP"
|
||||
},
|
||||
"hope": "Hope",
|
||||
"hordeHp": "Horde HP",
|
||||
"inactiveEffects": "Inactive Effects",
|
||||
"inventory": "Inventory",
|
||||
"level": "Level",
|
||||
|
|
@ -1163,9 +1267,12 @@
|
|||
"modifier": "Modifier",
|
||||
"multiclass": "Multiclass",
|
||||
"none": "None",
|
||||
"partner": "Partner",
|
||||
"proficiency": "Proficiency",
|
||||
"quantity": "Quantity",
|
||||
"range": "Range",
|
||||
"recovery": "Recovery",
|
||||
"roll": "Roll",
|
||||
"scalable": "Scalable",
|
||||
"stress": "Stress",
|
||||
"take": "Take",
|
||||
|
|
@ -1177,7 +1284,8 @@
|
|||
"use": "Use",
|
||||
"used": "Used",
|
||||
"uses": "Uses",
|
||||
"value": "Value"
|
||||
"value": "Value",
|
||||
"withThing": "With {thing}"
|
||||
},
|
||||
"ITEMS": {
|
||||
"FIELDS": {
|
||||
|
|
@ -1240,8 +1348,8 @@
|
|||
},
|
||||
"DomainCard": {
|
||||
"type": "Type",
|
||||
"foundation": "Foundation",
|
||||
"recallCost": "Recall Cost",
|
||||
"foundationTitle": "Foundation",
|
||||
"specializationTitle": "Specialization",
|
||||
"masteryTitle": "Mastery"
|
||||
},
|
||||
|
|
@ -1272,17 +1380,18 @@
|
|||
"hint": "Automatically increase the GM's fear pool on a fear duality roll result."
|
||||
},
|
||||
"FIELDS": {
|
||||
"hope": {
|
||||
"label": "Hope",
|
||||
"hint": "Automatically increase a character's hope on a hope duality roll result."
|
||||
"hopeFear": {
|
||||
"label": "Hope & Fear",
|
||||
"gm": { "label": "GM" },
|
||||
"players": { "label": "Players" }
|
||||
},
|
||||
"actionPoints": {
|
||||
"label": "Action Points",
|
||||
"hint": "Automatically give and take Action Points as combatants take their turns."
|
||||
},
|
||||
"countdowns": {
|
||||
"label": "Countdowns",
|
||||
"hint": "Automatically progress non-custom countdowns"
|
||||
"hordeDamage": {
|
||||
"label": "Automatic Horde Damage",
|
||||
"hint": "Automatically active horde effect to lower damage when reaching half or lower HP."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -1451,7 +1560,18 @@
|
|||
"noAvailableArmorMarks": "You have no more available armor marks",
|
||||
"notEnoughStress": "You don't have enough stress",
|
||||
"damageIgnore": "{character} did not take damage",
|
||||
"featureIsMissing": "Feature is missing"
|
||||
"featureIsMissing": "Feature is missing",
|
||||
"actionIsMissing": "Action is missing",
|
||||
"attackIsMissing": "Attack is missing",
|
||||
"unownedActionMacro": "Cannot make a Use macro for an Action not on your character",
|
||||
"unownedAttackMacro": "Cannot make a Use macro for an Attack that doesn't belong to one of your characters",
|
||||
"featureNotHope": "This feature is used as something else than a Hope feature and cannot be used here.",
|
||||
"featureNotClass": "This feature is used as something else than a Class feature and cannot be used here.",
|
||||
"featureNotPrimary": "This feature is used as something else than a Primary feature and cannot be used here.",
|
||||
"featureNotSecondary": "This feature is used as something else than a Secondary feature and cannot be used here.",
|
||||
"featureNotFoundation": "This feature is used as something else than a Foundation feature and cannot be used here.",
|
||||
"featureNotSpecialization": "This feature is used as something else than a Specialization feature and cannot be used here.",
|
||||
"featureNotMastery": "This feature is used as something else than a Mastery feature and cannot be used here."
|
||||
},
|
||||
"Tooltip": {
|
||||
"disableEffect": "Disable Effect",
|
||||
|
|
|
|||
|
|
@ -506,9 +506,7 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
|||
name: this.setup.ancestryName ?? this.setup.primaryAncestry.name,
|
||||
system: {
|
||||
...this.setup.primaryAncestry.system,
|
||||
features: [primaryAncestryFeature.uuid, secondaryAncestryFeature.uuid],
|
||||
primaryFeature: primaryAncestryFeature.uuid,
|
||||
secondaryFeature: secondaryAncestryFeature.uuid
|
||||
features: [primaryAncestryFeature.uuid, secondaryAncestryFeature.uuid]
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -64,6 +64,13 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
context.rollConfig = this.config;
|
||||
context.hasRoll = !!this.config.roll;
|
||||
context.canRoll = true;
|
||||
context.selectedRollMode = this.config.selectedRollMode;
|
||||
context.rollModes = Object.entries(CONFIG.Dice.rollModes).map(([action, { label, icon }]) => ({
|
||||
action,
|
||||
label,
|
||||
icon
|
||||
}));
|
||||
|
||||
if (this.config.costs?.length) {
|
||||
const updatedCosts = this.action.calcCosts(this.config.costs);
|
||||
context.costs = updatedCosts.map(x => ({
|
||||
|
|
@ -82,6 +89,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
if (this.roll) {
|
||||
context.roll = this.roll;
|
||||
context.rollType = this.roll?.constructor.name;
|
||||
context.rallyDie = this.roll.rallyChoices;
|
||||
context.experiences = Object.keys(this.config.data.experiences).map(id => ({
|
||||
id,
|
||||
...this.config.data.experiences[id]
|
||||
|
|
@ -99,6 +107,8 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
|
||||
static updateRollConfiguration(event, _, formData) {
|
||||
const { ...rest } = foundry.utils.expandObject(formData.object);
|
||||
this.config.selectedRollMode = rest.selectedRollMode;
|
||||
|
||||
if (this.config.costs) {
|
||||
this.config.costs = foundry.utils.mergeObject(this.config.costs, rest.costs);
|
||||
}
|
||||
|
|
@ -122,11 +132,6 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
}
|
||||
|
||||
static selectExperience(_, button) {
|
||||
/* if (this.config.experiences.find(x => x === button.dataset.key)) {
|
||||
this.config.experiences = this.config.experiences.filter(x => x !== button.dataset.key);
|
||||
} else {
|
||||
this.config.experiences = [...this.config.experiences, button.dataset.key];
|
||||
} */
|
||||
this.config.experiences =
|
||||
this.config.experiences.indexOf(button.dataset.key) > -1
|
||||
? this.config.experiences.filter(x => x !== button.dataset.key)
|
||||
|
|
|
|||
|
|
@ -48,12 +48,22 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
|
|||
: game.i18n.localize('DAGGERHEART.EFFECTS.ApplyLocations.damageRoll.name');
|
||||
context.extraFormula = this.config.extraFormula;
|
||||
context.formula = this.roll.constructFormula(this.config);
|
||||
context.directDamage = this.config.directDamage;
|
||||
context.selectedRollMode = this.config.selectedRollMode;
|
||||
context.rollModes = Object.entries(CONFIG.Dice.rollModes).map(([action, { label, icon }]) => ({
|
||||
action,
|
||||
label,
|
||||
icon
|
||||
}));
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
static updateRollConfiguration(event, _, formData) {
|
||||
static updateRollConfiguration(_event, _, formData) {
|
||||
const { ...rest } = foundry.utils.expandObject(formData.object);
|
||||
this.config.extraFormula = rest.extraFormula;
|
||||
this.config.selectedRollMode = rest.selectedRollMode;
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,8 +7,22 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
|||
this.actor = actor;
|
||||
this.shortrest = shortrest;
|
||||
|
||||
const options = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).restMoves;
|
||||
this.moveData = shortrest ? options.shortRest : options.longRest;
|
||||
this.moveData = foundry.utils.deepClone(
|
||||
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).restMoves
|
||||
);
|
||||
this.nrChoices = {
|
||||
shortRest: {
|
||||
max:
|
||||
(shortrest ? this.moveData.shortRest.nrChoices : 0) +
|
||||
actor.system.bonuses.rest[`${shortrest ? 'short' : 'long'}Rest`].shortMoves
|
||||
},
|
||||
longRest: {
|
||||
max:
|
||||
(!shortrest ? this.moveData.longRest.nrChoices : 0) +
|
||||
actor.system.bonuses.rest[`${shortrest ? 'short' : 'long'}Rest`].longMoves
|
||||
}
|
||||
};
|
||||
this.nrChoices.total = { max: this.nrChoices.shortRest.max + this.nrChoices.longRest.max };
|
||||
}
|
||||
|
||||
get title() {
|
||||
|
|
@ -17,8 +31,8 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
|||
|
||||
static DEFAULT_OPTIONS = {
|
||||
tag: 'form',
|
||||
classes: ['daggerheart', 'views', 'downtime'],
|
||||
position: { width: 680, height: 'auto' },
|
||||
classes: ['daggerheart', 'views', 'dh-style', 'dialog', 'downtime'],
|
||||
position: { width: 'auto', height: 'auto' },
|
||||
actions: {
|
||||
selectMove: this.selectMove,
|
||||
takeDowntime: this.takeDowntime
|
||||
|
|
@ -29,7 +43,7 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
|||
static PARTS = {
|
||||
application: {
|
||||
id: 'downtime',
|
||||
template: 'systems/daggerheart/templates/dialogs/downtime.hbs'
|
||||
template: 'systems/daggerheart/templates/dialogs/downtime/downtime.hbs'
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -37,46 +51,83 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
|||
super._attachPartListeners(partId, htmlElement, options);
|
||||
|
||||
htmlElement
|
||||
.querySelectorAll('.activity-image')
|
||||
.querySelectorAll('.activity-container')
|
||||
.forEach(element => element.addEventListener('contextmenu', this.deselectMove.bind(this)));
|
||||
}
|
||||
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
context.title = game.i18n.localize(
|
||||
`DAGGERHEART.APPLICATIONS.Downtime.${this.shortrest ? 'shortRest' : 'longRest'}.title`
|
||||
);
|
||||
context.selectedActivity = this.selectedActivity;
|
||||
context.moveData = this.moveData;
|
||||
context.nrCurrentChoices = Object.values(this.moveData.moves).reduce((acc, x) => acc + (x.selected ?? 0), 0);
|
||||
context.disabledDowntime = context.nrCurrentChoices < context.moveData.nrChoices;
|
||||
context.nrCurrentChoices = Object.values(this.moveData).reduce((acc, category) => {
|
||||
acc += Object.values(category.moves).reduce((acc, x) => acc + (x.selected ?? 0), 0);
|
||||
return acc;
|
||||
}, 0);
|
||||
|
||||
context.nrChoices = {
|
||||
...this.nrChoices,
|
||||
shortRest: {
|
||||
...this.nrChoices.shortRest,
|
||||
current: Object.values(this.moveData.shortRest.moves).reduce((acc, x) => acc + (x.selected ?? 0), 0)
|
||||
},
|
||||
longRest: {
|
||||
...this.nrChoices.longRest,
|
||||
current: Object.values(this.moveData.longRest.moves).reduce((acc, x) => acc + (x.selected ?? 0), 0)
|
||||
}
|
||||
};
|
||||
context.nrChoices.total = {
|
||||
...this.nrChoices.total,
|
||||
current: context.nrChoices.shortRest.current + context.nrChoices.longRest.current
|
||||
};
|
||||
|
||||
context.shortRestMoves = this.nrChoices.shortRest.max > 0 ? this.moveData.shortRest : null;
|
||||
context.longRestMoves = this.nrChoices.longRest.max > 0 ? this.moveData.longRest : null;
|
||||
|
||||
context.disabledDowntime = context.nrChoices.total.current < context.nrChoices.total.max;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
static selectMove(_, button) {
|
||||
const nrSelected = Object.values(this.moveData.moves).reduce((acc, x) => acc + (x.selected ?? 0), 0);
|
||||
if (nrSelected === this.moveData.nrChoices) {
|
||||
static selectMove(_, target) {
|
||||
const nrSelected = Object.values(this.moveData[target.dataset.category].moves).reduce(
|
||||
(acc, x) => acc + (x.selected ?? 0),
|
||||
0
|
||||
);
|
||||
|
||||
if (nrSelected === this.nrChoices[target.dataset.category].max) {
|
||||
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.noMoreMoves'));
|
||||
return;
|
||||
}
|
||||
|
||||
const move = button.dataset.move;
|
||||
this.moveData.moves[move].selected = this.moveData.moves[move].selected
|
||||
? this.moveData.moves[move].selected + 1
|
||||
const move = target.dataset.move;
|
||||
this.moveData[target.dataset.category].moves[move].selected = this.moveData[target.dataset.category].moves[move]
|
||||
.selected
|
||||
? this.moveData[target.dataset.category].moves[move].selected + 1
|
||||
: 1;
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
deselectMove(event) {
|
||||
const move = event.currentTarget.dataset.move;
|
||||
this.moveData.moves[move].selected = this.moveData.moves[move].selected
|
||||
? this.moveData.moves[move].selected - 1
|
||||
const button = event.target.closest('.activity-container');
|
||||
const move = button.dataset.move;
|
||||
this.moveData[button.dataset.category].moves[move].selected = this.moveData[button.dataset.category].moves[move]
|
||||
.selected
|
||||
? this.moveData[button.dataset.category].moves[move].selected - 1
|
||||
: 0;
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async takeDowntime() {
|
||||
const moves = Object.values(this.moveData.moves).filter(x => x.selected);
|
||||
const moves = Object.values(this.moveData).flatMap(category => {
|
||||
return Object.values(category.moves)
|
||||
.filter(x => x.selected)
|
||||
.flatMap(move => [...Array(move.selected).keys()].map(_ => move));
|
||||
});
|
||||
|
||||
const cls = getDocumentClass('ChatMessage');
|
||||
const msg = new cls({
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ export default class ResourceDiceDialog extends HandlebarsApplicationMixin(Appli
|
|||
|
||||
static async rerollDice() {
|
||||
const max = itemAbleRollParse(this.item.system.resource.max, this.actor, this.item);
|
||||
const diceFormula = `${max}d${this.item.system.resource.dieFaces}`;
|
||||
const diceFormula = `${max}${this.item.system.resource.dieFaces}`;
|
||||
const roll = await new Roll(diceFormula).evaluate();
|
||||
if (game.modules.get('dice-so-nice')?.active) await game.dice3d.showForRoll(roll, game.user, true);
|
||||
this.rollValues = roll.terms[0].results.map(x => ({ value: x.result, used: false }));
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
export { default as DHTokenHUD } from './tokenHud.mjs';
|
||||
export { default as DHTokenHUD } from './tokenHUD.mjs';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
export default class DHTokenHUD extends TokenHUD {
|
||||
export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ['daggerheart']
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,13 +4,14 @@ import DHActionConfig from '../../sheets-configs/action-config.mjs';
|
|||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
||||
export default class DhSettingsActionView extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
constructor(resolve, reject, title, name, img, description, actions) {
|
||||
constructor(resolve, reject, title, name, icon, img, description, actions) {
|
||||
super({});
|
||||
|
||||
this.resolve = resolve;
|
||||
this.reject = reject;
|
||||
this.viewTitle = title;
|
||||
this.name = name;
|
||||
this.icon = icon;
|
||||
this.img = img;
|
||||
this.description = description;
|
||||
this.actions = actions;
|
||||
|
|
@ -23,7 +24,7 @@ export default class DhSettingsActionView extends HandlebarsApplicationMixin(App
|
|||
static DEFAULT_OPTIONS = {
|
||||
tag: 'form',
|
||||
classes: ['daggerheart', 'setting', 'dh-style'],
|
||||
position: { width: '400', height: 'auto' },
|
||||
position: { width: 440, height: 'auto' },
|
||||
actions: {
|
||||
editImage: this.onEditImage,
|
||||
addItem: this.addItem,
|
||||
|
|
@ -46,6 +47,7 @@ export default class DhSettingsActionView extends HandlebarsApplicationMixin(App
|
|||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
context.name = this.name;
|
||||
context.icon = this.icon;
|
||||
context.img = this.img;
|
||||
context.description = this.description;
|
||||
context.enrichedDescription = await foundry.applications.ux.TextEditor.enrichHTML(context.description);
|
||||
|
|
@ -55,8 +57,9 @@ export default class DhSettingsActionView extends HandlebarsApplicationMixin(App
|
|||
}
|
||||
|
||||
static async updateData(event, element, formData) {
|
||||
const { name, img, description } = foundry.utils.expandObject(formData.object);
|
||||
const { name, icon, description } = foundry.utils.expandObject(formData.object);
|
||||
this.name = name;
|
||||
this.icon = icon;
|
||||
this.description = description;
|
||||
|
||||
this.render();
|
||||
|
|
@ -65,6 +68,7 @@ export default class DhSettingsActionView extends HandlebarsApplicationMixin(App
|
|||
static async saveForm(event) {
|
||||
this.resolve({
|
||||
name: this.name,
|
||||
icon: this.icon,
|
||||
img: this.img,
|
||||
description: this.description,
|
||||
actions: this.actions
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
reject,
|
||||
game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.downtimeMoves'),
|
||||
move.name,
|
||||
move.icon,
|
||||
move.img,
|
||||
move.description,
|
||||
move.actions
|
||||
|
|
@ -87,6 +88,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
await this.settings.updateSource({
|
||||
[`restMoves.${type}.moves.${id}`]: {
|
||||
name: data.name,
|
||||
icon: data.icon,
|
||||
img: data.img,
|
||||
description: data.description
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,3 +3,5 @@ export { default as AdversarySettings } from './adversary-settings.mjs';
|
|||
export { default as CompanionSettings } from './companion-settings.mjs';
|
||||
export { default as EnvironmentSettings } from './environment-settings.mjs';
|
||||
export { default as ActiveEffectConfig } from './activeEffectConfig.mjs';
|
||||
export { default as DhTokenConfig } from './token-config.mjs';
|
||||
export { default as DhPrototypeTokenConfig } from './prototype-token-config.mjs';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,24 @@
|
|||
import autocomplete from 'autocompleter';
|
||||
|
||||
export default class DhActiveEffectConfig extends foundry.applications.sheets.ActiveEffectConfig {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
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 = {
|
||||
classes: ['daggerheart', 'sheet', 'dh-style']
|
||||
};
|
||||
|
|
@ -27,36 +47,62 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
|
|||
}
|
||||
};
|
||||
|
||||
_attachPartListeners(partId, htmlElement, options) {
|
||||
super._attachPartListeners(partId, htmlElement, options);
|
||||
const changeChoices = this.changeChoices;
|
||||
|
||||
htmlElement.querySelectorAll('.effect-change-input').forEach(element => {
|
||||
autocomplete({
|
||||
input: element,
|
||||
fetch: function (text, update) {
|
||||
if (!text) {
|
||||
update(changeChoices);
|
||||
} else {
|
||||
text = text.toLowerCase();
|
||||
var suggestions = changeChoices.filter(n => n.label.toLowerCase().includes(text));
|
||||
update(suggestions);
|
||||
}
|
||||
},
|
||||
render: function (item, search) {
|
||||
const label = game.i18n.localize(item.label);
|
||||
const matchIndex = label.toLowerCase().indexOf(search);
|
||||
|
||||
const beforeText = label.slice(0, matchIndex);
|
||||
const matchText = label.slice(matchIndex, matchIndex + search.length);
|
||||
const after = label.slice(matchIndex + search.length, label.length);
|
||||
|
||||
const element = document.createElement('li');
|
||||
element.innerHTML = `${beforeText}${matchText ? `<strong>${matchText}</strong>` : ''}${after}`;
|
||||
if (item.hint) {
|
||||
element.dataset.tooltip = game.i18n.localize(item.hint);
|
||||
}
|
||||
|
||||
return element;
|
||||
},
|
||||
renderGroup: function (label) {
|
||||
const itemElement = document.createElement('div');
|
||||
itemElement.textContent = game.i18n.localize(label);
|
||||
return itemElement;
|
||||
},
|
||||
onSelect: function (item) {
|
||||
element.value = `system.${item.value}`;
|
||||
},
|
||||
click: e => e.fetch(),
|
||||
customize: function (_input, _inputRect, container) {
|
||||
container.style.zIndex = foundry.applications.api.ApplicationV2._maxZ;
|
||||
},
|
||||
minLength: 0
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async _preparePartContext(partId, context) {
|
||||
const partContext = await super._preparePartContext(partId, context);
|
||||
switch (partId) {
|
||||
case 'changes':
|
||||
const fieldPaths = [];
|
||||
const validFieldPath = fieldPath => this.validFieldPath(fieldPath, this.#unapplicablePaths);
|
||||
context.document.parent.system.schema.apply(function () {
|
||||
if (!(this instanceof foundry.data.fields.SchemaField)) {
|
||||
if (validFieldPath(this.fieldPath)) {
|
||||
fieldPaths.push(this.fieldPath);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
context.fieldPaths = fieldPaths;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return partContext;
|
||||
}
|
||||
|
||||
#unapplicablePaths = ['story', 'pronouns', 'description'];
|
||||
validFieldPath(fieldPath, unapplicablePaths) {
|
||||
const splitPath = fieldPath.split('.');
|
||||
if (splitPath.length > 1 && unapplicablePaths.includes(splitPath[1])) return false;
|
||||
|
||||
/* The current value of a resource should not be modified */
|
||||
if (new RegExp(/resources.*\.value/).exec(fieldPath)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
export default class DhPrototypeTokenConfig extends foundry.applications.sheets.PrototypeTokenConfig {
|
||||
/** @inheritDoc */
|
||||
async _prepareResourcesTab() {
|
||||
const token = this.token;
|
||||
const usesTrackableAttributes = !foundry.utils.isEmpty(CONFIG.Actor.trackableAttributes);
|
||||
const attributeSource =
|
||||
this.actor?.system instanceof foundry.abstract.DataModel && usesTrackableAttributes
|
||||
? this.actor?.type
|
||||
: this.actor?.system;
|
||||
const TokenDocument = foundry.utils.getDocumentClass('Token');
|
||||
const attributes = TokenDocument.getTrackedAttributes(attributeSource);
|
||||
return {
|
||||
barAttributes: TokenDocument.getTrackedAttributeChoices(attributes, attributeSource),
|
||||
bar1: token.getBarAttribute?.('bar1'),
|
||||
bar2: token.getBarAttribute?.('bar2'),
|
||||
turnMarkerModes: DhPrototypeTokenConfig.TURN_MARKER_MODES,
|
||||
turnMarkerAnimations: CONFIG.Combat.settings.turnMarkerAnimations
|
||||
};
|
||||
}
|
||||
}
|
||||
20
module/applications/sheets-configs/token-config.mjs
Normal file
20
module/applications/sheets-configs/token-config.mjs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
export default class DhTokenConfig extends foundry.applications.sheets.TokenConfig {
|
||||
/** @inheritDoc */
|
||||
async _prepareResourcesTab() {
|
||||
const token = this.token;
|
||||
const usesTrackableAttributes = !foundry.utils.isEmpty(CONFIG.Actor.trackableAttributes);
|
||||
const attributeSource =
|
||||
this.actor?.system instanceof foundry.abstract.DataModel && usesTrackableAttributes
|
||||
? this.actor?.type
|
||||
: this.actor?.system;
|
||||
const TokenDocument = foundry.utils.getDocumentClass('Token');
|
||||
const attributes = TokenDocument.getTrackedAttributes(attributeSource);
|
||||
return {
|
||||
barAttributes: TokenDocument.getTrackedAttributeChoices(attributes, attributeSource),
|
||||
bar1: token.getBarAttribute?.('bar1'),
|
||||
bar2: token.getBarAttribute?.('bar2'),
|
||||
turnMarkerModes: DhTokenConfig.TURN_MARKER_MODES,
|
||||
turnMarkerAnimations: CONFIG.Combat.settings.turnMarkerAnimations
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -25,6 +25,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
toggleEquipItem: CharacterSheet.#toggleEquipItem,
|
||||
toggleResourceDice: CharacterSheet.#toggleResourceDice,
|
||||
handleResourceDice: CharacterSheet.#handleResourceDice,
|
||||
useDowntime: this.useDowntime
|
||||
},
|
||||
window: {
|
||||
resizable: true
|
||||
|
|
@ -113,6 +114,11 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
htmlElement.querySelectorAll('.inventory-item-quantity').forEach(element => {
|
||||
element.addEventListener('change', this.updateItemQuantity.bind(this));
|
||||
});
|
||||
|
||||
// Add listener for armor marks input
|
||||
htmlElement.querySelectorAll('.armor-marks-input').forEach(element => {
|
||||
element.addEventListener('change', this.updateArmorMarks.bind(this));
|
||||
});
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
|
|
@ -519,6 +525,16 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
await item.update({ 'system.quantity': event.currentTarget.value });
|
||||
}
|
||||
|
||||
async updateArmorMarks(event) {
|
||||
const armor = this.document.system.armor;
|
||||
if (!armor) return;
|
||||
|
||||
const maxMarks = this.document.system.armorScore;
|
||||
const value = Math.min(Math.max(Number(event.currentTarget.value), 0), maxMarks);
|
||||
await armor.update({ 'system.marks.value': value });
|
||||
this.render();
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Application Clicks Actions */
|
||||
/* -------------------------------------------- */
|
||||
|
|
@ -669,7 +685,12 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
return acc;
|
||||
}, {})
|
||||
});
|
||||
//this.render();
|
||||
}
|
||||
|
||||
static useDowntime(_, button) {
|
||||
new game.system.api.applications.dialogs.Downtime(this.document, button.dataset.type === 'shortRest').render(
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
async _onDragStart(event) {
|
||||
|
|
|
|||
|
|
@ -272,13 +272,15 @@ export default function DHApplicationMixin(Base) {
|
|||
const getAction = (target) => {
|
||||
const { actionId } = target.closest('[data-action-id]').dataset;
|
||||
const { actions, attack } = this.document.system;
|
||||
return attack.id === actionId ? attack : actions?.find(a => a.id === actionId);
|
||||
return attack?.id === actionId ? attack : actions?.find(a => a.id === actionId);
|
||||
};
|
||||
|
||||
const options = [
|
||||
{
|
||||
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.useItem',
|
||||
icon: 'fa-solid fa-burst',
|
||||
condition: this.document instanceof foundry.documents.Actor ||
|
||||
(this.document instanceof foundry.documents.Item && this.document.parent),
|
||||
callback: (target, event) => getAction(target).use(event),
|
||||
},
|
||||
{
|
||||
|
|
@ -297,7 +299,7 @@ export default function DHApplicationMixin(Base) {
|
|||
condition: (target) => {
|
||||
const { actionId } = target.closest('[data-action-id]').dataset;
|
||||
const { attack } = this.document.system;
|
||||
return attack.id !== actionId
|
||||
return attack?.id !== actionId
|
||||
},
|
||||
callback: async (target) => {
|
||||
const action = getAction(target)
|
||||
|
|
@ -313,7 +315,7 @@ export default function DHApplicationMixin(Base) {
|
|||
if (!confirmed) return;
|
||||
|
||||
return this.document.update({
|
||||
'system.actions': this.document.system.actions.do.filter((a) => a.id !== action.id)
|
||||
'system.actions': this.document.system.actions.filter((a) => a.id !== action.id)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -353,7 +355,11 @@ export default function DHApplicationMixin(Base) {
|
|||
if (deletable) options.push({
|
||||
name: 'CONTROLS.CommonDelete',
|
||||
icon: 'fa-solid fa-trash',
|
||||
callback: target => getDocFromElement(target).deleteDialog(),
|
||||
callback: (target, event) => {
|
||||
const doc = getDocFromElement(target);
|
||||
if (event.shiftKey) return doc.delete();
|
||||
else return doc.deleteDialog();
|
||||
}
|
||||
})
|
||||
|
||||
return options.map(option => ({
|
||||
|
|
@ -402,7 +408,7 @@ export default function DHApplicationMixin(Base) {
|
|||
const isAction = !!actionId;
|
||||
descriptionElement.innerHTML = await foundry.applications.ux.TextEditor.implementation.enrichHTML(description, {
|
||||
relativeTo: isAction ? doc.parent : doc,
|
||||
rollData: doc.getRollData(),
|
||||
rollData: doc.getRollData?.(),
|
||||
secrets: isAction ? doc.parent.isOwner : doc.isOwner
|
||||
});
|
||||
}
|
||||
|
|
@ -432,7 +438,7 @@ export default function DHApplicationMixin(Base) {
|
|||
type: game.i18n.localize('DAGGERHEART.GENERAL.Action.single')
|
||||
}),
|
||||
}
|
||||
});
|
||||
}) ?? {};
|
||||
if (!actionType) return;
|
||||
const cls = game.system.api.models.actions.actionsTypes[actionType]
|
||||
const action = new cls({
|
||||
|
|
@ -482,7 +488,7 @@ export default function DHApplicationMixin(Base) {
|
|||
// TODO: REDO this
|
||||
const { actionId } = target.closest('[data-action-id]').dataset;
|
||||
const { actions, attack } = this.document.system;
|
||||
const action = attack.id === actionId ? attack : actions?.find(a => a.id === actionId);
|
||||
const action = attack?.id === actionId ? attack : actions?.find(a => a.id === actionId);
|
||||
new DHActionConfig(action).render({ force: true })
|
||||
}
|
||||
|
||||
|
|
@ -490,27 +496,32 @@ export default function DHApplicationMixin(Base) {
|
|||
* Delete an embedded document.
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #deleteDoc(_event, target) {
|
||||
static async #deleteDoc(event, target) {
|
||||
const doc = getDocFromElement(target);
|
||||
|
||||
// TODO: REDO this
|
||||
if (doc) return await doc.deleteDialog()
|
||||
if (doc) {
|
||||
if (event.shiftKey) return doc.delete()
|
||||
else return await doc.deleteDialog()
|
||||
}
|
||||
|
||||
// TODO: REDO this
|
||||
const { actionId } = target.closest('[data-action-id]').dataset;
|
||||
const { actions, attack } = this.document.system;
|
||||
if (attack.id === actionId) return;
|
||||
if (attack?.id === actionId) return;
|
||||
const action = actions.find(a => a.id === actionId);
|
||||
|
||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||
window: {
|
||||
title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', {
|
||||
type: game.i18n.localize(`DAGGERHEART.GENERAL.Action.single`),
|
||||
name: action.name
|
||||
})
|
||||
},
|
||||
content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: action.name })
|
||||
});
|
||||
if (!confirmed) return;
|
||||
if (!event.shiftKey) {
|
||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||
window: {
|
||||
title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', {
|
||||
type: game.i18n.localize(`DAGGERHEART.GENERAL.Action.single`),
|
||||
name: action.name
|
||||
})
|
||||
},
|
||||
content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: action.name })
|
||||
});
|
||||
if (!confirmed) return;
|
||||
}
|
||||
|
||||
return await this.document.update({
|
||||
'system.actions': actions.filter((a) => a.id !== action.id)
|
||||
|
|
@ -528,7 +539,7 @@ export default function DHApplicationMixin(Base) {
|
|||
if (!doc) {
|
||||
const { actionId } = target.closest('[data-action-id]').dataset;
|
||||
const { actions, attack } = this.document.system;
|
||||
doc = attack.id === actionId ? attack : actions?.find(a => a.id === actionId);
|
||||
doc = attack?.id === actionId ? attack : actions?.find(a => a.id === actionId);
|
||||
}
|
||||
return doc.toChat(this.document.id);
|
||||
}
|
||||
|
|
@ -544,6 +555,7 @@ export default function DHApplicationMixin(Base) {
|
|||
const { actionId } = target.closest('[data-action-id]').dataset;
|
||||
const { actions, attack } = this.document.system;
|
||||
doc = attack?.id === actionId ? attack : actions?.find(a => a.id === actionId);
|
||||
if(this.document instanceof foundry.documents.Item && !this.document.parent) return;
|
||||
}
|
||||
|
||||
await doc.use(event);
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
|||
}
|
||||
}
|
||||
],
|
||||
dragDrop: []
|
||||
dragDrop: [{ dragSelector: '.inventory-item[data-type="attack"]', dropSelector: null }]
|
||||
};
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
|
@ -139,4 +139,27 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
|||
)
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Application Drag/Drop */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* On dragStart on the item.
|
||||
* @param {DragEvent} event - The drag event
|
||||
*/
|
||||
async _onDragStart(event) {
|
||||
const attackItem = event.currentTarget.closest('.inventory-item[data-type="attack"]');
|
||||
|
||||
if (attackItem) {
|
||||
const attackData = {
|
||||
type: 'Attack',
|
||||
actorUuid: this.document.uuid,
|
||||
img: this.document.system.attack.img,
|
||||
fromInternal: true
|
||||
};
|
||||
event.dataTransfer.setData('text/plain', JSON.stringify(attackData));
|
||||
event.dataTransfer.setDragImage(attackItem.querySelector('img'), 60, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,14 +22,14 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
|||
actions: {
|
||||
removeAction: DHBaseItemSheet.#removeAction,
|
||||
addFeature: DHBaseItemSheet.#addFeature,
|
||||
editFeature: DHBaseItemSheet.#editFeature,
|
||||
removeFeature: DHBaseItemSheet.#removeFeature,
|
||||
deleteFeature: DHBaseItemSheet.#deleteFeature,
|
||||
addResource: DHBaseItemSheet.#addResource,
|
||||
removeResource: DHBaseItemSheet.#removeResource
|
||||
},
|
||||
dragDrop: [
|
||||
{ dragSelector: null, dropSelector: '.tab.features .drop-section' },
|
||||
{ dragSelector: '.feature-item', dropSelector: null }
|
||||
{ dragSelector: '.feature-item', dropSelector: null },
|
||||
{ dragSelector: '.action-item', dropSelector: null }
|
||||
],
|
||||
contextMenus: [
|
||||
{
|
||||
|
|
@ -153,16 +153,19 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
|||
const actionIndex = button.closest('[data-index]').dataset.index;
|
||||
const action = this.document.system.actions[actionIndex];
|
||||
|
||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||
window: {
|
||||
title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', {
|
||||
type: game.i18n.localize(`DAGGERHEART.GENERAL.Action.single`),
|
||||
name: action.name
|
||||
})
|
||||
},
|
||||
content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: action.name })
|
||||
});
|
||||
if (!confirmed) return;
|
||||
if(!event.shiftKey) {
|
||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||
window: {
|
||||
title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', {
|
||||
type: game.i18n.localize(`DAGGERHEART.GENERAL.Action.single`),
|
||||
name: action.name
|
||||
})
|
||||
},
|
||||
content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: action.name })
|
||||
});
|
||||
if (!confirmed) return;
|
||||
}
|
||||
|
||||
|
||||
await this.document.update({
|
||||
'system.actions': this.document.system.actions.filter((_, index) => index !== Number.parseInt(actionIndex))
|
||||
|
|
@ -173,58 +176,31 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
|||
* Add a new feature to the item, prompting the user for its type.
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #addFeature(_event, _button) {
|
||||
static async #addFeature(_, target) {
|
||||
const { type } = target.dataset;
|
||||
const cls = foundry.documents.Item.implementation;
|
||||
const feature = await cls.create({
|
||||
type: 'feature',
|
||||
name: cls.defaultName({ type: 'feature' }),
|
||||
"system.subType": CONFIG.DH.ITEM.featureSubTypes[type]
|
||||
});
|
||||
await this.document.update({
|
||||
'system.features': [...this.document.system.features, feature]
|
||||
'system.features': [...this.document.system.features, feature].map(f => f.uuid)
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit an existing feature on the item
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #editFeature(_event, button) {
|
||||
const target = button.closest('.feature-item');
|
||||
const feature = this.document.system.features.find(x => x?.id === target.id);
|
||||
if (!feature) {
|
||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureIsMissing'));
|
||||
return;
|
||||
}
|
||||
|
||||
feature.sheet.render(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a feature from the item.
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #removeFeature(event, button) {
|
||||
event.stopPropagation();
|
||||
const target = button.closest('.feature-item');
|
||||
const feature = this.document.system.features.find(x => x && x.id === target.id);
|
||||
|
||||
if (feature) {
|
||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||
window: {
|
||||
title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', {
|
||||
type: game.i18n.localize(`TYPES.Item.feature`),
|
||||
name: feature.name
|
||||
})
|
||||
},
|
||||
content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: feature.name })
|
||||
});
|
||||
if (!confirmed) return;
|
||||
}
|
||||
|
||||
static async #deleteFeature(_, target) {
|
||||
const feature = getDocFromElement(target);
|
||||
if (!feature) return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureIsMissing'));
|
||||
await feature.update({ 'system.subType': null });
|
||||
await this.document.update({
|
||||
'system.features': this.document.system.features
|
||||
.filter(feature => feature && feature.id !== target.id)
|
||||
.map(x => x.uuid)
|
||||
.filter(uuid => uuid !== feature.uuid)
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -269,6 +245,23 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
|||
const featureData = { type: 'Item', data: { ...feature.toObject(), _id: null }, fromInternal: true };
|
||||
event.dataTransfer.setData('text/plain', JSON.stringify(featureData));
|
||||
event.dataTransfer.setDragImage(featureItem.querySelector('img'), 60, 0);
|
||||
} else {
|
||||
const actionItem = event.currentTarget.closest('.action-item');
|
||||
if (actionItem) {
|
||||
const action = this.document.system.actions[actionItem.dataset.index];
|
||||
if (!action) {
|
||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.actionIsMissing'));
|
||||
return;
|
||||
}
|
||||
|
||||
const actionData = {
|
||||
type: 'Action',
|
||||
data: { ...action.toObject(), id: action.id, itemUuid: this.document.uuid },
|
||||
fromInternal: true
|
||||
};
|
||||
event.dataTransfer.setData('text/plain', JSON.stringify(actionData));
|
||||
event.dataTransfer.setDragImage(actionItem.querySelector('img'), 60, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ export default class AncestrySheet extends DHHeritageSheet {
|
|||
static DEFAULT_OPTIONS = {
|
||||
classes: ['ancestry'],
|
||||
actions: {
|
||||
addFeature: AncestrySheet.#addFeature,
|
||||
editFeature: AncestrySheet.#editFeature,
|
||||
removeFeature: AncestrySheet.#removeFeature
|
||||
},
|
||||
|
|
@ -23,23 +22,6 @@ export default class AncestrySheet extends DHHeritageSheet {
|
|||
/* Application Clicks Actions */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Add a new feature to the item, prompting the user for its type.
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #addFeature(_event, button) {
|
||||
const feature = await game.items.documentClass.create({
|
||||
type: 'feature',
|
||||
name: game.i18n.format('DOCUMENT.New', { type: game.i18n.localize('TYPES.Item.feature') }),
|
||||
system: {
|
||||
subType: button.dataset.type
|
||||
}
|
||||
});
|
||||
await this.document.update({
|
||||
'system.features': [...this.document.system.features.map(x => x.uuid), feature.uuid]
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit an existing feature on the item
|
||||
* @type {ApplicationClickAction}
|
||||
|
|
@ -63,22 +45,8 @@ export default class AncestrySheet extends DHHeritageSheet {
|
|||
event.stopPropagation();
|
||||
const target = button.closest('.feature-item');
|
||||
const feature = this.document.system[`${target.dataset.type}Feature`];
|
||||
const featureExists = feature && Object.keys(feature).length > 0;
|
||||
|
||||
if (featureExists) {
|
||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||
window: {
|
||||
title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', {
|
||||
type: game.i18n.localize(`TYPES.Item.feature`),
|
||||
name: feature.name
|
||||
})
|
||||
},
|
||||
content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: feature.name })
|
||||
});
|
||||
if (!confirmed) return;
|
||||
}
|
||||
|
||||
if (featureExists && target.dataset.type === 'primary') await feature.update({ 'system.primary': null });
|
||||
if (feature) await feature.update({ 'system.subType': null });
|
||||
await this.document.update({
|
||||
'system.features': this.document.system.features.filter(x => x && x.uuid !== feature.uuid).map(x => x.uuid)
|
||||
});
|
||||
|
|
@ -94,15 +62,18 @@ export default class AncestrySheet extends DHHeritageSheet {
|
|||
*/
|
||||
async _onDrop(event) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||
|
||||
const item = await fromUuid(data.uuid);
|
||||
if (item?.type === 'feature') {
|
||||
const subType = event.target.closest('.primary-feature') ? 'primary' : 'secondary';
|
||||
await item.update({ 'system.subType': subType });
|
||||
if (item.system.subType && item.system.subType !== CONFIG.DH.ITEM.featureSubTypes[subType]) {
|
||||
const error = subType === 'primary' ? 'featureNotPrimary' : 'featureNotSecondary';
|
||||
ui.notifications.warn(game.i18n.localize(`DAGGERHEART.UI.Notifications.${error}`));
|
||||
return;
|
||||
}
|
||||
|
||||
await item.update({ 'system.subType': subType });
|
||||
await this.document.update({
|
||||
'system.features': [...this.document.system.features.map(x => x.uuid), item.uuid]
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,8 +10,6 @@ export default class ClassSheet extends DHBaseItemSheet {
|
|||
actions: {
|
||||
removeItemFromCollection: ClassSheet.#removeItemFromCollection,
|
||||
removeSuggestedItem: ClassSheet.#removeSuggestedItem,
|
||||
addFeature: ClassSheet.#addFeature,
|
||||
deleteFeature: ClassSheet.#deleteFeature
|
||||
},
|
||||
tagifyConfigs: [
|
||||
{
|
||||
|
|
@ -80,6 +78,7 @@ export default class ClassSheet extends DHBaseItemSheet {
|
|||
/* -------------------------------------------- */
|
||||
|
||||
async _onDrop(event) {
|
||||
event.stopPropagation();
|
||||
const data = TextEditor.getDragEventData(event);
|
||||
const item = await fromUuid(data.uuid);
|
||||
const target = event.target.closest('fieldset.drop-section');
|
||||
|
|
@ -89,12 +88,24 @@ export default class ClassSheet extends DHBaseItemSheet {
|
|||
});
|
||||
} else if (item.type === 'feature') {
|
||||
if (target.classList.contains('hope-feature')) {
|
||||
if (item.system.subType && item.system.subType !== CONFIG.DH.ITEM.featureSubTypes.hope) {
|
||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureNotHope'));
|
||||
return;
|
||||
}
|
||||
|
||||
await item.update({ 'system.subType': CONFIG.DH.ITEM.featureSubTypes.hope });
|
||||
await this.document.update({
|
||||
'system.hopeFeatures': [...this.document.system.hopeFeatures.map(x => x.uuid), item.uuid]
|
||||
'system.features': [...this.document.system.features.map(x => x.uuid), item.uuid]
|
||||
});
|
||||
} else if (target.classList.contains('class-feature')) {
|
||||
if (item.system.subType && item.system.subType !== CONFIG.DH.ITEM.featureSubTypes.class) {
|
||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureNotClass'));
|
||||
return;
|
||||
}
|
||||
|
||||
await item.update({ 'system.subType': CONFIG.DH.ITEM.featureSubTypes.class });
|
||||
await this.document.update({
|
||||
'system.classFeatures': [...this.document.system.classFeatures.map(x => x.uuid), item.uuid]
|
||||
'system.features': [...this.document.system.features.map(x => x.uuid), item.uuid]
|
||||
});
|
||||
}
|
||||
} else if (item.type === 'weapon') {
|
||||
|
|
@ -168,28 +179,4 @@ export default class ClassSheet extends DHBaseItemSheet {
|
|||
const { target } = element.dataset;
|
||||
await this.document.update({ [`system.characterGuide.${target}`]: null });
|
||||
}
|
||||
|
||||
static async #addFeature(_, target) {
|
||||
const { actionPath } = target.dataset;
|
||||
const cls = foundry.documents.Item.implementation;
|
||||
|
||||
const feature = await cls.create({
|
||||
type: 'feature',
|
||||
name: cls.defaultName({ type: 'feature' }),
|
||||
});
|
||||
|
||||
await this.document.update({
|
||||
[`system.${actionPath}`]: [
|
||||
...this.document.system[actionPath],
|
||||
feature.uuid
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
static async #deleteFeature(_, button) {
|
||||
const { actionPath, itemUuid } = button.dataset;
|
||||
await this.document.update({
|
||||
[`system.${actionPath}`]: this.document.system[actionPath].filter(f => f.uuid !== itemUuid)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ export default class CommunitySheet extends DHHeritageSheet {
|
|||
static PARTS = {
|
||||
header: { template: 'systems/daggerheart/templates/sheets/items/community/header.hbs' },
|
||||
...super.PARTS,
|
||||
feature: {
|
||||
features: {
|
||||
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-features.hbs',
|
||||
scrollable: ['.feature']
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,10 +6,7 @@ export default class SubclassSheet extends DHBaseItemSheet {
|
|||
classes: ['subclass'],
|
||||
position: { width: 600 },
|
||||
window: { resizable: false },
|
||||
actions: {
|
||||
addFeature: this.addFeature,
|
||||
deleteFeature: this.deleteFeature
|
||||
}
|
||||
actions: {}
|
||||
};
|
||||
|
||||
/**@override */
|
||||
|
|
@ -40,24 +37,6 @@ export default class SubclassSheet extends DHBaseItemSheet {
|
|||
}
|
||||
};
|
||||
|
||||
static async addFeature(_, target) {
|
||||
const cls = foundry.documents.Item.implementation;
|
||||
const feature = await cls.create({
|
||||
type: 'feature',
|
||||
name: cls.defaultName({ type: 'feature' }),
|
||||
});
|
||||
|
||||
await this.document.update({
|
||||
[`system.${target.dataset.type}`]: feature
|
||||
});
|
||||
}
|
||||
|
||||
static async deleteFeature(_, button) {
|
||||
await this.document.update({
|
||||
[`system.${button.dataset.actionPath}`]: null
|
||||
});
|
||||
}
|
||||
|
||||
async _onDragStart(event) {
|
||||
const featureItem = event.currentTarget.closest('.drop-section');
|
||||
|
||||
|
|
@ -75,18 +54,45 @@ export default class SubclassSheet extends DHBaseItemSheet {
|
|||
}
|
||||
|
||||
async _onDrop(event) {
|
||||
event.stopPropagation();
|
||||
|
||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||
if (data.fromInternal) return;
|
||||
|
||||
const item = await fromUuid(data.uuid);
|
||||
if (item?.type === 'feature') {
|
||||
const dropSection = event.target.closest('.drop-section');
|
||||
if (this.document.system[dropSection.dataset.type]) {
|
||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.notifications.featureIsFull'));
|
||||
return;
|
||||
}
|
||||
const target = event.target.closest('fieldset.drop-section');
|
||||
if (item.type === 'feature') {
|
||||
if (target.dataset.type === 'foundation') {
|
||||
if (item.system.subType && item.system.subType !== CONFIG.DH.ITEM.featureSubTypes.foundation) {
|
||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureNotFoundation'));
|
||||
return;
|
||||
}
|
||||
|
||||
await this.document.update({ [`system.${dropSection.dataset.type}`]: item.uuid });
|
||||
await item.update({ 'system.subType': CONFIG.DH.ITEM.featureSubTypes.foundation });
|
||||
await this.document.update({
|
||||
'system.features': [...this.document.system.features.map(x => x.uuid), item.uuid]
|
||||
});
|
||||
} else if (target.dataset.type === 'specialization') {
|
||||
if (item.system.subType && item.system.subType !== CONFIG.DH.ITEM.featureSubTypes.specialization) {
|
||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureNotSpecialization'));
|
||||
return;
|
||||
}
|
||||
|
||||
await item.update({ 'system.subType': CONFIG.DH.ITEM.featureSubTypes.specialization });
|
||||
await this.document.update({
|
||||
'system.features': [...this.document.system.features.map(x => x.uuid), item.uuid]
|
||||
});
|
||||
} else if (target.dataset.type === 'mastery') {
|
||||
if (item.system.subType && item.system.subType !== CONFIG.DH.ITEM.featureSubTypes.mastery) {
|
||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureNotMastery'));
|
||||
return;
|
||||
}
|
||||
|
||||
await item.update({ 'system.subType': CONFIG.DH.ITEM.featureSubTypes.mastery });
|
||||
await this.document.update({
|
||||
'system.features': [...this.document.system.features.map(x => x.uuid), item.uuid]
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,3 +2,4 @@ export { default as DhChatLog } from './chatLog.mjs';
|
|||
export { default as DhCombatTracker } from './combatTracker.mjs';
|
||||
export * as DhCountdowns from './countdowns.mjs';
|
||||
export { default as DhFearTracker } from './fearTracker.mjs';
|
||||
export { default as DhHotbar } from './hotbar.mjs';
|
||||
|
|
|
|||
|
|
@ -14,8 +14,6 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
}
|
||||
|
||||
addChatListeners = async (app, html, data) => {
|
||||
super.addChatListeners(app, html, data);
|
||||
|
||||
html.querySelectorAll('.duality-action-damage').forEach(element =>
|
||||
element.addEventListener('click', event => this.onRollDamage(event, data.message))
|
||||
);
|
||||
|
|
@ -90,7 +88,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
onRollDamage = async (event, message) => {
|
||||
event.stopPropagation();
|
||||
const actor = await this.getActor(message.system.source.actor);
|
||||
if (!actor || !game.user.isGM) return true;
|
||||
if (game.user.character?.id !== actor.id && !game.user.isGM) return true;
|
||||
if (message.system.source.item && message.system.source.action) {
|
||||
const action = this.getAction(actor, message.system.source.item, message.system.source.action);
|
||||
if (!action || !action?.rollDamage) return;
|
||||
|
|
|
|||
|
|
@ -66,6 +66,11 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
|||
}
|
||||
|
||||
async setCombatantSpotlight(combatantId) {
|
||||
const update = {
|
||||
system: {
|
||||
'spotlight.requesting': false
|
||||
}
|
||||
};
|
||||
const combatant = this.viewed.combatants.get(combatantId);
|
||||
|
||||
const toggleTurn = this.viewed.combatants.contents
|
||||
|
|
@ -73,10 +78,18 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
|||
.map(x => x.id)
|
||||
.indexOf(combatantId);
|
||||
|
||||
if (this.viewed.turn !== toggleTurn) Hooks.callAll(CONFIG.DH.HOOKS.spotlight, {});
|
||||
if (this.viewed.turn !== toggleTurn) {
|
||||
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
|
||||
await updateCountdowns(CONFIG.DH.GENERAL.countdownTypes.spotlight.id);
|
||||
|
||||
const autoPoints = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).actionPoints;
|
||||
if (autoPoints) {
|
||||
update.system.actionTokens = Math.max(combatant.system.actionTokens - 1, 0);
|
||||
}
|
||||
}
|
||||
|
||||
await this.viewed.update({ turn: this.viewed.turn === toggleTurn ? null : toggleTurn });
|
||||
await combatant.update({ 'system.spotlight.requesting': false });
|
||||
await combatant.update(update);
|
||||
}
|
||||
|
||||
static async requestSpotlight(_, target) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { countdownTypes } from '../../config/generalConfig.mjs';
|
||||
import { GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||
import constructHTMLButton from '../../helpers/utils.mjs';
|
||||
import OwnershipSelection from '../dialogs/ownershipSelection.mjs';
|
||||
|
|
@ -328,43 +327,29 @@ export class EncounterCountdowns extends Countdowns {
|
|||
};
|
||||
}
|
||||
|
||||
export const registerCountdownApplicationHooks = () => {
|
||||
const updateCountdowns = async shouldProgress => {
|
||||
if (game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).countdowns) {
|
||||
const countdownSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||
for (let countdownCategoryKey in countdownSetting) {
|
||||
const countdownCategory = countdownSetting[countdownCategoryKey];
|
||||
for (let countdownKey in countdownCategory.countdowns) {
|
||||
const countdown = countdownCategory.countdowns[countdownKey];
|
||||
|
||||
if (shouldProgress(countdown)) {
|
||||
await countdownSetting.updateSource({
|
||||
[`${countdownCategoryKey}.countdowns.${countdownKey}.progress.current`]:
|
||||
countdown.progress.current - 1
|
||||
});
|
||||
await game.settings.set(
|
||||
CONFIG.DH.id,
|
||||
CONFIG.DH.SETTINGS.gameSettings.Countdowns,
|
||||
countdownSetting
|
||||
);
|
||||
foundry.applications.instances.get(`${countdownCategoryKey}-countdowns`)?.render();
|
||||
}
|
||||
export async function updateCountdowns(progressType) {
|
||||
const countdownSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||
const update = Object.keys(countdownSetting).reduce((update, typeKey) => {
|
||||
return foundry.utils.mergeObject(
|
||||
update,
|
||||
Object.keys(countdownSetting[typeKey].countdowns).reduce((acc, countdownKey) => {
|
||||
const countdown = countdownSetting[typeKey].countdowns[countdownKey];
|
||||
if (countdown.progress.current > 0 && countdown.progress.type.value === progressType) {
|
||||
acc[`${typeKey}.countdowns.${countdownKey}.progress.current`] = countdown.progress.current - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Hooks.on(CONFIG.DH.HOOKS.characterAttack, async () => {
|
||||
updateCountdowns(countdown => {
|
||||
return (
|
||||
countdown.progress.type.value === countdownTypes.characterAttack.id && countdown.progress.current > 0
|
||||
);
|
||||
});
|
||||
});
|
||||
return acc;
|
||||
}, {})
|
||||
);
|
||||
}, {});
|
||||
|
||||
Hooks.on(CONFIG.DH.HOOKS.spotlight, async () => {
|
||||
updateCountdowns(countdown => {
|
||||
return countdown.progress.type.value === countdownTypes.spotlight.id && countdown.progress.current > 0;
|
||||
});
|
||||
await countdownSetting.updateSource(update);
|
||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, countdownSetting);
|
||||
|
||||
const data = { refreshType: RefreshType.Countdown };
|
||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||
action: socketEvent.Refresh,
|
||||
data
|
||||
});
|
||||
};
|
||||
Hooks.callAll(socketEvent.Refresh, data);
|
||||
}
|
||||
|
|
|
|||
129
module/applications/ui/hotbar.mjs
Normal file
129
module/applications/ui/hotbar.mjs
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
export default class DhHotbar extends foundry.applications.ui.Hotbar {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
this.setupHooks();
|
||||
}
|
||||
|
||||
static async useItem(uuid) {
|
||||
const item = await fromUuid(uuid);
|
||||
if (!item) {
|
||||
return ui.notifications.warn('WARNING.ObjectDoesNotExist', {
|
||||
format: {
|
||||
name: game.i18n.localize('Document'),
|
||||
identifier: uuid
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await item.use({});
|
||||
}
|
||||
|
||||
static async useAction(itemUuid, actionId) {
|
||||
const item = await foundry.utils.fromUuid(itemUuid);
|
||||
if (!item) {
|
||||
return ui.notifications.warn('WARNING.ObjectDoesNotExist', {
|
||||
format: {
|
||||
name: game.i18n.localize('Document'),
|
||||
identifier: itemUuid
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const action = item.system.actions.find(x => x.id === actionId);
|
||||
if (!action) {
|
||||
return ui.notifications.warn('DAGGERHEART.UI.Notifications.actionIsMissing');
|
||||
}
|
||||
|
||||
await action.use({});
|
||||
}
|
||||
|
||||
static async useAttack(actorUuid) {
|
||||
const actor = await foundry.utils.fromUuid(actorUuid);
|
||||
if (!actor) {
|
||||
return ui.notifications.warn('WARNING.ObjectDoesNotExist', {
|
||||
format: {
|
||||
name: game.i18n.localize('Document'),
|
||||
identifier: actorUuid
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const attack = actor.system.attack;
|
||||
if (!attack) {
|
||||
return ui.notifications.warn('DAGGERHEART.UI.Notifications.attackIsMissing');
|
||||
}
|
||||
|
||||
await attack.use({});
|
||||
}
|
||||
|
||||
setupHooks() {
|
||||
Hooks.on('hotbarDrop', (bar, data, slot) => {
|
||||
if (data.type === 'Item') {
|
||||
const item = foundry.utils.fromUuidSync(data.uuid);
|
||||
if (item.uuid.startsWith('Compendium') || !item.isOwned || !item.isOwner) return true;
|
||||
|
||||
switch (item.type) {
|
||||
case 'ancestry':
|
||||
case 'community':
|
||||
case 'class':
|
||||
case 'subclass':
|
||||
return true;
|
||||
default:
|
||||
this.createItemMacro(item, slot);
|
||||
return false;
|
||||
}
|
||||
} else if (data.type === 'Action') {
|
||||
const item = foundry.utils.fromUuidSync(data.data.itemUuid);
|
||||
if (item.uuid.startsWith('Compendium')) return true;
|
||||
if (!item.isOwned || !item.isOwner) {
|
||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.unownedActionMacro'));
|
||||
return false;
|
||||
}
|
||||
|
||||
this.createActionMacro(data, slot);
|
||||
return false;
|
||||
} else if (data.type === 'Attack') {
|
||||
const actor = foundry.utils.fromUuidSync(data.actorUuid);
|
||||
if (actor.uuid.startsWith('Compendium')) return true;
|
||||
if (!actor.isOwner) {
|
||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.unownedAttackMacro'));
|
||||
return false;
|
||||
}
|
||||
|
||||
this.createAttackMacro(data, slot);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async createItemMacro(data, slot) {
|
||||
const macro = await Macro.implementation.create({
|
||||
name: `${game.i18n.localize('Display')} ${name}`,
|
||||
type: CONST.MACRO_TYPES.SCRIPT,
|
||||
img: data.img,
|
||||
command: `await game.system.api.applications.ui.DhHotbar.useItem("${data.uuid}");`
|
||||
});
|
||||
await game.user.assignHotbarMacro(macro, slot);
|
||||
}
|
||||
|
||||
async createActionMacro(data, slot) {
|
||||
const macro = await Macro.implementation.create({
|
||||
name: `${game.i18n.localize('Display')} ${name}`,
|
||||
type: CONST.MACRO_TYPES.SCRIPT,
|
||||
img: data.data.img,
|
||||
command: `await game.system.api.applications.ui.DhHotbar.useAction("${data.data.itemUuid}", "${data.data.id}");`
|
||||
});
|
||||
await game.user.assignHotbarMacro(macro, slot);
|
||||
}
|
||||
|
||||
async createAttackMacro(data, slot) {
|
||||
const macro = await Macro.implementation.create({
|
||||
name: `${game.i18n.localize('Display')} ${name}`,
|
||||
type: CONST.MACRO_TYPES.SCRIPT,
|
||||
img: data.img,
|
||||
command: `await game.system.api.applications.ui.DhHotbar.useAttack("${data.actorUuid}");`
|
||||
});
|
||||
await game.user.assignHotbarMacro(macro, slot);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
export { default as DhMeasuredTemplate } from './measuredTemplate.mjs';
|
||||
export { default as DhRuler } from './ruler.mjs';
|
||||
export { default as DhTemplateLayer } from './templateLayer.mjs';
|
||||
export { default as DhTokenPlaceable } from './token.mjs';
|
||||
export { default as DhTokenRuler } from './tokenRuler.mjs';
|
||||
|
|
|
|||
36
module/canvas/placeables/token.mjs
Normal file
36
module/canvas/placeables/token.mjs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
|
||||
/** @inheritDoc */
|
||||
async _drawEffects() {
|
||||
this.effects.renderable = false;
|
||||
|
||||
// Clear Effects Container
|
||||
this.effects.removeChildren().forEach(c => c.destroy());
|
||||
this.effects.bg = this.effects.addChild(new PIXI.Graphics());
|
||||
this.effects.bg.zIndex = -1;
|
||||
this.effects.overlay = null;
|
||||
|
||||
// Categorize effects
|
||||
const activeEffects = this.actor ? Array.from(this.actor.effects).filter(x => !x.disabled) : [];
|
||||
const overlayEffect = activeEffects.findLast(e => e.img && e.getFlag('core', 'overlay'));
|
||||
|
||||
// Draw effects
|
||||
const promises = [];
|
||||
for (const [i, effect] of activeEffects.entries()) {
|
||||
if (!effect.img) continue;
|
||||
const promise =
|
||||
effect === overlayEffect
|
||||
? this._drawOverlay(effect.img, effect.tint)
|
||||
: this._drawEffect(effect.img, effect.tint);
|
||||
promises.push(
|
||||
promise.then(e => {
|
||||
if (e) e.zIndex = i;
|
||||
})
|
||||
);
|
||||
}
|
||||
await Promise.allSettled(promises);
|
||||
|
||||
this.effects.sortChildren();
|
||||
this.effects.renderable = true;
|
||||
this.renderFlags.set({ refreshEffects: true });
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,6 @@ export * as domainConfig from './domainConfig.mjs';
|
|||
export * as effectConfig from './effectConfig.mjs';
|
||||
export * as flagsConfig from './flagsConfig.mjs';
|
||||
export * as generalConfig from './generalConfig.mjs';
|
||||
export * as hooksConfig from './hooksConfig.mjs';
|
||||
export * as itemConfig from './itemConfig.mjs';
|
||||
export * as settingsConfig from './settingsConfig.mjs';
|
||||
export * as systemConfig from './system.mjs';
|
||||
|
|
|
|||
|
|
@ -411,7 +411,7 @@ export const levelupData = {
|
|||
};
|
||||
|
||||
export const subclassFeatureLabels = {
|
||||
1: 'DAGGERHEART.ITEMS.DomainCard.foundation',
|
||||
1: 'DAGGERHEART.ITEMS.DomainCard.foundationTitle',
|
||||
2: 'DAGGERHEART.ITEMS.DomainCard.specializationTitle',
|
||||
3: 'DAGGERHEART.ITEMS.DomainCard.masteryTitle'
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,55 +3,55 @@ export const domains = {
|
|||
id: 'arcana',
|
||||
label: 'DAGGERHEART.GENERAL.Domain.arcana.label',
|
||||
src: 'systems/daggerheart/assets/icons/domains/arcana.svg',
|
||||
description: 'DAGGERHEART.GENERAL.Domain.Arcana'
|
||||
description: 'DAGGERHEART.GENERAL.Domain.arcana.description'
|
||||
},
|
||||
blade: {
|
||||
id: 'blade',
|
||||
label: 'DAGGERHEART.GENERAL.Domain.blade.label',
|
||||
src: 'systems/daggerheart/assets/icons/domains/blade.svg',
|
||||
description: 'DAGGERHEART.GENERAL.Domain.Blade'
|
||||
description: 'DAGGERHEART.GENERAL.Domain.blade.description'
|
||||
},
|
||||
bone: {
|
||||
id: 'bone',
|
||||
label: 'DAGGERHEART.GENERAL.Domain.bone.label',
|
||||
src: 'systems/daggerheart/assets/icons/domains/bone.svg',
|
||||
description: 'DAGGERHEART.GENERAL.Domain.Bone'
|
||||
description: 'DAGGERHEART.GENERAL.Domain.bone.description'
|
||||
},
|
||||
codex: {
|
||||
id: 'codex',
|
||||
label: 'DAGGERHEART.GENERAL.Domain.codex.label',
|
||||
src: 'systems/daggerheart/assets/icons/domains/codex.svg',
|
||||
description: 'DAGGERHEART.GENERAL.Domain.Codex'
|
||||
description: 'DAGGERHEART.GENERAL.Domain.codex.description'
|
||||
},
|
||||
grace: {
|
||||
id: 'grace',
|
||||
label: 'DAGGERHEART.GENERAL.Domain.grace.label',
|
||||
src: 'systems/daggerheart/assets/icons/domains/grace.svg',
|
||||
description: 'DAGGERHEART.GENERAL.Domain.Grace'
|
||||
description: 'DAGGERHEART.GENERAL.Domain.grace.description'
|
||||
},
|
||||
midnight: {
|
||||
id: 'midnight',
|
||||
label: 'DAGGERHEART.GENERAL.Domain.midnight.label',
|
||||
src: 'systems/daggerheart/assets/icons/domains/midnight.svg',
|
||||
description: 'DAGGERHEART.GENERAL.Domain.Midnight'
|
||||
description: 'DAGGERHEART.GENERAL.Domain.midnight.description'
|
||||
},
|
||||
sage: {
|
||||
id: 'sage',
|
||||
label: 'DAGGERHEART.GENERAL.Domain.sage.label',
|
||||
src: 'systems/daggerheart/assets/icons/domains/sage.svg',
|
||||
description: 'DAGGERHEART.GENERAL.Domain.Sage'
|
||||
description: 'DAGGERHEART.GENERAL.Domain.sage.description'
|
||||
},
|
||||
splendor: {
|
||||
id: 'splendor',
|
||||
label: 'DAGGERHEART.GENERAL.Domain.splendor.label',
|
||||
src: 'systems/daggerheart/assets/icons/domains/splendor.svg',
|
||||
description: 'DAGGERHEART.GENERAL.Domain.Splendor'
|
||||
description: 'DAGGERHEART.GENERAL.Domain.splendor.description'
|
||||
},
|
||||
valor: {
|
||||
id: 'valor',
|
||||
label: 'DAGGERHEART.GENERAL.Domain.valor.label',
|
||||
src: 'systems/daggerheart/assets/icons/domains/valor.svg',
|
||||
description: 'DAGGERHEART.GENERAL.Domain.Valor'
|
||||
description: 'DAGGERHEART.GENERAL.Domain.valor.description'
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -110,6 +110,18 @@ export const conditions = {
|
|||
name: 'DAGGERHEART.CONFIG.Condition.restrained.name',
|
||||
icon: 'icons/magic/control/debuff-chains-shackle-movement-red.webp',
|
||||
description: 'DAGGERHEART.CONFIG.Condition.restrained.description'
|
||||
},
|
||||
unconcious: {
|
||||
id: 'unconcious',
|
||||
name: 'DAGGERHEART.CONFIG.Condition.unconcious.name',
|
||||
icon: 'icons/magic/control/sleep-bubble-purple.webp',
|
||||
description: 'DAGGERHEART.CONFIG.Condition.unconcious.description'
|
||||
},
|
||||
dead: {
|
||||
id: 'dead',
|
||||
name: 'DAGGERHEART.CONFIG.Condition.dead.name',
|
||||
icon: 'icons/magic/death/grave-tombstone-glow-teal.webp',
|
||||
description: 'DAGGERHEART.CONFIG.Condition.dead.description'
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -118,6 +130,7 @@ export const defaultRestOptions = {
|
|||
tendToWounds: {
|
||||
id: 'tendToWounds',
|
||||
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.tendToWounds.name'),
|
||||
icon: 'fa-solid fa-bandage',
|
||||
img: 'icons/magic/life/cross-worn-green.webp',
|
||||
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.tendToWounds.description'),
|
||||
actions: [
|
||||
|
|
@ -141,6 +154,7 @@ export const defaultRestOptions = {
|
|||
clearStress: {
|
||||
id: 'clearStress',
|
||||
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.clearStress.name'),
|
||||
icon: 'fa-regular fa-face-surprise',
|
||||
img: 'icons/magic/perception/eye-ringed-green.webp',
|
||||
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.clearStress.description'),
|
||||
actions: [
|
||||
|
|
@ -164,6 +178,7 @@ export const defaultRestOptions = {
|
|||
repairArmor: {
|
||||
id: 'repairArmor',
|
||||
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.repairArmor.name'),
|
||||
icon: 'fa-solid fa-hammer',
|
||||
img: 'icons/skills/trades/smithing-anvil-silver-red.webp',
|
||||
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.repairArmor.description'),
|
||||
actions: []
|
||||
|
|
@ -171,6 +186,7 @@ export const defaultRestOptions = {
|
|||
prepare: {
|
||||
id: 'prepare',
|
||||
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.prepare.name'),
|
||||
icon: 'fa-solid fa-dumbbell',
|
||||
img: 'icons/skills/trades/academics-merchant-scribe.webp',
|
||||
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.prepare.description'),
|
||||
actions: []
|
||||
|
|
@ -180,6 +196,7 @@ export const defaultRestOptions = {
|
|||
tendToWounds: {
|
||||
id: 'tendToWounds',
|
||||
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.tendToWounds.name'),
|
||||
icon: 'fa-solid fa-bandage',
|
||||
img: 'icons/magic/life/cross-worn-green.webp',
|
||||
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.tendToWounds.description'),
|
||||
actions: []
|
||||
|
|
@ -187,6 +204,7 @@ export const defaultRestOptions = {
|
|||
clearStress: {
|
||||
id: 'clearStress',
|
||||
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.clearStress.name'),
|
||||
icon: 'fa-regular fa-face-surprise',
|
||||
img: 'icons/magic/perception/eye-ringed-green.webp',
|
||||
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.clearStress.description'),
|
||||
actions: []
|
||||
|
|
@ -194,6 +212,7 @@ export const defaultRestOptions = {
|
|||
repairArmor: {
|
||||
id: 'repairArmor',
|
||||
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.repairArmor.name'),
|
||||
icon: 'fa-solid fa-hammer',
|
||||
img: 'icons/skills/trades/smithing-anvil-silver-red.webp',
|
||||
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.repairArmor.description'),
|
||||
actions: []
|
||||
|
|
@ -201,6 +220,7 @@ export const defaultRestOptions = {
|
|||
prepare: {
|
||||
id: 'prepare',
|
||||
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.prepare.name'),
|
||||
icon: 'fa-solid fa-dumbbell',
|
||||
img: 'icons/skills/trades/academics-merchant-scribe.webp',
|
||||
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.prepare.description'),
|
||||
actions: []
|
||||
|
|
@ -208,19 +228,12 @@ export const defaultRestOptions = {
|
|||
workOnAProject: {
|
||||
id: 'workOnAProject',
|
||||
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.workOnAProject.name'),
|
||||
icon: 'fa-solid fa-diagram-project',
|
||||
img: 'icons/skills/social/thumbsup-approval-like.webp',
|
||||
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.workOnAProject.description'),
|
||||
actions: []
|
||||
}
|
||||
}),
|
||||
custom: {
|
||||
id: 'customActivity',
|
||||
name: '',
|
||||
img: 'icons/skills/trades/academics-investigation-puzzles.webp',
|
||||
description: '',
|
||||
namePlaceholder: 'DAGGERHEART.APPLICATIONS.Downtime.custom.namePlaceholder',
|
||||
placeholder: 'DAGGERHEART.APPLICATIONS.Downtime.custom.placeholder'
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
export const deathMoves = {
|
||||
|
|
@ -376,29 +389,29 @@ export const abilityCosts = {
|
|||
export const countdownTypes = {
|
||||
spotlight: {
|
||||
id: 'spotlight',
|
||||
label: 'DAGGERHEART.CONFIG.CountdownTypes.Spotlight'
|
||||
label: 'DAGGERHEART.CONFIG.CountdownType.spotlight'
|
||||
},
|
||||
characterAttack: {
|
||||
id: 'characterAttack',
|
||||
label: 'DAGGERHEART.CONFIG.CountdownTypes.CharacterAttack'
|
||||
label: 'DAGGERHEART.CONFIG.CountdownType.characterAttack'
|
||||
},
|
||||
custom: {
|
||||
id: 'custom',
|
||||
label: 'DAGGERHEART.CONFIG.CountdownTypes.Custom'
|
||||
label: 'DAGGERHEART.CONFIG.CountdownType.custom'
|
||||
}
|
||||
};
|
||||
export const rollTypes = {
|
||||
weapon: {
|
||||
id: 'weapon',
|
||||
label: 'DAGGERHEART.CONFIG.RollTypes.weapon.name'
|
||||
attack: {
|
||||
id: 'attack',
|
||||
label: 'DAGGERHEART.CONFIG.RollTypes.attack.name'
|
||||
},
|
||||
spellcast: {
|
||||
id: 'spellcast',
|
||||
label: 'DAGGERHEART.CONFIG.RollTypes.spellcast.name'
|
||||
},
|
||||
ability: {
|
||||
id: 'ability',
|
||||
label: 'DAGGERHEART.CONFIG.RollTypes.ability.name'
|
||||
trait: {
|
||||
id: 'trait',
|
||||
label: 'DAGGERHEART.CONFIG.RollTypes.trait.name'
|
||||
},
|
||||
diceSet: {
|
||||
id: 'diceSet',
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
export const hooks = {
|
||||
characterAttack: 'characterAttackHook',
|
||||
spotlight: 'spotlightHook'
|
||||
};
|
||||
|
|
@ -439,7 +439,7 @@ export const weaponFeatures = {
|
|||
{
|
||||
key: 'system.bonuses.damage.primaryWeapon.bonus',
|
||||
mode: 2,
|
||||
value: '@system.levelData.levels.current'
|
||||
value: '@system.levelData.level.current'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1261,15 +1261,6 @@ export const weaponFeatures = {
|
|||
timebending: {
|
||||
label: 'DAGGERHEART.CONFIG.WeaponFeature.timebending.name',
|
||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.timebending.description'
|
||||
},
|
||||
versatile: {
|
||||
label: 'DAGGERHEART.CONFIG.WeaponFeature.versatile.name',
|
||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.versatile.description'
|
||||
// versatile: {
|
||||
// characterTrait: '',
|
||||
// range: '',
|
||||
// damage: ''
|
||||
// }
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -1322,7 +1313,12 @@ export const featureTypes = {
|
|||
|
||||
export const featureSubTypes = {
|
||||
primary: 'primary',
|
||||
secondary: 'secondary'
|
||||
secondary: 'secondary',
|
||||
hope: 'hope',
|
||||
class: 'class',
|
||||
foundation: 'foundation',
|
||||
specialization: 'specialization',
|
||||
mastery: 'mastery'
|
||||
};
|
||||
|
||||
export const actionTypes = {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import * as DOMAIN from './domainConfig.mjs';
|
|||
import * as ACTOR from './actorConfig.mjs';
|
||||
import * as ITEM from './itemConfig.mjs';
|
||||
import * as SETTINGS from './settingsConfig.mjs';
|
||||
import { hooks as HOOKS } from './hooksConfig.mjs';
|
||||
import * as EFFECTS from './effectConfig.mjs';
|
||||
import * as ACTIONS from './actionConfig.mjs';
|
||||
import * as FLAGS from './flagsConfig.mjs';
|
||||
|
|
@ -17,7 +16,6 @@ export const SYSTEM = {
|
|||
ACTOR,
|
||||
ITEM,
|
||||
SETTINGS,
|
||||
HOOKS,
|
||||
EFFECTS,
|
||||
ACTIONS,
|
||||
FLAGS
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ export default class DHAttackAction extends DHDamageAction {
|
|||
static extraSchemas = [...super.extraSchemas, ...['roll', 'save']];
|
||||
|
||||
static getRollType(parent) {
|
||||
return parent.type === 'weapon' ? 'weapon' : 'spellcast';
|
||||
return parent.type === 'weapon' ? 'attack' : 'spellcast';
|
||||
}
|
||||
|
||||
get chatTemplate() {
|
||||
|
|
@ -21,7 +21,7 @@ export default class DHAttackAction extends DHDamageAction {
|
|||
}
|
||||
if (this.roll.useDefault) {
|
||||
this.roll.trait = this.item.system.attack.roll.trait;
|
||||
this.roll.type = 'weapon';
|
||||
this.roll.type = 'attack';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -37,4 +37,17 @@ export default class DHAttackAction extends DHDamageAction {
|
|||
base: true
|
||||
};
|
||||
}
|
||||
|
||||
async use(event, ...args) {
|
||||
const result = await super.use(event, args);
|
||||
|
||||
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
|
||||
await updateCountdowns(CONFIG.DH.GENERAL.countdownTypes.characterAttack.id);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// get modifiers() {
|
||||
// return [];
|
||||
// }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
|||
}
|
||||
|
||||
static getRollType(parent) {
|
||||
return 'ability';
|
||||
return 'trait';
|
||||
}
|
||||
|
||||
static getSourceConfig(parent) {
|
||||
|
|
@ -268,7 +268,8 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
|||
hasDamage: !!this.damage?.parts?.length,
|
||||
hasHealing: !!this.healing,
|
||||
hasEffect: !!this.effects?.length,
|
||||
hasSave: this.hasSave
|
||||
hasSave: this.hasSave,
|
||||
selectedRollMode: game.settings.get('core', 'rollMode')
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -308,7 +309,7 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
|||
|
||||
prepareRoll() {
|
||||
const roll = {
|
||||
modifiers: [],
|
||||
modifiers: this.modifiers,
|
||||
trait: this.roll?.trait,
|
||||
label: 'Attack',
|
||||
type: this.actionType,
|
||||
|
|
@ -362,6 +363,13 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
|||
get hasRoll() {
|
||||
return !!this.roll?.type || !!this.roll?.bonus;
|
||||
}
|
||||
|
||||
get modifiers() {
|
||||
if (!this.actor) return [];
|
||||
const modifiers = [];
|
||||
/** Placeholder for specific bonuses **/
|
||||
return modifiers;
|
||||
}
|
||||
/* ROLL */
|
||||
|
||||
/* SAVE */
|
||||
|
|
|
|||
|
|
@ -6,10 +6,20 @@ export default class DHDamageAction extends DHBaseAction {
|
|||
getFormulaValue(part, data) {
|
||||
let formulaValue = part.value;
|
||||
if (this.hasRoll && part.resultBased && data.system.roll.result.duality === -1) return part.valueAlt;
|
||||
|
||||
const isAdversary = this.actor.type === 'adversary';
|
||||
if (isAdversary && this.actor.system.type === CONFIG.DH.ACTOR.adversaryTypes.horde.id) {
|
||||
const hasHordeDamage = this.actor.effects.find(
|
||||
x => x.name === game.i18n.localize('DAGGERHEART.CONFIG.AdversaryType.horde.label')
|
||||
);
|
||||
if (hasHordeDamage) return part.valueAlt;
|
||||
}
|
||||
|
||||
return formulaValue;
|
||||
}
|
||||
|
||||
async rollDamage(event, data) {
|
||||
const systemData = data.system ?? data;
|
||||
let formula = this.damage.parts.map(p => this.getFormulaValue(p, data).getFormula(this.actor)).join(' + '),
|
||||
damageTypes = [...new Set(this.damage.parts.reduce((a, c) => a.concat([...c.type]), []))];
|
||||
|
||||
|
|
@ -19,15 +29,16 @@ export default class DHDamageAction extends DHBaseAction {
|
|||
let roll = { formula: formula, total: formula },
|
||||
bonusDamage = [];
|
||||
|
||||
if (isNaN(formula)) formula = Roll.replaceFormulaData(formula, this.getRollData(data.system ?? data));
|
||||
if (isNaN(formula)) formula = Roll.replaceFormulaData(formula, this.getRollData(systemData));
|
||||
|
||||
const config = {
|
||||
title: game.i18n.format('DAGGERHEART.UI.Chat.damageRoll.title', { damage: this.name }),
|
||||
roll: { formula },
|
||||
targets: data.system?.targets.filter(t => t.hit) ?? data.targets,
|
||||
targets: systemData.targets.filter(t => t.hit) ?? data.targets,
|
||||
hasSave: this.hasSave,
|
||||
isCritical: data.system?.roll?.isCritical ?? false,
|
||||
source: data.system?.source,
|
||||
isCritical: systemData.roll?.isCritical ?? false,
|
||||
source: systemData.source,
|
||||
data: this.getRollData(),
|
||||
damageTypes,
|
||||
event
|
||||
};
|
||||
|
|
@ -35,8 +46,14 @@ export default class DHDamageAction extends DHBaseAction {
|
|||
if (data.system) {
|
||||
config.source.message = data._id;
|
||||
config.directDamage = false;
|
||||
} else {
|
||||
config.directDamage = true;
|
||||
}
|
||||
|
||||
roll = CONFIG.Dice.daggerheart.DamageRoll.build(config);
|
||||
}
|
||||
|
||||
// get modifiers() {
|
||||
// return [];
|
||||
// }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,4 +39,8 @@ export default class DHHealingAction extends DHBaseAction {
|
|||
get chatTemplate() {
|
||||
return 'systems/daggerheart/templates/ui/chat/healing-roll.hbs';
|
||||
}
|
||||
|
||||
get modifiers() {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,7 @@
|
|||
import DHAdversarySettings from '../../applications/sheets-configs/adversary-settings.mjs';
|
||||
import ActionField from '../fields/actionField.mjs';
|
||||
import BaseDataActor from './base.mjs';
|
||||
|
||||
const resourceField = () =>
|
||||
new foundry.data.fields.SchemaField({
|
||||
value: new foundry.data.fields.NumberField({ initial: 0, integer: true }),
|
||||
max: new foundry.data.fields.NumberField({ initial: 0, integer: true }),
|
||||
isReversed: new foundry.data.fields.BooleanField({ initial: true })
|
||||
});
|
||||
import { resourceField, bonusField } from '../fields/actorField.mjs';
|
||||
|
||||
export default class DhpAdversary extends BaseDataActor {
|
||||
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Adversary'];
|
||||
|
|
@ -37,14 +31,29 @@ export default class DhpAdversary extends BaseDataActor {
|
|||
motivesAndTactics: new fields.StringField(),
|
||||
notes: new fields.HTMLField(),
|
||||
difficulty: new fields.NumberField({ required: true, initial: 1, integer: true }),
|
||||
hordeHp: new fields.NumberField({ required: true, initial: 1, integer: true }),
|
||||
hordeHp: new fields.NumberField({
|
||||
required: true,
|
||||
initial: 1,
|
||||
integer: true,
|
||||
label: 'DAGGERHEART.GENERAL.hordeHp'
|
||||
}),
|
||||
damageThresholds: new fields.SchemaField({
|
||||
major: new fields.NumberField({ required: true, initial: 0, integer: true }),
|
||||
severe: new fields.NumberField({ required: true, initial: 0, integer: true })
|
||||
major: new fields.NumberField({
|
||||
required: true,
|
||||
initial: 0,
|
||||
integer: true,
|
||||
label: 'DAGGERHEART.GENERAL.DamageThresholds.majorThreshold'
|
||||
}),
|
||||
severe: new fields.NumberField({
|
||||
required: true,
|
||||
initial: 0,
|
||||
integer: true,
|
||||
label: 'DAGGERHEART.GENERAL.DamageThresholds.severeThreshold'
|
||||
})
|
||||
}),
|
||||
resources: new fields.SchemaField({
|
||||
hitPoints: resourceField(),
|
||||
stress: resourceField()
|
||||
hitPoints: resourceField(0, 'DAGGERHEART.GENERAL.hitPoints.plural', true),
|
||||
stress: resourceField(0, 'DAGGERHEART.GENERAL.stress', true)
|
||||
}),
|
||||
attack: new ActionField({
|
||||
initial: {
|
||||
|
|
@ -59,7 +68,7 @@ export default class DhpAdversary extends BaseDataActor {
|
|||
amount: 1
|
||||
},
|
||||
roll: {
|
||||
type: 'weapon'
|
||||
type: 'attack'
|
||||
},
|
||||
damage: {
|
||||
parts: [
|
||||
|
|
@ -80,9 +89,14 @@ export default class DhpAdversary extends BaseDataActor {
|
|||
})
|
||||
),
|
||||
bonuses: new fields.SchemaField({
|
||||
difficulty: new fields.SchemaField({
|
||||
all: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
reaction: new fields.NumberField({ integer: true, initial: 0 })
|
||||
roll: new fields.SchemaField({
|
||||
attack: bonusField('DAGGERHEART.GENERAL.Roll.attack'),
|
||||
action: bonusField('DAGGERHEART.GENERAL.Roll.action'),
|
||||
reaction: bonusField('DAGGERHEART.GENERAL.Roll.reaction')
|
||||
}),
|
||||
damage: new fields.SchemaField({
|
||||
physical: bonusField('DAGGERHEART.GENERAL.Damage.physicalDamage'),
|
||||
magical: bonusField('DAGGERHEART.GENERAL.Damage.magicalDamage')
|
||||
})
|
||||
})
|
||||
};
|
||||
|
|
@ -95,4 +109,37 @@ export default class DhpAdversary extends BaseDataActor {
|
|||
get features() {
|
||||
return this.parent.items.filter(x => x.type === 'feature');
|
||||
}
|
||||
|
||||
async _preUpdate(changes, options, user) {
|
||||
const allowed = await super._preUpdate(changes, options, user);
|
||||
if (allowed === false) return false;
|
||||
|
||||
if (this.type === CONFIG.DH.ACTOR.adversaryTypes.horde.id) {
|
||||
if (changes.system?.resources?.hitPoints?.value) {
|
||||
const halfHP = Math.ceil(this.resources.hitPoints.max / 2);
|
||||
const newHitPoints = changes.system.resources.hitPoints.value;
|
||||
const previouslyAboveHalf = this.resources.hitPoints.value < halfHP;
|
||||
const loweredBelowHalf = previouslyAboveHalf && newHitPoints >= halfHP;
|
||||
const raisedAboveHalf = !previouslyAboveHalf && newHitPoints < halfHP;
|
||||
if (loweredBelowHalf) {
|
||||
await this.parent.createEmbeddedDocuments('ActiveEffect', [
|
||||
{
|
||||
name: game.i18n.localize('DAGGERHEART.CONFIG.AdversaryType.horde.label'),
|
||||
img: 'icons/magic/movement/chevrons-down-yellow.webp',
|
||||
disabled: !game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation)
|
||||
.hordeDamage
|
||||
}
|
||||
]);
|
||||
} else if (raisedAboveHalf) {
|
||||
const hordeEffects = this.parent.effects.filter(
|
||||
x => x.name === game.i18n.localize('DAGGERHEART.CONFIG.AdversaryType.horde.label')
|
||||
);
|
||||
await this.parent.deleteEmbeddedDocuments(
|
||||
'ActiveEffect',
|
||||
hordeEffects.map(x => x.id)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import DHBaseActorSettings from '../../applications/sheets/api/actor-setting.mjs';
|
||||
|
||||
const resistanceField = () =>
|
||||
const resistanceField = reductionLabel =>
|
||||
new foundry.data.fields.SchemaField({
|
||||
resistance: new foundry.data.fields.BooleanField({ initial: false }),
|
||||
immunity: new foundry.data.fields.BooleanField({ initial: false }),
|
||||
reduction: new foundry.data.fields.NumberField({ integer: true, initial: 0 })
|
||||
reduction: new foundry.data.fields.NumberField({ integer: true, initial: 0, label: reductionLabel })
|
||||
});
|
||||
|
||||
/**
|
||||
|
|
@ -40,8 +40,8 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
|
|||
if (this.metadata.isNPC) schema.description = new fields.HTMLField({ required: true, nullable: true });
|
||||
if (this.metadata.hasResistances)
|
||||
schema.resistance = new fields.SchemaField({
|
||||
physical: resistanceField(),
|
||||
magical: resistanceField()
|
||||
physical: resistanceField('DAGGERHEART.GENERAL.DamageResistance.physicalReduction'),
|
||||
magical: resistanceField('DAGGERHEART.GENERAL.DamageResistance.magicalReduction')
|
||||
});
|
||||
return schema;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,27 +2,11 @@ import { burden } from '../../config/generalConfig.mjs';
|
|||
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
||||
import DhLevelData from '../levelData.mjs';
|
||||
import BaseDataActor from './base.mjs';
|
||||
|
||||
const attributeField = () =>
|
||||
new foundry.data.fields.SchemaField({
|
||||
value: new foundry.data.fields.NumberField({ initial: 0, integer: true }),
|
||||
tierMarked: new foundry.data.fields.BooleanField({ initial: false })
|
||||
});
|
||||
|
||||
const resourceField = (max, reverse = false) =>
|
||||
new foundry.data.fields.SchemaField({
|
||||
value: new foundry.data.fields.NumberField({ initial: 0, integer: true }),
|
||||
max: new foundry.data.fields.NumberField({ initial: max, integer: true }),
|
||||
isReversed: new foundry.data.fields.BooleanField({ initial: reverse })
|
||||
});
|
||||
|
||||
const stressDamageReductionRule = () =>
|
||||
new foundry.data.fields.SchemaField({
|
||||
enabled: new foundry.data.fields.BooleanField({ required: true, initial: false }),
|
||||
cost: new foundry.data.fields.NumberField({ integer: true })
|
||||
});
|
||||
import { attributeField, resourceField, stressDamageReductionRule, bonusField } from '../fields/actorField.mjs';
|
||||
|
||||
export default class DhCharacter extends BaseDataActor {
|
||||
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Character'];
|
||||
|
||||
static get metadata() {
|
||||
return foundry.utils.mergeObject(super.metadata, {
|
||||
label: 'TYPES.Actor.character',
|
||||
|
|
@ -37,24 +21,36 @@ export default class DhCharacter extends BaseDataActor {
|
|||
return {
|
||||
...super.defineSchema(),
|
||||
resources: new fields.SchemaField({
|
||||
hitPoints: resourceField(0, true),
|
||||
stress: resourceField(6, true),
|
||||
hope: resourceField(6)
|
||||
hitPoints: resourceField(0, 'DAGGERHEART.GENERAL.hitPoints.plural', true),
|
||||
stress: resourceField(6, 'DAGGERHEART.GENERAL.stress', true),
|
||||
hope: resourceField(6, 'DAGGERHEART.GENERAL.hope')
|
||||
}),
|
||||
traits: new fields.SchemaField({
|
||||
agility: attributeField(),
|
||||
strength: attributeField(),
|
||||
finesse: attributeField(),
|
||||
instinct: attributeField(),
|
||||
presence: attributeField(),
|
||||
knowledge: attributeField()
|
||||
agility: attributeField('DAGGERHEART.CONFIG.Traits.agility.name'),
|
||||
strength: attributeField('DAGGERHEART.CONFIG.Traits.strength.name'),
|
||||
finesse: attributeField('DAGGERHEART.CONFIG.Traits.finesse.name'),
|
||||
instinct: attributeField('DAGGERHEART.CONFIG.Traits.instinct.name'),
|
||||
presence: attributeField('DAGGERHEART.CONFIG.Traits.presence.name'),
|
||||
knowledge: attributeField('DAGGERHEART.CONFIG.Traits.knowledge.name')
|
||||
}),
|
||||
proficiency: new fields.NumberField({ initial: 1, integer: true }),
|
||||
evasion: new fields.NumberField({ initial: 0, integer: true }),
|
||||
armorScore: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
proficiency: new fields.NumberField({
|
||||
initial: 1,
|
||||
integer: true,
|
||||
label: 'DAGGERHEART.GENERAL.proficiency'
|
||||
}),
|
||||
evasion: new fields.NumberField({ initial: 0, integer: true, label: 'DAGGERHEART.GENERAL.evasion' }),
|
||||
armorScore: new fields.NumberField({ integer: true, initial: 0, label: 'DAGGERHEART.GENERAL.armorScore' }),
|
||||
damageThresholds: new fields.SchemaField({
|
||||
severe: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
major: new fields.NumberField({ integer: true, initial: 0 })
|
||||
severe: new fields.NumberField({
|
||||
integer: true,
|
||||
initial: 0,
|
||||
label: 'DAGGERHEART.GENERAL.DamageThresholds.majorThreshold'
|
||||
}),
|
||||
major: new fields.NumberField({
|
||||
integer: true,
|
||||
initial: 0,
|
||||
label: 'DAGGERHEART.GENERAL.DamageThresholds.severeThreshold'
|
||||
})
|
||||
}),
|
||||
experiences: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
|
|
@ -91,24 +87,82 @@ export default class DhCharacter extends BaseDataActor {
|
|||
value: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }),
|
||||
subclass: new ForeignDocumentUUIDField({ type: 'Item', nullable: true })
|
||||
}),
|
||||
advantageSources: new fields.ArrayField(new fields.StringField()),
|
||||
disadvantageSources: new fields.ArrayField(new fields.StringField()),
|
||||
levelData: new fields.EmbeddedDataField(DhLevelData),
|
||||
bonuses: new fields.SchemaField({
|
||||
roll: new fields.SchemaField({
|
||||
attack: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
primaryWeapon: new fields.SchemaField({
|
||||
attack: new fields.NumberField({ integer: true, initial: 0 })
|
||||
}),
|
||||
spellcast: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
action: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
hopeOrFear: new fields.NumberField({ integer: true, initial: 0 })
|
||||
attack: bonusField('DAGGERHEART.GENERAL.Roll.attack'),
|
||||
spellcast: bonusField('DAGGERHEART.GENERAL.Roll.spellcast'),
|
||||
trait: bonusField('DAGGERHEART.GENERAL.Roll.trait'),
|
||||
action: bonusField('DAGGERHEART.GENERAL.Roll.action'),
|
||||
reaction: bonusField('DAGGERHEART.GENERAL.Roll.reaction'),
|
||||
primaryWeapon: bonusField('DAGGERHEART.GENERAL.Roll.primaryWeaponAttack'),
|
||||
secondaryWeapon: bonusField('DAGGERHEART.GENERAL.Roll.secondaryWeaponAttack')
|
||||
}),
|
||||
damage: new fields.SchemaField({
|
||||
all: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
physical: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
magic: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
primaryWeapon: new fields.SchemaField({
|
||||
bonus: new fields.NumberField({ integer: true }),
|
||||
extraDice: new fields.NumberField({ integer: true })
|
||||
physical: bonusField('DAGGERHEART.GENERAL.Damage.physicalDamage'),
|
||||
magical: bonusField('DAGGERHEART.GENERAL.Damage.magicalDamage'),
|
||||
primaryWeapon: bonusField('DAGGERHEART.GENERAL.Damage.primaryWeapon'),
|
||||
secondaryWeapon: bonusField('DAGGERHEART.GENERAL.Damage.secondaryWeapon')
|
||||
}),
|
||||
healing: bonusField('DAGGERHEART.GENERAL.Healing.healingAmount'),
|
||||
range: new fields.SchemaField({
|
||||
weapon: new fields.NumberField({
|
||||
integer: true,
|
||||
initial: 0,
|
||||
label: 'DAGGERHEART.GENERAL.Range.weapon'
|
||||
}),
|
||||
spell: new fields.NumberField({
|
||||
integer: true,
|
||||
initial: 0,
|
||||
label: 'DAGGERHEART.GENERAL.Range.spell'
|
||||
}),
|
||||
other: new fields.NumberField({
|
||||
integer: true,
|
||||
initial: 0,
|
||||
label: 'DAGGERHEART.GENERAL.Range.other'
|
||||
})
|
||||
}),
|
||||
rally: new fields.ArrayField(new fields.StringField(), {
|
||||
label: 'DAGGERHEART.CLASS.Feature.rallyDice'
|
||||
}),
|
||||
rest: new fields.SchemaField({
|
||||
shortRest: new fields.SchemaField({
|
||||
shortMoves: new fields.NumberField({
|
||||
required: true,
|
||||
integer: true,
|
||||
min: 0,
|
||||
initial: 0,
|
||||
label: 'DAGGERHEART.GENERAL.Bonuses.rest.shortRest.shortRestMoves.label',
|
||||
hint: 'DAGGERHEART.GENERAL.Bonuses.rest.shortRest.shortRestMoves.hint'
|
||||
}),
|
||||
longMoves: new fields.NumberField({
|
||||
required: true,
|
||||
integer: true,
|
||||
min: 0,
|
||||
initial: 0,
|
||||
label: 'DAGGERHEART.GENERAL.Bonuses.rest.shortRest.longRestMoves.label',
|
||||
hint: 'DAGGERHEART.GENERAL.Bonuses.rest.shortRest.longRestMoves.hint'
|
||||
})
|
||||
}),
|
||||
longRest: new fields.SchemaField({
|
||||
shortMoves: new fields.NumberField({
|
||||
required: true,
|
||||
integer: true,
|
||||
min: 0,
|
||||
initial: 0,
|
||||
label: 'DAGGERHEART.GENERAL.Bonuses.rest.longRest.shortRestMoves.label',
|
||||
hint: 'DAGGERHEART.GENERAL.Bonuses.rest.longRest.shortRestMoves.hint'
|
||||
}),
|
||||
longMoves: new fields.NumberField({
|
||||
required: true,
|
||||
integer: true,
|
||||
min: 0,
|
||||
initial: 0,
|
||||
label: 'DAGGERHEART.GENERAL.Bonuses.rest.longRest.longRestMoves.label',
|
||||
hint: 'DAGGERHEART.GENERAL.Bonuses.rest.longRest.longRestMoves.hint'
|
||||
})
|
||||
})
|
||||
})
|
||||
}),
|
||||
|
|
@ -117,25 +171,34 @@ export default class DhCharacter extends BaseDataActor {
|
|||
damageReduction: new fields.SchemaField({
|
||||
maxArmorMarked: new fields.SchemaField({
|
||||
value: new fields.NumberField({ required: true, integer: true, initial: 1 }),
|
||||
bonus: new fields.NumberField({ required: true, integer: true, initial: 0 }),
|
||||
stressExtra: new fields.NumberField({ required: true, integer: true, initial: 0 })
|
||||
bonus: new fields.NumberField({
|
||||
required: true,
|
||||
integer: true,
|
||||
initial: 0,
|
||||
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedBonus'
|
||||
}),
|
||||
stressExtra: new fields.NumberField({
|
||||
required: true,
|
||||
integer: true,
|
||||
initial: 0,
|
||||
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedStress.label',
|
||||
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedStress.hint'
|
||||
})
|
||||
}),
|
||||
stressDamageReduction: new fields.SchemaField({
|
||||
severe: stressDamageReductionRule(),
|
||||
major: stressDamageReductionRule(),
|
||||
minor: stressDamageReductionRule()
|
||||
severe: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.severe'),
|
||||
major: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.major'),
|
||||
minor: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.minor')
|
||||
}),
|
||||
increasePerArmorMark: new fields.NumberField({
|
||||
integer: true,
|
||||
initial: 1,
|
||||
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.label',
|
||||
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.hint'
|
||||
}),
|
||||
increasePerArmorMark: new fields.NumberField({ integer: true, initial: 1 }),
|
||||
magical: new fields.BooleanField({ initial: false }),
|
||||
physical: new fields.BooleanField({ initial: false })
|
||||
}),
|
||||
strangePatterns: new fields.NumberField({
|
||||
integer: true,
|
||||
min: 1,
|
||||
max: 12,
|
||||
nullable: true,
|
||||
initial: null
|
||||
}),
|
||||
weapon: new fields.SchemaField({
|
||||
/* Unimplemented
|
||||
-> Should remove the lowest damage dice from weapon damage
|
||||
|
|
@ -181,6 +244,11 @@ export default class DhCharacter extends BaseDataActor {
|
|||
return !this.class.value || !this.class.subclass;
|
||||
}
|
||||
|
||||
get spellcastModifier() {
|
||||
const subClasses = this.parent.items.filter(x => x.type === 'subclass') ?? [];
|
||||
return Math.max(subClasses?.map(sc => this.traits[sc.system.spellcastingTrait]?.value));
|
||||
}
|
||||
|
||||
get spellcastingModifiers() {
|
||||
return {
|
||||
main: this.class.subclass?.system?.spellcastingTrait,
|
||||
|
|
@ -219,23 +287,23 @@ export default class DhCharacter extends BaseDataActor {
|
|||
features = [];
|
||||
|
||||
for (let item of this.parent.items) {
|
||||
if (item.system.type === CONFIG.DH.ITEM.featureTypes.ancestry.id) {
|
||||
if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.ancestry.id) {
|
||||
ancestryFeatures.push(item);
|
||||
} else if (item.system.type === CONFIG.DH.ITEM.featureTypes.community.id) {
|
||||
} else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.community.id) {
|
||||
communityFeatures.push(item);
|
||||
} else if (item.system.type === CONFIG.DH.ITEM.featureTypes.class.id) {
|
||||
} else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.class.id) {
|
||||
classFeatures.push(item);
|
||||
} else if (item.system.type === CONFIG.DH.ITEM.featureTypes.subclass.id) {
|
||||
} else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.subclass.id) {
|
||||
const subclassState = this.class.subclass.system.featureState;
|
||||
const identifier = item.system.identifier;
|
||||
const subType = item.system.subType;
|
||||
if (
|
||||
identifier === 'foundationFeature' ||
|
||||
(identifier === 'specializationFeature' && subclassState >= 2) ||
|
||||
(identifier === 'masterFeature' && subclassState >= 3)
|
||||
subType === CONFIG.DH.ITEM.featureSubTypes.foundation ||
|
||||
(subType === CONFIG.DH.ITEM.featureSubTypes.specialization && subclassState >= 2) ||
|
||||
(subType === CONFIG.DH.ITEM.featureSubTypes.mastery && subclassState >= 3)
|
||||
) {
|
||||
subclassFeatures.push(item);
|
||||
}
|
||||
} else if (item.system.type === CONFIG.DH.ITEM.featureTypes.companion.id) {
|
||||
} else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.companion.id) {
|
||||
companionFeatures.push(item);
|
||||
} else if (item.type === 'feature' && !item.system.type) {
|
||||
features.push(item);
|
||||
|
|
@ -323,6 +391,8 @@ export default class DhCharacter extends BaseDataActor {
|
|||
}
|
||||
|
||||
prepareBaseData() {
|
||||
this.evasion = this.class.value?.system?.evasion ?? 0;
|
||||
|
||||
const currentLevel = this.levelData.level.current;
|
||||
const currentTier =
|
||||
currentLevel === 1
|
||||
|
|
@ -380,7 +450,8 @@ export default class DhCharacter extends BaseDataActor {
|
|||
}
|
||||
|
||||
prepareDerivedData() {
|
||||
this.resources.hope.value = Math.min(this.resources.hope.value, this.resources.hope.max);
|
||||
const baseHope = this.resources.hope.value + (this.companion?.system?.resources?.hope ?? 0);
|
||||
this.resources.hope.value = Math.min(baseHope, this.resources.hope.max);
|
||||
}
|
||||
|
||||
getRollData() {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
|||
import ActionField from '../fields/actionField.mjs';
|
||||
import { adjustDice, adjustRange } from '../../helpers/utils.mjs';
|
||||
import DHCompanionSettings from '../../applications/sheets-configs/companion-settings.mjs';
|
||||
import { resourceField, bonusField } from '../fields/actorField.mjs';
|
||||
|
||||
export default class DhCompanion extends BaseDataActor {
|
||||
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Companion'];
|
||||
|
|
@ -23,14 +24,16 @@ export default class DhCompanion extends BaseDataActor {
|
|||
...super.defineSchema(),
|
||||
partner: new ForeignDocumentUUIDField({ type: 'Actor' }),
|
||||
resources: new fields.SchemaField({
|
||||
stress: new fields.SchemaField({
|
||||
value: new fields.NumberField({ initial: 0, integer: true }),
|
||||
max: new fields.NumberField({ initial: 3, integer: true }),
|
||||
isReversed: new foundry.data.fields.BooleanField({ initial: true })
|
||||
}),
|
||||
hope: new fields.NumberField({ initial: 0, integer: true })
|
||||
stress: resourceField(3, 'DAGGERHEART.GENERAL.stress', true),
|
||||
hope: new fields.NumberField({ initial: 0, integer: true, label: 'DAGGERHEART.GENERAL.hope' })
|
||||
}),
|
||||
evasion: new fields.NumberField({
|
||||
required: true,
|
||||
min: 1,
|
||||
initial: 10,
|
||||
integer: true,
|
||||
label: 'DAGGERHEART.GENERAL.evasion'
|
||||
}),
|
||||
evasion: new fields.NumberField({ required: true, min: 1, initial: 10, integer: true }),
|
||||
experiences: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
name: new fields.StringField({}),
|
||||
|
|
@ -56,9 +59,8 @@ export default class DhCompanion extends BaseDataActor {
|
|||
amount: 1
|
||||
},
|
||||
roll: {
|
||||
type: 'weapon',
|
||||
bonus: 0,
|
||||
trait: 'instinct'
|
||||
type: 'attack',
|
||||
bonus: 0
|
||||
},
|
||||
damage: {
|
||||
parts: [
|
||||
|
|
@ -74,13 +76,13 @@ export default class DhCompanion extends BaseDataActor {
|
|||
}
|
||||
}),
|
||||
actions: new fields.ArrayField(new ActionField()),
|
||||
levelData: new fields.EmbeddedDataField(DhLevelData)
|
||||
};
|
||||
}
|
||||
|
||||
get traits() {
|
||||
return {
|
||||
instinct: { value: this.attack.roll.bonus }
|
||||
levelData: new fields.EmbeddedDataField(DhLevelData),
|
||||
bonuses: new fields.SchemaField({
|
||||
damage: new fields.SchemaField({
|
||||
physical: bonusField('DAGGERHEART.GENERAL.Damage.physicalDamage'),
|
||||
magical: bonusField('DAGGERHEART.GENERAL.Damage.magicalDamage')
|
||||
})
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -89,9 +91,7 @@ export default class DhCompanion extends BaseDataActor {
|
|||
}
|
||||
|
||||
prepareBaseData() {
|
||||
const partnerSpellcastingModifier = this.partner?.system?.spellcastingModifiers?.main;
|
||||
const spellcastingModifier = this.partner?.system?.traits?.[partnerSpellcastingModifier]?.value;
|
||||
this.attack.roll.bonus = spellcastingModifier ?? 0; // Needs to expand on which modifier it is that should be used because of multiclassing;
|
||||
this.attack.roll.bonus = this.partner?.system?.spellcastModifier ?? 0;
|
||||
|
||||
for (let levelKey in this.levelData.levelups) {
|
||||
const level = this.levelData.levelups[levelKey];
|
||||
|
|
@ -124,12 +124,6 @@ export default class DhCompanion extends BaseDataActor {
|
|||
}
|
||||
}
|
||||
|
||||
prepareDerivedData() {
|
||||
if (this.partner) {
|
||||
this.partner.system.resources.hope.max += this.resources.hope;
|
||||
}
|
||||
}
|
||||
|
||||
async _preDelete() {
|
||||
if (this.partner) {
|
||||
await this.partner.update({ 'system.companion': null });
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ class DhCountdown extends foundry.abstract.DataModel {
|
|||
value: new fields.StringField({
|
||||
required: true,
|
||||
choices: CONFIG.DH.GENERAL.countdownTypes,
|
||||
initial: CONFIG.DH.GENERAL.countdownTypes.spotlight.id,
|
||||
initial: CONFIG.DH.GENERAL.countdownTypes.custom.id,
|
||||
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.progress.type.value.label'
|
||||
}),
|
||||
label: new fields.StringField({
|
||||
|
|
@ -132,7 +132,13 @@ class DhCountdown extends foundry.abstract.DataModel {
|
|||
export const registerCountdownHooks = () => {
|
||||
Hooks.on(socketEvent.Refresh, ({ refreshType, application }) => {
|
||||
if (refreshType === RefreshType.Countdown) {
|
||||
foundry.applications.instances.get(application)?.render();
|
||||
if (application) {
|
||||
foundry.applications.instances.get(application)?.render();
|
||||
} else {
|
||||
foundry.applications.instances.get('narrative-countdowns')?.render();
|
||||
foundry.applications.instances.get('encounter-countdowns')?.render();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
|
|
|||
35
module/data/fields/actorField.mjs
Normal file
35
module/data/fields/actorField.mjs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
const fields = foundry.data.fields;
|
||||
|
||||
const attributeField = label =>
|
||||
new fields.SchemaField({
|
||||
value: new fields.NumberField({ initial: 0, integer: true, label }),
|
||||
tierMarked: new fields.BooleanField({ initial: false })
|
||||
});
|
||||
|
||||
const resourceField = (max = 0, label, reverse = false) =>
|
||||
new fields.SchemaField({
|
||||
value: new fields.NumberField({ initial: 0, integer: true, label }),
|
||||
max: new fields.NumberField({ initial: max, integer: true }),
|
||||
isReversed: new fields.BooleanField({ initial: reverse })
|
||||
});
|
||||
|
||||
const stressDamageReductionRule = localizationPath =>
|
||||
new fields.SchemaField({
|
||||
enabled: new fields.BooleanField({ required: true, initial: false }),
|
||||
cost: new fields.NumberField({
|
||||
integer: true,
|
||||
label: `${localizationPath}.label`,
|
||||
hint: `${localizationPath}.hint`
|
||||
})
|
||||
});
|
||||
|
||||
const bonusField = label =>
|
||||
new fields.SchemaField({
|
||||
bonus: new fields.NumberField({ integer: true, initial: 0, label: `${game.i18n.localize(label)} Value` }),
|
||||
dice: new fields.ArrayField(
|
||||
new fields.StringField(),
|
||||
{ label: `${game.i18n.localize(label)} Dice` }
|
||||
)
|
||||
});
|
||||
|
||||
export { attributeField, resourceField, stressDamageReductionRule, bonusField };
|
||||
|
|
@ -53,11 +53,14 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
|||
}),
|
||||
diceStates: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
value: new fields.NumberField({ integer: true, nullable: true, initial: null }),
|
||||
value: new fields.NumberField({ integer: true, initial: 1, min: 1 }),
|
||||
used: new fields.BooleanField({ initial: false })
|
||||
})
|
||||
),
|
||||
dieFaces: new fields.StringField({ initial: '4' })
|
||||
dieFaces: new fields.StringField({
|
||||
choices: CONFIG.DH.GENERAL.diceTypes,
|
||||
initial: CONFIG.DH.GENERAL.diceTypes.d4
|
||||
})
|
||||
},
|
||||
{ nullable: true, initial: null }
|
||||
);
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ export default class DHBeastform extends BaseDataItem {
|
|||
|
||||
const beastformEffect = this.parent.effects.find(x => x.type === 'beastform');
|
||||
await beastformEffect.updateSource({
|
||||
changes: [...beastformEffect.changes, { key: 'system.advantageSources', mode: 2, value: this.advantageOn }],
|
||||
system: {
|
||||
characterTokenData: {
|
||||
tokenImg: this.parent.parent.prototypeToken.texture.src,
|
||||
|
|
|
|||
|
|
@ -24,11 +24,10 @@ export default class DHClass extends BaseDataItem {
|
|||
integer: true,
|
||||
min: 1,
|
||||
initial: 5,
|
||||
label: 'DAGGERHEART.GENERAL.hitPoints'
|
||||
label: 'DAGGERHEART.GENERAL.hitPoints.plural'
|
||||
}),
|
||||
evasion: new fields.NumberField({ initial: 0, integer: true, label: 'DAGGERHEART.GENERAL.evasion' }),
|
||||
hopeFeatures: new ForeignDocumentUUIDArrayField({ type: 'Item' }),
|
||||
classFeatures: new ForeignDocumentUUIDArrayField({ type: 'Item' }),
|
||||
features: new ForeignDocumentUUIDArrayField({ type: 'Item' }),
|
||||
subclasses: new ForeignDocumentUUIDArrayField({ type: 'Item', required: false }),
|
||||
inventory: new fields.SchemaField({
|
||||
take: new ForeignDocumentUUIDArrayField({ type: 'Item', required: false }),
|
||||
|
|
@ -52,12 +51,18 @@ export default class DHClass extends BaseDataItem {
|
|||
};
|
||||
}
|
||||
|
||||
get hopeFeature() {
|
||||
return this.hopeFeatures.length > 0 ? this.hopeFeatures[0] : null;
|
||||
get hopeFeatures() {
|
||||
return (
|
||||
this.features.filter(x => x?.system?.subType === CONFIG.DH.ITEM.featureSubTypes.hope) ??
|
||||
(this.features.filter(x => !x).length > 0 ? {} : null)
|
||||
);
|
||||
}
|
||||
|
||||
get features() {
|
||||
return [...this.hopeFeatures.filter(x => x), ...this.classFeatures.filter(x => x)];
|
||||
get classFeatures() {
|
||||
return (
|
||||
this.features.filter(x => x?.system?.subType === CONFIG.DH.ITEM.featureSubTypes.class) ??
|
||||
(this.features.filter(x => !x).length > 0 ? {} : null)
|
||||
);
|
||||
}
|
||||
|
||||
async _preCreate(data, options, user) {
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ export default class DHDomainCard extends BaseDataItem {
|
|||
required: true,
|
||||
initial: CONFIG.DH.DOMAIN.cardTypes.ability.id
|
||||
}),
|
||||
foundation: new fields.BooleanField({ initial: false }),
|
||||
inVault: new fields.BooleanField({ initial: false }),
|
||||
actions: new fields.ArrayField(new ActionField())
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
||||
import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs';
|
||||
import BaseDataItem from './base.mjs';
|
||||
|
||||
export default class DHSubclass extends BaseDataItem {
|
||||
|
|
@ -22,20 +22,22 @@ export default class DHSubclass extends BaseDataItem {
|
|||
nullable: true,
|
||||
initial: null
|
||||
}),
|
||||
foundationFeature: new ForeignDocumentUUIDField({ type: 'Item' }),
|
||||
specializationFeature: new ForeignDocumentUUIDField({ type: 'Item' }),
|
||||
masteryFeature: new ForeignDocumentUUIDField({ type: 'Item' }),
|
||||
features: new ForeignDocumentUUIDArrayField({ type: 'Item' }),
|
||||
featureState: new fields.NumberField({ required: true, initial: 1, min: 1 }),
|
||||
isMulticlass: new fields.BooleanField({ initial: false })
|
||||
};
|
||||
}
|
||||
|
||||
get features() {
|
||||
return [
|
||||
{ ...this.foundationFeature?.toObject(), identifier: 'foundationFeature' },
|
||||
{ ...this.specializationFeature?.toObject(), identifier: 'specializationFeature' },
|
||||
{ ...this.masteryFeature?.toObject(), identifier: 'masteryFeature' }
|
||||
];
|
||||
get foundationFeatures() {
|
||||
return this.features.filter(x => x.system.subType === CONFIG.DH.ITEM.featureSubTypes.foundation);
|
||||
}
|
||||
|
||||
get specializationFeatures() {
|
||||
return this.features.filter(x => x.system.subType === CONFIG.DH.ITEM.featureSubTypes.specialization);
|
||||
}
|
||||
|
||||
get masteryFeatures() {
|
||||
return this.features.filter(x => x.system.subType === CONFIG.DH.ITEM.featureSubTypes.mastery);
|
||||
}
|
||||
|
||||
async _preCreate(data, options, user) {
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ export default class DHWeapon extends AttachableItem {
|
|||
},
|
||||
roll: {
|
||||
trait: 'agility',
|
||||
type: 'weapon'
|
||||
type: 'attack'
|
||||
},
|
||||
damage: {
|
||||
parts: [
|
||||
|
|
|
|||
|
|
@ -1,23 +1,28 @@
|
|||
export default class DhAutomation extends foundry.abstract.DataModel {
|
||||
static LOCALIZATION_PREFIXES = ['DAGGERHEART.SETTINGS.Automation']; // Doesn't work for some reason
|
||||
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
hope: new fields.BooleanField({
|
||||
required: true,
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.hope.label'
|
||||
}), // Label need to be updated into something like "Duality Roll Auto Gain" + a hint
|
||||
hopeFear: new fields.SchemaField({
|
||||
gm: new fields.BooleanField({
|
||||
required: true,
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.hopeFear.gm.label'
|
||||
}),
|
||||
players: new fields.BooleanField({
|
||||
required: true,
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.hopeFear.players.label'
|
||||
})
|
||||
}),
|
||||
actionPoints: new fields.BooleanField({
|
||||
required: true,
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.actionPoints.label'
|
||||
}),
|
||||
countdowns: new fields.BooleanField({
|
||||
requireD: true,
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.countdowns.label'
|
||||
hordeDamage: new fields.BooleanField({
|
||||
required: true,
|
||||
initial: true,
|
||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.hordeDamage.label'
|
||||
})
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ export default class DhHomebrew extends foundry.abstract.DataModel {
|
|||
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'],
|
||||
|
|
@ -70,6 +71,7 @@ export default class DhHomebrew extends foundry.abstract.DataModel {
|
|||
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'],
|
||||
|
|
|
|||
|
|
@ -39,11 +39,13 @@ export default class D20Roll extends DHRoll {
|
|||
}
|
||||
|
||||
get hasAdvantage() {
|
||||
return this.options.roll.advantage === this.constructor.ADV_MODE.ADVANTAGE;
|
||||
const adv = this.options.roll.advantage.type ?? this.options.roll.advantage;
|
||||
return adv === this.constructor.ADV_MODE.ADVANTAGE;
|
||||
}
|
||||
|
||||
get hasDisadvantage() {
|
||||
return this.options.roll.advantage === this.constructor.ADV_MODE.DISADVANTAGE;
|
||||
const adv = this.options.roll.advantage.type ?? this.options.roll.advantage;
|
||||
return adv === this.constructor.ADV_MODE.DISADVANTAGE;
|
||||
}
|
||||
|
||||
static applyKeybindings(config) {
|
||||
|
|
@ -74,7 +76,6 @@ export default class D20Roll extends DHRoll {
|
|||
}
|
||||
|
||||
constructFormula(config) {
|
||||
// this.terms = [];
|
||||
this.createBaseDice();
|
||||
this.configureModifiers();
|
||||
this.resetFormula();
|
||||
|
|
@ -91,7 +92,10 @@ export default class D20Roll extends DHRoll {
|
|||
|
||||
configureModifiers() {
|
||||
this.applyAdvantage();
|
||||
this.applyBaseBonus();
|
||||
|
||||
this.baseTerms = foundry.utils.deepClone(this.dice);
|
||||
|
||||
this.options.roll.modifiers = this.applyBaseBonus();
|
||||
|
||||
this.options.experiences?.forEach(m => {
|
||||
if (this.options.data.experiences?.[m])
|
||||
|
|
@ -101,12 +105,7 @@ export default class D20Roll extends DHRoll {
|
|||
});
|
||||
});
|
||||
|
||||
this.options.roll.modifiers?.forEach(m => {
|
||||
this.terms.push(...this.formatModifier(m.value));
|
||||
});
|
||||
|
||||
this.baseTerms = foundry.utils.deepClone(this.terms);
|
||||
|
||||
this.addModifiers();
|
||||
if (this.options.extraFormula) {
|
||||
this.terms.push(
|
||||
new foundry.dice.terms.OperatorTerm({ operator: '+' }),
|
||||
|
|
@ -125,13 +124,20 @@ export default class D20Roll extends DHRoll {
|
|||
}
|
||||
|
||||
applyBaseBonus() {
|
||||
this.options.roll.modifiers = [];
|
||||
if (!this.options.roll.bonus) return;
|
||||
this.options.roll.modifiers.push({
|
||||
label: 'Bonus to Hit',
|
||||
value: this.options.roll.bonus
|
||||
// value: Roll.replaceFormulaData('@attackBonus', this.data)
|
||||
});
|
||||
const modifiers = [];
|
||||
|
||||
if (this.options.roll.bonus)
|
||||
modifiers.push({
|
||||
label: 'Bonus to Hit',
|
||||
value: this.options.roll.bonus
|
||||
});
|
||||
|
||||
modifiers.push(...this.getBonus(`roll.${this.options.type}`, `${this.options.type?.capitalize()} Bonus`));
|
||||
modifiers.push(
|
||||
...this.getBonus(`roll.${this.options.roll.type}`, `${this.options.roll.type?.capitalize()} Bonus`)
|
||||
);
|
||||
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
static async buildEvaluate(roll, config = {}, message = {}) {
|
||||
|
|
|
|||
|
|
@ -24,8 +24,26 @@ export default class DamageRoll extends DHRoll {
|
|||
}
|
||||
}
|
||||
|
||||
applyBaseBonus() {
|
||||
const modifiers = [],
|
||||
type = this.options.messageType ?? 'damage';
|
||||
|
||||
modifiers.push(...this.getBonus(`${type}`, `${type.capitalize()} Bonus`));
|
||||
this.options.damageTypes?.forEach(t => {
|
||||
modifiers.push(...this.getBonus(`${type}.${t}`, `${t.capitalize()} ${type.capitalize()} Bonus`));
|
||||
});
|
||||
const weapons = ['primaryWeapon', 'secondaryWeapon'];
|
||||
weapons.forEach(w => {
|
||||
if (this.options.source.item && this.options.source.item === this.data[w]?.id)
|
||||
modifiers.push(...this.getBonus(`${type}.${w}`, 'Weapon Bonus'));
|
||||
});
|
||||
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
constructFormula(config) {
|
||||
super.constructFormula(config);
|
||||
|
||||
if (config.isCritical) {
|
||||
const tmpRoll = new Roll(this._formula)._evaluateSync({ maximize: true }),
|
||||
criticalBonus = tmpRoll.total - this.constructor.calculateTotalModifiers(tmpRoll);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ export default class DHRoll extends Roll {
|
|||
baseTerms = [];
|
||||
constructor(formula, data, options) {
|
||||
super(formula, data, options);
|
||||
if (!this.data || !Object.keys(this.data).length) this.data = options.data;
|
||||
}
|
||||
|
||||
static messageType = 'adversaryRoll';
|
||||
|
|
@ -86,7 +87,7 @@ export default class DHRoll extends Roll {
|
|||
system: config,
|
||||
rolls: [roll]
|
||||
};
|
||||
return await cls.create(msg);
|
||||
return await cls.create(msg, { rollMode: config.selectedRollMode });
|
||||
}
|
||||
|
||||
static applyKeybindings(config) {
|
||||
|
|
@ -99,11 +100,44 @@ export default class DHRoll extends Roll {
|
|||
}
|
||||
|
||||
formatModifier(modifier) {
|
||||
const numTerm = modifier < 0 ? '-' : '+';
|
||||
return [
|
||||
new foundry.dice.terms.OperatorTerm({ operator: numTerm }),
|
||||
new foundry.dice.terms.NumericTerm({ number: Math.abs(modifier) })
|
||||
];
|
||||
if (Array.isArray(modifier)) {
|
||||
return [
|
||||
new foundry.dice.terms.OperatorTerm({ operator: '+' }),
|
||||
...this.constructor.parse(modifier.join(' + '), this.options.data)
|
||||
];
|
||||
} else {
|
||||
const numTerm = modifier < 0 ? '-' : '+';
|
||||
return [
|
||||
new foundry.dice.terms.OperatorTerm({ operator: numTerm }),
|
||||
new foundry.dice.terms.NumericTerm({ number: Math.abs(modifier) })
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
applyBaseBonus() {
|
||||
return [];
|
||||
}
|
||||
|
||||
addModifiers() {
|
||||
this.options.roll.modifiers?.forEach(m => {
|
||||
this.terms.push(...this.formatModifier(m.value));
|
||||
});
|
||||
}
|
||||
|
||||
getBonus(path, label) {
|
||||
const bonus = foundry.utils.getProperty(this.data.bonuses, path),
|
||||
modifiers = [];
|
||||
if (bonus?.bonus)
|
||||
modifiers.push({
|
||||
label: label,
|
||||
value: bonus?.bonus
|
||||
});
|
||||
if (bonus?.dice?.length)
|
||||
modifiers.push({
|
||||
label: label,
|
||||
value: bonus?.dice
|
||||
});
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
getFaces(faces) {
|
||||
|
|
@ -113,6 +147,9 @@ export default class DHRoll extends Roll {
|
|||
constructFormula(config) {
|
||||
this.terms = Roll.parse(this.options.roll.formula, config.data);
|
||||
|
||||
this.options.roll.modifiers = this.applyBaseBonus();
|
||||
this.addModifiers();
|
||||
|
||||
if (this.options.extraFormula) {
|
||||
this.terms.push(
|
||||
new foundry.dice.terms.OperatorTerm({ operator: '+' }),
|
||||
|
|
@ -138,9 +175,10 @@ export default class DHRoll extends Roll {
|
|||
|
||||
export const registerRollDiceHooks = () => {
|
||||
Hooks.on(`${CONFIG.DH.id}.postRollDuality`, async (config, message) => {
|
||||
const hopeFearAutomation = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).hopeFear;
|
||||
if (
|
||||
!config.source?.actor ||
|
||||
!game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).hope ||
|
||||
(game.user.isGM ? !hopeFearAutomation.gm : !hopeFearAutomation.players) ||
|
||||
config.roll.type === 'reaction'
|
||||
)
|
||||
return;
|
||||
|
|
@ -148,11 +186,16 @@ export const registerRollDiceHooks = () => {
|
|||
const actor = await fromUuid(config.source.actor),
|
||||
updates = [];
|
||||
if (!actor) return;
|
||||
if (config.roll.isCritical || config.roll.result.duality === 1) updates.push({ type: 'hope', value: 1 });
|
||||
if (config.roll.isCritical) updates.push({ type: 'stress', value: -1 });
|
||||
if (config.roll.result.duality === -1) updates.push({ type: 'fear', value: 1 });
|
||||
if (config.roll.isCritical || config.roll.result.duality === 1) updates.push({ key: 'hope', value: 1 });
|
||||
if (config.roll.isCritical) updates.push({ key: 'stress', value: -1 });
|
||||
if (config.roll.result.duality === -1) updates.push({ key: 'fear', value: 1 });
|
||||
|
||||
if (updates.length) actor.modifyResource(updates);
|
||||
if (updates.length) {
|
||||
const target = actor.system.partner ?? actor;
|
||||
if (!['dead', 'unconcious'].some(x => actor.statuses.has(x))) {
|
||||
target.modifyResource(updates);
|
||||
}
|
||||
}
|
||||
|
||||
if (!config.roll.hasOwnProperty('success') && !config.targets?.length) return;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,9 +4,12 @@ import { setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs';
|
|||
|
||||
export default class DualityRoll extends D20Roll {
|
||||
_advantageFaces = 6;
|
||||
_advantageNumber = 1;
|
||||
_rallyIndex;
|
||||
|
||||
constructor(formula, data = {}, options = {}) {
|
||||
super(formula, data, options);
|
||||
this.rallyChoices = this.setRallyChoices();
|
||||
}
|
||||
|
||||
static messageType = 'dualityRoll';
|
||||
|
|
@ -51,6 +54,35 @@ export default class DualityRoll extends D20Roll {
|
|||
this._advantageFaces = this.getFaces(faces);
|
||||
}
|
||||
|
||||
get advantageNumber() {
|
||||
return this._advantageNumber;
|
||||
}
|
||||
|
||||
set advantageNumber(value) {
|
||||
this._advantageNumber = Number(value);
|
||||
}
|
||||
|
||||
setRallyChoices() {
|
||||
return this.data?.parent?.effects.reduce((a,c) => {
|
||||
const change = c.changes.find(ch => ch.key === 'system.bonuses.rally');
|
||||
if(change) a.push({ value: c.id, label: change.value });
|
||||
return a;
|
||||
}, []);
|
||||
}
|
||||
|
||||
get dRally() {
|
||||
if(!this.rallyFaces) return null;
|
||||
if(this.hasDisadvantage || this.hasAdvantage)
|
||||
return this.dice[3];
|
||||
else
|
||||
return this.dice[2];
|
||||
}
|
||||
|
||||
get rallyFaces() {
|
||||
const rallyChoice = this.rallyChoices?.find(r => r.value === this._rallyIndex)?.label;
|
||||
return rallyChoice ? this.getFaces(rallyChoice) : null;
|
||||
}
|
||||
|
||||
get isCritical() {
|
||||
if (!this.dHope._evaluated || !this.dFear._evaluated) return;
|
||||
return this.dHope.total === this.dFear.total;
|
||||
|
|
@ -66,10 +98,6 @@ export default class DualityRoll extends D20Roll {
|
|||
return this.dHope.total < this.dFear.total;
|
||||
}
|
||||
|
||||
get hasBarRally() {
|
||||
return null;
|
||||
}
|
||||
|
||||
get totalLabel() {
|
||||
const label = this.withHope
|
||||
? 'DAGGERHEART.GENERAL.hope'
|
||||
|
|
@ -98,37 +126,43 @@ export default class DualityRoll extends D20Roll {
|
|||
}
|
||||
|
||||
applyAdvantage() {
|
||||
const dieFaces = this.advantageFaces,
|
||||
bardRallyFaces = this.hasBarRally,
|
||||
advDie = new foundry.dice.terms.Die({ faces: dieFaces });
|
||||
if (this.hasAdvantage || this.hasDisadvantage || bardRallyFaces)
|
||||
this.terms.push(new foundry.dice.terms.OperatorTerm({ operator: this.hasDisadvantage ? '-' : '+' }));
|
||||
if (bardRallyFaces) {
|
||||
const rallyDie = new foundry.dice.terms.Die({ faces: bardRallyFaces });
|
||||
if (this.hasAdvantage) {
|
||||
this.terms.push(
|
||||
new foundry.dice.terms.PoolTerm({
|
||||
terms: [advDie.formula, rallyDie.formula],
|
||||
modifiers: ['kh']
|
||||
})
|
||||
);
|
||||
} else if (this.hasDisadvantage) {
|
||||
this.terms.push(advDie, new foundry.dice.terms.OperatorTerm({ operator: '+' }), rallyDie);
|
||||
}
|
||||
} else if (this.hasAdvantage || this.hasDisadvantage) this.terms.push(advDie);
|
||||
if (this.hasAdvantage || this.hasDisadvantage) {
|
||||
const dieFaces = this.advantageFaces,
|
||||
advDie = new foundry.dice.terms.Die({ faces: dieFaces, number: this.advantageNumber });
|
||||
if(this.advantageNumber > 1) advDie.modifiers = ['kh'];
|
||||
this.terms.push(
|
||||
new foundry.dice.terms.OperatorTerm({ operator: this.hasDisadvantage ? '-' : '+' }),
|
||||
advDie
|
||||
);
|
||||
}
|
||||
if(this.rallyFaces)
|
||||
this.terms.push(
|
||||
new foundry.dice.terms.OperatorTerm({ operator: this.hasDisadvantage ? '-' : '+' }),
|
||||
new foundry.dice.terms.Die({ faces: this.rallyFaces })
|
||||
);
|
||||
}
|
||||
|
||||
applyBaseBonus() {
|
||||
this.options.roll.modifiers = [];
|
||||
if (!this.options.roll.trait) return;
|
||||
this.options.roll.modifiers.push({
|
||||
label: `DAGGERHEART.CONFIG.Traits.${this.options.roll.trait}.name`,
|
||||
value: Roll.replaceFormulaData(`@traits.${this.options.roll.trait}.value`, this.data)
|
||||
const modifiers = super.applyBaseBonus();
|
||||
|
||||
if (this.options.roll.trait && this.data.traits[this.options.roll.trait])
|
||||
modifiers.unshift({
|
||||
label: `DAGGERHEART.CONFIG.Traits.${this.options.roll.trait}.name`,
|
||||
value: this.data.traits[this.options.roll.trait].value
|
||||
});
|
||||
|
||||
const weapons = ['primaryWeapon', 'secondaryWeapon'];
|
||||
weapons.forEach(w => {
|
||||
if (this.options.source.item && this.options.source.item === this.data[w]?.id)
|
||||
modifiers.push(...this.getBonus(`roll.${w}`, 'Weapon Bonus'));
|
||||
});
|
||||
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
static postEvaluate(roll, config = {}) {
|
||||
super.postEvaluate(roll, config);
|
||||
|
||||
config.roll.hope = {
|
||||
dice: roll.dHope.denomination,
|
||||
value: roll.dHope.total
|
||||
|
|
@ -137,12 +171,19 @@ export default class DualityRoll extends D20Roll {
|
|||
dice: roll.dFear.denomination,
|
||||
value: roll.dFear.total
|
||||
};
|
||||
config.roll.rally = {
|
||||
dice: roll.dRally?.denomination,
|
||||
value: roll.dRally?.total
|
||||
};
|
||||
config.roll.result = {
|
||||
duality: roll.withHope ? 1 : roll.withFear ? -1 : 0,
|
||||
total: roll.dHope.total + roll.dFear.total,
|
||||
label: roll.totalLabel
|
||||
};
|
||||
|
||||
if(roll._rallyIndex && roll.data?.parent)
|
||||
roll.data.parent.deleteEmbeddedDocuments('ActiveEffect', [roll._rallyIndex]);
|
||||
|
||||
setDiceSoNiceForDualityRoll(roll, config.roll.advantage.type);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,4 +3,5 @@ export { default as DHItem } from './item.mjs';
|
|||
export { default as DhpCombat } from './combat.mjs';
|
||||
export { default as DhActiveEffect } from './activeEffect.mjs';
|
||||
export { default as DhChatMessage } from './chatMessage.mjs';
|
||||
export { default as DhToken } from './token.mjs';
|
||||
export { default as DhTooltipManager } from './tooltipManager.mjs';
|
||||
|
|
|
|||
|
|
@ -55,10 +55,24 @@ export default class DhActiveEffect extends ActiveEffect {
|
|||
}
|
||||
|
||||
static applyField(model, change, field) {
|
||||
change.value = itemAbleRollParse(change.value, model, change.effect.parent);
|
||||
change.value = this.effectSafeEval(itemAbleRollParse(change.value, model, change.effect.parent));
|
||||
super.applyField(model, change, field);
|
||||
}
|
||||
|
||||
/* Altered Foundry safeEval to allow non-numeric returns */
|
||||
static effectSafeEval(expression) {
|
||||
let result;
|
||||
try {
|
||||
// eslint-disable-next-line no-new-func
|
||||
const evl = new Function('sandbox', `with (sandbox) { return ${expression}}`);
|
||||
result = evl(Roll.MATH_PROXY);
|
||||
} catch (err) {
|
||||
return expression;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async toChat(origin) {
|
||||
const cls = getDocumentClass('ChatMessage');
|
||||
const systemData = {
|
||||
|
|
|
|||
|
|
@ -371,7 +371,7 @@ export default class DhpActor extends Actor {
|
|||
getRollData() {
|
||||
const rollData = super.getRollData();
|
||||
rollData.prof = this.system.proficiency ?? 1;
|
||||
rollData.cast = this.system.spellcast ?? 1;
|
||||
rollData.cast = this.system.spellcastModifier ?? 1;
|
||||
return rollData;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,4 +37,15 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
|||
e.setAttribute('data-use-perm', document.testUserPermission(game.user, 'OWNER'));
|
||||
});
|
||||
}
|
||||
|
||||
async _preCreate(data, options, user) {
|
||||
options.speaker = ChatMessage.getSpeaker();
|
||||
const rollActorOwner = data.rolls?.[0]?.data?.parent?.owner;
|
||||
if (rollActorOwner) {
|
||||
data.author = rollActorOwner ? rollActorOwner.id : data.author;
|
||||
await this.updateSource({ author: rollActorOwner ?? user });
|
||||
}
|
||||
|
||||
return super._preCreate(data, options, rollActorOwner ?? user);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
57
module/documents/token.mjs
Normal file
57
module/documents/token.mjs
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
export default class DHToken extends TokenDocument {
|
||||
/**
|
||||
* 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();
|
||||
const barGroup = game.i18n.localize('TOKEN.BarAttributes');
|
||||
const valueGroup = game.i18n.localize('TOKEN.BarValues');
|
||||
|
||||
const bars = attributes.bar.map(v => {
|
||||
const a = v.join('.');
|
||||
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.label.compare(b.label));
|
||||
|
||||
const invalidAttributes = ['gold', 'levelData', 'actions', 'rules.damageReduction.maxArmorMarked.value'];
|
||||
const values = attributes.value.reduce((acc, v) => {
|
||||
const a = v.join('.');
|
||||
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));
|
||||
|
||||
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.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;
|
||||
}
|
||||
}
|
||||
|
|
@ -21,14 +21,79 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
|
|||
this.tooltip.innerHTML = html;
|
||||
options.direction = this._determineItemTooltipDirection(element);
|
||||
}
|
||||
} else {
|
||||
const shortRest = element.dataset.tooltip?.startsWith('#shortRest#');
|
||||
const longRest = element.dataset.tooltip?.startsWith('#longRest#');
|
||||
if (shortRest || longRest) {
|
||||
const key = element.dataset.tooltip.slice(shortRest ? 11 : 10);
|
||||
const downtimeOptions = shortRest
|
||||
? CONFIG.DH.GENERAL.defaultRestOptions.shortRest()
|
||||
: CONFIG.DH.GENERAL.defaultRestOptions.longRest();
|
||||
const move = downtimeOptions[key];
|
||||
html = await foundry.applications.handlebars.renderTemplate(
|
||||
`systems/daggerheart/templates/ui/tooltip/downtime.hbs`,
|
||||
{
|
||||
move: move
|
||||
}
|
||||
);
|
||||
|
||||
this.tooltip.innerHTML = html;
|
||||
options.direction = this._determineItemTooltipDirection(
|
||||
element,
|
||||
this.constructor.TOOLTIP_DIRECTIONS.UP
|
||||
);
|
||||
}
|
||||
|
||||
const isAdvantage = element.dataset.tooltip?.startsWith('#advantage#');
|
||||
const isDisadvantage = element.dataset.tooltip?.startsWith('#disadvantage#');
|
||||
if (isAdvantage || isDisadvantage) {
|
||||
const actorUuid = element.dataset.tooltip.slice(isAdvantage ? 11 : 14);
|
||||
const actor = await foundry.utils.fromUuid(actorUuid);
|
||||
|
||||
if (actor) {
|
||||
html = await foundry.applications.handlebars.renderTemplate(
|
||||
`systems/daggerheart/templates/ui/tooltip/advantage.hbs`,
|
||||
{
|
||||
sources: isAdvantage ? actor.system.advantageSources : actor.system.disadvantageSources
|
||||
}
|
||||
);
|
||||
|
||||
this.tooltip.innerHTML = html;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
super.activate(element, { ...options, html: html });
|
||||
}
|
||||
|
||||
_determineItemTooltipDirection(element) {
|
||||
_determineItemTooltipDirection(element, prefered = this.constructor.TOOLTIP_DIRECTIONS.LEFT) {
|
||||
const pos = element.getBoundingClientRect();
|
||||
const dirs = this.constructor.TOOLTIP_DIRECTIONS;
|
||||
return dirs[pos.x - this.tooltip.offsetWidth < 0 ? 'DOWN' : 'LEFT'];
|
||||
switch (prefered) {
|
||||
case this.constructor.TOOLTIP_DIRECTIONS.LEFT:
|
||||
return dirs[
|
||||
pos.x - this.tooltip.offsetWidth < 0
|
||||
? this.constructor.TOOLTIP_DIRECTIONS.DOWN
|
||||
: this.constructor.TOOLTIP_DIRECTIONS.LEFT
|
||||
];
|
||||
case this.constructor.TOOLTIP_DIRECTIONS.UP:
|
||||
return dirs[
|
||||
pos.y - this.tooltip.offsetHeight < 0
|
||||
? this.constructor.TOOLTIP_DIRECTIONS.RIGHT
|
||||
: this.constructor.TOOLTIP_DIRECTIONS.UP
|
||||
];
|
||||
case this.constructor.TOOLTIP_DIRECTIONS.RIGHT:
|
||||
return dirs[
|
||||
pos.x + this.tooltip.offsetWidth > document.body.clientWidth
|
||||
? this.constructor.TOOLTIP_DIRECTIONS.DOWN
|
||||
: this.constructor.TOOLTIP_DIRECTIONS.RIGHT
|
||||
];
|
||||
case this.constructor.TOOLTIP_DIRECTIONS.DOWN:
|
||||
return dirs[
|
||||
pos.y + this.tooltip.offsetHeight > document.body.clientHeight
|
||||
? this.constructor.TOOLTIP_DIRECTIONS.LEFT
|
||||
: this.constructor.TOOLTIP_DIRECTIONS.DOWN
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,8 +39,9 @@ export default class RegisterHandlebarsHelpers {
|
|||
}
|
||||
|
||||
static damageSymbols(damageParts) {
|
||||
const symbols = new Set();
|
||||
damageParts.forEach(part => symbols.add(...CONFIG.DH.GENERAL.damageTypes[part.type].icon));
|
||||
const symbols = [...new Set(damageParts.reduce((a, c) => a.concat([...c.type]), []))].map(
|
||||
p => CONFIG.DH.GENERAL.damageTypes[p].icon
|
||||
);
|
||||
return new Handlebars.SafeString(Array.from(symbols).map(symbol => `<i class="fa-solid ${symbol}"></i>`));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -318,7 +318,7 @@ export const itemAbleRollParse = (value, actor, item) => {
|
|||
const isItemTarget = value.toLowerCase().startsWith('item.');
|
||||
const slicedValue = isItemTarget ? value.slice(5) : value;
|
||||
try {
|
||||
return Roll.safeEval(Roll.replaceFormulaData(slicedValue, isItemTarget ? item : actor));
|
||||
return Roll.replaceFormulaData(slicedValue, isItemTarget ? item : actor);
|
||||
} catch (_) {
|
||||
return '';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ export const preloadHandlebarsTemplates = async function () {
|
|||
'systems/daggerheart/templates/settings/components/settings-item-line.hbs',
|
||||
'systems/daggerheart/templates/ui/chat/parts/damage-chat.hbs',
|
||||
'systems/daggerheart/templates/ui/chat/parts/target-chat.hbs',
|
||||
'systems/daggerheart/templates/ui/tooltip/parts/tooltipTags.hbs'
|
||||
'systems/daggerheart/templates/ui/tooltip/parts/tooltipTags.hbs',
|
||||
'systems/daggerheart/templates/dialogs/downtime/activities.hbs'
|
||||
]);
|
||||
};
|
||||
|
|
|
|||
6
package-lock.json
generated
6
package-lock.json
generated
|
|
@ -6,6 +6,7 @@
|
|||
"": {
|
||||
"dependencies": {
|
||||
"@yaireo/tagify": "^4.17.9",
|
||||
"autocompleter": "^9.3.2",
|
||||
"gulp": "^5.0.0",
|
||||
"gulp-less": "^5.0.0",
|
||||
"rollup": "^4.40.0"
|
||||
|
|
@ -608,6 +609,11 @@
|
|||
"node": ">= 10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/autocompleter": {
|
||||
"version": "9.3.2",
|
||||
"resolved": "https://registry.npmjs.org/autocompleter/-/autocompleter-9.3.2.tgz",
|
||||
"integrity": "sha512-rLbf2TLGOD7y+gOS36ksrZdIsvoHa2KXc2A7503w+NBRPrcF73zzFeYBxEcV/iMPjaBH3jFhNIYObZ7zt1fkCQ=="
|
||||
},
|
||||
"node_modules/available-typed-arrays": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"@yaireo/tagify": "^4.17.9",
|
||||
"autocompleter": "^9.3.2",
|
||||
"gulp": "^5.0.0",
|
||||
"gulp-less": "^5.0.0",
|
||||
"rollup": "^4.40.0"
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export default {
|
|||
path: './postcss.config.js'
|
||||
},
|
||||
extensions: ['.css'],
|
||||
extract: false
|
||||
extract: 'tagify.css'
|
||||
}),
|
||||
commonjs({
|
||||
include: /node_modules/,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"type": "Item",
|
||||
"folder": null,
|
||||
"name": "Rogue",
|
||||
"name": "Tier 1",
|
||||
"color": null,
|
||||
"sorting": "a",
|
||||
"_id": "AzwvgmcvToIUGYti",
|
||||
"_id": "sxvlEwi25uAoB2C5",
|
||||
"description": "",
|
||||
"sort": 0,
|
||||
"flags": {},
|
||||
|
|
@ -12,12 +12,12 @@
|
|||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.344",
|
||||
"coreVersion": "13.346",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "0.0.1",
|
||||
"createdTime": 1748709700301,
|
||||
"modifiedTime": 1748709700301,
|
||||
"lastModifiedBy": "MxkU9FQYKmOxbdzm"
|
||||
"createdTime": 1752684226915,
|
||||
"modifiedTime": 1752684226915,
|
||||
"lastModifiedBy": "k0gmQFlvrPvlTtbh"
|
||||
},
|
||||
"_key": "!folders!AzwvgmcvToIUGYti"
|
||||
"_key": "!folders!sxvlEwi25uAoB2C5"
|
||||
}
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"type": "Item",
|
||||
"folder": null,
|
||||
"name": "Ranger",
|
||||
"name": "Tier 2",
|
||||
"color": null,
|
||||
"sorting": "a",
|
||||
"_id": "84neMcqoIRAoIvXP",
|
||||
"_id": "OgzrmfH1ZbpljX7k",
|
||||
"description": "",
|
||||
"sort": 0,
|
||||
"flags": {},
|
||||
|
|
@ -12,12 +12,12 @@
|
|||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.344",
|
||||
"coreVersion": "13.346",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "0.0.1",
|
||||
"createdTime": 1748709694559,
|
||||
"modifiedTime": 1748709694559,
|
||||
"lastModifiedBy": "MxkU9FQYKmOxbdzm"
|
||||
"createdTime": 1752684230275,
|
||||
"modifiedTime": 1752684230275,
|
||||
"lastModifiedBy": "k0gmQFlvrPvlTtbh"
|
||||
},
|
||||
"_key": "!folders!84neMcqoIRAoIvXP"
|
||||
"_key": "!folders!OgzrmfH1ZbpljX7k"
|
||||
}
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"type": "Item",
|
||||
"folder": null,
|
||||
"name": "Bard",
|
||||
"name": "Tier 3",
|
||||
"color": null,
|
||||
"sorting": "a",
|
||||
"_id": "3QoIMKARHfRnBvQJ",
|
||||
"_id": "wTI7nZkPhKxl7Wwq",
|
||||
"description": "",
|
||||
"sort": 0,
|
||||
"flags": {},
|
||||
|
|
@ -12,12 +12,12 @@
|
|||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.344",
|
||||
"coreVersion": "13.346",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "0.0.1",
|
||||
"createdTime": 1748709671341,
|
||||
"modifiedTime": 1748709671341,
|
||||
"lastModifiedBy": "MxkU9FQYKmOxbdzm"
|
||||
"createdTime": 1752684233122,
|
||||
"modifiedTime": 1752684233122,
|
||||
"lastModifiedBy": "k0gmQFlvrPvlTtbh"
|
||||
},
|
||||
"_key": "!folders!3QoIMKARHfRnBvQJ"
|
||||
"_key": "!folders!wTI7nZkPhKxl7Wwq"
|
||||
}
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"type": "Item",
|
||||
"folder": null,
|
||||
"name": "Druid",
|
||||
"name": "Tier 4",
|
||||
"color": null,
|
||||
"sorting": "a",
|
||||
"_id": "l94HTIXSvdZG3zIh",
|
||||
"_id": "7XHlANCPz18yvl5L",
|
||||
"description": "",
|
||||
"sort": 0,
|
||||
"flags": {},
|
||||
|
|
@ -12,12 +12,12 @@
|
|||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.344",
|
||||
"coreVersion": "13.346",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "0.0.1",
|
||||
"createdTime": 1748709677349,
|
||||
"modifiedTime": 1748709677349,
|
||||
"lastModifiedBy": "MxkU9FQYKmOxbdzm"
|
||||
"createdTime": 1752684235596,
|
||||
"modifiedTime": 1752684235596,
|
||||
"lastModifiedBy": "k0gmQFlvrPvlTtbh"
|
||||
},
|
||||
"_key": "!folders!l94HTIXSvdZG3zIh"
|
||||
"_key": "!folders!7XHlANCPz18yvl5L"
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
{
|
||||
"name": "Clank",
|
||||
"type": "ancestry",
|
||||
"_id": "AzKMOIpXnCSLLDEB",
|
||||
"img": "icons/svg/item-bag.svg",
|
||||
"system": {
|
||||
"description": "<p><strong>Purposeful Design</strong>: Decide who made you and for what purpose. At character creation, choose one of your Experiences that best aligns with this purpose and gain a permanent +1 bonus to it.</p><p><strong>Efficient</strong>: When you take a short rest, you can choose a long rest move instead of a short rest move.</p>",
|
||||
"abilities": []
|
||||
},
|
||||
"effects": [],
|
||||
"folder": null,
|
||||
"sort": 100000,
|
||||
"ownership": {
|
||||
"default": 0,
|
||||
"NqO2eQGMjrvUO6v9": 3
|
||||
},
|
||||
"flags": {},
|
||||
"_stats": {
|
||||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.344",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "0.0.1",
|
||||
"createdTime": 1747990113503,
|
||||
"modifiedTime": 1747999445342,
|
||||
"lastModifiedBy": "NqO2eQGMjrvUO6v9"
|
||||
},
|
||||
"_key": "!items!AzKMOIpXnCSLLDEB"
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"type": "Item",
|
||||
"folder": null,
|
||||
"name": "Ancestry Features",
|
||||
"color": null,
|
||||
"sorting": "a",
|
||||
"_id": "dSAccOl5ccgXPyje",
|
||||
"description": "",
|
||||
"sort": 0,
|
||||
"flags": {},
|
||||
"_stats": {
|
||||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.346",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "0.0.1",
|
||||
"createdTime": 1752680699271,
|
||||
"modifiedTime": 1752680699271,
|
||||
"lastModifiedBy": "binNpU8lWev6geDj"
|
||||
},
|
||||
"_key": "!folders!dSAccOl5ccgXPyje"
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
{
|
||||
"name": "Arcane Sense",
|
||||
"type": "feature",
|
||||
"_id": "D5HUGwdizhBVZ0RW",
|
||||
"img": "icons/svg/item-bag.svg",
|
||||
"system": {
|
||||
"actionType": "action",
|
||||
"featureType": {
|
||||
"type": "normal",
|
||||
"data": {
|
||||
"property": "spellcastingTrait",
|
||||
"max": 1,
|
||||
"numbers": {}
|
||||
}
|
||||
},
|
||||
"refreshData": null,
|
||||
"multiclass": null,
|
||||
"disabled": false,
|
||||
"description": "<p>You can sense the presence of magical people and objects within Close range.</p>",
|
||||
"effects": {},
|
||||
"actions": [],
|
||||
"type": "class"
|
||||
},
|
||||
"effects": [],
|
||||
"folder": "ncQG0s0ttAPObaeV",
|
||||
"sort": 100000,
|
||||
"ownership": {
|
||||
"default": 0,
|
||||
"ei8OkswTzyDp4IGC": 3
|
||||
},
|
||||
"flags": {},
|
||||
"_stats": {
|
||||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.344",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "0.0.1",
|
||||
"createdTime": 1748033779155,
|
||||
"modifiedTime": 1748710066261,
|
||||
"lastModifiedBy": "MxkU9FQYKmOxbdzm"
|
||||
},
|
||||
"_key": "!items!D5HUGwdizhBVZ0RW"
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
{
|
||||
"name": "Attack of Opportunity",
|
||||
"type": "feature",
|
||||
"_id": "VfUbJwGU4Cka0xLP",
|
||||
"img": "icons/svg/item-bag.svg",
|
||||
"system": {
|
||||
"actionType": "action",
|
||||
"featureType": {
|
||||
"type": "normal",
|
||||
"data": {
|
||||
"property": "spellcastingTrait",
|
||||
"max": 1,
|
||||
"numbers": {}
|
||||
}
|
||||
},
|
||||
"refreshData": null,
|
||||
"multiclass": null,
|
||||
"disabled": false,
|
||||
"description": "<p>When an adversary within Melee range attempts to leave that range, make a reaction roll using a trait of your choice against their Difficulty. Choose one effect on a success, or two if you critically succeed:</p><p>• They can’t move from where they are.</p><p>• You deal damage to them equal to your primary weapon’s damage.</p><p>• You move with them.</p>",
|
||||
"effects": {},
|
||||
"actions": [],
|
||||
"type": "class"
|
||||
},
|
||||
"effects": [],
|
||||
"folder": "3znRxBqsK0lOJhvJ",
|
||||
"sort": 100000,
|
||||
"ownership": {
|
||||
"default": 0,
|
||||
"ei8OkswTzyDp4IGC": 3
|
||||
},
|
||||
"flags": {},
|
||||
"_stats": {
|
||||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.344",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "0.0.1",
|
||||
"createdTime": 1748034185325,
|
||||
"modifiedTime": 1748710073183,
|
||||
"lastModifiedBy": "MxkU9FQYKmOxbdzm"
|
||||
},
|
||||
"_key": "!items!VfUbJwGU4Cka0xLP"
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
{
|
||||
"name": "Beastform",
|
||||
"type": "feature",
|
||||
"_id": "NkSKDXGNNiOUlFqm",
|
||||
"img": "icons/svg/item-bag.svg",
|
||||
"system": {
|
||||
"actionType": "action",
|
||||
"featureType": {
|
||||
"type": "normal",
|
||||
"data": {
|
||||
"property": "spellcastingTrait",
|
||||
"max": 1,
|
||||
"numbers": {}
|
||||
}
|
||||
},
|
||||
"refreshData": null,
|
||||
"multiclass": null,
|
||||
"disabled": false,
|
||||
"description": "<p>Mark a Stress to magically transform into a creature of your tier or lower from the Beastform list. You can drop out of this form at any time. While transformed, you can’t use weapons or cast spells from domain cards, but you can still use other features or abilities you have access to. Spells you cast before you transform stay active and last for their normal duration, and you can talk and communicate as normal. Additionally, you gain the Beastform’s features, add their Evasion bonus to your Evasion, and use the trait specified in their statistics for your attack. While you’re in a Beastform, your armor becomes part of your body and you mark Armor Slots as usual; when you drop out of a Beastform, those marked Armor Slots remain marked. If you mark your last Hit Point, you automatically drop out of this form.</p>",
|
||||
"effects": {},
|
||||
"actions": [],
|
||||
"type": "class"
|
||||
},
|
||||
"effects": [],
|
||||
"folder": "l94HTIXSvdZG3zIh",
|
||||
"sort": 100000,
|
||||
"ownership": {
|
||||
"default": 0,
|
||||
"ei8OkswTzyDp4IGC": 3
|
||||
},
|
||||
"flags": {},
|
||||
"_stats": {
|
||||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.344",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "0.0.1",
|
||||
"createdTime": 1748028817729,
|
||||
"modifiedTime": 1748710083736,
|
||||
"lastModifiedBy": "MxkU9FQYKmOxbdzm"
|
||||
},
|
||||
"_key": "!items!NkSKDXGNNiOUlFqm"
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
{
|
||||
"name": "Channel Raw Power",
|
||||
"type": "feature",
|
||||
"_id": "ovxuqhl01XZSwx2n",
|
||||
"img": "icons/svg/item-bag.svg",
|
||||
"system": {
|
||||
"actionType": "action",
|
||||
"featureType": {
|
||||
"type": "normal",
|
||||
"data": {
|
||||
"property": "spellcastingTrait",
|
||||
"max": 1,
|
||||
"numbers": {}
|
||||
}
|
||||
},
|
||||
"refreshData": {
|
||||
"type": "longRest"
|
||||
},
|
||||
"multiclass": null,
|
||||
"disabled": false,
|
||||
"description": "<p>Once per long rest, you can place a domain card from your loadout into your vault and choose to either:</p><p>• Gain Hope equal to the level of the card.</p><p>• Enhance a spell that deals damage, gaining a bonus to your damage roll equal to twice the level of the card.</p>",
|
||||
"effects": {},
|
||||
"actions": [],
|
||||
"type": "class"
|
||||
},
|
||||
"effects": [],
|
||||
"folder": "ncQG0s0ttAPObaeV",
|
||||
"sort": 200000,
|
||||
"ownership": {
|
||||
"default": 0,
|
||||
"ei8OkswTzyDp4IGC": 3
|
||||
},
|
||||
"flags": {},
|
||||
"_stats": {
|
||||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.344",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "0.0.1",
|
||||
"createdTime": 1748033854379,
|
||||
"modifiedTime": 1748710088872,
|
||||
"lastModifiedBy": "MxkU9FQYKmOxbdzm"
|
||||
},
|
||||
"_key": "!items!ovxuqhl01XZSwx2n"
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
{
|
||||
"name": "Cloaked",
|
||||
"type": "feature",
|
||||
"_id": "TpaoHSJ3npjWiBOf",
|
||||
"img": "icons/svg/item-bag.svg",
|
||||
"system": {
|
||||
"actionType": "action",
|
||||
"featureType": {
|
||||
"type": "normal",
|
||||
"data": {
|
||||
"property": "spellcastingTrait",
|
||||
"max": 1,
|
||||
"numbers": {}
|
||||
}
|
||||
},
|
||||
"refreshData": null,
|
||||
"multiclass": null,
|
||||
"disabled": false,
|
||||
"description": "<p>Any time you would be Hidden, you are instead Cloaked. In addition to the benefits of the Hidden condition, while Cloaked you remain unseen if you are stationary when an adversary moves to where they would normally see you. After you make an attack or end a move within line of sight of an adversary, you are no longer Cloaked.</p>",
|
||||
"effects": {},
|
||||
"actions": [],
|
||||
"type": "class"
|
||||
},
|
||||
"effects": [],
|
||||
"folder": "AzwvgmcvToIUGYti",
|
||||
"sort": 100000,
|
||||
"ownership": {
|
||||
"default": 0,
|
||||
"ei8OkswTzyDp4IGC": 3
|
||||
},
|
||||
"flags": {},
|
||||
"_stats": {
|
||||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.344",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "0.0.1",
|
||||
"createdTime": 1748032226905,
|
||||
"modifiedTime": 1748710097141,
|
||||
"lastModifiedBy": "MxkU9FQYKmOxbdzm"
|
||||
},
|
||||
"_key": "!items!TpaoHSJ3npjWiBOf"
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
{
|
||||
"name": "Combat Training",
|
||||
"type": "feature",
|
||||
"_id": "elb6ZVertgu6OdKA",
|
||||
"img": "icons/svg/item-bag.svg",
|
||||
"system": {
|
||||
"actionType": "passive",
|
||||
"featureType": {
|
||||
"type": "normal",
|
||||
"data": {
|
||||
"property": "spellcastingTrait",
|
||||
"max": 1,
|
||||
"numbers": {}
|
||||
}
|
||||
},
|
||||
"refreshData": null,
|
||||
"multiclass": null,
|
||||
"disabled": false,
|
||||
"description": "<p>You ignore burden when equipping weapons. When you deal physical damage, you gain a bonus to your damage roll equal to your level.</p>",
|
||||
"effects": {},
|
||||
"actions": [],
|
||||
"type": "class"
|
||||
},
|
||||
"effects": [],
|
||||
"folder": "3znRxBqsK0lOJhvJ",
|
||||
"sort": 200000,
|
||||
"ownership": {
|
||||
"default": 0,
|
||||
"ei8OkswTzyDp4IGC": 3
|
||||
},
|
||||
"flags": {},
|
||||
"_stats": {
|
||||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.344",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "0.0.1",
|
||||
"createdTime": 1748034225886,
|
||||
"modifiedTime": 1748710182918,
|
||||
"lastModifiedBy": "MxkU9FQYKmOxbdzm"
|
||||
},
|
||||
"_key": "!items!elb6ZVertgu6OdKA"
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
{
|
||||
"name": "Evolution",
|
||||
"type": "feature",
|
||||
"_id": "bZxfyPTZ6rsakyA2",
|
||||
"img": "icons/svg/item-bag.svg",
|
||||
"system": {
|
||||
"actionType": "action",
|
||||
"featureType": {
|
||||
"type": "normal",
|
||||
"data": {
|
||||
"property": "spellcastingTrait",
|
||||
"max": 1,
|
||||
"numbers": {}
|
||||
}
|
||||
},
|
||||
"refreshData": null,
|
||||
"multiclass": null,
|
||||
"disabled": false,
|
||||
"description": "<p>Spend 3 Hope to transform into a Beastform without marking a Stress. When you do, choose one trait to raise by +1 until you drop out of that Beastform.</p>",
|
||||
"effects": {},
|
||||
"actions": [],
|
||||
"type": "class"
|
||||
},
|
||||
"effects": [],
|
||||
"folder": "l94HTIXSvdZG3zIh",
|
||||
"sort": 200000,
|
||||
"ownership": {
|
||||
"default": 0,
|
||||
"ei8OkswTzyDp4IGC": 3
|
||||
},
|
||||
"flags": {},
|
||||
"_stats": {
|
||||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.344",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "0.0.1",
|
||||
"createdTime": 1748028774087,
|
||||
"modifiedTime": 1748710255976,
|
||||
"lastModifiedBy": "MxkU9FQYKmOxbdzm"
|
||||
},
|
||||
"_key": "!items!bZxfyPTZ6rsakyA2"
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
{
|
||||
"name": "Frontline Tank",
|
||||
"type": "feature",
|
||||
"_id": "ftUZznLFJ5xbcxcu",
|
||||
"img": "icons/svg/item-bag.svg",
|
||||
"system": {
|
||||
"actionType": "action",
|
||||
"featureType": {
|
||||
"type": "normal",
|
||||
"data": {
|
||||
"property": "spellcastingTrait",
|
||||
"max": 1,
|
||||
"numbers": {}
|
||||
}
|
||||
},
|
||||
"refreshData": null,
|
||||
"multiclass": null,
|
||||
"disabled": false,
|
||||
"description": "<p>Spend 3 Hope to clear 2 Armor Slots</p>",
|
||||
"effects": {},
|
||||
"actions": [],
|
||||
"type": "class"
|
||||
},
|
||||
"effects": [],
|
||||
"folder": "imOcur5Zv8WcMHXz",
|
||||
"sort": 100000,
|
||||
"ownership": {
|
||||
"default": 0,
|
||||
"ei8OkswTzyDp4IGC": 3
|
||||
},
|
||||
"flags": {},
|
||||
"_stats": {
|
||||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.344",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "0.0.1",
|
||||
"createdTime": 1748030942761,
|
||||
"modifiedTime": 1748710261406,
|
||||
"lastModifiedBy": "MxkU9FQYKmOxbdzm"
|
||||
},
|
||||
"_key": "!items!ftUZznLFJ5xbcxcu"
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
{
|
||||
"name": "Hold Them Off",
|
||||
"type": "feature",
|
||||
"_id": "FSx2ojskU0pRE72g",
|
||||
"img": "icons/svg/item-bag.svg",
|
||||
"system": {
|
||||
"actionType": "action",
|
||||
"featureType": {
|
||||
"type": "normal",
|
||||
"data": {
|
||||
"property": "spellcastingTrait",
|
||||
"max": 1,
|
||||
"numbers": {}
|
||||
}
|
||||
},
|
||||
"refreshData": null,
|
||||
"multiclass": null,
|
||||
"disabled": false,
|
||||
"description": "<p>Spend 3 Hope when you succeed on an attack with a weapon to use that same roll against two additional adversaries within range of the attack</p>",
|
||||
"effects": {},
|
||||
"actions": [],
|
||||
"type": "class"
|
||||
},
|
||||
"effects": [],
|
||||
"folder": "84neMcqoIRAoIvXP",
|
||||
"sort": 100000,
|
||||
"ownership": {
|
||||
"default": 0,
|
||||
"ei8OkswTzyDp4IGC": 3
|
||||
},
|
||||
"flags": {},
|
||||
"_stats": {
|
||||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.344",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "0.0.1",
|
||||
"createdTime": 1748030888304,
|
||||
"modifiedTime": 1748710266972,
|
||||
"lastModifiedBy": "MxkU9FQYKmOxbdzm"
|
||||
},
|
||||
"_key": "!items!FSx2ojskU0pRE72g"
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
{
|
||||
"name": "Life Support",
|
||||
"type": "feature",
|
||||
"_id": "UZ9UjZArSJh6UHXG",
|
||||
"img": "icons/svg/item-bag.svg",
|
||||
"system": {
|
||||
"actionType": "action",
|
||||
"featureType": {
|
||||
"type": "normal",
|
||||
"data": {
|
||||
"property": "spellcastingTrait",
|
||||
"max": 1,
|
||||
"numbers": {}
|
||||
}
|
||||
},
|
||||
"refreshData": null,
|
||||
"multiclass": null,
|
||||
"disabled": false,
|
||||
"description": "<p>Spend 3 Hope to clear a Hit Point on an ally within Close range.</p>",
|
||||
"effects": {},
|
||||
"actions": [],
|
||||
"type": "class"
|
||||
},
|
||||
"effects": [],
|
||||
"folder": "aI54jXjBNrAOm7R8",
|
||||
"sort": 100000,
|
||||
"ownership": {
|
||||
"default": 0,
|
||||
"ei8OkswTzyDp4IGC": 3
|
||||
},
|
||||
"flags": {},
|
||||
"_stats": {
|
||||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.344",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "0.0.1",
|
||||
"createdTime": 1748033044907,
|
||||
"modifiedTime": 1748710332116,
|
||||
"lastModifiedBy": "MxkU9FQYKmOxbdzm"
|
||||
},
|
||||
"_key": "!items!UZ9UjZArSJh6UHXG"
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
{
|
||||
"name": "Make a Scene",
|
||||
"type": "feature",
|
||||
"_id": "Ddk0PAgwM4VLRbyY",
|
||||
"img": "icons/svg/item-bag.svg",
|
||||
"system": {
|
||||
"actionType": "action",
|
||||
"featureType": {
|
||||
"type": "normal",
|
||||
"data": {
|
||||
"property": "spellcastingTrait",
|
||||
"max": 1,
|
||||
"numbers": {}
|
||||
}
|
||||
},
|
||||
"refreshData": null,
|
||||
"multiclass": null,
|
||||
"disabled": false,
|
||||
"description": "<p>Spend 3 Hope to temporarily Distract a target within Close range, giving them a -2 penalty to their Difficulty.</p>",
|
||||
"effects": {},
|
||||
"actions": [],
|
||||
"type": "class"
|
||||
},
|
||||
"effects": [],
|
||||
"folder": "3QoIMKARHfRnBvQJ",
|
||||
"sort": 100000,
|
||||
"ownership": {
|
||||
"default": 0,
|
||||
"NqO2eQGMjrvUO6v9": 3
|
||||
},
|
||||
"flags": {},
|
||||
"_stats": {
|
||||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.344",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "0.0.1",
|
||||
"createdTime": 1747991384366,
|
||||
"modifiedTime": 1748710338024,
|
||||
"lastModifiedBy": "MxkU9FQYKmOxbdzm"
|
||||
},
|
||||
"_key": "!items!Ddk0PAgwM4VLRbyY"
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
{
|
||||
"name": "Minor Illusion",
|
||||
"type": "feature",
|
||||
"_id": "qFq7kynAZhbWTbT5",
|
||||
"img": "icons/svg/item-bag.svg",
|
||||
"system": {
|
||||
"actionType": "action",
|
||||
"featureType": {
|
||||
"type": "normal",
|
||||
"data": {
|
||||
"property": "spellcastingTrait",
|
||||
"max": 1,
|
||||
"numbers": {}
|
||||
}
|
||||
},
|
||||
"refreshData": null,
|
||||
"multiclass": null,
|
||||
"disabled": false,
|
||||
"description": "<p>Make a Spellcast Roll (10). On a success, you create a minor visual illusion no larger than yourself within Close range. This illusion is convincing to anyone at Close range or farther.</p>",
|
||||
"effects": {},
|
||||
"actions": [],
|
||||
"type": "class"
|
||||
},
|
||||
"effects": [],
|
||||
"folder": "ncQG0s0ttAPObaeV",
|
||||
"sort": 150000,
|
||||
"ownership": {
|
||||
"default": 0,
|
||||
"ei8OkswTzyDp4IGC": 3
|
||||
},
|
||||
"flags": {},
|
||||
"_stats": {
|
||||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.344",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "0.0.1",
|
||||
"createdTime": 1748033817443,
|
||||
"modifiedTime": 1748710347004,
|
||||
"lastModifiedBy": "MxkU9FQYKmOxbdzm"
|
||||
},
|
||||
"_key": "!items!qFq7kynAZhbWTbT5"
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
{
|
||||
"name": "No Mercy",
|
||||
"type": "feature",
|
||||
"_id": "t3tLoq4h9wgQD7E9",
|
||||
"img": "icons/svg/item-bag.svg",
|
||||
"system": {
|
||||
"actionType": "action",
|
||||
"featureType": {
|
||||
"type": "normal",
|
||||
"data": {
|
||||
"property": "spellcastingTrait",
|
||||
"max": 1,
|
||||
"numbers": {}
|
||||
}
|
||||
},
|
||||
"refreshData": null,
|
||||
"multiclass": null,
|
||||
"disabled": false,
|
||||
"description": "<p>Spend 3 Hope to gain a +1 bonus to your attack rolls until your next rest.</p>",
|
||||
"effects": {},
|
||||
"actions": [],
|
||||
"type": "class"
|
||||
},
|
||||
"effects": [],
|
||||
"folder": "3znRxBqsK0lOJhvJ",
|
||||
"sort": 150000,
|
||||
"ownership": {
|
||||
"default": 0,
|
||||
"ei8OkswTzyDp4IGC": 3
|
||||
},
|
||||
"flags": {},
|
||||
"_stats": {
|
||||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.344",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "0.0.1",
|
||||
"createdTime": 1748034146678,
|
||||
"modifiedTime": 1748710386929,
|
||||
"lastModifiedBy": "MxkU9FQYKmOxbdzm"
|
||||
},
|
||||
"_key": "!items!t3tLoq4h9wgQD7E9"
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
{
|
||||
"name": "Not This Time",
|
||||
"type": "feature",
|
||||
"_id": "5msGbQyFwdwdFdYs",
|
||||
"img": "icons/svg/item-bag.svg",
|
||||
"system": {
|
||||
"actionType": "reaction",
|
||||
"featureType": {
|
||||
"type": "normal",
|
||||
"data": {
|
||||
"property": "spellcastingTrait",
|
||||
"max": 1,
|
||||
"numbers": {}
|
||||
}
|
||||
},
|
||||
"refreshData": null,
|
||||
"multiclass": null,
|
||||
"disabled": false,
|
||||
"description": "<p>Spend 3 Hope to force an adversary within Far range to reroll an attack or damage roll</p>",
|
||||
"effects": {},
|
||||
"actions": [],
|
||||
"type": "class"
|
||||
},
|
||||
"effects": [],
|
||||
"folder": "8I0S9f458qu36qSW",
|
||||
"sort": 100000,
|
||||
"ownership": {
|
||||
"default": 0,
|
||||
"ei8OkswTzyDp4IGC": 3
|
||||
},
|
||||
"flags": {},
|
||||
"_stats": {
|
||||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.344",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "0.0.1",
|
||||
"createdTime": 1748034883014,
|
||||
"modifiedTime": 1748710410194,
|
||||
"lastModifiedBy": "MxkU9FQYKmOxbdzm"
|
||||
},
|
||||
"_key": "!items!5msGbQyFwdwdFdYs"
|
||||
}
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
{
|
||||
"name": "Prayer Dice",
|
||||
"type": "feature",
|
||||
"_id": "jXfGnLnU8PswJYJd",
|
||||
"img": "icons/svg/item-bag.svg",
|
||||
"system": {
|
||||
"actionType": "action",
|
||||
"featureType": {
|
||||
"type": "normal",
|
||||
"data": {
|
||||
"property": "spellcastingTrait",
|
||||
"max": 6,
|
||||
"numbers": {},
|
||||
"value": "d4"
|
||||
}
|
||||
},
|
||||
"refreshData": {
|
||||
"type": "session"
|
||||
},
|
||||
"multiclass": null,
|
||||
"disabled": false,
|
||||
"description": "<p>At the beginning of each session, roll a number of d4s equal to your subclass’s Spellcast trait and place them on this sheet in the space provided. These are your Prayer Dice. You can spend any number of Prayer Dice to aid yourself or an ally within Far range. You can use a spent die’s value to reduce incoming damage, add to a roll’s result after the roll is made, or gain Hope equal to the result. At the end of each session, clear all unspent Prayer Dice.</p>",
|
||||
"effects": {},
|
||||
"actions": [],
|
||||
"type": "class"
|
||||
},
|
||||
"effects": [],
|
||||
"folder": "aI54jXjBNrAOm7R8",
|
||||
"sort": 200000,
|
||||
"ownership": {
|
||||
"default": 0,
|
||||
"ei8OkswTzyDp4IGC": 3
|
||||
},
|
||||
"flags": {},
|
||||
"_stats": {
|
||||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.344",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "0.0.1",
|
||||
"createdTime": 1748033372327,
|
||||
"modifiedTime": 1748710416279,
|
||||
"lastModifiedBy": "MxkU9FQYKmOxbdzm"
|
||||
},
|
||||
"_key": "!items!jXfGnLnU8PswJYJd"
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
{
|
||||
"name": "Prestidigitation",
|
||||
"type": "feature",
|
||||
"_id": "ofBmJIn6NWxA0wPz",
|
||||
"img": "icons/svg/item-bag.svg",
|
||||
"system": {
|
||||
"actionType": "action",
|
||||
"featureType": {
|
||||
"type": "normal",
|
||||
"data": {
|
||||
"property": "spellcastingTrait",
|
||||
"max": 1,
|
||||
"numbers": {}
|
||||
}
|
||||
},
|
||||
"refreshData": null,
|
||||
"multiclass": null,
|
||||
"disabled": false,
|
||||
"description": "<p>You can perform harmless, subtle magical effects at will. For example, you can change an object’s color, create a smell, light a candle, cause a tiny object to float, illuminate a room, or repair a small object.</p>",
|
||||
"effects": {},
|
||||
"actions": [],
|
||||
"type": "class"
|
||||
},
|
||||
"effects": [],
|
||||
"folder": "8I0S9f458qu36qSW",
|
||||
"sort": 200000,
|
||||
"ownership": {
|
||||
"default": 0,
|
||||
"ei8OkswTzyDp4IGC": 3
|
||||
},
|
||||
"flags": {},
|
||||
"_stats": {
|
||||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.344",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "0.0.1",
|
||||
"createdTime": 1748034934147,
|
||||
"modifiedTime": 1748710421634,
|
||||
"lastModifiedBy": "MxkU9FQYKmOxbdzm"
|
||||
},
|
||||
"_key": "!items!ofBmJIn6NWxA0wPz"
|
||||
}
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
{
|
||||
"name": "Rally",
|
||||
"type": "feature",
|
||||
"_id": "8uORDWrXtNFzA00U",
|
||||
"img": "icons/svg/item-bag.svg",
|
||||
"system": {
|
||||
"actionType": "action",
|
||||
"featureType": {
|
||||
"type": "normal",
|
||||
"data": {
|
||||
"property": "spellcastingTrait",
|
||||
"max": 1,
|
||||
"numbers": {},
|
||||
"value": "d6"
|
||||
}
|
||||
},
|
||||
"refreshData": {
|
||||
"type": "session"
|
||||
},
|
||||
"multiclass": null,
|
||||
"disabled": false,
|
||||
"description": "<p>Once per session, describe how you rally the party and give yourself and each of your allies a Rally Die. At level 1, your Rally Die is a d6. A PC can spend their Rally Die to roll it, adding the result to their action roll, reaction roll, damage roll, or to clear a number of Stress equal to the result. At the end of each session, clear all unspent Rally Dice.</p><p>At level 5, your Rally Die increases to a d8.</p>",
|
||||
"effects": {},
|
||||
"actions": [],
|
||||
"type": "class"
|
||||
},
|
||||
"effects": [],
|
||||
"folder": "3QoIMKARHfRnBvQJ",
|
||||
"sort": 200000,
|
||||
"ownership": {
|
||||
"default": 0,
|
||||
"NqO2eQGMjrvUO6v9": 3
|
||||
},
|
||||
"flags": {},
|
||||
"_stats": {
|
||||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.344",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "0.0.1",
|
||||
"createdTime": 1747991484460,
|
||||
"modifiedTime": 1748710434139,
|
||||
"lastModifiedBy": "MxkU9FQYKmOxbdzm"
|
||||
},
|
||||
"_key": "!items!8uORDWrXtNFzA00U"
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
{
|
||||
"name": "Ranger's Focus",
|
||||
"type": "feature",
|
||||
"_id": "b4O4r2HPbWU8s59q",
|
||||
"img": "icons/svg/item-bag.svg",
|
||||
"system": {
|
||||
"actionType": "action",
|
||||
"featureType": {
|
||||
"type": "normal",
|
||||
"data": {
|
||||
"property": "spellcastingTrait",
|
||||
"max": 1,
|
||||
"numbers": {}
|
||||
}
|
||||
},
|
||||
"refreshData": null,
|
||||
"multiclass": null,
|
||||
"disabled": false,
|
||||
"description": "<p>Spend a Hope and make an attack against a target. On a success, deal your attack’s normal damage and temporarily make the attack’s target your Focus. Until this feature ends or you make a different creature yourFocus, you gain the following benefits against your Focus:</p><p>• You know precisely what direction they are in.</p><p>• When you deal damage to them, they must mark a Stress.</p><p>• When you fail an attack against them, you can end your Ranger’s Focus feature to reroll your Duality Dice.</p>",
|
||||
"effects": {},
|
||||
"actions": [],
|
||||
"type": "class"
|
||||
},
|
||||
"effects": [],
|
||||
"folder": "84neMcqoIRAoIvXP",
|
||||
"sort": 200000,
|
||||
"ownership": {
|
||||
"default": 0,
|
||||
"ei8OkswTzyDp4IGC": 3
|
||||
},
|
||||
"flags": {},
|
||||
"_stats": {
|
||||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.344",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "0.0.1",
|
||||
"createdTime": 1748030780267,
|
||||
"modifiedTime": 1748710475164,
|
||||
"lastModifiedBy": "MxkU9FQYKmOxbdzm"
|
||||
},
|
||||
"_key": "!items!b4O4r2HPbWU8s59q"
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
{
|
||||
"name": "Rogue’s Dodge",
|
||||
"type": "feature",
|
||||
"_id": "fPGn9JNV24nt1G9d",
|
||||
"img": "icons/svg/item-bag.svg",
|
||||
"system": {
|
||||
"actionType": "action",
|
||||
"featureType": {
|
||||
"type": "normal",
|
||||
"data": {
|
||||
"property": "spellcastingTrait",
|
||||
"max": 1,
|
||||
"numbers": {}
|
||||
}
|
||||
},
|
||||
"refreshData": null,
|
||||
"multiclass": null,
|
||||
"disabled": false,
|
||||
"description": "<p>Spend 3 Hope to gain a +2 bonus to your Evasion until the next time an attack succeeds against you. Otherwise, this bonus lasts until your next rest.</p>",
|
||||
"effects": {},
|
||||
"actions": [],
|
||||
"type": "class"
|
||||
},
|
||||
"effects": [],
|
||||
"folder": "AzwvgmcvToIUGYti",
|
||||
"sort": 200000,
|
||||
"ownership": {
|
||||
"default": 0,
|
||||
"ei8OkswTzyDp4IGC": 3
|
||||
},
|
||||
"flags": {},
|
||||
"_stats": {
|
||||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.344",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "0.0.1",
|
||||
"createdTime": 1748032185608,
|
||||
"modifiedTime": 1748710477647,
|
||||
"lastModifiedBy": "MxkU9FQYKmOxbdzm"
|
||||
},
|
||||
"_key": "!items!fPGn9JNV24nt1G9d"
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
{
|
||||
"name": "Sneak Attack",
|
||||
"type": "feature",
|
||||
"_id": "PhHOmsoYUDC42by6",
|
||||
"img": "icons/svg/item-bag.svg",
|
||||
"system": {
|
||||
"actionType": "action",
|
||||
"featureType": {
|
||||
"type": "normal",
|
||||
"data": {
|
||||
"property": "spellcastingTrait",
|
||||
"max": 1,
|
||||
"numbers": {}
|
||||
}
|
||||
},
|
||||
"refreshData": null,
|
||||
"multiclass": null,
|
||||
"disabled": false,
|
||||
"description": "<p>When you succeed on an attack while Cloaked or while an ally is within Melee range of your target, add a number of d6s equal to your tier to your damage roll.</p><p>Level 1 is Tier 1</p><p>Levels 2–4 are Tier 2</p><p>Levels 5–7 are Tier 3</p><p>Levels 8–10 are Tier 4</p>",
|
||||
"effects": {},
|
||||
"actions": [],
|
||||
"type": "class"
|
||||
},
|
||||
"effects": [],
|
||||
"folder": "AzwvgmcvToIUGYti",
|
||||
"sort": 150000,
|
||||
"ownership": {
|
||||
"default": 0,
|
||||
"ei8OkswTzyDp4IGC": 3
|
||||
},
|
||||
"flags": {},
|
||||
"_stats": {
|
||||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.344",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "0.0.1",
|
||||
"createdTime": 1748032274441,
|
||||
"modifiedTime": 1748710492033,
|
||||
"lastModifiedBy": "MxkU9FQYKmOxbdzm"
|
||||
},
|
||||
"_key": "!items!PhHOmsoYUDC42by6"
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
{
|
||||
"name": "Strange Patterns",
|
||||
"type": "feature",
|
||||
"_id": "ONtJ7r2g6tN5q6Ga",
|
||||
"img": "icons/svg/item-bag.svg",
|
||||
"system": {
|
||||
"actionType": "action",
|
||||
"featureType": {
|
||||
"type": "normal",
|
||||
"data": {
|
||||
"property": "spellcastingTrait",
|
||||
"max": 1,
|
||||
"numbers": {}
|
||||
}
|
||||
},
|
||||
"refreshData": null,
|
||||
"multiclass": null,
|
||||
"disabled": false,
|
||||
"description": "<p>Choose a number between 1 and 12. When you roll that number on a Duality Die, gain a Hope or clear a Stress. You can change this number when you take a long rest.</p>",
|
||||
"effects": {},
|
||||
"actions": [],
|
||||
"type": "class"
|
||||
},
|
||||
"effects": [],
|
||||
"folder": "8I0S9f458qu36qSW",
|
||||
"sort": 150000,
|
||||
"ownership": {
|
||||
"default": 0,
|
||||
"ei8OkswTzyDp4IGC": 3
|
||||
},
|
||||
"flags": {},
|
||||
"_stats": {
|
||||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.344",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "0.0.1",
|
||||
"createdTime": 1748034976746,
|
||||
"modifiedTime": 1748710516187,
|
||||
"lastModifiedBy": "MxkU9FQYKmOxbdzm"
|
||||
},
|
||||
"_key": "!items!ONtJ7r2g6tN5q6Ga"
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
{
|
||||
"name": "Unstoppable",
|
||||
"type": "feature",
|
||||
"_id": "rlpNYKW18FX4Hw7t",
|
||||
"img": "icons/svg/item-bag.svg",
|
||||
"system": {
|
||||
"actionType": "action",
|
||||
"featureType": {
|
||||
"type": "normal",
|
||||
"data": {
|
||||
"property": "spellcastingTrait",
|
||||
"max": 1,
|
||||
"numbers": {}
|
||||
}
|
||||
},
|
||||
"refreshData": {
|
||||
"type": "longRest"
|
||||
},
|
||||
"multiclass": null,
|
||||
"disabled": false,
|
||||
"description": "<p>Once per long rest, you can become Unstoppable.</p><p>You gain an Unstoppable Die. At level 1, your Unstoppable Die is a [[d4]]. Place it on this sheet in the space provided, starting with the 1 value facing up. After you make a damage roll that deals 1 or more Hit Points to a target, increase the Unstoppable Die value by one. When the die’s value would exceed its maximum value or when the scene ends, remove the die and drop out of Unstoppable. At level 5, your Unstoppable Die increases to a d6.</p><p>While Unstoppable, you gain the following benefits:</p><p>• You reduce the severity of physical damage by one threshold (Severe to Major, Major to Minor, Minor to None).</p><p>• You add the current value of the Unstoppable Die to your damage roll.</p><p>• You can’t be Restrained or Vulnerable.</p>",
|
||||
"effects": {},
|
||||
"actions": [],
|
||||
"type": "class"
|
||||
},
|
||||
"effects": [],
|
||||
"folder": "imOcur5Zv8WcMHXz",
|
||||
"sort": 200000,
|
||||
"ownership": {
|
||||
"default": 0,
|
||||
"ei8OkswTzyDp4IGC": 3
|
||||
},
|
||||
"flags": {},
|
||||
"_stats": {
|
||||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.344",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "0.0.1",
|
||||
"createdTime": 1748030398960,
|
||||
"modifiedTime": 1748710525532,
|
||||
"lastModifiedBy": "MxkU9FQYKmOxbdzm"
|
||||
},
|
||||
"_key": "!items!rlpNYKW18FX4Hw7t"
|
||||
}
|
||||
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