Compare commits

..

23 commits
main ... 1.4.4

Author SHA1 Message Date
WBHarry
4d062a6892 Merge branch 'main' into release 2025-12-31 04:52:34 +01:00
WBHarry
487c1fd9a2 Merge branch 'main' into release 2025-12-29 14:02:53 +01:00
WBHarry
3aa5cd806a Merge branch 'main' into release 2025-12-27 18:17:36 +01:00
WBHarry
2e93b79633 Merge branch 'main' into release 2025-12-24 03:06:02 +01:00
WBHarry
244dbd4902 Merge branch 'main' into release 2025-12-24 01:18:51 +01:00
WBHarry
c7aed6825a Merge branch 'main' into release 2025-12-13 23:06:23 +01:00
WBHarry
9cb5112b62 Merge branch 'main' into release 2025-12-08 02:35:06 +01:00
WBHarry
81b6f7fc51 Merge branch 'main' into release 2025-12-07 00:54:06 +01:00
WBHarry
828fffd552 Merge branch 'main' into release 2025-11-26 09:47:07 +01:00
WBHarry
fc5626ac47 Merge branch 'main' into release 2025-11-25 00:52:11 +01:00
WBHarry
2e62545aa7 Merge branch 'main' into release 2025-11-23 15:41:24 +01:00
WBHarry
b09c712dd5 Merging main 2025-11-20 11:48:58 +01:00
WBHarry
ca4336bd39 Merge branch 'main' into release 2025-11-17 16:55:14 +01:00
WBHarry
77ac11c522 Merge branch 'main' into release 2025-11-17 10:17:50 +01:00
WBHarry
50311679a5 Merge branch 'main' into release 2025-11-11 22:15:30 +01:00
WBHarry
3a7bcd1b0a Merge branch 'main' into release 2025-11-11 18:04:23 +01:00
WBHarry
511e4bd644 Merge branch 'main' into release 2025-11-11 16:23:35 +01:00
WBHarry
395820513b Merge branch 'main' into release 2025-11-11 16:06:03 +01:00
WBHarry
3566ea3fd3 Merge branch 'main' into release 2025-08-26 20:32:04 +02:00
WBHarry
29d502fb97 Merge branch 'main' into release 2025-08-24 21:11:38 +02:00
WBHarry
685a25d25a Merge branch 'main' into release 2025-08-22 01:47:03 +02:00
WBHarry
dd045b3df7 Merge branch 'main' into release 2025-08-19 20:58:05 +02:00
WBHarry
0aabcec340 Raised version 2025-08-19 18:56:30 +02:00
164 changed files with 550 additions and 6053 deletions

View file

@ -16,10 +16,9 @@ import {
settingsRegistration, settingsRegistration,
socketRegistration socketRegistration
} from './module/systemRegistration/_module.mjs'; } from './module/systemRegistration/_module.mjs';
import { placeables, DhTokenLayer } from './module/canvas/_module.mjs'; import { placeables } from './module/canvas/_module.mjs';
import './node_modules/@yaireo/tagify/dist/tagify.css'; import './node_modules/@yaireo/tagify/dist/tagify.css';
import TemplateManager from './module/documents/templateManager.mjs'; import TemplateManager from './module/documents/templateManager.mjs';
import TokenManager from './module/documents/tokenManager.mjs';
CONFIG.DH = SYSTEM; CONFIG.DH = SYSTEM;
CONFIG.TextEditor.enrichers.push(...enricherConfig); CONFIG.TextEditor.enrichers.push(...enricherConfig);
@ -52,8 +51,6 @@ CONFIG.ChatMessage.template = 'systems/daggerheart/templates/ui/chat/chat-messag
CONFIG.Canvas.rulerClass = placeables.DhRuler; CONFIG.Canvas.rulerClass = placeables.DhRuler;
CONFIG.Canvas.layers.templates.layerClass = placeables.DhTemplateLayer; CONFIG.Canvas.layers.templates.layerClass = placeables.DhTemplateLayer;
CONFIG.Canvas.layers.tokens.layerClass = DhTokenLayer;
CONFIG.MeasuredTemplate.objectClass = placeables.DhMeasuredTemplate; CONFIG.MeasuredTemplate.objectClass = placeables.DhMeasuredTemplate;
CONFIG.Scene.documentClass = documents.DhScene; CONFIG.Scene.documentClass = documents.DhScene;
@ -65,7 +62,6 @@ CONFIG.Token.rulerClass = placeables.DhTokenRuler;
CONFIG.Token.hudClass = applications.hud.DHTokenHUD; CONFIG.Token.hudClass = applications.hud.DHTokenHUD;
CONFIG.ui.combat = applications.ui.DhCombatTracker; CONFIG.ui.combat = applications.ui.DhCombatTracker;
CONFIG.ui.nav = applications.ui.DhSceneNavigation;
CONFIG.ui.chat = applications.ui.DhChatLog; CONFIG.ui.chat = applications.ui.DhChatLog;
CONFIG.ui.effectsDisplay = applications.ui.DhEffectsDisplay; CONFIG.ui.effectsDisplay = applications.ui.DhEffectsDisplay;
CONFIG.ui.hotbar = applications.ui.DhHotbar; CONFIG.ui.hotbar = applications.ui.DhHotbar;
@ -77,7 +73,6 @@ CONFIG.ui.countdowns = applications.ui.DhCountdowns;
CONFIG.ux.ContextMenu = applications.ux.DHContextMenu; CONFIG.ux.ContextMenu = applications.ux.DHContextMenu;
CONFIG.ux.TooltipManager = documents.DhTooltipManager; CONFIG.ux.TooltipManager = documents.DhTooltipManager;
CONFIG.ux.TemplateManager = new TemplateManager(); CONFIG.ux.TemplateManager = new TemplateManager();
CONFIG.ux.TokenManager = new TokenManager();
Hooks.once('init', () => { Hooks.once('init', () => {
game.system.api = { game.system.api = {
@ -89,8 +84,6 @@ Hooks.once('init', () => {
fields fields
}; };
game.system.registeredTriggers = new RegisteredTriggers();
const { DocumentSheetConfig } = foundry.applications.apps; const { DocumentSheetConfig } = foundry.applications.apps;
DocumentSheetConfig.unregisterSheet(TokenDocument, 'core', foundry.applications.sheets.TokenConfig); DocumentSheetConfig.unregisterSheet(TokenDocument, 'core', foundry.applications.sheets.TokenConfig);
DocumentSheetConfig.registerSheet(TokenDocument, SYSTEM.id, applications.sheetConfigs.DhTokenConfig, { DocumentSheetConfig.registerSheet(TokenDocument, SYSTEM.id, applications.sheetConfigs.DhTokenConfig, {
@ -309,7 +302,7 @@ Hooks.on('chatMessage', (_, message) => {
target, target,
difficulty, difficulty,
title, title,
label: game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll'), label: 'test',
actionType: null, actionType: null,
advantage advantage
}); });
@ -323,7 +316,7 @@ const updateActorsRangeDependentEffects = async token => {
CONFIG.DH.SETTINGS.gameSettings.variantRules CONFIG.DH.SETTINGS.gameSettings.variantRules
).rangeMeasurement; ).rangeMeasurement;
for (let effect of token.actor?.allApplicableEffects() ?? []) { for (let effect of token.actor.allApplicableEffects()) {
if (!effect.system.rangeDependence?.enabled) continue; if (!effect.system.rangeDependence?.enabled) continue;
const { target, range, type } = effect.system.rangeDependence; const { target, range, type } = effect.system.rangeDependence;
@ -337,14 +330,14 @@ const updateActorsRangeDependentEffects = async token => {
break; break;
} }
// Get required distance and special case 5 feet to test adjacency const distanceBetween = canvas.grid.measurePath([
const required = rangeMeasurement[range]; userTarget.document.movement.destination,
token.movement.destination
]).distance;
const distance = rangeMeasurement[range];
const reverse = type === CONFIG.DH.GENERAL.rangeInclusion.outsideRange.id; const reverse = type === CONFIG.DH.GENERAL.rangeInclusion.outsideRange.id;
const inRange = if (reverse ? distanceBetween <= distance : distanceBetween > distance) {
required === 5
? userTarget.isAdjacentWith(token.object)
: userTarget.distanceTo(token.object) <= required;
if (reverse ? inRange : !inRange) {
enabledEffect = false; enabledEffect = false;
break; break;
} }
@ -358,17 +351,16 @@ const updateAllRangeDependentEffects = async () => {
const effectsAutomation = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).effects; const effectsAutomation = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).effects;
if (!effectsAutomation.rangeDependent) return; if (!effectsAutomation.rangeDependent) return;
const tokens = canvas.scene?.tokens; // Only consider tokens on the active scene
if (!tokens) return; const tokens = game.scenes.find(x => x.active).tokens;
if (game.user.character) { if (game.user.character) {
// The character updates their character's token. There can be only one token. // The character updates their character's token. There can be only one token.
const characterToken = tokens.find(x => x.actor === game.user.character); const characterToken = tokens.find(x => x.actor === game.user.character);
updateActorsRangeDependentEffects(characterToken); updateActorsRangeDependentEffects(characterToken);
} else if (game.user.isActiveGM) { } else if (game.user.isGM) {
// The GM is responsible for all other tokens. // The GM is responsible for all other tokens.
const playerCharacters = game.users.players.filter(x => x.active).map(x => x.character); const playerCharacters = game.users.players.filter(x => x.active).map(x => x.character);
for (const token of tokens.filter(x => !playerCharacters.includes(x.actor))) { for (let token of tokens.filter(x => !playerCharacters.includes(x.actor))) {
updateActorsRangeDependentEffects(token); updateActorsRangeDependentEffects(token);
} }
} }
@ -376,62 +368,13 @@ const updateAllRangeDependentEffects = async () => {
const debouncedRangeEffectCall = foundry.utils.debounce(updateAllRangeDependentEffects, 50); const debouncedRangeEffectCall = foundry.utils.debounce(updateAllRangeDependentEffects, 50);
Hooks.on('targetToken', () => { Hooks.on('targetToken', async (user, token, targeted) => {
debouncedRangeEffectCall(); debouncedRangeEffectCall();
}); });
Hooks.on('refreshToken', (_, options) => { Hooks.on('moveToken', async (movedToken, data) => {
if (options.refreshPosition) {
debouncedRangeEffectCall(); debouncedRangeEffectCall();
}
}); });
Hooks.on('renderCompendiumDirectory', (app, html) => applications.ui.ItemBrowser.injectSidebarButton(html)); Hooks.on('renderCompendiumDirectory', (app, html) => applications.ui.ItemBrowser.injectSidebarButton(html));
Hooks.on('renderDocumentDirectory', (app, html) => applications.ui.ItemBrowser.injectSidebarButton(html)); Hooks.on('renderDocumentDirectory', (app, html) => applications.ui.ItemBrowser.injectSidebarButton(html));
class RegisteredTriggers extends Map {
constructor() {
super();
}
async registerTriggers(trigger, actor, triggeringActorType, uuid, commands) {
const existingTrigger = this.get(trigger);
if (!existingTrigger) this.set(trigger, new Map());
this.get(trigger).set(uuid, { actor, triggeringActorType, commands });
}
async runTrigger(trigger, currentActor, ...args) {
const updates = [];
const triggerSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).triggers;
if (!triggerSettings.enabled) return updates;
const dualityTrigger = this.get(trigger);
if (dualityTrigger) {
for (let { actor, triggeringActorType, commands } of dualityTrigger.values()) {
const triggerData = CONFIG.DH.TRIGGER.triggers[trigger];
if (triggerData.usesActor && triggeringActorType !== 'any') {
if (triggeringActorType === 'self' && currentActor?.uuid !== actor) continue;
else if (triggeringActorType === 'other' && currentActor?.uuid === actor) continue;
}
for (let command of commands) {
try {
const result = await command(...args);
if (result?.updates?.length) updates.push(...result.updates);
} catch (_) {
const triggerName = game.i18n.localize(triggerData.label);
ui.notifications.error(
game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerError', {
trigger: triggerName,
actor: currentActor?.name
})
);
}
}
}
}
return updates;
}
}

View file

@ -69,11 +69,7 @@
}, },
"summon": { "summon": {
"name": "Summon", "name": "Summon",
"tooltip": "Create tokens in the scene.", "tooltip": "Create tokens in the scene."
"error": "You do not have permission to summon tokens or there is no active scene.",
"invalidDrop": "You can only drop Actor entities to summon.",
"chatMessageTitle": "Test2",
"chatMessageHeaderTitle": "Summoning"
} }
}, },
"Config": { "Config": {
@ -94,9 +90,7 @@
"customFormula": "Custom Formula", "customFormula": "Custom Formula",
"formula": "Formula" "formula": "Formula"
}, },
"displayInChat": "Display in chat", "displayInChat": "Display in chat"
"deleteTriggerTitle": "Delete Trigger",
"deleteTriggerContent": "Are you sure you want to delete the {trigger} trigger?"
}, },
"RollField": { "RollField": {
"diceRolling": { "diceRolling": {
@ -126,9 +120,6 @@
}, },
"cost": { "cost": {
"stepTooltip": "+{step} per step" "stepTooltip": "+{step} per step"
},
"summon": {
"dropSummonsHere": "Drop Summons Here"
} }
} }
}, },
@ -203,8 +194,6 @@
"unequip": "Unequip", "unequip": "Unequip",
"useItem": "Use Item" "useItem": "Use Item"
}, },
"defaultHopeDice": "Default Hope Dice",
"defaultFearDice": "Default Fear Dice",
"disadvantageSources": { "disadvantageSources": {
"label": "Disadvantage Sources", "label": "Disadvantage Sources",
"hint": "Add single words or short text as reminders and hints of what a character has disadvantage on." "hint": "Add single words or short text as reminders and hints of what a character has disadvantage on."
@ -237,7 +226,6 @@
"confirmText": "Would you like to level up your companion {name} by {levelChange} levels at this time? (You can do it manually later)" "confirmText": "Would you like to level up your companion {name} by {levelChange} levels at this time? (You can do it manually later)"
}, },
"viewLevelups": "View Levelups", "viewLevelups": "View Levelups",
"viewParty": "View Party",
"InvalidOldCharacterImportTitle": "Old Character Import", "InvalidOldCharacterImportTitle": "Old Character Import",
"InvalidOldCharacterImportText": "Character data exported prior to system version 1.1 will not generate a complete character. Do you wish to continue?", "InvalidOldCharacterImportText": "Character data exported prior to system version 1.1 will not generate a complete character. Do you wish to continue?",
"cancelBeastform": "Cancel Beastform" "cancelBeastform": "Cancel Beastform"
@ -1155,8 +1143,7 @@
"any": "Any", "any": "Any",
"friendly": "Friendly", "friendly": "Friendly",
"hostile": "Hostile", "hostile": "Hostile",
"self": "Self", "self": "Self"
"other": "Other"
}, },
"TemplateTypes": { "TemplateTypes": {
"circle": "Circle", "circle": "Circle",
@ -1230,29 +1217,6 @@
} }
} }
}, },
"Triggers": {
"postDamageReduction": {
"label": "After Damage Reduction"
},
"preDamageReduction": {
"label": "Before Damage Reduction"
},
"dualityRoll": {
"label": "Duality Roll"
},
"fearRoll": {
"label": "Fear Roll"
},
"triggerTexts": {
"strangePatternsContentTitle": "Matched {nr} times.",
"strangePatternsContentSubTitle": "Increase hope and stress to a total of {nr}.",
"ferocityContent": "Spend 2 Hope to gain {bonus} bonus Evasion until after the next attack against you?",
"ferocityEffectDescription": "Your evasion is increased by {bonus}. This bonus lasts until after the next attack made against you."
},
"triggerType": "Trigger Type",
"triggeringActorType": "Triggering Actor Type",
"triggerError": "{trigger} trigger failed for {actor}. It's probably configured wrong."
},
"WeaponFeature": { "WeaponFeature": {
"barrier": { "barrier": {
"name": "Barrier", "name": "Barrier",
@ -2090,10 +2054,10 @@
"partyMembers": "Party Members", "partyMembers": "Party Members",
"projects": "Projects", "projects": "Projects",
"types": "Types", "types": "Types",
"itemFeatures": "Item Features",
"questions": "Questions", "questions": "Questions",
"configuration": "Configuration", "configuration": "Configuration",
"base": "Base", "base": "Base"
"triggers": "Triggers"
}, },
"Tiers": { "Tiers": {
"singular": "Tier", "singular": "Tier",
@ -2214,10 +2178,6 @@
"stress": "Stress", "stress": "Stress",
"subclasses": "Subclasses", "subclasses": "Subclasses",
"success": "Success", "success": "Success",
"summon": {
"single": "Summon",
"plural": "Summons"
},
"take": "Take", "take": "Take",
"Target": { "Target": {
"single": "Target", "single": "Target",
@ -2284,8 +2244,7 @@
"placeholder": "Using character dimensions", "placeholder": "Using character dimensions",
"disabledPlaceholder": "Set by character size", "disabledPlaceholder": "Set by character size",
"height": { "label": "Height" }, "height": { "label": "Height" },
"width": { "label": "Width" }, "width": { "label": "Width" }
"scale": { "label": "Token Scale" }
}, },
"evolved": { "evolved": {
"maximumTier": { "label": "Maximum Tier" }, "maximumTier": { "label": "Maximum Tier" },
@ -2334,9 +2293,6 @@
"DomainCard": { "DomainCard": {
"type": "Type", "type": "Type",
"recallCost": "Recall Cost", "recallCost": "Recall Cost",
"vaultActive": "Active In Vault",
"loadoutIgnore": "Ignores Loadout Limits",
"domainTouched": "Domain Touched",
"foundationTitle": "Foundation", "foundationTitle": "Foundation",
"specializationTitle": "Specialization", "specializationTitle": "Specialization",
"masteryTitle": "Mastery" "masteryTitle": "Mastery"
@ -2475,12 +2431,6 @@
"hint": "Automatically apply effects. Targets must be selected before the action is made and Reaction Roll Automation must be different than Never. Bypass users permissions." "hint": "Automatically apply effects. Targets must be selected before the action is made and Reaction Roll Automation must be different than Never. Bypass users permissions."
} }
}, },
"triggers": {
"enabled": {
"label": "Enabled",
"hint": "Advanced automation such as triggering a popup for a wizard's Strange Patterns"
}
},
"summaryMessages": { "summaryMessages": {
"label": "Summary Messages" "label": "Summary Messages"
} }
@ -2490,9 +2440,6 @@
}, },
"roll": { "roll": {
"title": "Actions" "title": "Actions"
},
"trigger": {
"title": "Triggers"
} }
}, },
"Homebrew": { "Homebrew": {
@ -2620,9 +2567,7 @@
} }
}, },
"disabledText": "Daggerheart Measurements are disabled in System Settings - Variant Rules", "disabledText": "Daggerheart Measurements are disabled in System Settings - Variant Rules",
"rangeMeasurement": "Range Measurement", "rangeMeasurement": "Range Measurement"
"sceneEnvironments": "Scene Environments",
"dragEnvironmentHere": "Drag environments here"
} }
}, },
"UI": { "UI": {
@ -2835,10 +2780,7 @@
"gmRequired": "This action requires an online GM", "gmRequired": "This action requires an online GM",
"gmOnly": "This can only be accessed by the GM", "gmOnly": "This can only be accessed by the GM",
"noActorOwnership": "You do not have permissions for this character", "noActorOwnership": "You do not have permissions for this character",
"documentIsMissing": "The {documentType} is missing from the world.", "documentIsMissing": "The {documentType} is missing from the world."
"tokenActorMissing": "{name} is missing an Actor",
"tokenActorsMissing": "[{names}] missing Actors",
"domainTouchRequirement": "This domain card requires {nr} {domain} cards in the loadout to be used"
}, },
"Sidebar": { "Sidebar": {
"actorDirectory": { "actorDirectory": {
@ -2883,8 +2825,7 @@
"deleteItem": "Delete Item", "deleteItem": "Delete Item",
"immune": "Immune", "immune": "Immune",
"middleClick": "[Middle Click] Keep tooltip view", "middleClick": "[Middle Click] Keep tooltip view",
"tokenSize": "The token size used on the canvas", "tokenSize": "The token size used on the canvas"
"previewTokenHelp": "Left-click to place, right-click to cancel"
} }
} }
} }

View file

