diff --git a/daggerheart.mjs b/daggerheart.mjs index 2eb5109f..0bbbd274 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -3,6 +3,7 @@ import * as applications from './module/applications/_module.mjs'; import * as data from './module/data/_module.mjs'; import * as models from './module/data/_module.mjs'; import * as documents from './module/documents/_module.mjs'; +import { macros } from './module/_module.mjs'; import * as collections from './module/documents/collections/_module.mjs'; import * as dice from './module/dice/_module.mjs'; import * as fields from './module/data/fields/_module.mjs'; @@ -94,6 +95,7 @@ Hooks.once('init', () => { data, models, documents, + macros, dice, fields }; diff --git a/lang/en.json b/lang/en.json index 47697fa8..5dc09a1e 100755 --- a/lang/en.json +++ b/lang/en.json @@ -2547,6 +2547,14 @@ "secondaryWeapon": "Secondary Weapon" } }, + "MACROS": { + "Spotlight": { + "errors": { + "noActiveCombat": "There is no active encounter", + "noCombatantSelected": "A combatant token must be either selected or hovered to spotlight it" + } + } + }, "ROLLTABLES": { "FIELDS": { "formulaName": { "label": "Formula Name" } @@ -2790,6 +2798,12 @@ "setResourceIdentifier": "Set Resource Identifier" } }, + "Keybindings": { + "spotlight": { + "name": "Spotlight Combatant", + "hint": "Move the spotlight to a hovered or selected token that's present in an active encounter" + } + }, "Menu": { "title": "Daggerheart Game Settings", "automation": { diff --git a/module/_module.mjs b/module/_module.mjs index 2e1d6fb4..4a00e97c 100644 --- a/module/_module.mjs +++ b/module/_module.mjs @@ -7,3 +7,4 @@ export * as documents from './documents/_module.mjs'; export * as enrichers from './enrichers/_module.mjs'; export * as helpers from './helpers/_module.mjs'; export * as systemRegistration from './systemRegistration/_module.mjs'; +export * as macros from './macros/_modules.mjs'; diff --git a/module/config/settingsConfig.mjs b/module/config/settingsConfig.mjs index 52a316cf..36892731 100644 --- a/module/config/settingsConfig.mjs +++ b/module/config/settingsConfig.mjs @@ -1,3 +1,7 @@ +export const keybindings = { + spotlight: 'DHSpotlight' +}; + export const menu = { Automation: { Name: 'GameSettingsAutomation', diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index 55c1c7d8..2878ad0c 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -757,7 +757,6 @@ export default class DhCharacter extends DhCreature { prepareDerivedData() { super.prepareDerivedData(); - let baseHope = this.resources.hope.value; if (this.companion) { for (let levelKey in this.companion.system.levelData.levelups) { const level = this.companion.system.levelData.levelups[levelKey]; @@ -772,7 +771,6 @@ export default class DhCharacter extends DhCreature { } this.resources.hope.max -= this.scars; - this.resources.hope.value = Math.min(baseHope, this.resources.hope.max); this.attack.roll.trait = this.rules.attack.roll.trait ?? this.attack.roll.trait; this.resources.armor = { diff --git a/module/data/actor/creature.mjs b/module/data/actor/creature.mjs index 601068ad..88646301 100644 --- a/module/data/actor/creature.mjs +++ b/module/data/actor/creature.mjs @@ -60,4 +60,14 @@ export default class DhCreature extends BaseDataActor { } } } + + prepareDerivedData() { + const minLimitResource = resource => { + if (resource) resource.value = Math.min(resource.value, resource.max); + }; + + minLimitResource(this.resources.stress); + minLimitResource(this.resources.hitPoints); + minLimitResource(this.resources.hope); + } } diff --git a/module/data/fields/actorField.mjs b/module/data/fields/actorField.mjs index 25e04317..7a57aa46 100644 --- a/module/data/fields/actorField.mjs +++ b/module/data/fields/actorField.mjs @@ -94,8 +94,9 @@ class ResourcesField extends fields.TypedObjectField { const resources = CONFIG.DH.RESOURCE[this.actorType].all; if (first in resources) { - this.element.label = resources[first].label; - return this.element._getField(path); + const field = this.element._getField(path); + field.label = resources[first].label; + return field; } return undefined; diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index 8105471b..3e3dfde4 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -30,6 +30,18 @@ export default class DhpActor extends Actor { return this.system.metadata.isNPC; } + prepareData() { + super.prepareData(); + + // Update effects if it is the user's character or is controlled + if (canvas.ready) { + const controlled = canvas.tokens.controlled.some(t => t.actor === this); + if (game.user.character === this || controlled) { + ui.effectsDisplay.render(); + } + } + } + /* -------------------------------------------- */ /** @inheritDoc */ @@ -122,14 +134,6 @@ export default class DhpActor extends Actor { } } - _onUpdateDescendantDocuments(parent, collection, documents, changes, options, userId) { - if (collection === 'effects') { - ui.effectsDisplay.render(); - } - - super._onUpdateDescendantDocuments(parent, collection, documents, changes, options, userId); - } - async updateLevel(newLevel) { if (!['character', 'companion'].includes(this.type) || newLevel === this.system.levelData.level.changed) return; diff --git a/module/macros/_modules.mjs b/module/macros/_modules.mjs new file mode 100644 index 00000000..d4a5599f --- /dev/null +++ b/module/macros/_modules.mjs @@ -0,0 +1 @@ +export { default as spotlightCombatant } from './spotlightCombatant.mjs'; diff --git a/module/macros/spotlightCombatant.mjs b/module/macros/spotlightCombatant.mjs new file mode 100644 index 00000000..68a26ff9 --- /dev/null +++ b/module/macros/spotlightCombatant.mjs @@ -0,0 +1,21 @@ +/** + * Spotlights a combatant. + * The combatant can be selected in a number of ways. If many are applied at the same time, the following order is used: + * 1) SelectedCombatant + * 2) HoveredCombatant + */ +const spotlightCombatant = () => { + if (!game.combat) + return ui.notifications.error(game.i18n.localize('DAGGERHEART.MACROS.Spotlight.errors.noActiveCombat')); + + const selectedCombatant = canvas.tokens.controlled.length > 0 ? canvas.tokens.controlled[0].combatant : null; + const hoveredCombatant = game.canvas.tokens.hover?.combatant; + + const combatant = selectedCombatant ?? hoveredCombatant; + if (!combatant) + return ui.notifications.error(game.i18n.localize('DAGGERHEART.MACROS.Spotlight.errors.noCombatantSelected')); + + ui.combat.setCombatantSpotlight(combatant.id); +}; + +export default spotlightCombatant; diff --git a/module/systemRegistration/settings.mjs b/module/systemRegistration/settings.mjs index 658d2bd1..5c7c7542 100644 --- a/module/systemRegistration/settings.mjs +++ b/module/systemRegistration/settings.mjs @@ -18,6 +18,7 @@ import { import { CompendiumBrowserSettings } from '../data/_module.mjs'; export const registerDHSettings = () => { + registerKeyBindings(); registerMenuSettings(); registerMenus(); registerNonConfigSettings(); @@ -33,6 +34,25 @@ export const registerDHSettings = () => { }); }; +export const registerKeyBindings = () => { + game.keybindings.register(CONFIG.DH.id, CONFIG.DH.SETTINGS.keybindings.spotlight, { + name: game.i18n.localize('DAGGERHEART.SETTINGS.Keybindings.spotlight.name'), + hint: game.i18n.localize('DAGGERHEART.SETTINGS.Keybindings.spotlight.hint'), + uneditable: [], + editable: [ + { + key: 's', + modifiers: [] + } + ], + onDown: game.system.api.macros.spotlightCombatant, + onUp: () => {}, + restricted: true, + reservedModifiers: [], + precedence: CONST.KEYBINDING_PRECEDENCE.NORMAL + }); +}; + const registerMenuSettings = () => { game.settings.register(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules, { scope: 'world',