Compare commits

..

18 commits
main ... 1.3.2

Author SHA1 Message Date
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
397 changed files with 2552 additions and 10199 deletions

View file

@ -16,10 +16,10 @@ 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 { registerRollDiceHooks } from './module/dice/dhRoll.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,12 +52,8 @@ 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.Token.documentClass = documents.DhToken; CONFIG.Token.documentClass = documents.DhToken;
CONFIG.Token.prototypeSheetClass = applications.sheetConfigs.DhPrototypeTokenConfig; CONFIG.Token.prototypeSheetClass = applications.sheetConfigs.DhPrototypeTokenConfig;
CONFIG.Token.objectClass = placeables.DhTokenPlaceable; CONFIG.Token.objectClass = placeables.DhTokenPlaceable;
@ -65,7 +61,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 +72,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,102 +83,40 @@ 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, {
makeDefault: true makeDefault: true
}); });
const sheetLabel = typePath => () =>
game.i18n.format('DAGGERHEART.GENERAL.typeSheet', {
type: game.i18n.localize(typePath)
});
const { Items, Actors } = foundry.documents.collections; const { Items, Actors } = foundry.documents.collections;
Items.unregisterSheet('core', foundry.applications.sheets.ItemSheetV2); Items.unregisterSheet('core', foundry.applications.sheets.ItemSheetV2);
Items.registerSheet(SYSTEM.id, applications.sheets.items.Ancestry, { Items.registerSheet(SYSTEM.id, applications.sheets.items.Ancestry, { types: ['ancestry'], makeDefault: true });
types: ['ancestry'], Items.registerSheet(SYSTEM.id, applications.sheets.items.Community, { types: ['community'], makeDefault: true });
makeDefault: true, Items.registerSheet(SYSTEM.id, applications.sheets.items.Class, { types: ['class'], makeDefault: true });
label: sheetLabel('TYPES.Item.ancestry') Items.registerSheet(SYSTEM.id, applications.sheets.items.Subclass, { types: ['subclass'], makeDefault: true });
}); Items.registerSheet(SYSTEM.id, applications.sheets.items.Feature, { types: ['feature'], makeDefault: true });
Items.registerSheet(SYSTEM.id, applications.sheets.items.Community, { Items.registerSheet(SYSTEM.id, applications.sheets.items.DomainCard, { types: ['domainCard'], makeDefault: true });
types: ['community'],
makeDefault: true,
label: sheetLabel('TYPES.Item.community')
});
Items.registerSheet(SYSTEM.id, applications.sheets.items.Class, {
types: ['class'],
makeDefault: true,
label: sheetLabel('TYPES.Item.class')
});
Items.registerSheet(SYSTEM.id, applications.sheets.items.Subclass, {
types: ['subclass'],
makeDefault: true,
label: sheetLabel('TYPES.Item.subclass')
});
Items.registerSheet(SYSTEM.id, applications.sheets.items.Feature, {
types: ['feature'],
makeDefault: true,
label: sheetLabel('TYPES.Item.feature')
});
Items.registerSheet(SYSTEM.id, applications.sheets.items.DomainCard, {
types: ['domainCard'],
makeDefault: true,
label: sheetLabel('TYPES.Item.domainCard')
});
Items.registerSheet(SYSTEM.id, applications.sheets.items.Loot, { Items.registerSheet(SYSTEM.id, applications.sheets.items.Loot, {
types: ['loot'], types: ['loot'],
makeDefault: true, makeDefault: true
label: sheetLabel('TYPES.Item.loot')
});
Items.registerSheet(SYSTEM.id, applications.sheets.items.Consumable, {
types: ['consumable'],
makeDefault: true,
label: sheetLabel('TYPES.Item.consumable')
});
Items.registerSheet(SYSTEM.id, applications.sheets.items.Weapon, {
types: ['weapon'],
makeDefault: true,
label: sheetLabel('TYPES.Item.weapon')
});
Items.registerSheet(SYSTEM.id, applications.sheets.items.Armor, {
types: ['armor'],
makeDefault: true,
label: sheetLabel('TYPES.Item.armor')
});
Items.registerSheet(SYSTEM.id, applications.sheets.items.Beastform, {
types: ['beastform'],
makeDefault: true,
label: sheetLabel('TYPES.Item.beastform')
}); });
Items.registerSheet(SYSTEM.id, applications.sheets.items.Consumable, { types: ['consumable'], makeDefault: true });
Items.registerSheet(SYSTEM.id, applications.sheets.items.Weapon, { types: ['weapon'], makeDefault: true });
Items.registerSheet(SYSTEM.id, applications.sheets.items.Armor, { types: ['armor'], makeDefault: true });
Items.registerSheet(SYSTEM.id, applications.sheets.items.Beastform, { types: ['beastform'], makeDefault: true });
Actors.unregisterSheet('core', foundry.applications.sheets.ActorSheetV2); Actors.unregisterSheet('core', foundry.applications.sheets.ActorSheetV2);
Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Character, { Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Character, { types: ['character'], makeDefault: true });
types: ['character'], Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Companion, { types: ['companion'], makeDefault: true });
makeDefault: true, Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Adversary, { types: ['adversary'], makeDefault: true });
label: sheetLabel('TYPES.Actor.character')
});
Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Companion, {
types: ['companion'],
makeDefault: true,
label: sheetLabel('TYPES.Actor.companion')
});
Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Adversary, {
types: ['adversary'],
makeDefault: true,
label: sheetLabel('TYPES.Actor.adversary')
});
Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Environment, { Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Environment, {
types: ['environment'], types: ['environment'],
makeDefault: true, makeDefault: true
label: sheetLabel('TYPES.Actor.environment')
}); });
Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Party, { Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Party, {
types: ['party'], types: ['party'],
makeDefault: true, makeDefault: true
label: sheetLabel('TYPES.Actor.party')
}); });
DocumentSheetConfig.unregisterSheet( DocumentSheetConfig.unregisterSheet(
@ -197,8 +129,7 @@ Hooks.once('init', () => {
SYSTEM.id, SYSTEM.id,
applications.sheetConfigs.ActiveEffectConfig, applications.sheetConfigs.ActiveEffectConfig,
{ {
makeDefault: true, makeDefault: true
label: sheetLabel('DOCUMENT.ActiveEffect')
} }
); );
@ -207,10 +138,9 @@ Hooks.once('init', () => {
// Make Compendium Dialog resizable // Make Compendium Dialog resizable
foundry.applications.sidebar.apps.Compendium.DEFAULT_OPTIONS.window.resizable = true; foundry.applications.sidebar.apps.Compendium.DEFAULT_OPTIONS.window.resizable = true;
DocumentSheetConfig.unregisterSheet(foundry.documents.Scene, 'core', foundry.applications.sheets.SceneConfig);
DocumentSheetConfig.registerSheet(foundry.documents.Scene, SYSTEM.id, applications.scene.DhSceneConfigSettings, { DocumentSheetConfig.registerSheet(foundry.documents.Scene, SYSTEM.id, applications.scene.DhSceneConfigSettings, {
makeDefault: true, makeDefault: true,
label: sheetLabel('DOCUMENT.Scene') label: 'Daggerheart'
}); });
settingsRegistration.registerDHSettings(); settingsRegistration.registerDHSettings();
@ -247,6 +177,7 @@ Hooks.on('ready', async () => {
ui.compendiumBrowser = new applications.ui.ItemBrowser(); ui.compendiumBrowser = new applications.ui.ItemBrowser();
socketRegistration.registerSocketHooks(); socketRegistration.registerSocketHooks();
registerRollDiceHooks();
socketRegistration.registerUserQueries(); socketRegistration.registerUserQueries();
if (!game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.welcomeMessage)) { if (!game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.welcomeMessage)) {
@ -262,9 +193,9 @@ Hooks.on('ready', async () => {
Hooks.once('dicesoniceready', () => {}); Hooks.once('dicesoniceready', () => {});
Hooks.on('renderChatMessageHTML', (document, element) => { Hooks.on('renderChatMessageHTML', (_, element, message) => {
enricherRenderSetup(element); enricherRenderSetup(element);
const cssClass = document.flags?.daggerheart?.cssClass; const cssClass = message.message.flags?.daggerheart?.cssClass;
if (cssClass) cssClass.split(' ').forEach(cls => element.classList.add(cls)); if (cssClass) cssClass.split(' ').forEach(cls => element.classList.add(cls));
}); });
@ -309,7 +240,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
}); });
@ -317,121 +248,53 @@ Hooks.on('chatMessage', (_, message) => {
} }
}); });
const updateActorsRangeDependentEffects = async token => { Hooks.on('moveToken', async (movedToken, data) => {
const effectsAutomation = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).effects;
if (!effectsAutomation.rangeDependent) return;
const rangeDependantEffects = movedToken.actor.effects.filter(effect => effect.system.rangeDependence?.enabled);
const updateEffects = async (disposition, token, effects, effectUpdates) => {
const rangeMeasurement = game.settings.get( const rangeMeasurement = game.settings.get(
CONFIG.DH.id, CONFIG.DH.id,
CONFIG.DH.SETTINGS.gameSettings.variantRules CONFIG.DH.SETTINGS.gameSettings.variantRules
).rangeMeasurement; ).rangeMeasurement;
for (let effect of token.actor?.allApplicableEffects() ?? []) { for (let effect of effects.filter(x => x.system.rangeDependence?.enabled)) {
if (!effect.system.rangeDependence?.enabled) continue;
const { target, range, type } = effect.system.rangeDependence; const { target, range, type } = effect.system.rangeDependence;
if ((target === 'friendly' && disposition !== 1) || (target === 'hostile' && disposition !== -1))
return false;
// If there are no targets, assume false. Otherwise, start with the effect enabled. const distanceBetween = canvas.grid.measurePath([
let enabledEffect = game.user.targets.size !== 0; { ...movedToken.toObject(), x: data.destination.x, y: data.destination.y },
// Expect all targets to meet the rangeDependence requirements token
for (let userTarget of game.user.targets) { ]).distance;
const disposition = userTarget.document.disposition; const distance = rangeMeasurement[range];
if ((target === 'friendly' && disposition !== 1) || (target === 'hostile' && disposition !== -1)) {
enabledEffect = false;
break;
}
// Get required distance and special case 5 feet to test adjacency
const required = rangeMeasurement[range];
const reverse = type === CONFIG.DH.GENERAL.rangeInclusion.outsideRange.id; const reverse = type === CONFIG.DH.GENERAL.rangeInclusion.outsideRange.id;
const inRange = const newDisabled = reverse ? distanceBetween <= distance : distanceBetween > distance;
required === 5 const oldDisabled = effectUpdates[effect.uuid] ? effectUpdates[effect.uuid].disabled : newDisabled;
? userTarget.isAdjacentWith(token.object) effectUpdates[effect.uuid] = {
: userTarget.distanceTo(token.object) <= required; disabled: oldDisabled || newDisabled,
if (reverse ? inRange : !inRange) { value: effect
enabledEffect = false; };
break;
} }
};
const effectUpdates = {};
for (let token of game.scenes.find(x => x.active).tokens) {
if (token.id !== movedToken.id) {
await updateEffects(token.disposition, token, rangeDependantEffects, effectUpdates);
} }
await effect.update({ disabled: !enabledEffect }); if (token.actor) await updateEffects(movedToken.disposition, token, token.actor.effects, effectUpdates);
} }
};
const updateAllRangeDependentEffects = async () => { for (let key in effectUpdates) {
const effectsAutomation = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).effects; const effect = effectUpdates[key];
if (!effectsAutomation.rangeDependent) return; await effect.value.update({ disabled: effect.disabled });
const tokens = canvas.scene?.tokens;
if (!tokens) return;
if (game.user.character) {
// The character updates their character's token. There can be only one token.
const characterToken = tokens.find(x => x.actor === game.user.character);
updateActorsRangeDependentEffects(characterToken);
} else if (game.user.isActiveGM) {
// The GM is responsible for all other tokens.
const playerCharacters = game.users.players.filter(x => x.active).map(x => x.character);
for (const token of tokens.filter(x => !playerCharacters.includes(x.actor))) {
updateActorsRangeDependentEffects(token);
}
}
};
const debouncedRangeEffectCall = foundry.utils.debounce(updateAllRangeDependentEffects, 50);
Hooks.on('targetToken', () => {
debouncedRangeEffectCall();
});
Hooks.on('refreshToken', (_, options) => {
if (options.refreshPosition) {
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

@ -36,7 +36,6 @@
}, },
"DAGGERHEART": { "DAGGERHEART": {
"CharacterSheet": "Character Sheet",
"ACTIONS": { "ACTIONS": {
"TYPES": { "TYPES": {
"attack": { "attack": {
@ -69,11 +68,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 +89,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 +119,6 @@
}, },
"cost": { "cost": {
"stepTooltip": "+{step} per step" "stepTooltip": "+{step} per step"
},
"summon": {
"dropSummonsHere": "Drop Summons Here"
} }
} }
}, },
@ -203,8 +193,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 +225,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"
@ -338,7 +325,6 @@
"equip": "Equip", "equip": "Equip",
"sendToChat": "Send To Chat", "sendToChat": "Send To Chat",
"toLoadout": "Send to Loadout", "toLoadout": "Send to Loadout",
"recall": "Recall",
"toVault": "Send to Vault", "toVault": "Send to Vault",
"unequip": "Unequip", "unequip": "Unequip",
"useItem": "Use Item" "useItem": "Use Item"
@ -599,7 +585,6 @@
}, },
"OwnershipSelection": { "OwnershipSelection": {
"title": "Ownership Selection - {name}", "title": "Ownership Selection - {name}",
"noPlayers": "No players to assign ownership to",
"default": "Default Ownership" "default": "Default Ownership"
}, },
"ReactionRoll": { "ReactionRoll": {
@ -626,9 +611,6 @@
"insufficientHope": "The initiating character doesn't have enough hope", "insufficientHope": "The initiating character doesn't have enough hope",
"createTagTeam": "Create TagTeam Roll", "createTagTeam": "Create TagTeam Roll",
"chatMessageRollTitle": "Roll" "chatMessageRollTitle": "Roll"
},
"TokenConfig": {
"actorSizeUsed": "Actor size is set, determining the dimensions"
} }
}, },
"CLASS": { "CLASS": {
@ -638,6 +620,11 @@
} }
}, },
"CONFIG": { "CONFIG": {
"ActionType": {
"passive": "Passive",
"action": "Action",
"reaction": "Reaction"
},
"AdversaryTrait": { "AdversaryTrait": {
"relentless": { "relentless": {
"name": "Relentless", "name": "Relentless",
@ -1046,12 +1033,6 @@
"description": "" "description": ""
} }
}, },
"FeatureForm": {
"label": "Feature Form",
"passive": "Passive",
"action": "Action",
"reaction": "Reaction"
},
"Gold": { "Gold": {
"title": "Gold", "title": "Gold",
"coins": "Coins", "coins": "Coins",
@ -1155,8 +1136,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",
@ -1166,14 +1146,6 @@
"rect": "Rectangle", "rect": "Rectangle",
"ray": "Ray" "ray": "Ray"
}, },
"TokenSize": {
"tiny": "Tiny",
"small": "Small",
"medium": "Medium",
"large": "Large",
"huge": "Huge",
"gargantuan": "Gargantuan"
},
"Traits": { "Traits": {
"agility": { "agility": {
"name": "Agility", "name": "Agility",
@ -1230,29 +1202,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",
@ -1837,9 +1786,7 @@
"label": "Long Rest: Bonus Long Rest Moves", "label": "Long Rest: Bonus Long Rest Moves",
"hint": "The number of extra Long Rest Moves the character can take during a Long Rest." "hint": "The number of extra Long Rest Moves the character can take during a Long Rest."
} }
}, }
"target": "Target",
"targetSelf": "Self"
}, },
"maxLoadout": { "maxLoadout": {
"label": "Max Loadout Cards Bonus" "label": "Max Loadout Cards Bonus"
@ -2090,10 +2037,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",
@ -2147,7 +2094,6 @@
"fear": "Fear", "fear": "Fear",
"features": "Features", "features": "Features",
"formula": "Formula", "formula": "Formula",
"general": "General",
"gm": "GM", "gm": "GM",
"healing": "Healing", "healing": "Healing",
"healingRoll": "Healing Roll", "healingRoll": "Healing Roll",
@ -2214,22 +2160,16 @@
"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",
"plural": "Targets" "plural": "Targets"
}, },
"title": "Title", "title": "Title",
"tokenSize": "Token Size",
"total": "Total", "total": "Total",
"traitModifier": "Trait Modifier", "traitModifier": "Trait Modifier",
"true": "True", "true": "True",
"type": "Type", "type": "Type",
"typeSheet": "System {type} Sheet",
"unarmed": "Unarmed", "unarmed": "Unarmed",
"unarmedAttack": "Unarmed Attack", "unarmedAttack": "Unarmed Attack",
"unarmored": "Unarmored", "unarmored": "Unarmored",
@ -2282,10 +2222,8 @@
"tokenRingImg": { "label": "Subject Texture" }, "tokenRingImg": { "label": "Subject Texture" },
"tokenSize": { "tokenSize": {
"placeholder": "Using character dimensions", "placeholder": "Using character dimensions",
"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" },
@ -2309,9 +2247,7 @@
"hybridizeFeatureTitle": "Hybrid Features", "hybridizeFeatureTitle": "Hybrid Features",
"hybridizeDrag": "Drag a form here to hybridize it.", "hybridizeDrag": "Drag a form here to hybridize it.",
"mainTrait": "Main Trait", "mainTrait": "Main Trait",
"traitBonus": "Trait Bonus", "traitBonus": "Trait Bonus"
"evolvedTokenHint": "An evolved beastform's token is based on that of the form you evolve",
"evolvedImagePlaceholder": "The image for the form selected for evolution will be used"
}, },
"Class": { "Class": {
"hopeFeatures": "Hope Features", "hopeFeatures": "Hope Features",
@ -2334,9 +2270,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 +2408,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 +2417,6 @@
}, },
"roll": { "roll": {
"title": "Actions" "title": "Actions"
},
"trigger": {
"title": "Triggers"
} }
}, },
"Homebrew": { "Homebrew": {
@ -2603,8 +2527,8 @@
"enabled": { "label": "Enabled" }, "enabled": { "label": "Enabled" },
"tokens": { "label": "Tokens" } "tokens": { "label": "Tokens" }
}, },
"massiveDamage": { "massiveDamage":{
"title": "Massive Damage", "title":"Massive Damage",
"enabled": { "label": "Enabled" } "enabled": { "label": "Enabled" }
} }
} }
@ -2620,9 +2544,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 +2757,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": {
@ -2881,10 +2800,7 @@
"companionPartnerLevelBlock": "The companion needs an assigned partner to level up.", "companionPartnerLevelBlock": "The companion needs an assigned partner to level up.",
"configureAttribution": "Configure Attribution", "configureAttribution": "Configure Attribution",
"deleteItem": "Delete Item", "deleteItem": "Delete Item",
"immune": "Immune", "immune": "Immune"
"middleClick": "[Middle Click] Keep tooltip view",
"tokenSize": "The token size used on the canvas",
"previewTokenHelp": "Left-click to place, right-click to cancel"
} }
} }
} }