@ -10,7 +10,6 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
this.config = config; this.config = config;
this.config.experiences = []; this.config.experiences = [];
this.reactionOverride = config.actionType === 'reaction'; this.reactionOverride = config.actionType === 'reaction';
this.selectedEffects = this.config.bonusEffects;
if (config.source?.action) { if (config.source?.action) {
this.item = config.data.parent.items.get(config.source.item) ?? config.data.parent; this.item = config.data.parent.items.get(config.source.item) ?? config.data.parent;
@ -36,7 +35,6 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
selectExperience: this.selectExperience, selectExperience: this.selectExperience,
toggleReaction: this.toggleReaction, toggleReaction: this.toggleReaction,
toggleTagTeamRoll: this.toggleTagTeamRoll, toggleTagTeamRoll: this.toggleTagTeamRoll,
toggleSelectedEffect: this.toggleSelectedEffect,
submitRoll: this.submitRoll submitRoll: this.submitRoll
}, },
form: { form: {
@ -78,9 +76,6 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
icon icon
})); }));
context.hasSelectedEffects = Boolean(this.selectedEffects && Object.keys(this.selectedEffects).length);
context.selectedEffects = this.selectedEffects;
this.config.costs ??= []; this.config.costs ??= [];
if (this.config.costs?.length) { if (this.config.costs?.length) {
const updatedCosts = game.system.api.fields.ActionFields.CostField.calcCosts.call( const updatedCosts = game.system.api.fields.ActionFields.CostField.calcCosts.call(
@ -109,7 +104,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
context.roll = this.roll; context.roll = this.roll;
context.rollType = this.roll?.constructor.name; context.rollType = this.roll?.constructor.name;
context.rallyDie = this.roll.rallyChoices; context.rallyDie = this.roll.rallyChoices;
const experiences = this.config.data?.system?.experiences || {}; const experiences = this.config.data?.system.experiences || {};
context.experiences = Object.keys(experiences).map(id => ({ context.experiences = Object.keys(experiences).map(id => ({
id, id,
...experiences[id] ...experiences[id]
@ -213,11 +208,6 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
this.render(); this.render();
} }
static toggleSelectedEffect(_event, button) {
this.selectedEffects[button.dataset.key].selected = !this.selectedEffects[button.dataset.key].selected;
this.render();
}
static async submitRoll() { static async submitRoll() {
await this.close({ submitted: true }); await this.close({ submitted: true });
} }

View file

@ -6,7 +6,6 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
this.roll = roll; this.roll = roll;
this.config = config; this.config = config;
this.selectedEffects = this.config.bonusEffects;
} }
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
@ -21,7 +20,6 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
icon: 'fa-solid fa-dice' icon: 'fa-solid fa-dice'
}, },
actions: { actions: {
toggleSelectedEffect: this.toggleSelectedEffect,
submitRoll: this.submitRoll submitRoll: this.submitRoll
}, },
form: { form: {
@ -59,9 +57,6 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
icon icon
})); }));
context.modifiers = this.config.modifiers; context.modifiers = this.config.modifiers;
context.hasSelectedEffects = Boolean(Object.keys(this.selectedEffects).length);
context.selectedEffects = this.selectedEffects;
return context; return context;
} }
@ -74,11 +69,6 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
this.render(); this.render();
} }
static toggleSelectedEffect(_event, button) {
this.selectedEffects[button.dataset.key].selected = !this.selectedEffects[button.dataset.key].selected;
this.render();
}
static async submitRoll() { static async submitRoll() {
await this.close({ submitted: true }); await this.close({ submitted: true });
} }

View file

@ -93,9 +93,7 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
} }
getRefreshables() { getRefreshables() {
const actionItems = this.actor.items const actionItems = this.actor.items.filter(x => this.actor.system.isItemAvailable(x)).reduce((acc, x) => {
.filter(x => this.actor.system.isItemAvailable(x))
.reduce((acc, x) => {
if (x.system.actions) { if (x.system.actions) {
const recoverable = x.system.actions.reduce((acc, action) => { const recoverable = x.system.actions.reduce((acc, action) => {
if (refreshIsAllowed([this.shortrest ? 'shortRest' : 'longRest'], action.uses.recovery)) { if (refreshIsAllowed([this.shortrest ? 'shortRest' : 'longRest'], action.uses.recovery)) {
@ -191,8 +189,7 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
})); }));
}); });
}); });
const characters = game.actors const characters = game.actors.filter(x => x.type === 'character')
.filter(x => x.type === 'character')
.filter(x => x.testUserPermission(game.user, 'LIMITED')) .filter(x => x.testUserPermission(game.user, 'LIMITED'))
.filter(x => x.uuid !== this.actor.uuid); .filter(x => x.uuid !== this.actor.uuid);

View file

@ -77,7 +77,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
cost: this.data.initiator.cost cost: this.data.initiator.cost
}; };
const selectedMember = Object.values(context.members).find(x => x.selected && x.roll); const selectedMember = Object.values(context.members).find(x => x.selected);
const selectedIsCritical = selectedMember?.roll?.system?.isCritical; const selectedIsCritical = selectedMember?.roll?.system?.isCritical;
context.selectedData = { context.selectedData = {
result: selectedMember result: selectedMember

View file

@ -21,8 +21,6 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
async _prepareContext(options) { async _prepareContext(options) {
const context = await super._prepareContext(options); const context = await super._prepareContext(options);
if (!this.actor) return context;
context.partyOnCanvas = context.partyOnCanvas =
this.actor.type === 'party' && this.actor.type === 'party' &&
this.actor.system.partyMembers.some(member => member.getActiveTokens().length > 0); this.actor.system.partyMembers.some(member => member.getActiveTokens().length > 0);
@ -60,33 +58,14 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
} }
static async #onToggleCombat() { static async #onToggleCombat() {
const tokensWithoutActors = canvas.tokens.controlled.filter(t => !t.actor);
const warning =
tokensWithoutActors.length === 1
? game.i18n.format('DAGGERHEART.UI.Notifications.tokenActorMissing', {
name: tokensWithoutActors[0].name
})
: game.i18n.format('DAGGERHEART.UI.Notifications.tokenActorsMissing', {
names: tokensWithoutActors.map(x => x.name).join(', ')
});
const tokens = canvas.tokens.controlled const tokens = canvas.tokens.controlled
.filter(t => t.actor && !DHTokenHUD.#nonCombatTypes.includes(t.actor.type)) .filter(t => !t.actor || !DHTokenHUD.#nonCombatTypes.includes(t.actor.type))
.map(t => t.document); .map(t => t.document);
if (!this.object.controlled && this.document.actor) tokens.push(this.document); if (!this.object.controlled) tokens.push(this.document);
try { try {
if (this.document.inCombat) { if (this.document.inCombat) await TokenDocument.implementation.deleteCombatants(tokens);
const tokensInCombat = tokens.filter(t => t.inCombat); else await TokenDocument.implementation.createCombatants(tokens);
await TokenDocument.implementation.deleteCombatants([...tokensInCombat, ...tokensWithoutActors]);
} else {
if (tokensWithoutActors.length) {
ui.notifications.warn(warning);
}
const tokensOutOfCombat = tokens.filter(t => !t.inCombat);
await TokenDocument.implementation.createCombatants(tokensOutOfCombat);
}
} catch (err) { } catch (err) {
ui.notifications.warn(err.message); ui.notifications.warn(err.message);
} }

View file

@ -1,25 +1,16 @@
import { RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
export default class DhSceneConfigSettings extends foundry.applications.sheets.SceneConfig { export default class DhSceneConfigSettings extends foundry.applications.sheets.SceneConfig {
constructor(options) { // static DEFAULT_OPTIONS = {
super(options); // ...super.DEFAULT_OPTIONS,
// form: {
Hooks.on(socketEvent.Refresh, ({ refreshType }) => { // handler: this.updateData,
if (refreshType === RefreshType.Scene) this.render(); // closeOnSubmit: true
}); // }
} // };
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
actions: {
...super.DEFAULT_OPTIONS.actions,
removeSceneEnvironment: DhSceneConfigSettings.#removeSceneEnvironment
}
};
static buildParts() { static buildParts() {
const { footer, tabs, ...parts } = super.PARTS; const { footer, tabs, ...parts } = super.PARTS;
const tmpParts = { const tmpParts = {
// tabs,
tabs: { template: 'systems/daggerheart/templates/scene/tabs.hbs' }, tabs: { template: 'systems/daggerheart/templates/scene/tabs.hbs' },
...parts, ...parts,
dh: { template: 'systems/daggerheart/templates/scene/dh-config.hbs' }, dh: { template: 'systems/daggerheart/templates/scene/dh-config.hbs' },
@ -37,47 +28,27 @@ export default class DhSceneConfigSettings extends foundry.applications.sheets.S
static TABS = DhSceneConfigSettings.buildTabs(); static TABS = DhSceneConfigSettings.buildTabs();
async _preRender(context, options) {
await super._preFirstRender(context, options);
if (!options.internalRefresh)
this.daggerheartFlag = new game.system.api.data.scenes.DHScene(this.document.flags.daggerheart);
}
_attachPartListeners(partId, htmlElement, options) { _attachPartListeners(partId, htmlElement, options) {
super._attachPartListeners(partId, htmlElement, options); super._attachPartListeners(partId, htmlElement, options);
switch (partId) { switch (partId) {
case 'dh': case 'dh':
htmlElement.querySelector('#rangeMeasurementSetting')?.addEventListener('change', async event => { htmlElement.querySelector('#rangeMeasurementSetting')?.addEventListener('change', async event => {
this.daggerheartFlag.updateSource({ rangeMeasurement: { setting: event.target.value } }); const flagData = foundry.utils.mergeObject(this.document.flags.daggerheart, {
this.render({ internalRefresh: true }); rangeMeasurement: { setting: event.target.value }
});
this.document.flags.daggerheart = flagData;
this.render();
}); });
const dragArea = htmlElement.querySelector('.scene-environments');
if (dragArea) dragArea.ondrop = this._onDrop.bind(this);
break; break;
} }
} }
async _onDrop(event) {
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
const item = await foundry.utils.fromUuid(data.uuid);
if (item instanceof game.system.api.documents.DhpActor && item.type === 'environment') {
await this.daggerheartFlag.updateSource({
sceneEnvironments: [...this.daggerheartFlag.sceneEnvironments, data.uuid]
});
this.render({ internalRefresh: true });
}
}
/** @inheritDoc */ /** @inheritDoc */
async _preparePartContext(partId, context, options) { async _preparePartContext(partId, context, options) {
context = await super._preparePartContext(partId, context, options); context = await super._preparePartContext(partId, context, options);
switch (partId) { switch (partId) {
case 'dh': case 'dh':
context.data = this.daggerheartFlag; context.data = new game.system.api.data.scenes.DHScene(canvas.scene.flags.daggerheart);
context.variantRules = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules); context.variantRules = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules);
break; break;
} }
@ -85,24 +56,8 @@ export default class DhSceneConfigSettings extends foundry.applications.sheets.S
return context; return context;
} }
static async #removeSceneEnvironment(_event, button) { // static async updateData(event, _, formData) {
await this.daggerheartFlag.updateSource({ // const data = foundry.utils.expandObject(formData.object);
sceneEnvironments: this.daggerheartFlag.sceneEnvironments.filter( // this.close(data);
(_, index) => index !== Number.parseInt(button.dataset.index) // }
)
});
this.render({ internalRefresh: true });
}
/** @override */
async _processSubmitData(event, form, submitData, options) {
submitData.flags.daggerheart = this.daggerheartFlag.toObject();
for (const key of Object.keys(this.document._source.flags.daggerheart?.sceneEnvironments ?? {})) {
if (!submitData.flags.daggerheart.sceneEnvironments[key]) {
submitData.flags.daggerheart.sceneEnvironments[`-=${key}`] = null;
}
}
super._processSubmitData(event, form, submitData, options);
}
} }

View file

@ -7,7 +7,6 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
this.action = action; this.action = action;
this.openSection = null; this.openSection = null;
this.openTrigger = this.action.triggers.length > 0 ? 0 : null;
} }
get title() { get title() {
@ -16,7 +15,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
tag: 'form', tag: 'form',
classes: ['daggerheart', 'dh-style', 'action-config', 'dialog', 'max-800'], classes: ['daggerheart', 'dh-style', 'dialog', 'max-800'],
window: { window: {
icon: 'fa-solid fa-wrench', icon: 'fa-solid fa-wrench',
resizable: false resizable: false
@ -30,18 +29,13 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
removeElement: this.removeElement, removeElement: this.removeElement,
editEffect: this.editEffect, editEffect: this.editEffect,
addDamage: this.addDamage, addDamage: this.addDamage,
removeDamage: this.removeDamage, removeDamage: this.removeDamage
editDoc: this.editDoc,
addTrigger: this.addTrigger,
removeTrigger: this.removeTrigger,
expandTrigger: this.expandTrigger
}, },
form: { form: {
handler: this.updateForm, handler: this.updateForm,
submitOnChange: true, submitOnChange: true,
closeOnSubmit: false closeOnSubmit: false
}, }
dragDrop: [{ dragSelector: null, dropSelector: '#summon-drop-zone', handlers: ['_onDrop'] }]
}; };
static PARTS = { static PARTS = {
@ -61,10 +55,6 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
effect: { effect: {
id: 'effect', id: 'effect',
template: 'systems/daggerheart/templates/sheets-settings/action-settings/effect.hbs' template: 'systems/daggerheart/templates/sheets-settings/action-settings/effect.hbs'
},
trigger: {
id: 'trigger',
template: 'systems/daggerheart/templates/sheets-settings/action-settings/trigger.hbs'
} }
}; };
@ -92,18 +82,10 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
id: 'effect', id: 'effect',
icon: null, icon: null,
label: 'DAGGERHEART.GENERAL.Tabs.effects' label: 'DAGGERHEART.GENERAL.Tabs.effects'
},
trigger: {
active: false,
cssClass: '',
group: 'primary',
id: 'trigger',
icon: null,
label: 'DAGGERHEART.GENERAL.Tabs.triggers'
} }
}; };
static CLEAN_ARRAYS = ['damage.parts', 'cost', 'effects', 'summon']; static CLEAN_ARRAYS = ['damage.parts', 'cost', 'effects'];
_getTabs(tabs) { _getTabs(tabs) {
for (const v of Object.values(tabs)) { for (const v of Object.values(tabs)) {
@ -114,24 +96,9 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
return tabs; return tabs;
} }
_attachPartListeners(partId, htmlElement, options) {
super._attachPartListeners(partId, htmlElement, options);
htmlElement.querySelectorAll('.summon-count-wrapper input').forEach(element => {
element.addEventListener('change', this.updateSummonCount.bind(this));
});
}
async _prepareContext(_options) { async _prepareContext(_options) {
const context = await super._prepareContext(_options, 'action'); const context = await super._prepareContext(_options, 'action');
context.source = this.action.toObject(true); context.source = this.action.toObject(true);
context.summons = [];
for (const summon of context.source.summon ?? []) {
const actor = await foundry.utils.fromUuid(summon.actorUUID);
context.summons.push({ actor, count: summon.count });
}
context.openSection = this.openSection; context.openSection = this.openSection;
context.tabs = this._getTabs(this.constructor.TABS); context.tabs = this._getTabs(this.constructor.TABS);
context.config = CONFIG.DH; context.config = CONFIG.DH;
@ -144,16 +111,6 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
context.baseSaveDifficulty = this.action.actor?.baseSaveDifficulty; context.baseSaveDifficulty = this.action.actor?.baseSaveDifficulty;
context.baseAttackBonus = this.action.actor?.system.attack?.roll.bonus; context.baseAttackBonus = this.action.actor?.system.attack?.roll.bonus;
context.hasRoll = this.action.hasRoll; context.hasRoll = this.action.hasRoll;
context.triggers = context.source.triggers.map((trigger, index) => {
const { hint, returns, usesActor } = CONFIG.DH.TRIGGER.triggers[trigger.trigger];
return {
...trigger,
hint,
returns,
usesActor,
revealed: this.openTrigger === index
};
});
const settingsTiers = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers).tiers; const settingsTiers = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers).tiers;
context.tierOptions = [ context.tierOptions = [
@ -224,9 +181,8 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
} }
static async updateForm(event, _, formData) { static async updateForm(event, _, formData) {
const submitData = this._prepareSubmitData(event, formData); const submitData = this._prepareSubmitData(event, formData),
data = foundry.utils.mergeObject(this.action.toObject(), submitData);
const data = foundry.utils.mergeObject(this.action.toObject(), submitData);
this.action = await this.action.update(data); this.action = await this.action.update(data);
this.sheetUpdate?.(this.action); this.sheetUpdate?.(this.action);
@ -245,26 +201,12 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
static removeElement(event, button) { static removeElement(event, button) {
event.stopPropagation(); event.stopPropagation();
const data = this.action.toObject(), const data = this.action.toObject(),
key = event.target.closest('[data-key]').dataset.key; key = event.target.closest('[data-key]').dataset.key,
index = button.dataset.index;
// Prefer explicit index, otherwise find by uuid
let index = button?.dataset.index;
if (index === undefined || index === null || index === '') {
const uuid = button?.dataset.uuid ?? button?.dataset.itemUuid;
index = data[key].findIndex(e => (e?.actorUUID ?? e?.uuid) === uuid);
if (index === -1) return;
} else index = Number(index);
data[key].splice(index, 1); data[key].splice(index, 1);
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) }); this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
} }
static async editDoc(_event, target) {
const element = target.closest('[data-item-uuid]');
const doc = (await foundry.utils.fromUuid(element.dataset.itemUuid)) ?? null;
if (doc) return doc.sheet.render({ force: true });
}
static addDamage(_event) { static addDamage(_event) {
if (!this.action.damage.parts) return; if (!this.action.damage.parts) return;
const data = this.action.toObject(), const data = this.action.toObject(),
@ -282,69 +224,6 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) }); this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
} }
static addTrigger() {
const data = this.action.toObject();
data.triggers.push({
trigger: CONFIG.DH.TRIGGER.triggers.dualityRoll.id,
triggeringActor: CONFIG.DH.TRIGGER.triggerActorTargetType.any.id
});
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
}
static async removeTrigger(_event, button) {
const trigger = CONFIG.DH.TRIGGER.triggers[this.action.triggers[button.dataset.index].trigger];
const confirmed = await foundry.applications.api.DialogV2.confirm({
window: {
title: game.i18n.localize('DAGGERHEART.ACTIONS.Config.deleteTriggerTitle')
},
content: game.i18n.format('DAGGERHEART.ACTIONS.Config.deleteTriggerContent', {
trigger: game.i18n.localize(trigger.label)
})
});
if (!confirmed) return;
const data = this.action.toObject();
data.triggers = data.triggers.filter((_, index) => index !== Number.parseInt(button.dataset.index));
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
}
static async expandTrigger(_event, button) {
const index = Number.parseInt(button.dataset.index);
const toggle = (element, codeMirror) => {
codeMirror.classList.toggle('revealed');
const button = element.querySelector('a > i');
button.classList.toggle('fa-angle-up');
button.classList.toggle('fa-angle-down');
};
const fieldset = button.closest('fieldset');
const codeMirror = fieldset.querySelector('.code-mirror-wrapper');
toggle(fieldset, codeMirror);
if (this.openTrigger !== null && this.openTrigger !== index) {
const previouslyExpanded = fieldset
.closest(`section`)
.querySelector(`fieldset[data-index="${this.openTrigger}"]`);
const codeMirror = previouslyExpanded.querySelector('.code-mirror-wrapper');
toggle(previouslyExpanded, codeMirror);
this.openTrigger = index;
} else if (this.openTrigger === index) {
this.openTrigger = null;
} else {
this.openTrigger = index;
}
}
updateSummonCount(event) {
event.stopPropagation();
const wrapper = event.target.closest('.summon-count-wrapper');
const index = wrapper.dataset.index;
const data = this.action.toObject();
data.summon[index].count = event.target.value;
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
}
/** Specific implementation in extending classes **/ /** Specific implementation in extending classes **/
static async addEffect(_event) {} static async addEffect(_event) {}
static removeEffect(_event, _button) {} static removeEffect(_event, _button) {}
@ -354,29 +233,4 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
this.tabGroups.primary = 'base'; this.tabGroups.primary = 'base';
await super.close(options); await super.close(options);
} }
async _onDrop(event) {
const data = foundry.applications.ux.TextEditor.getDragEventData(event);
const item = await foundry.utils.fromUuid(data.uuid);
if (!(item instanceof game.system.api.documents.DhpActor)) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.ACTIONS.TYPES.summon.invalidDrop'));
return;
}
const actionData = this.action.toObject();
let countvalue = 1;
for (const entry of actionData.summon) {
if (entry.actorUUID === data.uuid) {
entry.count += 1;
countvalue = entry.count;
await this.constructor.updateForm.bind(this)(null, null, {
object: foundry.utils.flattenObject(actionData)
});
return;
}
}
actionData.summon.push({ actorUUID: data.uuid, count: countvalue });
await this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(actionData) });
}
} }

View file

