diff --git a/assets/svg/trait-shield-light.svg b/assets/svg/trait-shield-light.svg index dc6f77c3..c4d1824d 100644 --- a/assets/svg/trait-shield-light.svg +++ b/assets/svg/trait-shield-light.svg @@ -1,3 +1,3 @@ - + diff --git a/assets/svg/trait-shield.svg b/assets/svg/trait-shield.svg index 87c18dd7..dcb87cf4 100644 --- a/assets/svg/trait-shield.svg +++ b/assets/svg/trait-shield.svg @@ -1,3 +1,3 @@ - + diff --git a/daggerheart.mjs b/daggerheart.mjs index 2eb5109f..240d8704 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -3,15 +3,13 @@ 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'; import RegisterHandlebarsHelpers from './module/helpers/handlebarsHelper.mjs'; import { enricherConfig, enricherRenderSetup } from './module/enrichers/_module.mjs'; -import { getCommandTarget, rollCommandToJSON } from './module/helpers/utils.mjs'; import { BaseRoll, DHRoll, DualityRoll, D20Roll, DamageRoll, FateRoll } from './module/dice/_module.mjs'; -import { enrichedDualityRoll } from './module/enrichers/DualityRollEnricher.mjs'; -import { enrichedFateRoll, getFateTypeData } from './module/enrichers/FateRollEnricher.mjs'; import { handlebarsRegistration, runMigrations, @@ -34,6 +32,8 @@ CONFIG.Dice.daggerheart = { FateRoll: FateRoll }; +Object.assign(CONFIG.Dice.termTypes, dice.diceTypes); + CONFIG.Actor.documentClass = documents.DhpActor; CONFIG.Actor.dataModels = models.actors.config; CONFIG.Actor.collection = collections.DhActorCollection; @@ -94,6 +94,7 @@ Hooks.once('init', () => { data, models, documents, + macros, dice, fields }; @@ -331,78 +332,6 @@ Hooks.on('renderHandlebarsApplication', (_, element) => { enricherRenderSetup(element); }); -Hooks.on('chatMessage', (_, message) => { - if (message.startsWith('/dr')) { - const result = - message.trim().toLowerCase() === '/dr' ? { result: {} } : rollCommandToJSON(message.replace(/\/dr\s?/, '')); - if (!result) { - ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.dualityParsing')); - return false; - } - - const { result: rollCommand, flavor } = result; - - const reaction = rollCommand.reaction; - const traitValue = rollCommand.trait?.toLowerCase(); - const advantage = rollCommand.advantage - ? CONFIG.DH.ACTIONS.advantageState.advantage.value - : rollCommand.disadvantage - ? CONFIG.DH.ACTIONS.advantageState.disadvantage.value - : undefined; - const difficulty = rollCommand.difficulty; - const grantResources = rollCommand.grantResources; - - const target = getCommandTarget({ allowNull: true }); - const title = - (flavor ?? traitValue) - ? game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', { - ability: game.i18n.localize(SYSTEM.ACTOR.abilities[traitValue].label) - }) - : game.i18n.localize('DAGGERHEART.GENERAL.duality'); - - enrichedDualityRoll({ - reaction, - traitValue, - target, - difficulty, - title, - label: game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll'), - actionType: null, - advantage, - grantResources - }); - return false; - } - - if (message.startsWith('/fr')) { - const result = - message.trim().toLowerCase() === '/fr' ? { result: {} } : rollCommandToJSON(message.replace(/\/fr\s?/, '')); - - if (!result) { - ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.fateParsing')); - return false; - } - - const { result: rollCommand, flavor } = result; - const fateTypeData = getFateTypeData(rollCommand?.type); - - if (!fateTypeData) - return ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.fateTypeParsing')); - - const { value: fateType, label: fateTypeLabel } = fateTypeData; - const target = getCommandTarget({ allowNull: true }); - const title = flavor ?? game.i18n.localize('DAGGERHEART.GENERAL.fateRoll'); - - enrichedFateRoll({ - target, - title, - label: fateTypeLabel, - fateType - }); - return false; - } -}); - Hooks.on(CONFIG.DH.HOOKS.hooksConfig.tagTeamStart, async data => { if (data.openForAllPlayers && data.partyId) { const party = game.actors.get(data.partyId); diff --git a/lang/en.json b/lang/en.json index 85d5f997..a1cf62d0 100755 --- a/lang/en.json +++ b/lang/en.json @@ -89,9 +89,14 @@ }, "Config": { "beastform": { - "exact": "Beastform Max Tier", - "exactHint": "The Character's Tier is used if empty", - "label": "Beastform" + "exact": { "label": "Beastform Max Tier", "hint": "The Character's Tier is used if empty" }, + "modifications": { + "traitBonuses": { + "label": { "single": "Trait Bonus", "plural": "Trait Bonuses" }, + "hint": "Pick bonuses you apply to freely chosen traits at the time of transforming", + "bonus": "Bonus Amount" + } + } }, "countdown": { "defaultOwnership": "Default Ownership", @@ -448,7 +453,8 @@ }, "DaggerheartMenu": { "title": "GM Tools", - "refreshFeatures": "Refresh Features" + "refreshFeatures": "Refresh Features", + "fallingAndCollision": "Falling And Collision Damage" }, "DeleteConfirmation": { "title": "Delete {type} - {name}", @@ -1163,6 +1169,12 @@ "description": "" } }, + "fallAndCollision": { + "veryClose": { "label": "Very Close", "chatTitle": "Fall Damage: Very Close" }, + "close": { "label": "Close", "chatTitle": "Fall Damage: Close" }, + "far": { "label": "Far", "chatTitle": "Fall Damage: Far" }, + "collision": { "label": "Collision", "chatTitle": "Dangerous Collision" } + }, "FeatureForm": { "label": "Feature Form", "passive": "Passive", @@ -2567,6 +2579,13 @@ "secondaryWeapon": "Secondary Weapon" } }, + "MACROS": { + "Spotlight": { + "errors": { + "noTokenSelected": "A token on the canvas must either be selected or hovered to spotlight it" + } + } + }, "ROLLTABLES": { "FIELDS": { "formulaName": { "label": "Formula Name" } @@ -2810,6 +2829,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/applications/dialogs/beastformDialog.mjs b/module/applications/dialogs/beastformDialog.mjs index 09a9222b..8ae6d5fe 100644 --- a/module/applications/dialogs/beastformDialog.mjs +++ b/module/applications/dialogs/beastformDialog.mjs @@ -10,6 +10,12 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat this.selected = null; this.evolved = { form: null }; this.hybrid = { forms: {}, advantages: {}, features: {} }; + this.modifications = { + traitBonuses: configData.modifications.traitBonuses.map(x => ({ + trait: null, + bonus: x.bonus + })) + }; this._dragDrop = this._createDragDropHandlers(); } @@ -28,6 +34,7 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat selectBeastform: this.selectBeastform, toggleHybridFeature: this.toggleHybridFeature, toggleHybridAdvantage: this.toggleHybridAdvantage, + toggleTraitBonus: this.toggleTraitBonus, submitBeastform: this.submitBeastform }, form: { @@ -48,6 +55,7 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat tabs: { template: 'systems/daggerheart/templates/dialogs/beastform/tabs.hbs' }, beastformTier: { template: 'systems/daggerheart/templates/dialogs/beastform/beastformTier.hbs' }, advanced: { template: 'systems/daggerheart/templates/dialogs/beastform/advanced.hbs' }, + modifications: { template: 'systems/daggerheart/templates/dialogs/beastform/modifications.hbs' }, footer: { template: 'systems/daggerheart/templates/dialogs/beastform/footer.hbs' } }; @@ -146,6 +154,9 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat {} ); + context.modifications = this.modifications; + context.traits = CONFIG.DH.ACTOR.abilities; + context.tier = beastformTiers[this.tabGroups.primary]; context.tierKey = this.tabGroups.primary; @@ -155,6 +166,9 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat } canSubmit() { + const modificationsFinished = this.modifications.traitBonuses.every(x => x.trait); + if (!modificationsFinished) return false; + if (this.selected) { switch (this.selected.system.beastformType) { case 'normal': @@ -261,6 +275,13 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat this.render(); } + static toggleTraitBonus(_, button) { + const { index, trait } = button.dataset; + this.modifications.traitBonuses[index].trait = + this.modifications.traitBonuses[index].trait === trait ? null : trait; + this.render(); + } + static async submitBeastform() { await this.close({ submitted: true }); } @@ -292,6 +313,23 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat } } + const beastformEffect = selected.effects.find(x => x.type === 'beastform'); + for (const traitBonus of app.modifications.traitBonuses) { + const existingChange = beastformEffect.changes.find( + x => x.key === `system.traits.${traitBonus.trait}.value` + ); + if (existingChange) { + existingChange.value = Number.parseInt(existingChange.value) + traitBonus.bonus; + } else { + beastformEffect.changes.push({ + key: `system.traits.${traitBonus.trait}.value`, + mode: 2, + priority: null, + value: traitBonus.bonus + }); + } + } + resolve({ selected: selected, evolved: { ...app.evolved, form: evolved }, diff --git a/module/applications/dialogs/tagTeamDialog.mjs b/module/applications/dialogs/tagTeamDialog.mjs index 27003162..2d363471 100644 --- a/module/applications/dialogs/tagTeamDialog.mjs +++ b/module/applications/dialogs/tagTeamDialog.mjs @@ -200,6 +200,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio partContext.members[partId] = { ...data, + roll: data.roll, isEditable: actor.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER), key: partId, readyToRoll: Boolean(data.rollChoice), @@ -448,23 +449,19 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio const { member, diceType } = button.dataset; const memberData = this.party.system.tagTeam.members[member]; - const dieIndex = diceType === 'hope' ? 0 : diceType === 'fear' ? 2 : 4; - - const { parsedRoll, newRoll } = await game.system.api.dice.DualityRoll.reroll( - memberData.rollData, - dieIndex, - diceType - ); - const rollData = parsedRoll.toJSON(); + const dieIndex = diceType === 'hope' ? 0 : diceType === 'fear' ? 1 : 2; + const newRoll = game.system.api.dice.DualityRoll.fromData(memberData.rollData); + const dice = newRoll.dice[dieIndex]; + await dice.reroll(`/r1=${dice.total}`, { + liveRoll: { + roll: newRoll, + isReaction: true + } + }); + const rollData = newRoll.toJSON(); this.updatePartyData( { - [`system.tagTeam.members.${member}.rollData`]: { - ...rollData, - options: { - ...rollData.options, - roll: newRoll - } - } + [`system.tagTeam.members.${member}.rollData`]: rollData }, this.getUpdatingParts(button) ); @@ -699,7 +696,9 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio const error = this.checkInitiatorHopeError(this.party.system.tagTeam.initiator); if (error) return error; - const mainRoll = (await this.getJoinedRoll()).rollData; + const joinedRoll = await this.getJoinedRoll(); + const mainRoll = joinedRoll.rollData; + const finalRoll = foundry.utils.deepClone(joinedRoll.roll); const mainActor = this.party.system.partyMembers.find(x => x.uuid === mainRoll.options.source.actor); mainRoll.options.title = game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.chatMessageRollTitle'); @@ -710,7 +709,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio title: game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.title'), speaker: cls.getSpeaker({ actor: mainActor }), system: mainRoll.options, - rolls: [mainRoll], + rolls: [JSON.stringify(joinedRoll.roll)], sound: null, flags: { core: { RollTable: true } } }; @@ -722,7 +721,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio const fearUpdate = { key: 'fear', value: null, total: null, enabled: true }; for (let memberId in tagTeamData.members) { const resourceUpdates = []; - const rollGivesHope = mainRoll.options.roll.isCritical || mainRoll.options.roll.result.duality === 1; + const rollGivesHope = finalRoll.isCritical || finalRoll.withHope; if (memberId === tagTeamData.initiator.memberId) { const value = tagTeamData.initiator.cost ? rollGivesHope @@ -733,9 +732,8 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio } else if (rollGivesHope) { resourceUpdates.push({ key: 'hope', value: 1, total: -1, enabled: true }); } - if (mainRoll.options.roll.isCritical) - resourceUpdates.push({ key: 'stress', value: -1, total: 1, enabled: true }); - if (mainRoll.options.roll.result.duality === -1) { + if (finalRoll.isCritical) resourceUpdates.push({ key: 'stress', value: -1, total: 1, enabled: true }); + if (finalRoll.withFear) { fearUpdate.value = fearUpdate.value === null ? 1 : fearUpdate.value + 1; fearUpdate.total = fearUpdate.total === null ? -1 : fearUpdate.total - 1; } diff --git a/module/applications/sheets-configs/action-base-config.mjs b/module/applications/sheets-configs/action-base-config.mjs index 05a3177d..0ae39477 100644 --- a/module/applications/sheets-configs/action-base-config.mjs +++ b/module/applications/sheets-configs/action-base-config.mjs @@ -36,7 +36,9 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) editDoc: this.editDoc, addTrigger: this.addTrigger, removeTrigger: this.removeTrigger, - expandTrigger: this.expandTrigger + expandTrigger: this.expandTrigger, + addBeastformTraitBonus: this.addBeastformTraitBonus, + removeBeastformTraitBonus: this.removeBeastformTraitBonus }, form: { handler: this.updateForm, @@ -412,6 +414,21 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) } } + static async addBeastformTraitBonus() { + const data = this.action.toObject(); + data.beastform.modifications.traitBonuses = [ + ...data.beastform.modifications.traitBonuses, + this.action.schema.fields.beastform.fields.modifications.fields.traitBonuses.element.getInitialValue() + ]; + this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) }); + } + + static async removeBeastformTraitBonus(_event, button) { + const data = this.action.toObject(); + data.beastform.modifications.traitBonuses.splice(button.dataset.index, 1); + this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) }); + } + updateSummonCount(event) { event.stopPropagation(); const wrapper = event.target.closest('.summon-count-wrapper'); diff --git a/module/applications/sheets/api/application-mixin.mjs b/module/applications/sheets/api/application-mixin.mjs index 83313454..64f62405 100644 --- a/module/applications/sheets/api/application-mixin.mjs +++ b/module/applications/sheets/api/application-mixin.mjs @@ -79,8 +79,6 @@ export default function DHApplicationMixin(Base) { */ constructor(options = {}) { super(options); - - this._setupDragDrop(); } /** @@ -175,9 +173,6 @@ export default function DHApplicationMixin(Base) { _attachPartListeners(partId, htmlElement, options) { super._attachPartListeners(partId, htmlElement, options); - /* Core dragDrop from ActorDocument is always only 1. Possible we could refactor our own */ - if (Array.isArray(this._dragDrop)) this._dragDrop.forEach(d => d.bind(htmlElement)); - // Handle delta inputs for (const deltaInput of htmlElement.querySelectorAll('input[data-allow-delta]')) { deltaInput.dataset.numValue = deltaInput.value; @@ -289,6 +284,16 @@ export default function DHApplicationMixin(Base) { async _onRender(context, options) { await super._onRender(context, options); this._createTagifyElements(this.options.tagifyConfigs); + + for (const d of this.options.dragDrop) { + new foundry.applications.ux.DragDrop.implementation({ + ...d, + callbacks: { + dragstart: this._onDragStart.bind(this), + drop: this._onDrop.bind(this) + } + }).bind(this.element); + } } /* -------------------------------------------- */ @@ -349,26 +354,6 @@ export default function DHApplicationMixin(Base) { /* Drag and Drop */ /* -------------------------------------------- */ - /** - * Creates drag-drop handlers from the configured options. - * @returns {foundry.applications.ux.DragDrop[]} - * @private - */ - _setupDragDrop() { - if (this._dragDrop) { - this._dragDrop.callbacks.dragStart = this._onDragStart; - this._dragDrop.callback.drop = this._onDrop; - } else { - this._dragDrop = this.options.dragDrop.map(d => { - d.callbacks = { - dragstart: this._onDragStart.bind(this), - drop: this._onDrop.bind(this) - }; - return new foundry.applications.ux.DragDrop.implementation(d); - }); - } - } - /** * Handle dragStart event. * @param {DragEvent} event diff --git a/module/applications/sheets/api/base-actor.mjs b/module/applications/sheets/api/base-actor.mjs index 4b0fd7d9..4a550d72 100644 --- a/module/applications/sheets/api/base-actor.mjs +++ b/module/applications/sheets/api/base-actor.mjs @@ -228,7 +228,6 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) { 'systems/daggerheart/templates/ui/chat/action.hbs', systemData ), - title: game.i18n.localize('DAGGERHEART.ACTIONS.Config.displayInChat'), speaker: cls.getSpeaker(), flags: { daggerheart: { diff --git a/module/applications/sidebar/tabs/daggerheartMenu.mjs b/module/applications/sidebar/tabs/daggerheartMenu.mjs index 26ae484b..86c1b8cb 100644 --- a/module/applications/sidebar/tabs/daggerheartMenu.mjs +++ b/module/applications/sidebar/tabs/daggerheartMenu.mjs @@ -31,7 +31,8 @@ export default class DaggerheartMenu extends HandlebarsApplicationMixin(Abstract }, actions: { selectRefreshable: DaggerheartMenu.#selectRefreshable, - refreshActors: DaggerheartMenu.#refreshActors + refreshActors: DaggerheartMenu.#refreshActors, + createFallCollisionDamage: DaggerheartMenu.#createFallCollisionDamage } }; @@ -50,6 +51,7 @@ export default class DaggerheartMenu extends HandlebarsApplicationMixin(Abstract const context = await super._prepareContext(options); context.refreshables = this.refreshSelections; context.disableRefresh = Object.values(this.refreshSelections).every(x => !x.selected); + context.fallAndCollision = CONFIG.DH.GENERAL.fallAndCollisionDamage; return context; } @@ -71,4 +73,22 @@ export default class DaggerheartMenu extends HandlebarsApplicationMixin(Abstract this.refreshSelections = DaggerheartMenu.defaultRefreshSelections(); this.render(); } + + static async #createFallCollisionDamage(_event, button) { + const data = CONFIG.DH.GENERAL.fallAndCollisionDamage[button.dataset.key]; + const roll = new Roll(data.damageFormula); + await roll.evaluate(); + + /* class BaseRoll needed to get rendered by foundryRoll.hbs */ + const rollJSON = roll.toJSON(); + rollJSON.class = 'BaseRoll'; + + foundry.documents.ChatMessage.implementation.create({ + title: game.i18n.localize(data.chatTitle), + author: game.user.id, + speaker: foundry.documents.ChatMessage.implementation.getSpeaker(), + rolls: [rollJSON], + sound: CONFIG.sounds.dice + }); + } } diff --git a/module/applications/ui/chatLog.mjs b/module/applications/ui/chatLog.mjs index e29498e6..8cbacb09 100644 --- a/module/applications/ui/chatLog.mjs +++ b/module/applications/ui/chatLog.mjs @@ -1,5 +1,8 @@ import { abilities } from '../../config/actorConfig.mjs'; -import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs'; +import { enrichedDualityRoll } from '../../enrichers/DualityRollEnricher.mjs'; +import { enrichedFateRoll, getFateTypeData } from '../../enrichers/FateRollEnricher.mjs'; +import { getCommandTarget, rollCommandToJSON } from '../../helpers/utils.mjs'; +import { emitAsGM, GMUpdateEvent } from '../../systemRegistration/socket.mjs'; export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLog { constructor(options) { @@ -21,6 +24,84 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo classes: ['daggerheart'] }; + static CHAT_COMMANDS = { + ...super.CHAT_COMMANDS, + dr: { + rgx: /^(?:\/dr)((?:\s)[^]*)?/, + fn: (_, match) => { + const argString = match[1]?.trim(); + const result = argString ? rollCommandToJSON(argString) : { result: {} }; + if (!result) { + ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.dualityParsing')); + return false; + } + + const { result: rollCommand, flavor } = result; + + const reaction = rollCommand.reaction; + const traitValue = rollCommand.trait?.toLowerCase(); + const advantage = rollCommand.advantage + ? CONFIG.DH.ACTIONS.advantageState.advantage.value + : rollCommand.disadvantage + ? CONFIG.DH.ACTIONS.advantageState.disadvantage.value + : undefined; + const difficulty = rollCommand.difficulty; + const grantResources = rollCommand.grantResources; + + const target = getCommandTarget({ allowNull: true }); + const title = + (flavor ?? traitValue) + ? game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', { + ability: game.i18n.localize(SYSTEM.ACTOR.abilities[traitValue].label) + }) + : game.i18n.localize('DAGGERHEART.GENERAL.duality'); + + enrichedDualityRoll({ + reaction, + traitValue, + target, + difficulty, + title, + label: game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll'), + actionType: null, + advantage, + grantResources + }); + return false; + } + }, + fr: { + rgx: /^(?:\/fr)((?:\s)[^]*)?/, + fn: (_, match) => { + const argString = match[1]?.trim(); + const result = argString ? rollCommandToJSON(argString) : { result: {} }; + + if (!result) { + ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.fateParsing')); + return false; + } + + const { result: rollCommand, flavor } = result; + const fateTypeData = getFateTypeData(rollCommand?.type); + + if (!fateTypeData) + return ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.fateTypeParsing')); + + const { value: fateType, label: fateTypeLabel } = fateTypeData; + const target = getCommandTarget({ allowNull: true }); + const title = flavor ?? game.i18n.localize('DAGGERHEART.GENERAL.fateRoll'); + + enrichedFateRoll({ + target, + title, + label: fateTypeLabel, + fateType + }); + return false; + } + } + }; + _getEntryContextOptions() { return [ ...super._getEntryContextOptions(), @@ -175,7 +256,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo action.use(event); } - async rerollEvent(event, message) { + async rerollEvent(event, messageData) { event.stopPropagation(); if (!event.shiftKey) { const confirmed = await foundry.applications.api.DialogV2.confirm({ @@ -187,6 +268,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo if (!confirmed) return; } + const message = game.messages.get(messageData._id); const target = event.target.closest('[data-die-index]'); if (target.dataset.type === 'damage') { @@ -209,27 +291,16 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo } }); } else { - let originalRoll_parsed = message.rolls.map(roll => JSON.parse(roll))[0]; - const rollClass = - game.system.api.dice[ - message.type === 'dualityRoll' - ? 'DualityRoll' - : target.dataset.type === 'damage' - ? 'DHRoll' - : 'D20Roll' - ]; - - if (!game.modules.get('dice-so-nice')?.active) foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice }); - - const { newRoll, parsedRoll } = await rollClass.reroll( - originalRoll_parsed, - target.dataset.dieIndex, - target.dataset.type - ); - - await game.messages.get(message._id).update({ - 'system.roll': newRoll, - 'rolls': [parsedRoll] + const rerollDice = message.system.roll.dice[target.dataset.dieIndex]; + await rerollDice.reroll(`/r1=${rerollDice.total}`, { + liveRoll: { + roll: message.system.roll, + actor: message.system.actionActor, + isReaction: message.system.roll.options.actionType === 'reaction' + } + }); + await message.update({ + rolls: [message.system.roll.toJSON()] }); } } diff --git a/module/applications/ui/combatTracker.mjs b/module/applications/ui/combatTracker.mjs index 345c6fcd..1043e128 100644 --- a/module/applications/ui/combatTracker.mjs +++ b/module/applications/ui/combatTracker.mjs @@ -1,5 +1,6 @@ import { AdversaryBPPerEncounter } from '../../config/encounterConfig.mjs'; import { expireActiveEffects } from '../../helpers/utils.mjs'; +import { clearPreviousSpotlight } from '../../macros/spotlightCombatant.mjs'; export default class DhCombatTracker extends foundry.applications.sidebar.tabs.CombatTracker { static DEFAULT_OPTIONS = { @@ -150,13 +151,13 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C } async setCombatantSpotlight(combatantId) { + const combatant = this.viewed.combatants.get(combatantId); const update = { system: { 'spotlight.requesting': false, 'spotlight.requestOrderIndex': 0 } }; - const combatant = this.viewed.combatants.get(combatantId); const toggleTurn = this.viewed.combatants.contents .sort(this.viewed._sortCombatants) @@ -187,6 +188,14 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C round: this.viewed.round + 1 }); await combatant.update(update); + if (combatant.token) clearPreviousSpotlight(); + } + + async clearTurn() { + await this.viewed.update({ + turn: null, + round: this.viewed.round + 1 + }); } static async requestSpotlight(_, target) { diff --git a/module/canvas/placeables/token.mjs b/module/canvas/placeables/token.mjs index 83fe3f41..c025b880 100644 --- a/module/canvas/placeables/token.mjs +++ b/module/canvas/placeables/token.mjs @@ -10,6 +10,36 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token { this.previewHelp ||= this.addChild(this.#drawPreviewHelp()); } + /**@inheritdoc */ + _refreshTurnMarker() { + // Should a Turn Marker be active? + const { turnMarker } = this.document; + const markersEnabled = + CONFIG.Combat.settings.turnMarker.enabled && turnMarker.mode !== CONST.TOKEN_TURN_MARKER_MODES.DISABLED; + const spotlighted = game.settings + .get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.SpotlightTracker) + .spotlightedTokens.has(this.document.uuid); + + const turnIsSet = typeof game.combat?.turn === 'number'; + const isTurn = game.combat?.combatant?.tokenId === this.id; + const markerActive = markersEnabled && turnIsSet ? isTurn : spotlighted; + + // Activate a Turn Marker + if (markerActive) { + if (!this.turnMarker) + this.turnMarker = this.addChildAt(new foundry.canvas.placeables.tokens.TokenTurnMarker(this), 0); + canvas.tokens.turnMarkers.add(this); + this.turnMarker.draw(); + } + + // Remove a Turn Marker + else if (this.turnMarker) { + canvas.tokens.turnMarkers.delete(this); + this.turnMarker.destroy(); + this.turnMarker = null; + } + } + /** @inheritDoc */ async _drawEffects() { this.effects.renderable = false; diff --git a/module/config/generalConfig.mjs b/module/config/generalConfig.mjs index 7c7c2668..3fd9d22d 100644 --- a/module/config/generalConfig.mjs +++ b/module/config/generalConfig.mjs @@ -1102,3 +1102,29 @@ export const comparator = { label: 'DAGGERHEART.CONFIG.Comparator.lte' } }; +export const fallAndCollisionDamage = { + veryClose: { + id: 'veryClose', + label: 'DAGGERHEART.CONFIG.fallAndCollision.veryClose.label', + chatTitle: 'DAGGERHEART.CONFIG.fallAndCollision.veryClose.chatTitle', + damageFormula: '1d10 + 3' + }, + close: { + id: 'veryClose', + label: 'DAGGERHEART.CONFIG.fallAndCollision.close.label', + chatTitle: 'DAGGERHEART.CONFIG.fallAndCollision.close.chatTitle', + damageFormula: '1d20 + 5' + }, + far: { + id: 'veryClose', + label: 'DAGGERHEART.CONFIG.fallAndCollision.far.label', + chatTitle: 'DAGGERHEART.CONFIG.fallAndCollision.far.chatTitle', + damageFormula: '1d100 + 15' + }, + collision: { + id: 'veryClose', + label: 'DAGGERHEART.CONFIG.fallAndCollision.collision.label', + chatTitle: 'DAGGERHEART.CONFIG.fallAndCollision.collision.chatTitle', + damageFormula: '1d20 + 5' + } +}; diff --git a/module/config/settingsConfig.mjs b/module/config/settingsConfig.mjs index 52a316cf..de4d96be 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', @@ -35,7 +39,8 @@ export const gameSettings = { Countdowns: 'Countdowns', LastMigrationVersion: 'LastMigrationVersion', SpotlightRequestQueue: 'SpotlightRequestQueue', - CompendiumBrowserSettings: 'CompendiumBrowserSettings' + CompendiumBrowserSettings: 'CompendiumBrowserSettings', + SpotlightTracker: 'SpotlightTracker' }; export const actionAutomationChoices = { diff --git a/module/data/_module.mjs b/module/data/_module.mjs index 43ff7807..0e7e295e 100644 --- a/module/data/_module.mjs +++ b/module/data/_module.mjs @@ -4,6 +4,7 @@ export { default as DhRollTable } from './rollTable.mjs'; export { default as RegisteredTriggers } from './registeredTriggers.mjs'; export { default as CompendiumBrowserSettings } from './compendiumBrowserSettings.mjs'; export { default as TagTeamData } from './tagTeamData.mjs'; +export { default as SpotlightTracker } from './spotlightTracker.mjs'; export * as countdowns from './countdowns.mjs'; export * as actions from './action/_module.mjs'; diff --git a/module/data/activeEffect/baseEffect.mjs b/module/data/activeEffect/baseEffect.mjs index 4296874c..4546a3ee 100644 --- a/module/data/activeEffect/baseEffect.mjs +++ b/module/data/activeEffect/baseEffect.mjs @@ -166,12 +166,11 @@ export default class BaseEffect extends foundry.data.ActiveEffectTypeDataModel { this.parent.actor?.type === 'character' && this.parent.actor.system.resources.armor ) { - const newArmorTotal = (changed.system?.changes ?? []).reduce((acc, change) => { - if (change.type === 'armor') acc += change.value.current; - return acc; - }, this.parent.actor.system.armor?.system?.armor?.current ?? 0); + const armorEffect = changed.system?.changes?.find(x => x.type === 'armor'); + const newArmorTotal = + armorEffect?.value?.current + (this.parent.actor.system.armor?.system?.armor?.current ?? 0); - if (newArmorTotal !== this.parent.actor.system.armorScore.value) { + if (armorEffect && newArmorTotal !== this.parent.actor.system.armorScore.value) { const armorData = getScrollTextData(this.parent.actor, { value: newArmorTotal }, 'armor'); options.scrollingTextData = [armorData]; } diff --git a/module/data/activeEffect/beastformEffect.mjs b/module/data/activeEffect/beastformEffect.mjs index 47e28b4c..128c0c52 100644 --- a/module/data/activeEffect/beastformEffect.mjs +++ b/module/data/activeEffect/beastformEffect.mjs @@ -25,7 +25,7 @@ export default class BeastformEffect extends BaseEffect { width: new fields.NumberField({ integer: false, nullable: true }) }) }), - advantageOn: new fields.ArrayField(new fields.StringField()), + advantageOn: new fields.TypedObjectField(new fields.SchemaField({ value: new fields.StringField() })), featureIds: new fields.ArrayField(new fields.StringField()), effectIds: new fields.ArrayField(new fields.StringField()) }; 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/chat-message/actorRoll.mjs b/module/data/chat-message/actorRoll.mjs index 1ea7ff93..e601f86d 100644 --- a/module/data/chat-message/actorRoll.mjs +++ b/module/data/chat-message/actorRoll.mjs @@ -32,7 +32,6 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel { return { title: new fields.StringField(), actionDescription: new fields.HTMLField(), - roll: new fields.ObjectField(), targets: targetsField(), hasRoll: new fields.BooleanField({ initial: false }), hasDamage: new fields.BooleanField({ initial: false }), @@ -55,6 +54,16 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel { }; } + get roll() { + if (this.parent.type === 'dualityRoll') + return this.parent.rolls.find(x => x instanceof game.system.api.dice.DualityRoll); + + if (this.parent.type === 'fateRoll') + return this.parent.rolls.find(x => x instanceof game.system.api.dice.FateRoll); + + return null; + } + get actionActor() { if (!this.source.actor) return null; return fromUuidSync(this.source.actor); diff --git a/module/data/fields/action/beastformField.mjs b/module/data/fields/action/beastformField.mjs index e19807c7..e3be9937 100644 --- a/module/data/fields/action/beastformField.mjs +++ b/module/data/fields/action/beastformField.mjs @@ -28,8 +28,21 @@ export default class BeastformField extends fields.SchemaField { { 1: game.i18n.localize('DAGGERHEART.GENERAL.Tiers.1') } ); }, - hint: 'DAGGERHEART.ACTIONS.Config.beastform.exactHint' + label: 'DAGGERHEART.ACTIONS.Config.beastform.exact.label', + hint: 'DAGGERHEART.ACTIONS.Config.beastform.exact.hint' }) + }), + modifications: new fields.SchemaField({ + traitBonuses: new fields.ArrayField( + new fields.SchemaField({ + bonus: new fields.NumberField({ + integer: true, + initial: 1, + min: 1, + label: 'DAGGERHEART.ACTIONS.Config.beastform.modifications.traitBonuses.bonus' + }) + }) + ) }) }; super(beastformFields, options, context); @@ -66,15 +79,9 @@ export default class BeastformField extends fields.SchemaField { ) ?? 1; config.tierLimit = this.beastform.tierAccess.exact ?? actorTier; + config.modifications = this.beastform.modifications; } - /** - * TODO by Harry - * @param {*} selectedForm - * @param {*} evolvedData - * @param {*} hybridData - * @returns - */ static async transform(selectedForm, evolvedData, hybridData) { const formData = evolvedData?.form ?? selectedForm; const beastformEffect = formData.effects.find(x => x.type === 'beastform'); diff --git a/module/data/item/beastform.mjs b/module/data/item/beastform.mjs index 2792f7e3..d30d6046 100644 --- a/module/data/item/beastform.mjs +++ b/module/data/item/beastform.mjs @@ -99,10 +99,14 @@ export default class DHBeastform extends BaseDataItem { get beastformAttackData() { const effect = this.parent.effects.find(x => x.type === 'beastform'); + return DHBeastform.getBeastformAttackData(effect); + } + + static getBeastformAttackData(effect) { if (!effect) return null; - const traitBonus = - effect.system.changes.find(x => x.key === `system.traits.${this.mainTrait}.value`)?.value ?? 0; + const mainTrait = effect.system.changes.find(x => x.key === 'system.rules.attack.roll.trait')?.value; + const traitBonus = effect.system.changes.find(x => x.key === `system.traits.${mainTrait}.value`)?.value ?? 0; const evasionBonus = effect.system.changes.find(x => x.key === 'system.evasion')?.value ?? 0; const damageDiceIndex = effect.system.changes.find(x => x.key === 'system.rules.attack.damage.diceIndex'); @@ -110,7 +114,7 @@ export default class DHBeastform extends BaseDataItem { const damageBonus = effect.system.changes.find(x => x.key === 'system.rules.attack.damage.bonus')?.value ?? 0; return { - trait: game.i18n.localize(CONFIG.DH.ACTOR.abilities[this.mainTrait].label), + trait: game.i18n.localize(CONFIG.DH.ACTOR.abilities[mainTrait]?.label), traitBonus: traitBonus ? Number(traitBonus).signedString() : '', evasionBonus: evasionBonus ? Number(evasionBonus).signedString() : '', damageDice: damageDice, diff --git a/module/data/spotlightTracker.mjs b/module/data/spotlightTracker.mjs new file mode 100644 index 00000000..57f54e16 --- /dev/null +++ b/module/data/spotlightTracker.mjs @@ -0,0 +1,9 @@ +export default class SpotlightTracker extends foundry.abstract.DataModel { + static defineSchema() { + const fields = foundry.data.fields; + + return { + spotlightedTokens: new fields.SetField(new fields.DocumentUUIDField()) + }; + } +} diff --git a/module/dice/_module.mjs b/module/dice/_module.mjs index b9339d87..e1206f82 100644 --- a/module/dice/_module.mjs +++ b/module/dice/_module.mjs @@ -4,3 +4,4 @@ export { default as DamageRoll } from './damageRoll.mjs'; export { default as DHRoll } from './dhRoll.mjs'; export { default as DualityRoll } from './dualityRoll.mjs'; export { default as FateRoll } from './fateRoll.mjs'; +export { diceTypes } from './die/_module.mjs'; diff --git a/module/dice/d20Roll.mjs b/module/dice/d20Roll.mjs index f117ff65..49db18cb 100644 --- a/module/dice/d20Roll.mjs +++ b/module/dice/d20Roll.mjs @@ -217,49 +217,11 @@ export default class D20Roll extends DHRoll { results: d.results }; }); - data.modifierTotal = this.calculateTotalModifiers(roll); + data.modifierTotal = roll.modifierTotal; return data; } resetFormula() { return (this._formula = this.constructor.getFormula(this.terms)); } - - static async reroll(rollString, _target, message) { - let parsedRoll = game.system.api.dice.D20Roll.fromData(rollString); - parsedRoll = await parsedRoll.reroll(); - const newRoll = game.system.api.dice.D20Roll.postEvaluate(parsedRoll, { - targets: message.system.targets, - roll: { - advantage: message.system.roll.advantage?.type, - difficulty: message.system.roll.difficulty ? Number(message.system.roll.difficulty) : null - } - }); - - if (game.modules.get('dice-so-nice')?.active) { - await game.dice3d.showForRoll(parsedRoll, game.user, true); - } - - const rerolled = { - any: true, - rerolls: [ - ...(message.system.roll.dice[0].rerolled?.rerolls?.length > 0 - ? [message.system.roll.dice[0].rerolled?.rerolls] - : []), - rollString.terms[0].results - ] - }; - return { - newRoll: { - ...newRoll, - dice: [ - { - ...newRoll.dice[0], - rerolled: rerolled - } - ] - }, - parsedRoll - }; - } } diff --git a/module/dice/dhRoll.mjs b/module/dice/dhRoll.mjs index a5d95cd1..e1ef16f2 100644 --- a/module/dice/dhRoll.mjs +++ b/module/dice/dhRoll.mjs @@ -12,6 +12,10 @@ export default class DHRoll extends Roll { return game.i18n.localize('DAGGERHEART.GENERAL.Roll.basic'); } + get modifierTotal() { + return this.constructor.calculateTotalModifiers(this); + } + static messageType = 'adversaryRoll'; static CHAT_TEMPLATE = 'systems/daggerheart/templates/ui/chat/roll.hbs'; @@ -122,10 +126,6 @@ export default class DHRoll extends Roll { if (roll._evaluated) { const message = await cls.create(msgData, { messageMode: config.selectedMessageMode }); - if (config.tagTeamSelected) { - game.system.api.applications.dialogs.TagTeamDialog.assignRoll(message.speakerActor, message); - } - if (roll.formula !== '' && game.modules.get('dice-so-nice')?.active) { await game.dice3d.waitFor3DAnimationByMessageID(message.id); } @@ -142,6 +142,7 @@ export default class DHRoll extends Roll { const chatData = await this._prepareChatRenderContext({ flavor, isPrivate, ...options }); return foundry.applications.handlebars.renderTemplate(template, { ...chatData, + roll: this, parent: chatData.parent, targetMode: chatData.targetMode, metagamingSettings @@ -245,16 +246,21 @@ export default class DHRoll extends Roll { return (this._formula = this.constructor.getFormula(this.terms)); } + /** + * Calculate total modifiers of any rolls, including non-dh rolls. + * This exists because damage rolls still may receive base roll classes + */ static calculateTotalModifiers(roll) { let modifierTotal = 0; for (let i = 0; i < roll.terms.length; i++) { - if ( - roll.terms[i] instanceof foundry.dice.terms.NumericTerm && - !!roll.terms[i - 1] && - roll.terms[i - 1] instanceof foundry.dice.terms.OperatorTerm - ) - modifierTotal += Number(`${roll.terms[i - 1].operator}${roll.terms[i].total}`); + if (!roll.terms[i].isDeterministic) continue; + const termTotal = roll.terms[i].total; + if (typeof termTotal === 'number') { + const multiplier = roll.terms[i - 1]?.operator === ' - ' ? -1 : 1; + modifierTotal += multiplier * termTotal; + } } + return modifierTotal; } diff --git a/module/dice/die/_module.mjs b/module/dice/die/_module.mjs new file mode 100644 index 00000000..ed892f6a --- /dev/null +++ b/module/dice/die/_module.mjs @@ -0,0 +1,9 @@ +import DualityDie from './dualityDie.mjs'; +import AdvantageDie from './advantageDie.mjs'; +import DisadvantageDie from './disadvantageDie.mjs'; + +export const diceTypes = { + DualityDie, + AdvantageDie, + DisadvantageDie +}; diff --git a/module/dice/die/advantageDie.mjs b/module/dice/die/advantageDie.mjs new file mode 100644 index 00000000..9c2f0b03 --- /dev/null +++ b/module/dice/die/advantageDie.mjs @@ -0,0 +1,7 @@ +export default class AdvantageDie extends foundry.dice.terms.Die { + constructor(options) { + super(options); + + this.modifiers = []; + } +} diff --git a/module/dice/die/disadvantageDie.mjs b/module/dice/die/disadvantageDie.mjs new file mode 100644 index 00000000..f56ebe96 --- /dev/null +++ b/module/dice/die/disadvantageDie.mjs @@ -0,0 +1,7 @@ +export default class DisadvantageDie extends foundry.dice.terms.Die { + constructor(options) { + super(options); + + this.modifiers = []; + } +} diff --git a/module/dice/die/dualityDie.mjs b/module/dice/die/dualityDie.mjs new file mode 100644 index 00000000..e9deb77f --- /dev/null +++ b/module/dice/die/dualityDie.mjs @@ -0,0 +1,62 @@ +import { ResourceUpdateMap } from '../../data/action/baseAction.mjs'; + +export default class DualityDie extends foundry.dice.terms.Die { + constructor(options) { + super(options); + + this.modifiers = []; + } + + #getDualityState(roll) { + if (!roll) return null; + return roll.withHope ? 1 : roll.withFear ? -1 : 0; + } + + #updateResources(oldDuality, newDuality, actor) { + const { hopeFear } = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation); + if (game.user.isGM ? !hopeFear.gm : !hopeFear.players) return; + + const updates = []; + const hope = (newDuality >= 0 ? 1 : 0) - (oldDuality >= 0 ? 1 : 0); + const stress = (newDuality === 0 ? 1 : 0) - (oldDuality === 0 ? 1 : 0); + const fear = (newDuality === -1 ? 1 : 0) - (oldDuality === -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 }); + + const resourceUpdates = new ResourceUpdateMap(actor); + resourceUpdates.addResources(updates); + resourceUpdates.updateResources(); + } + + async reroll(modifier, options) { + const oldDuality = this.#getDualityState(options.liveRoll.roll); + await super.reroll(modifier, options); + + if (options?.liveRoll) { + /* Can't currently test since DiceSoNice is not v14. Might need to set the appearance earlier if a roll is triggered by super.reroll */ + if (game.modules.get('dice-so-nice')?.active) { + const diceSoNiceRoll = { + _evaluated: true, + dice: [this], + options: { appearance: {} } + }; + + const preset = await getDiceSoNicePreset(diceSoNice[key], faces); + diceSoNiceRoll.dice[0].options.appearance = preset.appearance; + diceSoNiceRoll.dice[0].options.modelFile = preset.modelFile; + + await game.dice3d.showForRoll(diceSoNiceRoll, game.user, true); + } else { + foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice }); + } + + await options.liveRoll.roll._evaluate(); + if (options.liveRoll.isReaction) return; + + const newDuality = this.#getDualityState(options.liveRoll.roll); + this.#updateResources(oldDuality, newDuality, options.liveRoll.actor); + } + } +} diff --git a/module/dice/dualityRoll.mjs b/module/dice/dualityRoll.mjs index 03035f68..bc381f07 100644 --- a/module/dice/dualityRoll.mjs +++ b/module/dice/dualityRoll.mjs @@ -1,8 +1,6 @@ import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs'; import D20Roll from './d20Roll.mjs'; import { parseRallyDice, setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs'; -import { getDiceSoNicePresets } from '../config/generalConfig.mjs'; -import { ResourceUpdateMap } from '../data/action/baseAction.mjs'; export default class DualityRoll extends D20Roll { _advantageFaces = 6; @@ -26,27 +24,31 @@ export default class DualityRoll extends D20Roll { } get dHope() { - if (!(this.dice[0] instanceof foundry.dice.terms.Die)) this.createBaseDice(); + if (!(this.dice[0] instanceof game.system.api.dice.diceTypes.DualityDie)) this.createBaseDice(); return this.dice[0]; } set dHope(faces) { - if (!(this.dice[0] instanceof foundry.dice.terms.Die)) this.createBaseDice(); - this.dice[0].faces = this.getFaces(faces); + // TODO this should not be asymmetrical with the getter. updateRollConfiguration() should use dHope.faces + this.dHope.faces = this.getFaces(faces); } get dFear() { - if (!(this.dice[1] instanceof foundry.dice.terms.Die)) this.createBaseDice(); + if (!(this.dice[1] instanceof game.system.api.dice.diceTypes.DualityDie)) this.createBaseDice(); return this.dice[1]; } set dFear(faces) { - if (!(this.dice[1] instanceof foundry.dice.terms.Die)) this.createBaseDice(); - this.dice[1].faces = this.getFaces(faces); + // TODO this should not be asymmetrical with the getter. updateRollConfiguration() should use dFear.faces + this.dFear.faces = this.getFaces(faces); } get dAdvantage() { - return this.dice[2]; + return this.dice[2] instanceof game.system.api.dice.diceTypes.AdvantageDie ? this.dice[2] : null; + } + + get dDisadvantage() { + return this.dice[2] instanceof game.system.api.dice.diceTypes.DisadvantageDie ? this.dice[2] : null; } get advantageFaces() { @@ -65,6 +67,11 @@ export default class DualityRoll extends D20Roll { this._advantageNumber = Number(value); } + get extraDice() { + const { DualityDie, AdvantageDie, DisadvantageDie } = game.system.api.dice.diceTypes; + return this.dice.filter(x => ![DualityDie, AdvantageDie, DisadvantageDie].some(die => x instanceof die)); + } + setRallyChoices() { return this.data?.parent?.appliedEffects.reduce((a, c) => { const change = c.system.changes.find(ch => ch.key === 'system.bonuses.rally'); @@ -118,22 +125,28 @@ export default class DualityRoll extends D20Roll { /** @inheritDoc */ static fromData(data) { - data.terms[0].class = foundry.dice.terms.Die.name; - data.terms[2].class = foundry.dice.terms.Die.name; + data.terms[0].class = 'DualityDie'; + data.terms[2].class = 'DualityDie'; + if (data.options.roll.advantage?.type && data.terms[4]?.faces) { + data.terms[4].class = data.options.roll.advantage.type === 1 ? 'AdvantageDie' : 'DisadvantageDie'; + } return super.fromData(data); } createBaseDice() { - if (this.dice[0] instanceof foundry.dice.terms.Die && this.dice[1] instanceof foundry.dice.terms.Die) { + if ( + this.dice[0] instanceof game.system.api.dice.diceTypes.DualityDie && + this.dice[1] instanceof game.system.api.dice.diceTypes.DualityDie + ) { this.terms = [this.terms[0], this.terms[1], this.terms[2]]; return; } - this.terms[0] = new foundry.dice.terms.Die({ + this.terms[0] = new game.system.api.dice.diceTypes.DualityDie({ faces: this.data.rules.dualityRoll?.defaultHopeDice ?? 12 }); this.terms[1] = new foundry.dice.terms.OperatorTerm({ operator: '+' }); - this.terms[2] = new foundry.dice.terms.Die({ + this.terms[2] = new game.system.api.dice.diceTypes.DualityDie({ faces: this.data.rules.dualityRoll?.defaultFearDice ?? 12 }); } @@ -305,7 +318,6 @@ export default class DualityRoll extends D20Roll { !config.source?.actor || (game.user.isGM ? !hopeFearAutomation.gm : !hopeFearAutomation.players) || config.actionType === 'reaction' || - config.tagTeamSelected || config.skips?.resources ) return; @@ -346,7 +358,6 @@ export default class DualityRoll extends D20Roll { if ( automationSettings.countdownAutomation && config.actionType !== 'reaction' && - !config.tagTeamSelected && !config.skips?.updateCountdowns ) { const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns; @@ -373,61 +384,4 @@ export default class DualityRoll extends D20Roll { if (currentCombatant?.actorId == config.data.id) ui.combat.setCombatantSpotlight(currentCombatant.id); } } - - static async reroll(rollBase, dieIndex, diceType) { - let parsedRoll = game.system.api.dice.DualityRoll.fromData({ ...rollBase, evaluated: false }); - const term = parsedRoll.terms[dieIndex]; - await term.reroll(`/r1=${term.total}`); - const result = await parsedRoll.evaluate(); - - if (game.modules.get('dice-so-nice')?.active) { - const diceSoNiceRoll = { - _evaluated: true, - dice: [ - new foundry.dice.terms.Die({ - ...term, - faces: term._faces, - results: term.results.filter(x => !x.rerolled) - }) - ], - options: { appearance: {} } - }; - - const diceSoNicePresets = await getDiceSoNicePresets(`d${term._faces}`, `d${term._faces}`); - if (diceSoNicePresets[diceType]) { - diceSoNiceRoll.dice[0].options = diceSoNicePresets[diceType]; - } - - await game.dice3d.showForRoll(diceSoNiceRoll, game.user, true); - } else { - foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice }); - } - - const newRoll = game.system.api.dice.DualityRoll.postEvaluate(parsedRoll, { - targets: parsedRoll.options.targets ?? [], - roll: { - advantage: parsedRoll.options.roll.advantage?.type, - difficulty: parsedRoll.options.roll.difficulty ? Number(parsedRoll.options.roll.difficulty) : null - } - }); - - const extraIndex = newRoll.advantage ? 3 : 2; - newRoll.extra = newRoll.extra.slice(extraIndex); - - const actor = parsedRoll.options.source.actor - ? await foundry.utils.fromUuid(parsedRoll.options.source.actor) - : null; - const config = { - source: { actor: parsedRoll.options.source.actor ?? '' }, - targets: parsedRoll.targets, - roll: newRoll, - rerolledRoll: parsedRoll.roll, - resourceUpdates: new ResourceUpdateMap(actor) - }; - - await DualityRoll.addDualityResourceUpdates(config); - await config.resourceUpdates.updateResources(); - - return { newRoll, parsedRoll }; - } } diff --git a/module/dice/fateRoll.mjs b/module/dice/fateRoll.mjs index 418c8465..114fad59 100644 --- a/module/dice/fateRoll.mjs +++ b/module/dice/fateRoll.mjs @@ -21,8 +21,8 @@ export default class FateRoll extends D20Roll { } set dHope(faces) { - if (!(this.dice[0] instanceof foundry.dice.terms.Die)) this.createBaseDice(); - this.dice[0].faces = this.getFaces(faces); + // TODO this should not be asymmetrical with the getter. updateRollConfiguration() should use dHope.faces + this.dHope.faces = this.getFaces(faces); } get dFear() { @@ -31,8 +31,8 @@ export default class FateRoll extends D20Roll { } set dFear(faces) { - if (!(this.dice[0] instanceof foundry.dice.terms.Die)) this.createBaseDice(); - this.dice[0].faces = this.getFaces(faces); + // TODO this should not be asymmetrical with the getter. updateRollConfiguration() should use dFear.faces + this.dFear.faces = this.getFaces(faces); } get isCritical() { @@ -43,6 +43,20 @@ export default class FateRoll extends D20Roll { return this.data.fateType; } + get withHope() { + return this.data.fateType === 'Hope'; + } + + get withFear() { + return this.data.fateType === 'Fear'; + } + + get totalLabel() { + const label = this.withHope ? 'DAGGERHEART.GENERAL.hope' : 'DAGGERHEART.GENERAL.fear'; + + return game.i18n.localize(label); + } + static getHooks(hooks) { return [...(hooks ?? []), 'Fate']; } diff --git a/module/documents/activeEffect.mjs b/module/documents/activeEffect.mjs index c52e4cc0..14286b3c 100644 --- a/module/documents/activeEffect.mjs +++ b/module/documents/activeEffect.mjs @@ -1,5 +1,4 @@ import { itemAbleRollParse } from '../helpers/utils.mjs'; -import { RefreshType } from '../systemRegistration/socket.mjs'; export default class DhActiveEffect extends foundry.documents.ActiveEffect { /* -------------------------------------------- */ @@ -111,37 +110,41 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect { update.img = 'icons/magic/life/heart-cross-blue.webp'; } - const existingEffect = this.actor.effects.find(x => x.origin === data.origin); - const stacks = Boolean(data.system?.stacking); - if (existingEffect && !stacks) return false; + if (this.actor && data.origin) { + const existingEffect = this.actor.effects.find(x => x.origin === data.origin); + const stacks = Boolean(data.system?.stacking); + if (existingEffect && !stacks) return false; - if (existingEffect && stacks) { - const incrementedValue = existingEffect.system.stacking.value + 1; - await existingEffect.update({ - 'system.stacking.value': Math.min(incrementedValue, existingEffect.system.stacking.max ?? Infinity) - }); - return false; + if (existingEffect && stacks) { + const incrementedValue = existingEffect.system.stacking.value + 1; + await existingEffect.update({ + 'system.stacking.value': Math.min(incrementedValue, existingEffect.system.stacking.max ?? Infinity) + }); + return false; + } } - const statuses = Object.keys(data.statuses ?? {}); - const immuneStatuses = - statuses.filter( - status => - this.parent.system.rules?.conditionImmunities && - this.parent.system.rules.conditionImmunities[status] - ) ?? []; - if (immuneStatuses.length > 0) { - update.statuses = statuses.filter(x => !immuneStatuses.includes(x)); - const conditions = CONFIG.DH.GENERAL.conditions(); - const scrollingTexts = immuneStatuses.map(status => ({ - text: game.i18n.format('DAGGERHEART.ACTIVEEFFECT.immuneStatusText', { - status: game.i18n.localize(conditions[status].name) - }) - })); - if (update.statuses.length > 0) { - setTimeout(() => scrollingTexts, 500); - } else { - this.parent.queueScrollText(scrollingTexts); + if (this.parent) { + const statuses = Object.keys(data.statuses ?? {}); + const immuneStatuses = + statuses.filter( + status => + this.parent.system.rules?.conditionImmunities && + this.parent.system.rules.conditionImmunities[status] + ) ?? []; + if (immuneStatuses.length > 0) { + update.statuses = statuses.filter(x => !immuneStatuses.includes(x)); + const conditions = CONFIG.DH.GENERAL.conditions(); + const scrollingTexts = immuneStatuses.map(status => ({ + text: game.i18n.format('DAGGERHEART.ACTIVEEFFECT.immuneStatusText', { + status: game.i18n.localize(conditions[status].name) + }) + })); + if (update.statuses.length > 0) { + setTimeout(() => scrollingTexts, 500); + } else { + this.parent.queueScrollText(scrollingTexts); + } } } @@ -152,20 +155,6 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect { await super._preCreate(data, options, user); } - /** @inheritdoc */ - _onCreate(data, options, userId) { - super._onCreate(data, options, userId); - - Hooks.callAll(RefreshType.EffectsDisplay); - } - - /** @inheritdoc */ - _onDelete(data, options, userId) { - super._onDelete(data, options, userId); - - Hooks.callAll(RefreshType.EffectsDisplay); - } - /* -------------------------------------------- */ /* Methods */ /* -------------------------------------------- */ 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/documents/chatMessage.mjs b/module/documents/chatMessage.mjs index 8b094678..307677bb 100644 --- a/module/documents/chatMessage.mjs +++ b/module/documents/chatMessage.mjs @@ -1,4 +1,4 @@ -import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../systemRegistration/socket.mjs'; +import { emitAsGM, GMUpdateEvent } from '../systemRegistration/socket.mjs'; export default class DhpChatMessage extends foundry.documents.ChatMessage { targetHook = null; @@ -78,25 +78,14 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage { if (this.isContentVisible) { if (this.type === 'dualityRoll') { html.classList.add('duality'); - switch (this.system.roll?.result?.duality) { - case 1: - html.classList.add('hope'); - break; - case -1: - html.classList.add('fear'); - break; - default: - html.classList.add('critical'); - break; - } + if (this.system.roll.withHope) html.classList.add('hope'); + else if (this.system.roll.withFear) html.classList.add('fear'); + else html.classList.add('critical'); } if (this.type === 'fateRoll') { html.classList.add('fate'); - if (this.system.roll?.fate.fateDie == 'Hope') { - html.classList.add('hope'); - } - if (this.system.roll?.fate.fateDie == 'Fear') { - html.classList.add('fear'); + if (this.system.roll?.fateDie) { + html.classList.add(this.system.roll.fateDie.toLowerCase()); } } diff --git a/module/documents/item.mjs b/module/documents/item.mjs index 56048a81..d1a618c7 100644 --- a/module/documents/item.mjs +++ b/module/documents/item.mjs @@ -197,7 +197,6 @@ export default class DHItem extends foundry.documents.Item { actor: item.parent, speaker: cls.getSpeaker(), system: systemData, - title: game.i18n.localize('DAGGERHEART.ACTIONS.Config.displayInChat'), content: await foundry.applications.handlebars.renderTemplate( 'systems/daggerheart/templates/ui/chat/ability-use.hbs', systemData diff --git a/module/documents/token.mjs b/module/documents/token.mjs index 8e810689..4ee7ce05 100644 --- a/module/documents/token.mjs +++ b/module/documents/token.mjs @@ -494,62 +494,4 @@ export default class DHToken extends CONFIG.Token.documentClass { game.system.registeredTriggers.unregisterItemTriggers(this.actor.items); } } - - /* V14 TEMP until foundry fixes: https://discord.com/channels/170995199584108546/1421197211194228907/1467296028700049566 */ - _onRelatedUpdate(update = {}, operation = {}) { - this.#refreshOverrides(operation); - this._prepareBars(); - - // Update tracked Combat resource - const combatant = this.combatant; - if (combatant) { - const isActorUpdate = [this, null, undefined].includes(operation.parent); - const resource = game.combat.settings.resource; - const updates = Array.isArray(update) ? update : [update]; - if (isActorUpdate && resource && updates.some(u => foundry.utils.hasProperty(u.system ?? {}, resource))) { - combatant.updateResource(); - } - ui.combat.render(); - } - - // Trigger redraws on the token - if (this.parent.isView) { - if (this.object?.hasActiveHUD) canvas.tokens.hud.render(); - this.object?.renderFlags.set({ redrawEffects: true }); - for (const key of ['bar1', 'bar2']) { - const name = `${this.object?.objectId}.animate${key.capitalize()}`; - const easing = foundry.canvas.animation.CanvasAnimation.easeInOutCosine; - this.object?.animate({ [key]: this[key] }, { name, easing }); - } - for (const app of foundry.applications.sheets.TokenConfig.instances()) { - app._preview?.updateSource({ delta: this.toObject().delta }, { diff: false, recursive: false }); - app._preview?.object?.renderFlags.set({ refreshBars: true, redrawEffects: true }); - } - } - } - - /* V14 TEMP until foundry fixes: https://discord.com/channels/170995199584108546/1421197211194228907/1467296028700049566 */ - #refreshOverrides(operation) { - if (!this.actor) return; - - const { deepClone, mergeObject, equals, isEmpty } = foundry.utils; - const oldOverrides = deepClone(this._overrides) ?? {}; - const newOverrides = deepClone(this.actor?.tokenOverrides ?? {}, { prune: true }); - if (!equals(oldOverrides, newOverrides)) { - this._overrides = newOverrides; - this.reset(); - - // Send emulated update data to the PlaceableObject - if (!canvas.ready || canvas.scene !== this.scene) return; - const { width, height, depth, ...changes } = mergeObject( - mergeObject(oldOverrides, this, { insertKeys: false, insertValues: false }), - this._overrides - ); - this.object?._onUpdate(changes, {}, game.user.id); - - // Hand off size changes to a secondary handler requiring downstream implementation. - const sizeChanges = deepClone({ width, height, depth }, { prune: true }); - if (!isEmpty(sizeChanges)) this._onOverrideSize(sizeChanges, operation); - } - } } diff --git a/module/documents/tooltipManager.mjs b/module/documents/tooltipManager.mjs index bf107a42..e10dc5fa 100644 --- a/module/documents/tooltipManager.mjs +++ b/module/documents/tooltipManager.mjs @@ -31,12 +31,39 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti this.#bordered = true; let effect = {}; if (element.dataset.uuid) { - const effectData = (await foundry.utils.fromUuid(element.dataset.uuid)).toObject(); + const effectItem = await foundry.utils.fromUuid(element.dataset.uuid); + const effectData = effectItem.toObject(); + effect = { ...effectData, - name: game.i18n.localize(effectData.name), - description: game.i18n.localize(effectData.description ?? effectData.parent.system.description) + name: game.i18n.localize(effectData.name) }; + + if (effectData.type === 'beastform') { + const beastformData = { + features: [], + advantageOn: effectData.system.advantageOn, + beastformAttackData: game.system.api.data.items.DHBeastform.getBeastformAttackData(effectItem) + }; + + const features = effectItem.parent.items.filter(x => effectItem.system.featureIds.includes(x.id)); + for (const feature of features) { + const featureData = feature.toObject(); + featureData.enrichedDescription = await feature.system.getEnrichedDescription(); + beastformData.features.push(featureData); + } + + effect.description = await foundry.applications.handlebars.renderTemplate( + 'systems/daggerheart/templates/ui/tooltip/parts/beastformData.hbs', + { + item: { system: beastformData } + } + ); + } else { + effect.description = game.i18n.localize( + effectData.description ?? effectData.parent.system.description + ); + } } else { const conditions = CONFIG.DH.GENERAL.conditions(); const condition = conditions[element.dataset.condition]; 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..dc8339ac --- /dev/null +++ b/module/macros/spotlightCombatant.mjs @@ -0,0 +1,50 @@ +/** + * Spotlight a token on the canvas. If it is a combatant, run it through combatTracker's spotlight logic. + * @param {TokenDocument} token - The token to spotlight + * @returns {void} + */ +const spotlightCombatantMacro = async token => { + if (!token) + return ui.notifications.error(game.i18n.localize('DAGGERHEART.MACROS.Spotlight.errors.noTokenSelected')); + + const combatantCombat = token.combatant + ? game.combat + : game.combats.find(combat => combat.combatants.some(x => x.token && x.token.id === token.document.id)); + if (combatantCombat) { + const combatant = combatantCombat.combatants.find(x => x.token.id === token.document.id); + if (!combatantCombat.active) { + await combatantCombat.activate(); + if (combatantCombat.combatant?.id !== combatant.id) ui.combat.setCombatantSpotlight(combatant.id); + } else { + ui.combat.setCombatantSpotlight(combatant.id); + } + } else { + if (game.combat) await ui.combat.clearTurn(); + + const spotlightTracker = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.SpotlightTracker); + const isSpotlighted = spotlightTracker.spotlightedTokens.has(token.document.uuid); + if (!isSpotlighted) await clearPreviousSpotlight(); + + spotlightTracker.updateSource({ + spotlightedTokens: isSpotlighted ? [] : [token.document.uuid] + }); + + await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.SpotlightTracker, spotlightTracker); + token.renderFlags.set({ refreshTurnMarker: true }); + } +}; + +export const clearPreviousSpotlight = async () => { + const spotlightTracker = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.SpotlightTracker); + const previouslySpotlightedUuid = + spotlightTracker.spotlightedTokens.size > 0 ? spotlightTracker.spotlightedTokens.first() : null; + if (!previouslySpotlightedUuid) return; + + spotlightTracker.updateSource({ spotlightedTokens: [] }); + await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.SpotlightTracker, spotlightTracker); + + const previousToken = await foundry.utils.fromUuid(previouslySpotlightedUuid); + previousToken.object.renderFlags.set({ refreshTurnMarker: true }); +}; + +export default spotlightCombatantMacro; diff --git a/module/systemRegistration/handlebars.mjs b/module/systemRegistration/handlebars.mjs index 36df8b54..63e591c6 100644 --- a/module/systemRegistration/handlebars.mjs +++ b/module/systemRegistration/handlebars.mjs @@ -36,6 +36,7 @@ export const preloadHandlebarsTemplates = async function () { 'systems/daggerheart/templates/actionTypes/summon.hbs', 'systems/daggerheart/templates/actionTypes/transform.hbs', 'systems/daggerheart/templates/settings/components/settings-item-line.hbs', + 'systems/daggerheart/templates/ui/tooltip/parts/beastformData.hbs', 'systems/daggerheart/templates/ui/tooltip/parts/tooltipChips.hbs', 'systems/daggerheart/templates/ui/tooltip/parts/tooltipTags.hbs', 'systems/daggerheart/templates/dialogs/downtime/activities.hbs', diff --git a/module/systemRegistration/settings.mjs b/module/systemRegistration/settings.mjs index 658d2bd1..63611cda 100644 --- a/module/systemRegistration/settings.mjs +++ b/module/systemRegistration/settings.mjs @@ -16,8 +16,10 @@ import { DhVariantRuleSettings } from '../applications/settings/_module.mjs'; import { CompendiumBrowserSettings } from '../data/_module.mjs'; +import SpotlightTracker from '../data/spotlightTracker.mjs'; export const registerDHSettings = () => { + registerKeyBindings(); registerMenuSettings(); registerMenus(); registerNonConfigSettings(); @@ -33,6 +35,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: [], + onDown: () => { + const selectedTokens = canvas.tokens.controlled.length > 0 ? canvas.tokens.controlled[0] : null; + const hoveredTokens = game.canvas.tokens.hover ? game.canvas.tokens.hover : null; + const tokens = selectedTokens ?? hoveredTokens; + game.system.api.macros.spotlightCombatant(tokens); + }, + onUp: () => {}, + restricted: true, + reservedModifiers: [], + precedence: CONST.KEYBINDING_PRECEDENCE.NORMAL + }); +}; + const registerMenuSettings = () => { game.settings.register(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules, { scope: 'world', @@ -162,4 +183,10 @@ const registerNonConfigSettings = () => { config: false, type: CompendiumBrowserSettings }); + + game.settings.register(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.SpotlightTracker, { + scope: 'world', + config: false, + type: SpotlightTracker + }); }; diff --git a/src/packs/classes/feature_Evolution_6rlxhrRwFaVgq9fe.json b/src/packs/classes/feature_Evolution_6rlxhrRwFaVgq9fe.json index 46380fe8..421063a4 100644 --- a/src/packs/classes/feature_Evolution_6rlxhrRwFaVgq9fe.json +++ b/src/packs/classes/feature_Evolution_6rlxhrRwFaVgq9fe.json @@ -5,7 +5,7 @@ "_id": "6rlxhrRwFaVgq9fe", "img": "icons/magic/nature/wolf-paw-glow-large-orange.webp", "system": { - "description": "

Spend 3 Hope to transform into a Beastform without marking a Stress. When you do, choose one trait to raise by +1 until you drop out of that Beastform.

Note: Toggle one of the Evolution Traits in the effects tab to raise a trait by 1, e.g. Evolution: Agility

", + "description": "

Spend 3 Hope to transform into a Beastform without marking a Stress. When you do, choose one trait to raise by +1 until you drop out of that Beastform.

", "resource": null, "actions": { "bj4m9E8ObFT0xDQ4": { @@ -31,6 +31,13 @@ "beastform": { "tierAccess": { "exact": null + }, + "modifications": { + "traitBonuses": [ + { + "bonus": 1 + } + ] } }, "name": "Beastform", @@ -46,266 +53,7 @@ "artist": "" } }, - "effects": [ - { - "name": "Evolution: Agility", - "type": "base", - "system": { - "rangeDependence": { - "enabled": false, - "type": "withinRange", - "target": "hostile", - "range": "melee" - } - }, - "_id": "vQOqLZAxOltAzsVv", - "img": "icons/magic/nature/wolf-paw-glow-large-orange.webp", - "changes": [ - { - "key": "system.traits.agility.value", - "mode": 2, - "value": "1", - "priority": null - } - ], - "disabled": true, - "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null - }, - "description": "

Toggle this for +1 to Agility when using Evolution. Turn it off when you leave Beastform.

", - "origin": null, - "tint": "#ffffff", - "transfer": true, - "statuses": [], - "sort": 0, - "flags": {}, - "_stats": { - "compendiumSource": null - }, - "_key": "!items.effects!6rlxhrRwFaVgq9fe.vQOqLZAxOltAzsVv" - }, - { - "name": "Evolution: Strength", - "type": "base", - "system": { - "rangeDependence": { - "enabled": false, - "type": "withinRange", - "target": "hostile", - "range": "melee" - } - }, - "_id": "cwEsO1NZpkQHuoTT", - "img": "icons/magic/nature/wolf-paw-glow-large-orange.webp", - "changes": [ - { - "key": "system.traits.strength.value", - "mode": 2, - "value": "1", - "priority": null - } - ], - "disabled": true, - "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null - }, - "description": "

Toggle this for +1 to Strength when using Evolution. Turn it off when you leave Beastform.

", - "origin": null, - "tint": "#ffffff", - "transfer": true, - "statuses": [], - "sort": 0, - "flags": {}, - "_stats": { - "compendiumSource": null - }, - "_key": "!items.effects!6rlxhrRwFaVgq9fe.cwEsO1NZpkQHuoTT" - }, - { - "name": "Evolution: Finesse", - "type": "base", - "system": { - "rangeDependence": { - "enabled": false, - "type": "withinRange", - "target": "hostile", - "range": "melee" - } - }, - "_id": "8P0nwRHNsVnHVPjq", - "img": "icons/magic/nature/wolf-paw-glow-large-orange.webp", - "changes": [ - { - "key": "system.traits.finesse.value", - "mode": 2, - "value": "1", - "priority": null - } - ], - "disabled": true, - "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null - }, - "description": "

Toggle this for +1 to Finesse when using Evolution. Turn it off when you leave Beastform.

", - "origin": null, - "tint": "#ffffff", - "transfer": true, - "statuses": [], - "sort": 0, - "flags": {}, - "_stats": { - "compendiumSource": null - }, - "_key": "!items.effects!6rlxhrRwFaVgq9fe.8P0nwRHNsVnHVPjq" - }, - { - "name": "Evolution: Instinct", - "type": "base", - "system": { - "rangeDependence": { - "enabled": false, - "type": "withinRange", - "target": "hostile", - "range": "melee" - } - }, - "_id": "i2GhNGo5TnGtLuA0", - "img": "icons/magic/nature/wolf-paw-glow-large-orange.webp", - "changes": [ - { - "key": "system.traits.instinct.value", - "mode": 2, - "value": "1", - "priority": null - } - ], - "disabled": true, - "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null - }, - "description": "

Toggle this for +1 to Instinct when using Evolution. Turn it off when you leave Beastform.

", - "origin": null, - "tint": "#ffffff", - "transfer": true, - "statuses": [], - "sort": 0, - "flags": {}, - "_stats": { - "compendiumSource": null - }, - "_key": "!items.effects!6rlxhrRwFaVgq9fe.i2GhNGo5TnGtLuA0" - }, - { - "name": "Evolution: Presence", - "type": "base", - "system": { - "rangeDependence": { - "enabled": false, - "type": "withinRange", - "target": "hostile", - "range": "melee" - } - }, - "_id": "APQF1in1LXjBZh9n", - "img": "icons/magic/nature/wolf-paw-glow-large-orange.webp", - "changes": [ - { - "key": "system.traits.presence.value", - "mode": 2, - "value": "1", - "priority": null - } - ], - "disabled": true, - "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null - }, - "description": "

Toggle this for +1 to Presence when using Evolution. Turn it off when you leave Beastform.

", - "origin": null, - "tint": "#ffffff", - "transfer": true, - "statuses": [], - "sort": 0, - "flags": {}, - "_stats": { - "compendiumSource": null - }, - "_key": "!items.effects!6rlxhrRwFaVgq9fe.APQF1in1LXjBZh9n" - }, - { - "name": "Evolution: Knowledge", - "type": "base", - "system": { - "rangeDependence": { - "enabled": false, - "type": "withinRange", - "target": "hostile", - "range": "melee" - } - }, - "_id": "WwOvGJYJb4d37cOy", - "img": "icons/magic/nature/wolf-paw-glow-large-orange.webp", - "changes": [ - { - "key": "system.traits.knowledge.value", - "mode": 2, - "value": "1", - "priority": null - } - ], - "disabled": true, - "duration": { - "startTime": null, - "combat": null, - "seconds": null, - "rounds": null, - "turns": null, - "startRound": null, - "startTurn": null - }, - "description": "

Toggle this for +1 to Knowledge when using Evolution. Turn it off when you leave Beastform.

", - "origin": null, - "tint": "#ffffff", - "transfer": true, - "statuses": [], - "sort": 0, - "flags": {}, - "_stats": { - "compendiumSource": null - }, - "_key": "!items.effects!6rlxhrRwFaVgq9fe.WwOvGJYJb4d37cOy" - } - ], + "effects": [], "sort": 100000, "ownership": { "default": 0, diff --git a/styles/less/dialog/beastform/sheet.less b/styles/less/dialog/beastform/sheet.less index 9e87f53b..0e1fe746 100644 --- a/styles/less/dialog/beastform/sheet.less +++ b/styles/less/dialog/beastform/sheet.less @@ -204,6 +204,44 @@ } } + .modifications-container { + display: flex; + flex-direction: column; + gap: 16px; + + .trait-bonuses-container { + display: flex; + flex-direction: column; + gap: 8px; + + .bonus-separator { + background: light-dark(@dark-blue, @golden); + mask-image: linear-gradient(270deg, transparent 0%, black 50%, transparent 100%); + height: 2px; + width: calc(100% - 10px); + } + + .trait-bonus-container { + display: flex; + gap: 4px; + + .trait-card { + border: 1px solid light-dark(@dark-blue, @golden); + border-radius: 6px; + padding: 2px; + opacity: 0.4; + flex: 1; + white-space: nowrap; + text-align: center; + + &.selected { + opacity: 1; + } + } + } + } + } + footer { margin-top: 8px; display: flex; diff --git a/styles/less/dialog/character-creation/selections-container.less b/styles/less/dialog/character-creation/selections-container.less index 1de3d870..f9569fca 100644 --- a/styles/less/dialog/character-creation/selections-container.less +++ b/styles/less/dialog/character-creation/selections-container.less @@ -240,12 +240,16 @@ display: flex; align-items: center; justify-content: space-evenly; - gap: 8px; + gap: 2px; .trait-container { - width: 60px; - height: 60px; + span { + font-size: var(--font-size-10); + } + width: 65px; + height: 65px; background: url(../assets/svg/trait-shield.svg) no-repeat; + background-size: 100%; div { filter: drop-shadow(0 0 3px black); diff --git a/styles/less/sheets-settings/character-settings/sheet.less b/styles/less/sheets-settings/character-settings/sheet.less index f0c7c94e..f7f16df4 100644 --- a/styles/less/sheets-settings/character-settings/sheet.less +++ b/styles/less/sheets-settings/character-settings/sheet.less @@ -20,16 +20,22 @@ display: flex; align-items: center; justify-content: space-evenly; - gap: 8px; + gap: 2px; .trait-container { - width: 60px; - height: 60px; + width: 65px; + height: 65px; background: url(../assets/svg/trait-shield.svg) no-repeat; + background-size: 100%; + padding-top: 4px; display: flex; flex-direction: column; align-items: center; + span { + font-size: var(--font-size-10); + } + div { filter: drop-shadow(0 0 3px black); text-shadow: 0 0 3px black; diff --git a/styles/less/sheets/actions/actions.less b/styles/less/sheets/actions/actions.less index 5c21dc60..5a18d622 100644 --- a/styles/less/sheets/actions/actions.less +++ b/styles/less/sheets/actions/actions.less @@ -133,4 +133,18 @@ height: 300px; } } + + .deletable-row { + display: flex; + align-items: end; + gap: 8px; + + input { + flex: 1; + } + + a { + padding-bottom: 7px; + } + } } diff --git a/styles/less/ui/chat/chat.less b/styles/less/ui/chat/chat.less index e9ef9147..db6dceb9 100644 --- a/styles/less/ui/chat/chat.less +++ b/styles/less/ui/chat/chat.less @@ -384,6 +384,15 @@ justify-content: center; width: 15px; } + &.has-minus:before { + content: '-'; + font-size: var(--font-size-20); + grid-area: c; + display: flex; + align-items: center; + justify-content: center; + width: 15px; + } } } diff --git a/styles/less/ui/sidebar/daggerheartMenu.less b/styles/less/ui/sidebar/daggerheartMenu.less index 677214d7..88b139c5 100644 --- a/styles/less/ui/sidebar/daggerheartMenu.less +++ b/styles/less/ui/sidebar/daggerheartMenu.less @@ -15,17 +15,17 @@ font-weight: bold; } - .menu-refresh-container { + .menu-options-container { display: flex; flex-direction: column; gap: 8px; - .menu-refresh-inner-container { + .menu-options-inner-container { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; - .experience-chip { + .option-chip { display: flex; align-items: center; border-radius: 5px; diff --git a/styles/less/ux/index.less b/styles/less/ux/index.less index 6fad3ab1..a73f2d1c 100644 --- a/styles/less/ux/index.less +++ b/styles/less/ux/index.less @@ -1,3 +1,4 @@ +@import './tooltip/sheet.less'; @import './tooltip/tooltip.less'; @import './tooltip/armorManagement.less'; @import './tooltip/battlepoints.less'; diff --git a/styles/less/ux/tooltip/bordered-tooltip.less b/styles/less/ux/tooltip/bordered-tooltip.less index abec93b7..d72f635e 100644 --- a/styles/less/ux/tooltip/bordered-tooltip.less +++ b/styles/less/ux/tooltip/bordered-tooltip.less @@ -18,7 +18,7 @@ display: flex; flex-direction: column; align-items: center; - text-align: start; + text-align: center; padding: 5px; gap: 0px; diff --git a/styles/less/ux/tooltip/sheet.less b/styles/less/ux/tooltip/sheet.less new file mode 100644 index 00000000..59e4e638 --- /dev/null +++ b/styles/less/ux/tooltip/sheet.less @@ -0,0 +1,129 @@ +#tooltip:has(div.daggerheart.dh-style.tooltip.card-style), +aside[role='tooltip']:has(div.daggerheart.dh-style.tooltip), +#tooltip.bordered-tooltip { + .tooltip-title { + font-size: var(--font-size-20); + color: light-dark(@dark-blue, @golden); + font-weight: 700; + } + + .tooltip-description { + font-style: inherit; + text-align: inherit; + width: 100%; + padding: 5px 10px; + position: relative; + margin-top: 5px; + + &::before { + content: ''; + background: @golden; + mask-image: linear-gradient(270deg, transparent 0%, black 50%, transparent 100%); + height: 2px; + width: calc(100% - 10px); + } + + &::before { + position: absolute; + top: -5px; + } + } + + .tooltip-separator { + background: @golden; + mask-image: linear-gradient(270deg, transparent 0%, black 50%, transparent 100%); + height: 2px; + width: calc(100% - 10px); + margin-bottom: 2px; + } + + .tooltip-tags { + display: flex; + flex-direction: column; + gap: 10px; + width: 100%; + padding: 5px 10px; + position: relative; + max-height: 150px; + overflow-y: auto; + position: relative; + + scrollbar-width: thin; + scrollbar-color: light-dark(@dark-blue, @golden) transparent; + + .tooltip-tag { + display: flex; + gap: 10px; + flex-direction: column; + + .tooltip-tag-label-container { + display: flex; + align-items: center; + gap: 5px; + + img { + width: 40px; + height: 40px; + border-radius: 3px; + } + } + } + } + + .tags { + display: flex; + gap: 5px 10px; + padding-bottom: 16px; + flex-wrap: wrap; + justify-content: center; + + &.advantages { + width: 100%; + padding: 5px 10px; + padding-bottom: 16px; + position: relative; + margin-top: 5px; + + &::before { + content: ''; + background: @golden; + mask-image: linear-gradient(270deg, transparent 0%, black 50%, transparent 100%); + height: 2px; + width: calc(100% - 10px); + } + + &::before { + position: absolute; + top: -5px; + } + + .tag { + background: @green-10; + color: @green; + border-color: @green; + } + } + + .tag { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 3px 5px; + font-size: var(--font-size-12); + font: @font-body; + + background: light-dark(@dark-15, @beige-15); + border: 1px solid light-dark(@dark, @beige); + border-radius: 3px; + } + + .label { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + font-size: var(--font-size-12); + } + } +} diff --git a/styles/less/ux/tooltip/tooltip.less b/styles/less/ux/tooltip/tooltip.less index ac09afc0..1566059f 100644 --- a/styles/less/ux/tooltip/tooltip.less +++ b/styles/less/ux/tooltip/tooltip.less @@ -13,13 +13,6 @@ aside[role='tooltip']:has(div.daggerheart.dh-style.tooltip.card-style) { outline: 1px solid light-dark(@dark-80, @beige-80); box-shadow: 0 0 25px rgba(0, 0, 0, 0.8); - .tooltip-title { - font-size: var(--font-size-20); - color: light-dark(@dark-blue, @golden); - font-weight: 700; - margin-bottom: 5px; - } - .tooltip-subtitle { margin: 0; } @@ -80,110 +73,6 @@ aside[role='tooltip']:has(div.daggerheart.dh-style.tooltip.card-style) { } } - .tooltip-tags { - display: flex; - flex-direction: column; - gap: 10px; - width: 100%; - padding: 5px 10px; - position: relative; - padding-top: 10px; - max-height: 150px; - overflow-y: auto; - position: relative; - - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; - - &::before { - content: ''; - background: @golden; - mask-image: linear-gradient(270deg, transparent 0%, black 50%, transparent 100%); - height: 2px; - width: calc(100% - 10px); - } - - &::before { - position: absolute; - top: 0px; - } - - .tooltip-tag { - display: flex; - gap: 10px; - flex-direction: column; - - .tooltip-tag-label-container { - display: flex; - align-items: center; - gap: 5px; - - img { - width: 40px; - height: 40px; - border-radius: 3px; - } - } - } - } - - .tags { - display: flex; - gap: 5px 10px; - padding-bottom: 16px; - flex-wrap: wrap; - justify-content: center; - - &.advantages { - width: 100%; - padding: 5px 10px; - padding-bottom: 16px; - position: relative; - margin-top: 5px; - - &::before { - content: ''; - background: @golden; - mask-image: linear-gradient(270deg, transparent 0%, black 50%, transparent 100%); - height: 2px; - width: calc(100% - 10px); - } - - &::before { - position: absolute; - top: -5px; - } - - .tag { - background: @green-10; - color: @green; - border-color: @green; - } - } - - .tag { - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - padding: 3px 5px; - font-size: var(--font-size-12); - font: @font-body; - - background: light-dark(@dark-15, @beige-15); - border: 1px solid light-dark(@dark, @beige); - border-radius: 3px; - } - - .label { - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - font-size: var(--font-size-12); - } - } - .item-icons-list { position: absolute; display: flex; diff --git a/system.json b/system.json index 9a78bf50..cc322576 100644 --- a/system.json +++ b/system.json @@ -5,7 +5,7 @@ "version": "2.0.0", "compatibility": { "minimum": "14.355", - "verified": "14.357", + "verified": "14.358", "maximum": "14" }, "authors": [ diff --git a/templates/actionTypes/beastform.hbs b/templates/actionTypes/beastform.hbs index b9bea445..885038a1 100644 --- a/templates/actionTypes/beastform.hbs +++ b/templates/actionTypes/beastform.hbs @@ -1,4 +1,16 @@ +{{formGroup fields.tierAccess.fields.exact value=source.tierAccess.exact name="beastform.tierAccess.exact" labelAttr="label" valueAttr="key" localize=true blank=""}} +
- {{localize "DAGGERHEART.ACTIONS.Config.beastform.label"}} - {{formGroup fields.tierAccess.fields.exact value=source.tierAccess.exact name="beastform.tierAccess.exact" labelAttr="label" valueAttr="key" localize=true blank=""}} + {{localize "DAGGERHEART.ACTIONS.Config.beastform.modifications.traitBonuses.label.plural"}} + + {{#if source.modifications.traitBonuses.length}} + {{#each source.modifications.traitBonuses as |traitBonus index|}} +
+ {{formGroup ../fields.modifications.fields.traitBonuses.element.fields.bonus value=traitBonus.bonus name=(concat "beastform.modifications.traitBonuses." index ".bonus") localize=true}} + +
+ {{/each}} + {{else}} + {{localize "DAGGERHEART.ACTIONS.Config.beastform.modifications.traitBonuses.hint"}} + {{/if}}
\ No newline at end of file diff --git a/templates/dialogs/beastform/modifications.hbs b/templates/dialogs/beastform/modifications.hbs new file mode 100644 index 00000000..2563ea13 --- /dev/null +++ b/templates/dialogs/beastform/modifications.hbs @@ -0,0 +1,28 @@ +
+ {{#if modifications.traitBonuses.length}} +
+ + {{#if (gt modifications.traitBonuses.length 1)}} + {{localize "DAGGERHEART.ACTIONS.Config.beastform.modifications.traitBonuses.label.plural"}} + {{else}} + {{localize "DAGGERHEART.ACTIONS.Config.beastform.modifications.traitBonuses.label.single"}} + {{/if}} + + +
+ {{#each modifications.traitBonuses as |traitBonus index|}} +
+ {{#each @root.traits as |trait|}} + + {{localize trait.label}} +{{traitBonus.bonus}} + + {{/each}} +
+ {{#unless @last}}
{{/unless}} + {{/each}} +
+
+ {{/if}} +
\ No newline at end of file diff --git a/templates/dialogs/dice-roll/rollSelection.hbs b/templates/dialogs/dice-roll/rollSelection.hbs index 4451160b..64a3cdcb 100644 --- a/templates/dialogs/dice-roll/rollSelection.hbs +++ b/templates/dialogs/dice-roll/rollSelection.hbs @@ -53,14 +53,14 @@ {{#if @root.advantage}} {{#if (eq @root.advantage 1)}}
- +
{{localize "DAGGERHEART.GENERAL.Advantage.full"}}
{{else if (eq @root.advantage -1)}}
- +
{{localize "DAGGERHEART.GENERAL.Disadvantage.full"}}
@@ -158,7 +158,7 @@ {{/times}}
{{#if abilities}} diff --git a/templates/dialogs/tagTeamDialog/parts/tagTeamDamageParts.hbs b/templates/dialogs/tagTeamDialog/parts/tagTeamDamageParts.hbs index 058777a5..49fc8f4f 100644 --- a/templates/dialogs/tagTeamDialog/parts/tagTeamDamageParts.hbs +++ b/templates/dialogs/tagTeamDialog/parts/tagTeamDamageParts.hbs @@ -7,7 +7,7 @@ {{#each damage.parts as |part|}}
{{#each part.dice as |dice index|}} - + {{dice.total}} diff --git a/templates/dialogs/tagTeamDialog/tagTeamMember.hbs b/templates/dialogs/tagTeamDialog/tagTeamMember.hbs index 5c100d02..7510422f 100644 --- a/templates/dialogs/tagTeamDialog/tagTeamMember.hbs +++ b/templates/dialogs/tagTeamDialog/tagTeamMember.hbs @@ -62,32 +62,30 @@
- {{#if rollData}} - {{#with rollData.options.roll}} -
-
{{this.total}} {{localize "DAGGERHEART.GENERAL.withThing" thing=this.result.label}}
-
- - {{this.hope.value}} - - - + - - {{this.fear.value}} - - - {{#if this.advantage.type}} - {{#if (eq this.advantage.type 1)}}+{{else}}-{{/if}} - - {{this.advantage.value}} - - - {{/if}} - {{#if (gte this.modifierTotal 0)}}+{{else}}-{{/if}} - {{positive this.modifierTotal}} -
+ {{#if roll}} +
+
{{roll.total}} {{localize "DAGGERHEART.GENERAL.withThing" thing=roll.totalLabel}}
+
+ + {{roll.dHope.total}} + + + + + + {{roll.dFear.total}} + + + {{#if roll.advantage.type}} + {{#if (eq roll.advantage.type 1)}}+{{else}}-{{/if}} + + {{roll.advantage.value}} + + + {{/if}} + {{#if (gte roll.modifierTotal 0)}}+{{else}}-{{/if}} + {{positive roll.modifierTotal}}
- {{/with}} +
{{else}} {{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.makeYourRoll"}} {{/if}} diff --git a/templates/dialogs/tagTeamDialog/tagTeamRoll.hbs b/templates/dialogs/tagTeamDialog/tagTeamRoll.hbs index 478255ce..79b5138e 100644 --- a/templates/dialogs/tagTeamDialog/tagTeamRoll.hbs +++ b/templates/dialogs/tagTeamDialog/tagTeamRoll.hbs @@ -6,16 +6,16 @@ {{#if hintText}}
{{localize hintText}}
{{else}} - {{#if joinedRoll.rollData}} + {{#if joinedRoll.roll}}
-
{{joinedRoll.rollData.options.roll.total}}
-
{{localize "DAGGERHEART.GENERAL.withThing" thing=joinedRoll.rollData.options.roll.result.label}}
+
{{joinedRoll.roll.total}}
+
{{localize "DAGGERHEART.GENERAL.withThing" thing=joinedRoll.roll.totalLabel}}
{{/if}} - {{#if hasDamage}} + {{#if joinedRoll.rollData.options.hasDamage}}
{{#each joinedRoll.rollData.options.damage as |damage key|}} diff --git a/templates/sheets/global/partials/inventory-item-V2.hbs b/templates/sheets/global/partials/inventory-item-V2.hbs index a758a28f..2129b969 100644 --- a/templates/sheets/global/partials/inventory-item-V2.hbs +++ b/templates/sheets/global/partials/inventory-item-V2.hbs @@ -17,7 +17,7 @@ Parameters: - showActions {boolean} : If true show feature's actions. --}} -
  • {{localize "DAGGERHEART.APPLICATIONS.DaggerheartMenu.refreshFeatures"}} -