View file

@ -278,26 +278,19 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
'close', 'close',
async () => { async () => {
const selected = app.selected.toObject(); const selected = app.selected.toObject();
const evolved = app.evolved.form ? app.evolved.form.toObject() : null;
const data = await game.system.api.data.items.DHBeastform.getWildcardImage( const data = await game.system.api.data.items.DHBeastform.getWildcardImage(
app.configData.data.parent, app.configData.data.parent,
evolved ?? app.selected app.selected
); );
if (data) { if (data) {
if (!data.selectedImage) selected = null; if (!data.selectedImage) selected = null;
else { else {
const imageSource = evolved ?? selected; if (data.usesDynamicToken) selected.system.tokenRingImg = data.selectedImage;
if (imageSource.usesDynamicToken) imageSource.system.tokenRingImg = data.selectedImage; else selected.system.tokenImg = data.selectedImage;
else imageSource.system.tokenImg = data.selectedImage;
} }
} }
resolve({ resolve({ selected: selected, evolved: app.evolved, hybrid: app.hybrid, item: featureItem });
selected: selected,
evolved: { ...app.evolved, form: evolved },
hybrid: app.hybrid,
item: featureItem
});
}, },
{ once: true } { once: true }
); );

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?.experiences || {};
context.experiences = Object.keys(experiences).map(id => ({ context.experiences = Object.keys(experiences).map(id => ({
id, id,
...experiences[id] ...experiences[id]
@ -190,7 +185,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
extKey: button.dataset.key, extKey: button.dataset.key,
key: this.config?.data?.parent?.isNPC ? 'fear' : 'hope', key: this.config?.data?.parent?.isNPC ? 'fear' : 'hope',
value: 1, value: 1,
name: this.config.data?.system.experiences?.[button.dataset.key]?.name name: this.config.data?.experiences?.[button.dataset.key]?.name
} }
]; ];
this.render(); this.render();
@ -200,9 +195,9 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
if (this.config.roll) { if (this.config.roll) {
this.reactionOverride = !this.reactionOverride; this.reactionOverride = !this.reactionOverride;
this.config.actionType = this.reactionOverride this.config.actionType = this.reactionOverride
? 'reaction' ? CONFIG.DH.ITEM.actionTypes.reaction.id
: this.config.actionType === 'reaction' : this.config.actionType === CONFIG.DH.ITEM.actionTypes.reaction.id
? 'action' ? CONFIG.DH.ITEM.actionTypes.action.id
: this.config.actionType; : this.config.actionType;
this.render(); this.render();
} }
@ -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.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)) {
@ -183,18 +181,12 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
.filter(x => category.moves[x].selected) .filter(x => category.moves[x].selected)
.flatMap(key => { .flatMap(key => {
const move = category.moves[key]; const move = category.moves[key];
const needsTarget = move.actions.filter(x => x.target?.type && x.target.type !== 'self').length > 0;
return [...Array(move.selected).keys()].map(_ => ({ return [...Array(move.selected).keys()].map(_ => ({
...move, ...move,
movePath: `${categoryKey}.moves.${key}`, movePath: `${categoryKey}.moves.${key}`
needsTarget: needsTarget
})); }));
}); });
}); });
const characters = game.actors
.filter(x => x.type === 'character')
.filter(x => x.testUserPermission(game.user, 'LIMITED'))
.filter(x => x.uuid !== this.actor.uuid);
const cls = getDocumentClass('ChatMessage'); const cls = getDocumentClass('ChatMessage');
const msg = { const msg = {
@ -214,9 +206,7 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
`DAGGERHEART.APPLICATIONS.Downtime.${this.shortrest ? 'shortRest' : 'longRest'}.title` `DAGGERHEART.APPLICATIONS.Downtime.${this.shortrest ? 'shortRest' : 'longRest'}.title`
), ),
actor: { name: this.actor.name, img: this.actor.img }, actor: { name: this.actor.name, img: this.actor.img },
moves: moves, moves: moves
characters: characters,
selfId: this.actor.uuid
} }
), ),
flags: { flags: {

View file

@ -1,61 +1,69 @@
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
export default class ItemTransferDialog extends HandlebarsApplicationMixin(ApplicationV2) { export default class ItemTransferDialog extends HandlebarsApplicationMixin(ApplicationV2) {
constructor(data) { constructor(item) {
super({}); super({});
this.data = data;
this.item = item;
this.quantity = item.system.quantity;
} }
get title() { get title() {
return this.data.title; return this.item.name;
} }
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
tag: 'form', tag: 'form',
classes: ['daggerheart', 'dh-style', 'dialog', 'item-transfer'], classes: ['daggerheart', 'dh-style', 'dialog', 'item-transfer'],
position: { width: 400, height: 'auto' }, position: { width: 300, height: 'auto' },
window: { icon: 'fa-solid fa-hand-holding-hand' }, window: { icon: 'fa-solid fa-hand-holding-hand' },
actions: { actions: {
finish: ItemTransferDialog.#finish finish: ItemTransferDialog.#finish
} },
form: { handler: this.updateData, submitOnChange: true, closeOnSubmit: false }
}; };
static PARTS = { static PARTS = {
main: { template: 'systems/daggerheart/templates/dialogs/item-transfer.hbs', root: true } main: { template: 'systems/daggerheart/templates/dialogs/item-transfer.hbs' }
}; };
_attachPartListeners(partId, htmlElement, options) {
super._attachPartListeners(partId, htmlElement, options);
htmlElement.querySelector('.number-display').addEventListener('change', event => {
this.quantity = isNaN(event.target.value) ? this.quantity : Number(event.target.value);
this.render();
});
}
async _prepareContext(_options) { async _prepareContext(_options) {
const context = await super._prepareContext(_options); const context = await super._prepareContext(_options);
return foundry.utils.mergeObject(context, this.data); context.item = this.item;
context.quantity = this.quantity;
return context;
}
static async updateData(_event, _element, formData) {
const { quantity } = foundry.utils.expandObject(formData.object);
this.quantity = quantity;
this.render();
} }
static async #finish() { static async #finish() {
this.selected = this.form.elements.quantity.valueAsNumber || null; this.close({ submitted: true });
this.close();
} }
static #determineTransferOptions({ originActor, targetActor, item, currency }) { close(options = {}) {
originActor ??= item?.actor; if (!options.submitted) this.quantity = null;
const homebrewKey = CONFIG.DH.SETTINGS.gameSettings.Homebrew;
const currencySetting = game.settings.get(CONFIG.DH.id, homebrewKey).currency?.[currency] ?? null;
return { super.close();
originActor,
targetActor,
itemImage: item?.img,
currencyIcon: currencySetting?.icon,
max: item?.system.quantity ?? originActor.system.gold[currency] ?? 0,
title: item?.name ?? currencySetting?.label
};
} }
static async configure(options) { static async configure(item) {
return new Promise(resolve => { return new Promise(resolve => {
const data = this.#determineTransferOptions(options); const app = new this(item);
if (data.max <= 1) return resolve(data.max); app.addEventListener('close', () => resolve(app.quantity), { once: true });
const app = new this(data);
app.addEventListener('close', () => resolve(app.selected), { once: true });
app.render({ force: true }); app.render({ force: true });
}); });
} }

View file

@ -38,6 +38,7 @@ export default class OwnershipSelection extends HandlebarsApplicationMixin(Appli
async _prepareContext(_options) { async _prepareContext(_options) {
const context = await super._prepareContext(_options); const context = await super._prepareContext(_options);
context.ownershipDefaultOptions = CONFIG.DH.GENERAL.basicOwnershiplevels;
context.ownershipOptions = CONFIG.DH.GENERAL.simpleOwnershiplevels; context.ownershipOptions = CONFIG.DH.GENERAL.simpleOwnershiplevels;
context.defaultOwnership = this.defaultOwnership; context.defaultOwnership = this.defaultOwnership;
context.ownership = game.users.reduce((acc, user) => { context.ownership = game.users.reduce((acc, user) => {
@ -51,7 +52,6 @@ export default class OwnershipSelection extends HandlebarsApplicationMixin(Appli
return acc; return acc;
}, {}); }, {});
context.showOwnership = Boolean(Object.keys(context.ownership).length);
return context; return context;
} }

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
@ -220,8 +220,8 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
!roll.system.isCritical && criticalRoll !roll.system.isCritical && criticalRoll
? (await getCritDamageBonus(damage.formula)) + damage.total ? (await getCritDamageBonus(damage.formula)) + damage.total
: damage.total; : damage.total;
const updatedDamageParts = damage.parts;
if (systemData.damage[key]) { if (systemData.damage[key]) {
const updatedDamageParts = damage.parts;
if (!roll.system.isCritical && criticalRoll) { if (!roll.system.isCritical && criticalRoll) {
for (let part of updatedDamageParts) { for (let part of updatedDamageParts) {
const criticalDamage = await getCritDamageBonus(part.formula); const criticalDamage = await getCritDamageBonus(part.formula);

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

@ -280,19 +280,11 @@ export default class DhCharacterLevelUp extends LevelUpBase {
break; break;
case 'experience': case 'experience':
if (!advancement[choiceKey]) advancement[choiceKey] = []; if (!advancement[choiceKey]) advancement[choiceKey] = [];
const allExperiences = {
...this.actor.system.experiences,
...Object.values(this.levelup.levels).reduce((acc, level) => {
for (const key of Object.keys(level.achievements.experiences)) {
acc[key] = level.achievements.experiences[key];
}
return acc;
}, {})
};
const data = checkbox.data.map(data => { const data = checkbox.data.map(data => {
const experience = Object.keys(allExperiences).find(x => x === data); const experience = Object.keys(this.actor.system.experiences).find(
return allExperiences[experience]?.name ?? ''; x => x === data
);
return this.actor.system.experiences[experience]?.name ?? '';
}); });
advancement[choiceKey].push({ data: data, value: checkbox.value }); advancement[choiceKey].push({ data: data, value: checkbox.value });
break; break;

View file

@ -357,23 +357,11 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
const experienceIncreaseTagify = htmlElement.querySelector('.levelup-experience-increases'); const experienceIncreaseTagify = htmlElement.querySelector('.levelup-experience-increases');
if (experienceIncreaseTagify) { if (experienceIncreaseTagify) {
const allExperiences = {
...this.actor.system.experiences,
...Object.values(this.levelup.levels).reduce((acc, level) => {
for (const key of Object.keys(level.achievements.experiences)) {
acc[key] = level.achievements.experiences[key];
}
return acc;
}, {})
};
tagifyElement( tagifyElement(
experienceIncreaseTagify, experienceIncreaseTagify,
Object.keys(allExperiences).reduce((acc, id) => { Object.keys(this.actor.system.experiences).reduce((acc, id) => {
const experience = allExperiences[id]; const experience = this.actor.system.experiences[id];
if (experience.name) {
acc.push({ id: id, label: experience.name }); acc.push({ id: id, label: experience.name });
}
return acc; return acc;
}, []), }, []),

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

@ -44,7 +44,6 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
deleteAdversaryType: this.deleteAdversaryType, deleteAdversaryType: this.deleteAdversaryType,
selectAdversaryType: this.selectAdversaryType, selectAdversaryType: this.selectAdversaryType,
save: this.save, save: this.save,
resetTokenSizes: this.resetTokenSizes,
reset: this.reset reset: this.reset
}, },
form: { handler: this.updateData, submitOnChange: true } form: { handler: this.updateData, submitOnChange: true }
@ -425,14 +424,6 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
this.close(); this.close();
} }
static async resetTokenSizes() {
await this.settings.updateSource({
tokenSizes: this.settings.schema.fields.tokenSizes.initial
});
this.render();
}
static async reset() { static async reset() {
const confirmed = await foundry.applications.api.DialogV2.confirm({ const confirmed = await foundry.applications.api.DialogV2.confirm({
window: { window: {

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(false);
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

@ -9,9 +9,6 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
if (!ignoredActorKeys.includes(key)) { if (!ignoredActorKeys.includes(key)) {
const model = game.system.api.models.actors[key]; const model = game.system.api.models.actors[key];
const attributes = CONFIG.Token.documentClass.getTrackedAttributes(model); const attributes = CONFIG.Token.documentClass.getTrackedAttributes(model);
// As per DHToken._getTrackedAttributesFromSchema, attributes.bar have a max version as well.
const maxAttributes = attributes.bar.map(x => [...x, 'max']);
attributes.value.push(...maxAttributes);
const group = game.i18n.localize(model.metadata.label); const group = game.i18n.localize(model.metadata.label);
const choices = CONFIG.Token.documentClass const choices = CONFIG.Token.documentClass
.getTrackedAttributeChoices(attributes, model) .getTrackedAttributeChoices(attributes, model)

View file

@ -51,19 +51,6 @@ export default class DHAdversarySettings extends DHBaseActorSettings {
} }
}; };
async _prepareContext(options) {
const context = await super._prepareContext(options);
const featureForms = ['passive', 'action', 'reaction'];
context.features = context.document.system.features.sort((a, b) =>
a.system.featureForm !== b.system.featureForm
? featureForms.indexOf(a.system.featureForm) - featureForms.indexOf(b.system.featureForm)
: a.sort - b.sort
);
return context;
}
/* -------------------------------------------- */ /* -------------------------------------------- */
/** /**

View file

@ -49,19 +49,6 @@ export default class DHEnvironmentSettings extends DHBaseActorSettings {
} }
}; };
async _prepareContext(options) {
const context = await super._prepareContext(options);
const featureForms = ['passive', 'action', 'reaction'];
context.features = context.document.system.features.sort((a, b) =>
a.system.featureForm !== b.system.featureForm
? featureForms.indexOf(a.system.featureForm) - featureForms.indexOf(b.system.featureForm)
: a.sort - b.sort
);
return context;
}
/** /**
* Adds a new category entry to the actor. * Adds a new category entry to the actor.
* @type {ApplicationClickAction} * @type {ApplicationClickAction}

View file

@ -1,30 +1,20 @@
import DHTokenConfigMixin from './token-config-mixin.mjs'; export default class DhPrototypeTokenConfig extends foundry.applications.sheets.PrototypeTokenConfig {
import { getActorSizeFromForm } from './token-config-mixin.mjs';
export default class DhPrototypeTokenConfig extends DHTokenConfigMixin(
foundry.applications.sheets.PrototypeTokenConfig
) {
/** @inheritDoc */ /** @inheritDoc */
static DEFAULT_OPTIONS = { async _prepareResourcesTab() {
...super.DEFAULT_OPTIONS, const token = this.token;
form: { handler: DhPrototypeTokenConfig.#onSubmit } const usesTrackableAttributes = !foundry.utils.isEmpty(CONFIG.Actor.trackableAttributes);
const attributeSource =
this.actor?.system instanceof foundry.abstract.DataModel && usesTrackableAttributes
? this.actor?.type
: this.actor?.system;
const TokenDocument = foundry.utils.getDocumentClass('Token');
const attributes = TokenDocument.getTrackedAttributes(attributeSource);
return {
barAttributes: TokenDocument.getTrackedAttributeChoices(attributes, attributeSource),
bar1: token.getBarAttribute?.('bar1'),
bar2: token.getBarAttribute?.('bar2'),
turnMarkerModes: DhPrototypeTokenConfig.TURN_MARKER_MODES,
turnMarkerAnimations: CONFIG.Combat.settings.turnMarkerAnimations
}; };
/**
* Process form submission for the sheet
* @this {PrototypeTokenConfig}
* @type {ApplicationFormSubmission}
*/
static async #onSubmit(event, form, formData) {
const submitData = this._processFormData(event, form, formData);
submitData.detectionModes ??= []; // Clear detection modes array
this._processChanges(submitData);
const changes = { prototypeToken: submitData };
const changedTokenSizeValue = getActorSizeFromForm(this.element, this.actor);
if (changedTokenSizeValue) changes.system = { size: changedTokenSizeValue };
this.actor.validate({ changes, clean: true, fallback: false });
await this.actor.update(changes);
} }
} }

View file

@ -1,114 +0,0 @@
export default function DHTokenConfigMixin(Base) {
class DHTokenConfigBase extends Base {
/** @override */
static PARTS = {
tabs: super.PARTS.tabs,
identity: super.PARTS.identity,
appearance: {
template: 'systems/daggerheart/templates/sheets-settings/token-config/appearance.hbs',
scrollable: ['']
},
vision: super.PARTS.vision,
light: super.PARTS.light,
resources: super.PARTS.resources,
footer: super.PARTS.footer
};
_attachPartListeners(partId, htmlElement, options) {
super._attachPartListeners(partId, htmlElement, options);
switch (partId) {
case 'appearance':
htmlElement
.querySelector('#dhTokenSize')
?.addEventListener('change', this.onTokenSizeChange.bind(this));
break;
}
}
/** @inheritDoc */
async _prepareResourcesTab() {
const token = this.token;
const usesTrackableAttributes = !foundry.utils.isEmpty(CONFIG.Actor.trackableAttributes);
const attributeSource =
this.actor?.system instanceof foundry.abstract.DataModel && usesTrackableAttributes
? this.actor?.type
: this.actor?.system;
const TokenDocument = foundry.utils.getDocumentClass('Token');
const attributes = TokenDocument.getTrackedAttributes(attributeSource);
return {
barAttributes: TokenDocument.getTrackedAttributeChoices(attributes, attributeSource),
bar1: token.getBarAttribute?.('bar1'),
bar2: token.getBarAttribute?.('bar2'),
turnMarkerModes: DHTokenConfigBase.TURN_MARKER_MODES,
turnMarkerAnimations: CONFIG.Combat.settings.turnMarkerAnimations
};
}
async _prepareAppearanceTab() {
const context = await super._prepareAppearanceTab();
context.tokenSizes = CONFIG.DH.ACTOR.tokenSize;
context.tokenSize = this.actor?.system?.size;
context.usesActorSize = this.actor?.system?.metadata?.usesSize;
context.actorSizeDisable = context.usesActorSize && this.actor.system.size !== 'custom';
return context;
}
/** @inheritDoc */
_previewChanges(changes) {
if (!changes || !this._preview) return;
const tokenSizeSelect = this.element?.querySelector('#dhTokenSize');
if (this.actor && tokenSizeSelect && tokenSizeSelect.value !== 'custom') {
const tokenSizes = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes;
const tokenSize = tokenSizes[tokenSizeSelect.value];
changes.width = tokenSize;
changes.height = tokenSize;
}
const deletions = { '-=actorId': null, '-=actorLink': null };
const mergeOptions = { inplace: false, performDeletions: true };
this._preview.updateSource(mergeObject(changes, deletions, mergeOptions));
if (this._preview?.object?.destroyed === false) {
this._preview.object.initializeSources();
this._preview.object.renderFlags.set({ refresh: true });
}
}
async onTokenSizeChange(event) {
const value = event.target.value;
const tokenSizeDimensions = this.element.querySelector('#tokenSizeDimensions');
if (tokenSizeDimensions) {
const disabled = value !== 'custom';
tokenSizeDimensions.dataset.tooltip = disabled
? game.i18n.localize('DAGGERHEART.APPLICATIONS.TokenConfig.actorSizeUsed')
: '';
const disabledIcon = tokenSizeDimensions.querySelector('i');
if (disabledIcon) {
disabledIcon.style.opacity = disabled ? '' : '0';
}
const dimensionsInputs = tokenSizeDimensions.querySelectorAll('.form-fields input');
for (const input of dimensionsInputs) {
input.disabled = disabled;
}
}
}
}
return DHTokenConfigBase;
}
export function getActorSizeFromForm(element, actor) {
const tokenSizeSelect = element.querySelector('#dhTokenSize');
const isSizeDifferent = tokenSizeSelect?.value !== actor?.system?.size;
if (tokenSizeSelect && actor && isSizeDifferent) {
return tokenSizeSelect.value;
}
return null;
}

View file

@ -1,11 +1,20 @@
import DHTokenConfigMixin from './token-config-mixin.mjs'; export default class DhTokenConfig extends foundry.applications.sheets.TokenConfig {
import { getActorSizeFromForm } from './token-config-mixin.mjs'; /** @inheritDoc */
async _prepareResourcesTab() {
export default class DhTokenConfig extends DHTokenConfigMixin(foundry.applications.sheets.TokenConfig) { const token = this.token;
async _processSubmitData(event, form, submitData, options) { const usesTrackableAttributes = !foundry.utils.isEmpty(CONFIG.Actor.trackableAttributes);
const changedTokenSizeValue = getActorSizeFromForm(this.element, this.actor); const attributeSource =
if (changedTokenSizeValue) this.token.actor.update({ 'system.size': changedTokenSizeValue }); this.actor?.system instanceof foundry.abstract.DataModel && usesTrackableAttributes
? this.actor?.type
super._processSubmitData(event, form, submitData, options); : this.actor?.system;
const TokenDocument = foundry.utils.getDocumentClass('Token');
const attributes = TokenDocument.getTrackedAttributes(attributeSource);
return {
barAttributes: TokenDocument.getTrackedAttributeChoices(attributes, attributeSource),
bar1: token.getBarAttribute?.('bar1'),
bar2: token.getBarAttribute?.('bar2'),
turnMarkerModes: DhTokenConfig.TURN_MARKER_MODES,
turnMarkerAnimations: CONFIG.Combat.settings.turnMarkerAnimations
};
} }
} }

View file

@ -26,12 +26,7 @@ export default class AdversarySheet extends DHBaseActorSheet {
} }
] ]
}, },
dragDrop: [ dragDrop: [{ dragSelector: '[data-item-id][draggable="true"]', dropSelector: null }]
{
dragSelector: '[data-item-id][draggable="true"], [data-item-id] [draggable="true"]',
dropSelector: null
}
]
}; };
static PARTS = { static PARTS = {
@ -93,13 +88,6 @@ export default class AdversarySheet extends DHBaseActorSheet {
context.resources.stress.emptyPips = context.resources.stress.emptyPips =
context.resources.stress.max < maxResource ? maxResource - context.resources.stress.max : 0; context.resources.stress.max < maxResource ? maxResource - context.resources.stress.max : 0;
const featureForms = ['passive', 'action', 'reaction'];
context.features = this.document.system.features.sort((a, b) =>
a.system.featureForm !== b.system.featureForm
? featureForms.indexOf(a.system.featureForm) - featureForms.indexOf(b.system.featureForm)
: a.sort - b.sort
);
return context; return context;
} }
@ -176,15 +164,6 @@ export default class AdversarySheet extends DHBaseActorSheet {
}); });
} }
/** @inheritdoc */
async _onDragStart(event) {
const inventoryItem = event.currentTarget.closest('.inventory-item');
if (inventoryItem) {
event.dataTransfer.setDragImage(inventoryItem.querySelector('img'), 60, 0);
}
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,
@ -47,7 +46,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
}, },
dragDrop: [ dragDrop: [
{ {
dragSelector: '[data-item-id][draggable="true"], [data-item-id] [draggable="true"]', dragSelector: '[data-item-id][draggable="true"]',
dropSelector: null dropSelector: null
} }
], ],
@ -319,45 +318,6 @@ export default class CharacterSheet extends DHBaseActorSheet {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.loadoutMaxReached')); ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.loadoutMaxReached'));
} }
}, },
{
name: 'recall',
icon: 'fa-solid fa-bolt-lightning',
condition: target => {
const doc = getDocFromElementSync(target);
return doc && doc.system.inVault;
},
callback: async (target, event) => {
const doc = await getDocFromElement(target);
const actorLoadout = doc.actor.system.loadoutSlot;
if (!actorLoadout.available) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.loadoutMaxReached'));
return;
}
if (doc.system.recallCost == 0) {
return doc.update({ 'system.inVault': false });
}
const type = 'effect';
const cls = game.system.api.models.actions.actionsTypes[type];
const action = new cls(
{
...cls.getSourceConfig(doc.system),
type: type,
chatDisplay: false,
cost: [
{
key: 'stress',
value: doc.system.recallCost
}
]
},
{ parent: doc.system }
);
const config = await action.use(event);
if (config) {
return doc.update({ 'system.inVault': false });
}
}
},
{ {
name: 'toVault', name: 'toVault',
icon: 'fa-solid fa-arrow-down', icon: 'fa-solid fa-arrow-down',
@ -712,27 +672,19 @@ 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
};
const result = await this.document.diceRoll({
...config,
actionType: 'action', actionType: 'action',
headerTitle: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${this.actor.name}`, headerTitle: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${this.actor.name}`,
title: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', { title: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
ability: abilityLabel ability: abilityLabel
}) })
}; });
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 */
const costResources = result.costs
.filter(x => x.enabled)
.map(cost => ({ ...cost, value: -cost.value, total: -cost.total }));
config.resourceUpdates.addResources(costResources);
await config.resourceUpdates.updateResources();
} }
//TODO: redo toggleEquipItem method //TODO: redo toggleEquipItem method
@ -830,7 +782,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 +853,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}
@ -946,15 +863,6 @@ export default class CharacterSheet extends DHBaseActorSheet {
}); });
} }
/** @inheritdoc */
async _onDragStart(event) {
const inventoryItem = event.currentTarget.closest('.inventory-item');
if (inventoryItem) {
event.dataTransfer.setDragImage(inventoryItem.querySelector('img'), 60, 0);
}
super._onDragStart(event);
}
async _onDropItem(event, item) { async _onDropItem(event, item) {
if (this.document.uuid === item.parent?.uuid) { if (this.document.uuid === item.parent?.uuid) {
return super._onDropItem(event, item); return super._onDropItem(event, item);

View file

@ -25,12 +25,7 @@ export default class DhpEnvironment extends DHBaseActorSheet {
toggleResourceDice: DhpEnvironment.#toggleResourceDice, toggleResourceDice: DhpEnvironment.#toggleResourceDice,
handleResourceDice: DhpEnvironment.#handleResourceDice handleResourceDice: DhpEnvironment.#handleResourceDice
}, },
dragDrop: [ dragDrop: [{ dragSelector: '.inventory-item', dropSelector: null }]
{
dragSelector: '[data-item-id][draggable="true"], [data-item-id] [draggable="true"]',
dropSelector: null
}
]
}; };
/**@override */ /**@override */
@ -79,9 +74,6 @@ export default class DhpEnvironment extends DHBaseActorSheet {
case 'header': case 'header':
await this._prepareHeaderContext(context, options); await this._prepareHeaderContext(context, options);
break;
case 'features':
await this._prepareFeaturesContext(context, options);
break; break;
case 'notes': case 'notes':
await this._prepareNotesContext(context, options); await this._prepareNotesContext(context, options);
@ -118,22 +110,6 @@ export default class DhpEnvironment extends DHBaseActorSheet {
} }
} }
/**
* Prepare render context for the features part.
* @param {ApplicationRenderContext} context
* @param {ApplicationRenderOptions} options
* @returns {Promise<void>}
* @protected
*/
async _prepareFeaturesContext(context, _options) {
const featureForms = ['passive', 'action', 'reaction'];
context.features = this.document.system.features.sort((a, b) =>
a.system.featureForm !== b.system.featureForm
? featureForms.indexOf(a.system.featureForm) - featureForms.indexOf(b.system.featureForm)
: a.sort - b.sort
);
}
/** /**
* Prepare render context for the Header part. * Prepare render context for the Header part.
* @param {ApplicationRenderContext} context * @param {ApplicationRenderContext} context

View file

@ -40,7 +40,7 @@ export default class Party extends DHBaseActorSheet {
selectRefreshable: DaggerheartMenu.selectRefreshable, selectRefreshable: DaggerheartMenu.selectRefreshable,
refreshActors: DaggerheartMenu.refreshActors refreshActors: DaggerheartMenu.refreshActors
}, },
dragDrop: [{ dragSelector: '[data-item-id]', dropSelector: null }] dragDrop: [{ dragSelector: '[data-item-id][draggable="true"]', dropSelector: null }]
}; };
/**@override */ /**@override */