@ -31,7 +31,7 @@ export default class AdversarySheet extends DHBaseActorSheet {
dragSelector: '[data-item-id][draggable="true"], [data-item-id] [draggable="true"]', dragSelector: '[data-item-id][draggable="true"], [data-item-id] [draggable="true"]',
dropSelector: null dropSelector: null
} }
] ],
}; };
static PARTS = { static PARTS = {
@ -185,6 +185,7 @@ export default class AdversarySheet extends DHBaseActorSheet {
super._onDragStart(event); super._onDragStart(event);
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
/* Application Clicks Actions */ /* Application Clicks Actions */
/* -------------------------------------------- */ /* -------------------------------------------- */

View file

@ -32,8 +32,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
handleResourceDice: CharacterSheet.#handleResourceDice, handleResourceDice: CharacterSheet.#handleResourceDice,
advanceResourceDie: CharacterSheet.#advanceResourceDie, advanceResourceDie: CharacterSheet.#advanceResourceDie,
cancelBeastform: CharacterSheet.#cancelBeastform, cancelBeastform: CharacterSheet.#cancelBeastform,
useDowntime: this.useDowntime, useDowntime: this.useDowntime
viewParty: CharacterSheet.#viewParty
}, },
window: { window: {
resizable: true, resizable: true,
@ -338,20 +337,15 @@ export default class CharacterSheet extends DHBaseActorSheet {
} }
const type = 'effect'; const type = 'effect';
const cls = game.system.api.models.actions.actionsTypes[type]; const cls = game.system.api.models.actions.actionsTypes[type];
const action = new cls( const action = new cls({
{
...cls.getSourceConfig(doc.system), ...cls.getSourceConfig(doc.system),
type: type, type: type,
chatDisplay: false, chatDisplay: false,
cost: [ cost: [{
{
key: 'stress', key: 'stress',
value: doc.system.recallCost value: doc.system.recallCost
} }]
] }, { parent: doc.system });
},
{ parent: doc.system }
);
const config = await action.use(event); const config = await action.use(event);
if (config) { if (config) {
return doc.update({ 'system.inVault': false }); return doc.update({ 'system.inVault': false });
@ -712,10 +706,8 @@ export default class CharacterSheet extends DHBaseActorSheet {
headerTitle: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', { headerTitle: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
ability: abilityLabel ability: abilityLabel
}), }),
effects: Array.from(await this.document.allApplicableEffects()),
roll: { roll: {
trait: button.dataset.attribute, trait: button.dataset.attribute
type: 'trait'
}, },
hasRoll: true, hasRoll: true,
actionType: 'action', actionType: 'action',
@ -725,7 +717,6 @@ export default class CharacterSheet extends DHBaseActorSheet {
}) })
}; };
const result = await this.document.diceRoll(config); const result = await this.document.diceRoll(config);
if (!result) return;
/* This could be avoided by baking config.costs into config.resourceUpdates. Didn't feel like messing with it at the time */ /* This could be avoided by baking config.costs into config.resourceUpdates. Didn't feel like messing with it at the time */
const costResources = result.costs const costResources = result.costs
@ -830,7 +821,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
static async #toggleVault(_event, button) { static async #toggleVault(_event, button) {
const doc = await getDocFromElement(button); const doc = await getDocFromElement(button);
const { available } = this.document.system.loadoutSlot; const { available } = this.document.system.loadoutSlot;
if (doc.system.inVault && !available && !doc.system.loadoutIgnore) { if (doc.system.inVault && !available) {
return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.loadoutMaxReached')); return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.loadoutMaxReached'));
} }
@ -901,41 +892,6 @@ export default class CharacterSheet extends DHBaseActorSheet {
game.system.api.fields.ActionFields.BeastformField.handleActiveTransformations.call(item); game.system.api.fields.ActionFields.BeastformField.handleActiveTransformations.call(item);
} }
static async #viewParty(_, target) {
const parties = this.document.parties;
if (parties.size <= 1) {
parties.first()?.sheet.render({ force: true });
return;
}
const buttons = parties.map(p => {
const button = document.createElement('button');
button.type = 'button';
button.classList.add('plain');
const img = document.createElement('img');
img.src = p.img;
button.append(img);
const name = document.createElement('span');
name.textContent = p.name;
button.append(name);
button.addEventListener('click', () => {
p.sheet?.render({ force: true });
game.tooltip.dismissLockedTooltips();
});
return button;
});
const html = document.createElement('div');
html.classList.add('party-list');
html.append(...buttons);
game.tooltip.dismissLockedTooltips();
game.tooltip.activate(target, {
html,
locked: true
});
}
/** /**
* Open the downtime application. * Open the downtime application.
* @type {ApplicationClickAction} * @type {ApplicationClickAction}

View file

@ -211,7 +211,7 @@ export default function DHApplicationMixin(Base) {
const step = event.key === 'ArrowUp' ? 1 : event.key === 'ArrowDown' ? -1 : 0; const step = event.key === 'ArrowUp' ? 1 : event.key === 'ArrowDown' ? -1 : 0;
if (step !== 0) { if (step !== 0) {
handleUpdate(step); handleUpdate(step);
deltaInput.dispatchEvent(new Event('change', { bubbles: true })); deltaInput.dispatchEvent(new Event("change", { bubbles: true }));
} }
}); });
@ -222,7 +222,7 @@ export default function DHApplicationMixin(Base) {
if (deltaInput === document.activeElement) { if (deltaInput === document.activeElement) {
event.preventDefault(); event.preventDefault();
handleUpdate(Math.sign(-1 * event.deltaY)); handleUpdate(Math.sign(-1 * event.deltaY));
deltaInput.dispatchEvent(new Event('change', { bubbles: true })); deltaInput.dispatchEvent(new Event("change", { bubbles: true }));
} }
}, },
{ passive: false } { passive: false }
@ -236,7 +236,7 @@ export default function DHApplicationMixin(Base) {
// Handle contenteditable // Handle contenteditable
for (const input of htmlElement.querySelectorAll('[contenteditable][data-property]')) { for (const input of htmlElement.querySelectorAll('[contenteditable][data-property]')) {
const property = input.dataset.property; const property = input.dataset.property;
input.addEventListener('blur', () => { input.addEventListener("blur", () => {
const selection = document.getSelection(); const selection = document.getSelection();
if (input.contains(selection.anchorNode)) { if (input.contains(selection.anchorNode)) {
selection.empty(); selection.empty();
@ -244,12 +244,12 @@ export default function DHApplicationMixin(Base) {
this.document.update({ [property]: input.textContent }); this.document.update({ [property]: input.textContent });
}); });
input.addEventListener('keydown', event => { input.addEventListener("keydown", event => {
if (event.key === 'Enter') input.blur(); if (event.key === "Enter") input.blur();
}); });
// Chrome sometimes add <br>, which aren't a problem for the value but are for the placeholder // Chrome sometimes add <br>, which aren't a problem for the value but are for the placeholder
input.addEventListener('input', () => input.querySelectorAll('br').forEach(i => i.remove())); input.addEventListener("input", () => input.querySelectorAll("br").forEach((i) => i.remove()));
} }
} }
@ -505,7 +505,6 @@ export default function DHApplicationMixin(Base) {
const doc = await getDocFromElement(target), const doc = await getDocFromElement(target),
action = doc?.system?.attack ?? doc; action = doc?.system?.attack ?? doc;
const config = action.prepareConfig(event); const config = action.prepareConfig(event);
config.effects = Array.from(await this.document.allApplicableEffects());
config.hasRoll = false; config.hasRoll = false;
return action && action.workflow.get('damage').execute(config, null, true); return action && action.workflow.get('damage').execute(config, null, true);
} }
@ -586,9 +585,7 @@ export default function DHApplicationMixin(Base) {
if (!doc || !descriptionElement) continue; if (!doc || !descriptionElement) continue;
// localize the description (idk if it's still necessary) // localize the description (idk if it's still necessary)
const description = doc.system?.getEnrichedDescription const description = game.i18n.localize(doc.system?.description ?? doc.description);
? await doc.system.getEnrichedDescription()
: game.i18n.localize(doc.system?.description ?? doc.description);
// Enrich the description and attach it; // Enrich the description and attach it;
const isAction = doc.documentName === 'Action'; const isAction = doc.documentName === 'Action';
@ -739,7 +736,7 @@ export default function DHApplicationMixin(Base) {
}; };
if (inVault) data['system.inVault'] = true; if (inVault) data['system.inVault'] = true;
if (disabled) data.disabled = true; if (disabled) data.disabled = true;
if (type === 'domainCard' && parent?.system.domains?.length) { if (type === "domainCard" && parent?.system.domains?.length) {
data.system.domain = parent.system.domains[0]; data.system.domain = parent.system.domains[0];
} }

View file

@ -76,10 +76,16 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
/**@inheritdoc */ /**@inheritdoc */
async _preparePartContext(partId, context, options) { async _preparePartContext(partId, context, options) {
await super._preparePartContext(partId, context, options); await super._preparePartContext(partId, context, options);
const { TextEditor } = foundry.applications.ux;
switch (partId) { switch (partId) {
case 'description': case 'description':
context.enrichedDescription = await this.document.system.getEnrichedDescription(); const value = foundry.utils.getProperty(this.document, 'system.description') ?? '';
context.enrichedDescription = await TextEditor.enrichHTML(value, {
relativeTo: this.item,
rollData: this.item.getRollData(),
secrets: this.item.isOwner
});
break; break;
case 'effects': case 'effects':
await this._prepareEffectsContext(context, options); await this._prepareEffectsContext(context, options);

View file

@ -5,5 +5,4 @@ export { default as DhCombatTracker } from './combatTracker.mjs';
export { default as DhEffectsDisplay } from './effectsDisplay.mjs'; export { default as DhEffectsDisplay } from './effectsDisplay.mjs';
export { default as DhFearTracker } from './fearTracker.mjs'; export { default as DhFearTracker } from './fearTracker.mjs';
export { default as DhHotbar } from './hotbar.mjs'; export { default as DhHotbar } from './hotbar.mjs';
export { default as DhSceneNavigation } from './sceneNavigation.mjs';
export { ItemBrowser } from './itemBrowser.mjs'; export { ItemBrowser } from './itemBrowser.mjs';

View file

@ -135,7 +135,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
async actionUseButton(event, message) { async actionUseButton(event, message) {
const { moveIndex, actionIndex, movePath } = event.currentTarget.dataset; const { moveIndex, actionIndex, movePath } = event.currentTarget.dataset;
const targetUuid = event.currentTarget.closest('.action-use-button-parent').querySelector('select')?.value; const targetUuid = event.currentTarget.closest('.action-use-button-parent').querySelector('select')?.value;
const parent = await foundry.utils.fromUuid(targetUuid || message.system.actor); const parent = await foundry.utils.fromUuid(targetUuid || message.system.actor)
const actionType = message.system.moves[moveIndex].actions[actionIndex]; const actionType = message.system.moves[moveIndex].actions[actionIndex];
const cls = game.system.api.models.actions.actionsTypes[actionType.type]; const cls = game.system.api.models.actions.actionsTypes[actionType.type];

View file

@ -127,7 +127,7 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
resource, resource,
active: index === combat.turn, active: index === combat.turn,
canPing: combatant.sceneId === canvas.scene?.id && game.user.hasPermission('PING_CANVAS'), canPing: combatant.sceneId === canvas.scene?.id && game.user.hasPermission('PING_CANVAS'),
type: combatant.actor?.system?.type, type: combatant.actor.system.type,
img: await this._getCombatantThumbnail(combatant) img: await this._getCombatantThumbnail(combatant)
}; };
@ -165,7 +165,7 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
if (this.viewed.turn !== toggleTurn) { if (this.viewed.turn !== toggleTurn) {
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns; const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
if (combatant.actor?.type === 'character') { if (combatant.actor.type === 'character') {
await updateCountdowns( await updateCountdowns(
CONFIG.DH.GENERAL.countdownProgressionTypes.spotlight.id, CONFIG.DH.GENERAL.countdownProgressionTypes.spotlight.id,
CONFIG.DH.GENERAL.countdownProgressionTypes.characterSpotlight.id CONFIG.DH.GENERAL.countdownProgressionTypes.characterSpotlight.id

View file

@ -230,14 +230,6 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
result.flatMap(r => r), result.flatMap(r => r),
'name' 'name'
); );
/* If any noticeable slowdown occurs, consider replacing with enriching description on clicking to expand descriptions */
for (const item of this.items) {
item.system.enrichedDescription =
(await item.system.getEnrichedDescription?.()) ??
(await foundry.applications.ux.TextEditor.implementation.enrichHTML(item.description));
}
this.fieldFilter = this._createFieldFilter(); this.fieldFilter = this._createFieldFilter();
if (this.presets?.filter) { if (this.presets?.filter) {

View file

@ -1,89 +0,0 @@
import { emitAsGM, GMUpdateEvent } from '../../systemRegistration/socket.mjs';
export default class DhSceneNavigation extends foundry.applications.ui.SceneNavigation {
/** @inheritdoc */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: ['faded-ui', 'flexcol', 'scene-navigation'],
actions: {
openSceneEnvironment: DhSceneNavigation.#openSceneEnvironment
}
};
/** @inheritdoc */
static PARTS = {
scenes: {
root: true,
template: 'systems/daggerheart/templates/ui/sceneNavigation/scene-navigation.hbs'
}
};
/** @inheritdoc */
async _prepareContext(options) {
const context = await super._prepareContext(options);
const extendScenes = scenes =>
scenes.map(x => {
const scene = game.scenes.get(x.id);
if (!scene.flags.daggerheart) return x;
const daggerheartInfo = new game.system.api.data.scenes.DHScene(scene.flags.daggerheart);
const environments = daggerheartInfo.sceneEnvironments.filter(
x => x && x.testUserPermission(game.user, 'LIMITED')
);
const hasEnvironments = environments.length > 0 && x.isView;
return {
...x,
hasEnvironments,
environmentImage: hasEnvironments ? environments[0].img : null,
environments: environments
};
});
context.scenes.active = extendScenes(context.scenes.active);
context.scenes.inactive = extendScenes(context.scenes.inactive);
return context;
}
static async #openSceneEnvironment(event, button) {
const scene = game.scenes.get(button.dataset.sceneId);
const sceneEnvironments = new game.system.api.data.scenes.DHScene(
scene.flags.daggerheart
).sceneEnvironments.filter(x => x.testUserPermission(game.user, 'LIMITED'));
if (sceneEnvironments.length === 1 || event.shiftKey) {
sceneEnvironments[0].sheet.render(true);
} else {
new foundry.applications.ux.ContextMenu.implementation(
button,
'.scene-environment',
sceneEnvironments.map(environment => ({
name: environment.name,
callback: () => {
if (scene.flags.daggerheart.sceneEnvironments[0] !== environment.uuid) {
const newEnvironments = scene.flags.daggerheart.sceneEnvironments;
const newFirst = newEnvironments.splice(
newEnvironments.findIndex(x => x === environment.uuid)
)[0];
newEnvironments.unshift(newFirst);
emitAsGM(
GMUpdateEvent.UpdateDocument,
scene.update.bind(scene),
{ 'flags.daggerheart.sceneEnvironments': newEnvironments },
scene.uuid
);
}
environment.sheet.render({ force: true });
}
})),
{
jQuery: false,
fixed: true
}
);
CONFIG.ux.ContextMenu.triggerContextMenu(event, '.scene-environment');
}
}
}

View file

@ -96,11 +96,11 @@ export default class DHContextMenu extends foundry.applications.ux.ContextMenu {
* Trigger a context menu event in response to a normal click on a additional options button. * Trigger a context menu event in response to a normal click on a additional options button.
* @param {PointerEvent} event * @param {PointerEvent} event
*/ */
static triggerContextMenu(event, altSelector) { static triggerContextMenu(event) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
const { clientX, clientY } = event; const { clientX, clientY } = event;
const selector = altSelector ?? '[data-item-uuid]'; const selector = '[data-item-uuid]';
const target = event.target.closest(selector) ?? event.currentTarget.closest(selector); const target = event.target.closest(selector) ?? event.currentTarget.closest(selector);
target?.dispatchEvent( target?.dispatchEvent(
new PointerEvent('contextmenu', { new PointerEvent('contextmenu', {

View file

@ -1,2 +1 @@
export * as placeables from './placeables/_module.mjs'; export * as placeables from './placeables/_module.mjs';
export { default as DhTokenLayer } from './tokens.mjs';

View file

@ -1,12 +1,4 @@
export default class DhTokenPlaceable extends foundry.canvas.placeables.Token { export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
/** @inheritdoc */
async _draw(options) {
await super._draw(options);
if (this.document.flags.daggerheart?.createPlacement)
this.previewHelp ||= this.addChild(this.#drawPreviewHelp());
}
/** @inheritDoc */ /** @inheritDoc */
async _drawEffects() { async _drawEffects() {
this.effects.renderable = false; this.effects.renderable = false;
@ -42,69 +34,6 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
this.renderFlags.set({ refreshEffects: true }); this.renderFlags.set({ refreshEffects: true });
} }
/**
* Returns the distance from this token to another token object.
* This value is corrected to handle alternate token sizes and other grid types
* according to the diagonal rules.
*/
distanceTo(target) {
if (!canvas.ready) return NaN;
if (this === target) return 0;
const originPoint = this.center;
const destinationPoint = target.center;
// Compute for gridless. This version returns circular edge to edge + grid distance,
// so that tokens that are touching return 5.
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
const boundsCorrection = canvas.grid.distance / canvas.grid.size;
const originRadius = (this.bounds.width * boundsCorrection) / 2;
const targetRadius = (target.bounds.width * boundsCorrection) / 2;
const distance = canvas.grid.measurePath([originPoint, destinationPoint]).distance;
return distance - originRadius - targetRadius + canvas.grid.distance;
}
// Compute what the closest grid space of each token is, then compute that distance
const originEdge = this.#getEdgeBoundary(this.bounds, originPoint, destinationPoint);
const targetEdge = this.#getEdgeBoundary(target.bounds, originPoint, destinationPoint);
const adjustedOriginPoint = canvas.grid.getTopLeftPoint({
x: originEdge.x + Math.sign(originPoint.x - originEdge.x),
y: originEdge.y + Math.sign(originPoint.y - originEdge.y)
});
const adjustDestinationPoint = canvas.grid.getTopLeftPoint({
x: targetEdge.x + Math.sign(destinationPoint.x - targetEdge.x),
y: targetEdge.y + Math.sign(destinationPoint.y - targetEdge.y)
});
return canvas.grid.measurePath([adjustedOriginPoint, adjustDestinationPoint]).distance;
}
/** Returns the point at which a line starting at origin and ending at destination intersects the edge of the bounds */
#getEdgeBoundary(bounds, originPoint, destinationPoint) {
const points = [
{ x: bounds.x, y: bounds.y },
{ x: bounds.x + bounds.width, y: bounds.y },
{ x: bounds.x + bounds.width, y: bounds.y + bounds.height },
{ x: bounds.x, y: bounds.y + bounds.height }
];
const pairsToTest = [
[points[0], points[1]],
[points[1], points[2]],
[points[2], points[3]],
[points[3], points[0]]
];
for (const pair of pairsToTest) {
const result = foundry.utils.lineSegmentIntersection(originPoint, destinationPoint, pair[0], pair[1]);
if (result) return result;
}
return null;
}
/** Tests if the token is at least adjacent with another, with some leeway for diagonals */
isAdjacentWith(token) {
return this.distanceTo(token) <= canvas.grid.distance * 1.5;
}
/** @inheritDoc */ /** @inheritDoc */
_drawBar(number, bar, data) { _drawBar(number, bar, data) {
const val = Number(data.value); const val = Number(data.value);
@ -140,25 +69,4 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
bar.position.set(0, posY); bar.position.set(0, posY);
return true; return true;
} }
/**
* Draw a helptext for previews as a text object
* @returns {PreciseText} The Text object for the preview helper
*/
#drawPreviewHelp() {
const { uiScale } = canvas.dimensions;
const textStyle = CONFIG.canvasTextStyle.clone();
textStyle.fontSize = 18;
textStyle.wordWrapWidth = this.w * 2.5;
textStyle.fontStyle = 'italic';
const helpText = new foundry.canvas.containers.PreciseText(
`(${game.i18n.localize('DAGGERHEART.UI.Tooltip.previewTokenHelp')})`,
textStyle
);
helpText.anchor.set(helpText.width / 900, 1);
helpText.scale.set(uiScale, uiScale);
return helpText;
}
} }

View file

@ -1,16 +0,0 @@
export default class DhTokenLayer extends foundry.canvas.layers.TokenLayer {
async _createPreview(createData, options) {
if (options.actor) {
const tokenSizes = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes;
if (options.actor?.system.metadata.usesSize) {
const tokenSize = tokenSizes[options.actor.system.size];
if (tokenSize && options.actor.system.size !== CONFIG.DH.ACTOR.tokenSize.custom.id) {
createData.width = tokenSize;
createData.height = tokenSize;
}
}
}
return super._createPreview(createData, options);
}
}

View file

@ -10,4 +10,3 @@ export * as itemConfig from './itemConfig.mjs';
export * as settingsConfig from './settingsConfig.mjs'; export * as settingsConfig from './settingsConfig.mjs';
export * as systemConfig from './system.mjs'; export * as systemConfig from './system.mjs';
export * as itemBrowserConfig from './itemBrowserConfig.mjs'; export * as itemBrowserConfig from './itemBrowserConfig.mjs';
export * as triggerConfig from './triggerConfig.mjs';

View file

@ -9,7 +9,7 @@ export const AdversaryBPPerEncounter = (adversaries, characters) => {
); );
if (existingEntry) { if (existingEntry) {
existingEntry.nr += 1; existingEntry.nr += 1;
} else if (adversary.type) { } else {
acc.push({ adversary, nr: 1 }); acc.push({ adversary, nr: 1 });
} }
return acc; return acc;

View file

@ -496,8 +496,6 @@ export const diceTypes = {
d20: 'd20' d20: 'd20'
}; };
export const dieFaces = [4, 6, 8, 10, 12, 20];
export const multiplierTypes = { export const multiplierTypes = {
prof: 'Proficiency', prof: 'Proficiency',
cast: 'Spellcast', cast: 'Spellcast',

View file

@ -1,3 +1,5 @@
export const hooksConfig = { const hooksConfig = {
effectDisplayToggle: 'DHEffectDisplayToggle' effectDisplayToggle: 'DHEffectDisplayToggle'
}; };
export default hooksConfig;

View file

@ -435,8 +435,7 @@ export const armorFeatures = {
{ {
key: 'system.resistance.magical.reduction', key: 'system.resistance.magical.reduction',
mode: 2, mode: 2,
value: '@system.armorScore', value: '@system.armorScore'
priority: 21
} }
] ]
} }
@ -710,8 +709,7 @@ export const weaponFeatures = {
{ {
key: 'system.evasion', key: 'system.evasion',
mode: 2, mode: 2,
value: '@system.armorScore', value: '@system.armorScore'
priority: 21
} }
] ]
} }
@ -1326,8 +1324,7 @@ export const weaponFeatures = {
{ {
key: 'system.bonuses.damage.primaryWeapon.bonus', key: 'system.bonuses.damage.primaryWeapon.bonus',
mode: 2, mode: 2,
value: '@system.traits.agility.value', value: '@system.traits.agility.value'
priority: 21
} }
] ]
} }
@ -1419,9 +1416,9 @@ export const orderedWeaponFeatures = () => {
}; };
export const featureForm = { export const featureForm = {
passive: 'DAGGERHEART.CONFIG.FeatureForm.passive', passive: "DAGGERHEART.CONFIG.FeatureForm.passive",
action: 'DAGGERHEART.CONFIG.FeatureForm.action', action: "DAGGERHEART.CONFIG.FeatureForm.action",
reaction: 'DAGGERHEART.CONFIG.FeatureForm.reaction' reaction: "DAGGERHEART.CONFIG.FeatureForm.reaction"
}; };
export const featureTypes = { export const featureTypes = {

View file

@ -7,8 +7,7 @@ import * as SETTINGS from './settingsConfig.mjs';
import * as EFFECTS from './effectConfig.mjs'; import * as EFFECTS from './effectConfig.mjs';
import * as ACTIONS from './actionConfig.mjs'; import * as ACTIONS from './actionConfig.mjs';
import * as FLAGS from './flagsConfig.mjs'; import * as FLAGS from './flagsConfig.mjs';
import * as HOOKS from './hooksConfig.mjs'; import HOOKS from './hooksConfig.mjs';
import * as TRIGGER from './triggerConfig.mjs';
import * as ITEMBROWSER from './itemBrowserConfig.mjs'; import * as ITEMBROWSER from './itemBrowserConfig.mjs';
export const SYSTEM_ID = 'daggerheart'; export const SYSTEM_ID = 'daggerheart';
@ -25,6 +24,5 @@ export const SYSTEM = {
ACTIONS, ACTIONS,
FLAGS, FLAGS,
HOOKS, HOOKS,
TRIGGER,
ITEMBROWSER ITEMBROWSER
}; };

View file

@ -1,42 +0,0 @@
/* hints and returns are intentionally not translated. They are programatical terms and best understood in english */
export const triggers = {
dualityRoll: {
id: 'dualityRoll',
usesActor: true,
args: ['roll', 'actor'],
label: 'DAGGERHEART.CONFIG.Triggers.dualityRoll.label',
hint: 'this: Action, roll: DhRoll, actor: DhActor',
returns: '{ updates: [{ key, value, total }] }'
},
fearRoll: {
id: 'fearRoll',
usesActor: true,
args: ['roll', 'actor'],
label: 'DAGGERHEART.CONFIG.Triggers.fearRoll.label',
hint: 'this: Action, roll: DhRoll, actor: DhActor',
returns: '{ updates: [{ key, value, total }] }'
},
postDamageReduction: {
id: 'postDamageReduction',
usesActor: true,
args: ['damageUpdates', 'actor'],
label: 'DAGGERHEART.CONFIG.Triggers.postDamageReduction.label',
hint: 'damageUpdates: ResourceUpdates, actor: DhActor',
returns: '{ updates: [{ originActor: this.actor, updates: [{ key, value, total }] }] }'
}
};
export const triggerActorTargetType = {
any: {
id: 'any',
label: 'DAGGERHEART.CONFIG.TargetTypes.any'
},
self: {
id: 'self',
label: 'DAGGERHEART.CONFIG.TargetTypes.self'
},
other: {
id: 'other',
label: 'DAGGERHEART.CONFIG.TargetTypes.other'
}
};

View file

@ -36,7 +36,6 @@ export default class DHAttackAction extends DHDamageAction {
async use(event, options) { async use(event, options) {
const result = await super.use(event, options); const result = await super.use(event, options);
if (!result.message) return;
if (result.message.system.action.roll?.type === 'attack') { if (result.message.system.action.roll?.type === 'attack') {
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns; const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;

View file

@ -2,7 +2,6 @@ import DhpActor from '../../documents/actor.mjs';
import D20RollDialog from '../../applications/dialogs/d20RollDialog.mjs'; import D20RollDialog from '../../applications/dialogs/d20RollDialog.mjs';
import { ActionMixin } from '../fields/actionField.mjs'; import { ActionMixin } from '../fields/actionField.mjs';
import { originItemField } from '../chat-message/actorRoll.mjs'; import { originItemField } from '../chat-message/actorRoll.mjs';
import TriggerField from '../fields/triggerField.mjs';
const fields = foundry.data.fields; const fields = foundry.data.fields;
@ -35,8 +34,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
nullable: false, nullable: false,
required: true required: true
}), }),
targetUuid: new fields.StringField({ initial: undefined }), targetUuid: new fields.StringField({ initial: undefined })
triggers: new fields.ArrayField(new TriggerField())
}; };
this.extraSchemas.forEach(s => { this.extraSchemas.forEach(s => {
@ -166,6 +164,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
*/ */
getRollData(data = {}) { getRollData(data = {}) {
const actorData = this.actor ? this.actor.getRollData(false) : {}; const actorData = this.actor ? this.actor.getRollData(false) : {};
actorData.result = data.roll?.total ?? 1; actorData.result = data.roll?.total ?? 1;
actorData.scale = data.costs?.length // Right now only return the first scalable cost. actorData.scale = data.costs?.length // Right now only return the first scalable cost.
? (data.costs.find(c => c.scalable)?.total ?? 1) ? (data.costs.find(c => c.scalable)?.total ?? 1)
@ -198,8 +197,6 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
let config = this.prepareConfig(event); let config = this.prepareConfig(event);
if (!config) return; if (!config) return;
await this.addEffects(config);
if (Hooks.call(`${CONFIG.DH.id}.preUseAction`, this, config) === false) return; if (Hooks.call(`${CONFIG.DH.id}.preUseAction`, this, config) === false) return;
// Display configuration window if necessary // Display configuration window if necessary
@ -266,16 +263,6 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
return config; return config;
} }
/** */
async addEffects(config) {
let effects = [];
if (this.actor) {
effects = Array.from(await this.actor.allApplicableEffects());
}
config.effects = effects;
}
/** /**
* Method used to know if a configuration dialog must be shown or not when there is no roll. * Method used to know if a configuration dialog must be shown or not when there is no roll.
* @param {*} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods. * @param {*} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
@ -356,10 +343,6 @@ export class ResourceUpdateMap extends Map {
} }
addResources(resources) { addResources(resources) {
if (!resources?.length) return;
const invalidResources = resources.some(resource => !resource.key);
if (invalidResources) return;
for (const resource of resources) { for (const resource of resources) {
if (!resource.key) continue; if (!resource.key) continue;

View file

@ -1,5 +1,19 @@
import DHBaseAction from './baseAction.mjs'; import DHBaseAction from './baseAction.mjs';
export default class DHSummonAction extends DHBaseAction { export default class DHSummonAction extends DHBaseAction {
static extraSchemas = [...super.extraSchemas, 'summon']; static defineSchema() {
const fields = foundry.data.fields;
return {
...super.defineSchema(),
documentUUID: new fields.DocumentUUIDField({ type: 'Actor' })
};
}
async trigger(event, ...args) {
if (!this.canSummon || !canvas.scene) return;
}
get canSummon() {
return game.user.can('TOKEN_CREATE');
}
} }

View file

@ -1,17 +1,3 @@
/** -- Changes Type Priorities --
* - Base Number -
* Custom: 0
* Multiply: 10
* Add: 20
* Downgrade: 30
* Upgrade: 40
* Override: 50
*
* - Changes Value Priorities -
* Standard: +0
* "Anything that uses another data model value as its value": +1 - Effects that increase traits have to be calculated first at Base priority. (EX: Raise evasion by half your agility)
*/
export default class BaseEffect extends foundry.abstract.TypeDataModel { export default class BaseEffect extends foundry.abstract.TypeDataModel {
static defineSchema() { static defineSchema() {
const fields = foundry.data.fields; const fields = foundry.data.fields;

View file

@ -19,7 +19,6 @@ export default class BeastformEffect extends BaseEffect {
base64: false base64: false
}), }),
tokenSize: new fields.SchemaField({ tokenSize: new fields.SchemaField({
scale: new fields.NumberField({ nullable: false, initial: 1 }),
height: new fields.NumberField({ integer: false, nullable: true }), height: new fields.NumberField({ integer: false, nullable: true }),
width: new fields.NumberField({ integer: false, nullable: true }) width: new fields.NumberField({ integer: false, nullable: true })
}) })
@ -56,9 +55,7 @@ export default class BeastformEffect extends BaseEffect {
const update = { const update = {
...baseUpdate, ...baseUpdate,
texture: { texture: {
src: this.characterTokenData.tokenImg, src: this.characterTokenData.tokenImg
scaleX: this.characterTokenData.tokenSize.scale,
scaleY: this.characterTokenData.tokenSize.scale
}, },
ring: { ring: {
enabled: this.characterTokenData.usesDynamicToken, enabled: this.characterTokenData.usesDynamicToken,
@ -89,9 +86,7 @@ export default class BeastformEffect extends BaseEffect {
y, y,
'texture': { 'texture': {
enabled: this.characterTokenData.usesDynamicToken, enabled: this.characterTokenData.usesDynamicToken,
src: token.flags.daggerheart?.beastformTokenImg ?? this.characterTokenData.tokenImg, src: token.flags.daggerheart?.beastformTokenImg ?? this.characterTokenData.tokenImg
scaleX: this.characterTokenData.tokenSize.scale,
scaleY: this.characterTokenData.tokenSize.scale
}, },
'ring': { 'ring': {
subject: { subject: {

View file

@ -280,24 +280,6 @@ export default class DhCharacter extends BaseDataActor {
}) })
}) })
}), }),
dualityRoll: new fields.SchemaField({
defaultHopeDice: new fields.NumberField({
nullable: false,
required: true,
integer: true,
choices: CONFIG.DH.GENERAL.dieFaces,
initial: 12,
label: 'DAGGERHEART.ACTORS.Character.defaultHopeDice'
}),
defaultFearDice: new fields.NumberField({
nullable: false,
required: true,
integer: true,
choices: CONFIG.DH.GENERAL.dieFaces,
initial: 12,
label: 'DAGGERHEART.ACTORS.Character.defaultFearDice'
})
}),
runeWard: new fields.BooleanField({ initial: false }), runeWard: new fields.BooleanField({ initial: false }),
burden: new fields.SchemaField({ burden: new fields.SchemaField({
ignore: new fields.BooleanField() ignore: new fields.BooleanField()

View file

@ -1,11 +1,8 @@
import BaseDataActor from './base.mjs'; import BaseDataActor from './base.mjs';
import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs'; import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs';
import DHEnvironmentSettings from '../../applications/sheets-configs/environment-settings.mjs'; import DHEnvironmentSettings from '../../applications/sheets-configs/environment-settings.mjs';
import { RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
export default class DhEnvironment extends BaseDataActor { export default class DhEnvironment extends BaseDataActor {
scenes = new Set();
/**@override */ /**@override */
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Environment']; static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Environment'];
@ -56,31 +53,6 @@ export default class DhEnvironment extends BaseDataActor {
} }
isItemValid(source) { isItemValid(source) {
return source.type === 'feature'; return source.type === "feature";
}
_onUpdate(changes, options, userId) {
super._onUpdate(changes, options, userId);
for (const scene of this.scenes) {
scene.render();
}
}
_onDelete(options, userId) {
super._onDelete(options, userId);
for (const scene of this.scenes) {
if (game.user.isActiveGM) {
const newSceneEnvironments = scene.flags.daggerheart.sceneEnvironments.filter(
x => x !== this.parent.uuid
);
scene.update({ 'flags.daggerheart.sceneEnvironments': newSceneEnvironments }).then(() => {
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Scene });
game.socket.emit(`system.${CONFIG.DH.id}`, {
action: socketEvent.Refresh,
data: { refreshType: RefreshType.TagTeamRoll }
});
});
}
}
} }
} }

View file

@ -15,9 +15,8 @@ export default class DhCombat extends foundry.abstract.TypeDataModel {
get extendedBattleToggles() { get extendedBattleToggles() {
const modifiers = CONFIG.DH.ENCOUNTER.BPModifiers; const modifiers = CONFIG.DH.ENCOUNTER.BPModifiers;
const adversaries = const adversaries =
this.parent.turns?.filter(x => x.actor && x.isNPC)?.map(x => ({ ...x.actor, type: x.actor.system.type })) ?? this.parent.turns?.filter(x => x.isNPC)?.map(x => ({ ...x.actor, type: x.actor.system.type })) ?? [];
[]; const characters = this.parent.turns?.filter(x => !x.isNPC) ?? [];
const characters = this.parent.turns?.filter(x => x.actor && !x.isNPC) ?? [];
const activeAutomatic = Object.keys(modifiers).reduce((acc, categoryKey) => { const activeAutomatic = Object.keys(modifiers).reduce((acc, categoryKey) => {
const category = modifiers[categoryKey]; const category = modifiers[categoryKey];

View file

@ -2,6 +2,5 @@ export { ActionCollection } from './actionField.mjs';
export { default as FormulaField } from './formulaField.mjs'; export { default as FormulaField } from './formulaField.mjs';
export { default as ForeignDocumentUUIDField } from './foreignDocumentUUIDField.mjs'; export { default as ForeignDocumentUUIDField } from './foreignDocumentUUIDField.mjs';
export { default as ForeignDocumentUUIDArrayField } from './foreignDocumentUUIDArrayField.mjs'; export { default as ForeignDocumentUUIDArrayField } from './foreignDocumentUUIDArrayField.mjs';
export { default as TriggerField } from './triggerField.mjs';
export { default as MappingField } from './mappingField.mjs'; export { default as MappingField } from './mappingField.mjs';
export * as ActionFields from './action/_module.mjs'; export * as ActionFields from './action/_module.mjs';

View file

@ -9,4 +9,3 @@ export { default as BeastformField } from './beastformField.mjs';
export { default as DamageField } from './damageField.mjs'; export { default as DamageField } from './damageField.mjs';
export { default as RollField } from './rollField.mjs'; export { default as RollField } from './rollField.mjs';
export { default as MacroField } from './macroField.mjs'; export { default as MacroField } from './macroField.mjs';
export { default as SummonField } from './summonField.mjs';

View file

@ -87,7 +87,7 @@ export class DHActionRollData extends foundry.abstract.DataModel {
if (this.type === CONFIG.DH.GENERAL.rollTypes.attack.id) if (this.type === CONFIG.DH.GENERAL.rollTypes.attack.id)
modifiers.push({ modifiers.push({
label: 'Bonus to Hit', label: 'Bonus to Hit',
value: this.bonus ?? this.parent.actor.system.attack.roll.bonus ?? 0 value: this.bonus ?? this.parent.actor.system.attack.roll.bonus
}); });
break; break;
default: default:

View file

@ -1,89 +0,0 @@
import FormulaField from '../formulaField.mjs';
const fields = foundry.data.fields;
export default class DHSummonField extends fields.ArrayField {
/**
* Action Workflow order
*/
static order = 120;
constructor(options = {}, context = {}) {
const summonFields = new fields.SchemaField({
actorUUID: new fields.DocumentUUIDField({
type: 'Actor',
required: true
}),
count: new FormulaField({
required: true,
default: '1'
})
});
super(summonFields, options, context);
}
static async execute() {
if (!canvas.scene) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.ACTIONS.TYPES.summon.error'));
return;
}
if (this.summon.length === 0) {
ui.notifications.warn('No actors configured for this Summon action.');
return;
}
const rolls = [];
const summonData = [];
for (const summon of this.summon) {
let count = summon.count;
const roll = new Roll(summon.count);
if (!roll.isDeterministic) {
await roll.evaluate();
if (game.modules.get('dice-so-nice')?.active) rolls.push(roll);
count = roll.total;
}
const actor = DHSummonField.getWorldActor(await foundry.utils.fromUuid(summon.actorUUID));
/* Extending summon data in memory so it's available in actionField.toChat. Think it's harmless, but ugly. Could maybe find a better way. */
summon.rolledCount = count;
summon.actor = actor.toObject();
summonData.push({ actor, count: count });
}
if (rolls.length) await Promise.all(rolls.map(roll => game.dice3d.showForRoll(roll, game.user, true)));
this.actor.sheet?.minimize();
DHSummonField.handleSummon(summonData, this.actor);
}
/* Check for any available instances of the actor present in the world if we're missing artwork in the compendium */
static getWorldActor(baseActor) {
const dataType = game.system.api.data.actors[`Dh${baseActor.type.capitalize()}`];
if (baseActor.inCompendium && dataType && baseActor.img === dataType.DEFAULT_ICON) {
const worldActorCopy = game.actors.find(x => x.name === baseActor.name);
return worldActorCopy ?? baseActor;
}
return baseActor;
}
static async handleSummon(summonData, actionActor, summonIndex = 0) {
const summon = summonData[summonIndex];
const result = await CONFIG.ux.TokenManager.createPreviewAsync(summon.actor, {
name: `${summon.actor.prototypeToken.name}${summon.count > 1 ? ` (${summon.count}x)` : ''}`
});
if (!result) return actionActor.sheet?.maximize();
summon.actor = result.actor;
summon.count--;
if (summon.count <= 0) {
summonIndex++;
if (summonIndex === summonData.length) return actionActor.sheet?.maximize();
}
DHSummonField.handleSummon(summonData, actionActor, summonIndex);
}
}

View file

@ -267,23 +267,17 @@ export function ActionMixin(Base) {
action: { action: {
name: this.name, name: this.name,
img: this.baseAction ? this.parent.parent.img : this.img, img: this.baseAction ? this.parent.parent.img : this.img,
tags: this.tags ? this.tags : ['Spell', 'Arcana', 'Lv 10'], tags: this.tags ? this.tags : ['Spell', 'Arcana', 'Lv 10']
summon: this.summon
}, },
itemOrigin: this.item, itemOrigin: this.item,
description: this.description || (this.item instanceof Item ? this.item.system.description : '') description: this.description || (this.item instanceof Item ? this.item.system.description : '')
}; };
const speaker = cls.getSpeaker();
const msg = { const msg = {
type: 'abilityUse', type: 'abilityUse',
user: game.user.id, user: game.user.id,
actor: { name: this.actor.name, img: this.actor.img }, actor: { name: this.actor.name, img: this.actor.img },
author: this.author, author: this.author,
speaker: { speaker: cls.getSpeaker(),
speaker,
actor: speaker.actor ?? this.actor
},
title: game.i18n.localize('DAGGERHEART.UI.Chat.action.title'), title: game.i18n.localize('DAGGERHEART.UI.Chat.action.title'),
system: systemData, system: systemData,
content: await foundry.applications.handlebars.renderTemplate( content: await foundry.applications.handlebars.renderTemplate(

View file

@ -1,24 +0,0 @@
export default class TriggerField extends foundry.data.fields.SchemaField {
constructor(context) {
super(
{
trigger: new foundry.data.fields.StringField({
nullable: false,
blank: false,
initial: CONFIG.DH.TRIGGER.triggers.dualityRoll.id,
choices: CONFIG.DH.TRIGGER.triggers,
label: 'DAGGERHEART.CONFIG.Triggers.triggerType'
}),
triggeringActorType: new foundry.data.fields.StringField({
nullable: false,
blank: false,
initial: CONFIG.DH.TRIGGER.triggerActorTargetType.any.id,
choices: CONFIG.DH.TRIGGER.triggerActorTargetType,
label: 'DAGGERHEART.CONFIG.Triggers.triggeringActorType'
}),
command: new foundry.data.fields.JavaScriptField({ async: true })
},
context
);
}
}

View file

@ -54,21 +54,6 @@ export default class DHArmor extends AttachableItem {
); );
} }
/**@inheritdoc */
async getDescriptionData() {
const baseDescription = this.description;
const allFeatures = CONFIG.DH.ITEM.allArmorFeatures();
const features = this.armorFeatures.map(x => allFeatures[x.value]);
if (!features.length) return { prefix: null, value: baseDescription, suffix: null };
const prefix = await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/sheets/items/armor/description.hbs',
{ features }
);
return { prefix, value: baseDescription, suffix: null };
}
/**@inheritdoc */ /**@inheritdoc */
async _preUpdate(changes, options, user) { async _preUpdate(changes, options, user) {
const allowed = await super._preUpdate(changes, options, user); const allowed = await super._preUpdate(changes, options, user);

View file

@ -8,7 +8,7 @@
* @property {boolean} isInventoryItem- Indicates whether items of this type is a Inventory Item * @property {boolean} isInventoryItem- Indicates whether items of this type is a Inventory Item
*/ */
import { addLinkedItemsDiff, getScrollTextData, updateLinkedItemApps } from '../../helpers/utils.mjs'; import { addLinkedItemsDiff, createScrollText, getScrollTextData, updateLinkedItemApps } from '../../helpers/utils.mjs';
import { ActionsField } from '../fields/actionField.mjs'; import { ActionsField } from '../fields/actionField.mjs';
import FormulaField from '../fields/formulaField.mjs'; import FormulaField from '../fields/formulaField.mjs';
@ -124,33 +124,6 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
return [source, page ? `pg ${page}.` : null].filter(x => x).join('. '); return [source, page ? `pg ${page}.` : null].filter(x => x).join('. ');
} }
/**
* Augments the description for the item with type specific info to display. Implemented in applicable item subtypes.
* @param {object} [options] - Options that modify the styling of the rendered template. { headerStyle: undefined|'none'|'large' }
* @returns {string}
*/
async getDescriptionData(_options) {
return { prefix: null, value: this.description, suffix: null };
}
/**
* Gets the enriched and augmented description for the item.
* @param {object} [options] - Options that modify the styling of the rendered template. { headerStyle: undefined|'none'|'large' }
* @returns {string}
*/
async getEnrichedDescription() {
if (!this.metadata.hasDescription) return '';
const { prefix, value, suffix } = await this.getDescriptionData();
const fullDescription = [prefix, value, suffix].filter(p => !!p).join('\n<hr>\n');
return await foundry.applications.ux.TextEditor.implementation.enrichHTML(fullDescription, {
relativeTo: this,
rollData: this.getRollData(),
secrets: this.isOwner
});
}
/** /**
* Obtain a data object used to evaluate any dice rolls associated with this Item Type * Obtain a data object used to evaluate any dice rolls associated with this Item Type
* @param {object} [options] - Options which modify the getRollData method. * @param {object} [options] - Options which modify the getRollData method.
@ -162,30 +135,6 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
return data; return data;
} }
prepareBaseData() {
super.prepareBaseData();
for (const action of this.actions ?? []) {
if (!action.actor) continue;
const actionsToRegister = [];
for (let i = 0; i < action.triggers.length; i++) {
const trigger = action.triggers[i];
const { args } = CONFIG.DH.TRIGGER.triggers[trigger.trigger];
const fn = new foundry.utils.AsyncFunction(...args, `{${trigger.command}\n}`);
actionsToRegister.push(fn.bind(action));
if (i === action.triggers.length - 1)
game.system.registeredTriggers.registerTriggers(
trigger.trigger,
action.actor?.uuid,
trigger.triggeringActorType,
this.parent.uuid,
actionsToRegister
);
}
}
}
async _preCreate(data, options, user) { async _preCreate(data, options, user) {
// Skip if no initial action is required or actions already exist // Skip if no initial action is required or actions already exist
if (this.metadata.hasInitialAction && foundry.utils.isEmpty(this.actions)) { if (this.metadata.hasInitialAction && foundry.utils.isEmpty(this.actions)) {

View file

@ -49,7 +49,6 @@ export default class DHBeastform extends BaseDataItem {
choices: CONFIG.DH.ACTOR.tokenSize, choices: CONFIG.DH.ACTOR.tokenSize,
initial: CONFIG.DH.ACTOR.tokenSize.custom.id initial: CONFIG.DH.ACTOR.tokenSize.custom.id
}), }),
scale: new fields.NumberField({ nullable: false, min: 0.2, max: 3, step: 0.05, initial: 1 }),
height: new fields.NumberField({ integer: true, min: 1, initial: null, nullable: true }), height: new fields.NumberField({ integer: true, min: 1, initial: null, nullable: true }),
width: new fields.NumberField({ integer: true, min: 1, initial: null, nullable: true }) width: new fields.NumberField({ integer: true, min: 1, initial: null, nullable: true })
}), }),
@ -185,7 +184,6 @@ export default class DHBeastform extends BaseDataItem {
tokenImg: this.parent.parent.prototypeToken.texture.src, tokenImg: this.parent.parent.prototypeToken.texture.src,
tokenRingImg: this.parent.parent.prototypeToken.ring.subject.texture, tokenRingImg: this.parent.parent.prototypeToken.ring.subject.texture,
tokenSize: { tokenSize: {
scale: this.parent.parent.prototypeToken.texture.scaleX,
height: this.parent.parent.prototypeToken.height, height: this.parent.parent.prototypeToken.height,
width: this.parent.parent.prototypeToken.width width: this.parent.parent.prototypeToken.width
} }
@ -211,9 +209,7 @@ export default class DHBeastform extends BaseDataItem {
height, height,
width, width,
texture: { texture: {
src: this.tokenImg, src: this.tokenImg
scaleX: this.tokenSize.scale,
scaleY: this.tokenSize.scale
}, },
ring: { ring: {
subject: { subject: {

View file

@ -29,21 +29,7 @@ export default class DHDomainCard extends BaseDataItem {
required: true, required: true,
initial: CONFIG.DH.DOMAIN.cardTypes.ability.id initial: CONFIG.DH.DOMAIN.cardTypes.ability.id
}), }),
inVault: new fields.BooleanField({ initial: false }), inVault: new fields.BooleanField({ initial: false })
vaultActive: new fields.BooleanField({
required: true,
nullable: false,
initial: false
}),
loadoutIgnore: new fields.BooleanField({
required: true,
nullable: false,
initial: false
}),
domainTouched: new fields.NumberField({
nullable: true,
initial: null
})
}; };
} }
@ -52,19 +38,6 @@ export default class DHDomainCard extends BaseDataItem {
return game.i18n.localize(allDomainData[this.domain].label); return game.i18n.localize(allDomainData[this.domain].label);
} }
get isVaultSupressed() {
return this.inVault && !this.vaultActive;
}
get isDomainTouchedSuppressed() {
if (!this.parent.system.domainTouched || this.parent.parent?.type !== 'character') return false;
const matchingDomainCards = this.parent.parent.items.filter(
item => !item.system.inVault && item.system.domain === this.parent.system.domain
).length;
return matchingDomainCards < this.parent.system.domainTouched;
}
/* -------------------------------------------- */ /* -------------------------------------------- */
/**@override */ /**@override */

View file

@ -110,21 +110,6 @@ export default class DHWeapon extends AttachableItem {
); );
} }
/**@inheritdoc */
async getDescriptionData() {
const baseDescription = this.description;
const allFeatures = CONFIG.DH.ITEM.allWeaponFeatures();
const features = this.weaponFeatures.map(x => allFeatures[x.value]);
if (!features.length) return { prefix: null, value: baseDescription, suffix: null };
const prefix = await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/sheets/items/weapon/description.hbs',
{ features }
);
return { prefix, value: baseDescription, suffix: null };
}
prepareDerivedData() { prepareDerivedData() {
this.attack.roll.trait = this.rules.attack.roll.trait ?? this.attack.roll.trait; this.attack.roll.trait = this.rules.attack.roll.trait ?? this.attack.roll.trait;
} }

View file

@ -1,8 +1,3 @@
import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs';
/* Foundry does not add any system data for subtyped Scenes. The data model is therefore used by instantiating a new instance of it for sceneConfigSettings.mjs.
Needed dataprep and lifetime hooks are handled in documents/scene.
*/
export default class DHScene extends foundry.abstract.DataModel { export default class DHScene extends foundry.abstract.DataModel {
static defineSchema() { static defineSchema() {
const fields = foundry.data.fields; const fields = foundry.data.fields;
@ -18,8 +13,7 @@ export default class DHScene extends foundry.abstract.DataModel {
veryClose: new fields.NumberField({ integer: true, label: 'DAGGERHEART.CONFIG.Range.veryClose.name' }), veryClose: new fields.NumberField({ integer: true, label: 'DAGGERHEART.CONFIG.Range.veryClose.name' }),
close: new fields.NumberField({ integer: true, label: 'DAGGERHEART.CONFIG.Range.close.name' }), close: new fields.NumberField({ integer: true, label: 'DAGGERHEART.CONFIG.Range.close.name' }),
far: new fields.NumberField({ integer: true, label: 'DAGGERHEART.CONFIG.Range.far.name' }) far: new fields.NumberField({ integer: true, label: 'DAGGERHEART.CONFIG.Range.far.name' })
}), })
sceneEnvironments: new ForeignDocumentUUIDArrayField({ type: 'Actor', prune: true })
}; };
} }
} }

View file

@ -173,13 +173,6 @@ export default class DhAutomation extends foundry.abstract.DataModel {
label: 'DAGGERHEART.GENERAL.player.plurial' label: 'DAGGERHEART.GENERAL.player.plurial'
}) })
}) })
}),
triggers: new fields.SchemaField({
enabled: new fields.BooleanField({
nullable: false,
initial: true,
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.triggers.enabled.label'
})
}) })
}; };
} }

