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
404 changed files with 2570 additions and 10237 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;
}
}
await effect.update({ disabled: !enabledEffect });
} }
}; };
const updateAllRangeDependentEffects = async () => { const effectUpdates = {};
const effectsAutomation = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).effects; for (let token of game.scenes.find(x => x.active).tokens) {
if (!effectsAutomation.rangeDependent) return; if (token.id !== movedToken.id) {
await updateEffects(token.disposition, token, rangeDependantEffects, effectUpdates);
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);
} }
if (token.actor) await updateEffects(movedToken.disposition, token, token.actor.effects, effectUpdates);
} }
};
const debouncedRangeEffectCall = foundry.utils.debounce(updateAllRangeDependentEffects, 50); for (let key in effectUpdates) {
const effect = effectUpdates[key];
Hooks.on('targetToken', () => { await effect.value.update({ disabled: effect.disabled });
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"
@ -2085,16 +2032,15 @@
"tier4": "tier 4", "tier4": "tier 4",
"domains": "Domains", "domains": "Domains",
"downtime": "Downtime", "downtime": "Downtime",
"itemFeatures": "Item Features",
"roll": "Roll", "roll": "Roll",
"rules": "Rules", "rules": "Rules",
"partyMembers": "Party Members", "partyMembers": "Party Members",
"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",
@ -2148,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",
@ -2215,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",
@ -2283,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" },
@ -2310,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",
@ -2335,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"
@ -2476,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"
} }
@ -2491,9 +2417,6 @@
}, },
"roll": { "roll": {
"title": "Actions" "title": "Actions"
},
"trigger": {
"title": "Triggers"
} }
}, },
"Homebrew": { "Homebrew": {
@ -2621,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": {
@ -2836,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": {
@ -2882,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]
@ -123,7 +118,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
context.formula = this.roll.constructFormula(this.config); context.formula = this.roll.constructFormula(this.config);
if (this.actor?.system?.traits) context.abilities = this.getTraitModifiers(); if (this.actor?.system?.traits) context.abilities = this.getTraitModifiers();
context.showReaction = !this.config.roll?.type || context.rollType === 'DualityRoll'; context.showReaction = !this.config.roll?.type && context.rollType === 'DualityRoll';
context.reactionOverride = this.reactionOverride; context.reactionOverride = this.reactionOverride;
} }
@ -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

@ -25,7 +25,7 @@ export default class DaggerheartMenu extends HandlebarsApplicationMixin(Abstract
/** @override */ /** @override */
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
classes: ['dh-style', 'directory'], classes: ['dh-style'],
window: { window: {
title: 'SIDEBAR.TabSettings' title: 'SIDEBAR.TabSettings'
}, },

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)
@ -92,19 +91,6 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
super.close(options); super.close(options);
} }
/** Ensure the chat theme inherits the interface theme */
_replaceHTML(result, content, options) {
const themedElement = result.log?.querySelector(".chat-log");
themedElement?.classList.remove("themed", "theme-light", "theme-dark");
super._replaceHTML(result, content, options);
}
/** Remove chat log theme from notifications area */
async _onFirstRender(result, content) {
await super._onFirstRender(result, content);
document.querySelector("#chat-notifications .chat-log")?.classList.remove("themed", "theme-light", "theme-dark")
}
async onRollSimple(event, message) { async onRollSimple(event, message) {
const buttonType = event.target.dataset.type ?? 'damage', const buttonType = event.target.dataset.type ?? 'damage',
total = message.rolls.reduce((a, c) => a + Roll.fromJSON(c).total, 0), total = message.rolls.reduce((a, c) => a + Roll.fromJSON(c).total, 0),
@ -147,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(
@ -161,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 });
}
if (updates.length) {
const target = actor.system.partner ?? actor;
if (!['dead', 'defeated', 'unconscious'].some(x => actor.statuses.has(x))) {
await target.modifyResource(updates);
}
}
}
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);
}
});
}; };
}
return acc;
}, {}) ?? []
);
}
getActionChangeKeys() {
return [];
}
}

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>
@ -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;
} }

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