View file

@ -179,10 +179,10 @@ export default function DHApplicationMixin(Base) {
super._attachPartListeners(partId, htmlElement, options); super._attachPartListeners(partId, htmlElement, options);
this._dragDrop.forEach(d => d.bind(htmlElement)); this._dragDrop.forEach(d => d.bind(htmlElement));
// Handle delta inputs
for (const deltaInput of htmlElement.querySelectorAll('input[data-allow-delta]')) { for (const deltaInput of htmlElement.querySelectorAll('input[data-allow-delta]')) {
deltaInput.dataset.numValue = deltaInput.value; deltaInput.dataset.numValue = deltaInput.value;
deltaInput.inputMode = 'numeric'; deltaInput.inputMode = 'numeric';
deltaInput.pattern = '^[+=\\-]?\d*';
const handleUpdate = (delta = 0) => { const handleUpdate = (delta = 0) => {
const min = Number(deltaInput.min) || 0; const min = Number(deltaInput.min) || 0;
@ -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 }
@ -232,25 +232,6 @@ export default function DHApplicationMixin(Base) {
handleUpdate(); handleUpdate();
}); });
} }
// Handle contenteditable
for (const input of htmlElement.querySelectorAll('[contenteditable][data-property]')) {
const property = input.dataset.property;
input.addEventListener('blur', () => {
const selection = document.getSelection();
if (input.contains(selection.anchorNode)) {
selection.empty();
}
this.document.update({ [property]: input.textContent });
});
input.addEventListener('keydown', event => {
if (event.key === 'Enter') input.blur();
});
// 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()));
}
} }
/**@inheritdoc */ /**@inheritdoc */
@ -505,7 +486,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 +566,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 +717,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

@ -34,10 +34,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
} }
} }
], ],
dragDrop: [ dragDrop: [{ dragSelector: '.inventory-item[data-type="attack"]', dropSelector: null }]
{ dragSelector: '.inventory-item[data-type="attack"]', dropSelector: null },
{ dragSelector: ".currency[data-currency] .drag-handle", dropSelector: null }
]
}; };
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -257,35 +254,14 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
/* Application Drag/Drop */ /* Application Drag/Drop */
/* -------------------------------------------- */ /* -------------------------------------------- */
async _onDrop(event) {
event.stopPropagation();
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
if (data.type === 'Currency' && ['character', 'party'].includes(this.document.type)) {
const originActor = await foundry.utils.fromUuid(data.originActor);
if (!originActor || originActor.uuid === this.document.uuid) return;
const currency = data.currency;
const quantity = await game.system.api.applications.dialogs.ItemTransferDialog.configure({
originActor,
targetActor: this.document,
currency
});
if (quantity) {
originActor.update({ [`system.gold.${currency}`]: Math.max(0, originActor.system.gold[currency] - quantity) });
this.document.update({ [`system.gold.${currency}`]: this.document.system.gold[currency] + quantity });
}
return;
}
return super._onDrop(event);
}
async _onDropItem(event, item) { async _onDropItem(event, item) {
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event); const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
const physicalActorTypes = ['character', 'party'];
const originActor = item.actor; const originActor = item.actor;
if ( if (
item.actor?.uuid === this.document.uuid || item.actor?.uuid === this.document.uuid ||
!originActor || !originActor ||
!['character', 'party'].includes(this.document.type) !physicalActorTypes.includes(this.document.type)
) { ) {
return super._onDropItem(event, item); return super._onDropItem(event, item);
} }
@ -294,10 +270,10 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
if (item.system.metadata.isInventoryItem) { if (item.system.metadata.isInventoryItem) {
if (item.system.metadata.isQuantifiable) { if (item.system.metadata.isQuantifiable) {
const actorItem = originActor.items.get(data.originId); const actorItem = originActor.items.get(data.originId);
const quantityTransfered = await game.system.api.applications.dialogs.ItemTransferDialog.configure({ const quantityTransfered =
item, actorItem.system.quantity === 1
targetActor: this.document ? 1
}); : await game.system.api.applications.dialogs.ItemTransferDialog.configure(item);
if (quantityTransfered) { if (quantityTransfered) {
if (quantityTransfered === actorItem.system.quantity) { if (quantityTransfered === actorItem.system.quantity) {
@ -338,16 +314,6 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
* @param {DragEvent} event - The drag event * @param {DragEvent} event - The drag event
*/ */
async _onDragStart(event) { async _onDragStart(event) {
// Handle drag/dropping currencies
const currencyEl = event.currentTarget.closest(".currency[data-currency]");
if (currencyEl) {
const currency = currencyEl.dataset.currency;
const data = { type: 'Currency', currency, originActor: this.document.uuid };
event.dataTransfer.setData('text/plain', JSON.stringify(data));
return;
}
// Handle drag/dropping attacks
const attackItem = event.currentTarget.closest('.inventory-item[data-type="attack"]'); const attackItem = event.currentTarget.closest('.inventory-item[data-type="attack"]');
if (attackItem) { if (attackItem) {
const attackData = { const attackData = {

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

@ -77,7 +77,6 @@ export default class BeastformSheet extends DHBaseItemSheet {
name: context.document.system.advantageOn[key].value name: context.document.system.advantageOn[key].value
})) }))
); );
context.dimensionsDisabled = context.document.system.tokenSize.size !== 'custom';
break; break;
case 'effects': case 'effects':
context.effects.actives = context.effects.actives.map(effect => { context.effects.actives = context.effects.actives.map(effect => {

View file

@ -31,11 +31,4 @@ export default class FeatureSheet extends DHBaseItemSheet {
labelPrefix: 'DAGGERHEART.GENERAL.Tabs' labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
} }
}; };
//Might be wrong location but testing out if here is okay.
/**@override */
async _prepareContext(options) {
const context = await super._prepareContext(options);
context.featureFormChoices = CONFIG.DH.ITEM.featureForm;
return context;
}
} }

View file

@ -17,30 +17,4 @@ export default class DhActorDirectory extends foundry.applications.sidebar.tabs.
: null; : null;
}; };
} }
/** @inheritDoc */
_onDragStart(event) {
let actor;
const { entryId } = event.currentTarget.dataset;
if (entryId) {
actor = this.collection.get(entryId);
if (!actor?.visible) return false;
}
super._onDragStart(event);
// Create the drag preview.
if (actor && canvas.ready) {
const img = event.currentTarget.querySelector('img');
const pt = actor.prototypeToken;
const usesSize = actor.system.metadata.usesSize;
const tokenSizes = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes;
const width = usesSize ? tokenSizes[actor.system.size] : pt.width;
const height = usesSize ? tokenSizes[actor.system.size] : pt.height;
const w = width * canvas.dimensions.size * Math.abs(pt.texture.scaleX) * canvas.stage.scale.x;
const h = height * canvas.dimensions.size * Math.abs(pt.texture.scaleY) * canvas.stage.scale.y;
const preview = foundry.applications.ux.DragDrop.implementation.createDragImage(img, w, h);
event.dataTransfer.setDragImage(preview, w / 2, h / 2);
}
}
} }

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

@ -55,28 +55,27 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
]; ];
} }
addChatListeners = async (document, html, data) => { addChatListeners = async (app, html, data) => {
const message = data?.message ?? document.toObject(false);
html.querySelectorAll('.simple-roll-button').forEach(element => html.querySelectorAll('.simple-roll-button').forEach(element =>
element.addEventListener('click', event => this.onRollSimple(event, message)) element.addEventListener('click', event => this.onRollSimple(event, data.message))
); );
html.querySelectorAll('.ability-use-button').forEach(element => html.querySelectorAll('.ability-use-button').forEach(element =>
element.addEventListener('click', event => this.abilityUseButton(event, message)) element.addEventListener('click', event => this.abilityUseButton(event, data.message))
); );
html.querySelectorAll('.action-use-button').forEach(element => html.querySelectorAll('.action-use-button').forEach(element =>
element.addEventListener('click', event => this.actionUseButton(event, message)) element.addEventListener('click', event => this.actionUseButton(event, data.message))
); );
html.querySelectorAll('.reroll-button').forEach(element => html.querySelectorAll('.reroll-button').forEach(element =>
element.addEventListener('click', event => this.rerollEvent(event, message)) element.addEventListener('click', event => this.rerollEvent(event, data.message))
); );
html.querySelectorAll('.group-roll-button').forEach(element => html.querySelectorAll('.group-roll-button').forEach(element =>
element.addEventListener('click', event => this.groupRollButton(event, message)) element.addEventListener('click', event => this.groupRollButton(event, data.message))
); );
html.querySelectorAll('.group-roll-reroll').forEach(element => html.querySelectorAll('.group-roll-reroll').forEach(element =>
element.addEventListener('click', event => this.groupRollReroll(event, message)) element.addEventListener('click', event => this.groupRollReroll(event, data.message))
); );
html.querySelectorAll('.group-roll-success').forEach(element => html.querySelectorAll('.group-roll-success').forEach(element =>
element.addEventListener('click', event => this.groupRollSuccessEvent(event, message)) element.addEventListener('click', event => this.groupRollSuccessEvent(event, data.message))
); );
html.querySelectorAll('.group-roll-header-expand-section').forEach(element => html.querySelectorAll('.group-roll-header-expand-section').forEach(element =>
element.addEventListener('click', this.groupRollExpandSection) element.addEventListener('click', this.groupRollExpandSection)
@ -134,9 +133,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 parent = await foundry.utils.fromUuid(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];
const action = new cls( const action = new cls(
@ -148,8 +145,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
type: CONFIG.DH.ITEM.originItemType.restMove, type: CONFIG.DH.ITEM.originItemType.restMove,
itemPath: movePath, itemPath: movePath,
actionIndex: actionIndex actionIndex: actionIndex
}, }
targetUuid: targetUuid
}, },
{ parent: parent.system } { parent: parent.system }
); );