View file

@ -35,9 +35,7 @@ export default class D20Roll extends DHRoll {
get isCritical() { get isCritical() {
if (!this.d20._evaluated) return; if (!this.d20._evaluated) return;
return this.d20.total >= this.data.system.criticalThreshold;
const criticalThreshold = this.options.actionType === 'reaction' ? 20 : this.data.system.criticalThreshold;
return this.d20.total >= criticalThreshold;
} }
get hasAdvantage() { get hasAdvantage() {
@ -129,55 +127,15 @@ export default class D20Roll extends DHRoll {
const modifiers = foundry.utils.deepClone(this.options.roll.baseModifiers) ?? []; const modifiers = foundry.utils.deepClone(this.options.roll.baseModifiers) ?? [];
modifiers.push( modifiers.push(
...this.getBonus( ...this.getBonus(`roll.${this.options.actionType}`, `${this.options.actionType?.capitalize()} Bonus`)
`system.bonuses.roll.${this.options.actionType}`,
`${this.options.actionType?.capitalize()} Bonus`
)
); );
if (this.options.roll.type !== CONFIG.DH.GENERAL.rollTypes.attack.id) {
modifiers.push( modifiers.push(
...this.getBonus( ...this.getBonus(`roll.${this.options.roll.type}`, `${this.options.roll.type?.capitalize()} Bonus`)
`system.bonuses.roll.${this.options.roll.type}`,
`${this.options.roll.type?.capitalize()} Bonus`
)
); );
}
if (
this.options.roll.type === CONFIG.DH.GENERAL.rollTypes.attack.id ||
(this.options.roll.type === CONFIG.DH.GENERAL.rollTypes.spellcast.id && this.options.hasDamage)
) {
modifiers.push(
...this.getBonus(`system.bonuses.roll.attack`, `${this.options.roll.type?.capitalize()} Bonus`)
);
}
return modifiers; return modifiers;
} }
getActionChangeKeys() {
const changeKeys = new Set([`system.bonuses.roll.${this.options.actionType}`]);
if (this.options.roll.type !== CONFIG.DH.GENERAL.rollTypes.attack.id) {
changeKeys.add(`system.bonuses.roll.${this.options.roll.type}`);
}
if (
this.options.roll.type === CONFIG.DH.GENERAL.rollTypes.attack.id ||
(this.options.roll.type === CONFIG.DH.GENERAL.rollTypes.spellcast.id && this.options.hasDamage)
) {
changeKeys.add(`system.bonuses.roll.attack`);
}
if (this.options.roll.trait && this.data.traits?.[this.options.roll.trait]) {
if (this.options.roll.type !== CONFIG.DH.GENERAL.rollTypes.spellcast.id)
changeKeys.add('system.bonuses.roll.trait');
}
return changeKeys;
}
static postEvaluate(roll, config = {}) { static postEvaluate(roll, config = {}) {
const data = super.postEvaluate(roll, config); const data = super.postEvaluate(roll, config);
data.type = config.actionType; data.type = config.actionType;

View file

@ -93,6 +93,7 @@ export default class DamageRoll extends DHRoll {
type = this.options.messageType ?? (this.options.hasHealing ? 'healing' : 'damage'), type = this.options.messageType ?? (this.options.hasHealing ? 'healing' : 'damage'),
options = part ?? this.options; options = part ?? this.options;
modifiers.push(...this.getBonus(`${type}`, `${type.capitalize()} Bonus`));
if (!this.options.hasHealing) { if (!this.options.hasHealing) {
options.damageTypes?.forEach(t => { options.damageTypes?.forEach(t => {
modifiers.push(...this.getBonus(`${type}.${t}`, `${t.capitalize()} ${type.capitalize()} Bonus`)); modifiers.push(...this.getBonus(`${type}.${t}`, `${t.capitalize()} ${type.capitalize()} Bonus`));
@ -107,31 +108,6 @@ export default class DamageRoll extends DHRoll {
return modifiers; return modifiers;
} }
getActionChangeKeys() {
const type = this.options.messageType ?? (this.options.hasHealing ? 'healing' : 'damage');
const changeKeys = [];
for (const roll of this.options.roll) {
for (const damageType of roll.damageTypes?.values?.() ?? []) {
changeKeys.push(`system.bonuses.${type}.${damageType}`);
}
}
const item = this.data.parent?.items?.get(this.options.source.item);
if (item) {
switch (item.type) {
case 'weapon':
if (!this.options.hasHealing)
['primaryWeapon', 'secondaryWeapon'].forEach(w =>
changeKeys.push(`system.bonuses.damage.${w}`)
);
break;
}
}
return changeKeys;
}
constructFormula(config) { constructFormula(config) {
this.options.roll.forEach((part, index) => { this.options.roll.forEach((part, index) => {
part.roll = new Roll(Roll.replaceFormulaData(part.formula, config.data)); part.roll = new Roll(Roll.replaceFormulaData(part.formula, config.data));

View file

@ -4,7 +4,6 @@ export default class DHRoll extends Roll {
baseTerms = []; baseTerms = [];
constructor(formula, data = {}, options = {}) { constructor(formula, data = {}, options = {}) {
super(formula, data, options); super(formula, data, options);
options.bonusEffects = this.bonusEffectBuilder();
if (!this.data || !Object.keys(this.data).length) this.data = options.data; if (!this.data || !Object.keys(this.data).length) this.data = options.data;
} }
@ -165,17 +164,11 @@ export default class DHRoll extends Roll {
new foundry.dice.terms.OperatorTerm({ operator: '+' }), new foundry.dice.terms.OperatorTerm({ operator: '+' }),
...this.constructor.parse(modifier.join(' + '), this.options.data) ...this.constructor.parse(modifier.join(' + '), this.options.data)
]; ];
} else if (Number.isNumeric(modifier)) {
const numTerm = modifier < 0 ? '-' : '+';
return [
new foundry.dice.terms.OperatorTerm({ operator: numTerm }),
new foundry.dice.terms.NumericTerm({ number: Math.abs(modifier) })
];
} else { } else {
const numTerm = modifier < 0 ? '-' : '+'; const numTerm = modifier < 0 ? '-' : '+';
return [ return [
new foundry.dice.terms.OperatorTerm({ operator: numTerm }), new foundry.dice.terms.OperatorTerm({ operator: numTerm }),
...this.constructor.parse(modifier, this.options.data) new foundry.dice.terms.NumericTerm({ number: Math.abs(modifier) })
]; ];
} }
} }
@ -192,20 +185,18 @@ export default class DHRoll extends Roll {
} }
getBonus(path, label) { getBonus(path, label) {
const modifiers = []; const bonus = foundry.utils.getProperty(this.data.bonuses, path),
for (const effect of Object.values(this.options.bonusEffects)) { modifiers = [];
if (!effect.selected) continue; if (bonus?.bonus)
for (const change of effect.changes) { modifiers.push({
if (!change.key.includes(path)) continue; label: label,
const changeValue = game.system.api.documents.DhActiveEffect.getChangeValue( value: bonus?.bonus
this.data, });
change, if (bonus?.dice?.length)
effect.origEffect modifiers.push({
); label: label,
modifiers.push({ label: label, value: changeValue }); value: bonus?.dice
} });
}
return modifiers; return modifiers;
} }
@ -244,28 +235,4 @@ export default class DHRoll extends Roll {
static temporaryModifierBuilder(config) { static temporaryModifierBuilder(config) {
return {}; return {};
} }
bonusEffectBuilder() {
const changeKeys = this.getActionChangeKeys();
return (
this.options.effects?.reduce((acc, effect) => {
if (effect.changes.some(x => changeKeys.some(key => x.key.includes(key)))) {
acc[effect.id] = {
id: effect.id,
name: effect.name,
description: effect.description,
changes: effect.changes,
origEffect: effect,
selected: !effect.disabled
};
}
return acc;
}, {}) ?? []
);
}
getActionChangeKeys() {
return [];
}
} }

View file

@ -130,14 +130,9 @@ export default class DualityRoll extends D20Roll {
this.terms = [this.terms[0], this.terms[1], this.terms[2]]; this.terms = [this.terms[0], this.terms[1], this.terms[2]];
return; return;
} }
this.terms[0] = new foundry.dice.terms.Die({ faces: 12 });
this.terms[0] = new foundry.dice.terms.Die({
faces: this.data.rules.dualityRoll?.defaultHopeDice ?? 12
});
this.terms[1] = new foundry.dice.terms.OperatorTerm({ operator: '+' }); this.terms[1] = new foundry.dice.terms.OperatorTerm({ operator: '+' });
this.terms[2] = new foundry.dice.terms.Die({ this.terms[2] = new foundry.dice.terms.Die({ faces: 12 });
faces: this.data.rules.dualityRoll?.defaultFearDice ?? 12
});
} }
applyAdvantage() { applyAdvantage() {
@ -178,34 +173,6 @@ export default class DualityRoll extends D20Roll {
return modifiers; return modifiers;
} }
getActionChangeKeys() {
const changeKeys = new Set([`system.bonuses.roll.${this.options.actionType}`]);
if (this.options.roll.type !== CONFIG.DH.GENERAL.rollTypes.attack.id) {
changeKeys.add(`system.bonuses.roll.${this.options.roll.type}`);
}
if (
this.options.roll.type === CONFIG.DH.GENERAL.rollTypes.attack.id ||
(this.options.roll.type === CONFIG.DH.GENERAL.rollTypes.spellcast.id && this.options.hasDamage)
) {
changeKeys.add(`system.bonuses.roll.attack`);
}
if (this.options.roll.trait && this.data.traits?.[this.options.roll.trait]) {
if (this.options.roll.type !== CONFIG.DH.GENERAL.rollTypes.spellcast.id)
changeKeys.add('system.bonuses.roll.trait');
}
const weapons = ['primaryWeapon', 'secondaryWeapon'];
weapons.forEach(w => {
if (this.options.source.item && this.options.source.item === this.data[w]?.id)
changeKeys.add(`system.bonuses.roll.${w}`);
});
return changeKeys;
}
static async buildEvaluate(roll, config = {}, message = {}) { static async buildEvaluate(roll, config = {}, message = {}) {
await super.buildEvaluate(roll, config, message); await super.buildEvaluate(roll, config, message);
@ -257,32 +224,6 @@ export default class DualityRoll extends D20Roll {
await super.buildPost(roll, config, message); await super.buildPost(roll, config, message);
await DualityRoll.dualityUpdate(config); await DualityRoll.dualityUpdate(config);
await DualityRoll.handleTriggers(roll, config);
}
static async handleTriggers(roll, config) {
if (!config.source?.actor) return;
const updates = [];
const dualityUpdates = await game.system.registeredTriggers.runTrigger(
CONFIG.DH.TRIGGER.triggers.dualityRoll.id,
roll.data?.parent,
roll,
roll.data?.parent
);
if (dualityUpdates?.length) updates.push(...dualityUpdates);
if (config.roll.result.duality === -1) {
const fearUpdates = await game.system.registeredTriggers.runTrigger(
CONFIG.DH.TRIGGER.triggers.fearRoll.id,
roll.data?.parent,
roll,
roll.data?.parent
);
if (fearUpdates?.length) updates.push(...fearUpdates);
}
config.resourceUpdates.addResources(updates);
} }
static async addDualityResourceUpdates(config) { static async addDualityResourceUpdates(config) {

View file

@ -8,4 +8,3 @@ export { default as DhScene } from './scene.mjs';
export { default as DhToken } from './token.mjs'; export { default as DhToken } from './token.mjs';
export { default as DhTooltipManager } from './tooltipManager.mjs'; export { default as DhTooltipManager } from './tooltipManager.mjs';
export { default as DhTemplateManager } from './templateManager.mjs'; export { default as DhTemplateManager } from './templateManager.mjs';
export { default as DhTokenManager } from './tokenManager.mjs';

View file

@ -20,10 +20,7 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
} }
if (this.parent?.type === 'domainCard') { if (this.parent?.type === 'domainCard') {
const isVaultSupressed = this.parent.system.isVaultSupressed; return this.parent.system.inVault;
const domainTouchedSupressed = this.parent.system.isDomainTouchedSuppressed;
return isVaultSupressed || domainTouchedSupressed;
} }
return super.isSuppressed; return super.isSuppressed;
@ -109,29 +106,23 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
/**@inheritdoc*/ /**@inheritdoc*/
static applyField(model, change, field) { static applyField(model, change, field) {
change.value = DhActiveEffect.getChangeValue(model, change, change.effect); const isOriginTarget = change.value.toLowerCase().includes('origin.@');
super.applyField(model, change, field);
}
/** */
static getChangeValue(model, change, effect) {
let value = change.value;
const isOriginTarget = value.toLowerCase().includes('origin.@');
let parseModel = model; let parseModel = model;
if (isOriginTarget && effect.origin) { if (isOriginTarget && change.effect.origin) {
value = change.value.replaceAll(/origin\.@/gi, '@'); change.value = change.value.replaceAll(/origin\.@/gi, '@');
try { try {
const originEffect = foundry.utils.fromUuidSync(effect.origin); const effect = foundry.utils.fromUuidSync(change.effect.origin);
const doc = const doc =
originEffect.parent?.parent instanceof game.system.api.documents.DhpActor effect.parent?.parent instanceof game.system.api.documents.DhpActor
? originEffect.parent ? effect.parent
: originEffect.parent.parent; : effect.parent.parent;
if (doc) parseModel = doc; if (doc) parseModel = doc;
} catch (_) {} } catch (_) {}
} }
const evalValue = this.effectSafeEval(itemAbleRollParse(value, parseModel, effect.parent)); const evalValue = this.effectSafeEval(itemAbleRollParse(change.value, parseModel, change.effect.parent));
return evalValue ?? value; change.value = evalValue ?? change.value;
super.applyField(model, change, field);
} }
/** /**

View file

@ -646,19 +646,6 @@ export default class DhpActor extends Actor {
} }
} }
const results = await game.system.registeredTriggers.runTrigger(
CONFIG.DH.TRIGGER.triggers.postDamageReduction.id,
this,
updates,
this
);
if (results?.length) {
const resourceMap = new ResourceUpdateMap(results[0].originActor);
for (var result of results) resourceMap.addResources(result.updates);
resourceMap.updateResources();
}
updates.forEach( updates.forEach(
u => u =>
(u.value = (u.value =

View file

@ -157,10 +157,7 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
event.stopPropagation(); event.stopPropagation();
const config = foundry.utils.deepClone(this.system); const config = foundry.utils.deepClone(this.system);
config.event = event; config.event = event;
if (this.system.action) { await this.system.action?.workflow.get('damage')?.execute(config, this._id, true);
await this.system.action.addEffects(config);
await this.system.action.workflow.get('damage')?.execute(config, this._id, true);
}
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.TagTeamRoll }); Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.TagTeamRoll });
await game.socket.emit(`system.${CONFIG.DH.id}`, { await game.socket.emit(`system.${CONFIG.DH.id}`, {

View file

@ -31,7 +31,7 @@ export default class DHItem extends foundry.documents.Item {
static async createDocuments(sources, operation) { static async createDocuments(sources, operation) {
// Ensure that items being created are valid to the actor its being added to // Ensure that items being created are valid to the actor its being added to
const actor = operation.parent; const actor = operation.parent;
sources = actor?.system?.isItemValid ? sources.filter(s => actor.system.isItemValid(s)) : sources; sources = actor?.system?.isItemValid ? sources.filter((s) => actor.system.isItemValid(s)) : sources;
return super.createDocuments(sources, operation); return super.createDocuments(sources, operation);
} }
@ -146,16 +146,6 @@ export default class DHItem extends foundry.documents.Item {
/* -------------------------------------------- */ /* -------------------------------------------- */
async use(event) { async use(event) {
/* DomainCard check. Can be expanded or made neater */
if (this.system.isDomainTouchedSuppressed) {
return ui.notifications.warn(
game.i18n.format('DAGGERHEART.UI.Notifications.domainTouchRequirement', {
nr: this.domainTouched,
domain: game.i18n.localize(CONFIG.DH.DOMAIN.allDomains()[this.domain].label)
})
);
}
const actions = new Set(this.system.actionsList); const actions = new Set(this.system.actionsList);
if (actions?.size) { if (actions?.size) {
let action = actions.first(); let action = actions.first();

View file

@ -37,30 +37,4 @@ export default class DhScene extends Scene {
this.#sizeSyncBatch.clear(); this.#sizeSyncBatch.clear();
this.updateEmbeddedDocuments('Token', entries, { animation: { movementSpeed: 1.5 } }); this.updateEmbeddedDocuments('Token', entries, { animation: { movementSpeed: 1.5 } });
}, 0); }, 0);
prepareBaseData() {
super.prepareBaseData();
if (this instanceof game.system.api.documents.DhScene) {
const system = new game.system.api.data.scenes.DHScene(this.flags.daggerheart);
// Register this scene to all environements
for (const environment of system.sceneEnvironments) {
environment.system.scenes?.add(this);
}
}
}
_onDelete(options, userId) {
super._onDelete(options, userId);
if (this instanceof game.system.api.documents.DhScene) {
const system = new game.system.api.data.scenes.DHScene(this.flags.daggerheart);
// Clear this scene from all environments that aren't deleted
for (const environment of system.sceneEnvironments) {
environment?.system?.scenes?.delete(this);
}
}
}
} }

View file

@ -83,7 +83,7 @@ export default class DHToken extends CONFIG.Token.documentClass {
if (combat?.system?.battleToggles?.length) { if (combat?.system?.battleToggles?.length) {
await combat.toggleModifierEffects( await combat.toggleModifierEffects(
true, true,
tokens.filter(x => x.actor).map(x => x.actor) tokens.map(x => x.actor)
); );
} }
super.createCombatants(tokens, combat ?? {}); super.createCombatants(tokens, combat ?? {});
@ -95,7 +95,7 @@ export default class DHToken extends CONFIG.Token.documentClass {
if (combat?.system?.battleToggles?.length) { if (combat?.system?.battleToggles?.length) {
await combat.toggleModifierEffects( await combat.toggleModifierEffects(
false, false,
tokens.filter(x => x.actor).map(x => x.actor) tokens.map(x => x.actor)
); );
} }
super.deleteCombatants(tokens, combat ?? {}); super.deleteCombatants(tokens, combat ?? {});

View file

@ -1,104 +0,0 @@
/**
* A singleton class that handles preview tokens.
*/
export default class DhTokenManager {
#activePreview;
#actor;
#resolve;
/**
* Create a template preview, deactivating any existing ones.
* @param {object} data
*/
async createPreview(actor, tokenData) {
this.#actor = actor;
const token = await canvas.tokens._createPreview(
{
...actor.prototypeToken,
displayName: 50,
...tokenData
},
{ renderSheet: false, actor }
);
this.#activePreview = {
document: token.document,
object: token,
origin: { x: token.document.x, y: token.document.y }
};
this.#activePreview.events = {
contextmenu: this.#cancelTemplate.bind(this),
mousedown: this.#confirmTemplate.bind(this),
mousemove: this.#onDragMouseMove.bind(this)
};
canvas.stage.on('mousemove', this.#activePreview.events.mousemove);
canvas.stage.on('mousedown', this.#activePreview.events.mousedown);
canvas.app.view.addEventListener('contextmenu', this.#activePreview.events.contextmenu);
}
/* Currently intended for using as a preview of where to create a token. (note the flag) */
async createPreviewAsync(actor, tokenData = {}) {
return new Promise(resolve => {
this.#resolve = resolve;
this.createPreview(actor, { ...tokenData, flags: { daggerheart: { createPlacement: true } } });
});
}
/**
* Handles the movement of the token preview on mousedrag.
* @param {mousemove Event} event
*/
#onDragMouseMove(event) {
event.stopPropagation();
const { moveTime, object } = this.#activePreview;
const update = {};
const now = Date.now();
if (now - (moveTime || 0) <= 16) return;
this.#activePreview.moveTime = now;
let cursor = event.getLocalPosition(canvas.templates);
Object.assign(update, canvas.grid.getTopLeftPoint(cursor));
object.document.updateSource(update);
object.renderFlags.set({ refresh: true });
}
/**
* Cancels the preview token on right-click.
* @param {contextmenu Event} event
*/
#cancelTemplate(_event, resolved) {
const { mousemove, mousedown, contextmenu } = this.#activePreview.events;
this.#activePreview.object.destroy();
canvas.stage.off('mousemove', mousemove);
canvas.stage.off('mousedown', mousedown);
canvas.app.view.removeEventListener('contextmenu', contextmenu);
if (this.#resolve && !resolved) this.#resolve(false);
}
/**
* Creates a real Actor and token at the preview location and cancels the preview.
* @param {click Event} event
*/
async #confirmTemplate(event) {
event.stopPropagation();
this.#cancelTemplate(event, true);
const actor = this.#actor.inCompendium
? await game.system.api.documents.DhpActor.create(this.#actor.toObject())
: this.#actor;
const tokenData = await actor.getTokenDocument();
const result = await canvas.scene.createEmbeddedDocuments('Token', [
{ ...tokenData, x: this.#activePreview.document.x, y: this.#activePreview.document.y }
]);
this.#activePreview = undefined;
if (this.#resolve && result.length) this.#resolve(result[0]);
}
}

View file

@ -67,7 +67,7 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
if (item) { if (item) {
const isAction = item instanceof game.system.api.models.actions.actionsTypes.base; const isAction = item instanceof game.system.api.models.actions.actionsTypes.base;
const isEffect = item instanceof ActiveEffect; const isEffect = item instanceof ActiveEffect;
await this.enrichText(item); await this.enrichText(item, isAction || isEffect);
const type = isAction ? 'action' : isEffect ? 'effect' : item.type; const type = isAction ? 'action' : isEffect ? 'effect' : item.type;
html = await foundry.applications.handlebars.renderTemplate( html = await foundry.applications.handlebars.renderTemplate(
@ -202,20 +202,10 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
} }
} }
async enrichText(item) { async enrichText(item, flatStructure) {
const { TextEditor } = foundry.applications.ux; const { TextEditor } = foundry.applications.ux;
if (item.system?.metadata?.hasDescription) {
const enrichedValue =
(await item.system?.getEnrichedDescription?.()) ??
(await TextEditor.enrichHTML(item.system.description));
foundry.utils.setProperty(item, 'system.enrichedDescription', enrichedValue);
} else if (item.description) {
const enrichedValue = await TextEditor.enrichHTML(item.description);
foundry.utils.setProperty(item, 'enrichedDescription', enrichedValue);
}
const enrichPaths = [ const enrichPaths = [
{ path: flatStructure ? '' : 'system', name: 'description' },
{ path: 'system', name: 'features' }, { path: 'system', name: 'features' },
{ path: 'system', name: 'actions' }, { path: 'system', name: 'actions' },
{ path: 'system', name: 'customActions' } { path: 'system', name: 'customActions' }
@ -230,15 +220,12 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
for (const [index, itemValue] of pathValue.entries()) { for (const [index, itemValue] of pathValue.entries()) {
const itemIsAction = itemValue instanceof game.system.api.models.actions.actionsTypes.base; const itemIsAction = itemValue instanceof game.system.api.models.actions.actionsTypes.base;
const value = itemIsAction || !itemValue?.item ? itemValue : itemValue.item; const value = itemIsAction || !itemValue?.item ? itemValue : itemValue.item;
const enrichedValue = const enrichedValue = await TextEditor.enrichHTML(value.system?.description ?? value.description);
(await value.system?.getEnrichedDescription?.()) ??
(await TextEditor.enrichHTML(value.system?.description ?? value.description));
if (itemIsAction) value.enrichedDescription = enrichedValue; if (itemIsAction) value.enrichedDescription = enrichedValue;
else foundry.utils.setProperty(item, `${basePath}.${index}.enrichedDescription`, enrichedValue); else foundry.utils.setProperty(item, `${basePath}.${index}.enrichedDescription`, enrichedValue);
} }
} else { } else {
const enrichedValue = const enrichedValue = await TextEditor.enrichHTML(pathValue);
(await item.system?.getEnrichedDescription?.()) ?? (await TextEditor.enrichHTML(pathValue));
foundry.utils.setProperty( foundry.utils.setProperty(
item, item,
`${data.path ? `${data.path}.` : ''}enriched${data.name.capitalize()}`, `${data.path ? `${data.path}.` : ''}enriched${data.name.capitalize()}`,
@ -275,7 +262,7 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
const combat = game.combats.get(combatId); const combat = game.combats.get(combatId);
const adversaries = const adversaries =
combat.turns?.filter(x => x.actor?.isNPC)?.map(x => ({ ...x.actor, type: x.actor.system.type })) ?? []; combat.turns?.filter(x => x.actor?.isNPC)?.map(x => ({ ...x.actor, type: x.actor.system.type })) ?? [];
const characters = combat.turns?.filter(x => !x.isNPC && x.actor) ?? []; const characters = combat.turns?.filter(x => !x.isNPC) ?? [];
const nrCharacters = characters.length; const nrCharacters = characters.length;
const currentBP = AdversaryBPPerEncounter(adversaries, characters); const currentBP = AdversaryBPPerEncounter(adversaries, characters);
@ -285,7 +272,7 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
); );
const categories = combat.combatants.reduce((acc, combatant) => { const categories = combat.combatants.reduce((acc, combatant) => {
if (combatant.actor?.type === 'adversary') { if (combatant.actor.type === 'adversary') {
const keyData = Object.keys(acc).reduce((identifiers, categoryKey) => { const keyData = Object.keys(acc).reduce((identifiers, categoryKey) => {
if (identifiers) return identifiers; if (identifiers) return identifiers;
const category = acc[categoryKey]; const category = acc[categoryKey];
@ -365,7 +352,7 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
await combat.toggleModifierEffects( await combat.toggleModifierEffects(
event.target.checked, event.target.checked,
combat.combatants.filter(x => x.actor?.type === 'adversary').map(x => x.actor), combat.combatants.filter(x => x.actor.type === 'adversary').map(x => x.actor),
category, category,
grouping grouping
); );

View file

@ -86,9 +86,9 @@ export const enrichedDualityRoll = async (
const config = { const config = {
event: event ?? {}, event: event ?? {},
title: title, title: title,
headerTitle: label,
roll: { roll: {
trait: traitValue && target ? traitValue : null, trait: traitValue && target ? traitValue : null,
label: label,
difficulty: difficulty, difficulty: difficulty,
advantage, advantage,
type: reaction ? 'reaction' : null type: reaction ? 'reaction' : null
@ -101,7 +101,7 @@ export const enrichedDualityRoll = async (
await target.diceRoll(config); await target.diceRoll(config);
} else { } else {
// For no target, call DualityRoll directly with basic data // For no target, call DualityRoll directly with basic data
config.data = { experiences: {}, traits: {}, rules: {} }; config.data = { experiences: {}, traits: {} };
config.source = { actor: null }; config.source = { actor: null };
await CONFIG.Dice.daggerheart.DualityRoll.build(config); await CONFIG.Dice.daggerheart.DualityRoll.build(config);
} }

View file

@ -119,7 +119,7 @@ export const tagifyElement = (element, baseOptions, onChange, tagifyOptions = {}
spellcheck='false' spellcheck='false'
tabIndex="${this.settings.a11y.focusableTags ? 0 : -1}" tabIndex="${this.settings.a11y.focusableTags ? 0 : -1}"
class="${this.settings.classNames.tag} ${tagData.class ? tagData.class : ''}" class="${this.settings.classNames.tag} ${tagData.class ? tagData.class : ''}"
data-tooltip="${tagData.description ? htmlToText(tagData.description) : tagData.name}" data-tooltip="${tagData.description || tagData.name}"
${this.getAttributes(tagData)}> ${this.getAttributes(tagData)}>
<x class="${this.settings.classNames.tagX}" role='button' aria-label='remove tag'></x> <x class="${this.settings.classNames.tagX}" role='button' aria-label='remove tag'></x>
<div> <div>
@ -474,10 +474,3 @@ export async function getCritDamageBonus(formula) {
const critRoll = new Roll(formula); const critRoll = new Roll(formula);
return critRoll.dice.reduce((acc, dice) => acc + dice.faces * dice.number, 0); return critRoll.dice.reduce((acc, dice) => acc + dice.faces * dice.number, 0);
} }
export function htmlToText(html) {
var tempDivElement = document.createElement('div');
tempDivElement.innerHTML = html;
return tempDivElement.textContent || tempDivElement.innerText || '';
}

View file

@ -32,7 +32,6 @@ export const preloadHandlebarsTemplates = async function () {
'systems/daggerheart/templates/actionTypes/effect.hbs', 'systems/daggerheart/templates/actionTypes/effect.hbs',
'systems/daggerheart/templates/actionTypes/beastform.hbs', 'systems/daggerheart/templates/actionTypes/beastform.hbs',
'systems/daggerheart/templates/actionTypes/countdown.hbs', 'systems/daggerheart/templates/actionTypes/countdown.hbs',
'systems/daggerheart/templates/actionTypes/summon.hbs',
'systems/daggerheart/templates/settings/components/settings-item-line.hbs', 'systems/daggerheart/templates/settings/components/settings-item-line.hbs',
'systems/daggerheart/templates/ui/tooltip/parts/tooltipChips.hbs', 'systems/daggerheart/templates/ui/tooltip/parts/tooltipChips.hbs',
'systems/daggerheart/templates/ui/tooltip/parts/tooltipTags.hbs', 'systems/daggerheart/templates/ui/tooltip/parts/tooltipTags.hbs',

View file

@ -37,8 +37,7 @@ export const GMUpdateEvent = {
export const RefreshType = { export const RefreshType = {
Countdown: 'DhCoundownRefresh', Countdown: 'DhCoundownRefresh',
TagTeamRoll: 'DhTagTeamRollRefresh', TagTeamRoll: 'DhTagTeamRollRefresh',
EffectsDisplay: 'DhEffectsDisplayRefresh', EffectsDisplay: 'DhEffectsDisplayRefresh'
Scene: 'DhSceneRefresh'
}; };
export const registerSocketHooks = () => { export const registerSocketHooks = () => {

View file

@ -533,31 +533,33 @@
"description": "<p><strong>Spend a Fear</strong> to summon a @UUID[Compendium.daggerheart.adversaries.Actor.YhJrP7rTBiRdX5Fp]{Zombie Legion}, which appears at Close range and immediately takes the spotlight.</p>", "description": "<p><strong>Spend a Fear</strong> to summon a @UUID[Compendium.daggerheart.adversaries.Actor.YhJrP7rTBiRdX5Fp]{Zombie Legion}, which appears at Close range and immediately takes the spotlight.</p>",
"resource": null, "resource": null,
"actions": { "actions": {
"qSuWxC8xQOhnbBx9": { "gZg3AkzCYUTExjE6": {
"type": "summon", "type": "effect",
"_id": "qSuWxC8xQOhnbBx9", "_id": "gZg3AkzCYUTExjE6",
"systemPath": "actions", "systemPath": "actions",
"baseAction": false,
"description": "", "description": "",
"chatDisplay": true, "chatDisplay": true,
"originItem": {
"type": "itemCollection"
},
"actionType": "action", "actionType": "action",
"cost": [], "cost": [
{
"scalable": false,
"key": "fear",
"value": 1,
"step": null
}
],
"uses": { "uses": {
"value": null, "value": null,
"max": "", "max": "",
"recovery": null, "recovery": null
"consumeOnSuccess": false },
"effects": [],
"target": {
"type": "any",
"amount": null
}, },
"summon": [
{
"actorUUID": "Compendium.daggerheart.adversaries.Actor.YhJrP7rTBiRdX5Fp",
"count": "1"
}
],
"name": "Spend Fear", "name": "Spend Fear",
"img": "icons/magic/death/undead-zombie-grave-green.webp",
"range": "" "range": ""
} }
}, },

View file

@ -284,7 +284,7 @@
"key": "system.bonuses.roll.attack.bonus", "key": "system.bonuses.roll.attack.bonus",
"mode": 2, "mode": 2,
"value": "ITEM.@system.resource.value", "value": "ITEM.@system.resource.value",
"priority": 21 "priority": null
} }
], ],
"disabled": false, "disabled": false,

View file

@ -312,14 +312,7 @@
"range": "melee" "range": "melee"
} }
}, },
"changes": [ "changes": [],
{
"key": "system.rules.dualityRoll.defaultHopeDice",
"mode": 5,
"value": "d8",
"priority": null
}
],
"disabled": false, "disabled": false,
"duration": { "duration": {
"startTime": null, "startTime": null,

View file

@ -256,45 +256,34 @@
"description": "<p><strong>Spend a Fear</strong> to boil the blood of all PCs within Far range. They use a d20 as their Fear Die until the end of the scene.</p><p>@Template[type:emanation|range:f]</p>", "description": "<p><strong>Spend a Fear</strong> to boil the blood of all PCs within Far range. They use a d20 as their Fear Die until the end of the scene.</p><p>@Template[type:emanation|range:f]</p>",
"resource": null, "resource": null,
"actions": { "actions": {
"jKvzbQT0vp66DDOH": { "V142qYppCGJn8OiN": {
"type": "effect", "type": "effect",
"_id": "jKvzbQT0vp66DDOH", "_id": "V142qYppCGJn8OiN",
"systemPath": "actions", "systemPath": "actions",
"baseAction": false,
"description": "", "description": "",
"chatDisplay": true, "chatDisplay": true,
"originItem": {
"type": "itemCollection"
},
"actionType": "action", "actionType": "action",
"cost": [ "cost": [
{ {
"scalable": false, "scalable": false,
"key": "fear", "key": "fear",
"value": 1, "value": 1,
"itemId": null, "step": null
"step": null,
"consumeOnSuccess": false
} }
], ],
"uses": { "uses": {
"value": null, "value": null,
"max": "", "max": "",
"recovery": null, "recovery": null
"consumeOnSuccess": false
}, },
"effects": [ "effects": [],
{
"_id": "gFeHLGgeRoDdd3VG",
"onSave": false
}
],
"target": { "target": {
"type": "hostile", "type": "self",
"amount": null "amount": null
}, },
"name": "Spend Fear", "name": "Spend Fear",
"range": "far" "img": "icons/skills/melee/maneuver-greatsword-yellow.webp",
"range": ""
} }
}, },
"originItemType": null, "originItemType": null,
@ -303,51 +292,7 @@
}, },
"_id": "a33PW8UkziliowlR", "_id": "a33PW8UkziliowlR",
"img": "icons/skills/melee/maneuver-greatsword-yellow.webp", "img": "icons/skills/melee/maneuver-greatsword-yellow.webp",
"effects": [ "effects": [],
{
"name": "Battle Lust",
"img": "icons/skills/melee/maneuver-greatsword-yellow.webp",
"origin": "Compendium.daggerheart.adversaries.Actor.5lphJAgzoqZI3VoG.Item.a33PW8UkziliowlR",
"transfer": false,
"_id": "gFeHLGgeRoDdd3VG",
"type": "base",
"system": {
"rangeDependence": {
"enabled": false,
"type": "withinRange",
"target": "hostile",
"range": "melee"
}
},
"changes": [
{
"key": "system.rules.dualityRoll.defaultFearDice",
"mode": 5,
"value": "d20",
"priority": null
}
],
"disabled": false,
"duration": {
"startTime": null,
"combat": null,
"seconds": null,
"rounds": null,
"turns": null,
"startRound": null,
"startTurn": null
},
"description": "<p>You use a d20 as your Fear Die until the end of the scene.</p>",
"tint": "#ffffff",
"statuses": [],
"sort": 0,
"flags": {},
"_stats": {
"compendiumSource": null
},
"_key": "!actors.items.effects!5lphJAgzoqZI3VoG.a33PW8UkziliowlR.gFeHLGgeRoDdd3VG"
}
],
"folder": null, "folder": null,
"sort": 0, "sort": 0,
"ownership": { "ownership": {
@ -512,12 +457,11 @@
"img": "icons/creatures/unholy/demon-fire-horned-clawed.webp", "img": "icons/creatures/unholy/demon-fire-horned-clawed.webp",
"range": "" "range": ""
}, },
"FlE6i0tbKEguF9wz": { "7G6uWlFEeOLsJIWY": {
"type": "summon", "type": "effect",
"_id": "FlE6i0tbKEguF9wz", "_id": "7G6uWlFEeOLsJIWY",
"systemPath": "actions", "systemPath": "actions",
"baseAction": false, "description": "<p>Summon [[/r 1d4]]@UUID[Compendium.daggerheart.adversaries.Actor.3tqCjDwJAQ7JKqMb]{Minor Demons}, who appear at Close range.</p>",
"description": "",
"chatDisplay": true, "chatDisplay": true,
"originItem": { "originItem": {
"type": "itemCollection" "type": "itemCollection"
@ -530,13 +474,13 @@
"recovery": null, "recovery": null,
"consumeOnSuccess": false "consumeOnSuccess": false
}, },
"summon": [ "effects": [],
{ "target": {
"actorUUID": "Compendium.daggerheart.adversaries.Actor.3tqCjDwJAQ7JKqMb", "type": "any",
"count": "1d4" "amount": null
} },
],
"name": "Summon", "name": "Summon",
"img": "icons/creatures/unholy/demon-fire-horned-clawed.webp",
"range": "" "range": ""
} }
}, },

View file

@ -363,31 +363,33 @@
"description": "<p><strong>Spend a Fear</strong> to grow three @UUID[Compendium.daggerheart.adversaries.Actor.o63nS0k3wHu6EgKP]{Treant Sapling Minions}, who appear at Close range and immediately take the spotlight.</p>", "description": "<p><strong>Spend a Fear</strong> to grow three @UUID[Compendium.daggerheart.adversaries.Actor.o63nS0k3wHu6EgKP]{Treant Sapling Minions}, who appear at Close range and immediately take the spotlight.</p>",
"resource": null, "resource": null,
"actions": { "actions": {
"R84DdS0OIx2cUt1w": { "84Q2b0zIY9c7Yhho": {
"type": "summon", "type": "effect",
"_id": "R84DdS0OIx2cUt1w", "_id": "84Q2b0zIY9c7Yhho",
"systemPath": "actions", "systemPath": "actions",
"baseAction": false,
"description": "", "description": "",
"chatDisplay": true, "chatDisplay": true,
"originItem": {
"type": "itemCollection"
},
"actionType": "action", "actionType": "action",
"cost": [], "cost": [
{
"scalable": false,
"key": "fear",
"value": 1,
"step": null
}
],
"uses": { "uses": {
"value": null, "value": null,
"max": "", "max": "",
"recovery": null, "recovery": null
"consumeOnSuccess": false },
"effects": [],
"target": {
"type": "self",
"amount": null
}, },
"summon": [
{
"actorUUID": "Compendium.daggerheart.adversaries.Actor.o63nS0k3wHu6EgKP",
"count": "3"
}
],
"name": "Spend Fear", "name": "Spend Fear",
"img": "icons/magic/unholy/orb-hands-pink.webp",
"range": "" "range": ""
} }
}, },

View file

@ -510,41 +510,34 @@
"description": "<p>When the @Lookup[@name] has 3 or more HP marked, you can <strong>spend a Fear</strong> to split them into two @UUID[Compendium.daggerheart.adversaries.Actor.aLkLFuVoKz2NLoBK]{Tiny Green Oozes} (with no marked HP or Stress). Immediately spotlight both of them.</p>", "description": "<p>When the @Lookup[@name] has 3 or more HP marked, you can <strong>spend a Fear</strong> to split them into two @UUID[Compendium.daggerheart.adversaries.Actor.aLkLFuVoKz2NLoBK]{Tiny Green Oozes} (with no marked HP or Stress). Immediately spotlight both of them.</p>",
"resource": null, "resource": null,
"actions": { "actions": {
"J8U7dw3cDSsEirr5": { "s5mLw6DRGd76MLcC": {
"type": "summon", "type": "effect",
"_id": "J8U7dw3cDSsEirr5", "_id": "s5mLw6DRGd76MLcC",
"systemPath": "actions", "systemPath": "actions",
"baseAction": false,
"description": "", "description": "",
"chatDisplay": true, "chatDisplay": true,
"originItem": {
"type": "itemCollection"
},
"actionType": "action", "actionType": "action",
"cost": [ "cost": [
{ {
"scalable": false, "scalable": false,
"key": "fear", "key": "fear",
"value": 1, "value": 1,
"itemId": null, "step": null
"step": null,
"consumeOnSuccess": false
} }
], ],
"uses": { "uses": {
"value": null, "value": null,
"max": "", "max": "",
"recovery": null, "recovery": null
"consumeOnSuccess": false },
"effects": [],
"target": {
"type": "self",
"amount": null
}, },
"summon": [
{
"actorUUID": "Compendium.daggerheart.adversaries.Actor.aLkLFuVoKz2NLoBK",
"count": "2"
}
],
"name": "Spend Fear", "name": "Spend Fear",
"range": "self" "img": "icons/creatures/slimes/slime-movement-pseudopods-green.webp",
"range": ""
} }
}, },
"originItemType": null, "originItemType": null,

View file

@ -474,31 +474,33 @@
"description": "<p><strong>Spend 2 Fear</strong> to summon [[/r 1d4]] @UUID[Compendium.daggerheart.adversaries.Actor.WWyUp6Mxl1S3KYUG]{Vampires}, who appear at Far range and immediately take the spotlight.</p>", "description": "<p><strong>Spend 2 Fear</strong> to summon [[/r 1d4]] @UUID[Compendium.daggerheart.adversaries.Actor.WWyUp6Mxl1S3KYUG]{Vampires}, who appear at Far range and immediately take the spotlight.</p>",
"resource": null, "resource": null,
"actions": { "actions": {
"jGFOnU6PNdWU6iF4": { "5Q6RMUTiauKw0tDj": {
"type": "summon", "type": "effect",
"_id": "jGFOnU6PNdWU6iF4", "_id": "5Q6RMUTiauKw0tDj",
"systemPath": "actions", "systemPath": "actions",
"baseAction": false,
"description": "", "description": "",
"chatDisplay": true, "chatDisplay": true,
"originItem": {
"type": "itemCollection"
},
"actionType": "action", "actionType": "action",
"cost": [], "cost": [
{
"scalable": false,
"key": "fear",
"value": 2,
"step": null
}
],
"uses": { "uses": {
"value": null, "value": null,
"max": "", "max": "",
"recovery": null, "recovery": null
"consumeOnSuccess": false
}, },
"summon": [ "effects": [],
{ "target": {
"actorUUID": "Compendium.daggerheart.adversaries.Actor.WWyUp6Mxl1S3KYUG", "type": "any",
"count": "1d4" "amount": null
} },
], "name": "Summon Vampires",
"name": "Spend Fear", "img": "icons/creatures/mammals/bat-giant-tattered-purple.webp",
"range": "" "range": ""
} }
}, },

View file

@ -479,31 +479,33 @@
"description": "<p>When the @Lookup[@name] has 4 or more HP marked, you can <strong>spend a Fear</strong> to split them into two @UUID[Compendium.daggerheart.adversaries.Actor.SHXedd9zZPVfUgUa]{Green Oozes}(with no marked HP or Stress). Immediately spotlight both of them.</p>", "description": "<p>When the @Lookup[@name] has 4 or more HP marked, you can <strong>spend a Fear</strong> to split them into two @UUID[Compendium.daggerheart.adversaries.Actor.SHXedd9zZPVfUgUa]{Green Oozes}(with no marked HP or Stress). Immediately spotlight both of them.</p>",
"resource": null, "resource": null,
"actions": { "actions": {
"aeRdkiRsDNagTKhp": { "iQsYAqpUFvJslRDr": {
"type": "summon", "type": "effect",
"_id": "aeRdkiRsDNagTKhp", "_id": "iQsYAqpUFvJslRDr",
"systemPath": "actions", "systemPath": "actions",
"baseAction": false,
"description": "", "description": "",
"chatDisplay": true, "chatDisplay": true,
"originItem": {
"type": "itemCollection"
},
"actionType": "action", "actionType": "action",
"cost": [], "cost": [
{
"scalable": false,
"key": "fear",
"value": 1,
"step": null
}
],
"uses": { "uses": {
"value": null, "value": null,
"max": "", "max": "",
"recovery": null, "recovery": null
"consumeOnSuccess": false },
"effects": [],
"target": {
"type": "any",
"amount": null
}, },
"summon": [
{
"actorUUID": "Compendium.daggerheart.adversaries.Actor.SHXedd9zZPVfUgUa",
"count": "2"
}
],
"name": "Spend Fear", "name": "Spend Fear",
"img": "icons/creatures/slimes/slime-movement-pseudopods-green.webp",
"range": "" "range": ""
} }
}, },

View file

@ -287,35 +287,7 @@
"system": { "system": {
"description": "<p>Summon three @Compendium[daggerheart.adversaries.Actor.C0OMQqV7pN6t7ouR], who appear at Far range.</p>", "description": "<p>Summon three @Compendium[daggerheart.adversaries.Actor.C0OMQqV7pN6t7ouR], who appear at Far range.</p>",
"resource": null, "resource": null,
"actions": { "actions": {},
"MCTBsw9lusUdubj0": {
"type": "summon",
"_id": "MCTBsw9lusUdubj0",
"systemPath": "actions",
"baseAction": false,
"description": "",
"chatDisplay": true,
"originItem": {
"type": "itemCollection"
},
"actionType": "action",
"cost": [],
"uses": {
"value": null,
"max": "",
"recovery": null,
"consumeOnSuccess": false
},
"summon": [
{
"actorUUID": "Compendium.daggerheart.adversaries.Actor.C0OMQqV7pN6t7ouR",
"count": "3"
}
],
"name": "Summon",
"range": ""
}
},
"originItemType": null, "originItemType": null,
"subType": null, "subType": null,
"originId": null, "originId": null,

View file

@ -258,40 +258,57 @@
"description": "<p>Once per scene, <strong>mark a Stress</strong> to summon <strong>1d4</strong> @UUID[Compendium.daggerheart.adversaries.Actor.B4LZcGuBAHzyVdzy]{Bladed Guards}, who appear at Far range to enforce the @Lookup[@name]s will.</p>", "description": "<p>Once per scene, <strong>mark a Stress</strong> to summon <strong>1d4</strong> @UUID[Compendium.daggerheart.adversaries.Actor.B4LZcGuBAHzyVdzy]{Bladed Guards}, who appear at Far range to enforce the @Lookup[@name]s will.</p>",
"resource": null, "resource": null,
"actions": { "actions": {
"tioTtYfIGFIXRITN": { "cUKwhq1imsTVru8D": {
"type": "summon", "type": "attack",
"_id": "tioTtYfIGFIXRITN", "_id": "cUKwhq1imsTVru8D",
"systemPath": "actions", "systemPath": "actions",
"baseAction": false, "description": "<p>Once per scene, <strong>mark a Stress</strong> to summon <strong>1d4</strong> @UUID[Compendium.daggerheart.adversaries.Actor.B4LZcGuBAHzyVdzy]{Bladed Guards}, who appear at Far range to enforce the Nobles will.</p>",
"description": "",
"chatDisplay": true, "chatDisplay": true,
"originItem": {
"type": "itemCollection"
},
"actionType": "action", "actionType": "action",
"cost": [ "cost": [
{ {
"scalable": false, "scalable": false,
"key": "stress", "key": "stress",
"value": 1, "value": 1,
"itemId": null, "step": null
"step": null,
"consumeOnSuccess": false
} }
], ],
"uses": { "uses": {
"value": null, "value": null,
"max": "1", "max": "",
"recovery": "scene", "recovery": null
"consumeOnSuccess": false },
"damage": {
"parts": [],
"includeBase": false
},
"target": {
"type": "any",
"amount": null
},
"effects": [],
"roll": {
"type": "diceSet",
"trait": null,
"difficulty": null,
"bonus": null,
"advState": "neutral",
"diceRolling": {
"multiplier": "prof",
"flatMultiplier": 1,
"dice": "d4",
"compare": null,
"treshold": null
},
"useDefault": false
},
"save": {
"trait": null,
"difficulty": null,
"damageMod": "none"
}, },
"summon": [
{
"actorUUID": "Compendium.daggerheart.adversaries.Actor.B4LZcGuBAHzyVdzy",
"count": "1d4"
}
],
"name": "Summon Guards", "name": "Summon Guards",
"img": "icons/environment/people/infantry-armored.webp",
"range": "" "range": ""
} }
}, },

View file

@ -313,43 +313,36 @@
"_id": "WGEGO0DSOs5cF0EL", "_id": "WGEGO0DSOs5cF0EL",
"img": "icons/environment/people/charge.webp", "img": "icons/environment/people/charge.webp",
"system": { "system": {
"description": "<p>Once per scene, <strong>mark a Stress</strong> to summon a @UUID[Compendium.daggerheart.adversaries.Actor.5YgEajn0wa4i85kC]{Pirate Raider Horde}, which appears at Far range.</p>", "description": "<p>Once per scene, <strong>mark a Stress</strong> to summon a Pirate Raiders Horde, which appears at Far range.</p>",
"resource": null, "resource": null,
"actions": { "actions": {
"nuYk5WeLLpIKa69q": { "NlgIp0KrmZoS27Xy": {
"type": "summon", "type": "effect",
"_id": "nuYk5WeLLpIKa69q", "_id": "NlgIp0KrmZoS27Xy",
"systemPath": "actions", "systemPath": "actions",
"baseAction": false,
"description": "", "description": "",
"chatDisplay": true, "chatDisplay": true,
"originItem": {
"type": "itemCollection"
},
"actionType": "action", "actionType": "action",
"cost": [ "cost": [
{ {
"scalable": false, "scalable": false,
"key": "stress", "key": "stress",
"value": 1, "value": 1,
"itemId": null, "step": null
"step": null,
"consumeOnSuccess": false
} }
], ],
"uses": { "uses": {
"value": null, "value": null,
"max": "", "max": "",
"recovery": null, "recovery": null
"consumeOnSuccess": false },
"effects": [],
"target": {
"type": "any",
"amount": null
}, },
"summon": [
{
"actorUUID": "Compendium.daggerheart.adversaries.Actor.5YgEajn0wa4i85kC",
"count": "1"
}
],
"name": "Mark Stress", "name": "Mark Stress",
"img": "icons/environment/people/charge.webp",
"range": "" "range": ""
} }
}, },

View file

@ -454,40 +454,33 @@
"description": "<p>When the @Lookup[@name] has 3 or more HP marked, you can <strong>spend a Fear</strong> to split them into two @UUID[Compendium.daggerheart.adversaries.Actor.1fkLQXVtmILqfJ44]{Tiny Red Oozes} (with no marked HP or Stress). Immediately spotlight both of them.</p>", "description": "<p>When the @Lookup[@name] has 3 or more HP marked, you can <strong>spend a Fear</strong> to split them into two @UUID[Compendium.daggerheart.adversaries.Actor.1fkLQXVtmILqfJ44]{Tiny Red Oozes} (with no marked HP or Stress). Immediately spotlight both of them.</p>",
"resource": null, "resource": null,
"actions": { "actions": {
"BMEr77hDxaQyYBna": { "dw6Juw8mriH7sg0e": {
"type": "summon", "type": "effect",
"_id": "BMEr77hDxaQyYBna", "_id": "dw6Juw8mriH7sg0e",
"systemPath": "actions", "systemPath": "actions",
"baseAction": false,
"description": "", "description": "",
"chatDisplay": true, "chatDisplay": true,
"originItem": {
"type": "itemCollection"
},
"actionType": "action", "actionType": "action",
"cost": [ "cost": [
{ {
"scalable": false, "scalable": false,
"key": "fear", "key": "fear",
"value": 1, "value": 1,
"itemId": null, "step": null
"step": null,
"consumeOnSuccess": false
} }
], ],
"uses": { "uses": {
"value": null, "value": null,
"max": "", "max": "",
"recovery": null, "recovery": null
"consumeOnSuccess": false },
"effects": [],
"target": {
"type": "any",
"amount": null
}, },
"summon": [
{
"actorUUID": "Compendium.daggerheart.adversaries.Actor.1fkLQXVtmILqfJ44",
"count": "2"
}
],
"name": "Spend Fear", "name": "Spend Fear",
"img": "icons/creatures/slimes/slime-movement-splashing-red.webp",
"range": "" "range": ""
} }
}, },

View file

@ -416,6 +416,28 @@
"description": "<p><em>Countdown (6)</em>. When the @Lookup[@name] is in the spotlight for the first time, activate the countdown. When they mark HP, tick down this countdown by the number of HP marked. When it triggers, summon a @UUID[Compendium.daggerheart.adversaries.Actor.3tqCjDwJAQ7JKqMb]{Minor Demon} who appears at Close range.</p>", "description": "<p><em>Countdown (6)</em>. When the @Lookup[@name] is in the spotlight for the first time, activate the countdown. When they mark HP, tick down this countdown by the number of HP marked. When it triggers, summon a @UUID[Compendium.daggerheart.adversaries.Actor.3tqCjDwJAQ7JKqMb]{Minor Demon} who appears at Close range.</p>",
"resource": null, "resource": null,
"actions": { "actions": {
"0rixG6jLRynAYNqA": {
"type": "effect",
"_id": "0rixG6jLRynAYNqA",
"systemPath": "actions",
"description": "<p>Summon a @UUID[Compendium.daggerheart.adversaries.Actor.3tqCjDwJAQ7JKqMb]{Minor Demon} who appears at Close range.</p>",
"chatDisplay": true,
"actionType": "action",
"cost": [],
"uses": {
"value": null,
"max": "",
"recovery": null
},
"effects": [],
"target": {
"type": "any",
"amount": null
},
"name": "Summon",
"img": "icons/magic/unholy/silhouette-light-fire-blue.webp",
"range": "close"
},
"ZVXHY2fpomoKV7jG": { "ZVXHY2fpomoKV7jG": {
"type": "countdown", "type": "countdown",
"_id": "ZVXHY2fpomoKV7jG", "_id": "ZVXHY2fpomoKV7jG",
@ -452,33 +474,6 @@
"name": "Start Countdown", "name": "Start Countdown",
"img": "icons/magic/unholy/silhouette-light-fire-blue.webp", "img": "icons/magic/unholy/silhouette-light-fire-blue.webp",
"range": "" "range": ""
},
"YReYG6DrWp4QGSij": {
"type": "summon",
"_id": "YReYG6DrWp4QGSij",
"systemPath": "actions",
"baseAction": false,
"description": "",
"chatDisplay": true,
"originItem": {
"type": "itemCollection"
},
"actionType": "action",
"cost": [],
"uses": {
"value": null,
"max": "",
"recovery": null,
"consumeOnSuccess": false
},
"summon": [
{
"actorUUID": "Compendium.daggerheart.adversaries.Actor.3tqCjDwJAQ7JKqMb",
"count": "1"
}
],
"name": "Summon",
"range": ""
} }
}, },
"originItemType": null, "originItemType": null,
@ -507,31 +502,33 @@
"description": "<p>Once per scene, when the @Lookup[@name] marks 2 or more HP, you can <strong>mark a Stress</strong> to summon a @UUID[Compendium.daggerheart.adversaries.Actor.NoRZ1PqB8N5wcIw0]{Demonic Hound Pack}, which appears at Close range and is immediately spotlighted.</p>", "description": "<p>Once per scene, when the @Lookup[@name] marks 2 or more HP, you can <strong>mark a Stress</strong> to summon a @UUID[Compendium.daggerheart.adversaries.Actor.NoRZ1PqB8N5wcIw0]{Demonic Hound Pack}, which appears at Close range and is immediately spotlighted.</p>",
"resource": null, "resource": null,
"actions": { "actions": {
"tfmY6HYkkY27NBaF": { "JBuQUJhif2A7IlJd": {
"type": "summon", "type": "effect",
"_id": "tfmY6HYkkY27NBaF", "_id": "JBuQUJhif2A7IlJd",
"systemPath": "actions", "systemPath": "actions",
"baseAction": false,
"description": "", "description": "",
"chatDisplay": true, "chatDisplay": true,
"originItem": {
"type": "itemCollection"
},
"actionType": "action", "actionType": "action",
"cost": [], "cost": [
"uses": {
"value": null,
"max": "",
"recovery": null,
"consumeOnSuccess": false
},
"summon": [
{ {
"actorUUID": "Compendium.daggerheart.adversaries.Actor.NoRZ1PqB8N5wcIw0", "scalable": false,
"count": "1" "key": "stress",
"value": 1,
"step": null
} }
], ],
"uses": {
"value": null,
"max": "1",
"recovery": "scene"
},
"effects": [],
"target": {
"type": "self",
"amount": null
},
"name": "Mark Stress", "name": "Mark Stress",
"img": "icons/creatures/unholy/demon-fire-horned-clawed.webp",
"range": "" "range": ""
} }
}, },

View file

@ -340,35 +340,7 @@
"system": { "system": {
"description": "<p>When an attack from the @Lookup[@name] causes a target to mark HP and there are three or more @Lookup[@name] Minions within Close range, you can combine the Minions into a @UUID[Compendium.daggerheart.adversaries.Actor.PKSXFuaIHUCoH63A]{Tangle Bramble Swarm Horde}. The Hordes HP is equal to the number of Minions combined.</p>", "description": "<p>When an attack from the @Lookup[@name] causes a target to mark HP and there are three or more @Lookup[@name] Minions within Close range, you can combine the Minions into a @UUID[Compendium.daggerheart.adversaries.Actor.PKSXFuaIHUCoH63A]{Tangle Bramble Swarm Horde}. The Hordes HP is equal to the number of Minions combined.</p>",
"resource": null, "resource": null,
"actions": { "actions": {},
"g1OQ5xlMHFWsoktd": {
"type": "summon",
"_id": "g1OQ5xlMHFWsoktd",
"systemPath": "actions",
"baseAction": false,
"description": "",
"chatDisplay": true,
"originItem": {
"type": "itemCollection"
},
"actionType": "action",
"cost": [],
"uses": {
"value": null,
"max": "",
"recovery": null,
"consumeOnSuccess": false
},
"summon": [
{
"actorUUID": "Compendium.daggerheart.adversaries.Actor.PKSXFuaIHUCoH63A",
"count": "1"
}
],
"name": "Summon",
"range": ""
}
},
"originItemType": null, "originItemType": null,
"subType": null, "subType": null,
"originId": null, "originId": null,

View file

@ -36,13 +36,13 @@
"key": "system.damageThresholds.major", "key": "system.damageThresholds.major",
"mode": 2, "mode": 2,
"value": "@prof", "value": "@prof",
"priority": 21 "priority": null
}, },
{ {
"key": "system.damageThresholds.severe", "key": "system.damageThresholds.severe",
"mode": 2, "mode": 2,
"value": "@prof", "value": "@prof",
"priority": 21 "priority": null
} }
], ],
"disabled": false, "disabled": false,

View file

@ -100,7 +100,7 @@
"key": "system.resistance.physical.reduction", "key": "system.resistance.physical.reduction",
"mode": 2, "mode": 2,
"value": "@system.armorScore", "value": "@system.armorScore",
"priority": 21 "priority": null
} }
], ],
"disabled": false, "disabled": false,

View file

@ -80,14 +80,7 @@
}, },
"name": "Clear Stress", "name": "Clear Stress",
"img": "icons/magic/symbols/rune-sigil-black-pink.webp", "img": "icons/magic/symbols/rune-sigil-black-pink.webp",
"range": "", "range": ""
"triggers": [
{
"trigger": "dualityRoll",
"triggeringActorType": "self",
"command": "/* Ignore if it's a TagTeam roll */\nconst tagTeam = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);\nif (tagTeam.members[actor.id]) return;\n\n/* Check if there's a Strange Pattern match */\nconst dice = [roll.dFear.total, roll.dHope.total];\nconst resource = this.parent.resource?.diceStates ? Object.values(this.parent.resource.diceStates).map(x => x.value)[0] : null;\nconst nrMatches = dice.filter(x => x === resource).length;\n\nif (!nrMatches) return;\n\n/* Create a dialog to choose Hope or Stress - or to cancel*/\nconst content = `\n <div><div>${game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerTexts.strangePatternsContentTitle', { nr: nrMatches })}</div>\n <div>${game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerTexts.strangePatternsContentSubTitle', { nr: nrMatches })}</div>\n <div class=\"flexrow\" style=\"gap: 8px;\">\n <button type=\"button\" id=\"hopeButton\">\n <i class=\"fa-solid fa-hands-holding\"></i>\n <label>0</label>\n </button>\n <button type=\"button\" id=\"stressButton\">\n <i class=\"fa-solid fa-bolt-lightning\"></i>\n <label>0</label>\n </button>\n </div>\n</div>`;\n\nconst result = await foundry.applications.api.DialogV2.input({\n classes: ['dh-style', 'two-big-buttons'],\n window: { title: this.item.name },\n content: content,\n render: (_, dialog) => {\n const hopeButton = dialog.element.querySelector('#hopeButton');\n const stressButton = dialog.element.querySelector('#stressButton');\ndialog.element.querySelector('button[type=\"submit\"]').disabled = true;\n \n const updateFunc = (event, selector, adding, clamp) => {\n const button = event.target.closest(`#${selector}Button`);\n const parent = event.target.closest('.flexrow');\n const hope = Number.parseInt(parent.querySelector('#hopeButton label').innerHTML);\n const stress = Number.parseInt(parent.querySelector('#stressButton label').innerHTML);\n const currentTotal = (Number.isNumeric(hope) ? hope : 0) + (Number.isNumeric(stress) ? stress : 0);\n if (adding && currentTotal === nrMatches) return;\n \n const current = Number.parseInt(button.querySelector('label').innerHTML);\n if (!adding && current === 0) return;\n \n const value = Number.isNumeric(current) ? adding ? current+1 : current-1 : 1;\n if (!dialog.data) dialog.data = {};\n dialog.data[selector] = clamp(value);\n button.querySelector('label').innerHTML = dialog.data[selector];\n\n event.target.closest('.dialog-form').querySelector('button[type=\"submit\"]').disabled = !adding || currentTotal < (nrMatches-1);\n \n };\n hopeButton.addEventListener('click', event => updateFunc(event, 'hope', true, x => Math.min(x, nrMatches)));\n hopeButton.addEventListener('contextmenu', event => updateFunc(event, 'hope', false, x => Math.max(x, 0)));\n stressButton.addEventListener('click', event => updateFunc(event, 'stress', true, x => Math.min(x, nrMatches)));\n stressButton.addEventListener('contextmenu', event => updateFunc(event, 'stress', false, x => Math.max(x, 0)));\n },\n ok: { callback: (_event, _result, dialog) => {\n const hope = dialog.data.hope ?? 0;\n const stress = dialog.data.stress ?? 0;\n if (!hope && !stress) return;\n\n /* Return resource update according to choices */\n const hopeUpdate = hope ? { key: 'hope', value: hope, total: -hope, enabled: true } : null;\n const stressUpdate = stress ? { key: 'stress', value: -stress, total: stress, enabled: true } : null;\n return { updates: [hopeUpdate, stressUpdate].filter(x => x) };\n }}\n});\n\nreturn result;"
}
]
} }
}, },
"originItemType": null, "originItemType": null,

View file

@ -54,8 +54,7 @@
"source": "Daggerheart SRD", "source": "Daggerheart SRD",
"page": 120, "page": 120,
"artist": "" "artist": ""
}, }
"domainTouched": 4
}, },
"flags": {}, "flags": {},
"_id": "5PvMQKCjrgSxzstn", "_id": "5PvMQKCjrgSxzstn",

View file

@ -13,8 +13,7 @@
"source": "Daggerheart SRD", "source": "Daggerheart SRD",
"page": 121, "page": 121,
"artist": "" "artist": ""
}, }
"domainTouched": 4
}, },
"flags": {}, "flags": {},
"_id": "Gb5bqpFSBiuBxUix", "_id": "Gb5bqpFSBiuBxUix",

View file

@ -38,13 +38,13 @@
"key": "system.bonuses.damage.primaryWeapon.bonus", "key": "system.bonuses.damage.primaryWeapon.bonus",
"mode": 2, "mode": 2,
"value": "@system.traits.strength.value", "value": "@system.traits.strength.value",
"priority": 21 "priority": null
}, },
{ {
"key": "system.bonuses.damage.secondaryWeapon.bonus", "key": "system.bonuses.damage.secondaryWeapon.bonus",
"mode": 2, "mode": 2,
"value": "@system.traits.strength.value", "value": "@system.traits.strength.value",
"priority": 21 "priority": null
} }
], ],
"disabled": false, "disabled": false,
@ -57,7 +57,7 @@
"startRound": null, "startRound": null,
"startTurn": null "startTurn": null
}, },
"description": "<p><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\">On a successful attack using a weapon with a </span><span class=\"tooltip-convert\" style=\"box-sizing:border-box;scrollbar-width:thin;scrollbar-color:rgb(93, 20, 43) rgba(0, 0, 0, 0);color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial\">Melee</span><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\"> range, gain a bonus to your damage roll equal to your </span><span class=\"tooltip-convert\" style=\"box-sizing:border-box;scrollbar-width:thin;scrollbar-color:rgb(93, 20, 43) rgba(0, 0, 0, 0);color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial\">Strength</span><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\">.</span></p>", "description": "<p><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\">On a successful attack using a weapon with a </span><span class=\"tooltip-convert\" style=\"box-sizing:border-box;scrollbar-width:thin;scrollbar-color:rgb(93, 20, 43) rgba(0, 0, 0, 0);color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial\">Melee</span><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\"> range, gain a bonus to your damage roll equal to your </span><span class=\"tooltip-convert\" style=\"box-sizing:border-box;scrollbar-width:thin;scrollbar-color:rgb(93, 20, 43) rgba(0, 0, 0, 0);color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial\">Strength</span><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\">.</p>",
"origin": null, "origin": null,
"tint": "#ffffff", "tint": "#ffffff",
"transfer": true, "transfer": true,

View file

@ -46,8 +46,7 @@
"source": "Daggerheart SRD", "source": "Daggerheart SRD",
"page": 123, "page": 123,
"artist": "" "artist": ""
}, }
"domainTouched": 4
}, },
"flags": {}, "flags": {},
"_id": "ON5bvnoQBy0SYc9Y", "_id": "ON5bvnoQBy0SYc9Y",

View file

@ -71,8 +71,7 @@
"source": "Daggerheart SRD", "source": "Daggerheart SRD",
"page": 125, "page": 125,
"artist": "" "artist": ""
}, }
"domainTouched": 4
}, },
"flags": {}, "flags": {},
"_id": "7Pu83ABdMukTxu3e", "_id": "7Pu83ABdMukTxu3e",

View file

@ -37,13 +37,13 @@
"key": "system.bonuses.damage.primaryWeapon.bonus", "key": "system.bonuses.damage.primaryWeapon.bonus",
"mode": 2, "mode": 2,
"value": "@system.traits.agility.value", "value": "@system.traits.agility.value",
"priority": 21 "priority": null
}, },
{ {
"key": "system.bonuses.damage.secondaryWeapon.bonus", "key": "system.bonuses.damage.secondaryWeapon.bonus",
"mode": 2, "mode": 2,
"value": "@system.traits.agility.value", "value": "@system.traits.agility.value",
"priority": 21 "priority": null
} }
], ],
"disabled": true, "disabled": true,

View file

@ -17,16 +17,7 @@
"description": "<p class=\"Body-Foundation\">When you cause an adversary to mark 1 or more Hit Points, you can <strong>spend 2 Hope</strong> to increase your Evasion by the number of Hit Points they marked. This bonus lasts until after the next attack made against you.</p>", "description": "<p class=\"Body-Foundation\">When you cause an adversary to mark 1 or more Hit Points, you can <strong>spend 2 Hope</strong> to increase your Evasion by the number of Hit Points they marked. This bonus lasts until after the next attack made against you.</p>",
"chatDisplay": true, "chatDisplay": true,
"actionType": "action", "actionType": "action",
"cost": [ "cost": [],
{
"scalable": false,
"key": "hope",
"value": 2,
"itemId": null,
"step": null,
"consumeOnSuccess": false
}
],
"uses": { "uses": {
"value": null, "value": null,
"max": "", "max": "",
@ -39,15 +30,8 @@
"amount": null "amount": null
}, },
"name": "Spend Hope", "name": "Spend Hope",
"img": "icons/skills/melee/maneuver-sword-katana-yellow.webp", "img": "icons/skills/melee/maneuver-daggers-paired-orange.webp",
"range": "", "range": ""
"triggers": [
{
"trigger": "postDamageReduction",
"triggeringActorType": "other",
"command": "/* Check if sufficient hope */\nif (this.actor.system.resources.hope.value < 2) return;\n\n/* Check if hit point damage was dealt */\nconst hpDamage = damageUpdates.find(u => u.key === CONFIG.DH.GENERAL.healingTypes.hitPoints.id);\nif (hpDamage.value < 0) return;\n\n/* Dialog to give player choice */\nconst confirmed = await foundry.applications.api.DialogV2.confirm({\n window: { title: this.item?.name ?? '' },\n content: game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerTexts.ferocityContent', { bonus: hpDamage.value }),\n});\n\nif (!confirmed) return;\n\n/* Create the effect */\nthis.actor.createEmbeddedDocuments('ActiveEffect', [{\n name: this.item.name,\n img: 'icons/skills/melee/maneuver-sword-katana-yellow.webp',\n description: game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerTexts.ferocityEffectDescription', { bonus: hpDamage.value }),\n changes: [{ key: 'system.evasion', mode: 2, value: hpDamage.value }]\n}]);\n\n/* Update hope */\nreturn { updates: [{ \n originActor: this.actor, \n updates: [{\n key: CONFIG.DH.GENERAL.healingTypes.hope.id,\n value: -2,\n total: 2\n }] \n}]}"
}
]
} }
}, },
"attribution": { "attribution": {

View file

@ -18,7 +18,7 @@
}, },
"flags": {}, "flags": {},
"_id": "BFWN2cObMdlk9uVz", "_id": "BFWN2cObMdlk9uVz",
"sort": 3500000, "sort": 3400000,
"effects": [ "effects": [
{ {
"name": "Get Back Up", "name": "Get Back Up",

View file

@ -96,8 +96,7 @@
"source": "Daggerheart SRD", "source": "Daggerheart SRD",
"page": 127, "page": 127,
"artist": "" "artist": ""
}, }
"domainTouched": 4
}, },
"flags": {}, "flags": {},
"_id": "KAuNb51AwhD8KEXk", "_id": "KAuNb51AwhD8KEXk",