View file

@ -5,7 +5,8 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
actions: { actions: {
requestSpotlight: this.requestSpotlight, requestSpotlight: this.requestSpotlight,
toggleSpotlight: this.toggleSpotlight, toggleSpotlight: this.toggleSpotlight,
setActionTokens: this.setActionTokens setActionTokens: this.setActionTokens,
openCountdowns: this.openCountdowns
} }
}; };
@ -56,10 +57,7 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
const adversaries = context.turns?.filter(x => x.isNPC) ?? []; const adversaries = context.turns?.filter(x => x.isNPC) ?? [];
const characters = context.turns?.filter(x => !x.isNPC) ?? []; const characters = context.turns?.filter(x => !x.isNPC) ?? [];
const spotlightQueueEnabled = game.settings.get( const spotlightQueueEnabled = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.SpotlightRequestQueue);
CONFIG.DH.id,
CONFIG.DH.SETTINGS.gameSettings.SpotlightRequestQueue
);
const spotlightRequests = characters const spotlightRequests = characters
?.filter(x => !x.isNPC && spotlightQueueEnabled) ?.filter(x => !x.isNPC && spotlightQueueEnabled)
@ -73,9 +71,7 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
Object.assign(context, { Object.assign(context, {
actionTokens: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules).actionTokens, actionTokens: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules).actionTokens,
adversaries, adversaries,
characters: characters characters: characters?.filter(x => !x.isNPC).filter(x => !spotlightQueueEnabled || x.system.spotlight.requestOrderIndex == 0),
?.filter(x => !x.isNPC)
.filter(x => !spotlightQueueEnabled || x.system.spotlight.requestOrderIndex == 0),
spotlightRequests spotlightRequests
}); });
} }
@ -127,7 +123,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,11 +161,9 @@ 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
);
} else { } else {
await updateCountdowns(CONFIG.DH.GENERAL.countdownProgressionTypes.spotlight.id); await updateCountdowns(CONFIG.DH.GENERAL.countdownProgressionTypes.spotlight.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

@ -2,7 +2,7 @@ export const actionTypes = {
attack: { attack: {
id: 'attack', id: 'attack',
name: 'DAGGERHEART.ACTIONS.TYPES.attack.name', name: 'DAGGERHEART.ACTIONS.TYPES.attack.name',
icon: 'fa-hand-fist', icon: 'fa-khanda',
tooltip: 'DAGGERHEART.ACTIONS.TYPES.attack.tooltip' tooltip: 'DAGGERHEART.ACTIONS.TYPES.attack.tooltip'
}, },
countdown: { countdown: {

View file

@ -211,44 +211,6 @@ export const adversaryTraits = {
} }
}; };
export const tokenSize = {
custom: {
id: 'custom',
value: 0,
label: 'DAGGERHEART.GENERAL.custom'
},
tiny: {
id: 'tiny',
value: 1,
label: 'DAGGERHEART.CONFIG.TokenSize.tiny'
},
small: {
id: 'small',
value: 2,
label: 'DAGGERHEART.CONFIG.TokenSize.small'
},
medium: {
id: 'medium',
value: 3,
label: 'DAGGERHEART.CONFIG.TokenSize.medium'
},
large: {
id: 'large',
value: 4,
label: 'DAGGERHEART.CONFIG.TokenSize.large'
},
huge: {
id: 'huge',
value: 5,
label: 'DAGGERHEART.CONFIG.TokenSize.huge'
},
gargantuan: {
id: 'gargantuan',
value: 6,
label: 'DAGGERHEART.CONFIG.TokenSize.gargantuan'
}
};
export const levelChoices = { export const levelChoices = {
attributes: { attributes: {
name: 'attributes', name: 'attributes',

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;
@ -84,7 +84,6 @@ export const BPModifiers = {
increaseDamage: { increaseDamage: {
sort: 2, sort: 2,
description: 'DAGGERHEART.CONFIG.BPModifiers.increaseDamage.description', description: 'DAGGERHEART.CONFIG.BPModifiers.increaseDamage.description',
effectTargetTypes: ['adversary'],
effects: [ effects: [
{ {
name: 'DAGGERHEART.CONFIG.BPModifiers.increaseDamage.effect.name', name: 'DAGGERHEART.CONFIG.BPModifiers.increaseDamage.effect.name',

View file

@ -232,7 +232,7 @@ export const defaultRestOptions = {
actionType: 'action', actionType: 'action',
chatDisplay: false, chatDisplay: false,
target: { target: {
type: 'friendly' type: 'self'
}, },
damage: { damage: {
parts: [ parts: [
@ -298,7 +298,7 @@ export const defaultRestOptions = {
actionType: 'action', actionType: 'action',
chatDisplay: false, chatDisplay: false,
target: { target: {
type: 'friendly' type: 'self'
}, },
damage: { damage: {
parts: [ parts: [
@ -341,7 +341,7 @@ export const defaultRestOptions = {
actionType: 'action', actionType: 'action',
chatDisplay: false, chatDisplay: false,
target: { target: {
type: 'friendly' type: 'self'
}, },
damage: { damage: {
parts: [ parts: [
@ -407,7 +407,7 @@ export const defaultRestOptions = {
actionType: 'action', actionType: 'action',
chatDisplay: false, chatDisplay: false,
target: { target: {
type: 'friendly' type: 'self'
}, },
damage: { damage: {
parts: [ parts: [
@ -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

@ -5,6 +5,7 @@ export const armorFeatures = {
actions: [ actions: [
{ {
type: 'damage', type: 'damage',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.ArmorFeature.burning.actions.burn.name', name: 'DAGGERHEART.CONFIG.ArmorFeature.burning.actions.burn.name',
description: 'DAGGERHEART.CONFIG.ArmorFeature.burning.actions.burn.description', description: 'DAGGERHEART.CONFIG.ArmorFeature.burning.actions.burn.description',
@ -173,6 +174,7 @@ export const armorFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.ArmorFeature.hopeful.actions.hope.name', name: 'DAGGERHEART.CONFIG.ArmorFeature.hopeful.actions.hope.name',
description: 'DAGGERHEART.CONFIG.ArmorFeature.hopeful.actions.hope.description', description: 'DAGGERHEART.CONFIG.ArmorFeature.hopeful.actions.hope.description',
@ -186,6 +188,7 @@ export const armorFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.ArmorFeature.impenetrable.actions.impenetrable.name', name: 'DAGGERHEART.CONFIG.ArmorFeature.impenetrable.actions.impenetrable.name',
description: 'DAGGERHEART.CONFIG.ArmorFeature.impenetrable.actions.impenetrable.description', description: 'DAGGERHEART.CONFIG.ArmorFeature.impenetrable.actions.impenetrable.description',
@ -228,6 +231,7 @@ export const armorFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.ArmorFeature.painful.actions.pain.name', name: 'DAGGERHEART.CONFIG.ArmorFeature.painful.actions.pain.name',
description: 'DAGGERHEART.CONFIG.ArmorFeature.painful.actions.pain.description', description: 'DAGGERHEART.CONFIG.ArmorFeature.painful.actions.pain.description',
@ -265,6 +269,7 @@ export const armorFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.ArmorFeature.quiet.actions.quiet.name', name: 'DAGGERHEART.CONFIG.ArmorFeature.quiet.actions.quiet.name',
description: 'DAGGERHEART.CONFIG.ArmorFeature.quiet.actions.quiet.description', description: 'DAGGERHEART.CONFIG.ArmorFeature.quiet.actions.quiet.description',
@ -301,6 +306,7 @@ export const armorFeatures = {
actions: [ actions: [
{ {
type: 'attack', type: 'attack',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.ArmorFeature.resilient.actions.resilient.name', name: 'DAGGERHEART.CONFIG.ArmorFeature.resilient.actions.resilient.name',
description: 'DAGGERHEART.CONFIG.ArmorFeature.resilient.actions.resilient.description', description: 'DAGGERHEART.CONFIG.ArmorFeature.resilient.actions.resilient.description',
@ -347,6 +353,7 @@ export const armorFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.ArmorFeature.shifting.actions.shift.name', name: 'DAGGERHEART.CONFIG.ArmorFeature.shifting.actions.shift.name',
description: 'DAGGERHEART.CONFIG.ArmorFeature.shifting.actions.shift.description', description: 'DAGGERHEART.CONFIG.ArmorFeature.shifting.actions.shift.description',
@ -366,6 +373,7 @@ export const armorFeatures = {
actions: [ actions: [
{ {
type: 'attack', type: 'attack',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.ArmorFeature.timeslowing.actions.slowTime.name', name: 'DAGGERHEART.CONFIG.ArmorFeature.timeslowing.actions.slowTime.name',
description: 'DAGGERHEART.CONFIG.ArmorFeature.timeslowing.actions.slowTime.description', description: 'DAGGERHEART.CONFIG.ArmorFeature.timeslowing.actions.slowTime.description',
@ -393,6 +401,7 @@ export const armorFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.ArmorFeature.truthseeking.actions.truthseeking.name', name: 'DAGGERHEART.CONFIG.ArmorFeature.truthseeking.actions.truthseeking.name',
description: 'DAGGERHEART.CONFIG.ArmorFeature.truthseeking.actions.truthseeking.description', description: 'DAGGERHEART.CONFIG.ArmorFeature.truthseeking.actions.truthseeking.description',
@ -435,8 +444,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
} }
] ]
} }
@ -529,6 +537,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.bouncing.actions.bounce.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.bouncing.actions.bounce.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.bouncing.actions.bounce.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.bouncing.actions.bounce.description',
@ -573,6 +582,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.brutal.actions.addDamage.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.brutal.actions.addDamage.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.brutal.actions.addDamage.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.brutal.actions.addDamage.description',
@ -586,6 +596,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.burning.actions.burn.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.burning.actions.burn.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.burning.actions.burn.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.burning.actions.burn.description',
@ -599,6 +610,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.charged.actions.markStress.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.charged.actions.markStress.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.charged.actions.markStress.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.charged.actions.markStress.description',
@ -635,6 +647,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.concussive.actions.attack.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.concussive.actions.attack.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.concussive.actions.attack.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.concussive.actions.attack.description',
@ -675,6 +688,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.deadly.actions.extraDamage.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.deadly.actions.extraDamage.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.deadly.actions.extraDamage.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.deadly.actions.extraDamage.description',
@ -688,6 +702,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.deflecting.actions.deflect.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.deflecting.actions.deflect.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.deflecting.actions.deflect.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.deflecting.actions.deflect.description',
@ -710,8 +725,7 @@ export const weaponFeatures = {
{ {
key: 'system.evasion', key: 'system.evasion',
mode: 2, mode: 2,
value: '@system.armorScore', value: '@system.armorScore'
priority: 21
} }
] ]
} }
@ -725,6 +739,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'damage', type: 'damage',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.destructive.actions.attack.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.destructive.actions.attack.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.destructive.actions.attack.descriptive', description: 'DAGGERHEART.CONFIG.WeaponFeature.destructive.actions.attack.descriptive',
@ -769,6 +784,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.devastating.actions.devastate.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.devastating.actions.devastate.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.devastating.actions.devastate.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.devastating.actions.devastate.description',
@ -819,6 +835,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.doubledUp.actions.doubleUp.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.doubledUp.actions.doubleUp.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.doubledUp.actions.doubleUp.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.doubledUp.actions.doubleUp.description',
@ -832,6 +849,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.dueling.actions.duel.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.dueling.actions.duel.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.dueling.actions.duel.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.dueling.actions.duel.description',
@ -845,6 +863,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', // Should prompt a dc 14 reaction save on adversaries type: 'effect', // Should prompt a dc 14 reaction save on adversaries
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.eruptive.actions.erupt.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.eruptive.actions.erupt.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.eruptive.actions.erupt.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.eruptive.actions.erupt.description',
@ -858,6 +877,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.grappling.actions.grapple.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.grappling.actions.grapple.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.grappling.actions.grapple.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.grappling.actions.grapple.description',
@ -877,6 +897,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.greedy.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.greedy.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.greedy.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.greedy.description',
@ -908,6 +929,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'healing', type: 'healing',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.healing.actions.heal.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.healing.actions.heal.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.healing.actions.heal.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.healing.actions.heal.description',
@ -955,6 +977,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.hooked.actions.hook.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.hooked.actions.hook.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.hooked.actions.hook.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.hooked.actions.hook.description',
@ -968,6 +991,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.hot.actions.hot.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.hot.actions.hot.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.hot.actions.hot.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.hot.actions.hot.description',
@ -981,6 +1005,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.invigorating.actions.invigorate.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.invigorating.actions.invigorate.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.invigorating.actions.invigorate.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.invigorating.actions.invigorate.description',
@ -994,6 +1019,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.lifestealing.actions.lifesteal.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.lifestealing.actions.lifesteal.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.lifestealing.actions.lifesteal.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.lifestealing.actions.lifesteal.description',
@ -1007,6 +1033,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.lockedOn.actions.lockOn.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.lockedOn.actions.lockOn.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.lockedOn.actions.lockOn.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.lockedOn.actions.lockOn.description',
@ -1020,6 +1047,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.long.actions.long.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.long.actions.long.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.long.actions.long.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.long.actions.long.description',
@ -1033,6 +1061,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.lucky.actions.luck.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.lucky.actions.luck.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.lucky.actions.luck.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.lucky.actions.luck.description',
@ -1070,6 +1099,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.painful.actions.pain.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.painful.actions.pain.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.painful.actions.pain.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.painful.actions.pain.description',
@ -1115,6 +1145,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.parry.actions.parry.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.parry.actions.parry.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.parry.actions.parry.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.parry.actions.parry.description',
@ -1128,6 +1159,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.persuasive.actions.persuade.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.persuasive.actions.persuade.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.persuasive.actions.persuade.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.persuasive.actions.persuade.description',
@ -1164,6 +1196,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.pompous.actions.pompous.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.pompous.actions.pompous.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.pompous.actions.pompous.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.pompous.actions.pompous.description',
@ -1207,6 +1240,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.quick.actions.quick.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.quick.actions.quick.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.quick.actions.quick.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.quick.actions.quick.description',
@ -1244,6 +1278,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.reloading.actions.reload.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.reloading.actions.reload.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.reloading.actions.reload.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.reloading.actions.reload.description',
@ -1257,6 +1292,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.retractable.actions.retract.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.retractable.actions.retract.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.retractable.actions.retract.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.retractable.actions.retract.description',
@ -1270,6 +1306,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.returning.actions.return.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.returning.actions.return.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.returning.actions.return.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.returning.actions.return.description',
@ -1283,6 +1320,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.scary.actions.scare.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.scary.actions.scare.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.scary.actions.scare.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.scary.actions.scare.description',
@ -1326,8 +1364,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
} }
] ]
} }
@ -1339,6 +1376,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.sheltering.actions.shelter.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.sheltering.actions.shelter.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.sheltering.actions.shelter.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.sheltering.actions.shelter.description',
@ -1352,6 +1390,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.startling.actions.startle.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.startling.actions.startle.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.startling.actions.startle.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.startling.actions.startle.description',
@ -1371,6 +1410,7 @@ export const weaponFeatures = {
actions: [ actions: [
{ {
type: 'effect', type: 'effect',
actionType: 'action',
chatDisplay: true, chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.timebending.actions.bendTime.name', name: 'DAGGERHEART.CONFIG.WeaponFeature.timebending.actions.bendTime.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.timebending.actions.bendTime.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.timebending.actions.bendTime.description',
@ -1418,12 +1458,6 @@ export const orderedWeaponFeatures = () => {
return Object.values(all).sort((a, b) => game.i18n.localize(a.label).localeCompare(game.i18n.localize(b.label))); return Object.values(all).sort((a, b) => game.i18n.localize(a.label).localeCompare(game.i18n.localize(b.label)));
}; };
export const featureForm = {
passive: 'DAGGERHEART.CONFIG.FeatureForm.passive',
action: 'DAGGERHEART.CONFIG.FeatureForm.action',
reaction: 'DAGGERHEART.CONFIG.FeatureForm.reaction'
};
export const featureTypes = { export const featureTypes = {
ancestry: { ancestry: {
id: 'ancestry', id: 'ancestry',
@ -1481,6 +1515,21 @@ export const featureSubTypes = {
mastery: 'mastery' mastery: 'mastery'
}; };
export const actionTypes = {
passive: {
id: 'passive',
label: 'DAGGERHEART.CONFIG.ActionType.passive'
},
action: {
id: 'action',
label: 'DAGGERHEART.CONFIG.ActionType.action'
},
reaction: {
id: 'reaction',
label: 'DAGGERHEART.CONFIG.ActionType.reaction'
}
};
export const itemResourceTypes = { export const itemResourceTypes = {
simple: { simple: {
id: 'simple', id: 'simple',

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;
@ -34,9 +33,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
initial: 'action', initial: 'action',
nullable: false, nullable: false,
required: true required: true
}), })
targetUuid: new fields.StringField({ initial: undefined }),
triggers: new fields.ArrayField(new TriggerField())
}; };
this.extraSchemas.forEach(s => { this.extraSchemas.forEach(s => {
@ -98,9 +95,6 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
prepareData() { prepareData() {
this.name = this.name || game.i18n.localize(CONFIG.DH.ACTIONS.actionTypes[this.type].name); this.name = this.name || game.i18n.localize(CONFIG.DH.ACTIONS.actionTypes[this.type].name);
this.img = this.img ?? this.parent?.parent?.img; this.img = this.img ?? this.parent?.parent?.img;
/* Fallback to feature description */
this.description = this.description || this.parent?.description;
} }
/** /**
@ -165,8 +159,12 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
* @returns {object} * @returns {object}
*/ */
getRollData(data = {}) { getRollData(data = {}) {
const actorData = this.actor ? this.actor.getRollData(false) : {}; if (!this.actor) return null;
const actorData = this.actor.getRollData(false);
// Add Roll results to RollDatas
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)
: 1; : 1;
@ -195,11 +193,11 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
async use(event) { async use(event) {
if (!this.actor) throw new Error("An Action can't be used outside of an Actor context."); if (!this.actor) throw new Error("An Action can't be used outside of an Actor context.");
if (this.chatDisplay) await this.toChat();
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
@ -210,12 +208,9 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
// Execute the Action Worflow in order based of schema fields // Execute the Action Worflow in order based of schema fields
await this.executeWorkflow(config); await this.executeWorkflow(config);
await config.resourceUpdates.updateResources();
if (Hooks.call(`${CONFIG.DH.id}.postUseAction`, this, config) === false) return; if (Hooks.call(`${CONFIG.DH.id}.postUseAction`, this, config) === false) return;
if (this.chatDisplay) await this.toChat();
return config; return config;
} }
@ -244,11 +239,8 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
isDirect: !!this.damage?.direct, isDirect: !!this.damage?.direct,
selectedRollMode: game.settings.get('core', 'rollMode'), selectedRollMode: game.settings.get('core', 'rollMode'),
data: this.getRollData(), data: this.getRollData(),
evaluate: this.hasRoll, evaluate: this.hasRoll
resourceUpdates: new ResourceUpdateMap(this.actor),
targetUuid: this.targetUuid
}; };
DHBaseAction.applyKeybindings(config); DHBaseAction.applyKeybindings(config);
return config; return config;
} }
@ -266,16 +258,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.
@ -340,50 +322,11 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
* @returns {string[]} An array of localized tag strings. * @returns {string[]} An array of localized tag strings.
*/ */
_getTags() { _getTags() {
const tags = [game.i18n.localize(`DAGGERHEART.ACTIONS.TYPES.${this.type}.name`)]; const tags = [
game.i18n.localize(`DAGGERHEART.ACTIONS.TYPES.${this.type}.name`),
game.i18n.localize(`DAGGERHEART.CONFIG.ActionType.${this.actionType}`)
];
return tags; return tags;
} }
} }
export class ResourceUpdateMap extends Map {
#actor;
constructor(actor) {
super();
this.#actor = actor;
}
addResources(resources) {
if (!resources?.length) return;
const invalidResources = resources.some(resource => !resource.key);
if (invalidResources) return;
for (const resource of resources) {
if (!resource.key) continue;
const existing = this.get(resource.key);
if (existing) {
this.set(resource.key, {
...existing,
value: existing.value + (resource.value ?? 0),
total: existing.total + (resource.total ?? 0)
});
} else {
this.set(resource.key, resource);
}
}
}
#getResources() {
return Array.from(this.values());
}
async updateResources() {
if (this.#actor) {
const target = this.#actor.system.partner ?? this.#actor;
await target.modifyResource(this.#getResources());
}
}
}

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,
@ -68,30 +65,11 @@ export default class BeastformEffect extends BaseEffect {
} }
}; };
const updateToken = token => { const updateToken = token => ({
let x = null,
y = null;
if (token.object?.scene?.grid) {
const positionData = game.system.api.documents.DhToken.getSnappedPositionInSquareGrid(
token.object.scene.grid,
{ x: token.x, y: token.y, elevation: token.elevation },
baseUpdate.width,
baseUpdate.height
);
x = positionData.x;
y = positionData.y;
}
return {
...baseUpdate, ...baseUpdate,
x,
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: {
@ -100,8 +78,7 @@ export default class BeastformEffect extends BaseEffect {
} }
}, },
'flags.daggerheart': { '-=beastformTokenImg': null, '-=beastformSubjectTexture': null } 'flags.daggerheart': { '-=beastformTokenImg': null, '-=beastformSubjectTexture': null }
}; });
};
await updateActorTokens(this.parent.parent, update, updateToken); await updateActorTokens(this.parent.parent, update, updateToken);

View file

@ -1,6 +1,6 @@
import DHAdversarySettings from '../../applications/sheets-configs/adversary-settings.mjs'; import DHAdversarySettings from '../../applications/sheets-configs/adversary-settings.mjs';
import { ActionField } from '../fields/actionField.mjs'; import { ActionField } from '../fields/actionField.mjs';
import BaseDataActor, { commonActorRules } from './base.mjs'; import BaseDataActor from './base.mjs';
import { resourceField, bonusField } from '../fields/actorField.mjs'; import { resourceField, bonusField } from '../fields/actorField.mjs';
export default class DhpAdversary extends BaseDataActor { export default class DhpAdversary extends BaseDataActor {
@ -11,8 +11,7 @@ export default class DhpAdversary extends BaseDataActor {
label: 'TYPES.Actor.adversary', label: 'TYPES.Actor.adversary',
type: 'adversary', type: 'adversary',
settingSheet: DHAdversarySettings, settingSheet: DHAdversarySettings,
hasAttribution: true, hasAttribution: true
usesSize: true
}); });
} }
@ -56,11 +55,27 @@ export default class DhpAdversary extends BaseDataActor {
}) })
}), }),
resources: new fields.SchemaField({ resources: new fields.SchemaField({
hitPoints: resourceField(0, 0, 'DAGGERHEART.GENERAL.HitPoints.plural', true), hitPoints: resourceField(
stress: resourceField(0, 0, 'DAGGERHEART.GENERAL.stress', true) 0,
0,
'DAGGERHEART.GENERAL.HitPoints.plural',
true,
game.i18n.localize('DAGGERHEART.GENERAL.max')
),
stress: resourceField(
0,
0,
'DAGGERHEART.GENERAL.stress',
true,
game.i18n.localize('DAGGERHEART.GENERAL.max')
)
}), }),
rules: new fields.SchemaField({ rules: new fields.SchemaField({
...commonActorRules() conditionImmunities: new fields.SchemaField({
hidden: new fields.BooleanField({ initial: false }),
restrained: new fields.BooleanField({ initial: false }),
vulnerable: new fields.BooleanField({ initial: false })
})
}), }),
attack: new ActionField({ attack: new ActionField({
initial: { initial: {
@ -127,7 +142,7 @@ export default class DhpAdversary extends BaseDataActor {
} }
isItemValid(source) { isItemValid(source) {
return source.type === 'feature'; return source.type === "feature";
} }
async _preUpdate(changes, options, user) { async _preUpdate(changes, options, user) {

View file

@ -1,24 +1,21 @@
import DHBaseActorSettings from '../../applications/sheets/api/actor-setting.mjs'; import DHBaseActorSettings from '../../applications/sheets/api/actor-setting.mjs';
import DHItem from '../../documents/item.mjs';
import { getScrollTextData } from '../../helpers/utils.mjs'; import { getScrollTextData } from '../../helpers/utils.mjs';
const fields = foundry.data.fields;
const resistanceField = (resistanceLabel, immunityLabel, reductionLabel) => const resistanceField = (resistanceLabel, immunityLabel, reductionLabel) =>
new fields.SchemaField({ new foundry.data.fields.SchemaField({
resistance: new fields.BooleanField({ resistance: new foundry.data.fields.BooleanField({
initial: false, initial: false,
label: `${resistanceLabel}.label`, label: `${resistanceLabel}.label`,
hint: `${resistanceLabel}.hint`, hint: `${resistanceLabel}.hint`,
isAttributeChoice: true isAttributeChoice: true
}), }),
immunity: new fields.BooleanField({ immunity: new foundry.data.fields.BooleanField({
initial: false, initial: false,
label: `${immunityLabel}.label`, label: `${immunityLabel}.label`,
hint: `${immunityLabel}.hint`, hint: `${immunityLabel}.hint`,
isAttributeChoice: true isAttributeChoice: true
}), }),
reduction: new fields.NumberField({ reduction: new foundry.data.fields.NumberField({
integer: true, integer: true,
initial: 0, initial: 0,
label: `${reductionLabel}.label`, label: `${reductionLabel}.label`,
@ -26,25 +23,6 @@ const resistanceField = (resistanceLabel, immunityLabel, reductionLabel) =>
}) })
}); });
/* Common rules applying to Characters and Adversaries */
export const commonActorRules = (extendedData = { damageReduction: {} }) => ({
conditionImmunities: new fields.SchemaField({
hidden: new fields.BooleanField({ initial: false }),
restrained: new fields.BooleanField({ initial: false }),
vulnerable: new fields.BooleanField({ initial: false })
}),
damageReduction: new fields.SchemaField({
thresholdImmunities: new fields.SchemaField({
minor: new fields.BooleanField({ initial: false })
}),
reduceSeverity: new fields.SchemaField({
magical: new fields.NumberField({ initial: 0, min: 0 }),
physical: new fields.NumberField({ initial: 0, min: 0 })
}),
...extendedData.damageReduction
})
});
/** /**
* Describes metadata about the actor data model type * Describes metadata about the actor data model type
* @typedef {Object} ActorDataModelMetadata * @typedef {Object} ActorDataModelMetadata
@ -63,8 +41,7 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
settingSheet: null, settingSheet: null,
hasResistances: true, hasResistances: true,
hasAttribution: false, hasAttribution: false,
hasLimitedView: true, hasLimitedView: true
usesSize: false
}; };
} }
@ -75,6 +52,7 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
/** @inheritDoc */ /** @inheritDoc */
static defineSchema() { static defineSchema() {
const fields = foundry.data.fields;
const schema = {}; const schema = {};
if (this.metadata.hasAttribution) { if (this.metadata.hasAttribution) {
@ -98,13 +76,6 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
'DAGGERHEART.GENERAL.DamageResistance.magicalReduction' 'DAGGERHEART.GENERAL.DamageResistance.magicalReduction'
) )
}); });
if (this.metadata.usesSize)
schema.size = new fields.StringField({
required: true,
nullable: false,
choices: CONFIG.DH.ACTOR.tokenSize,
initial: CONFIG.DH.ACTOR.tokenSize.custom.id
});
return schema; return schema;
} }
@ -135,17 +106,6 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
return data; return data;
} }
/**
* Checks if an item is available for use, such as multiclass features being disabled
* on a character.
*
* @param {DHItem} item The item being checked for availability
* @return {boolean} whether the item is available
*/
isItemAvailable(item) {
return true;
}
async _preDelete() { async _preDelete() {
/* Clear all partyMembers from tagTeam setting.*/ /* Clear all partyMembers from tagTeam setting.*/
/* Revisit this when tagTeam is improved for many parties */ /* Revisit this when tagTeam is improved for many parties */

View file

@ -1,7 +1,7 @@
import { burden } from '../../config/generalConfig.mjs'; import { burden } from '../../config/generalConfig.mjs';
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs'; import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
import DhLevelData from '../levelData.mjs'; import DhLevelData from '../levelData.mjs';
import BaseDataActor, { commonActorRules } from './base.mjs'; import BaseDataActor from './base.mjs';
import { attributeField, resourceField, stressDamageReductionRule, bonusField } from '../fields/actorField.mjs'; import { attributeField, resourceField, stressDamageReductionRule, bonusField } from '../fields/actorField.mjs';
import { ActionField } from '../fields/actionField.mjs'; import { ActionField } from '../fields/actionField.mjs';
import DHCharacterSettings from '../../applications/sheets-configs/character-settings.mjs'; import DHCharacterSettings from '../../applications/sheets-configs/character-settings.mjs';
@ -217,10 +217,7 @@ export default class DhCharacter extends BaseDataActor {
}), }),
companion: new ForeignDocumentUUIDField({ type: 'Actor', nullable: true, initial: null }), companion: new ForeignDocumentUUIDField({ type: 'Actor', nullable: true, initial: null }),
rules: new fields.SchemaField({ rules: new fields.SchemaField({
...commonActorRules({ damageReduction: new fields.SchemaField({
damageReduction: {
magical: new fields.BooleanField({ initial: false }),
physical: new fields.BooleanField({ initial: false }),
maxArmorMarked: new fields.SchemaField({ maxArmorMarked: new fields.SchemaField({
value: new fields.NumberField({ value: new fields.NumberField({
required: true, required: true,
@ -237,9 +234,7 @@ export default class DhCharacter extends BaseDataActor {
}) })
}), }),
stressDamageReduction: new fields.SchemaField({ stressDamageReduction: new fields.SchemaField({
severe: stressDamageReductionRule( severe: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.severe'),
'DAGGERHEART.GENERAL.Rules.damageReduction.stress.severe'
),
major: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.major'), major: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.major'),
minor: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.minor'), minor: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.minor'),
any: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.any') any: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.any')
@ -250,8 +245,16 @@ export default class DhCharacter extends BaseDataActor {
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.label', label: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.label',
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.hint' hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.hint'
}), }),
magical: new fields.BooleanField({ initial: false }),
physical: new fields.BooleanField({ initial: false }),
thresholdImmunities: new fields.SchemaField({
minor: new fields.BooleanField({ initial: false })
}),
reduceSeverity: new fields.SchemaField({
magical: new fields.NumberField({ initial: 0, min: 0 }),
physical: new fields.NumberField({ initial: 0, min: 0 })
}),
disabledArmor: new fields.BooleanField({ intial: false }) disabledArmor: new fields.BooleanField({ intial: false })
}
}), }),
attack: new fields.SchemaField({ attack: new fields.SchemaField({
damage: new fields.SchemaField({ damage: new fields.SchemaField({
@ -280,23 +283,10 @@ export default class DhCharacter extends BaseDataActor {
}) })
}) })
}), }),
dualityRoll: new fields.SchemaField({ conditionImmunities: new fields.SchemaField({
defaultHopeDice: new fields.NumberField({ hidden: new fields.BooleanField({ initial: false }),
nullable: false, restrained: new fields.BooleanField({ initial: false }),
required: true, vulnerable: new fields.BooleanField({ initial: false })
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({
@ -445,33 +435,6 @@ export default class DhCharacter extends BaseDataActor {
return attack; return attack;
} }
/** @inheritDoc */
isItemAvailable(item) {
if (!super.isItemAvailable(this)) return false;
/**
* Preventing subclass features from being available if the chacaracter does not
* have the right subclass advancement
*/
if (item.system.originItemType !== CONFIG.DH.ITEM.featureTypes.subclass.id) {
return true;
}
if (!this.class.subclass) return false;
const prop = item.system.multiclassOrigin ? 'multiclass' : 'class';
const subclassState = this[prop].subclass?.system?.featureState;
if (!subclassState) return false;
if (
item.system.identifier === CONFIG.DH.ITEM.featureSubTypes.foundation ||
(item.system.identifier === CONFIG.DH.ITEM.featureSubTypes.specialization && subclassState >= 2) ||
(item.system.identifier === CONFIG.DH.ITEM.featureSubTypes.mastery && subclassState >= 3)
) {
return true;
} else {
return false;
}
}
get sheetLists() { get sheetLists() {
const ancestryFeatures = [], const ancestryFeatures = [],
communityFeatures = [], communityFeatures = [],
@ -480,7 +443,7 @@ export default class DhCharacter extends BaseDataActor {
companionFeatures = [], companionFeatures = [],
features = []; features = [];
for (let item of this.parent.items.filter(x => this.isItemAvailable(x))) { for (let item of this.parent.items) {
if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.ancestry.id) { if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.ancestry.id) {
ancestryFeatures.push(item); ancestryFeatures.push(item);
} else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.community.id) { } else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.community.id) {
@ -488,7 +451,20 @@ export default class DhCharacter extends BaseDataActor {
} else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.class.id) { } else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.class.id) {
classFeatures.push(item); classFeatures.push(item);
} else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.subclass.id) { } else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.subclass.id) {
if (this.class.subclass) {
const prop = item.system.multiclassOrigin ? 'multiclass' : 'class';
const subclassState = this[prop].subclass?.system?.featureState;
if (!subclassState) continue;
if (
item.system.identifier === CONFIG.DH.ITEM.featureSubTypes.foundation ||
(item.system.identifier === CONFIG.DH.ITEM.featureSubTypes.specialization &&
subclassState >= 2) ||
(item.system.identifier === CONFIG.DH.ITEM.featureSubTypes.mastery && subclassState >= 3)
) {
subclassFeatures.push(item); subclassFeatures.push(item);
}
}
} else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.companion.id) { } else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.companion.id) {
companionFeatures.push(item); companionFeatures.push(item);
} else if (item.type === 'feature' && !item.system.type) { } else if (item.type === 'feature' && !item.system.type) {

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

@ -26,7 +26,7 @@ export default class DhParty extends BaseDataActor {
/* -------------------------------------------- */ /* -------------------------------------------- */
isItemValid(source) { isItemValid(source) {
return ['weapon', 'armor', 'consumable', 'loot'].includes(source.type); return ["weapon", "armor", "consumable", "loot"].includes(source.type);
} }
prepareBaseData() { prepareBaseData() {

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

@ -76,7 +76,7 @@ export default class BeastformField extends fields.SchemaField {
* @returns * @returns
*/ */
static async transform(selectedForm, evolvedData, hybridData) { static async transform(selectedForm, evolvedData, hybridData) {
const formData = evolvedData?.form ?? selectedForm; const formData = evolvedData?.form ? evolvedData.form.toObject() : selectedForm;
const beastformEffect = formData.effects.find(x => x.type === 'beastform'); const beastformEffect = formData.effects.find(x => x.type === 'beastform');
if (!beastformEffect) { if (!beastformEffect) {
ui.notifications.error('DAGGERHEART.UI.Notifications.beastformMissingEffect'); ui.notifications.error('DAGGERHEART.UI.Notifications.beastformMissingEffect');
@ -92,18 +92,6 @@ export default class BeastformField extends fields.SchemaField {
beastformEffect.changes = [...beastformEffect.changes, ...evolvedForm.changes]; beastformEffect.changes = [...beastformEffect.changes, ...evolvedForm.changes];
formData.system.features = [...formData.system.features, ...selectedForm.system.features.map(x => x.uuid)]; formData.system.features = [...formData.system.features, ...selectedForm.system.features.map(x => x.uuid)];
const baseSize = evolvedData.form.system.tokenSize.size;
const evolvedSize =
baseSize === 'custom'
? 'custom'
: (Object.keys(CONFIG.DH.ACTOR.tokenSize).find(
x => CONFIG.DH.ACTOR.tokenSize[x].value === CONFIG.DH.ACTOR.tokenSize[baseSize].value + 1
) ?? baseSize);
formData.system.tokenSize = {
...evolvedData.form.system.tokenSize,
size: evolvedSize
};
} }
if (selectedForm.system.beastformType === CONFIG.DH.ITEM.beastformTypes.hybrid.id) { if (selectedForm.system.beastformType === CONFIG.DH.ITEM.beastformTypes.hybrid.id) {

View file

@ -75,7 +75,7 @@ export default class CostField extends fields.ArrayField {
} }
}, []); }, []);
config.resourceUpdates.addResources(resources); await actor.modifyResource(resources);
} }
/** /**

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

@ -124,7 +124,8 @@ export default class SaveField extends fields.SchemaField {
*/ */
static async updateSaveMessage(result, message, targetId) { static async updateSaveMessage(result, message, targetId) {
if (!result) return; if (!result) return;
const updateMsg = async function (message, targetId, result) {
// setTimeout(async () => {
const chatMessage = ui.chat.collection.get(message._id), const chatMessage = ui.chat.collection.get(message._id),
changes = { changes = {
flags: { flags: {
@ -139,6 +140,13 @@ export default class SaveField extends fields.SchemaField {
} }
}; };
await chatMessage.update(changes); await chatMessage.update(changes);
// }, 100);
};
if (game.modules.get('dice-so-nice')?.active)
game.dice3d
.waitFor3DAnimationByMessageID(result.message.id ?? result.message._id)
.then(async () => await updateMsg(message, targetId, result));
else await updateMsg(message, targetId, result);
} }
/** /**

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

@ -25,12 +25,9 @@ export default class TargetField extends fields.SchemaField {
config.hasTarget = true; config.hasTarget = true;
let targets; let targets;
// If the Action is configured as self-targeted, set targets as the owner. Probably better way than to fallback to getDependentTokens // If the Action is configured as self-targeted, set targets as the owner. Probably better way than to fallback to getDependentTokens
if (this.target?.type === CONFIG.DH.GENERAL.targetTypes.self.id) { if (this.target?.type === CONFIG.DH.GENERAL.targetTypes.self.id)
targets = [this.actor.token ?? this.actor.prototypeToken]; targets = [this.actor.token ?? this.actor.prototypeToken];
} else if (config.targetUuid) { else {
const actor = fromUuidSync(config.targetUuid);
targets = [actor.token ?? actor.prototypeToken];
} else {
targets = Array.from(game.user.targets); targets = Array.from(game.user.targets);
if (this.target.type !== CONFIG.DH.GENERAL.targetTypes.any.id) { if (this.target.type !== CONFIG.DH.GENERAL.targetTypes.any.id) {
targets = targets.filter(target => TargetField.isTargetFriendly(this.actor, target, this.target.type)); targets = targets.filter(target => TargetField.isTargetFriendly(this.actor, target, this.target.type));
@ -82,7 +79,7 @@ export default class TargetField extends fields.SchemaField {
return { return {
id: token.id, id: token.id,
actorId: token.actor.uuid, actorId: token.actor.uuid,
name: token.name, name: token.actor.name,
img: token.actor.img, img: token.actor.img,
difficulty: token.actor.system.difficulty, difficulty: token.actor.system.difficulty,
evasion: token.actor.system.evasion, evasion: token.actor.system.evasion,

View file

@ -141,12 +141,6 @@ export function ActionMixin(Base) {
return this.documentName; return this.documentName;
} }
//Getter for icons
get typeIcon() {
const config = CONFIG.DH.ACTIONS.actionTypes[this.type];
return config?.icon || 'fa-question'; // Fallback icon just in case
}
get relativeUUID() { get relativeUUID() {
return `.Item.${this.item.id}.Action.${this.id}`; return `.Item.${this.item.id}.Action.${this.id}`;
} }
@ -262,28 +256,22 @@ export function ActionMixin(Base) {
async toChat(origin) { async toChat(origin) {
const cls = getDocumentClass('ChatMessage'); const cls = getDocumentClass('ChatMessage');
const systemData = { const systemData = {
title: game.i18n.localize('DAGGERHEART.CONFIG.FeatureForm.action'), title: game.i18n.localize('DAGGERHEART.CONFIG.ActionType.action'),
origin: origin, origin: origin,
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

@ -43,13 +43,6 @@ export default class DHBeastform extends BaseDataItem {
base64: false base64: false
}), }),
tokenSize: new fields.SchemaField({ tokenSize: new fields.SchemaField({
size: new fields.StringField({
required: true,
nullable: false,
choices: CONFIG.DH.ACTOR.tokenSize,
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 +178,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
} }
@ -198,22 +190,11 @@ export default class DHBeastform extends BaseDataItem {
await this.parent.parent.createEmbeddedDocuments('ActiveEffect', [beastformEffect.toObject()]); await this.parent.parent.createEmbeddedDocuments('ActiveEffect', [beastformEffect.toObject()]);
const autoTokenSize =
this.tokenSize.size !== 'custom'
? game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes[
this.tokenSize.size
]
: null;
const width = autoTokenSize ?? this.tokenSize.width;
const height = autoTokenSize ?? this.tokenSize.height;
const prototypeTokenUpdate = { const prototypeTokenUpdate = {
height, height: this.tokenSize.height,
width, width: this.tokenSize.width,
texture: { texture: {
src: this.tokenImg, src: this.tokenImg
scaleX: this.tokenSize.scale,
scaleY: this.tokenSize.scale
}, },
ring: { ring: {
subject: { subject: {
@ -221,33 +202,16 @@ export default class DHBeastform extends BaseDataItem {
} }
} }
}; };
const tokenUpdate = token => {
let x = null,
y = null;
if (token.object?.scene?.grid) {
const positionData = game.system.api.documents.DhToken.getSnappedPositionInSquareGrid(
token.object.scene.grid,
{ x: token.x, y: token.y, elevation: token.elevation },
width ?? token.width,
height ?? token.height
);
x = positionData.x; const tokenUpdate = token => ({
y = positionData.y;
}
return {
...prototypeTokenUpdate, ...prototypeTokenUpdate,
x,
y,
flags: { flags: {
daggerheart: { daggerheart: {
beastformTokenImg: token.texture.src, beastformTokenImg: token.texture.src,
beastformSubjectTexture: token.ring.subject.texture beastformSubjectTexture: token.ring.subject.texture
} }
} }
}; });
};
await updateActorTokens(this.parent.parent, prototypeTokenUpdate, tokenUpdate); await updateActorTokens(this.parent.parent, prototypeTokenUpdate, tokenUpdate);

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

@ -30,13 +30,7 @@ export default class DHFeature extends BaseDataItem {
initial: null initial: null
}), }),
multiclassOrigin: new fields.BooleanField({ initial: false }), multiclassOrigin: new fields.BooleanField({ initial: false }),
identifier: new fields.StringField(), identifier: new fields.StringField()
featureForm: new fields.StringField({
required: true,
initial: 'passive',
choices: CONFIG.DH.ITEM.featureForm,
label: 'DAGGERHEART.CONFIG.FeatureForm.label'
})
}; };
} }
} }

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

@ -40,38 +40,6 @@ export default class DhHomebrew extends foundry.abstract.DataModel {
traitArray: new fields.ArrayField(new fields.NumberField({ required: true, integer: true }), { traitArray: new fields.ArrayField(new fields.NumberField({ required: true, integer: true }), {
initial: () => [2, 1, 1, 0, 0, -1] initial: () => [2, 1, 1, 0, 0, -1]
}), }),
tokenSizes: new fields.SchemaField({
tiny: new fields.NumberField({
integer: false,
initial: 0.5,
label: 'DAGGERHEART.CONFIG.TokenSize.tiny'
}),
small: new fields.NumberField({
integer: false,
initial: 0.8,
label: 'DAGGERHEART.CONFIG.TokenSize.small'
}),
medium: new fields.NumberField({
integer: false,
initial: 1,
label: 'DAGGERHEART.CONFIG.TokenSize.medium'
}),
large: new fields.NumberField({
integer: false,
initial: 2,
label: 'DAGGERHEART.CONFIG.TokenSize.large'
}),
huge: new fields.NumberField({
integer: false,
initial: 3,
label: 'DAGGERHEART.CONFIG.TokenSize.huge'
}),
gargantuan: new fields.NumberField({
integer: false,
initial: 4,
label: 'DAGGERHEART.CONFIG.TokenSize.gargantuan'
})
}),
currency: new fields.SchemaField({ currency: new fields.SchemaField({
title: new fields.StringField({ title: new fields.StringField({
required: true, required: true,

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() {
@ -100,10 +98,10 @@ export default class D20Roll extends DHRoll {
this.options.roll.modifiers = this.applyBaseBonus(); this.options.roll.modifiers = this.applyBaseBonus();
this.options.experiences?.forEach(m => { this.options.experiences?.forEach(m => {
if (this.options.data.system?.experiences?.[m]) if (this.options.data.experiences?.[m])
this.options.roll.modifiers.push({ this.options.roll.modifiers.push({
label: this.options.data.system.experiences[m].name, label: this.options.data.experiences[m].name,
value: this.options.data.system.experiences[m].value value: this.options.data.experiences[m].value
}); });
}); });
@ -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,82 @@ export default class DHRoll extends Roll {
static temporaryModifierBuilder(config) { static temporaryModifierBuilder(config) {
return {}; return {};
} }
}
bonusEffectBuilder() { async function automateHopeFear(config) {
const changeKeys = this.getActionChangeKeys(); const automationSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation);
return ( const hopeFearAutomation = automationSettings.hopeFear;
this.options.effects?.reduce((acc, effect) => { if (!config.source?.actor ||
if (effect.changes.some(x => changeKeys.some(key => x.key.includes(key)))) { (game.user.isGM ? !hopeFearAutomation.gm : !hopeFearAutomation.players) ||
acc[effect.id] = { config.actionType === 'reaction' ||
id: effect.id, config.tagTeamSelected ||
name: effect.name, config.skips?.resources)
description: effect.description, return;
changes: effect.changes, const actor = await fromUuid(config.source.actor);
origEffect: effect, let updates = [];
selected: !effect.disabled if (!actor) return;
};
if (config.rerolledRoll) {
if (config.roll.result.duality != config.rerolledRoll.result.duality) {
const hope = (config.roll.isCritical || config.roll.result.duality === 1 ? 1 : 0)
- (config.rerolledRoll.isCritical || config.rerolledRoll.result.duality === 1 ? 1 : 0);
const stress = (config.roll.isCritical ? 1 : 0) - (config.rerolledRoll.isCritical ? 1 : 0);
const fear = (config.roll.result.duality === -1 ? 1 : 0)
- (config.rerolledRoll.result.duality === -1 ? 1 : 0)
if (hope !== 0)
updates.push({ key: 'hope', value: hope, total: -1 * hope, enabled: true });
if (stress !== 0)
updates.push({ key: 'stress', value: -1 * stress, total: stress, enabled: true });
if (fear !== 0)
updates.push({ key: 'fear', value: fear, total: -1 * fear, enabled: true });
}
} else {
if (config.roll.isCritical || config.roll.result.duality === 1)
updates.push({ key: 'hope', value: 1, total: -1, enabled: true });
if (config.roll.isCritical)
updates.push({ key: 'stress', value: -1, total: 1, enabled: true });
if (config.roll.result.duality === -1)
updates.push({ key: 'fear', value: 1, total: -1, enabled: true });
} }
return acc; if (updates.length) {
}, {}) ?? [] const target = actor.system.partner ?? actor;
); if (!['dead', 'defeated', 'unconscious'].some(x => actor.statuses.has(x))) {
await target.modifyResource(updates);
} }
getActionChangeKeys() {
return [];
} }
} }
export const registerRollDiceHooks = () => {
Hooks.on(`${CONFIG.DH.id}.postRollDuality`, async (config, message) => {
const automationSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation);
if (
automationSettings.countdownAutomation &&
config.actionType !== CONFIG.DH.ITEM.actionTypes.reaction.id &&
!config.tagTeamSelected &&
!config.skips?.updateCountdowns
) {
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
if (config.roll.result.duality === -1) {
await updateCountdowns(CONFIG.DH.GENERAL.countdownProgressionTypes.actionRoll.id,
CONFIG.DH.GENERAL.countdownProgressionTypes.fear.id);
} else {
await updateCountdowns(CONFIG.DH.GENERAL.countdownProgressionTypes.actionRoll.id);
}
}
await automateHopeFear(config);
if (!config.roll.hasOwnProperty('success') && !config.targets?.length) return;
const rollResult = config.roll.success || config.targets.some(t => t.hit),
looseSpotlight = !rollResult || config.roll.result.duality === -1;
if (looseSpotlight && game.combat?.active) {
const currentCombatant = game.combat.combatants.get(game.combat.current?.combatantId);
if (currentCombatant?.actorId == actor.id) ui.combat.setCombatantSpotlight(currentCombatant.id);
}
});
};

View file

@ -2,7 +2,6 @@ import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs';
import D20Roll from './d20Roll.mjs'; import D20Roll from './d20Roll.mjs';
import { setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs'; import { setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs';
import { getDiceSoNicePresets } from '../config/generalConfig.mjs'; import { getDiceSoNicePresets } from '../config/generalConfig.mjs';
import { ResourceUpdateMap } from '../data/action/baseAction.mjs';
export default class DualityRoll extends D20Roll { export default class DualityRoll extends D20Roll {
_advantageFaces = 6; _advantageFaces = 6;
@ -20,7 +19,7 @@ export default class DualityRoll extends D20Roll {
get title() { get title() {
return game.i18n.localize( return game.i18n.localize(
`DAGGERHEART.GENERAL.${this.options?.actionType === 'reaction' ? 'reactionRoll' : 'dualityRoll'}` `DAGGERHEART.GENERAL.${this.options?.actionType === CONFIG.DH.ITEM.actionTypes.reaction.id ? 'reactionRoll' : 'dualityRoll'}`
); );
} }
@ -130,14 +129,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 +172,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);
@ -253,114 +219,6 @@ export default class DualityRoll extends D20Roll {
return data; return data;
} }
static async buildPost(roll, config, message) {
await super.buildPost(roll, config, message);
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) {
const automationSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation);
const hopeFearAutomation = automationSettings.hopeFear;
if (
!config.source?.actor ||
(game.user.isGM ? !hopeFearAutomation.gm : !hopeFearAutomation.players) ||
config.actionType === 'reaction' ||
config.tagTeamSelected ||
config.skips?.resources
)
return;
const actor = await fromUuid(config.source.actor);
let updates = [];
if (!actor) return;
if (config.rerolledRoll) {
if (config.roll.result.duality != config.rerolledRoll.result.duality) {
const hope =
(config.roll.isCritical || config.roll.result.duality === 1 ? 1 : 0) -
(config.rerolledRoll.isCritical || config.rerolledRoll.result.duality === 1 ? 1 : 0);
const stress = (config.roll.isCritical ? 1 : 0) - (config.rerolledRoll.isCritical ? 1 : 0);
const fear =
(config.roll.result.duality === -1 ? 1 : 0) - (config.rerolledRoll.result.duality === -1 ? 1 : 0);
if (hope !== 0) updates.push({ key: 'hope', value: hope, total: -1 * hope, enabled: true });
if (stress !== 0) updates.push({ key: 'stress', value: -1 * stress, total: stress, enabled: true });
if (fear !== 0) updates.push({ key: 'fear', value: fear, total: -1 * fear, enabled: true });
}
} else {
if (config.roll.isCritical || config.roll.result.duality === 1)
updates.push({ key: 'hope', value: 1, total: -1, enabled: true });
if (config.roll.isCritical) updates.push({ key: 'stress', value: -1, total: 1, enabled: true });
if (config.roll.result.duality === -1) updates.push({ key: 'fear', value: 1, total: -1, enabled: true });
}
if (updates.length) {
// const target = actor.system.partner ?? actor;
if (!['dead', 'defeated', 'unconscious'].some(x => actor.statuses.has(x))) {
config.resourceUpdates.addResources(updates);
}
}
}
static async dualityUpdate(config) {
const automationSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation);
if (
automationSettings.countdownAutomation &&
config.actionType !== 'reaction' &&
!config.tagTeamSelected &&
!config.skips?.updateCountdowns
) {
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
if (config.roll.result.duality === -1) {
await updateCountdowns(
CONFIG.DH.GENERAL.countdownProgressionTypes.actionRoll.id,
CONFIG.DH.GENERAL.countdownProgressionTypes.fear.id
);
} else {
await updateCountdowns(CONFIG.DH.GENERAL.countdownProgressionTypes.actionRoll.id);
}
}
await DualityRoll.addDualityResourceUpdates(config);
if (!config.roll.hasOwnProperty('success') && !config.targets?.length) return;
const rollResult = config.roll.success || config.targets?.some(t => t.hit),
looseSpotlight = !rollResult || config.roll.result.duality === -1;
if (looseSpotlight && game.combat?.active) {
const currentCombatant = game.combat.combatants.get(game.combat.current?.combatantId);
if (currentCombatant?.actorId == config.data.id) ui.combat.setCombatantSpotlight(currentCombatant.id);
}
}
static async reroll(rollString, target, message) { static async reroll(rollString, target, message) {
let parsedRoll = game.system.api.dice.DualityRoll.fromData({ ...rollString, evaluated: false }); let parsedRoll = game.system.api.dice.DualityRoll.fromData({ ...rollString, evaluated: false });
const term = parsedRoll.terms[target.dataset.dieIndex]; const term = parsedRoll.terms[target.dataset.dieIndex];
@ -399,20 +257,13 @@ export default class DualityRoll extends D20Roll {
newRoll.extra = newRoll.extra.slice(2); newRoll.extra = newRoll.extra.slice(2);
const tagTeamSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll); const tagTeamSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
Hooks.call(`${CONFIG.DH.id}.postRollDuality`, {
const actor = message.system.source.actor ? await foundry.utils.fromUuid(message.system.source.actor) : null;
const config = {
source: { actor: message.system.source.actor ?? '' }, source: { actor: message.system.source.actor ?? '' },
targets: message.system.targets, targets: message.system.targets,
tagTeamSelected: Object.values(tagTeamSettings.members).some(x => x.messageId === message._id), tagTeamSelected: Object.values(tagTeamSettings.members).some(x => x.messageId === message._id),
roll: newRoll, roll: newRoll,
rerolledRoll: message.system.roll, rerolledRoll: message.system.roll
resourceUpdates: new ResourceUpdateMap(actor) });
};
await DualityRoll.addDualityResourceUpdates(config);
await config.resourceUpdates.updateResources();
return { newRoll, parsedRoll }; return { newRoll, parsedRoll };
} }
} }

View file

@ -4,8 +4,6 @@ export { default as DhpCombat } from './combat.mjs';
export { default as DHCombatant } from './combatant.mjs'; export { default as DHCombatant } from './combatant.mjs';
export { default as DhActiveEffect } from './activeEffect.mjs'; export { default as DhActiveEffect } from './activeEffect.mjs';
export { default as DhChatMessage } from './chatMessage.mjs'; export { default as DhChatMessage } from './chatMessage.mjs';
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);
} }
/** /**
@ -203,11 +194,28 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
} }
prepareDerivedData() { prepareDerivedData() {
/* Check for item availability such as in the case of subclass advancement. */ /* Preventing subclass features from transferring to actor if they do not have the right subclass advancement */
if (this.parent?.parent?.system?.isItemAvailable) { if (this.parent?.type === 'feature') {
if (!this.parent.parent.system.isItemAvailable(this.parent)) { const origSubclassParent = this.parent.system.originItemType === 'subclass';
if (origSubclassParent) {
const subclass = this.parent.parent.items.find(
x =>
x.type === 'subclass' &&
x.system.isMulticlass === (this.parent.system.identifier === 'multiclass')
);
if (subclass) {
const featureState = subclass.system.featureState;
if (
(this.parent.system.identifier === CONFIG.DH.ITEM.featureSubTypes.specialization &&
featureState < 2) ||
(this.parent.system.identifier === CONFIG.DH.ITEM.featureSubTypes.mastery && featureState < 3)
) {
this.transfer = false; this.transfer = false;
} }
} }
} }
}
}
} }

View file

@ -1,9 +1,8 @@
import { emitAsGM, GMUpdateEvent } from '../systemRegistration/socket.mjs'; import { emitAsGM, GMUpdateEvent } from '../systemRegistration/socket.mjs';
import { LevelOptionType } from '../data/levelTier.mjs'; import { LevelOptionType } from '../data/levelTier.mjs';
import DHFeature from '../data/item/feature.mjs'; import DHFeature from '../data/item/feature.mjs';
import { createScrollText, damageKeyToNumber, getDamageKey } from '../helpers/utils.mjs'; import { createScrollText, damageKeyToNumber } from '../helpers/utils.mjs';
import DhCompanionLevelUp from '../applications/levelup/companionLevelup.mjs'; import DhCompanionLevelUp from '../applications/levelup/companionLevelup.mjs';
import { ResourceUpdateMap } from '../data/action/baseAction.mjs';
export default class DhpActor extends Actor { export default class DhpActor extends Actor {
parties = new Set(); parties = new Set();
@ -74,27 +73,16 @@ export default class DhpActor extends Actor {
/**@inheritdoc */ /**@inheritdoc */
async _preCreate(data, options, user) { async _preCreate(data, options, user) {
if ((await super._preCreate(data, options, user)) === false) return false; if ((await super._preCreate(data, options, user)) === false) return false;
const update = {};
// Set default token size. Done here as we do not want to set a datamodel default, since that would apply the sizing to third party actor modules that aren't set up with the size system.
if (this.system.metadata.usesSize && !data.system?.size) {
Object.assign(update, {
system: {
size: CONFIG.DH.ACTOR.tokenSize.medium.id
}
});
}
// Configure prototype token settings // Configure prototype token settings
const prototypeToken = {};
if (['character', 'companion', 'party'].includes(this.type)) if (['character', 'companion', 'party'].includes(this.type))
Object.assign(update, { Object.assign(prototypeToken, {
prototypeToken: {
sight: { enabled: true }, sight: { enabled: true },
actorLink: true, actorLink: true,
disposition: CONST.TOKEN_DISPOSITIONS.FRIENDLY disposition: CONST.TOKEN_DISPOSITIONS.FRIENDLY
}
}); });
this.updateSource(update); this.updateSource({ prototypeToken });
} }
_onUpdate(changes, options, userId) { _onUpdate(changes, options, userId) {
@ -489,7 +477,6 @@ export default class DhpActor extends Actor {
async diceRoll(config) { async diceRoll(config) {
config.source = { ...(config.source ?? {}), actor: this.uuid }; config.source = { ...(config.source ?? {}), actor: this.uuid };
config.data = this.getRollData(); config.data = this.getRollData();
config.resourceUpdates = new ResourceUpdateMap(this);
const rollClass = config.roll.lite ? CONFIG.Dice.daggerheart['DHRoll'] : this.rollClass; const rollClass = config.roll.lite ? CONFIG.Dice.daggerheart['DHRoll'] : this.rollClass;
return await rollClass.build(config); return await rollClass.build(config);
} }
@ -539,11 +526,7 @@ export default class DhpActor extends Actor {
/**@inheritdoc */ /**@inheritdoc */
getRollData() { getRollData() {
const rollData = foundry.utils.deepClone(super.getRollData()); const rollData = super.getRollData();
/* system gets repeated infinately which causes issues when trying to use the data for document creation */
delete rollData.system;
rollData.id = this.id;
rollData.name = this.name; rollData.name = this.name;
rollData.system = this.system.getRollData(); rollData.system = this.system.getRollData();
rollData.prof = this.system.proficiency ?? 1; rollData.prof = this.system.proficiency ?? 1;
@ -631,32 +614,6 @@ export default class DhpActor extends Actor {
} }
} }
} }
if (this.type === 'adversary') {
const reducedSeverity = hpDamage.damageTypes.reduce((value, curr) => {
return Math.max(this.system.rules.damageReduction.reduceSeverity[curr], value);
}, 0);
hpDamage.value = Math.max(hpDamage.value - reducedSeverity, 0);
if (
hpDamage.value &&
this.system.rules.damageReduction.thresholdImmunities[getDamageKey(hpDamage.value)]
) {
hpDamage.value -= 1;
}
}
}
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(
@ -808,9 +765,8 @@ export default class DhpActor extends Actor {
} }
convertDamageToThreshold(damage) { convertDamageToThreshold(damage) {
const massiveDamageEnabled = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules) const massiveDamageEnabled=game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules).massiveDamage.enabled;
.massiveDamage.enabled; if (massiveDamageEnabled && damage >= (this.system.damageThresholds.severe * 2)) {
if (massiveDamageEnabled && damage >= this.system.damageThresholds.severe * 2) {
return 4; return 4;
} }
return damage >= this.system.damageThresholds.severe ? 3 : damage >= this.system.damageThresholds.major ? 2 : 1; return damage >= this.system.damageThresholds.severe ? 3 : damage >= this.system.damageThresholds.major ? 2 : 1;

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

@ -28,7 +28,6 @@ export default class DhpCombat extends Combat {
...effect, ...effect,
name: game.i18n.localize(effect.name), name: game.i18n.localize(effect.name),
description: game.i18n.localize(effect.description), description: game.i18n.localize(effect.description),
effectTargetTypes: grouping.effectTargetTypes ?? [],
flags: { flags: {
[`${CONFIG.DH.id}.${CONFIG.DH.FLAGS.combatToggle}`]: { [`${CONFIG.DH.id}.${CONFIG.DH.FLAGS.combatToggle}`]: {
category: toggle.category, category: toggle.category,
@ -46,7 +45,11 @@ export default class DhpCombat extends Combat {
for (let actor of actors) { for (let actor of actors) {
await actor.createEmbeddedDocuments( await actor.createEmbeddedDocuments(
'ActiveEffect', 'ActiveEffect',
effects.filter(x => x.effectTargetTypes.includes(actor.type)) effects.map(effect => ({
...effect,
name: game.i18n.localize(effect.name),
description: game.i18n.localize(effect.description)
}))
); );
} }
} else { } else {

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

@ -1,66 +0,0 @@
import DHToken from './token.mjs';
export default class DhScene extends Scene {
/** A map of `TokenDocument` IDs embedded in this scene long with new dimensions from actor size-category changes */
#sizeSyncBatch = new Map();
/** Synchronize a token's dimensions with its actor's size category. */
syncTokenDimensions(tokenDoc, tokenSize) {
if (!tokenDoc.parent?.tokens.has(tokenDoc.id)) return;
const prototype = tokenDoc.actor?.prototypeToken ?? tokenDoc;
this.#sizeSyncBatch.set(tokenDoc.id, {
size: tokenSize,
prototypeSize: { width: prototype.width, height: prototype.height },
position: { x: tokenDoc.x, y: tokenDoc.y, elevation: tokenDoc.elevation }
});
this.#processSyncBatch();
}
/** Retrieve size and clear size-sync batch, make updates. */
#processSyncBatch = foundry.utils.debounce(() => {
const tokenSizes = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes;
const entries = this.#sizeSyncBatch
.entries()
.toArray()
.map(([_id, { size, prototypeSize, position }]) => {
const tokenSize = tokenSizes[size];
const width = size !== CONFIG.DH.ACTOR.tokenSize.custom.id ? tokenSize : prototypeSize.width;
const height = size !== CONFIG.DH.ACTOR.tokenSize.custom.id ? tokenSize : prototypeSize.height;
const updatedPosition = DHToken.getSnappedPositionInSquareGrid(this.grid, position, width, height);
return {
_id,
width,
height,
...updatedPosition
};
});
this.#sizeSyncBatch.clear();
this.updateEmbeddedDocuments('Token', entries, { animation: { movementSpeed: 1.5 } });
}, 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

@ -1,4 +1,4 @@
export default class DHToken extends CONFIG.Token.documentClass { export default class DHToken extends TokenDocument {
/** /**
* Inspect the Actor data model and identify the set of attributes which could be used for a Token Bar. * Inspect the Actor data model and identify the set of attributes which could be used for a Token Bar.
* @param {object} attributes The tracked attributes which can be chosen from * @param {object} attributes The tracked attributes which can be chosen from
@ -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,445 +95,9 @@ 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 ?? {});
} }
/**@inheritdoc */
static async _preCreateOperation(documents, operation, user) {
const allowed = await super._preCreateOperation(documents, operation, user);
if (allowed === false) return false;
const tokenSizes = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes;
for (const document of documents) {
const actor = document.actor;
if (actor?.system.metadata.usesSize) {
const tokenSize = tokenSizes[actor.system.size];
if (tokenSize && actor.system.size !== CONFIG.DH.ACTOR.tokenSize.custom.id) {
document.updateSource({
width: tokenSize,
height: tokenSize
});
}
}
}
}
/**@inheritdoc */
_onRelatedUpdate(update = {}, operation = {}) {
super._onRelatedUpdate(update, operation);
if (!this.actor?.isOwner) return;
const updates = Array.isArray(update) ? update : [update];
const activeGM = game.users.activeGM; // Let the active GM take care of updates if available
for (let update of updates) {
if (
this.actor.system.metadata.usesSize &&
update.system?.size &&
activeGM &&
game.user.id === activeGM.id
) {
const tokenSizes = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes;
const tokenSize = tokenSizes[update.system.size];
if (tokenSize !== this.width || tokenSize !== this.height) {
this.parent?.syncTokenDimensions(this, update.system.size);
}
}
}
}
/**@inheritdoc */
getSnappedPosition(data = {}) {
const grid = this.parent?.grid ?? BaseScene.defaultGrid;
const x = data.x ?? this.x;
const y = data.y ?? this.y;
let elevation = data.elevation ?? this.elevation;
const unsnapped = { x, y, elevation };
// Gridless grid
if (grid.isGridless) return unsnapped;
// Get position and elevation
elevation = Math.round(elevation / grid.distance) * grid.distance;
let width = data.width ?? this.width;
let height = data.height ?? this.height;
if (this.actor?.system.metadata.usesSize) {
const tokenSizes = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes;
const tokenSize = tokenSizes[this.actor.system.size];
if (tokenSize && this.actor.system.size !== CONFIG.DH.ACTOR.tokenSize.custom.id) {
width = tokenSize ?? width;
height = tokenSize ?? height;
}
}
// Round width and height to nearest multiple of 0.5 if not small
width = width < 1 ? width : Math.round(width * 2) / 2;
height = height < 1 ? height : Math.round(height * 2) / 2;
const shape = data.shape ?? this.shape;
// Square grid
let snapped;
if (grid.isSquare) snapped = DHToken.getSnappedPositionInSquareGrid(grid, unsnapped, width, height);
// Hexagonal grid
else snapped = DHToken.getSnappedPositionInHexagonalGrid(grid, unsnapped, width, height, shape);
return { x: snapped.x, y: snapped.y, elevation };
}
static getSnappedPositionInSquareGrid(grid, position, width, height) {
const M = CONST.GRID_SNAPPING_MODES;
// Small tokens snap to any vertex of the subgrid with resolution 4
// where the token is fully contained within the grid space
const isTiny = (width === 0.5 && height <= 1) || (width <= 1 && height === 0.5);
if (isTiny) {
let x = position.x / grid.size;
let y = position.y / grid.size;
if (width === 1) x = Math.round(x);
else {
x = Math.floor(x * 8);
const k = ((x % 8) + 8) % 8;
if (k >= 6) x = Math.ceil(x / 8);
else if (k === 5) x = Math.floor(x / 8) + 0.5;
else x = Math.round(x / 2) / 4;
}
if (height === 1) y = Math.round(y);
else {
y = Math.floor(y * 8);
const k = ((y % 8) + 8) % 8;
if (k >= 6) y = Math.ceil(y / 8);
else if (k === 5) y = Math.floor(y / 8) + 0.5;
else y = Math.round(y / 2) / 4;
}
x *= grid.size;
y *= grid.size;
return { x, y };
} else if (width < 1 && height < 1) {
// isSmall
let xGrid = Math.round(position.x / grid.size);
let yGrid = Math.round(position.y / grid.size);
const x = xGrid * grid.size + grid.size / 2 - (width * grid.size) / 2;
const y = yGrid * grid.size + grid.size / 2 - (height * grid.size) / 2;
return { x, y };
}
const modeX = Number.isInteger(width) ? M.VERTEX : M.VERTEX | M.EDGE_MIDPOINT | M.CENTER;
const modeY = Number.isInteger(height) ? M.VERTEX : M.VERTEX | M.EDGE_MIDPOINT | M.CENTER;
if (modeX === modeY) return grid.getSnappedPoint(position, { mode: modeX });
return {
x: grid.getSnappedPoint(position, { mode: modeX }).x,
y: grid.getSnappedPoint(position, { mode: modeY }).y
};
}
//#region CopyPasta for mean private methods that have to be duplicated
static getSnappedPositionInHexagonalGrid(grid, position, width, height, shape) {
// Hexagonal shape
const hexagonalShape = DHToken.#getHexagonalShape(width, height, shape, grid.columns);
if (hexagonalShape) {
const offsetX = hexagonalShape.anchor.x * grid.sizeX;
const offsetY = hexagonalShape.anchor.y * grid.sizeY;
position = grid.getCenterPoint({ x: position.x + offsetX, y: position.y + offsetY });
position.x -= offsetX;
position.y -= offsetY;
return position;
}
// Rectagular shape
const M = CONST.GRID_SNAPPING_MODES;
return grid.getSnappedPoint(position, { mode: M.CENTER | M.VERTEX | M.CORNER | M.SIDE_MIDPOINT });
}
/**
* The cache of hexagonal shapes.
* @type {Map<string, DeepReadonly<TokenHexagonalShapeData>>}
*/
static #hexagonalShapes = new Map();
static #getHexagonalShape(width, height, shape, columns) {
if (!Number.isInteger(width * 2) || !Number.isInteger(height * 2)) return null;
// TODO: can we set a max of 2^13 on width and height so that we may use an integer key?
const key = `${width},${height},${shape}${columns ? 'C' : 'R'}`;
let data = DHToken.#hexagonalShapes.get(key);
if (data) return data;
// Hexagon symmetry
if (columns) {
const rowData = BaseToken.#getHexagonalShape(height, width, shape, false);
if (!rowData) return null;
// Transpose the offsets/points of the shape in row orientation
const offsets = { even: [], odd: [] };
for (const { i, j } of rowData.offsets.even) offsets.even.push({ i: j, j: i });
for (const { i, j } of rowData.offsets.odd) offsets.odd.push({ i: j, j: i });
offsets.even.sort(({ i: i0, j: j0 }, { i: i1, j: j1 }) => j0 - j1 || i0 - i1);
offsets.odd.sort(({ i: i0, j: j0 }, { i: i1, j: j1 }) => j0 - j1 || i0 - i1);
const points = [];
for (let i = rowData.points.length; i > 0; i -= 2) {
points.push(rowData.points[i - 1], rowData.points[i - 2]);
}
data = {
offsets,
points,
center: { x: rowData.center.y, y: rowData.center.x },
anchor: { x: rowData.anchor.y, y: rowData.anchor.x }
};
}
// Small hexagon
else if (width === 0.5 && height === 0.5) {
data = {
offsets: { even: [{ i: 0, j: 0 }], odd: [{ i: 0, j: 0 }] },
points: [0.25, 0.0, 0.5, 0.125, 0.5, 0.375, 0.25, 0.5, 0.0, 0.375, 0.0, 0.125],
center: { x: 0.25, y: 0.25 },
anchor: { x: 0.25, y: 0.25 }
};
}
// Normal hexagon
else if (width === 1 && height === 1) {
data = {
offsets: { even: [{ i: 0, j: 0 }], odd: [{ i: 0, j: 0 }] },
points: [0.5, 0.0, 1.0, 0.25, 1, 0.75, 0.5, 1.0, 0.0, 0.75, 0.0, 0.25],
center: { x: 0.5, y: 0.5 },
anchor: { x: 0.5, y: 0.5 }
};
}
// Hexagonal ellipse or trapezoid
else if (shape <= CONST.TOKEN_SHAPES.TRAPEZOID_2) {
data = DHToken.#createHexagonalEllipseOrTrapezoid(width, height, shape);
}
// Hexagonal rectangle
else if (shape <= CONST.TOKEN_SHAPES.RECTANGLE_2) {
data = DHToken.#createHexagonalRectangle(width, height, shape);
}
// Cache the shape
if (data) {
foundry.utils.deepFreeze(data);
DHToken.#hexagonalShapes.set(key, data);
}
return data;
}
static #createHexagonalEllipseOrTrapezoid(width, height, shape) {
if (!Number.isInteger(width) || !Number.isInteger(height)) return null;
const points = [];
let top;
let bottom;
switch (shape) {
case CONST.TOKEN_SHAPES.ELLIPSE_1:
if (height >= 2 * width) return null;
top = Math.floor(height / 2);
bottom = Math.floor((height - 1) / 2);
break;
case CONST.TOKEN_SHAPES.ELLIPSE_2:
if (height >= 2 * width) return null;
top = Math.floor((height - 1) / 2);
bottom = Math.floor(height / 2);
break;
case CONST.TOKEN_SHAPES.TRAPEZOID_1:
if (height > width) return null;
top = height - 1;
bottom = 0;
break;
case CONST.TOKEN_SHAPES.TRAPEZOID_2:
if (height > width) return null;
top = 0;
bottom = height - 1;
break;
}
const offsets = { even: [], odd: [] };
for (let i = bottom; i > 0; i--) {
for (let j = 0; j < width - i; j++) {
offsets.even.push({ i: bottom - i, j: j + (((bottom & 1) + i + 1) >> 1) });
offsets.odd.push({ i: bottom - i, j: j + (((bottom & 1) + i) >> 1) });
}
}
for (let i = 0; i <= top; i++) {
for (let j = 0; j < width - i; j++) {
offsets.even.push({ i: bottom + i, j: j + (((bottom & 1) + i + 1) >> 1) });
offsets.odd.push({ i: bottom + i, j: j + (((bottom & 1) + i) >> 1) });
}
}
let x = 0.5 * bottom;
let y = 0.25;
for (let k = width - bottom; k--; ) {
points.push(x, y);
x += 0.5;
y -= 0.25;
points.push(x, y);
x += 0.5;
y += 0.25;
}
points.push(x, y);
for (let k = bottom; k--; ) {
y += 0.5;
points.push(x, y);
x += 0.5;
y += 0.25;
points.push(x, y);
}
y += 0.5;
for (let k = top; k--; ) {
points.push(x, y);
x -= 0.5;
y += 0.25;
points.push(x, y);
y += 0.5;
}
for (let k = width - top; k--; ) {
points.push(x, y);
x -= 0.5;
y += 0.25;
points.push(x, y);
x -= 0.5;
y -= 0.25;
}
points.push(x, y);
for (let k = top; k--; ) {
y -= 0.5;
points.push(x, y);
x -= 0.5;
y -= 0.25;
points.push(x, y);
}
y -= 0.5;
for (let k = bottom; k--; ) {
points.push(x, y);
x += 0.5;
y -= 0.25;
points.push(x, y);
y -= 0.5;
}
return {
offsets,
points,
// We use the centroid of the polygon for ellipse and trapzoid shapes
center: foundry.utils.polygonCentroid(points),
anchor: bottom % 2 ? { x: 0.0, y: 0.5 } : { x: 0.5, y: 0.5 }
};
}
/**
* Create the row-based hexagonal rectangle given the type, width, and height.
* @param {number} width The width of the Token (positive)
* @param {number} height The height of the Token (positive)
* @param {TokenShapeType} shape The shape type (must be RECTANGLE_1 or RECTANGLE_2)
* @returns {TokenHexagonalShapeData|null} The hexagonal shape or null if there is no shape
* for the given combination of arguments
*/
static #createHexagonalRectangle(width, height, shape) {
if (width < 1 || !Number.isInteger(height)) return null;
if (width === 1 && height > 1) return null;
if (!Number.isInteger(width) && height === 1) return null;
const even = shape === CONST.TOKEN_SHAPES.RECTANGLE_1 || height === 1;
const offsets = { even: [], odd: [] };
for (let i = 0; i < height; i++) {
const j0 = even ? 0 : (i + 1) & 1;
const j1 = ((width + (i & 1) * 0.5) | 0) - (even ? i & 1 : 0);
for (let j = j0; j < j1; j++) {
offsets.even.push({ i, j: j + (i & 1) });
offsets.odd.push({ i, j });
}
}
let x = even ? 0.0 : 0.5;
let y = 0.25;
const points = [x, y];
while (x + 1 <= width) {
x += 0.5;
y -= 0.25;
points.push(x, y);
x += 0.5;
y += 0.25;
points.push(x, y);
}
if (x !== width) {
y += 0.5;
points.push(x, y);
x += 0.5;
y += 0.25;
points.push(x, y);
}
while (y + 1.5 <= 0.75 * height) {
y += 0.5;
points.push(x, y);
x -= 0.5;
y += 0.25;
points.push(x, y);
y += 0.5;
points.push(x, y);
x += 0.5;
y += 0.25;
points.push(x, y);
}
if (y + 0.75 < 0.75 * height) {
y += 0.5;
points.push(x, y);
x -= 0.5;
y += 0.25;
points.push(x, y);
}
y += 0.5;
points.push(x, y);
while (x - 1 >= 0) {
x -= 0.5;
y += 0.25;
points.push(x, y);
x -= 0.5;
y -= 0.25;
points.push(x, y);
}
if (x !== 0) {
y -= 0.5;
points.push(x, y);
x -= 0.5;
y -= 0.25;
points.push(x, y);
}
while (y - 1.5 > 0) {
y -= 0.5;
points.push(x, y);
x += 0.5;
y -= 0.25;
points.push(x, y);
y -= 0.5;
points.push(x, y);
x -= 0.5;
y -= 0.25;
points.push(x, y);
}
if (y - 0.75 > 0) {
y -= 0.5;
points.push(x, y);
x += 0.5;
y -= 0.25;
points.push(x, y);
}
return {
offsets,
points,
// We use center of the rectangle (and not the centroid of the polygon) for the rectangle shapes
center: {
x: width / 2,
y: (0.75 * Math.floor(height) + 0.5 * (height % 1) + 0.25) / 2
},
anchor: even ? { x: 0.5, y: 0.5 } : { x: 0.0, y: 0.5 }
};
}
//#endregion
} }

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

@ -3,11 +3,6 @@ import { parseInlineParams } from './parser.mjs';
export default function DhLookupEnricher(match, { rollData }) { export default function DhLookupEnricher(match, { rollData }) {
const results = parseInlineParams(match[1], { first: 'formula' }); const results = parseInlineParams(match[1], { first: 'formula' });
const element = document.createElement('span'); const element = document.createElement('span');
element.textContent = Roll.replaceFormulaData(String(results.formula), rollData);
const lookupCommand = match[0];
const lookupParam = match[1];
const lookupText = Roll.replaceFormulaData(String(results.formula), rollData);
element.textContent = lookupText === lookupParam ? lookupCommand : lookupText;
return element; return element;
} }

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>
@ -198,7 +198,7 @@ foundry.dice.terms.Die.prototype.selfCorrecting = function (modifier) {
}; };
export const getDamageKey = damage => { export const getDamageKey = damage => {
return ['none', 'minor', 'major', 'severe', 'massive', 'any'][damage]; return ['none', 'minor', 'major', 'severe', 'massive','any'][damage];
}; };
export const getDamageLabel = damage => { export const getDamageLabel = damage => {
@ -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

@ -21,8 +21,8 @@ export const registerDHSettings = () => {
scope: 'world', scope: 'world',
config: true, config: true,
type: Boolean, type: Boolean,
onChange: () => ui.combat.render() onChange: () => ui.combat.render(),
}); })
}; };
const registerMenuSettings = () => { const registerMenuSettings = () => {
@ -46,9 +46,6 @@ const registerMenuSettings = () => {
if (value.maxFear) { if (value.maxFear) {
if (ui.resources) ui.resources.render({ force: true }); if (ui.resources) ui.resources.render({ force: true });
} }
// Some homebrew settings may change sheets in various ways, so trigger a re-render
resetActors();
} }
}); });
@ -143,25 +140,3 @@ const registerNonConfigSettings = () => {
type: DhTagTeamRoll type: DhTagTeamRoll
}); });
}; };
/**
* Triggers a reset and non-forced re-render on all given actors (if given)
* or all world actors and actors in all scenes to show immediate results for a changed setting.
*/
function resetActors(actors) {
actors ??= [
game.actors.contents,
game.scenes.contents.flatMap(s => s.tokens.contents).flatMap(t => t.actor ?? [])
].flat();
actors = new Set(actors);
for (const actor of actors) {
for (const app of Object.values(actor.apps)) {
for (const element of app.element?.querySelectorAll('prose-mirror.active')) {
element.open = false; // This triggers a save
}
}
actor.reset();
actor.render();
}
}

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 = () => {
@ -74,13 +73,10 @@ export const registerSocketHooks = () => {
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Countdown }); Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Countdown });
break; break;
case GMUpdateEvent.UpdateSaveMessage: case GMUpdateEvent.UpdateSaveMessage:
const message = game.messages.get(data.update.message); const action = await fromUuid(data.update.action),
if (!message) return; message = game.messages.get(data.update.message);
game.system.api.fields.ActionFields.SaveField.updateSaveMessage( if (!action || !message) return;
data.update.result, action.updateSaveMessage(data.update.result, message, data.update.token);
message,
data.update.token
);
break; break;
} }

View file

@ -40,8 +40,7 @@
"experiences": { "experiences": {
"pe7OIoJsqlpMXEvs": { "pe7OIoJsqlpMXEvs": {
"name": "Tremor Sense", "name": "Tremor Sense",
"value": 2, "value": 2
"description": ""
} }
}, },
"bonuses": { "bonuses": {
@ -149,8 +148,7 @@
"source": "Daggerheart SRD", "source": "Daggerheart SRD",
"page": 75, "page": 75,
"artist": "" "artist": ""
}, }
"size": "large"
}, },
"flags": {}, "flags": {},
"ownership": { "ownership": {
@ -260,14 +258,14 @@
"_id": "MFmGN6Tbf5GYxrQ9", "_id": "MFmGN6Tbf5GYxrQ9",
"img": "icons/magic/unholy/silhouette-evil-horned-giant.webp", "img": "icons/magic/unholy/silhouette-evil-horned-giant.webp",
"system": { "system": {
"description": "<p>The @Lookup[@name] can be spotlighted up to three times per GM turn. Spend Fear as usual to spotlight them.</p>", "description": "<p>The Burrower can be spotlighted up to three times per GM turn. Spend Fear as usual to spotlight them.</p>",
"resource": null, "resource": null,
"actions": { "actions": {
"3lGGgkxnzgUwHGIp": { "3lGGgkxnzgUwHGIp": {
"type": "effect", "type": "effect",
"_id": "3lGGgkxnzgUwHGIp", "_id": "3lGGgkxnzgUwHGIp",
"systemPath": "actions", "systemPath": "actions",
"description": "", "description": "<p>The Burrower can be spotlighted up to three times per GM turn. Spend Fear as usual to spotlight them.</p>",
"chatDisplay": false, "chatDisplay": false,
"actionType": "passive", "actionType": "passive",
"cost": [ "cost": [
@ -319,14 +317,14 @@
"_id": "ctXYwil2D1zfsekT", "_id": "ctXYwil2D1zfsekT",
"img": "icons/magic/earth/barrier-stone-explosion-red.webp", "img": "icons/magic/earth/barrier-stone-explosion-red.webp",
"system": { "system": {
"description": "<p><strong>Mark a Stress</strong> to have the @Lookup[@name] burst out of the ground. All creatures within Very Close range must succeed on an Agility Reaction Roll or be knocked over, making them Vulnerable until they next act.</p><p>@Template[type:emanation|range:vc]</p>", "description": "<p><strong>Mark a Stress</strong> to have the Burrower burst out of the ground. All creatures within Very Close range must succeed on an Agility Reaction Roll or be knocked over, making them Vulnerable until they next act.</p><p>@Template[type:emanation|range:vc]</p>",
"resource": null, "resource": null,
"actions": { "actions": {
"4ppSeiTdbqnMzWAs": { "4ppSeiTdbqnMzWAs": {
"type": "attack", "type": "attack",
"_id": "4ppSeiTdbqnMzWAs", "_id": "4ppSeiTdbqnMzWAs",
"systemPath": "actions", "systemPath": "actions",
"description": "", "description": "<p><strong>Mark a Stress</strong> to have the Burrower burst out of the ground. All creatures within Very Close range must succeed on an Agility Reaction Roll or be knocked over, making them Vulnerable until they next act.</p><p>@Template[type:emanation|range:vc]</p>",
"chatDisplay": true, "chatDisplay": true,
"actionType": "action", "actionType": "action",
"cost": [ "cost": [
@ -383,8 +381,7 @@
}, },
"originItemType": null, "originItemType": null,
"subType": null, "subType": null,
"originId": null, "originId": null
"featureForm": "action"
}, },
"effects": [ "effects": [
{ {
@ -444,14 +441,14 @@
"_id": "UpFsnlbZkyvM2Ftv", "_id": "UpFsnlbZkyvM2Ftv",
"img": "icons/magic/acid/projectile-smoke-glowing.webp", "img": "icons/magic/acid/projectile-smoke-glowing.webp",
"system": { "system": {
"description": "<p>Make an attack against all targets in front of the @Lookup[@name] within Close range. Targets the @Lookup[@name] succeeds against take <strong>2d6</strong> physical damage and must mark an Armor Slot without receiving its benefits (they can still use armor to reduce the damage). If they cant mark an Armor Slot, they must mark an additional HP and you gain a Fear.</p><p>@Template[type:inFront|range:c]</p>", "description": "<p>Make an attack against all targets in front of the Burrower within Close range. Targets the Burrower succeeds against take <strong>2d6</strong> physical damage and must mark an Armor Slot without receiving its benefits (they can still use armor to reduce the damage). If they cant mark an Armor Slot, they must mark an additional HP and you gain a Fear.</p><p>@Template[type:inFront|range:c]</p>",
"resource": null, "resource": null,
"actions": { "actions": {
"yd10HwK6Wa3OEvv2": { "yd10HwK6Wa3OEvv2": {
"type": "attack", "type": "attack",
"_id": "yd10HwK6Wa3OEvv2", "_id": "yd10HwK6Wa3OEvv2",
"systemPath": "actions", "systemPath": "actions",
"description": "", "description": "<p>Make an attack against all targets in front of the Burrower within Close range. Targets the Burrower succeeds against take <strong>2d6</strong> physical damage and must mark an Armor Slot without receiving its benefi ts (they can still use armor to reduce the damage). If they cant mark an Armor Slot, they must mark an additional HP and you gain a Fear.</p><p>@Template[type:inFront|range:c]</p>",
"chatDisplay": true, "chatDisplay": true,
"actionType": "action", "actionType": "action",
"cost": [], "cost": [],
@ -549,8 +546,7 @@
}, },
"originItemType": null, "originItemType": null,
"subType": null, "subType": null,
"originId": null, "originId": null
"featureForm": "action"
}, },
"effects": [], "effects": [],
"folder": null, "folder": null,
@ -571,14 +567,14 @@
"_id": "aNIVT5LKhwLyjKpI", "_id": "aNIVT5LKhwLyjKpI",
"img": "icons/magic/acid/dissolve-drip-droplet-smoke.webp", "img": "icons/magic/acid/dissolve-drip-droplet-smoke.webp",
"system": { "system": {
"description": "<p>When the @Lookup[@name] takes Severe damage, all creatures within Close range are bathed in their acidic blood, taking <strong>1d10</strong> physical damage. This splash covers the ground within Very Close range with blood, and all creatures other than the @Lookup[@name] who move through it take <strong>1d6</strong> physical damage.</p><p>@Template[type:emanation|range:c]</p>", "description": "<p>When the Burrower takes Severe damage, all creatures within Close range are bathed in their acidic blood, taking <strong>1d10</strong> physical damage. This splash covers the ground within Very Close range with blood, and all creatures other than the Burrower who move through it take <strong>1d6</strong> physical damage.</p><p>@Template[type:emanation|range:c]</p>",
"resource": null, "resource": null,
"actions": { "actions": {
"XbtTzOBvlTaxOKTy": { "XbtTzOBvlTaxOKTy": {
"type": "damage", "type": "damage",
"_id": "XbtTzOBvlTaxOKTy", "_id": "XbtTzOBvlTaxOKTy",
"systemPath": "actions", "systemPath": "actions",
"description": "", "description": "<p>When the Burrower takes Severe damage, all creatures within Close range are bathed in their acidic blood, taking <strong>1d10</strong> physical damage. This splash covers the ground within Very Close range with blood, and all creatures other than the Burrower who move through it take <strong>1d6</strong> physical damage.</p><p>@Template[type:emanation|range:c]</p>",
"chatDisplay": true, "chatDisplay": true,
"actionType": "action", "actionType": "action",
"cost": [], "cost": [],
@ -632,7 +628,7 @@
"type": "damage", "type": "damage",
"_id": "xpcp1ECTWF20kxve", "_id": "xpcp1ECTWF20kxve",
"systemPath": "actions", "systemPath": "actions",
"description": "<p>This splash covers the ground within Very Close range with blood, and all creatures other than the @Lookup[@name] who move through it take <strong>1d6</strong> physical damage.</p>", "description": "<p>This splash covers the ground within Very Close range with blood, and all creatures other than the Burrower who move through it take <strong>1d6</strong> physical damage.</p>",
"chatDisplay": true, "chatDisplay": true,
"actionType": "action", "actionType": "action",
"cost": [], "cost": [],
@ -685,8 +681,7 @@
}, },
"originItemType": null, "originItemType": null,
"subType": null, "subType": null,
"originId": null, "originId": null
"featureForm": "reaction"
}, },
"effects": [], "effects": [],
"folder": null, "folder": null,

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