View file

@ -111,8 +111,7 @@
"source": "Daggerheart SRD", "source": "Daggerheart SRD",
"page": 129, "page": 129,
"artist": "" "artist": ""
}, }
"domainTouched": 4
}, },
"flags": {}, "flags": {},
"_id": "uSyGKVxOJcnp28po", "_id": "uSyGKVxOJcnp28po",

View file

@ -44,8 +44,7 @@
"source": "Daggerheart SRD", "source": "Daggerheart SRD",
"page": 127, "page": 127,
"artist": "" "artist": ""
}, }
"loadoutIgnore": true
}, },
"flags": {}, "flags": {},
"_id": "IqxzvvjZiYbgx21A", "_id": "IqxzvvjZiYbgx21A",

View file

@ -101,7 +101,7 @@
"key": "system.traits.presence.value", "key": "system.traits.presence.value",
"mode": 5, "mode": 5,
"value": "@cast", "value": "@cast",
"priority": 51 "priority": null
} }
], ],
"disabled": false, "disabled": false,

View file

@ -113,13 +113,13 @@
"key": "system.bonuses.damage.magical.bonus", "key": "system.bonuses.damage.magical.bonus",
"mode": 2, "mode": 2,
"value": "2*@system.traits.strength.value", "value": "2*@system.traits.strength.value",
"priority": 21 "priority": null
}, },
{ {
"key": "system.bonuses.damage.physical.bonus", "key": "system.bonuses.damage.physical.bonus",
"mode": 2, "mode": 2,
"value": "2*@system.traits.strength.value", "value": "2*@system.traits.strength.value",
"priority": 21 "priority": null
} }
], ],
"disabled": false, "disabled": false,
@ -162,13 +162,13 @@
"key": "system.bonuses.damage.magical.bonus", "key": "system.bonuses.damage.magical.bonus",
"mode": 2, "mode": 2,
"value": "4*@system.traits.strength.value", "value": "4*@system.traits.strength.value",
"priority": 21 "priority": null
}, },
{ {
"key": "system.bonuses.damage.physical.bonus", "key": "system.bonuses.damage.physical.bonus",
"mode": 2, "mode": 2,
"value": "4*@system.traits.strength.value", "value": "4*@system.traits.strength.value",
"priority": 21 "priority": null
} }
], ],
"disabled": false, "disabled": false,

View file

@ -106,7 +106,7 @@
"key": "system.damageThresholds.severe", "key": "system.damageThresholds.severe",
"mode": 2, "mode": 2,
"value": "@system.proficiency", "value": "@system.proficiency",
"priority": 21 "priority": null
} }
], ],
"disabled": false, "disabled": false,
@ -119,7 +119,7 @@
"startRound": null, "startRound": null,
"startTurn": null "startTurn": null
}, },
"description": "<p><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\">Gain a bonus to your </span><span class=\"tooltip-convert\" style=\"box-sizing:border-box;scrollbar-width:thin;scrollbar-color:rgb(93, 20, 43) rgba(0, 0, 0, 0);color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial\">Severe </span><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\">threshold equal to your </span><span class=\"tooltip-convert\" style=\"box-sizing:border-box;scrollbar-width:thin;scrollbar-color:rgb(93, 20, 43) rgba(0, 0, 0, 0);color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial\">Proficiency</span><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\">.</span></p>", "description": "<p><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\">Gain a bonus to your </span><span class=\"tooltip-convert\" style=\"box-sizing:border-box;scrollbar-width:thin;scrollbar-color:rgb(93, 20, 43) rgba(0, 0, 0, 0);color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial\">Severe </span><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\">threshold equal to your </span><span class=\"tooltip-convert\" style=\"box-sizing:border-box;scrollbar-width:thin;scrollbar-color:rgb(93, 20, 43) rgba(0, 0, 0, 0);color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial\">Proficiency</span><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\">.</p>",
"origin": null, "origin": null,
"tint": "#ffffff", "tint": "#ffffff",
"transfer": true, "transfer": true,

View file

@ -94,8 +94,7 @@
"source": "Daggerheart SRD", "source": "Daggerheart SRD",
"page": 131, "page": 131,
"artist": "" "artist": ""
}, }
"domainTouched": 4
}, },
"flags": {}, "flags": {},
"_id": "VOSFaQHZbmhMyXwi", "_id": "VOSFaQHZbmhMyXwi",

View file

@ -95,7 +95,7 @@
}, },
"flags": {}, "flags": {},
"_id": "4uAFGp3LxiC07woC", "_id": "4uAFGp3LxiC07woC",
"sort": 3500000, "sort": 3400000,
"effects": [], "effects": [],
"ownership": { "ownership": {
"default": 0 "default": 0

Some files were not shown because too many files have changed in this diff Show more