diff --git a/daggerheart.d.ts b/daggerheart.d.ts index 02abf063..891a3a2a 100644 --- a/daggerheart.d.ts +++ b/daggerheart.d.ts @@ -11,9 +11,6 @@ import * as documents from './module/documents/_module.mjs'; import { macros } from './module/_module.mjs'; import * as dice from './module/dice/_module.mjs'; import * as fields from './module/data/fields/_module.mjs'; -import { gameSettings } from './module/config/settingsConfig.mjs'; -import DhCountdowns from './module/data/countdowns.mjs'; -import DhAutomation from './module/data/settings/Automation.mjs'; // Foundry's use of `Object.assign(globalThis) means many globally available objects are not read as such @@ -106,13 +103,3 @@ declare module '@client/packages/system.mjs' { }; } } - -declare module '@client/helpers/client-settings.mjs' { - // Add explicit typed overrides for auto complete. These require /** @type {"string"} on the vars themselves to work */ - export default interface ClientSettings { - get(namespace: 'daggerheart', key: typeof gameSettings.Automation): DhAutomation; - get(namespace: 'daggerheart', key: typeof gameSettings.Homebrew): DhHomebrew; - get(namespace: 'daggerheart', key: typeof gameSettings.Countdowns): DhCountdowns; - get(namespace: 'daggerheart', key: string): unknown; - } -} \ No newline at end of file diff --git a/daggerheart.mjs b/daggerheart.mjs index 2c51c1f6..63127aa4 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -6,7 +6,6 @@ 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 die from './module/dice/die/_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'; @@ -24,7 +23,7 @@ import TokenManager from './module/documents/tokenManager.mjs'; CONFIG.DH = SYSTEM; CONFIG.TextEditor.enrichers.push(...enricherConfig); -CONFIG.Dice.rolls = [Roll = BaseRoll, DHRoll, DualityRoll, D20Roll, DamageRoll, FateRoll]; +CONFIG.Dice.rolls = [BaseRoll, DHRoll, DualityRoll, D20Roll, DamageRoll, FateRoll]; CONFIG.Dice.daggerheart = { DHRoll: DHRoll, DualityRoll: DualityRoll, @@ -39,8 +38,6 @@ CONFIG.RegionBehavior.dataModels = { }; Object.assign(CONFIG.Dice.termTypes, dice.diceTypes); -CONFIG.Dice.terms.d = die.BaseDie; -CONFIG.Dice.types = [die.BaseDie, CONFIG.Dice.terms.f]; CONFIG.Actor.documentClass = documents.DhpActor; CONFIG.Actor.dataModels = models.actors.config; @@ -270,10 +267,6 @@ Hooks.on('i18nInit', () => { }); Hooks.on('setup', () => { - if (game.user.isGM) { - document.body.dataset.gm = true; - } - CONFIG.statusEffects = [ ...CONFIG.statusEffects.filter(x => !['dead', 'unconscious'].includes(x.id)), ...Object.values(SYSTEM.GENERAL.conditions()).map(x => ({ diff --git a/lang/en.json b/lang/en.json index 232315c2..7863fb67 100755 --- a/lang/en.json +++ b/lang/en.json @@ -104,10 +104,8 @@ "startCountdown": "Start Countdown" }, "damage": { - "addResource": "Add Resource", "multiplier": "Multiplier", - "flatMultiplier": "Flat Multiplier", - "markResources": "Mark Resources" + "flatMultiplier": "Flat Multiplier" }, "general": { "customFormula": "Custom Formula", @@ -126,15 +124,6 @@ "damageOnSave": "Damage on Save", "useDefaultItemValues": "Use default Item values" }, - "Reload": { - "checkReload": "Check Reload", - "reloadRequired": "Reload Required!", - "notRolled": "Not Rolled", - "checkFailed": "Check Failed", - "checkPassed": "Check Passed", - "rerollConfirmationTitle": "Reroll Reload Check", - "rerollConfirmationText": "Are you sure you want to reload the reload check?" - }, "RollField": { "diceRolling": { "compare": "Should be", @@ -1331,11 +1320,6 @@ "short": "V. Far" } }, - "ReloadChoices": { - "off": { "label": "Don't Use" }, - "manual": { "label": "Manual" }, - "auto": { "label": "Automatic" } - }, "RollTypes": { "trait": { "name": "Trait" @@ -2235,9 +2219,7 @@ }, "Resource": { "single": "Resource", - "plural": "Resources", - "unloaded": "The weapon is not loaded", - "loaded": "The weapon is loaded" + "plural": "Resources" }, "Roll": { "attack": "Attack Roll", @@ -2263,7 +2245,7 @@ "hint": "A used armor slot normally reduces damage by one step. This value increases the number of steps damage is reduced by." }, "magical": { - "label": "Damage Reduction: Only Magical", + "label": "Daamge Reduction: Only Magical", "hint": "Armor can only be used to reduce magical damage" }, "maxArmorMarkedBonus": "Max Armor Used", @@ -2501,6 +2483,7 @@ "reroll": "Reroll", "rerolled": "Rerolled", "rerollThing": "Reroll {thing}", + "resource": "Resource", "result": { "single": "Result", "plural": "Results" @@ -2567,9 +2550,6 @@ }, "identifier": { "label": "Identifier" - }, - "gmNotes": { - "label": "GM Notes" } }, "Ancestry": { @@ -2585,9 +2565,6 @@ "severe": "Severe Threshold" } }, - "Base": { - "addGMNote": "Add GM Note" - }, "Beastform": { "FIELDS": { "beastformType": { "label": "Beastform Type" }, @@ -2704,9 +2681,6 @@ "displayFear": { "label": "Display Fear" }, - "fearPosition": { - "label": "Fear Position" - }, "displayCountdownUI": { "label": "Display Countdown UI" }, @@ -2758,13 +2732,6 @@ "token": "Tokens", "bar": "Bar", "hide": "Hide" - }, - "fearPosition": { - "free": "Free", - "topCenter": "Top + Center", - "bottomCenter": "Bottom + Center", - "rightTop": "Right + Top", - "leftBottom": "Left + Bottom" } }, "Automation": { @@ -2823,10 +2790,6 @@ "hint": "Effects with defined range dependency will automatically turn on/off depending on range" } }, - "reload": { - "label": "Reload Checking", - "hint": "If the system should automatically roll reload checks or wait for the manual press of a button in chat" - }, "resourceScrollTexts": { "label": "Show Resource Change Scrolltexts", "hint": "When a character is damaged, uses armor etc, a scrolling text will briefly appear by the token to signify this." @@ -3288,8 +3251,7 @@ "knowTheTide": "Know The Tide gained a token", "lackingItemTransferPermission": "User {user} lacks owner permission needed to transfer items to {target}", "noTokenTargeted": "No token is targeted", - "behaviorRegionRequiresGM": "Creating a Region with an attached Behavior requires an online GM", - "reloadRequired": "The {weapon} must be reloaded to be used!" + "behaviorRegionRequiresGM": "Creating a Region with an attached Behavior requires an online GM" }, "Progress": { "migrationLabel": "Performing system migration. Please wait and do not close Foundry." diff --git a/module/applications/dialogs/damageDialog.mjs b/module/applications/dialogs/damageDialog.mjs index ce613ade..46d3d41f 100644 --- a/module/applications/dialogs/damageDialog.mjs +++ b/module/applications/dialogs/damageDialog.mjs @@ -51,11 +51,7 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application const context = await super._prepareContext(_options); context.config = CONFIG.DH; context.title = this.config.title ?? this.title; - - const { damageFormula, resourceFormulas } = this.roll.constructFormulas(this.config); - context.damageFormula = damageFormula; - context.resourceFormulas = resourceFormulas; - + context.formula = this.roll.constructFormula(this.config); context.hasHealing = this.config.hasHealing; context.directDamage = this.config.directDamage; context.selectedMessageMode = this.config.selectedMessageMode; @@ -77,11 +73,7 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application static updateRollConfiguration(_event, _, formData) { const data = foundry.utils.expandObject(formData.object); - - if (this.config.damageFormula) - foundry.utils.mergeObject(this.config.damageFormula, data.damageFormula); - - foundry.utils.mergeObject(this.config.resourceFormulas, data.resourceFormulas); + foundry.utils.mergeObject(this.config.roll, data.roll); foundry.utils.mergeObject(this.config.modifiers, data.modifiers); this.config.selectedMessageMode = data.selectedMessageMode; diff --git a/module/applications/dialogs/deathMove.mjs b/module/applications/dialogs/deathMove.mjs index cfd7687b..8e0ed6af 100644 --- a/module/applications/dialogs/deathMove.mjs +++ b/module/applications/dialogs/deathMove.mjs @@ -185,6 +185,8 @@ export default class DhDeathMove extends HandlebarsApplicationMixin(ApplicationV if (result === undefined) return; + const autoExpandDescription = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance) + .expandRollMessage?.desc; const cls = getDocumentClass('ChatMessage'); const msg = { @@ -200,6 +202,7 @@ export default class DhDeathMove extends HandlebarsApplicationMixin(ApplicationV img: this.selectedMove.img, description: game.i18n.localize(this.selectedMove.description), result: result, + open: autoExpandDescription ? 'open' : '', showRiskItAllButton: this.showRiskItAllButton, riskItAllButtonLabel: this.riskItAllButtonLabel, riskItAllHope: this.riskItAllHope diff --git a/module/applications/dialogs/downtime.mjs b/module/applications/dialogs/downtime.mjs index 5ba8e48e..e209cc3b 100644 --- a/module/applications/dialogs/downtime.mjs +++ b/module/applications/dialogs/downtime.mjs @@ -196,6 +196,9 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV .filter(x => x.testUserPermission(game.user, 'LIMITED')) .filter(x => x.uuid !== this.actor.uuid); + const autoExpandDescription = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance) + .expandRollMessage?.desc; + const cls = getDocumentClass('ChatMessage'); const msg = { user: game.user.id, @@ -216,7 +219,8 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV actor: { name: this.actor.name, img: this.actor.img }, moves: moves, characters: characters, - selfId: this.actor.uuid + selfId: this.actor.uuid, + open: autoExpandDescription ? 'open' : '' } ), flags: { diff --git a/module/applications/dialogs/tagTeamDialog.mjs b/module/applications/dialogs/tagTeamDialog.mjs index 4c6c5339..b33541a3 100644 --- a/module/applications/dialogs/tagTeamDialog.mjs +++ b/module/applications/dialogs/tagTeamDialog.mjs @@ -1,5 +1,4 @@ import { ResourceUpdateMap } from '../../data/action/baseAction.mjs'; -import { ChatDamageData } from '../../data/chat-message/chatDamageData.mjs'; import { MemberData } from '../../data/tagTeamData.mjs'; import { getCritDamageBonus, shouldUseHopeFearAutomation } from '../../helpers/utils.mjs'; import { emitGMUpdate, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs'; @@ -141,7 +140,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio const hasRolled = Boolean(data.rollData); if (!hasRolled) return false; - return !data.rollData.options.hasDamage || data.damageRollData.active; + return !data.rollData.options.hasDamage || Boolean(data.rollData.options.damage); }); return context; @@ -182,7 +181,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio const selectedRoll = Object.values(this.party.system.tagTeam.members).find(member => member.selected); const critSelected = !selectedRoll ? undefined - : (selectedRoll?.roll?.isCritical ?? false); + : (selectedRoll?.rollData?.options?.roll?.isCritical ?? false); partContext.hintText = await this.getInfoTexts(this.party.system.tagTeam.members); partContext.joinedRoll = await this.getJoinedRoll({ @@ -236,7 +235,8 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio } const selectedRoll = Object.values(this.party.system.tagTeam.members).find(member => member.selected); - const critSelected = !selectedRoll ? undefined : (selectedRoll?.roll?.isCritical ?? false); + const critSelected = !selectedRoll ? undefined : (selectedRoll?.rollData?.options?.roll?.isCritical ?? false); + const damage = data.rollData?.options?.damage; return { ...data, @@ -247,9 +247,9 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio hasRolled: Boolean(data.rollData), rollOptions, damageRollOptions, - damage: data.damageRollData, - critDamage: await this.getCriticalDamage(data.damageRollData), - useCritDamage: critSelected || (critSelected === undefined && data.roll?.isCritical) + damage: damage, + critDamage: await this.getCriticalDamage(damage), + useCritDamage: critSelected || (critSelected === undefined && data.rollData?.options?.roll?.isCritical) }; } @@ -379,7 +379,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio let rollIsSelected = false; for (const member of Object.values(members)) { const rollFinished = Boolean(member.rollData); - const damageFinished = member.rollData?.options?.hasDamage ? member.damageRollData.active : true; + const damageFinished = member.rollData?.options?.hasDamage ? Boolean(member.rollData.options.damage) : true; rollsAreFinished = rollsAreFinished && rollFinished && damageFinished; rollIsSelected = rollIsSelected || member.selected; @@ -540,10 +540,17 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio await action.workflow.get('damage').execute(config, null, true); if (!config.damage) return; - + + const current = this.party.system.tagTeam.members[memberKey].rollData; await this.updatePartyData( { - [`system.tagTeam.members.${memberKey}.damageRollData`]: config.damage + [`system.tagTeam.members.${memberKey}.rollData`]: { + ...current, + options: { + ...current.options, + damage: config.damage + } + } }, this.getUpdatingParts(button) ); @@ -551,11 +558,15 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio static async #removeDamageRoll(_, button) { const { memberKey } = button.dataset; + const current = this.party.system.tagTeam.members[memberKey].rollData; this.updatePartyData( { - [`system.tagTeam.members.${memberKey}.damageRollData`]: { - main: null, - resources: _replace({}) + [`system.tagTeam.members.${memberKey}.rollData`]: { + ...current, + options: { + ...current.options, + damage: null + } } }, this.getUpdatingParts(button) @@ -563,44 +574,89 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio } static async #rerollDamageDice(_, button) { - const { isResource, memberKey, damageKey, diceIndex, resultIndex } = button.dataset; + const { memberKey, damageKey, part, dice } = button.dataset; const memberData = this.party.system.tagTeam.members[memberKey]; - await memberData.damageRollData.rerollDamageDie(isResource, damageKey, diceIndex, resultIndex); + const partData = memberData.rollData.options.damage[damageKey].parts[part]; + const activeDiceResultKey = Object.keys(partData.dice[dice].results).find( + index => partData.dice[dice].results[index].active + ); + const { parsedRoll, rerolledDice } = await game.system.api.dice.DamageRoll.reroll( + partData, + dice, + activeDiceResultKey + ); + + const rollData = this.party.system.tagTeam.members[memberKey].rollData; + rollData.options.damage[damageKey].parts = rollData.options.damage[damageKey].parts.map((damagePart, index) => { + if (index !== Number.parseInt(part)) return damagePart; + + return { + ...damagePart, + total: parsedRoll.total, + dice: rerolledDice + }; + }); + rollData.options.damage[damageKey].total = rollData.options.damage[damageKey].parts.reduce((acc, part) => { + acc += part.total; + return acc; + }, 0); - const basePath = `system.tagTeam.members.${memberKey}.damageRollData`; - const updatePath = isResource ? `${basePath}.resources.${damageKey}` : `${basePath}.main`; - const updateValue = isResource ? - memberData.damageRollData.resources[damageKey] : memberData.damageRollData.main; this.updatePartyData( { - [updatePath]: updateValue.toJSON() + [`system.tagTeam.members.${memberKey}.rollData`]: rollData }, this.getUpdatingParts(button) ); } - async getCriticalDamage(origDamage) { - const newDamage = origDamage ? ChatDamageData.fromJSON(JSON.stringify(origDamage)) : null; - if (newDamage?.main) { - const criticalDamage = await getCritDamageBonus(newDamage.main.formula); - if (criticalDamage) { - const criticalTerm = new foundry.dice.terms.NumericTerm({ number: criticalDamage, evaluated: true }); - criticalTerm.evaluate(); - newDamage.main = await Roll.fromTerms([ - ...origDamage.main.terms, - new foundry.dice.terms.OperatorTerm({ operator: '+' }), - criticalTerm - ]); - newDamage.main.options = foundry.utils.deepClone(origDamage.main.options); + async getCriticalDamage(damage) { + const newDamage = foundry.utils.deepClone(damage); + for (let key in newDamage) { + var damage = newDamage[key]; + damage.formula = ''; + damage.total = 0; + + for (let part of damage.parts) { + const criticalDamage = await getCritDamageBonus(part.formula); + if (criticalDamage) { + part.modifierTotal += criticalDamage; + part.total += criticalDamage; + part.formula = `${part.dice.map(x => x.formula).join(' + ')} + ${part.modifierTotal}`; + part.roll = new Roll(part.formula); + } + + damage.formula = [damage.formula, part.formula].filter(x => x).join(' + '); + damage.total += part.total; } - } + } + + return newDamage; + } + + async getNonCriticalDamage(config) { + const newDamage = foundry.utils.deepClone(config.damage); + for (let key in newDamage) { + var damage = newDamage[key]; + damage.formula = ''; + damage.total = 0; + + for (let part of damage.parts) { + const critDamageBonus = await getCritDamageBonus(part.formula); + part.modifierTotal -= critDamageBonus; + part.total -= critDamageBonus; + part.formula = `${part.dice.map(x => x.formula).join(' + ')} + ${part.modifierTotal}`; + part.roll = new Roll(part.formula); + + damage.formula = [damage.formula, part.formula].filter(x => x).join(' + '); + damage.total += part.total; + } + } return newDamage; } static async #selectRoll(_, button) { const { memberKey } = button.dataset; - this.updatePartyData( { [`system.tagTeam.members`]: Object.entries(this.party.system.tagTeam.members).reduce( @@ -611,12 +667,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio {} ) }, - /* Selecting a roll must update all member sections hbs to display the correct damage information incase of a critical */ - [ - ...Object.keys(this.party.system.tagTeam.members), - this.constructor.PARTS.rollSelection.id, - this.constructor.PARTS.result.id - ] + this.getUpdatingParts(button) ); } @@ -634,65 +685,29 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio if (!baseMainRoll?.rollData || !baseSecondaryRoll) return null; const mainRoll = new MemberData(baseMainRoll.toObject()); - mainRoll.damageRollData = baseMainRoll.damageRollData ? - ChatDamageData.fromJSON(JSON.stringify(baseMainRoll.damageRollData)) : null; - const secondaryRoll = new MemberData(baseSecondaryRoll.toObject()); - secondaryRoll.damageRollData = baseSecondaryRoll.damageRollData ? - ChatDamageData.fromJSON(JSON.stringify(baseSecondaryRoll.damageRollData)) : null; + const secondaryRollData = new MemberData(baseSecondaryRoll.toObject()).rollData; + const systemData = mainRoll.rollData.options; + const isCritical = overrideIsCritical ?? systemData.roll.isCritical; + if (isCritical) systemData.damage = await this.getCriticalDamage(systemData.damage); - const isCritical = overrideIsCritical ?? mainRoll.roll.isCritical; - if (isCritical) mainRoll.damageRollData = await this.getCriticalDamage(mainRoll.damageRollData); - - if (secondaryRoll.damageRollData) { + if (secondaryRollData?.options.hasDamage) { const secondaryDamage = (displayVersion ? overrideIsCritical : isCritical) - ? await this.getCriticalDamage(secondaryRoll.damageRollData) - : secondaryRoll.damageRollData; - if (mainRoll.damageRollData) { - if (secondaryDamage.main) { - if (mainRoll.damageRollData.main) { - mainRoll.damageRollData.main = Roll.fromTerms([ - ...baseMainRoll.damageRollData.main.terms, - new foundry.dice.terms.OperatorTerm({ operator: '+' }), - ...baseSecondaryRoll.damageRollData.main.terms - ]); - - /* Joining the roll.options of both rolls */ - const joinedDamageTypes = new Set([ - ...baseMainRoll.damageRollData.main.options.damageTypes, - ...baseSecondaryRoll.damageRollData.main.options.damageTypes - ]); - mainRoll.damageRollData.main.options = { - ...baseMainRoll.damageRollData.main.options, - damageTypes: [...joinedDamageTypes] - }; + ? await this.getCriticalDamage(secondaryRollData.options.damage) + : secondaryRollData.options.damage; + if (systemData.damage) { + for (const [key, damage] of Object.entries(secondaryDamage ?? {})) { + if (key in systemData.damage) { + systemData.damage[key].formula = [systemData.damage[key]?.formula, damage.formula] + .filter(x => x) + .join(' + '); + systemData.damage[key].total += damage.total; + systemData.damage[key].parts.push(...damage.parts); } else { - mainRoll.damageRollData.main = secondaryDamage.main; - } - } - - for (const [key, damage] of Object.entries(secondaryDamage.resources ?? {})) { - if (key in mainRoll.damageRollData.resources) { - mainRoll.damageRollData.resources[key] = Roll.fromTerms([ - ...baseMainRoll.damageRollData.resources[key].terms, - new foundry.dice.terms.OperatorTerm({ operator: '+' }), - ...baseSecondaryRoll.damageRollData.resources[key].terms - ]); - - /* Joining the roll.options of both rolls */ - const joinedDamageTypes = new Set([ - ...baseMainRoll.damageRollData.resources[key].options.damageTypes, - ...baseSecondaryRoll.damageRollData.resources[key].options.damageTypes - ]); - mainRoll.damageRollData.resources[key].options = { - ...baseMainRoll.damageRollData.resources[key].options, - damageTypes: [...joinedDamageTypes] - }; - } else { - mainRoll.damageRollData.resources[key] = damage; + systemData.damage[key] = damage; } } } else { - mainRoll.damageRollData = secondaryDamage; + systemData.damage = secondaryDamage; } } @@ -747,27 +762,13 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio const mainActor = this.party.system.partyMembers.find(x => x.uuid === mainRoll.options.source.actor); mainRoll.options.title = game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.chatMessageRollTitle'); - - /* This could assumably be done better. For some reason rolls don't get correctly done through rollData.toJSON */ - const systemData = { - ...mainRoll.options, - damage: joinedRoll.damageRollData?.toJSON() - }; - - if (joinedRoll.damageRollData.main) { - systemData.damage.main = joinedRoll.damageRollData.toJSON(); - } - for (const type of Object.keys(joinedRoll.damageRollData?.resources ?? {})) { - systemData.damage.resources[type] = joinedRoll.damageRollData.resources[type].toJSON(); - } - const cls = getDocumentClass('ChatMessage'), msgData = { type: 'dualityRoll', user: game.user.id, title: game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.title'), speaker: cls.getSpeaker({ actor: mainActor }), - system: systemData, + system: mainRoll.options, rolls: [JSON.stringify(joinedRoll.roll)], sound: null, flags: { core: { RollTable: true } } diff --git a/module/applications/sheets-configs/action-base-config.mjs b/module/applications/sheets-configs/action-base-config.mjs index e675fb3f..b65e1cdf 100644 --- a/module/applications/sheets-configs/action-base-config.mjs +++ b/module/applications/sheets-configs/action-base-config.mjs @@ -1,4 +1,4 @@ -import { DHDamageData } from '../../data/fields/action/damageField.mjs'; +import { getUnusedDamageTypes } from '../../helpers/utils.mjs'; import DaggerheartSheet from '../sheets/daggerheart-sheet.mjs'; const { ApplicationV2 } = foundry.applications.api; @@ -31,10 +31,8 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) removeElement: this.removeElement, removeTransformActor: this.removeTransformActor, editEffect: this.editEffect, - addDamage: this.#onAddDamage, - removeDamage: this.#onRemoveDamage, - addDamageResource: this.#onAddDamageResource, - removeDamageResource: this.#onRemoveDamageResource, + addDamage: this.addDamage, + removeDamage: this.removeDamage, editDoc: this.editDoc, addTrigger: this.addTrigger, removeTrigger: this.removeTrigger, @@ -159,9 +157,9 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) context.tabs = this._getTabs(this.constructor.TABS); context.config = CONFIG.DH; if (this.action.damage) { - const allKeys = Object.keys(CONFIG.DH.GENERAL.healingTypes); - context.allDamageTypesUsed = allKeys.every(k => k in this.action._source.damage.resources); - if (this.action.damage?.main?.hasOwnProperty('includeBase') && this.action.type === 'attack') + context.allDamageTypesUsed = !getUnusedDamageTypes(this.action.damage.parts).length; + + if (this.action.damage.hasOwnProperty('includeBase') && this.action.type === 'attack') context.hasBaseDamage = !!this.action.parent.attack; } @@ -255,7 +253,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) const submitData = this._prepareSubmitData(event, formData); const data = foundry.utils.mergeObject(this.action.toObject(), submitData); - this.action = (await this.action.update(data)) ?? this.action; + this.action = await this.action.update(data); this.sheetUpdate?.(this.action); this.render(); @@ -301,70 +299,53 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) }); } - /** @this DHActionBaseConfig */ - static #onAddDamage() { - if (!this.action.damage || this.action.damage?.main) return; + static addDamage(_event) { + if (!this.action.damage.parts) return; - const data = this.action.toObject(); - data.damage.main = { - ...DHDamageData.schema.getInitialValue(), - applyTo: 'hitPoints', - type: 'physical' - }; - this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) }); - } - - /** @this DHActionBaseConfig */ - static #onRemoveDamage() { - if (!this.action.damage?.main) return; - const data = this.action.toObject(); - data.damage.main = null; - this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) }); - } - - /** @this DHActionBaseConfig */ - static #onAddDamageResource(_event) { - if (!this.action.damage) return; - - const allKeys = Object.keys(CONFIG.DH.GENERAL.healingTypes); - const unused = allKeys.filter(k => !(k in this.action._source.damage.resources)); - const choices = unused.map(k => ({ value: k, label: _loc(CONFIG.DH.GENERAL.healingTypes[k].label) })); + const choices = getUnusedDamageTypes(this.action._source.damage.parts); const content = new foundry.data.fields.StringField({ - label: _loc('DAGGERHEART.GENERAL.Resource.single'), + label: game.i18n.localize('Damage Type'), choices, required: true - }).toFormGroup({}, { - name: 'type', - localize: true, - nameAttr: 'value', - labelAttr: 'label' - }).outerHTML; + }).toFormGroup( + {}, + { + name: 'type', + localize: true, + nameAttr: 'value', + labelAttr: 'label' + } + ).outerHTML; const callback = (_, button) => { const data = this.action.toObject(); const type = choices[button.form.elements.type.value].value; - data.damage.resources[type] = { - ...this.action.schema.fields.damage.fields.resources.element.getInitialValue(), - applyTo: type - }; + const part = this.action.schema.fields.damage.fields.parts.element.getInitialValue(); + part.applyTo = type; + if (type === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) + part.type = this.action.schema.fields.damage.fields.parts.element.fields.type.element.initial; + + data.damage.parts[type] = part; this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) }); }; const typeDialog = new foundry.applications.api.DialogV2({ buttons: [ - { - action: 'ok', - label: 'Confirm', - icon: 'fas fa-check', - default: true, - callback - } + foundry.utils.mergeObject( + { + action: 'ok', + label: 'Confirm', + icon: 'fas fa-check', + default: true + }, + { callback: callback } + ) ], content: content, rejectClose: false, modal: false, window: { - title: _loc('DAGGERHEART.ACTIONS.Config.damage.addResource') + title: game.i18n.localize('Add Damage') }, position: { width: 300 } }); @@ -372,12 +353,12 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) typeDialog.render(true); } - /** @this DHActionBaseConfig */ - static #onRemoveDamageResource(_event, button) { - if (!this.action.damage?.resources) return; + static removeDamage(_event, button) { + if (!this.action.damage.parts) return; const data = this.action.toObject(); const key = button.dataset.key; - data.damage.resources[key] = _del; + delete data.damage.parts[key]; + data.damage.parts[`${key}`] = _del; this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) }); } diff --git a/module/applications/sheets-configs/adversary-settings.mjs b/module/applications/sheets-configs/adversary-settings.mjs index 0bf18ee6..ff3f3039 100644 --- a/module/applications/sheets-configs/adversary-settings.mjs +++ b/module/applications/sheets-configs/adversary-settings.mjs @@ -1,4 +1,3 @@ -import { DHDamageData } from '../../data/fields/action/damageField.mjs'; import DHBaseActorSettings from '../sheets/api/actor-setting.mjs'; /**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */ @@ -9,10 +8,8 @@ export default class DHAdversarySettings extends DHBaseActorSettings { classes: ['adversary-settings'], position: { width: 455, height: 'auto' }, actions: { - addExperience: this.#onAddExperience, - removeExperience: this.#onRemoveExperience, - addDamage: this.#onAddDamage, - removeDamage: this.#onRemoveDamage + addExperience: DHAdversarySettings.#addExperience, + removeExperience: DHAdversarySettings.#removeExperience } }; @@ -74,7 +71,7 @@ export default class DHAdversarySettings extends DHBaseActorSettings { * Adds a new experience entry to the actor. * @type {ApplicationClickAction} */ - static async #onAddExperience() { + static async #addExperience() { const newExperience = { name: 'Experience', modifier: 0 @@ -86,7 +83,7 @@ export default class DHAdversarySettings extends DHBaseActorSettings { * Removes an experience entry from the actor. * @type {ApplicationClickAction} */ - static async #onRemoveExperience(_, target) { + static async #removeExperience(_, target) { const experience = this.actor.system.experiences[target.dataset.experience]; const confirmed = await foundry.applications.api.DialogV2.confirm({ window: { @@ -101,28 +98,4 @@ export default class DHAdversarySettings extends DHBaseActorSettings { await this.actor.update({ [`system.experiences.${target.dataset.experience}`]: _del }); } - - /** - * @this DHAdversarySettings - * @type {ApplicationClickAction} - */ - static #onAddDamage() { - this.actor.update({ - 'system.attack.damage.main': { - ...DHDamageData.schema.getInitialValue(), - applyTo: 'hitPoints', - type: 'physical' - } - }); - } - - /** - * @this DHAdversarySettings - * @type {ApplicationClickAction} - */ - static #onRemoveDamage() { - this.actor.update({ - 'system.attack.damage.main': null - }); - } } diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index b88ec6fd..b5c04f78 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -3,7 +3,7 @@ import DhDeathMove from '../../dialogs/deathMove.mjs'; import { CharacterLevelup, LevelupViewMode } from '../../levelup/_module.mjs'; import DhCharacterCreation from '../../characterCreation/characterCreation.mjs'; import FilterMenu from '../../ux/filter-menu.mjs'; -import { getArmorSources, getDocFromElement, getDocFromElementSync, itemAbleRollParse, sortBy } from '../../../helpers/utils.mjs'; +import { getArmorSources, getDocFromElement, getDocFromElementSync, sortBy } from '../../../helpers/utils.mjs'; /**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */ @@ -29,7 +29,6 @@ export default class CharacterSheet extends DHBaseActorSheet { toggleResourceDice: CharacterSheet.#toggleResourceDice, handleResourceDice: CharacterSheet.#handleResourceDice, advanceResourceDie: CharacterSheet.#advanceResourceDie, - toggleItemReload: CharacterSheet.#onToggleItemReload, cancelBeastform: CharacterSheet.#cancelBeastform, toggleResourceManagement: CharacterSheet.#toggleResourceManagement, useDowntime: this.useDowntime, @@ -955,21 +954,11 @@ export default class CharacterSheet extends DHBaseActorSheet { }); } + /** */ static #advanceResourceDie(_, target) { this.updateResourceDie(target, true); } - static async #onToggleItemReload(_, target) { - const item = await getDocFromElement(target); - if (!item || !item.system.resource?.max) - return; - - await item.update({ - 'system.resource.value': item.system.needsReload ? - itemAbleRollParse(item.system.resource.max, this.document, item) : 0 - }) - } - lowerResourceDie(event) { event.preventDefault(); event.stopPropagation(); diff --git a/module/applications/sheets/api/base-item.mjs b/module/applications/sheets/api/base-item.mjs index 70a6bcc6..1e08fc05 100644 --- a/module/applications/sheets/api/base-item.mjs +++ b/module/applications/sheets/api/base-item.mjs @@ -30,8 +30,7 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) { addFeature: DHBaseItemSheet.#addFeature, deleteFeature: DHBaseItemSheet.#deleteFeature, addResource: DHBaseItemSheet.#addResource, - removeResource: DHBaseItemSheet.#removeResource, - editGMNote: DHBaseItemSheet.#onEditGMNote + removeResource: DHBaseItemSheet.#removeResource }, dragDrop: [ { dragSelector: null, dropSelector: '.drop-section' }, @@ -77,16 +76,10 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) { /**@inheritdoc */ async _preparePartContext(partId, context, options) { await super._preparePartContext(partId, context, options); - const TextEditor = foundry.applications.ux.TextEditor.implementation; switch (partId) { case 'description': - context.enrichedDescription = await this.document.system.getEnrichedDescription({ gmNotes: false }); - context.enrichedGMNotes = await TextEditor.implementation.enrichHTML(this.item.system.gmNotes, { - relativeTo: this.item, - rollData: this.item.getRollData(), - secrets: this.item.isOwner - }) + context.enrichedDescription = await this.document.system.getEnrichedDescription(); break; case 'effects': await this._prepareEffectsContext(context, options); @@ -338,45 +331,4 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) { } } } - - /** - * Handles the Add GM Note button being pressed. This is only used when an item has no GM notes. - * Later edits to a GM note instead go through the normal editor toggle workflow. - * @this DHBaseItemSheet - */ - static #onEditGMNote() { - // Open the editor, which might be hidden. We remove the css class to hide temporarily - // so that menu auto resizing functions properly. - const editor = this.element.querySelector('prose-mirror[name="system.gmNotes"]'); - const wasHidden = editor.classList.contains('hide-if-inactive'); - editor.classList.remove('hide-if-inactive'); - editor.open = true; - window.setTimeout(() => { - if (wasHidden) editor.classList.add('hide-if-inactive'); - }, 0); - } - - /** @inheritdoc */ - async _onRender(context, options) { - await super._onRender(context, options); - - // Render an add gmnotes button if there are no set GM notes. - // We need to re-render on close since its possible to prosemirror to close *without* triggering a full re-render - if (game.user.isGM && !this.item.system.gmNotes) { - const description = this.element.querySelector('[name="system.description"]'); - const addButton = () => { - if (description.querySelector('[data-action=editGMNote]')) return; - - const button = document.createElement('button'); - button.type = 'button'; - button.classList.add('icon', 'toggle', 'fa-regular', 'fa-note-medical'); - button.dataset.action = 'editGMNote'; - button.dataset.tooltip = 'DAGGERHEART.ITEMS.Base.addGMNote'; - description.appendChild(button); - } - - addButton(); - description.addEventListener('close', () => addButton()); - } - } } diff --git a/module/applications/ui/chatLog.mjs b/module/applications/ui/chatLog.mjs index 5fb61857..199ee87d 100644 --- a/module/applications/ui/chatLog.mjs +++ b/module/applications/ui/chatLog.mjs @@ -1,7 +1,6 @@ import { enrichedDualityRoll } from '../../enrichers/DualityRollEnricher.mjs'; import { enrichedFateRoll, getFateTypeData } from '../../enrichers/FateRollEnricher.mjs'; import { getCommandTarget, rollCommandToJSON } from '../../helpers/utils.mjs'; -import FearTracker from './fearTracker.mjs'; export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLog { constructor(options) { @@ -111,7 +110,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo const message = game.messages.get(li.dataset.messageId); return message.system.hasRoll && (game.user.isGM || message.isAuthor); }, - onClick: async (_event, li) => { + callback: async li => { const message = game.messages.get(li.dataset.messageId); const reroll = await message.rolls[0].reroll({ liveRoll: true }); message.update({ rolls: [reroll] }); @@ -127,7 +126,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo : false; return (game.user.isGM || message.isAuthor) && hasRolledDamage; }, - onClick: async (_event, li) => { + callback: async li => { const message = game.messages.get(li.dataset.messageId); const update = await message.system.getRerolledDamage(); message.update(update); @@ -153,9 +152,6 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo html.querySelectorAll('.risk-it-all-button').forEach(element => element.addEventListener('click', event => this.riskItAllClearStressAndHitPoints(event, data)) ); - for (const element of html.querySelectorAll('.roll-reload-check')) { - element.addEventListener('click', event => this.onRollReloadCheck(event, message)); - } }; setupHooks() { @@ -183,15 +179,28 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo } async onRollSimple(event, message) { - const buttonType = event.target.dataset.type ?? 'damage'; - const total = message.rolls.reduce((a, c) => a + Roll.fromJSON(c).total, 0); - const targets = Array.from(game.user.targets); + const buttonType = event.target.dataset.type ?? 'damage', + total = message.rolls.reduce((a, c) => a + Roll.fromJSON(c).total, 0), + damages = { + hitPoints: { + parts: [ + { + applyTo: 'hitPoints', + damageTypes: [], + total + } + ] + } + }, + targets = Array.from(game.user.targets); + if (targets.length === 0) return ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelected')); - for (const target of targets) { - if (buttonType === 'healing') target.actor.takeHealing({ hitPoints: total }); - else target.actor.takeDamage({ total }); - } + + targets.forEach(target => { + if (buttonType === 'healing') target.actor.takeHealing(damages); + else target.actor.takeDamage(damages); + }); } async abilityUseButton(event, message) { @@ -247,17 +256,26 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo } const message = game.messages.get(messageData._id); - const target = event.target.closest('[data-result]'); + const target = event.target.closest('[data-die-index]'); if (target.dataset.type === 'damage') { - const { isResource, damageType, dice, result } = target.dataset; - await message.system.damage.rerollDamageDie(isResource, damageType, dice, result); - - const updatePath = isResource ? `system.damage.resources.${damageType}` : 'system.damage.main'; - const updateValue = isResource ? - message.system.damage.resources[damageType] : message.system.damage.main; - await message.update({ - [updatePath]: updateValue.toJSON() + const { damageType, part, dice, result } = target.dataset; + const damagePart = message.system.damage[damageType].parts[part]; + const { parsedRoll, rerolledDice } = await game.system.api.dice.DamageRoll.reroll(damagePart, dice, result); + const damageParts = message.system.damage[damageType].parts.map((damagePart, index) => { + if (index !== Number(part)) return damagePart; + return { + ...damagePart, + total: parsedRoll.total, + dice: rerolledDice + }; + }); + const updateMessage = game.messages.get(message._id); + await updateMessage.update({ + [`system.damage.${damageType}`]: { + total: parsedRoll.total, + parts: damageParts + } }); } else { const rerollDice = message.system.roll.dice[target.dataset.dieIndex]; @@ -279,27 +297,4 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo const actor = game.actors.get(event.target.dataset.actorId); new game.system.api.applications.dialogs.RiskItAllDialog(actor, resourceValue).render({ force: true }); } - - _toggleNotifications({ closing = false } = {}) { - super._toggleNotifications(closing) - FearTracker.handleOffSet(); - } - - async onRollReloadCheck(_event, messageData) { - const message = game.messages.get(messageData._id); - - if (message.system.reloadCheckValue) { - const confirmed = await foundry.applications.api.DialogV2.confirm({ - window: { - title: _loc('DAGGERHEART.ACTIONS.Reload.rerollConfirmationTitle') - }, - content: _loc('DAGGERHEART.ACTIONS.Reload.rerollConfirmationText') - }); - - if (!confirmed) return; - } - - const { rollValue } = await message.system.action.handleReload?.({ awaitRoll: true }); - await message.update({ 'system.reloadCheckValue': rollValue }); - } } diff --git a/module/applications/ui/countdownEdit.mjs b/module/applications/ui/countdownEdit.mjs index af1af46b..1dbc56be 100644 --- a/module/applications/ui/countdownEdit.mjs +++ b/module/applications/ui/countdownEdit.mjs @@ -153,6 +153,7 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio action: socketEvent.Refresh, data: { refreshType: RefreshType.Countdown } }); + Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Countdown }); } static #addCountdown() { diff --git a/module/applications/ui/countdowns.mjs b/module/applications/ui/countdowns.mjs index 996a46ea..5cf79100 100644 --- a/module/applications/ui/countdowns.mjs +++ b/module/applications/ui/countdowns.mjs @@ -12,11 +12,14 @@ const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; export default class DhCountdowns extends HandlebarsApplicationMixin(ApplicationV2) { previousCountdownData = null; + changedCountdownsForAnimation = new Set(); constructor(options = {}) { super(options); + this.previousCountdownData = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns).countdowns; + this.setupHooks(); } /** @inheritDoc */ @@ -77,15 +80,7 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application return frame; } - /** @inheritdoc */ - async _onFirstRender(context, options) { - await super._onFirstRender(context, options); - this._createContextMenu(this._getCountdownContextOptions, '.countdown-container[data-countdown]', { - parentClassHooks: false, fixed: true - }); - } - /** @inheritdoc */ async _onRender(context, options) { await super._onRender(context, options); @@ -100,7 +95,7 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application /* Handle animations to draw attention to countdown values changing */ const typesToAnimate = new Set(); - for (const countdownKey of options.animate ?? []) { + for (const countdownKey of this.changedCountdownsForAnimation) { const shimmerAnimation = [ { backgroundPositionX: '98%' }, { backgroundPositionX: '0%' } @@ -132,6 +127,8 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application const element = this.element.querySelector(`.header-type-toggles .header-type[data-type="${type}"]`); element?.animate(pulseAnimation, pulseTiming); } + + this.changedCountdownsForAnimation.clear(); } /** Returns countdown data filtered by ownership */ @@ -140,15 +137,17 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application const values = Object.entries(setting.countdowns).map(([key, countdown]) => ({ key, countdown, - ownership: countdown.getUserLevel(game.user) + ownership: DhCountdowns.#getPlayerOwnership(game.user, setting, countdown) })); return values.filter(v => v.ownership !== CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE); } _getCountdownData() { + const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns); + return this.#getCountdowns().reduce((acc, { key, countdown, ownership }) => { const playersWithAccess = game.users.reduce((acc, user) => { - const ownership = countdown.getUserLevel(user); + const ownership = DhCountdowns.#getPlayerOwnership(user, setting, countdown); if (!user.isGM && ownership && ownership !== CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE) { acc.push(user); } @@ -214,6 +213,19 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application return context; } + static #getPlayerOwnership(user, setting, countdown) { + if (user.isGM) return CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER; + + const playerOwnership = countdown.ownership[user.id]; + return playerOwnership === undefined || playerOwnership === CONST.DOCUMENT_OWNERSHIP_LEVELS.INHERIT + ? setting.defaultOwnership + : playerOwnership; + } + + cooldownRefresh = ({ refreshType }) => { + if (refreshType === RefreshType.Countdown) this.render(); + }; + static canPerformEdit() { if (game.user.isGM) return true; @@ -311,12 +323,18 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application action: socketEvent.Refresh, data: { refreshType: RefreshType.Countdown } }); + Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Countdown }); + } + + setupHooks() { + Hooks.on(socketEvent.Refresh, this.cooldownRefresh.bind()); } async close(options) { /* Opt out of Foundry's standard behavior of closing all application windows marked as UI when Escape is pressed */ if (options.closeKey) return; + Hooks.off(socketEvent.Refresh, this.cooldownRefresh); return super.close(options); } @@ -359,30 +377,4 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application refreshType: RefreshType.Countdown }); } - - /** - * @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - */ - _getCountdownContextOptions() { - /** @param {HTMLElement} element */ - const getCountdownFromElement = element => { - const id = element.closest('[data-countdown]').dataset.countdown; - if (!id) return null; - const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns); - return setting.countdowns[id ?? '']; - } - - return [ - { - label: 'CONTROLS.CommonDelete', - icon: 'fa-solid fa-trash', - visible: element => { - return getCountdownFromElement(element)?.isOwner; - }, - onClick: (_, target) => { - getCountdownFromElement(target)?.delete(); - } - } - ]; - } } diff --git a/module/applications/ui/fearTracker.mjs b/module/applications/ui/fearTracker.mjs index d1813736..8c247f79 100644 --- a/module/applications/ui/fearTracker.mjs +++ b/module/applications/ui/fearTracker.mjs @@ -13,14 +13,6 @@ const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV2) { constructor(options = {}) { super(options); - - this._dragData = { - isDragging: false, - startX: 0, - startY: 0, - startLeft: 0, - startTop: 0 - } } /** @inheritDoc */ @@ -29,20 +21,19 @@ export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV classes: [], tag: 'div', window: { - frame: false, + frame: true, title: 'DAGGERHEART.GENERAL.fear', positioned: true, resizable: true, minimizable: false }, - classes: ['daggerheart', 'dh-style', 'fear-tracker'], actions: { setFear: FearTracker.setFear, increaseFear: FearTracker.increaseFear }, position: { - width: 540, - height: 'auto' + width: 222, + height: 222 } }; @@ -62,10 +53,6 @@ export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV return game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxFear; } - get fearPosition() { - return game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance).fearPosition; - } - /* -------------------------------------------- */ /* Rendering */ /* -------------------------------------------- */ @@ -76,48 +63,15 @@ export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV current = this.currentFear, max = this.maxFear, percent = (current / max) * 100, - isGM = game.user.isGM, - locked = false, - isFree = this.fearPosition == 'free'; + isGM = game.user.isGM; - return { display, current, max, percent, isGM, locked, isFree }; + return { display, current, max, percent, isGM }; } /** @override */ - async _onRender(context, options) { - await super._onRender(context, options); - - this.#setupDragging(); - this.#setupResizing(); - - const fearPosition = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance).fearPosition; - - if (options.isFirstRender) FearTracker.handleOffSet(); - if (!options.force) return; - - this.handleStyleElement(fearPosition); - - switch (fearPosition) { - case 'topCenter': - document.getElementById('ui-top')?.appendChild(this.element); - break; - case 'bottomCenter': - document.getElementById('ui-bottom')?.prepend(this.element); - break; - case 'rightTop': - document.getElementById('ui-right-column-1')?.appendChild(this.element); - break; - case 'leftBottom': - document.getElementById('ui-left-column-1')?.insertBefore(this.element, document.getElementById('players')); - break; - - default: - document.body?.appendChild(this.element); - const position = - game.user.getFlag(CONFIG.DH.id, 'app.resources.position') ?? FearTracker.DEFAULT_OPTIONS.position; - this.setPosition(position); - break; - } + async _preFirstRender(context, options) { + options.position = + game.user.getFlag(CONFIG.DH.id, 'app.resources.position') ?? FearTracker.DEFAULT_OPTIONS.position; } /** @override */ @@ -126,31 +80,15 @@ export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear, this.maxFear); } - handleStyleElement(fearPosition) { - for (const position of Object.values(CONFIG.DH.GENERAL.fearPosition)) { - this.element.classList.remove(position.value); - } - - this.element.classList.add(fearPosition); - } - - static handleOffSet() { - const fearTracker = document.getElementById('resources'); - const hotbar = document.getElementById('hotbar'); - - if (!fearTracker) return; - - const offset = Number(hotbar.style.getPropertyValue('--offset').replace(/px$/, '')) || 0; - - if (offset > 0) return; - - fearTracker.style.setProperty('--offset', `${offset - 13}px`); - } - _onPosition(position) { game.user.setFlag(CONFIG.DH.id, 'app.resources.position', position); } + async close(options = {}) { + if (!options.allowed) return; + else super.close(options); + } + static async setFear(event, target) { if (!game.user.isGM) return; const fearCount = Number(target.dataset.index ?? 0); @@ -172,119 +110,4 @@ export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV value ); } - - // TODO: Remove methods later to use Foundry's dragger and resize methods - /* -------------------------------------------- */ - /* Dragging handlers */ - /* -------------------------------------------- */ - #setupDragging() { - const dragHandle = this.element.querySelector('.drag-handle'); - if (!dragHandle) return; - dragHandle.addEventListener('mousedown', this.#onDragStart.bind(this)); - } - - #onDragStart(event) { - if (event.button !== 0) return; - this._dragData.isDragging = true; - this._dragData.startX = event.clientX; - this._dragData.startY = event.clientY; - const rect = this.element.getBoundingClientRect(); - this._dragData.startLeft = rect.left; - this._dragData.startTop = rect.top; - this.element.style.cursor = 'grabbing'; - - this._dragHandler = this.#onDragging.bind(this); - this._dragEndHandler = this.#onDragEnd.bind(this); - window.addEventListener('mousemove', this._dragHandler); - window.addEventListener('mouseup', this._dragEndHandler); - } - - #onDragging(event) { - if (!this._dragData.isDragging) return; - - const dragX = event.clientX - this._dragData.startX; - const dragY = event.clientY - this._dragData.startY; - - this.element.style.left = `${this._dragData.startLeft + dragX}px`; - this.element.style.top = `${this._dragData.startTop + dragY}px`; - } - - #onDragEnd() { - if (!this._dragData.isDragging) return; - this._dragData.isDragging = false; - this.element.style.cursor = ''; - - if (this._dragHandler) window.removeEventListener('mousemove', this._dragHandler); - if (this._dragEndHandler) window.removeEventListener('mouseup', this._dragEndHandler); - - const rect = this.element.getBoundingClientRect(); - const pos = { top: rect.top, left: rect.left }; - - this.setPosition(pos); - } - - /* -------------------------------------------- */ - /* Resize handlers */ - /* -------------------------------------------- */ - - #setupResizing() { - const resizeHandle = this.element.querySelector('.resize-handle'); - if (!resizeHandle) return; - resizeHandle.addEventListener('mousedown', this.#onResizeStart.bind(this)); - } - - #onResizeStart(e) { - if (e.button !== 0) return; - e.stopPropagation(); - - let maxAllowedWidth = 10000; - - this._resizeData = { - isResizing: true, - startX: e.clientX, - startY: e.clientY, - startWidth: this.element.offsetWidth, - startHeight: this.element.offsetHeight, - maxAllowedWidth: Math.max(50, maxAllowedWidth) - }; - - this._resizeHandler = this.#onResizing.bind(this); - this._resizeEndHandler = this.#onResizeEnd.bind(this); - window.addEventListener('mousemove', this._resizeHandler); - window.addEventListener('mouseup', this._resizeEndHandler); - } - - #onResizing(e) { - if (!this._resizeData?.isResizing) return; - - const currentDx = e.clientX - this._resizeData.startX; - const potentialWidth = Math.max(50, this._resizeData.startWidth + currentDx); - - const width = Math.min(potentialWidth, this._resizeData.maxAllowedWidth); - - this.element.style.width = `${width}px`; - - if (width < 100) { - this.element.classList.add('narrow'); - } else { - this.element.classList.remove('narrow'); - } - } - - #onResizeEnd() { - if (!this._resizeData?.isResizing) return; - this._resizeData.isResizing = false; - - if (this._resizeHandler) window.removeEventListener('mousemove', this._resizeHandler); - if (this._resizeEndHandler) window.removeEventListener('mouseup', this._resizeEndHandler); - - let width = parseFloat(this.element.style.width); - - - if (isNaN(width)) { - width = this.element.getBoundingClientRect().width; - } - - this.setPosition({ width: width }); - } } diff --git a/module/config/generalConfig.mjs b/module/config/generalConfig.mjs index 3876f6b5..188efafb 100644 --- a/module/config/generalConfig.mjs +++ b/module/config/generalConfig.mjs @@ -825,7 +825,7 @@ export const refreshTypes = { export const itemAbilityCosts = { resource: { id: 'resource', - label: 'DAGGERHEART.GENERAL.Resource.single', + label: 'DAGGERHEART.GENERAL.resource', group: 'Global' }, quantity: { @@ -924,14 +924,6 @@ export const fearDisplay = { hide: { value: 'hide', label: 'DAGGERHEART.SETTINGS.Appearance.fearDisplay.hide' } }; -export const fearPosition = { - free: { value: 'free', label: 'DAGGERHEART.SETTINGS.Appearance.fearPosition.free' }, - topCenter: { value: 'topCenter', label: 'DAGGERHEART.SETTINGS.Appearance.fearPosition.topCenter' }, - bottomCenter: { value: 'bottomCenter', label: 'DAGGERHEART.SETTINGS.Appearance.fearPosition.bottomCenter' }, - rightTop: { value: 'rightTop', label: 'DAGGERHEART.SETTINGS.Appearance.fearPosition.rightTop' }, - leftBottom: { value: 'leftBottom', label: 'DAGGERHEART.SETTINGS.Appearance.fearPosition.leftBottom' } -}; - export const basicOwnershiplevels = { 0: { value: 0, label: 'OWNERSHIP.NONE' }, 2: { value: 2, label: 'OWNERSHIP.OBSERVER' }, diff --git a/module/config/settingsConfig.mjs b/module/config/settingsConfig.mjs index 92a3361c..50841084 100644 --- a/module/config/settingsConfig.mjs +++ b/module/config/settingsConfig.mjs @@ -27,10 +27,8 @@ export const menu = { }; export const gameSettings = { - /** @type {'Automation'} */ Automation: 'Automation', Metagaming: 'Metagaming', - /** @type {'Homebrew'} */ Homebrew: 'Homebrew', appearance: 'Appearance', GlobalOverrides: 'GlobalOverrides', @@ -39,7 +37,6 @@ export const gameSettings = { Fear: 'ResourcesFear' }, LevelTiers: 'LevelTiers', - /** @type {'Countdowns'} */ Countdowns: 'Countdowns', LastMigrationVersion: 'LastMigrationVersion', SpotlightRequestQueue: 'SpotlightRequestQueue', @@ -62,18 +59,3 @@ export const actionAutomationChoices = { label: 'DAGGERHEART.CONFIG.ActionAutomationChoices.always' } }; - -export const reloadChoices = { - off: { - id: 'off', - label: 'DAGGERHEART.CONFIG.ReloadChoices.off.label' - }, - manual: { - id: 'manual', - label: 'DAGGERHEART.CONFIG.ReloadChoices.manual.label' - }, - auto: { - id: 'auto', - label: 'DAGGERHEART.CONFIG.ReloadChoices.auto.label' - } -}; \ No newline at end of file diff --git a/module/config/system.mjs b/module/config/system.mjs index de6630ec..31dba518 100644 --- a/module/config/system.mjs +++ b/module/config/system.mjs @@ -12,7 +12,6 @@ import * as HOOKS from './hooksConfig.mjs'; import * as TRIGGER from './triggerConfig.mjs'; import * as ITEMBROWSER from './itemBrowserConfig.mjs'; -/** @type {"daggerheart"} */ export const SYSTEM_ID = 'daggerheart'; export const SYSTEM = { diff --git a/module/data/_types.d.ts b/module/data/_types.d.ts deleted file mode 100644 index f0e2ade5..00000000 --- a/module/data/_types.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { DhCountdown } from './countdowns.mjs' - -declare module './countdowns.mjs' { - export default interface DhCountdowns { - countdowns: Record; - } -} \ No newline at end of file diff --git a/module/data/action/attackAction.mjs b/module/data/action/attackAction.mjs index ba11c6ff..1988b1d8 100644 --- a/module/data/action/attackAction.mjs +++ b/module/data/action/attackAction.mjs @@ -13,19 +13,18 @@ export default class DHAttackAction extends DHDamageAction { if (this.damage.includeBase) { const baseDamage = this.getParentHitPointDamage(); if (baseDamage) { - if (!this.damage.main) { - this.damage.main = baseDamage; + if (!this.damage.parts.hitPoints) { + this.damage.parts.hitPoints = baseDamage; } else { - for (const type of baseDamage.type) this.damage.main.type.add(type); + for (const type of baseDamage.type) this.damage.parts.hitPoints.type.add(type); - this.damage.main.value.custom = { + this.damage.parts.hitPoints.value.custom = { enabled: true, - formula: `${baseDamage.value.getFormula()} + ${this.damage.main.value.getFormula()}` + formula: `${baseDamage.value.getFormula()} + ${this.damage.parts.hitPoints.value.getFormula()}` }; } } } - if (this.roll.useDefault) { this.roll.trait = this.item.system.attack.roll.trait; this.roll.type = 'attack'; @@ -34,28 +33,24 @@ export default class DHAttackAction extends DHDamageAction { } getParentHitPointDamage() { - return this.item?.system?.attack.damage.main; + return this.item?.system?.attack.damage.parts.hitPoints; } get damageFormula() { - const hitPointsPart = this.damage.main; + const hitPointsPart = this.damage.parts.hitPoints; if (!hitPointsPart) return '0'; return hitPointsPart.value.getFormula(); } get altDamageFormula() { - const hitPointsPart = this.damage.main; + const hitPointsPart = this.damage.parts.hitPoints; if (!hitPointsPart) return '0'; return hitPointsPart.valueAlt.getFormula(); } async use(event, options) { - if (this.item?.system.needsReload) { - return ui.notifications.error(_loc('DAGGERHEART.UI.Notifications.reloadRequired', { weapon: this.item.name })); - } - const result = await super.use(event, options); if (result?.message?.system.action?.roll?.type === 'attack') { @@ -66,23 +61,6 @@ export default class DHAttackAction extends DHDamageAction { return result; } - async handleReload(options = { awaitRoll: false }) { - const roll = await new Roll('1d6').evaluate(); - if (game.modules.get('dice-so-nice')?.active) { - if (options.awaitRoll) - await game.dice3d.showForRoll(roll, game.user, true); - else - game.dice3d.showForRoll(roll, game.user, true); - } - - const needsReload = roll.total === 1; - if (needsReload) { - this.item.update({ 'system.resource.value': 0 }); - } - - return { needsReload, rollValue: roll.total }; - } - /** * Generate a localized label array for this item subtype. * @returns {(string | { value: string, icons: string[] })[]} An array of localized strings and damage label objects. @@ -95,7 +73,7 @@ export default class DHAttackAction extends DHDamageAction { if (range) labels.push(game.i18n.localize(`DAGGERHEART.CONFIG.Range.${range}.short`)); const useAltDamage = this.actor?.effects?.find(x => x.type === 'horde')?.active; - for (const { value, valueAlt, type } of [damage.main, ...damage.resources].filter(d => !!d)) { + for (const { value, valueAlt, type } of damage.parts) { const usedValue = useAltDamage ? valueAlt : value; const damageString = Roll.replaceFormulaData(usedValue.getFormula(), this.actor?.getRollData() ?? {}); const str = damageString @@ -104,7 +82,7 @@ export default class DHAttackAction extends DHDamageAction { x: game.i18n.localize('DAGGERHEART.GENERAL.damage') }); - const icons = Array.from(type ?? []) + const icons = Array.from(type) .map(t => CONFIG.DH.GENERAL.damageTypes[t]?.icon) .filter(Boolean); diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index 7c783f05..be7224cd 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -256,8 +256,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel if (Hooks.call(`${CONFIG.DH.id}.postUseAction`, this, config) === false) return; - if (this.chatDisplay && !config.skips.createMessage && !config.actionChatMessageHandled) - await this.toChat(null, config); + if (this.chatDisplay && !config.skips.createMessage && !config.actionChatMessageHandled) await this.toChat(); return config; } @@ -289,6 +288,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel hasEffect: this.hasEffect, hasSave: this.hasSave, onSave: this.save?.damageMod, + isDirect: !!this.damage?.direct, selectedMessageMode: game.settings.get('core', 'messageMode'), data: this.getRollData(), evaluate: this.hasRoll, @@ -306,20 +306,20 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel }; if (this.damage) { - config.isDirect = !!this.damage.main?.direct; + config.isDirect = this.damage.direct; - const groupAttackTokens = this.damage.main?.groupAttack + const groupAttackTokens = this.damage.groupAttack ? game.system.api.fields.ActionFields.DamageField.getGroupAttackTokens( this.actor.id, - this.damage.main.groupAttack + this.damage.groupAttack ) : null; config.damageOptions = { - groupAttack: this.damage.main?.groupAttack + groupAttack: this.damage.groupAttack ? { numAttackers: Math.max(groupAttackTokens.length, 1), - range: this.damage.main.groupAttack + range: this.damage.groupAttack } : null }; @@ -429,11 +429,11 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel } get hasDamage() { - return this.type !== 'healing' && (Boolean(this.damage?.main) || !foundry.utils.isEmpty(this.damage?.resources)); + return Boolean(Object.keys(this.damage?.parts ?? {}).length) && this.type !== 'healing'; } get hasHealing() { - return this.type === 'healing' && !foundry.utils.isEmpty(this.damage?.resources); + return Boolean(Object.keys(this.damage?.parts ?? {}).length) && this.type === 'healing'; } get hasSave() { @@ -469,25 +469,6 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel return acc; }, {}); } - - if (source.damage?.parts && !source.damage.resources && !source.damage.main) { - source.damage.main = null; - source.damage.resources = {}; - for (const [partKey, part] of Object.entries(source.damage.parts)) { - if (partKey === 'hitPoints' && source.type !== 'healing') { - source.damage.main = { - ...part, - includeBase: source.damage.includeBase, - direct: source.damage.direct, - groupAttack: source.damage.groupAttack - }; - } else { - source.damage.resources[partKey] = part; - } - } - - delete source.damage.parts; - } } } diff --git a/module/data/action/damageAction.mjs b/module/data/action/damageAction.mjs index 15135e0d..51735543 100644 --- a/module/data/action/damageAction.mjs +++ b/module/data/action/damageAction.mjs @@ -8,8 +8,11 @@ export default class DHDamageAction extends DHBaseAction { * @returns Formula string */ getDamageFormula() { - if (!this.damage.main) return ''; + const strings = []; + for (const { value } of this.damage.parts) { + strings.push(Roll.replaceFormulaData(value.getFormula(), this.actor?.getRollData() ?? {})); + } - return Roll.replaceFormulaData(this.damage.main.value.getFormula(), this.actor?.getRollData() ?? {}); + return strings.join(' + '); } } diff --git a/module/data/actor/adversary.mjs b/module/data/actor/adversary.mjs index 2640211f..ae17c128 100644 --- a/module/data/actor/adversary.mjs +++ b/module/data/actor/adversary.mjs @@ -84,11 +84,13 @@ export default class DhpAdversary extends DhCreature { type: 'attack' }, damage: { - main: { - type: ['physical'], - applyTo: 'hitPoints', - value: { - multiplier: 'flat' + parts: { + hitPoints: { + type: ['physical'], + applyTo: 'hitPoints', + value: { + multiplier: 'flat' + } } } } diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index 8ff50a5d..b39c64aa 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -104,13 +104,15 @@ export default class DhCharacter extends DhCreature { trait: 'strength' }, damage: { - main: { - type: ['physical'], - applyTo: 'hitPoints', - value: { - custom: { - enabled: true, - formula: '@profd4' + parts: { + hitPoints: { + type: ['physical'], + applyTo: 'hitPoints', + value: { + custom: { + enabled: true, + formula: '@profd4' + } } } } @@ -836,7 +838,7 @@ export default class DhCharacter extends DhCreature { isReversed: true }; - this.attack.damage.main.value.custom.formula = `@prof${this.basicAttackDamageDice}${this.rules.attack.damage.bonus ? ` + ${this.rules.attack.damage.bonus}` : ''}`; + this.attack.damage.parts.hitPoints.value.custom.formula = `@prof${this.basicAttackDamageDice}${this.rules.attack.damage.bonus ? ` + ${this.rules.attack.damage.bonus}` : ''}`; // Clamp resources (must be done last to ensure all updates occur) this.resources.clamp(); diff --git a/module/data/actor/tierAdjustment.mjs b/module/data/actor/tierAdjustment.mjs index 4bf74a45..8b9e5bdc 100644 --- a/module/data/actor/tierAdjustment.mjs +++ b/module/data/actor/tierAdjustment.mjs @@ -40,17 +40,17 @@ export function getTierAdjustedAdversary(source, tier) { // Store initial attack damage for abilities that have you deal a "standard attack" const initialAttack = { - type: source.system.attack.damage?.main?.type?.toSorted(), - value: getFormula(source.system.attack.damage?.main?.value) + type: source.system.attack.damage?.parts.hitPoints?.type?.toSorted(), + value: getFormula(source.system.attack.damage?.parts.hitPoints?.value) }; // Update damage of base attack. try { const damage = source.system.attack.damage; - if (!damage?.main) throw new Error('Unexpected missing damage in adversary'); + if (!damage?.parts.hitPoints) throw new Error('Unexpected missing attack in adversary'); for (const property of ['value', 'valueAlt']) { - const data = damage.main[property]; + const data = damage.parts.hitPoints[property]; const previousFormula = getFormula(data); const value = calculateAdjustedDamage(previousFormula, 'attack', damageMeta); applyAdjustedDamage(data, value); @@ -82,12 +82,12 @@ export function getTierAdjustedAdversary(source, tier) { // Update damage in item actions and convert all formula matches in the descriptions to the new damage for (const action of Object.values(item.system.actions)) { - if (!action.damage?.main) continue; + if (!action.damage?.parts.hitPoints) continue; try { // Apply conversions and save a record. If it matches attack damage *and* Its not in the description, use attack conversion instead const result = []; for (const property of ['value', 'valueAlt']) { - const { [property]: data, type: damageType } = action.damage.main; + const { [property]: data, type: damageType } = action.damage.parts.hitPoints; const previousFormula = getFormula(data); const isActuallyAttack = previousFormula === initialAttack.value && @@ -199,7 +199,7 @@ function calculateAdjustedDamage(formula, type, { currentDamageRange, newDamageR } /** - * Get formula from either damage data *or* a simple formula object. + * Get formula from either damage parts *or* a simple formula object. * @returns {string} the new formula data */ function getFormula(data) { diff --git a/module/data/chat-message/actorRoll.mjs b/module/data/chat-message/actorRoll.mjs index 3dcf2a8d..ccfe25ea 100644 --- a/module/data/chat-message/actorRoll.mjs +++ b/module/data/chat-message/actorRoll.mjs @@ -1,5 +1,4 @@ import { triggerChatRollFx } from '../../helpers/utils.mjs'; -import { ChatDamageData } from './chatDamageData.mjs'; const fields = foundry.data.fields; @@ -42,7 +41,6 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel { hasEffect: new fields.BooleanField({ initial: false }), hasSave: new fields.BooleanField({ initial: false }), hasTarget: new fields.BooleanField({ initial: false }), - reloadCheckValue: new fields.NumberField({ integer: true, nullable: true, initial: null }), isDirect: new fields.BooleanField({ initial: false }), onSave: new fields.StringField(), source: new fields.SchemaField({ @@ -51,7 +49,7 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel { originItem: originItemField(), action: new fields.StringField() }), - damage: new fields.EmbeddedDataField(ChatDamageData), + damage: new fields.ObjectField(), damageOptions: new fields.ObjectField(), costs: new fields.ArrayField(new fields.ObjectField()), successConsumed: new fields.BooleanField({ initial: false }) @@ -76,14 +74,10 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel { return fromUuidSync(this.source.actor); } - get item() { + get actionItem() { const actionActor = this.actionActor; if (!actionActor || !this.source.item) return null; - - return actionActor.items.get(this.source.item); - } - get actionItem() { switch (this.source.originItem.type) { case CONFIG.DH.ITEM.originItemType.restMove: const restMoves = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).restMoves; @@ -91,18 +85,11 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel { this.source.originItem.actionIndex ]; default: - return this.item?.system.actionsList?.find(a => a.id === this.source.action); + const item = actionActor.items.get(this.source.item); + return item ? item.system.actionsList?.find(a => a.id === this.source.action) : null; } } - get hasReload() { - return this.item?.system.hasReload; - } - - get reloadCheckFailed() { - return this.reloadCheckValue === 1; - } - get action() { const { actionActor, actionItem: itemAction } = this; if (!this.source.action) return null; @@ -145,21 +132,28 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel { }); } + /* TODO: Change how damage data is stored somehow to enable better rerolling */ async getRerolledDamage() { - if (!this.damage.active) return; + if (!this.damage) return; const rerolls = []; - const update = { system: { damage: { main: null, resources: _replace({}) } } }; - if (this.damage.main) { - const reroll = await this.damage.main.reroll(); - rerolls.push(reroll); - update.system.damage.main = reroll.toJSON(); - } + const update = { system: { damage: {} } }; + for (const partKey in this.damage) { + const part = this.damage[partKey]; + const testRoll = Roll.fromData(part.parts[0].roll); + const rerolled = await testRoll.reroll(); + rerolls.push(rerolled); - for (const key of Object.keys(this.damage.resources)) { - const reroll = await this.damage.resources[key].reroll(); - rerolls.push(reroll); - update.system.damage.resources[key] = reroll.toJSON(); + if (!update.system.damage[partKey]) update.system.damage[partKey] = { parts: [part.parts[0]] }; + const partData = update.system.damage[partKey].parts[0]; + update.system.damage[partKey].total = rerolled.total; + partData.modifierTotal = rerolled.terms.reduce((acc, x) => { + if (x.isDeterministic && !x.operator) acc += x.total; + return acc; + }, 0); + partData.dice = rerolled.dice.map(d => ({ ...d.toJSON(), dice: d.denomination })); + partData.total = rerolled.total; + partData.roll = rerolled.toJSON(); } await triggerChatRollFx(rerolls); @@ -204,43 +198,6 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel { this.isGM = game.user.isGM; //temp } - static migrateData(source) { - const { main, resources, ...flatDamageKeys } = source.damage ?? {}; - if (source.damage && !main && !resources) { - source.damage.main = null; - source.damage.resources = {}; - - const getRoll = key => { - const damageData = source.damage[key]; - const oldRoll = damageData.parts[0]?.roll; - return oldRoll ? JSON.stringify({ - ...oldRoll, - class: 'BaseRoll', - options: { - ...oldRoll.options, - damageTypes: damageData.parts[0].damageTypes ?? [] - } - }) : null; - }; - - for (const key of Object.keys(flatDamageKeys)) { - if (key === 'hitPoints' && source.hasDamage && !source.hasHealing) { - source.damage.main = getRoll('hitPoints'); - } - else { - source.damage.resources[key] = getRoll(key); - } - } - } - - for (const key of Object.keys(flatDamageKeys)) { - delete source.damage[key]; - } - - return source; - } - - getTargetList() { const targets = this.targetMode && this.parent.isAuthor diff --git a/module/data/chat-message/chatDamageData.mjs b/module/data/chat-message/chatDamageData.mjs deleted file mode 100644 index 404cda43..00000000 --- a/module/data/chat-message/chatDamageData.mjs +++ /dev/null @@ -1,58 +0,0 @@ -import { triggerChatRollFx } from '../../helpers/utils.mjs'; - -export class ChatDamageData extends foundry.abstract.DataModel { - constructor(data = {}, options = {}) { - super(data, options); - - this._prepareRolls(); - } - - static defineSchema() { - const fields = foundry.data.fields; - - return { - main: new fields.JSONField({ nullable: true, validate: ChatDamageData.#validateRoll}), - resources: new fields.TypedObjectField(new fields.JSONField({validate: ChatDamageData.#validateRoll})) - }; - } - - get active() { - return !!this.main || Boolean(Object.keys(this.resources).length); - } - - static #validateRoll(rollJSON) { - if (rollJSON) { - const roll = JSON.parse(rollJSON); - if (!roll.evaluated) throw new Error('Roll objects added to ChatMessage documents must be evaluated'); - } - } - - _prepareRolls() { - this.main &&= Roll.fromData(this.main); - for (const key of Object.keys(this.resources)) { - this.resources[key] = Roll.fromData(this.resources[key]); - } - } - - async rerollDamageDie(isResource, damageType, dice, resultIndex) { - const reroll = isResource ? this.resources[damageType] : this.main; - const rerollDice = reroll.dice[dice]; - await rerollDice.rerollResult(resultIndex); - await reroll._evaluate(); - - const rerolledResult = rerollDice.results[rerollDice.results.length - 1]; - if (rerolledResult) { - const fakeRoll = { - _evaluated: true, - dice: [new foundry.dice.terms.Die({ - ...rerollDice, - results: [rerolledResult], - total: rerolledResult.value, - faces: rerollDice.faces - })], - options: { appearance: {} } - }; - await triggerChatRollFx([fakeRoll]); - } - } -} \ No newline at end of file diff --git a/module/data/countdowns.mjs b/module/data/countdowns.mjs index 5fe01e20..ffe4d26b 100644 --- a/module/data/countdowns.mjs +++ b/module/data/countdowns.mjs @@ -1,4 +1,3 @@ -import { RefreshType, socketEvent } from '../systemRegistration/socket.mjs'; import FormulaField from './fields/formulaField.mjs'; export default class DhCountdowns extends foundry.abstract.DataModel { @@ -15,36 +14,19 @@ export default class DhCountdowns extends foundry.abstract.DataModel { }; } - /** @inheritdoc */ - _initialize(options) { - super._initialize(options); - for (const [id, countdown] of Object.entries(this.countdowns)) { - countdown.id = id; - } - } - - async handleChange() { + handleChange() { const previousCountdowns = foundry.ui.countdowns.previousCountdownData; const changedCountdowns = Object.entries(this.countdowns).reduce((acc, [key, countdown]) => { - const previous = previousCountdowns[key]; - const currentChanged = !previous || (previous.progress.current !== countdown.progress.current); - if (currentChanged && previous?.progress.start === countdown.progress.start) { + const previousCountdown = previousCountdowns[key]; + if (!previousCountdown || (previousCountdown.progress.current !== countdown.progress.current)) { acc.push(key); } + return acc; }, []); - // Re-render countdowns applications. When the change is due to an actual update, resync the editor - if (!foundry.utils.equals(previousCountdowns, this.countdowns)) { - await foundry.ui.countdowns.render({ animate: changedCountdowns }); - for (const instance of game.system.api.applications.ui.CountdownEdit.instances()) { - instance.data = this; - await instance.render(); - } - } - - // Inform modules of updates - Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Countdown }); + for (const countdownKey of changedCountdowns) + foundry.ui.countdowns.changedCountdownsForAnimation.add(countdownKey); } static migrateData(source) { @@ -178,14 +160,6 @@ export class DhCountdown extends foundry.abstract.DataModel { }, {}); } - /** - * A boolean indicator for whether the current game User has ownership rights for this countdown - * @returns {boolean} - */ - get isOwner() { - return this.getUserLevel(game.user) === CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER; - } - /** @inheritDoc */ static migrateData(source) { if (source.progress.max) { @@ -196,29 +170,4 @@ export class DhCountdown extends foundry.abstract.DataModel { return super.migrateData(source); } - - /** - * Get the explicit permission level that a User has over this Document, a value in CONST.DOCUMENT_OWNERSHIP_LEVELS. - * Compendium content ignores the ownership field in favor of User role-based ownership. Otherwise, Documents use - * granular per-User ownership definitions and Embedded Documents defer to their parent ownership. - * - * @param {BaseUser} [user=game.user] The User being tested - * @returns {DocumentOwnershipNumber} A numeric permission level from {@link CONST.DOCUMENT_OWNERSHIP_LEVELS} - */ - getUserLevel(user) { - if (user.isGM) return CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER; - - const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns); - const playerOwnership = this.ownership[user.id]; - return playerOwnership === undefined || playerOwnership === CONST.DOCUMENT_OWNERSHIP_LEVELS.INHERIT - ? setting.defaultOwnership - : playerOwnership; - } - - async delete() { - const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns); - const data = foundry.utils.deepClone(setting._source); - delete data.countdowns[this.id]; - await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, data); - } } diff --git a/module/data/fields/action/damageField.mjs b/module/data/fields/action/damageField.mjs index 25b3ba97..9b21d3ba 100644 --- a/module/data/fields/action/damageField.mjs +++ b/module/data/fields/action/damageField.mjs @@ -12,10 +12,20 @@ export default class DamageField extends fields.SchemaField { /** @inheritDoc */ constructor(options, context = {}) { - super({ - main: new fields.EmbeddedDataField(DHDamageData, { nullable: true }), - resources: new IterableTypedObjectField(DHResourceData) - }, options, context); + const damageFields = { + parts: new IterableTypedObjectField(DHDamageData), + includeBase: new fields.BooleanField({ + initial: false, + label: 'DAGGERHEART.ACTIONS.Settings.includeBase.label' + }), + direct: new fields.BooleanField({ initial: false, label: 'DAGGERHEART.CONFIG.DamageType.direct.name' }), + groupAttack: new fields.StringField({ + choices: CONFIG.DH.GENERAL.groupAttackRange, + blank: true, + label: 'DAGGERHEART.ACTIONS.Settings.groupAttack.label' + }) + }; + super(damageFields, options, context); } /** @@ -31,23 +41,25 @@ export default class DamageField extends fields.SchemaField { this.hasRoll && DamageField.getAutomation() === CONFIG.DH.SETTINGS.actionAutomationChoices.never.id && !force - ) { + ) return; - } - const damageFormula = this.damage.main ? - DamageField.formatFormulas.call(this, [this.damage.main], config)[0] : null; - const resourceFormulas = DamageField.formatFormulas.call(this, this.damage.resources, config); + let formulas = this.damage.parts.map(p => ({ + formula: DamageField.getFormulaValue.call(this, p, config).getFormula(this.actor), + damageTypes: p.applyTo === 'hitPoints' && !p.type.size ? new Set(['physical']) : p.type, + applyTo: p.applyTo + })); - if (!damageFormula && !resourceFormulas.length) return false; + if (!formulas.length) return false; + + formulas = DamageField.formatFormulas.call(this, formulas, config); messageId = config.message?._id ?? messageId; const message = game.messages.get(messageId); const damageConfig = { dialog: {}, ...config, - damageFormula, - resourceFormulas, + roll: formulas, data: this.getRollData(), isCritical: Boolean(message?.system.roll?.isCritical) }; @@ -80,7 +92,7 @@ export default class DamageField extends fields.SchemaField { const targetDamage = []; const damagePromises = []; - for (const target of targets) { + for (let target of targets) { const actor = foundry.utils.fromUuidSync(target.actorId); if (!actor) continue; if (!config.hasHealing && config.onSave && target.saved?.success === true) { @@ -102,12 +114,13 @@ export default class DamageField extends fields.SchemaField { actor.takeHealing(config.damage).then(updates => targetDamage.push({ token, updates })) ); else { - const configDamage = config.damage.clone(); - configDamage.main &&= configDamage.main.toJSON(); - if (configDamage.main) { - const multiplier = config.actionActor?.system.rules?.attack?.damage?.hpDamageMultiplier ?? 1; - const takenMultiplier = actor.system.rules?.attack?.damage?.hpDamageTakenMultiplier; - configDamage.main.total = Math.ceil(configDamage.main.total * multiplier * takenMultiplier); + const configDamage = foundry.utils.deepClone(config.damage); + const hpDamageMultiplier = config.actionActor?.system.rules?.attack?.damage?.hpDamageMultiplier ?? 1; + const hpDamageTakenMultiplier = actor.system.rules?.attack?.damage?.hpDamageTakenMultiplier; + if (configDamage.hitPoints) { + for (const part of configDamage.hitPoints.parts) { + part.total = Math.ceil(part.total * hpDamageMultiplier * hpDamageTakenMultiplier); + } } damagePromises.push( @@ -170,19 +183,13 @@ export default class DamageField extends fields.SchemaField { /** * Prepare formulas for Damage Roll * Must be called within Action context or similar. - * @param {DHResourceData[]} damageData Array of DHResourceData + * @param {object[]} formulas Array of formatted formulas object * @param {object} data Action getRollData * @returns */ - static formatFormulas(damageData, data) { - const formulas = damageData.map(x => ({ - formula: DamageField.getFormulaValue.call(this, x, data).getFormula(this.actor), - damageTypes: x.type ?? new Set(), - applyTo: x.applyTo - })); - + static formatFormulas(formulas, data) { const formattedFormulas = []; - for (const formula of formulas) { + formulas.forEach(formula => { if (isNaN(formula.formula)) formula.formula = Roll.replaceFormulaData(formula.formula, this.getRollData(data)); const same = formattedFormulas.find( @@ -190,8 +197,7 @@ export default class DamageField extends fields.SchemaField { ); if (same) same.formula += ` + ${formula.formula}`; else formattedFormulas.push(formula); - } - + }); return formattedFormulas; } @@ -269,18 +275,10 @@ export class DHActionDiceData extends foundry.abstract.DataModel { }; } - /** - * @returns {string} the formula associated with this damage field - */ getFormula() { - if (this.custom.enabled) return this.custom.formula; - - const multiplier = this.multiplier === 'flat' ? this.flatMultiplier : `@${this.multiplier}`; - if (!multiplier) return String(this.bonus || 0); - - const dice = `${multiplier ?? 1}${this.dice}`; - const sign = this.bonus < 0 ? ' - ' : ' + '; - return this.bonus ? `${dice} ${sign} ${Math.abs(this.bonus)}` : dice; + const multiplier = this.multiplier === 'flat' ? this.flatMultiplier : `@${this.multiplier}`, + bonus = this.bonus ? (this.bonus < 0 ? ` - ${Math.abs(this.bonus)}` : ` + ${this.bonus}`) : ''; + return this.custom.enabled ? this.custom.formula : `${multiplier ?? 1}${this.dice}${bonus}`; } } @@ -288,7 +286,6 @@ export class DHResourceData extends foundry.abstract.DataModel { /** @override */ static defineSchema() { return { - base: new fields.BooleanField({ initial: false, readonly: true, label: 'Base' }), applyTo: new fields.StringField({ choices: CONFIG.DH.GENERAL.healingTypes, required: true, @@ -311,16 +308,7 @@ export class DHDamageData extends DHResourceData { static defineSchema() { return { ...super.defineSchema(), - includeBase: new fields.BooleanField({ - initial: false, - label: 'DAGGERHEART.ACTIONS.Settings.includeBase.label' - }), - direct: new fields.BooleanField({ initial: false, label: 'DAGGERHEART.CONFIG.DamageType.direct.name' }), - groupAttack: new fields.StringField({ - choices: CONFIG.DH.GENERAL.groupAttackRange, - blank: true, - label: 'DAGGERHEART.ACTIONS.Settings.groupAttack.label' - }), + base: new fields.BooleanField({ initial: false, readonly: true, label: 'Base' }), type: new fields.SetField( new fields.StringField({ choices: CONFIG.DH.GENERAL.damageTypes, diff --git a/module/data/fields/action/summonField.mjs b/module/data/fields/action/summonField.mjs index fef6625a..6845d2ba 100644 --- a/module/data/fields/action/summonField.mjs +++ b/module/data/fields/action/summonField.mjs @@ -23,7 +23,7 @@ export default class DHSummonField extends fields.ArrayField { super(summonFields, options, context); } - static async execute(config) { + static async execute() { if (!canvas.scene) { ui.notifications.warn(game.i18n.localize('DAGGERHEART.ACTIONS.TYPES.summon.error')); return; @@ -36,7 +36,6 @@ export default class DHSummonField extends fields.ArrayField { const rolls = []; const summonData = []; - const chatMessageData = []; for (const summon of this.summon) { const roll = new Roll(itemAbleRollParse(summon.count, this.actor, this.item)); await roll.evaluate(); @@ -55,18 +54,17 @@ export default class DHSummonField extends fields.ArrayField { tokenPreviewName: `${actor.prototypeToken.name}${remaining > 1 ? ` (${remaining}x)` : ''}` }); } - - chatMessageData.push({ - data: actor, - quantity: countNumber - }); } if (rolls.length) await triggerChatRollFx(rolls); this.actor.sheet?.minimize(); - await CONFIG.ux.TokenManager.createTokensWithPreview(summonData, { elevation: this.actor.token?.elevation }); - this.actor.sheet?.maximize(); - config.summonData = chatMessageData; + DHSummonField.handleSummon(summonData, this.actor); + } + + static async handleSummon(summonData, actionActor) { + await CONFIG.ux.TokenManager.createTokensWithPreview(summonData, { elevation: actionActor.token?.elevation }); + + return actionActor.sheet?.maximize(); } } diff --git a/module/data/fields/actionField.mjs b/module/data/fields/actionField.mjs index f3bdce8a..83672c8e 100644 --- a/module/data/fields/actionField.mjs +++ b/module/data/fields/actionField.mjs @@ -238,7 +238,6 @@ export function ActionMixin(Base) { result = this.parent; } else { result = await this.item.update({ [path]: updates }, options); - if (!result) return result; } return this.inCollection @@ -270,7 +269,10 @@ export function ActionMixin(Base) { return this.delete(); } - async toChat(origin, config) { + async toChat(origin) { + const autoExpandDescription = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance) + .expandRollMessage?.desc; + const cls = getDocumentClass('ChatMessage'); const systemData = { title: game.i18n.localize('DAGGERHEART.CONFIG.FeatureForm.action'), @@ -280,7 +282,7 @@ export function ActionMixin(Base) { img: this.baseAction ? this.parent.parent.img : this.img, tags: this.tags ? this.tags : ['Spell', 'Arcana', 'Lv 10'], areas: this.areas, - summon: config?.summonData + summon: this.summon }, source: { actor: this.actor.uuid, @@ -305,7 +307,7 @@ export function ActionMixin(Base) { system: systemData, content: await foundry.applications.handlebars.renderTemplate( 'systems/daggerheart/templates/ui/chat/action.hbs', - systemData + { ...systemData, open: autoExpandDescription ? 'open' : '' } ), flags: { daggerheart: { diff --git a/module/data/item/base.mjs b/module/data/item/base.mjs index 095ba8f2..ba114fda 100644 --- a/module/data/item/base.mjs +++ b/module/data/item/base.mjs @@ -49,10 +49,7 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel { }) }; - if (this.metadata.hasDescription) { - schema.description = new fields.HTMLField({ required: true, nullable: true }); - schema.gmNotes = new fields.HTMLField({ required: true, nullable: true }); - } + if (this.metadata.hasDescription) schema.description = new fields.HTMLField({ required: true, nullable: true }); if (this.metadata.hasResource) { schema.resource = new fields.SchemaField( @@ -137,7 +134,7 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel { /** * Augments the description for the item with type specific info to display. Implemented in applicable item subtypes. * @param {object} [options] - Options that modify the styling of the rendered template. { headerStyle: undefined|'none'|'large' } - * @returns {Promise<{ prefix: string | null; value: string | null; suffix: string | null }>} + * @returns {string} */ async getDescriptionData(_options) { return { prefix: null, value: this.description, suffix: null }; @@ -146,26 +143,16 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel { /** * Gets the enriched and augmented description for the item. * @param {object} [options] - Options that modify the styling of the rendered template. { headerStyle: undefined|'none'|'large' } - * @returns {Promise} + * @returns {string} */ - async getEnrichedDescription({ gmNotes = true } = {}) { + async getEnrichedDescription() { if (!this.metadata.hasDescription) return ''; const { prefix, value, suffix } = await this.getDescriptionData(); - let fullDescription = [prefix, value, suffix].filter(p => !!p).join('\n
\n'); - if (this.gmNotes && gmNotes) { - const gmNotesElement = document.createElement('section'); - gmNotesElement.classList.add('gm-notes-section'); - gmNotesElement.dataset.visibility = 'gm'; - const header = document.createElement('header'); - header.classList.add('gm-notes'); - header.textContent = _loc('DAGGERHEART.ITEMS.FIELDS.gmNotes.label'); - gmNotesElement.innerHTML = header.outerHTML + this.gmNotes; - fullDescription += gmNotesElement.outerHTML; - } + const fullDescription = [prefix, value, suffix].filter(p => !!p).join('\n
\n'); return await foundry.applications.ux.TextEditor.implementation.enrichHTML(fullDescription, { - relativeTo: this.parent, + relativeTo: this, rollData: this.getRollData(), secrets: this.parent.isOwner }); diff --git a/module/data/item/weapon.mjs b/module/data/item/weapon.mjs index 3d50e68a..42326f93 100644 --- a/module/data/item/weapon.mjs +++ b/module/data/item/weapon.mjs @@ -67,11 +67,13 @@ export default class DHWeapon extends AttachableItem { type: 'attack' }, damage: { - main: { - type: ['physical'], - value: { - multiplier: 'prof', - dice: 'd8' + parts: { + hitPoints: { + type: ['physical'], + value: { + multiplier: 'prof', + dice: 'd8' + } } } } @@ -116,14 +118,6 @@ export default class DHWeapon extends AttachableItem { return this.weaponFeatures; } - get hasReload() { - return Boolean(this.weaponFeatures.find(x => x.value === 'reloading')); - } - - get needsReload() { - return this.hasReload && this.resource.value === 0; - } - /**@inheritdoc */ async getDescriptionData() { const baseDescription = this.description; @@ -236,12 +230,11 @@ export default class DHWeapon extends AttachableItem { game.i18n.localize(`DAGGERHEART.CONFIG.Burden.${burden}`) ]; - if (attack.damage.main) { - const { value, type } = attack.damage.main; + for (const { value, type } of attack.damage.parts) { const parts = value.custom.enabled ? [game.i18n.localize('DAGGERHEART.GENERAL.custom')] : [value.dice]; if (!value.custom.enabled && value.bonus) parts.push(value.bonus.signedString()); - if (type?.size) { + if (type.size > 0) { const typeTags = Array.from(type) .map(t => game.i18n.localize(`DAGGERHEART.CONFIG.DamageType.${t}.abbreviation`)) .join(' | '); @@ -250,7 +243,7 @@ export default class DHWeapon extends AttachableItem { tags.push(parts.join('')); } - + return tags; } @@ -265,10 +258,10 @@ export default class DHWeapon extends AttachableItem { if (roll.trait) labels.push(game.i18n.localize(`DAGGERHEART.CONFIG.Traits.${roll.trait}.short`)); if (range) labels.push(game.i18n.localize(`DAGGERHEART.CONFIG.Range.${range}.short`)); - for (const { value, type } of [damage.main, ...damage.resources].filter(d => !!d)) { + for (const { value, type } of damage.parts) { const str = Roll.replaceFormulaData(value.getFormula(), this.actor?.getRollData() ?? {}); - const icons = Array.from(type ?? []) + const icons = Array.from(type) .map(t => CONFIG.DH.GENERAL.damageTypes[t]?.icon) .filter(Boolean); diff --git a/module/data/settings/Appearance.mjs b/module/data/settings/Appearance.mjs index 16f5fac0..4db27be0 100644 --- a/module/data/settings/Appearance.mjs +++ b/module/data/settings/Appearance.mjs @@ -41,11 +41,6 @@ export default class DhAppearance extends foundry.abstract.DataModel { choices: CONFIG.DH.GENERAL.fearDisplay, initial: CONFIG.DH.GENERAL.fearDisplay.token.value }), - fearPosition: new StringField({ - required: true, - choices: CONFIG.DH.GENERAL.fearPosition, - initial: CONFIG.DH.GENERAL.fearPosition.topCenter.value - }), displayCountdownUI: new BooleanField({ initial: true }), diceSoNice: new SchemaField({ hope: diceStyle({ fg: '#ffffff', bg: '#ffe760', outline: '#000000', edge: '#ffffff' }), diff --git a/module/data/settings/Automation.mjs b/module/data/settings/Automation.mjs index 523ddaac..35e87327 100644 --- a/module/data/settings/Automation.mjs +++ b/module/data/settings/Automation.mjs @@ -196,13 +196,6 @@ export default class DhAutomation extends foundry.abstract.DataModel { }) }) }), - reload: new fields.StringField({ - required: true, - choices: CONFIG.DH.SETTINGS.reloadChoices, - initial: CONFIG.DH.SETTINGS.reloadChoices.manual.id, - label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.reload.label', - hint: 'DAGGERHEART.SETTINGS.Automation.FIELDS.reload.hint' - }), autoExpireActiveEffects: new fields.BooleanField({ required: true, initial: true, diff --git a/module/data/tagTeamData.mjs b/module/data/tagTeamData.mjs index ef6536f1..640c2f6c 100644 --- a/module/data/tagTeamData.mjs +++ b/module/data/tagTeamData.mjs @@ -1,5 +1,3 @@ -import { ChatDamageData } from './chat-message/chatDamageData.mjs'; - export default class TagTeamData extends foundry.abstract.DataModel { static defineSchema() { const fields = foundry.data.fields; @@ -39,7 +37,6 @@ export class MemberData extends foundry.abstract.DataModel { }), rollChoice: new fields.StringField({ nullable: true, initial: null }), rollData: new fields.JSONField({ nullable: true, initial: null }), - damageRollData: new fields.EmbeddedDataField(ChatDamageData), selected: new fields.BooleanField({ initial: false }) }; } diff --git a/module/dice/baseRoll.mjs b/module/dice/baseRoll.mjs index 2c42d38a..4d065fff 100644 --- a/module/dice/baseRoll.mjs +++ b/module/dice/baseRoll.mjs @@ -4,8 +4,4 @@ export default class BaseRoll extends Roll { /** @inheritdoc */ static TOOLTIP_TEMPLATE = 'systems/daggerheart/templates/ui/chat/foundryRollTooltip.hbs'; - - get modifierTotal() { - return this.total - this.dice.reduce((acc, dice) => acc + dice.total, 0); - } } diff --git a/module/dice/damageRoll.mjs b/module/dice/damageRoll.mjs index 683d14d8..3f2f79e0 100644 --- a/module/dice/damageRoll.mjs +++ b/module/dice/damageRoll.mjs @@ -13,34 +13,36 @@ export default class DamageRoll extends DHRoll { static DefaultDialog = DamageDialog; - static createRollInstance(config) { - return new this(undefined, config.data, config); - } - /** @inheritdoc */ - static async buildEvaluate(roll, config = {}) { - if (config.dialog.configure === false) roll.constructFormulas(config); - - const evaluateRoll = async roll => { - await roll.roll.evaluate(); - roll.roll.options = { damageTypes: roll.damageTypes ? [...roll.damageTypes] : [] }; - return roll.roll; - } - - if (!config.damage) config.damage = { main: null, resources: {} }; - - if (config.damageFormula) { - config.damage.main = await evaluateRoll(config.damageFormula); - config.damage.main.options = { damageTypes: - config.damageFormula.damageTypes ? [...config.damageFormula.damageTypes] : [] - }; - } - - for (const roll of config.resourceFormulas) { - config.damage.resources[roll.applyTo] = await evaluateRoll(roll); - } + static async buildEvaluate(roll, config = {}, message = {}) { + if (config.dialog.configure === false) roll.constructFormula(config); + for (const roll of config.roll) await roll.roll.evaluate(); roll._evaluated = true; + + const parts = []; + for (const rollData of config.roll) { + const roll = rollData.roll; + parts.push({ + ...rollData, + ...roll.options.roll, + total: roll.total, + formula: roll.formula, + dice: roll.dice.map(d => ({ + dice: d.denomination, + total: d.total, + formula: d.formula, + results: d.results + })), + damageTypes: [...(rollData.damageTypes ?? [])], + roll, + type: config.type, + modifierTotal: this.calculateTotalModifiers(roll) + }); + rollData.roll = JSON.stringify(roll.toJSON()); + } + + config.damage = this.unifyDamageRoll(parts); } static async buildPost(roll, config, message) { @@ -51,10 +53,9 @@ export default class DamageRoll extends DHRoll { const diceRolls = []; if (game.modules.get('dice-so-nice')?.active) { config.mute = true; - const pool = foundry.dice.terms.PoolTerm.fromRolls([ - ...(config.damage.main ? [config.damage.main] : []), - ...Object.values(config.damage.resources) - ]); + const pool = foundry.dice.terms.PoolTerm.fromRolls( + Object.values(config.damage).flatMap(r => r.parts.map(p => p.roll)) + ); diceRolls.push(Roll.fromTerms([pool])); } @@ -65,14 +66,22 @@ export default class DamageRoll extends DHRoll { await super.buildPost(roll, config, message); if (config.source?.message) { - chatMessage.update({ 'system.damage': { - ...config.damage.toObject(), - main: config.damage.main, - resources: config.damage.resources - }}); + chatMessage.update({ 'system.damage': config.damage }); } } + static unifyDamageRoll(rolls) { + const unified = {}; + rolls.forEach(r => { + const resource = unified[r.applyTo] ?? { formula: '', total: 0, parts: [] }; + resource.formula += `${resource.formula !== '' ? ' + ' : ''}${r.formula}`; + resource.total += r.total; + resource.parts.push(r); + unified[r.applyTo] = resource; + }); + return unified; + } + static formatGlobal(rolls) { let formula, total; const applyTo = new Set(rolls.flatMap(r => r.applyTo)); @@ -121,10 +130,12 @@ export default class DamageRoll extends DHRoll { const type = this.options.messageType ?? (this.options.hasHealing ? 'healing' : 'damage'); const changeKeys = []; - for (const damageType of this.options.damageFormula?.damageTypes?.values?.() ?? []) { - changeKeys.push(`system.bonuses.${type}.${damageType}`); + for (const roll of this.options.roll) { + for (const damageType of roll.damageTypes?.values?.() ?? []) { + changeKeys.push(`system.bonuses.${type}.${damageType}`); + } } - + const item = this.data.parent?.items?.get(this.options.source.item); if (item) { switch (item.type) { @@ -140,69 +151,62 @@ export default class DamageRoll extends DHRoll { return changeKeys; } - constructFormulas(config) { - return { - damageFormula: this.constructFormula(this.options.damageFormula, config, true), - resourceFormulas: this.options.resourceFormulas.map(x => this.constructFormula(x, config)) - }; - } - - constructFormula(formulaData, config, isDamage) { - if (!formulaData) return null; + constructFormula(config) { this.options.isCritical = config.isCritical; - - formulaData.roll = new Roll(Roll.replaceFormulaData(formulaData.formula, config.data)); - formulaData.roll.terms = Roll.parse(formulaData.roll.formula, config.data); - - if (formulaData.extraFormula) { - formulaData.roll.terms.push( - new foundry.dice.terms.OperatorTerm({ operator: '+' }), - ...this.constructor.parse(formulaData.extraFormula, this.options.data) - ); - } - - if (isDamage && formulaData.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) { - formulaData.modifiers = this.applyBaseBonus(formulaData); - this.addModifiers(formulaData); - formulaData.modifiers?.forEach(m => { - formulaData.roll.terms.push(...this.formatModifier(m.value)); - }); - - /* To Remove When Reaction System */ - for (const mod in config.modifiers) { - const modifier = config.modifiers[mod]; - if ( - modifier.beforeCrit === true && - (modifier.enabled || modifier.value) - ) modifier.callback(formulaData); + for (const [index, part] of this.options.roll.entries()) { + const isHitpointPart = part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id; + part.roll = new Roll(Roll.replaceFormulaData(part.formula, config.data)); + part.roll.terms = Roll.parse(part.roll.formula, config.data); + if (part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) { + part.modifiers = this.applyBaseBonus(part); + this.addModifiers(part); + part.modifiers?.forEach(m => { + part.roll.terms.push(...this.formatModifier(m.value)); + }); } /* To Remove When Reaction System */ - for (const mod in config.modifiers) { - const modifier = config.modifiers[mod]; - if (!modifier.beforeCrit && (modifier.enabled || modifier.value)) modifier.callback(formulaData); + if (index === 0 && part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) { + for (const mod in config.modifiers) { + const modifier = config.modifiers[mod]; + if (modifier.beforeCrit === true && (modifier.enabled || modifier.value)) modifier.callback(part); + } } - if (config.damageOptions.groupAttack?.numAttackers > 1) { + if (part.extraFormula) { + part.roll.terms.push( + new foundry.dice.terms.OperatorTerm({ operator: '+' }), + ...this.constructor.parse(part.extraFormula, this.options.data) + ); + } + + if (config.damageOptions.groupAttack?.numAttackers > 1 && isHitpointPart) { const damageTypes = [foundry.dice.terms.Die, foundry.dice.terms.NumericTerm]; - for (const term of formulaData.roll.terms) { + for (const term of part.roll.terms) { if (damageTypes.some(type => term instanceof type)) { term.number *= config.damageOptions.groupAttack.numAttackers; } } } - if (config.isCritical) { - const total = formulaData.roll.dice.reduce((acc, term) => acc + term._faces * term._number, 0); + if (config.isCritical && isHitpointPart) { + const total = part.roll.dice.reduce((acc, term) => acc + term._faces * term._number, 0); if (total > 0) { - formulaData.roll.terms.push(...this.formatModifier(total)); + part.roll.terms.push(...this.formatModifier(total)); } } - } - formulaData.roll._formula = this.constructor.getFormula(formulaData.roll.terms); - - return formulaData; + /* To Remove When Reaction System */ + if (index === 0 && part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) { + for (const mod in config.modifiers) { + const modifier = config.modifiers[mod]; + if (!modifier.beforeCrit && (modifier.enabled || modifier.value)) modifier.callback(part); + } + } + + part.roll._formula = this.constructor.getFormula(part.roll.terms); + } + return this.options.roll; } /* To Remove When Reaction System */ @@ -295,4 +299,76 @@ export default class DamageRoll extends DHRoll { config.modifiers = mods; return mods; } + + static async reroll(rollPart, dice, result) { + let diceIndex = 0; + let parsedRoll = game.system.api.dice.DamageRoll.fromData({ + ...rollPart.roll, + terms: rollPart.roll.terms.map(term => { + const isDie = term.class === 'Die'; + const fixedTerm = { + ...term, + ...(isDie ? { results: rollPart.dice[diceIndex].results } : {}) + }; + + if (isDie) diceIndex++; + return fixedTerm; + }), + class: 'DamageRoll', + evaluated: false + }); + + const parsedDiceTerms = Object.keys(parsedRoll.terms).reduce((acc, key) => { + const term = parsedRoll.terms[key]; + if (term instanceof CONFIG.Dice.termTypes.DiceTerm) acc[Object.keys(acc).length] = term; + return acc; + }, {}); + const term = parsedDiceTerms[dice]; + const termResult = parsedDiceTerms[dice].results[result]; + + const newIndex = parsedDiceTerms[dice].results.length; + await term.reroll(`/r1=${termResult.result}`); + + const diceRolls = []; + if (game.modules.get('dice-so-nice')?.active) { + const newResult = parsedDiceTerms[dice].results[newIndex]; + diceRolls.push({ + _evaluated: true, + dice: [ + new foundry.dice.terms.Die({ + ...term, + total: newResult.result, + faces: term._faces, + results: [newResult] + }) + ], + options: { appearance: {} } + }); + } + + await triggerChatRollFx(diceRolls); + await parsedRoll.evaluate(); + + const results = parsedRoll.dice[dice].results.map(result => ({ + ...result, + discarded: !result.active + })); + const newResult = results.splice(results.length - 1, 1); + results.splice(Number(result) + 1, 0, newResult[0]); + + const rerolledDice = parsedRoll.dice.map((x, index) => { + const isRerollDice = index === Number(dice); + if (!isRerollDice) return { ...x, dice: x.denomination }; + return { + dice: parsedRoll.dice[dice].denomination, + total: parsedRoll.dice[dice].total, + results: results.map(result => ({ + ...result, + hasRerolls: result.hasRerolls || isRerollDice + })) + }; + }); + + return { parsedRoll, rerolledDice }; + } } diff --git a/module/dice/dhRoll.mjs b/module/dice/dhRoll.mjs index 68185984..16472fea 100644 --- a/module/dice/dhRoll.mjs +++ b/module/dice/dhRoll.mjs @@ -1,8 +1,7 @@ import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs'; import { triggerChatRollFx } from '../helpers/utils.mjs'; -import BaseRoll from './baseRoll.mjs'; -export default class DHRoll extends BaseRoll { +export default class DHRoll extends Roll { baseTerms = []; constructor(formula, data = {}, options = {}) { super(formula, data, foundry.utils.mergeObject(options, { roll: [] }, { overwrite: false })); @@ -41,10 +40,6 @@ export default class DHRoll extends BaseRoll { return config; } - static createRollInstance(config) { - return new this(config.roll.formula, config.data, config); - } - /** * @param {Partial} config * @returns {Promise} @@ -62,7 +57,7 @@ export default class DHRoll extends BaseRoll { this.temporaryModifierBuilder(config); - let roll = this.createRollInstance(config); + let roll = new this(config.roll.formula, config.data, config); if (config.dialog.configure !== false) { // Open Roll Dialog const DialogClass = config.dialog?.class ?? this.DefaultDialog; @@ -117,11 +112,7 @@ export default class DHRoll extends BaseRoll { static async toMessage(roll, config) { const item = config.data.parent?.items?.get?.(config.source.item) ?? null; - const actions = item ? [ - ...item.system.actions, - ...(item.system.attack?.id === config.source.action ? [item.system.attack] : []) - ] : []; - const action = actions.find(x => x.id === config.source.action); + const action = item ? item.system.actions.get(config.source.action) : null; let actionDescription = null; if (action?.chatDisplay) { actionDescription = action @@ -133,14 +124,6 @@ export default class DHRoll extends BaseRoll { config.actionChatMessageHandled = true; } - const reloadSetting = - game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).reload; - const useReload = - item?.system.hasReload && - action?.type === 'attack' && - reloadSetting === CONFIG.DH.SETTINGS.reloadChoices.auto.id; - const reloadResult = useReload ? await action?.handleReload?.() : {}; - const cls = getDocumentClass('ChatMessage'), msgData = { type: this.messageType, @@ -148,11 +131,7 @@ export default class DHRoll extends BaseRoll { title: roll.title, speaker: cls.getSpeaker({ actor: roll.data?.parent }), sound: config.mute ? null : CONFIG.sounds.dice, - system: { - ...config, - actionDescription, - reloadCheckValue: reloadResult.rollValue - }, + system: { ...config, actionDescription }, rolls: [roll] }; @@ -174,17 +153,14 @@ export default class DHRoll extends BaseRoll { if (!this._evaluated) return; const metagamingSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Metagaming); - const automationSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation); const chatData = await this._prepareChatRenderContext({ flavor, isPrivate, ...options }); return foundry.applications.handlebars.renderTemplate(template, { roll: this, ...chatData, - action: chatData.action, parent: chatData.parent, targetMode: chatData.targetMode, areas: chatData.action?.areas, - metagamingSettings, - automationSettings + metagamingSettings }); } diff --git a/module/dice/die/_module.mjs b/module/dice/die/_module.mjs index b84cef86..19ca951a 100644 --- a/module/dice/die/_module.mjs +++ b/module/dice/die/_module.mjs @@ -3,7 +3,6 @@ import HopeDie from './hopeDie.mjs'; import FearDie from './fearDie.mjs'; import AdvantageDie from './advantageDie.mjs'; import DisadvantageDie from './disadvantageDie.mjs'; -export { default as BaseDie } from './baseDie.mjs'; export const diceTypes = { DualityDie, diff --git a/module/dice/die/advantageDie.mjs b/module/dice/die/advantageDie.mjs index 66b6f124..9c2f0b03 100644 --- a/module/dice/die/advantageDie.mjs +++ b/module/dice/die/advantageDie.mjs @@ -1,6 +1,4 @@ -import BaseDie from './baseDie.mjs'; - -export default class AdvantageDie extends BaseDie { +export default class AdvantageDie extends foundry.dice.terms.Die { constructor(options) { super(options); diff --git a/module/dice/die/baseDie.mjs b/module/dice/die/baseDie.mjs deleted file mode 100644 index cc88f753..00000000 --- a/module/dice/die/baseDie.mjs +++ /dev/null @@ -1,12 +0,0 @@ -export default class BaseDie extends foundry.dice.terms.Die { - async rerollResult(resultIndex) { - const result = this.results[resultIndex]; - result.rerolled = true; - result.active = false; - await this.roll({ reroll: true }); - - const rerolledResult = this.results[this.results.length - 1]; - this.results.splice(this.results.length - 1, 1); - this.results.splice(resultIndex, 0, rerolledResult); - } -} \ No newline at end of file diff --git a/module/dice/die/disadvantageDie.mjs b/module/dice/die/disadvantageDie.mjs index e79845f3..f56ebe96 100644 --- a/module/dice/die/disadvantageDie.mjs +++ b/module/dice/die/disadvantageDie.mjs @@ -1,6 +1,4 @@ -import BaseDie from './baseDie.mjs'; - -export default class DisadvantageDie extends BaseDie { +export default class DisadvantageDie extends foundry.dice.terms.Die { constructor(options) { super(options); diff --git a/module/dice/die/dualityDie.mjs b/module/dice/die/dualityDie.mjs index 2f4dae77..cc7ee75e 100644 --- a/module/dice/die/dualityDie.mjs +++ b/module/dice/die/dualityDie.mjs @@ -1,7 +1,6 @@ -import BaseDie from './baseDie.mjs'; import { updateResourcesForDualityReroll } from '../helpers.mjs'; -export default class DualityDie extends BaseDie { +export default class DualityDie extends foundry.dice.terms.Die { constructor(options) { super(options); diff --git a/module/dice/dualityRoll.mjs b/module/dice/dualityRoll.mjs index 6503efb1..38d9315f 100644 --- a/module/dice/dualityRoll.mjs +++ b/module/dice/dualityRoll.mjs @@ -409,4 +409,8 @@ export default class DualityRoll extends D20Roll { return rerolled; } + + fromJSON(json) { + return super.fromJSON(json); + } } diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index 666d9ac1..f8880597 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -622,11 +622,6 @@ export default class DhpActor extends Actor { return rollData; } - /** - * Checks to see if damage can be reduced in one way or another. - * @param {number} hpDamage the amount of marked hp that will be marked - * @param {Set} types a list of damage types - */ #canReduceDamage(hpDamage, types) { const { stressDamageReduction, disabledArmor, reduceSeverity, thresholdImmunities } = this.system.rules.damageReduction; @@ -653,32 +648,35 @@ export default class DhpActor extends Actor { return canUseArmor || canUseStress || hasReduceSeverity || hasThresholdImmunity; } - async takeDamage(args, isDirect = false) { - args = this.#parseDamageArgs(args); - if (Hooks.call(`${CONFIG.DH.id}.preTakeDamage`, this, args) === false) return null; + async takeDamage(damages, isDirect = false) { + if (Hooks.call(`${CONFIG.DH.id}.preTakeDamage`, this, damages) === false) return null; if (this.type === 'companion') { await this.modifyResource([{ value: 1, key: 'stress' }]); return; } - const updates = args.resourceUpdates; - if (args.main) { - // todo: avoid side effects, but hook currently requires it - args.main.value = this.calculateDamage(args.main.value, args.main.damageTypes); - } + const updates = []; - if (Hooks.call(`${CONFIG.DH.id}.postCalculateDamage`, this, args) === false) return null; + Object.entries(damages).forEach(([key, damage]) => { + damage.parts.forEach(part => { + if (part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) + part.total = this.calculateDamage(part.total, part.damageTypes); + const update = updates.find(u => u.key === key); + if (update) { + update.value += part.total; + update.damageTypes.add(...new Set(part.damageTypes)); + } else updates.push({ value: part.total, key, damageTypes: new Set(part.damageTypes) }); + }); + }); - // Convert deducted resources to a record of updates. Return if nothing to do. - if (!updates.some(u => u.value) && !args.main) return; + if (Hooks.call(`${CONFIG.DH.id}.postCalculateDamage`, this, damages) === false) return null; - if (args.main) { - const hpDamage = { - value: this.convertDamageToThreshold(args.main.value), - damageTypes: new Set(args.main.damageTypes), - key: CONFIG.DH.GENERAL.healingTypes.hitPoints.id - }; + if (!updates.length) return; + + const hpDamage = updates.find(u => u.key === CONFIG.DH.GENERAL.healingTypes.hitPoints.id); + if (hpDamage?.value) { + hpDamage.value = this.convertDamageToThreshold(hpDamage.value); if (this.type === 'character' && !isDirect && this.#canReduceDamage(hpDamage.value, hpDamage.damageTypes)) { const armorSlotResult = await this.owner.query( 'armorSlot', @@ -693,7 +691,7 @@ export default class DhpActor extends Actor { ); if (armorSlotResult) { const { modifiedDamage, armorChanges, stressSpent } = armorSlotResult; - hpDamage.value = modifiedDamage; + updates.find(u => u.key === 'hitPoints').value = modifiedDamage; for (const armorChange of armorChanges) { updates.push({ value: armorChange.amount, key: 'armor', uuid: armorChange.uuid }); } @@ -703,23 +701,19 @@ export default class DhpActor extends Actor { else updates.push({ value: stressSpent, key: 'stress' }); } } - } else if (this.type === 'adversary') { + } + if (this.type === 'adversary') { const reducedSeverity = hpDamage.damageTypes.reduce((value, curr) => { return Math.max(this.system.rules.damageReduction.reduceSeverity[curr], value); }, 0); hpDamage.value = Math.max(hpDamage.value - reducedSeverity, 0); - if (this.system.rules.damageReduction.thresholdImmunities[getDamageKey(hpDamage.value)]) { - hpDamage.value = Math.max(0, hpDamage.value - 1); - } - } - // Merge existing hitPoint deduction with finalised damage deduction - const existing = updates.find(u => u.key === CONFIG.DH.GENERAL.healingTypes.hitPoints.id); - if (existing) { - existing.value += hpDamage.value; - existing.damageTypes = hpDamage.damageTypes; - } else { - updates.push(hpDamage); + if ( + hpDamage.value && + this.system.rules.damageReduction.thresholdImmunities[getDamageKey(hpDamage.value)] + ) { + hpDamage.value -= 1; + } } } @@ -735,10 +729,12 @@ export default class DhpActor extends Actor { for (var result of results) resourceMap.addResources(result.updates); resourceMap.updateResources(); } - - for (const u of updates) { - u.value = u.key === 'fear' || this.system?.resources?.[u.key]?.isReversed === false ? u.value * -1 : u.value; - } + + updates.forEach( + u => + (u.value = + u.key === 'fear' || this.system?.resources?.[u.key]?.isReversed === false ? u.value * -1 : u.value) + ); await this.modifyResource(updates); @@ -747,36 +743,6 @@ export default class DhpActor extends Actor { return updates; } - async takeHealing(args) { - args = this.#parseDamageArgs({ resources: 'resources' in args ? args.resources : args }); - if (Hooks.call(`${CONFIG.DH.id}.preTakeHealing`, this, args) === false) return null; - - const updates = args.resourceUpdates; - for (const u of updates) { - const shouldFlip = !(u.key === 'fear' || this.system?.resources?.[u.key]?.isReversed === false); - u.value = shouldFlip ? u.value * -1 : u.value; - } - await this.modifyResource(updates); - - if (Hooks.call(`${CONFIG.DH.id}.postTakeHealing`, this, updates) === false) return null; - - return updates; - } - - /** Parse damage args that may be coming from takeHealing or takeDamage. Used to simplify macro usage and roll vs non-roll usage */ - #parseDamageArgs(args = {}) { - const damageRoll = 'total' in args ? args : (args.main ?? args.damage); - const damageValue = typeof damageRoll === 'number' ? damageRoll : damageRoll?.total; - const damageTypes = Array.from(damageRoll?.options?.damageTypes ?? damageRoll?.damageTypes ?? []); - const main = typeof damageValue === 'number' ? { key: 'damage', value: damageValue, damageTypes } : null; - const resourceUpdates = Object.entries(args.resources ?? {}).map(([key, damage]) => ({ - key, - value: typeof damage === 'number' ? damage : damage?.total ?? 0 - })); - - return { main, resourceUpdates }; - } - calculateDamage(baseDamage, type) { if (this.canResist(type, 'immunity')) return 0; if (this.canResist(type, 'resistance')) baseDamage = Math.ceil(baseDamage / 2); @@ -801,6 +767,32 @@ export default class DhpActor extends Actor { return reduction === Infinity ? 0 : reduction; } + async takeHealing(healings) { + if (Hooks.call(`${CONFIG.DH.id}.preTakeHealing`, this, healings) === false) return null; + + const updates = []; + Object.entries(healings).forEach(([key, healing]) => { + healing.parts.forEach(part => { + const update = updates.find(u => u.key === key); + if (update) update.value += part.total; + else updates.push({ value: part.total, key }); + }); + }); + + updates.forEach( + u => + (u.value = !(u.key === 'fear' || this.system?.resources?.[u.key]?.isReversed === false) + ? u.value * -1 + : u.value) + ); + + await this.modifyResource(updates); + + if (Hooks.call(`${CONFIG.DH.id}.postTakeHealing`, this, updates) === false) return null; + + return updates; + } + /** * Resources are modified asynchronously, so be careful not to update the same resource in * quick succession. diff --git a/module/documents/chatMessage.mjs b/module/documents/chatMessage.mjs index d53a76bd..fd68997c 100644 --- a/module/documents/chatMessage.mjs +++ b/module/documents/chatMessage.mjs @@ -3,13 +3,6 @@ import { emitGMUpdate, emitGMCreate, GMUpdateEvent } from '../systemRegistration export default class DhpChatMessage extends foundry.documents.ChatMessage { targetHook = null; - static #EXPAND_SECTIONS = [ - { selector: 'roll-section [data-action="expandRoll"]', key: 'roll' }, - { selector: 'damage-section', key: 'damage' }, - { selector: 'target-section', key: 'target' }, - { selector: 'description-section', key: 'desc' } - ]; - async renderHTML() { const actor = game.actors.get(this.speaker.actor); const actorData = @@ -96,26 +89,23 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage { } } - // Check registered selectors and the main item section for expanding - // Preserving during re-render is handled by core foundry on anything with [data-action=expandRoll] const autoExpandRoll = game.settings.get( - CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance - ).expandRollMessage; - for (const { selector, key } of DhpChatMessage.#EXPAND_SECTIONS) { - const elements = html.querySelectorAll(selector); - for (const element of elements) { - element.classList.toggle('expanded', autoExpandRoll[key]); - } - } - - // Auto expand the item description. These are not preserved by foundry during re-renders - const itemDesc = html.querySelector('details'); - if (itemDesc) { - const existing = document.querySelector(`.chat-message[data-message-id="${this.id}"] details`); - if (existing?.hasAttribute('open') ?? autoExpandRoll.desc) { - itemDesc.setAttribute('open', ''); - } - } + CONFIG.DH.id, + CONFIG.DH.SETTINGS.gameSettings.appearance + ).expandRollMessage, + rollSections = html.querySelectorAll('.roll-part'), + itemDesc = html.querySelector('.domain-card-move'); + rollSections.forEach(s => { + if (s.classList.contains('roll-section')) { + const toExpand = s.querySelector('[data-action="expandRoll"]'); + toExpand.classList.toggle('expanded', autoExpandRoll.roll); + } else if (s.classList.contains('damage-section')) + s.classList.toggle('expanded', autoExpandRoll.damage); + else if (s.classList.contains('target-section')) s.classList.toggle('expanded', autoExpandRoll.target); + else if (s.classList.contains('description-section')) + s.classList.toggle('expanded', autoExpandRoll.desc); + }); + if (itemDesc && autoExpandRoll.desc) itemDesc.setAttribute('open', ''); } if (!this.isAuthor && !this.speakerActor?.isOwner) { diff --git a/module/documents/item.mjs b/module/documents/item.mjs index 8112e99f..14717538 100644 --- a/module/documents/item.mjs +++ b/module/documents/item.mjs @@ -208,7 +208,10 @@ export default class DHItem extends foundry.documents.Item { tags: this._getTags() }, actions: item.system.actionsList, - description: await this.system.getEnrichedDescription() + description: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.system.description, { + relativeTo: this.parent, + rollData: this.parent?.getRollData() ?? {} + }) }; const msg = { diff --git a/module/documents/tokenManager.mjs b/module/documents/tokenManager.mjs index b21b56a0..7678d2c7 100644 --- a/module/documents/tokenManager.mjs +++ b/module/documents/tokenManager.mjs @@ -19,10 +19,10 @@ export default class DhTokenManager { } } - const placedData = await canvas.tokens.placeTokens( + return await canvas.tokens.placeTokens( [ { - ...(await actor.getTokenDocument()).toObject(), + ...actor.prototypeToken.toObject(), actorId: actor.id, displayName: 50, ...tokenData @@ -30,8 +30,6 @@ export default class DhTokenManager { ], { create: false } ); - - return placedData?.[0] ?? null; } /** @@ -48,24 +46,22 @@ export default class DhTokenManager { const createElevation = elevation ?? level.elevation.bottom; for (const tokenData of tokensData) { - const previewToken = await this.createPreview(tokenData.actor, { + const previewTokens = await this.createPreview(tokenData.actor, { name: tokenData.tokenPreviewName, level: game.user.viewedLevel, elevation: createElevation, flags: { daggerheart: { createPlacement: true } } }); - if (!previewToken) return null; - - const finalTokenData = { - ...previewToken.toObject(), - name: tokenData.actor.prototypeToken.name, - displayName: tokenData.actor.prototypeToken.displayName, - flags: tokenData.actor.prototypeToken.flags - }; + if (!previewTokens?.length) return null; await canvas.scene.createEmbeddedDocuments( 'Token', - [finalTokenData], + previewTokens.map(x => ({ + ...x.toObject(), + name: tokenData.actor.prototypeToken.name, + displayName: tokenData.actor.prototypeToken.displayName, + flags: tokenData.actor.prototypeToken.flags + })), { controlObject: true, parent: canvas.scene } ); } diff --git a/module/documents/tooltipManager.mjs b/module/documents/tooltipManager.mjs index 6e794b6a..3e3f4a16 100644 --- a/module/documents/tooltipManager.mjs +++ b/module/documents/tooltipManager.mjs @@ -4,234 +4,199 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti #wide = false; #bordered = false; - /** @inheritdoc */ async activate(element, options = {}) { - this.#wide = false; - this.#bordered = false; - - const isMacro = document.getElementById('action-bar').contains(element); - const macro = isMacro ? game.macros.get(game.user.hotbar[Number(element.dataset.slot)] ?? null) : null; - const macroItemUuid = macro?.type === 'script' ? macro.command.match(/await game\.system\.api\.applications\.ui\.DhHotbar\.useItem\("([^"]+)"\);/)?.[1] : null; - if (macroItemUuid && await fromUuid(macroItemUuid, { strict: false })) { - element.dataset.tooltip = `#item#${macroItemUuid}`; - options.direction = this.constructor.TOOLTIP_DIRECTIONS.UP; - } + const { TextEditor } = foundry.applications.ux; let html = options.html; - const key = element.dataset.tooltip?.match(/^#([\w-]+)#/)?.[1]; - switch (key) { - case 'battlepoints': - return this.#activateBattlepoints(element, options); - case 'effect-display': - html = await this.#activateEffectDisplay(element, options); - break; - case 'item': - html = await this.#activateItem(element, options); - break; - case 'attack': - html = await this.#activateAttack(element, options); - break; - case 'shortRest': - case 'longRest': - html = await this.#activateRest(element, options); - break; - case 'advantage': - case 'disadvantage': - html = await this.#activateAdvantageDisadvantage(element, options); - break; - case 'deathMove': - html = await this.#activateDeathMove(element, options); - break; + if (element.dataset.tooltip?.startsWith('#battlepoints#')) { + this.#wide = true; + this.#bordered = true; + + html = await this.getBattlepointHTML(element.dataset.combatId); + options.direction = this._determineItemTooltipDirection(element); + super.activate(element, { ...options, html: html }); + + const lockedTooltip = this.lockTooltip(); + lockedTooltip.querySelectorAll('.battlepoint-toggle-container input').forEach(element => { + element.addEventListener('input', this.toggleModifier.bind(this)); + }); + return; + } else { + this.#wide = false; + this.#bordered = false; + } + + if (element.dataset.tooltip === '#effect-display#') { + this.#bordered = true; + let effect = {}; + if (element.dataset.uuid) { + const effectItem = await foundry.utils.fromUuid(element.dataset.uuid); + const effectData = effectItem.toObject(); + + effect = { + ...effectData, + 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]; + effect = { + ...condition, + name: game.i18n.localize(condition.name), + description: game.i18n.localize(condition.description), + appliedBy: element.dataset.appliedBy, + isLockedCondition: true + }; + } + + html = await foundry.applications.handlebars.renderTemplate( + `systems/daggerheart/templates/ui/tooltip/effect-display.hbs`, + { + effect + } + ); + + this.tooltip.innerHTML = html; + options.direction = this._determineItemTooltipDirection(element); + } else { + this.#bordered = false; + } + + if (element.dataset.tooltip?.startsWith('#item#')) { + const itemUuid = element.dataset.tooltip.slice(6); + const item = await foundry.utils.fromUuid(itemUuid); + if (item) { + const isAction = item instanceof game.system.api.models.actions.actionsTypes.base; + const isEffect = item instanceof ActiveEffect; + await this.enrichText(item); + + const type = isAction ? 'action' : isEffect ? 'effect' : item.type; + html = await foundry.applications.handlebars.renderTemplate( + `systems/daggerheart/templates/ui/tooltip/${type}.hbs`, + { + item: item, + description: item.system?.enrichedDescription ?? item.enrichedDescription, + config: CONFIG.DH, + allDomains: CONFIG.DH.DOMAIN.allDomains() + } + ); + + this.tooltip.innerHTML = html; + options.direction = this._determineItemTooltipDirection(element); + } + } else { + const attack = element.dataset.tooltip?.startsWith('#attack#'); + if (attack) { + const actorUuid = element.dataset.tooltip.slice(8); + const actor = await foundry.utils.fromUuid(actorUuid); + const attack = actor.system.attack; + + const description = await TextEditor.enrichHTML(attack.description); + html = await foundry.applications.handlebars.renderTemplate( + `systems/daggerheart/templates/ui/tooltip/attack.hbs`, + { + attack: attack, + description: description, + parent: actor, + config: CONFIG.DH + } + ); + + this.tooltip.innerHTML = html; + } + + const shortRest = element.dataset.tooltip?.startsWith('#shortRest#'); + const longRest = element.dataset.tooltip?.startsWith('#longRest#'); + if (shortRest || longRest) { + const key = element.dataset.tooltip.slice(shortRest ? 11 : 10); + + const moves = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).restMoves[ + element.dataset.restType + ].moves; + const move = moves[key]; + const description = await TextEditor.enrichHTML(move.description); + html = await foundry.applications.handlebars.renderTemplate( + `systems/daggerheart/templates/ui/tooltip/downtime.hbs`, + { + move: move, + description: description + } + ); + + this.tooltip.innerHTML = html; + options.direction = this._determineItemTooltipDirection( + element, + this.constructor.TOOLTIP_DIRECTIONS.RIGHT + ); + } + + const isAdvantage = element.dataset.tooltip?.startsWith('#advantage#'); + const isDisadvantage = element.dataset.tooltip?.startsWith('#disadvantage#'); + if (isAdvantage || isDisadvantage) { + const actorUuid = element.dataset.tooltip.slice(isAdvantage ? 11 : 14); + const actor = await foundry.utils.fromUuid(actorUuid); + + if (actor) { + html = await foundry.applications.handlebars.renderTemplate( + `systems/daggerheart/templates/ui/tooltip/advantage.hbs`, + { + sources: isAdvantage ? actor.system.advantageSources : actor.system.disadvantageSources + } + ); + + this.tooltip.innerHTML = html; + } + } + + const deathMove = element.dataset.tooltip?.startsWith('#deathMove#'); + if (deathMove) { + const name = element.dataset.deathName; + const img = element.dataset.deathImg; + const description = element.dataset.deathDescription; + + html = await foundry.applications.handlebars.renderTemplate( + `systems/daggerheart/templates/ui/tooltip/death-move.hbs`, + { + move: { name: name, img: img, description: description } + } + ); + + this.tooltip.innerHTML = html; + options.direction = this._determineItemTooltipDirection( + element, + this.constructor.TOOLTIP_DIRECTIONS.RIGHT + ); + } } this.noOffset = options.noOffset; - super.activate(element, { ...options, html }); - } - - async #activateBattlepoints(element, options) { - this.#wide = true; - this.#bordered = true; - - const html = await this.getBattlepointHTML(element.dataset.combatId); - options.direction = this._determineItemTooltipDirection(element); super.activate(element, { ...options, html: html }); - - const lockedTooltip = this.lockTooltip(); - lockedTooltip.querySelectorAll('.battlepoint-toggle-container input').forEach(element => { - element.addEventListener('input', this.toggleModifier.bind(this)); - }); - } - - async #activateEffectDisplay(element, options) { - this.#bordered = true; - let effect = {}; - if (element.dataset.uuid) { - const effectItem = await foundry.utils.fromUuid(element.dataset.uuid); - const effectData = effectItem.toObject(); - - effect = { - ...effectData, - 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]; - effect = { - ...condition, - name: game.i18n.localize(condition.name), - description: game.i18n.localize(condition.description), - appliedBy: element.dataset.appliedBy, - isLockedCondition: true - }; - } - - const html = await foundry.applications.handlebars.renderTemplate( - `systems/daggerheart/templates/ui/tooltip/effect-display.hbs`, - { - effect - } - ); - - this.tooltip.innerHTML = html; - options.direction = this._determineItemTooltipDirection(element); - - return html; - } - - async #activateItem(element, options) { - const itemUuid = element.dataset.tooltip.slice(6); - const item = await foundry.utils.fromUuid(itemUuid); - if (item) { - const isAction = item instanceof game.system.api.models.actions.actionsTypes.base; - const isEffect = item instanceof ActiveEffect; - await this.enrichText(item); - - const type = isAction ? 'action' : isEffect ? 'effect' : item.type; - const html = await foundry.applications.handlebars.renderTemplate( - `systems/daggerheart/templates/ui/tooltip/${type}.hbs`, - { - item: item, - description: item.system?.enrichedDescription ?? item.enrichedDescription, - config: CONFIG.DH, - allDomains: CONFIG.DH.DOMAIN.allDomains() - } - ); - - this.tooltip.innerHTML = html; - options.direction ??= this._determineItemTooltipDirection(element); - return html; - } - - return null; - } - - async #activateAttack(element, options) { - const actorUuid = element.dataset.tooltip.slice(8); - const actor = await foundry.utils.fromUuid(actorUuid); - const attack = actor.system.attack; - - const description = await foundry.applications.ux.TextEditor.enrichHTML(attack.description); - const html = await foundry.applications.handlebars.renderTemplate( - `systems/daggerheart/templates/ui/tooltip/attack.hbs`, - { - attack: attack, - description: description, - parent: actor, - config: CONFIG.DH - } - ); - - this.tooltip.innerHTML = html; - return html; - } - - async #activateAdvantageDisadvantage(element, options) { - const isAdvantage = element.dataset.tooltip?.startsWith('#advantage#'); - const actorUuid = element.dataset.tooltip.slice(isAdvantage ? 11 : 14); - const actor = await foundry.utils.fromUuid(actorUuid); - - if (actor) { - const html = await foundry.applications.handlebars.renderTemplate( - `systems/daggerheart/templates/ui/tooltip/advantage.hbs`, - { - sources: isAdvantage ? actor.system.advantageSources : actor.system.disadvantageSources - } - ); - - this.tooltip.innerHTML = html; - return html; - } - return null; - } - - async #activateDeathMove(element, options) { - const name = element.dataset.deathName; - const img = element.dataset.deathImg; - const description = element.dataset.deathDescription; - - const html = await foundry.applications.handlebars.renderTemplate( - `systems/daggerheart/templates/ui/tooltip/death-move.hbs`, - { - move: { name: name, img: img, description: description } - } - ); - - this.tooltip.innerHTML = html; - options.direction = this._determineItemTooltipDirection( - element, - this.constructor.TOOLTIP_DIRECTIONS.RIGHT - ); - return html; - } - - async #activateRest(element, options) { - const isShortRest = element.dataset.tooltip?.startsWith('#shortRest#'); - const key = element.dataset.tooltip.slice(isShortRest ? 11 : 10); - const moves = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).restMoves[ - element.dataset.restType - ].moves; - const move = moves[key]; - const description = await foundry.applications.ux.TextEditor.enrichHTML(move.description); - const html = await foundry.applications.handlebars.renderTemplate( - `systems/daggerheart/templates/ui/tooltip/downtime.hbs`, - { - move: move, - description: description - } - ); - - this.tooltip.innerHTML = html; - options.direction = this._determineItemTooltipDirection( - element, - this.constructor.TOOLTIP_DIRECTIONS.RIGHT - ); - return html; } _setAnchor(direction) { diff --git a/module/helpers/handlebarsHelper.mjs b/module/helpers/handlebarsHelper.mjs index dbcc50dc..7f30d970 100644 --- a/module/helpers/handlebarsHelper.mjs +++ b/module/helpers/handlebarsHelper.mjs @@ -48,8 +48,9 @@ export default class RegisterHandlebarsHelpers { return formula; } - static damageSymbols(damageData) { - const symbols = damageData.type.map(p => CONFIG.DH.GENERAL.damageTypes[p].icon); + static damageSymbols(damageParts) { + const allTypes = [...new Set([...damageParts].flatMap(x => Array.from(x.type)))]; + const symbols = allTypes.map(p => CONFIG.DH.GENERAL.damageTypes[p].icon); return new Handlebars.SafeString(Array.from(symbols).map(symbol => ``)); } diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index f600eae6..84bcacf2 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -700,6 +700,19 @@ export async function RefreshFeatures( return refreshedActors; } +export function getUnusedDamageTypes(parts) { + const usedKeys = Object.keys(parts); + return Object.keys(CONFIG.DH.GENERAL.healingTypes).reduce((acc, key) => { + if (!usedKeys.includes(key)) + acc.push({ + value: key, + label: game.i18n.localize(CONFIG.DH.GENERAL.healingTypes[key].label) + }); + + return acc; + }, []); +} + /** Returns resolved armor sources ordered by application order */ export function getArmorSources(actor) { const rawArmorSources = Array.from(actor.allApplicableEffects()).filter(x => x.system.armorData); diff --git a/module/systemRegistration/migration-handlers/2_5_2.mjs b/module/systemRegistration/migration-handlers/2_5_2.mjs index 096f4bb7..f6d9ea77 100644 --- a/module/systemRegistration/migration-handlers/2_5_2.mjs +++ b/module/systemRegistration/migration-handlers/2_5_2.mjs @@ -7,8 +7,7 @@ export class Migration_2_5_2 extends MigrationHandlerBase { async updateActiveEffectSource(effectSource, item) { let shouldUpdate = false; const newChanges = []; - - const srdItem = item?._stats?.compendiumSource ? + const srdItem = item?._stats.compendiumSource ? await foundry.utils.fromUuid(item?._stats.compendiumSource) : null; for (let i = 0; i < effectSource.system.changes.length; i++) { @@ -16,7 +15,7 @@ export class Migration_2_5_2 extends MigrationHandlerBase { const srdEffect = srdItem?.effects.find(x => x.name === effectSource.name); if (change.type === 'custom') { const srdChange = srdEffect ? srdEffect.system.changes[i] : null; - if (srdChange && + if ( change.key === srdChange.key && change.value === srdChange.value && change.type !== srdChange.type diff --git a/module/systemRegistration/migration-handlers/2_6_0.mjs b/module/systemRegistration/migration-handlers/2_6_0.mjs deleted file mode 100644 index 9edda9a2..00000000 --- a/module/systemRegistration/migration-handlers/2_6_0.mjs +++ /dev/null @@ -1,20 +0,0 @@ -import { MigrationHandlerBase } from './base.mjs'; - -export class Migration_2_6_0 extends MigrationHandlerBase { - version = '2.6.0'; - - /** @inheritdoc */ - async updateActorSource(actor) { - if (actor.type === 'party' && Object.keys(actor.system.tagTeam.members).length) { - return { - _id: actor._id, - system: { - tagTeam: { - initiator: null, - members: _replace({}) - } - } - }; - } - } -} \ No newline at end of file diff --git a/module/systemRegistration/migration-handlers/base.mjs b/module/systemRegistration/migration-handlers/base.mjs index 29c181ce..7426570d 100644 --- a/module/systemRegistration/migration-handlers/base.mjs +++ b/module/systemRegistration/migration-handlers/base.mjs @@ -1,9 +1,8 @@ /** * @import DHItem from "../../documents/item.mjs"; -* @import DhActor from "../../documents/actor.mjs"; */ -/** +/** * The base class of an async migration. * These are generally run between versions for things that require compendiums or must be done in post. * The migrate() functions calls the various updateXSource() functions. @@ -23,16 +22,6 @@ export class MigrationHandlerBase { return null; } - /** - * Update a world actor - * @param {DhActor} actor - * @returns {Promise} - * @protected - */ - async updateActorSource(actor) { - return null; - } - async migrate() { // todo: handle more than just migrating effects. Right now this can only migrate effects // NOTE: the preload is hardcoded, we should not hardcode it @@ -71,39 +60,10 @@ export class MigrationHandlerBase { } }; - const updateActor = async actor => { - const actorUpdate = await this.updateActorSource(actor); - if (actorUpdate) { - batch.push({ - action: 'update', - documentName: 'Actor', - updates: [actorUpdate] - }); - } - - const aeUpdates = []; + for (const actor of game.actors) { for (const item of actor.items) { await updateItem(item); } - - for (const effect of actor.effects) { - const changes = await this.updateActiveEffectSource(effect.toObject(), { parent: actor }); - if (changes) aeUpdates.push(changes); - } - if (aeUpdates.length) { - batch.push({ - action: 'update', - documentName: 'ActiveEffect', - updates: aeUpdates, - parent: actor - }); - } - } - - - for (const actor of game.actors) { - await updateActor(actor); - progress.advance(); } for (const item of game.items) { diff --git a/module/systemRegistration/migrations.mjs b/module/systemRegistration/migrations.mjs index af11cd2c..fef97b8f 100644 --- a/module/systemRegistration/migrations.mjs +++ b/module/systemRegistration/migrations.mjs @@ -1,6 +1,5 @@ import { defaultRestOptions } from '../config/generalConfig.mjs'; import { Migration_2_5_2 } from './migration-handlers/2_5_2.mjs'; -import { Migration_2_6_0 } from './migration-handlers/2_6_0.mjs'; export async function runMigrations() { let lastMigrationVersion = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LastMigrationVersion); @@ -330,8 +329,7 @@ export async function runMigrations() { /* -------------------------------------------- */ const migrations = [ - new Migration_2_5_2(), - new Migration_2_6_0() + new Migration_2_5_2() ].filter(m => m.version && foundry.utils.isNewerVersion(m.version, lastMigrationVersion)); for (const handler of migrations) { diff --git a/src/packs/environments/environment_Abandoned_Grove_pGEdzdLkqYtBhxnG.json b/src/packs/environments/environment_Abandoned_Grove_pGEdzdLkqYtBhxnG.json index 039eafcf..23c1d966 100644 --- a/src/packs/environments/environment_Abandoned_Grove_pGEdzdLkqYtBhxnG.json +++ b/src/packs/environments/environment_Abandoned_Grove_pGEdzdLkqYtBhxnG.json @@ -391,7 +391,7 @@ "type": "effect", "_id": "p6V4k4yMwJ1UPZMz", "systemPath": "actions", - "description": "", + "description": "

Spend a Fear to summon a @UUID[Compendium.daggerheart.adversaries.Actor.sRn4bqerfARvhgSV]{Minor Chaos Elemental} drawn to the echoes of violence and discord. They appear within Far range of a chosen PC and immediately take the spotlight.

What color does the grass turn as the elemental appears? How does the chaos warp insects and small wildlife within the grove?

", "chatDisplay": true, "actionType": "action", "cost": [], diff --git a/src/packs/environments/environment_Ambushers_uXZpebPR77YQ1oXI.json b/src/packs/environments/environment_Ambushers_uXZpebPR77YQ1oXI.json index 582e7ec0..e8ba889a 100644 --- a/src/packs/environments/environment_Ambushers_uXZpebPR77YQ1oXI.json +++ b/src/packs/environments/environment_Ambushers_uXZpebPR77YQ1oXI.json @@ -35,9 +35,12 @@ "src": "systems/daggerheart/assets/icons/documents/actors/forest.svg", "anchorX": 0.5, "anchorY": 0.5, + "offsetX": 0, + "offsetY": 0, "fit": "contain", "scaleX": 1, "scaleY": 1, + "rotation": 0, "tint": "#ffffff", "alphaThreshold": 0.75 }, @@ -88,7 +91,7 @@ "saturation": 0, "contrast": 0 }, - "detectionModes": {}, + "detectionModes": [], "occludable": { "radius": 0 }, @@ -114,8 +117,7 @@ "flags": {}, "randomImg": false, "appendNumber": false, - "prependAdjective": false, - "depth": 1 + "prependAdjective": false }, "items": [ { @@ -154,7 +156,7 @@ "type": "effect", "_id": "6DKa1Pm605HpChPd", "systemPath": "actions", - "description": "", + "description": "

When a PC starts the ambush on unsuspecting adversaries, you lose 2 Fear and the first attack roll a PC makes has advantage.

What are the adversaries in the middle of doing when the ambush starts? How does this impact their approach to the fight?

", "chatDisplay": true, "actionType": "action", "cost": [ diff --git a/src/packs/environments/environment_Castle_Siege_1eZ32Esq7rfZOjlu.json b/src/packs/environments/environment_Castle_Siege_1eZ32Esq7rfZOjlu.json index 0df61deb..190d78b1 100644 --- a/src/packs/environments/environment_Castle_Siege_1eZ32Esq7rfZOjlu.json +++ b/src/packs/environments/environment_Castle_Siege_1eZ32Esq7rfZOjlu.json @@ -330,7 +330,7 @@ "type": "attack", "_id": "r5JN5oFYL5DC6Qqw", "systemPath": "actions", - "description": "", + "description": "

When an adversary is defeated, you can spend a Fear to have a stray attack from a siege weapon hit a point on the battlefield. All targets within Very Close range of that point must make an Agility Reaction Roll.

  • Targets who fail take 3d8+3 physical or magic damage and must mark a Stress.

  • Targets who succeed must mark a Stress.

What debris is scattered by the attack? What is broken by the strike that can’t be easily mended?

", "chatDisplay": true, "actionType": "action", "cost": [], diff --git a/src/packs/environments/environment_Cliffside_Ascent_LPpfdlNKqiZIl04w.json b/src/packs/environments/environment_Cliffside_Ascent_LPpfdlNKqiZIl04w.json index c65ede29..ef367d67 100644 --- a/src/packs/environments/environment_Cliffside_Ascent_LPpfdlNKqiZIl04w.json +++ b/src/packs/environments/environment_Cliffside_Ascent_LPpfdlNKqiZIl04w.json @@ -45,9 +45,12 @@ "src": "systems/daggerheart/assets/icons/documents/actors/forest.svg", "anchorX": 0.5, "anchorY": 0.5, + "offsetX": 0, + "offsetY": 0, "fit": "contain", "scaleX": 1, "scaleY": 1, + "rotation": 0, "tint": "#ffffff", "alphaThreshold": 0.75 }, @@ -98,7 +101,7 @@ "saturation": 0, "contrast": 0 }, - "detectionModes": {}, + "detectionModes": [], "occludable": { "radius": 0 }, @@ -124,8 +127,7 @@ "flags": {}, "randomImg": false, "appendNumber": false, - "prependAdjective": false, - "depth": 1 + "prependAdjective": false }, "items": [ { @@ -202,7 +204,7 @@ "type": "damage", "_id": "p1UiGEiGyl6r7PrA", "systemPath": "actions", - "description": "", + "description": "

Previous climbers left behind large metal rods that climbers can use to aid their ascent. If a PC using the pitons fails an action roll to climb, they can mark a Stress instead of ticking the countdown up.

What do the shape and material of these pitons tell you about the previous climbers? How far apart are they from one another?

", "chatDisplay": true, "actionType": "action", "cost": [], @@ -433,7 +435,7 @@ "type": "effect", "_id": "M8MfD2qBfYCwNKvH", "systemPath": "actions", - "description": "", + "description": "

Spend a Fear to have a PC’s handhold fail, plummeting them toward the ground. If they aren’t saved on the next action, they hit the ground and tick up the countdown by 2. The PC takes 1d12 physical damage if the countdown is between 8 and 12, 2d12 between 4 and 7, and 3d12 at 3 or lower.

How can you tell many others have fallen here before? What lives in these walls that might try to scare adventurers into falling for an easy meal?

", "chatDisplay": true, "actionType": "action", "cost": [ diff --git a/src/packs/environments/environment_Cult_Ritual_QAXXiOKBDmCTauHD.json b/src/packs/environments/environment_Cult_Ritual_QAXXiOKBDmCTauHD.json index e3e90c26..d6809cd1 100644 --- a/src/packs/environments/environment_Cult_Ritual_QAXXiOKBDmCTauHD.json +++ b/src/packs/environments/environment_Cult_Ritual_QAXXiOKBDmCTauHD.json @@ -45,9 +45,12 @@ "src": "systems/daggerheart/assets/icons/documents/actors/forest.svg", "anchorX": 0.5, "anchorY": 0.5, + "offsetX": 0, + "offsetY": 0, "fit": "contain", "scaleX": 1, "scaleY": 1, + "rotation": 0, "tint": "#ffffff", "alphaThreshold": 0.75 }, @@ -98,7 +101,7 @@ "saturation": 0, "contrast": 0 }, - "detectionModes": {}, + "detectionModes": [], "occludable": { "radius": 0 }, @@ -124,8 +127,7 @@ "flags": {}, "randomImg": false, "appendNumber": false, - "prependAdjective": false, - "depth": 1 + "prependAdjective": false }, "items": [ { @@ -234,7 +236,7 @@ "type": "effect", "_id": "EATw4ZkcuGeDfgLZ", "systemPath": "actions", - "description": "", + "description": "

A portion of the ritual’s power is diverted into a cult member to fight off interlopers. Choose one adversary to become Imbued with terrible magic until the scene ends or they’re defeated. An Imbued adversary immediately takes the spotlight and gains one of the following benefits, or all three if you spend a Fear:

  • They gain advantage on all attacks.

  • They deal an extra 1d10 damage on a successful attack.

  • They gain the following feature: Relentless (2) - Passive. This adversary can be spotlighted up to two times per GM turn. Spend Fear as usual to spotlight them.

How does the enemy change in appearance? What fears do their blows bring to the surface?

", "chatDisplay": true, "actionType": "action", "cost": [], @@ -281,10 +283,13 @@ }, "disabled": false, "duration": { - "value": null, - "units": "seconds", - "expiry": null, - "expired": false + "startTime": null, + "combat": null, + "seconds": null, + "rounds": null, + "turns": null, + "startRound": null, + "startTurn": null }, "description": "

An Imbued adversary immediately takes the spotlight and gains one of the following benefits, or all three if you spend a Fear:

  • They gain advantage on all attacks.

  • They deal an extra 1d10 damage on a successful attack.

  • They gain the following feature: Relentless (2) - Passive. This adversary can be spotlighted up to two times per GM turn. Spend Fear as usual to spotlight them.

How does the enemy change in appearance? What fears do their blows bring to the surface?

", "tint": "#ffffff", @@ -294,9 +299,6 @@ "_stats": { "compendiumSource": null }, - "start": null, - "showIcon": 1, - "folder": null, "_key": "!actors.items.effects!QAXXiOKBDmCTauHD.0Rgqw1kUPeJ11ldd.dYQBQq1xIysM0qLo" }, { @@ -324,10 +326,13 @@ }, "disabled": true, "duration": { - "value": null, - "units": "seconds", - "expiry": null, - "expired": false + "startTime": null, + "combat": null, + "seconds": null, + "rounds": null, + "turns": null, + "startRound": null, + "startTurn": null }, "description": "", "tint": "#ffffff", @@ -337,9 +342,6 @@ "_stats": { "compendiumSource": null }, - "start": null, - "showIcon": 1, - "folder": null, "_key": "!actors.items.effects!QAXXiOKBDmCTauHD.0Rgqw1kUPeJ11ldd.Hxw5lXE77bGzuaOu" } ], diff --git a/src/packs/environments/environment_Hallowed_Temple_dsA6j69AnaJhUyqH.json b/src/packs/environments/environment_Hallowed_Temple_dsA6j69AnaJhUyqH.json index d8b04d22..c510a87f 100644 --- a/src/packs/environments/environment_Hallowed_Temple_dsA6j69AnaJhUyqH.json +++ b/src/packs/environments/environment_Hallowed_Temple_dsA6j69AnaJhUyqH.json @@ -44,9 +44,12 @@ "src": "systems/daggerheart/assets/icons/documents/actors/forest.svg", "anchorX": 0.5, "anchorY": 0.5, + "offsetX": 0, + "offsetY": 0, "fit": "contain", "scaleX": 1, "scaleY": 1, + "rotation": 0, "tint": "#ffffff", "alphaThreshold": 0.75 }, @@ -97,7 +100,7 @@ "saturation": 0, "contrast": 0 }, - "detectionModes": {}, + "detectionModes": [], "occludable": { "radius": 0 }, @@ -123,8 +126,7 @@ "flags": {}, "randomImg": false, "appendNumber": false, - "prependAdjective": false, - "depth": 1 + "prependAdjective": false }, "items": [ { @@ -138,7 +140,7 @@ "type": "healing", "_id": "uLCoTKa7Jn2HaRqR", "systemPath": "actions", - "description": "", + "description": "

A PC who takes a rest in the Hallowed Temple automatically clears all HP.

What does the incense smell like? What kinds of songs do the acolytes sing?

", "chatDisplay": true, "actionType": "action", "cost": [], @@ -335,7 +337,7 @@ "type": "effect", "_id": "pJVipg7CbA9CB0Um", "systemPath": "actions", - "description": "", + "description": "

When the PCs have trespassed, blasphemed, or offended the clergy, you can spend a Fear to summon a @UUID[Compendium.daggerheart.adversaries.Actor.r1mbfSSwKWdcFdAU]{High Seraph} and [[/r 1d4]] @UUID[Compendium.daggerheart.adversaries.Actor.B4LZcGuBAHzyVdzy]{Bladed Guard} within Close range of the senior priest to reinforce their will.

What symbols or icons do they bear that signal they are anointed agents of the divinity? Who leads the group and what led them to this calling?

", "chatDisplay": true, "actionType": "action", "cost": [ diff --git a/src/packs/environments/environment_Haunted_City_OzYbizKraK92FDiI.json b/src/packs/environments/environment_Haunted_City_OzYbizKraK92FDiI.json index bbc3ef0a..564612cb 100644 --- a/src/packs/environments/environment_Haunted_City_OzYbizKraK92FDiI.json +++ b/src/packs/environments/environment_Haunted_City_OzYbizKraK92FDiI.json @@ -44,9 +44,12 @@ "src": "systems/daggerheart/assets/icons/documents/actors/forest.svg", "anchorX": 0.5, "anchorY": 0.5, + "offsetX": 0, + "offsetY": 0, "fit": "contain", "scaleX": 1, "scaleY": 1, + "rotation": 0, "tint": "#ffffff", "alphaThreshold": 0.75 }, @@ -97,7 +100,7 @@ "saturation": 0, "contrast": 0 }, - "detectionModes": {}, + "detectionModes": [], "occludable": { "radius": 0 }, @@ -123,8 +126,7 @@ "flags": {}, "randomImg": false, "appendNumber": false, - "prependAdjective": false, - "depth": 1 + "prependAdjective": false }, "items": [ { @@ -280,7 +282,7 @@ "type": "countdown", "_id": "VhqZKDA4032i8zY3", "systemPath": "actions", - "description": "", + "description": "

Spend a Fear to manifest the echo of a past disaster that ravaged the city. Activate a Progress Countdown (5) as the disaster replays around the PCs. To complete the countdown and escape the catastrophe, the PCs must overcome threats such as rampaging fires, stampeding civilians, collapsing buildings, or crumbling streets, while recalling history and finding clues to escape the inevitable.

Is this the disaster that led the city to be abandoned? What is known about this disaster and how could that help the PCs escape?

", "chatDisplay": true, "originItem": { "type": "itemCollection" diff --git a/src/packs/environments/environment_Imperial_Court_jr1xAoXzVwVblzxI.json b/src/packs/environments/environment_Imperial_Court_jr1xAoXzVwVblzxI.json index 93851e3c..5807d43c 100644 --- a/src/packs/environments/environment_Imperial_Court_jr1xAoXzVwVblzxI.json +++ b/src/packs/environments/environment_Imperial_Court_jr1xAoXzVwVblzxI.json @@ -46,9 +46,12 @@ "src": "systems/daggerheart/assets/icons/documents/actors/forest.svg", "anchorX": 0.5, "anchorY": 0.5, + "offsetX": 0, + "offsetY": 0, "fit": "contain", "scaleX": 1, "scaleY": 1, + "rotation": 0, "tint": "#ffffff", "alphaThreshold": 0.75 }, @@ -99,7 +102,7 @@ "saturation": 0, "contrast": 0 }, - "detectionModes": {}, + "detectionModes": [], "occludable": { "radius": 0 }, @@ -125,8 +128,7 @@ "flags": {}, "randomImg": false, "appendNumber": false, - "prependAdjective": false, - "depth": 1 + "prependAdjective": false }, "items": [ { @@ -295,7 +297,7 @@ "type": "attack", "_id": "9ipckCFMz9DVw8ab", "systemPath": "actions", - "description": "", + "description": "

Spend a Fear to tick down a long-term countdown related to the empire’s agenda by [[/r 1d4]]. If this triggers the countdown, a proclamation related to the agenda is announced at court as the plan is executed.

What display of power or transfer of wealth was needed to expedite this plan? Whose lives were disrupted or upended to make this happen?

", "chatDisplay": true, "actionType": "action", "cost": [], diff --git a/src/packs/environments/environment_Necromancer_s_Ossuary_h3KyRL7AshhLAmcH.json b/src/packs/environments/environment_Necromancer_s_Ossuary_h3KyRL7AshhLAmcH.json index 3d87930c..299e8729 100644 --- a/src/packs/environments/environment_Necromancer_s_Ossuary_h3KyRL7AshhLAmcH.json +++ b/src/packs/environments/environment_Necromancer_s_Ossuary_h3KyRL7AshhLAmcH.json @@ -137,7 +137,7 @@ "type": "damage", "_id": "jVY198vniaTSlgsX", "systemPath": "actions", - "description": "", + "description": "

A feature or action that clears HP requires spending a Hope to use. If it already costs Hope, a PC must spend an additional Hope.

What does it feel like to try to heal in a place so antithetical to life?

", "chatDisplay": true, "actionType": "action", "cost": [], @@ -240,7 +240,7 @@ "type": "attack", "_id": "M1mOwi4Limw2hRwL", "systemPath": "actions", - "description": "", + "description": "

All targets within Close range of a point you choose in this environment must succeed on an Agility Reaction Roll or take 4d8+8 physical damage from skeletal shrapnel as part of the ossuary detonates around them.

What ancient skeletal architecture is destroyed? What bones stick in your armor?

", "chatDisplay": true, "actionType": "action", "cost": [], @@ -421,7 +421,7 @@ "type": "effect", "_id": "hFeTdiHWeCYkb8Hg", "systemPath": "actions", - "description": "", + "description": "

Spend a Fear to summon [[/r 1d6]] @UUID[Compendium.daggerheart.adversaries.Actor.gP3fWTLzSFnpA8EJ]{Rotted Zombie}, two @UUID[Compendium.daggerheart.adversaries.Actor.CP6iRfHdyFWniTHY]{Perfected Zombie}, or a @UUID[Compendium.daggerheart.adversaries.Actor.YhJrP7rTBiRdX5Fp]{Zombie Legion}, who appear at Close range of a chosen PC.

Who were these people before they became the necromancer’s pawns? What vestiges of those lives remain for the heroes to see?

", "chatDisplay": true, "actionType": "action", "cost": [ diff --git a/src/packs/environments/environment_Pitched_Battle_EWD3ZsLoK6VMVOf7.json b/src/packs/environments/environment_Pitched_Battle_EWD3ZsLoK6VMVOf7.json index 120b92a0..42fbd8f9 100644 --- a/src/packs/environments/environment_Pitched_Battle_EWD3ZsLoK6VMVOf7.json +++ b/src/packs/environments/environment_Pitched_Battle_EWD3ZsLoK6VMVOf7.json @@ -199,7 +199,7 @@ "type": "attack", "_id": "1giAFbu3tGqXwi8g", "systemPath": "actions", - "description": "", + "description": "

Spend a Fear as a mage from one side uses large-scale destructive magic. Pick a point on the battlefield within Very Far range of the mage. All targets within Close range of that point must make an Agility Reaction Roll. Targets who fail take 3d12+8 magic damage and must mark a Stress.

What form does the attack take—fireball raining acid a storm of blades? What tactical objective is this attack meant to accomplish and what comes next?

", "chatDisplay": true, "actionType": "action", "cost": [ diff --git a/src/packs/items/weapons/weapon_Black_Powder_Revolver_AokqTusPzn0hghkE.json b/src/packs/items/weapons/weapon_Black_Powder_Revolver_AokqTusPzn0hghkE.json index f8b254f3..34371c2b 100644 --- a/src/packs/items/weapons/weapon_Black_Powder_Revolver_AokqTusPzn0hghkE.json +++ b/src/packs/items/weapons/weapon_Black_Powder_Revolver_AokqTusPzn0hghkE.json @@ -128,15 +128,6 @@ "source": "Daggerheart SRD", "page": 48, "artist": "" - }, - "resource": { - "type": "simple", - "value": 1, - "max": "1", - "recovery": null, - "progression": "decreasing", - "dieFaces": "d4", - "icon": "fa-solid fa-gun" } }, "effects": [], diff --git a/src/packs/items/weapons/weapon_Blunderbuss_SLFrK0WmldPo0shz.json b/src/packs/items/weapons/weapon_Blunderbuss_SLFrK0WmldPo0shz.json index 570d8856..9b2f455a 100644 --- a/src/packs/items/weapons/weapon_Blunderbuss_SLFrK0WmldPo0shz.json +++ b/src/packs/items/weapons/weapon_Blunderbuss_SLFrK0WmldPo0shz.json @@ -128,15 +128,6 @@ "source": "Daggerheart SRD", "page": 46, "artist": "" - }, - "resource": { - "type": "simple", - "value": 1, - "max": "1", - "recovery": null, - "progression": "decreasing", - "dieFaces": "d4", - "icon": "fa-solid fa-gun" } }, "effects": [], diff --git a/src/packs/items/weapons/weapon_Hand_Cannon_MyGz8nd5sieRQ7zl.json b/src/packs/items/weapons/weapon_Hand_Cannon_MyGz8nd5sieRQ7zl.json index f9d54bcd..4967c6e4 100644 --- a/src/packs/items/weapons/weapon_Hand_Cannon_MyGz8nd5sieRQ7zl.json +++ b/src/packs/items/weapons/weapon_Hand_Cannon_MyGz8nd5sieRQ7zl.json @@ -128,15 +128,6 @@ "source": "Daggerheart SRD", "page": 50, "artist": "" - }, - "resource": { - "type": "simple", - "value": 1, - "max": "1", - "recovery": null, - "progression": "decreasing", - "dieFaces": "d4", - "icon": "fa-solid fa-gun" } }, "effects": [], diff --git a/src/packs/items/weapons/weapon_Ilmari_s_Rifle_TMrUzVC3KvcHmdt8.json b/src/packs/items/weapons/weapon_Ilmari_s_Rifle_TMrUzVC3KvcHmdt8.json index 512168f5..cddd762a 100644 --- a/src/packs/items/weapons/weapon_Ilmari_s_Rifle_TMrUzVC3KvcHmdt8.json +++ b/src/packs/items/weapons/weapon_Ilmari_s_Rifle_TMrUzVC3KvcHmdt8.json @@ -128,15 +128,6 @@ "source": "Daggerheart SRD", "page": 49, "artist": "" - }, - "resource": { - "type": "simple", - "value": 1, - "max": "1", - "recovery": null, - "progression": "decreasing", - "dieFaces": "d4", - "icon": "fa-solid fa-gun" } }, "effects": [], diff --git a/src/packs/items/weapons/weapon_Magus_Revolver_jGykNGQiKm63tCiE.json b/src/packs/items/weapons/weapon_Magus_Revolver_jGykNGQiKm63tCiE.json index fd3e86d3..9dbbb1c1 100644 --- a/src/packs/items/weapons/weapon_Magus_Revolver_jGykNGQiKm63tCiE.json +++ b/src/packs/items/weapons/weapon_Magus_Revolver_jGykNGQiKm63tCiE.json @@ -128,15 +128,6 @@ "source": "Daggerheart SRD", "page": 51, "artist": "" - }, - "resource": { - "type": "simple", - "value": 1, - "max": "1", - "recovery": null, - "progression": "decreasing", - "dieFaces": "d4", - "icon": "fa-solid fa-gun" } }, "effects": [], diff --git a/styles/less/dialog/damage-selection/sheet.less b/styles/less/dialog/damage-selection/sheet.less index 0bb0f2be..9f8cfc8a 100644 --- a/styles/less/dialog/damage-selection/sheet.less +++ b/styles/less/dialog/damage-selection/sheet.less @@ -17,13 +17,6 @@ } } - .section-header { - font-size: var(--font-size-20); - color: light-dark(@dark, @beige); - text-align: center; - margin-bottom: -12px; - } - .bonuses { gap: 4px; .critical-chip { diff --git a/styles/less/global/chat.less b/styles/less/global/chat.less index 4e29cfff..b9478ea4 100644 --- a/styles/less/global/chat.less +++ b/styles/less/global/chat.less @@ -7,12 +7,12 @@ .chat-log .chat-message { background-image: url('../assets/parchments/dh-parchment-light.png'); - .message-header .message-header-main .message-metadata, - .message-header .message-header-main .name { + .message-header .message-header-metadata .message-metadata, + .message-header .message-header-main .message-sub-header-container { color: @dark; } - .message-header .message-header-main h4 { + .message-header .message-header-main .message-sub-header-container h4 { color: @dark-blue; } @@ -42,12 +42,27 @@ .message-header { display: flex; - gap: 8px; + gap: 4px; padding: 8px; - align-items: center; - .portrait { - flex: 0 0 auto; + .message-header-metadata { + flex: none; + display: flex; + flex-direction: column; + + .message-metadata { + font-family: @font-body; + color: @beige; + } + } + + .message-header-main { + display: flex; + align-items: center; + gap: 8px; + flex: 1; + overflow: hidden; + .actor-img { border-radius: 50%; width: 40px; @@ -55,60 +70,20 @@ object-fit: cover; object-position: top center; } - } - .message-header-main { - display: flex; - align-items: center; - column-gap: 4px; - row-gap: 2px; - flex: 1; - overflow: hidden; - - display: grid; - grid-template: - "title metadata" - "subtitle subtitle"; - - h4 { - font-size: var(--font-size-16); - font-weight: bold; - margin-bottom: 0; - font-family: @font-subtitle; - color: @golden; - white-space: nowrap; - text-overflow: ellipsis; - flex: 1; - overflow: hidden; - align-self: flex-end; - } - - .message-metadata { - font-family: @font-body; - color: @beige; - white-space: nowrap; - align-items: baseline; - .message-timestamp { - font-size: var(--font-size-11); - } - } - - .subtitle { - grid-area: subtitle; + .message-sub-header-container { flex: 1; display: flex; + flex-direction: column; justify-content: space-between; color: @beige; - gap: 4px; - align-items: baseline; - line-height: 1; - .name { - flex: 1; - } - .whisper-to { - color: @color-text-subtle; - flex: 0; - white-space: nowrap; + + h4 { + font-size: var(--font-size-16); + font-weight: bold; + margin-bottom: 0; + font-family: @font-subtitle; + color: @golden; } } } diff --git a/styles/less/global/elements.less b/styles/less/global/elements.less index d31e09b9..e35f527a 100755 --- a/styles/less/global/elements.less +++ b/styles/less/global/elements.less @@ -595,86 +595,7 @@ margin-top: 4px; color: light-dark(#14142599, #efe6d850); font-size: var(--font-size-12); - padding-left: 16px; - } - - section.gm-notes-section { - padding-bottom: var(--spacer-4); - header.gm-notes + p { - margin-top: 0; - } - } - - header.gm-notes { - position: relative; - display: flex; - gap: 6px; - align-items: center; - &::before, - &::after { - content: " "; - flex: 1; - border-bottom: 1px solid var(--color-dark-6); - } - &::before { - mask-image: linear-gradient(270deg, black 0%, black calc(100% - 10px), transparent 100%); - } - &::after { - mask-image: linear-gradient(270deg, transparent 0%, black 10px, black 100%); - } - margin-top: var(--spacer-8); - margin-bottom: var(--spacer-4); - font-size: var(--font-size-11); - text-transform: uppercase; - } - - secret-block { - display: block; - - /** A buffer to make the hover behavior work a bit better. The bottom in the button needs to compensate */ - @buffer: 8px; - margin-top: -@buffer; - padding-top: @buffer; - - button.reveal { - --button-size: 1rem; - height: var(--button-size); - position: absolute; - margin: auto; - left: 0; - right: 0; - width: min-content; - padding: 1px 8px 0 8px; - bottom: calc(100% - 0.4375rem - 1px); - - background-color: var(--dh-window-button-color-bg); // todo: find a better var name - border-color: var(--color-secret-border); - color: var(--dh-window-button-color-text); - font-size: var(--font-size-10); - user-select: none; - text-transform: uppercase; - white-space: nowrap; - - visibility: hidden; - } - - &:hover button.reveal { - visibility: visible; - } - } - - /** - * The element inside a secret-block. - * This is separate since during prosemirror editing, the secret-block container does not exist. - */ - section.secret { - --color-secret-bg: @red-10; - --color-revealed-bg: @green-10; - position: relative; - padding: 0; - p { - margin: 0.5rem 0; - } + padding-left: 3px; } } @@ -896,8 +817,4 @@ right: 2px; } } - - .gm-notes { - font-style: italic; - } } diff --git a/styles/less/global/feature-section.less b/styles/less/global/feature-section.less index ecfb4ff6..2fd4e20f 100644 --- a/styles/less/global/feature-section.less +++ b/styles/less/global/feature-section.less @@ -3,7 +3,7 @@ .sheet.daggerheart.dh-style.item { .tab.features { - padding: 7px 10px; + padding: 0 10px; overflow-y: auto; .feature-list { display: flex; diff --git a/styles/less/global/global.less b/styles/less/global/global.less index 2c44c94e..19a9e519 100644 --- a/styles/less/global/global.less +++ b/styles/less/global/global.less @@ -111,7 +111,3 @@ body.theme-light, .themed.theme-light { color-scheme: light; } - -body:not([data-gm=true]) [data-visibility="gm"] { - display: none; -} \ No newline at end of file diff --git a/styles/less/global/inventory-item.less b/styles/less/global/inventory-item.less index d2ca76c6..2e6cc863 100644 --- a/styles/less/global/inventory-item.less +++ b/styles/less/global/inventory-item.less @@ -144,10 +144,6 @@ display: flex; align-items: center; gap: 4px; - - .unloaded { - opacity: 0.5; - } } } @@ -167,7 +163,37 @@ } .inventory-description { overflow: hidden; - .typography(); + + h1 { + font-size: var(--font-size-32); + } + h2 { + font-size: var(--font-size-28); + font-weight: 600; + } + h3 { + font-size: var(--font-size-20); + font-weight: 600; + } + h4 { + font-size: var(--font-size-16); + color: @beige; + font-weight: 600; + } + + ul, + ol { + margin: 1rem 0; + padding: 0 0 0 1.25rem; + + li { + margin-bottom: 0.25rem; + } + } + + ul { + list-style: disc; + } } } .item-resources { diff --git a/styles/less/global/item-header.less b/styles/less/global/item-header.less index 1a8d7fce..f47ca7dc 100755 --- a/styles/less/global/item-header.less +++ b/styles/less/global/item-header.less @@ -12,14 +12,12 @@ }); .application.sheet.daggerheart.dh-style { - --portrait-size: 150px; - .item-sheet-header { display: flex; .profile { - height: var(--portrait-size); - width: var(--portrait-size); + height: 150px; + width: 150px; object-fit: cover; border-right: 1px solid light-dark(@dark-blue, @golden); border-bottom: 1px solid light-dark(@dark-blue, @golden); @@ -36,24 +34,19 @@ text-align: center; width: 80%; - .item-name { - display: flex; - flex-direction: column; - margin: 10px 10px 0 10px; - input[type='text'] { - font-size: var(--font-size-30); - text-align: center; - width: 100%; - transition: all 0.3s ease; - outline: 2px solid transparent; - border: 1px solid transparent; - text-overflow: ellipsis; + .item-name input[type='text'] { + font-size: var(--font-size-32); + height: 42px; + text-align: center; + width: 90%; + transition: all 0.3s ease; + outline: 2px solid transparent; + border: 1px solid transparent; - &:hover[type='text'], - &:focus[type='text'] { - box-shadow: none; - outline: 2px solid light-dark(@dark-blue, @golden); - } + &:hover[type='text'], + &:focus[type='text'] { + box-shadow: none; + outline: 2px solid light-dark(@dark-blue, @golden); } } diff --git a/styles/less/global/prose-mirror.less b/styles/less/global/prose-mirror.less index 430ca79d..27048ddf 100644 --- a/styles/less/global/prose-mirror.less +++ b/styles/less/global/prose-mirror.less @@ -14,7 +14,36 @@ } .editor-content { .with-scroll-shadows(); - .typography(); + h1 { + font-size: var(--font-size-32); + } + h2 { + font-size: var(--font-size-28); + font-weight: 600; + } + h3 { + font-size: var(--font-size-20); + font-weight: 600; + } + h4 { + font-size: var(--font-size-16); + color: light-dark(@dark, @beige); + font-weight: 600; + } + + ul, + ol { + margin: 1rem 0; + padding: 0 0 0 1.25rem; + + li { + margin-bottom: 0.25rem; + } + } + + ul { + list-style: disc; + } } // Fixes centering and makes it not render over scrollbar &:hover button.toggle:enabled { diff --git a/styles/less/global/sheet.less b/styles/less/global/sheet.less index d7be1a84..8381c7c3 100755 --- a/styles/less/global/sheet.less +++ b/styles/less/global/sheet.less @@ -36,8 +36,8 @@ body.game:is(.performance-low, .noblur) { } button { - background: var(--dh-window-button-color-bg); - color: var(--dh-window-button-color-text); + background: light-dark(#e8e6e3, @deep-black); + color: light-dark(@dark-blue, @beige); border: 1px solid light-dark(@dark-blue, transparent); padding: 0; diff --git a/styles/less/global/tab-description.less b/styles/less/global/tab-description.less index e2869723..5c18e02b 100644 --- a/styles/less/global/tab-description.less +++ b/styles/less/global/tab-description.less @@ -6,80 +6,11 @@ display: flex; flex-direction: column; flex: 1; - overflow: hidden; - padding: 0; - margin: 0; + overflow-y: hidden !important; + padding-top: 10px; - .description-section { - flex: 1; - display: flex; - flex-direction: column; - overflow: auto; - padding: 12px 16px 4px 16px; - .with-scroll-shadows(); - prose-mirror { - button.toggle { - top: 0px; - right: 0; - } - button[data-action=editGMNote] { - right: calc(var(--button-size) + 4px); - } - &.inactive { - height: unset!important; - overflow: unset; - .editor-content { - position: relative; - overflow: unset; - - // Allows content links to peek out - margin-top: -4px; - padding: 4px 0 0 0; - } - } - &.active { - --min-height: 250px; - padding: 8px 0 0 16px; - button[data-action=editGMNote] { - display: none; - } - .editor-content { - padding-right: 16px; - padding-bottom: 4px; - } - } - } - /** Hide editors that are empty when inactive if we need them to be */ - prose-mirror.inactive.hide-if-inactive { - display: none; - } - &:has(prose-mirror.active) { - padding: 0; - } - /** Description should fill available room (with overriden exceptions) */ - prose-mirror[name="system.description"] { - flex: 1 0; - } - &:has(prose-mirror[name="system.gmNotes"]:not(.hide-if-inactive)) { - prose-mirror.inactive { - --min-height: 3rem; - &[name="system.description"] { - flex: 0 0; - } - &[name="system.gmNotes"] { - flex: 1 0; - } - } - } - } - - /** Hide other elements if an editor is open */ - &:has(prose-mirror.active) { - prose-mirror.inactive, - header.gm-notes, - .artist-attribution { - display: none; - } + prose-mirror.active + .artist-attribution { + display: none; } } } diff --git a/styles/less/sheets/actors/actor-sheet-shared.less b/styles/less/sheets/actors/actor-sheet-shared.less index a464d7a1..3e233013 100644 --- a/styles/less/sheets/actors/actor-sheet-shared.less +++ b/styles/less/sheets/actors/actor-sheet-shared.less @@ -93,6 +93,10 @@ padding: 8px 0 0 16px; } } + + .artist-attribution { + padding-left: 16px; + } } .search-section { diff --git a/styles/less/sheets/items/beastform.less b/styles/less/sheets/items/beastform.less index 017c4ef0..100b024a 100644 --- a/styles/less/sheets/items/beastform.less +++ b/styles/less/sheets/items/beastform.less @@ -1,6 +1,4 @@ .application.sheet.daggerheart.dh-style.beastform { - --portrait-size: 130px; - .settings.tab { .advantage-on-section { display: flex; @@ -11,11 +9,4 @@ font-style: italic; } } - .tab.features.active { - display: flex; - flex-direction: column; - gap: 10px; - padding: 8px calc(12px - var(--scrollbar-width)) 4px 12px; - .stable-scroll-container(); - } } diff --git a/styles/less/sheets/items/feature.less b/styles/less/sheets/items/feature.less index 9166fac1..f3c7cd49 100644 --- a/styles/less/sheets/items/feature.less +++ b/styles/less/sheets/items/feature.less @@ -2,9 +2,17 @@ @import '../../utils/fonts.less'; .application.sheet.daggerheart.dh-style.feature { - --portrait-size: 130px; + .item-sheet-header { + display: flex; + + .profile { + height: 130px; + width: 130px; + } + } section.tab { + height: 400px; overflow-y: auto; } } diff --git a/styles/less/sheets/items/index.less b/styles/less/sheets/items/index.less index 7f9bb684..7c40a2e3 100644 --- a/styles/less/sheets/items/index.less +++ b/styles/less/sheets/items/index.less @@ -1,6 +1,6 @@ -@import './item-sheet-shared.less'; @import './beastform.less'; @import './class.less'; @import './domain-card.less'; @import './feature.less'; -@import './heritage.less'; \ No newline at end of file +@import './heritage.less'; +@import './item-sheet-shared.less'; \ No newline at end of file diff --git a/styles/less/sheets/items/item-sheet-shared.less b/styles/less/sheets/items/item-sheet-shared.less index 63846b8e..5155ad70 100644 --- a/styles/less/sheets/items/item-sheet-shared.less +++ b/styles/less/sheets/items/item-sheet-shared.less @@ -1,4 +1,4 @@ -.item.daggerheart.dh-style:where(.application.sheet) { +.application.sheet.daggerheart.dh-style.item { &.minimized { .attribution-header-label { display: none; @@ -14,22 +14,4 @@ button.plain.inline-control { flex: 0 0 auto; } - - .tab-navigation { - margin-bottom: 0; - } - - /** Default tab stylings */ - .tab.active { - padding-top: 8px; - .with-scroll-shadows(); - - &.effects { - display: flex; - flex-direction: column; - gap: 10px; - padding: 8px calc(12px - var(--scrollbar-width)) 4px 12px; - .stable-scroll-container(); - } - } } diff --git a/styles/less/ui/chat/ability-use.less b/styles/less/ui/chat/ability-use.less index 9d3cb84e..d028fa7a 100644 --- a/styles/less/ui/chat/ability-use.less +++ b/styles/less/ui/chat/ability-use.less @@ -117,9 +117,7 @@ } .description { - padding: 0; - margin: 8px; - .typography(); + padding: 8px; } .ability-card-footer { @@ -133,11 +131,6 @@ height: 40px; flex: 1 1 calc(50% - 5px); - span { - font-family: @font-body; - font-weight: 700; - } - &:nth-last-child(1):nth-child(odd) { flex-basis: 100%; } diff --git a/styles/less/ui/chat/action.less b/styles/less/ui/chat/action.less index cff617d8..6eeb7a52 100644 --- a/styles/less/ui/chat/action.less +++ b/styles/less/ui/chat/action.less @@ -46,14 +46,9 @@ padding: 0 8px; button { - height: 36px; + height: 40px; flex: 1 1 calc(50% - 5px); - span { - font-family: @font-body; - font-weight: 700; - } - &:nth-last-child(1):nth-child(odd) { flex-basis: 100%; } diff --git a/styles/less/ui/chat/chat.less b/styles/less/ui/chat/chat.less index 1b18a823..4d627e39 100644 --- a/styles/less/ui/chat/chat.less +++ b/styles/less/ui/chat/chat.less @@ -621,74 +621,16 @@ } .roll-buttons { - margin-top: 8px; display: flex; - flex-direction: column; gap: 5px; + margin-top: 8px; button { height: 32px; - } + flex: 1; - .main-buttons { - display: flex; - gap: 5px; - - button { - flex: 1; - - &.end-button { - flex: 0; - } - } - } - - .extra-button-row { - width: 100%; - display: flex; - gap: 5px; - - .image-container { - position: relative; - display: flex; - align-items: center; - justify-content: center; - - img { - height: 24px; - filter: @dark-filter; - } - - .dice-result { - position: absolute; - font-size: 18px; - color: @beige; - filter: drop-shadow(0 0 1px @light-white); - } - } - - .reload-data { - flex: 1; - display: flex; - align-items: center; - justify-content: center; - border-radius: 5px; - gap: 5px; - cursor: pointer; - padding: 5px; - transition: all 0.3s ease; - color: @golden; - background: @golden-40; - - &.failed-check { - background: @red-40; - color: @red; - } - - &.passed-check { - background: @green-40; - color: @green; - } + &.end-button { + flex: 0; } } } diff --git a/styles/less/ui/countdown/countdown.less b/styles/less/ui/countdown/countdown.less index 4e64d50a..96e01ffd 100644 --- a/styles/less/ui/countdown/countdown.less +++ b/styles/less/ui/countdown/countdown.less @@ -32,7 +32,7 @@ background: var(--background); border-radius: 4px; opacity: var(--ui-fade-opacity); - transition: opacity var(--ui-fade-delay) var(--ui-fade-duration); + transition: opacity var(--ui-fade-duration); } &:not(.performance-low, .noblur) { @@ -41,7 +41,6 @@ &:hover::before { opacity: 1; - transition: opacity var(--ui-fade-duration); } #ui-right:has(#effects-display .effect-container) & { diff --git a/styles/less/ui/resources/resources.less b/styles/less/ui/resources/resources.less index 27f11942..3982d990 100644 --- a/styles/less/ui/resources/resources.less +++ b/styles/less/ui/resources/resources.less @@ -1,221 +1,119 @@ :root { - --hotbar-size: 60px; -} - -#interface.theme-dark, -body.theme-dark { - .daggerheart.dh-style.fear-tracker { - --background: url(../assets/parchments/dh-parchment-dark.png); - } -} - -#interface.theme-light, -body.theme-light { - .daggerheart.dh-style.fear-tracker { - --background: url('../assets/parchments/dh-parchment-light.png') no-repeat center; - } -} - -#ui-middle:has(#hotbar.sm), -#ui-middle:has(#hotbar.md.offset) { - #resources { - &.top-center, - &.bottom-center { - width: calc((var(--hotbar-size) * 5) + 32px) !important; - } - } + --shadow-text-stroke: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000; + --fear-animation: background 0.3s ease, box-shadow 0.3s ease, border-color 0.3s ease, opacity 0.3s ease; } #resources { - position: static; min-height: calc(var(--header-height) + 4rem); min-width: 4rem; color: #d3d3d3; - pointer-events: all; - padding: var(--spacer-8); - max-width: 540px; - min-width: 100px; - - &::before { - content: ' '; - position: absolute; - inset: 0; - background: var(--background); - border-radius: 8px; - opacity: var(--ui-fade-opacity); - transition: opacity var(--ui-fade-delay) var(--ui-fade-duration); - + transition: var(--fear-animation); + header, + .controls, + .window-resize-handle { + transition: var(--fear-animation); } - - &:hover::before { - opacity: 1; - transition: opacity var(--ui-fade-duration); - } - - &.free { - position: absolute; - } - - &.topCenter, - &.bottomCenter { - margin: 1rem 0; - width: 100% !important; - transform: translateX(var(--offset)); - transition: all 250ms ease; - } - - &.rightTop { - width: 300px !important; - max-width: 300px; - } - - &.leftBottom { - width: 200px !important; - max-width: 200px; - background: transparent; - } - - &:not(.performance-low, .noblur) { - backdrop-filter: blur(5px); - } - - #ui-right:has(#effects-display .effect-container) & { - right: 62px; - } - - #resource-fear { - position: relative; - - &:hover { - .fear-header { - opacity: 1; - height: 18.75px; - visibility: visible; - } - - .resize-handle { - opacity: 1; - } - } - - .fear-header { - display: flex; - gap: 5px; - pointer-events: all; - margin-bottom: 0.5rem; - opacity: 0; - height: 0; - visibility: hidden; - transition: all 0.3s ease; - - .drag-handle { - cursor: grab; - } - - .fear-title { - font-size: var(--font-size-13); - } - } - - .fear-tokens { + .window-content { + padding: 0.5rem; + #resource-fear { display: flex; flex-direction: row; - flex-wrap: wrap; - justify-content: center; gap: 0.5rem 0.25rem; - - .fear-token { - font-size: var(--font-size-16); - border: 1.5px double light-dark(@dark-15, @dark-golden-80); + flex-wrap: wrap; + i { + font-size: var(--font-size-18); + border: 1px solid rgba(0, 0, 0, 0.5); border-radius: 50%; aspect-ratio: 1; display: flex; justify-content: center; align-items: center; - width: 2.5rem; + width: 3rem; background-color: @primary-color-fear; - color: @beige; + -webkit-box-shadow: 0px 0px 5px 1px rgba(0, 0, 0, 0.75); + box-shadow: 0px 0px 5px 1px rgba(0, 0, 0, 0.75); + color: #d3d3d3; flex-grow: 0; - text-shadow: none; - &.inactive { filter: grayscale(1) !important; opacity: 0.5; } } - } - - .resize-handle { - position: absolute; - bottom: -12px; - right: -9px; - cursor: nwse-resize; - opacity: 0; - transition: all 0.3s ease; - } - - - .resource-bar { - display: flex; - flex-direction: column; - align-items: center; - position: relative; - height: 30px; - width: 100%; - - .progress-bar { - position: absolute; - appearance: none; - width: 100%; - height: 100%; - border: 1px solid light-dark(@dark-15, @dark-golden-80); - border-radius: 999px; - z-index: 0; - background: @dark-blue; - - &::-webkit-progress-bar { - border: none; - background: @dark-blue; - border-radius: 999px; - } - &::-webkit-progress-value { - background: linear-gradient(90deg, rgba(2, 0, 38, 1) 0%, rgba(199, 1, 252, 1) 100%); - border-radius: 999px; - } + .controls, + .resource-bar { + border: 2px solid rgb(153 122 79); + background-color: rgb(24 22 46); } - - .label { - margin: auto 0; - z-index: 2; - height: auto; - text-align: center; - font-size: var(--font-size-18); - color: @beige; - } - } - - .controls { - display: flex; - gap: 8px; - justify-content: center; - align-items: center; - margin-top: 0.5rem; - color: light-dark(@dark, @beige); - - .disabled { - opacity: 0.5; - } - } - - &.isGM { - .fear-token { + .controls { + display: flex; + align-self: center; + border-radius: 50%; + align-items: center; + justify-content: center; + width: 30px; + height: 30px; + font-size: var(--font-size-20); cursor: pointer; - transition: box-shadow 0.15s ease; - &:hover { - box-shadow: 0 0 8px @primary-color-fear ; + font-size: 1.5rem; + } + &.disabled { + opacity: 0.5; + } + } + .resource-bar { + display: flex; + justify-content: center; + border-radius: 6px; + font-size: var(--font-size-20); + overflow: hidden; + position: relative; + padding: 0.25rem 0.5rem; + flex: 1; + text-shadow: var(--shadow-text-stroke); + &:before { + content: ''; + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: var(--fear-percent); + max-width: 100%; + background: linear-gradient(90deg, rgba(2, 0, 38, 1) 0%, rgba(199, 1, 252, 1) 100%); + z-index: 0; + border-radius: 4px; + } + span { + position: inherit; + z-index: 1; + } + &.fear { + } + } + &.isGM { + i { + cursor: pointer; + &:hover { + font-size: var(--font-size-20); + } } } } } + button[data-action='close'] { + display: none; + } + &:not(:hover):not(.minimized) { + background: transparent; + box-shadow: unset; + border-color: transparent; + header, + #resource-fear .controls, + .window-resize-handle { + opacity: 0; + } + } + &:has(.fear-bar) { + min-width: 200px; + } } diff --git a/styles/less/utils/colors.less b/styles/less/utils/colors.less index d9358112..bb219ebb 100755 --- a/styles/less/utils/colors.less +++ b/styles/less/utils/colors.less @@ -107,8 +107,6 @@ --dh-input-color-text: @dark; --dh-trait-color-bg: #b1afb6; --dh-trait-color-border: #8e8d96; - --dh-window-button-color-bg: #e8e6e3; - --dh-window-button-color-text: @dark-blue; } } @scope (.theme-dark) to (.themed) { @@ -126,8 +124,6 @@ --dh-input-color-text: @beige; --dh-trait-color-bg: #50433F; --dh-trait-color-border: #927952; - --dh-window-button-color-bg: @deep-black; - --dh-window-button-color-text: @beige; } } diff --git a/styles/less/utils/mixin.less b/styles/less/utils/mixin.less index 429fb3ef..2ce85166 100644 --- a/styles/less/utils/mixin.less +++ b/styles/less/utils/mixin.less @@ -203,38 +203,4 @@ overflow-y: auto; scrollbar-gutter: stable; .with-scroll-shadows(); -} - -/** Typography stylings for most longform text, usually item descriptions */ -.typography() { - h1 { - font-size: var(--font-size-32); - } - h2 { - font-size: var(--font-size-28); - font-weight: 600; - } - h3 { - font-size: var(--font-size-20); - font-weight: 600; - } - h4 { - font-size: var(--font-size-16); - color: light-dark(@dark, @beige); - font-weight: 600; - } - - ul, - ol { - margin: 0.5rem 0; - padding: 0 0 0 1.25rem; - - li { - margin-bottom: 0.25rem; - } - } - - ul { - list-style: disc; - } -} +} \ No newline at end of file diff --git a/system.json b/system.json index 0aafe3f5..43e06254 100644 --- a/system.json +++ b/system.json @@ -2,15 +2,15 @@ "id": "daggerheart", "title": "Daggerheart", "description": "An unofficial implementation of the Daggerheart system", - "version": "2.5.4", + "version": "2.5.2", "compatibility": { "minimum": "14.364", - "verified": "14.365", + "verified": "14.364", "maximum": "14" }, "url": "https://github.com/Foundryborne/daggerheart", "manifest": "https://raw.githubusercontent.com/Foundryborne/daggerheart/v14/system.json", - "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.5.4/system.zip", + "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.5.2/system.zip", "authors": [ { "name": "WBHarry" @@ -256,34 +256,34 @@ }, "Item": { "ancestry": { - "htmlFields": ["description", "gmNotes"] + "htmlFields": ["description"] }, "community": { - "htmlFields": ["description", "gmNotes"] + "htmlFields": ["description"] }, "class": { - "htmlFields": ["description", "gmNotes"] + "htmlFields": ["description"] }, "subclass": { - "htmlFields": ["description", "gmNotes"] + "htmlFields": ["description"] }, "feature": { - "htmlFields": ["description", "gmNotes"] + "htmlFields": ["description"] }, "domainCard": { - "htmlFields": ["description", "gmNotes"] + "htmlFields": ["description"] }, "loot": { - "htmlFields": ["description", "gmNotes"] + "htmlFields": ["description"] }, "consumable": { - "htmlFields": ["description", "gmNotes"] + "htmlFields": ["description"] }, "weapon": { - "htmlFields": ["description", "gmNotes"] + "htmlFields": ["description"] }, "armor": { - "htmlFields": ["description", "gmNotes"] + "htmlFields": ["description"] }, "beastform": {} }, diff --git a/templates/actionTypes/cost.hbs b/templates/actionTypes/cost.hbs index b8661b5b..7a9f33d9 100644 --- a/templates/actionTypes/cost.hbs +++ b/templates/actionTypes/cost.hbs @@ -9,7 +9,7 @@ {{/if}}
{{formField ../fields.scalable label="DAGGERHEART.GENERAL.scalable" value=cost.scalable name=(concat "cost." index ".scalable") classes="checkbox" localize=true}} - {{formField ../fields.key choices=(@root.disableOption index @root.costOptions ../source) label="DAGGERHEART.GENERAL.Resource.single" value=cost.key name=(concat "cost." index ".key") localize=true blank=false}} + {{formField ../fields.key choices=(@root.disableOption index @root.costOptions ../source) label="DAGGERHEART.GENERAL.resource" value=cost.key name=(concat "cost." index ".key") localize=true blank=false}} {{formField ../fields.value label="DAGGERHEART.GENERAL.amount" value=cost.value name=(concat "cost." index ".value") localize=true}} {{formField ../fields.step label="DAGGERHEART.GENERAL.step" value=cost.step name=(concat "cost." index ".step") disabled=(not cost.scalable) localize=true}} diff --git a/templates/actionTypes/damage.hbs b/templates/actionTypes/damage.hbs index d430b51f..03300840 100644 --- a/templates/actionTypes/damage.hbs +++ b/templates/actionTypes/damage.hbs @@ -1,114 +1,92 @@ -{{#unless (eq @root.source.type 'healing')}} -
- + +
+ + {{#if (eq @root.source.type 'healing')}} + {{localize "DAGGERHEART.GENERAL.healing"}} + {{else}} {{localize "DAGGERHEART.GENERAL.damage"}} - {{#if source.main}} - - {{else}} - - {{/if}} - - - {{#if source.main}} -
- {{#if @root.hasBaseDamage}} - {{formField @root.fields.damage.fields.main.fields.includeBase value=source.main.includeBase name="damage.main.includeBase" classes="checkbox" localize=true }} - {{/if}} - {{#unless (eq @root.source.type 'healing')}} - {{formField baseFields.main.fields.direct value=source.main.direct name=(concat path "damage.main.direct") localize=true classes="checkbox"}} - {{/unless}} - {{#if (and @root.isNPC (not (eq path 'system.attack.')))}} - {{formField baseFields.main.fields.groupAttack value=source.main.groupAttack name=(concat path "damage.main.groupAttack") localize=true classes="select"}} - {{/if}} -
- {{> damageData damage=source.main fields=fields.main.fields basePath=(concat path "damage.main")}} - {{#if horde}} - {{> hordeDamage source=source.main fields=fields.main.fields basePath=(concat path "damage.main")}} - {{/if}} - {{#if (ne @root.source.type 'healing')}} - {{formField fields.main.fields.type value=source.main.type name=(concat path "damage.main.type") localize=true}} - {{/if}} {{/if}} -
-{{/unless}} + {{#unless (eq path 'system.attack.')}}{{/unless}} +
+
+ {{#if @root.hasBaseDamage}} + {{formField @root.fields.damage.fields.includeBase value=@root.source.damage.includeBase name="damage.includeBase" classes="checkbox" localize=true }} + {{/if}} + {{#unless (eq @root.source.type 'healing')}} + {{formField baseFields.direct value=source.direct name=(concat path "damage.direct") localize=true classes="checkbox"}} + {{/unless}} + {{#if (and @root.isNPC (not (eq path 'system.attack.')))}} + {{formField baseFields.groupAttack value=source.groupAttack name=(concat path "damage.groupAttack") localize=true classes="select"}} + {{/if}} +
-{{#unless (eq path 'system.attack.')}} - {{! In the future, consider allowing this even on NPCs}} -
- - {{#if (eq @root.source.type 'healing')}} - {{localize "DAGGERHEART.GENERAL.healing"}} - {{else}} - {{localize "DAGGERHEART.ACTIONS.Config.damage.markResources"}} - {{/if}} - {{#unless @root.allDamageTypesUsed}}{{/unless}} - - {{#each source.resources as |dmg key|}} -
- - - {{localize (concat "DAGGERHEART.CONFIG.HealingType." dmg.applyTo ".name")}} - {{#unless (or dmg.base ../path)}} - - {{/unless}} - - {{> damageData damage=dmg fields=../fields.resources.element.fields basePath=(concat ../path "damage.resources." dmg.applyTo)}} -
-
- {{/each}} - -{{/unless}} + {{!-- Handlebars uses Symbol.Iterator to produce index|key. This isn't compatible with our parts object, so we instead use applyTo, which is the same value --}} + {{#each source.parts as |dmg key|}} +
+ + + {{localize (concat "DAGGERHEART.CONFIG.HealingType." dmg.applyTo ".name")}} + {{#unless (or dmg.base ../path)}} + + {{/unless}} + + + {{#if (and (not @root.isNPC) @root.hasRoll (not dmg.base))}} + {{formField ../fields.resultBased value=dmg.resultBased name=(concat "damage.parts." dmg.applyTo ".resultBased") localize=true classes="checkbox"}} + {{/if}} + {{#if (and (not @root.isNPC) @root.hasRoll (not dmg.base) dmg.resultBased)}} +
+
+ {{localize "DAGGERHEART.GENERAL.withThing" thing=(localize "DAGGERHEART.GENERAL.hope")}} + {{> formula fields=../fields.value.fields type=../fields.type dmg=dmg source=dmg.value target="value" key=dmg.applyTo path=../path}} +
+
+ {{localize "DAGGERHEART.GENERAL.withThing" thing=(localize "DAGGERHEART.GENERAL.fear")}} + {{> formula fields=../fields.valueAlt.fields type=../fields.type dmg=dmg source=dmg.valueAlt target="valueAlt" key=dmg.applyTo path=../path}} +
+
+ {{else}} + {{> formula fields=../fields.value.fields type=../fields.type dmg=dmg source=dmg.value target="value" key=dmg.applyTo path=../path}} + {{/if}} + + {{#if (and (eq dmg.applyTo 'hitPoints') (ne @root.source.type 'healing'))}} + {{formField ../fields.type value=dmg.type name=(concat ../path "damage.parts." dmg.applyTo ".type") localize=true}} + {{/if}} + + {{#if ../horde}} +
+ {{localize "DAGGERHEART.ACTORS.Adversary.hordeDamage"}} +
+ + {{formField ../fields.valueAlt.fields.flatMultiplier value=dmg.valueAlt.flatMultiplier name=(concat ../path "damage.parts." dmg.applyTo ".valueAlt.flatMultiplier") label="DAGGERHEART.ACTIONS.Settings.multiplier" classes="inline-child" localize=true }} + {{formField ../fields.valueAlt.fields.dice value=dmg.valueAlt.dice name=(concat ../path "damage.parts." dmg.applyTo ".valueAlt.dice") classes="inline-child" localize=true}} + {{formField ../fields.valueAlt.fields.bonus value=dmg.valueAlt.bonus name=(concat ../path "damage.parts." dmg.applyTo ".valueAlt.bonus") localize=true classes="inline-child"}} +
+
+ {{/if}} + + +
+ {{/each}} + {{#*inline "formula"}} - {{#unless isBase}} - {{formField fields.custom.fields.enabled value=source.custom.enabled name=(concat basePath ".custom.enabled") classes="checkbox" localize=true}} - {{/unless}} - {{#if source.custom.enabled}} - {{formField fields.custom.fields.formula value=source.custom.formula name=(concat basePath ".custom.formula") localize=true}} - {{else}} -
- {{#unless @root.isNPC}} - {{formField fields.multiplier value=source.multiplier name=(concat basePath ".multiplier") localize=true}} - {{/unless}} - {{#if (eq source.multiplier 'flat')}}{{formField fields.flatMultiplier value=source.flatMultiplier name=(concat basePath ".flatMultiplier") localize=true }}{{/if}} - {{formField fields.dice value=source.dice name=(concat basePath ".dice") localize=true}} - {{formField fields.bonus value=source.bonus name=(concat basePath ".bonus") localize=true}} -
- {{/if}} - {{#if @root.isNPC}} - - {{/if}} -{{/inline}} - -{{#*inline "damageData"}} - {{#if (and (not @root.isNPC) @root.hasRoll (not damage.base))}} - {{formField fields.resultBased value=damage.resultBased name=(concat basePath ".resultBased") localize=true classes="checkbox"}} - {{/if}} - {{#if (and (not @root.isNPC) @root.hasRoll (not damage.base) damage.resultBased)}} -
-
- {{localize "DAGGERHEART.GENERAL.withThing" thing=(localize "DAGGERHEART.GENERAL.hope")}} - {{> formula key=damage.applyTo fields=fields.value.fields type=fields.type isBase=damage.base source=damage.value basePath=(concat basePath ".value")}} -
-
- {{localize "DAGGERHEART.GENERAL.withThing" thing=(localize "DAGGERHEART.GENERAL.fear")}} - {{> formula key=damage.applyTo fields=fields.valueAlt.fields type=fields.type isBase=damage.base source=damage.valueAlt basePath=(concat basePath ".valueAlt")}} -
-
- {{else}} - {{> formula key=damage.applyTo fields=fields.value.fields type=fields.type isBase=damage.base source=damage.value basePath=(concat basePath ".value")}} - {{/if}} - -{{/inline}} - -{{#*inline "hordeDamage"}} -
- {{localize "DAGGERHEART.ACTORS.Adversary.hordeDamage"}} -
- - {{formField fields.valueAlt.fields.flatMultiplier value=source.valueAlt.flatMultiplier name=(concat basePath ".valueAlt.flatMultiplier") label="DAGGERHEART.ACTIONS.Settings.multiplier" classes="inline-child" localize=true }} - {{formField fields.valueAlt.fields.dice value=source.valueAlt.dice name=(concat basePath ".valueAlt.dice") classes="inline-child" localize=true}} - {{formField fields.valueAlt.fields.bonus value=source.valueAlt.bonus name=(concat basePath ".valueAlt.bonus") localize=true classes="inline-child"}} -
-
+ {{#unless dmg.base}} + {{formField fields.custom.fields.enabled value=source.custom.enabled name=(concat path "damage.parts." key "." target ".custom.enabled") classes="checkbox" localize=true}} + {{/unless}} + {{#if source.custom.enabled}} + {{formField fields.custom.fields.formula value=source.custom.formula name=(concat path "damage.parts." key "." target ".custom.formula") localize=true}} + {{else}} +
+ {{#unless @root.isNPC}} + {{formField fields.multiplier value=source.multiplier name=(concat path "damage.parts." key "." target ".multiplier") localize=true}} + {{/unless}} + {{#if (eq source.multiplier 'flat')}}{{formField fields.flatMultiplier value=source.flatMultiplier name=(concat path "damage.parts." key "." target ".flatMultiplier") localize=true }}{{/if}} + {{formField fields.dice value=source.dice name=(concat path "damage.parts." key "." target ".dice") localize=true}} + {{formField fields.bonus value=source.bonus name=(concat path "damage.parts." key "." target ".bonus") localize=true}} +
+ {{/if}} + {{#if @root.isNPC}} + + {{/if}} {{/inline}} \ No newline at end of file diff --git a/templates/actionTypes/resource.hbs b/templates/actionTypes/resource.hbs index 97304b9a..9c8fc965 100644 --- a/templates/actionTypes/resource.hbs +++ b/templates/actionTypes/resource.hbs @@ -1,7 +1,7 @@
-
{{localize "DAGGERHEART.GENERAL.Resource.single"}}
+
{{localize "DAGGERHEART.GENERAL.resource"}}
diff --git a/templates/dialogs/dice-roll/damageSelection.hbs b/templates/dialogs/dice-roll/damageSelection.hbs index e865ebe6..7bcd7063 100644 --- a/templates/dialogs/dice-roll/damageSelection.hbs +++ b/templates/dialogs/dice-roll/damageSelection.hbs @@ -15,7 +15,34 @@ {{/each}}
{{/if}} - + + {{#each @root.formula}} +
+ {{localize "DAGGERHEART.GENERAL.formula"}}: {{roll.formula}} + + {{#with (lookup @root.config.GENERAL.healingTypes applyTo)}} + {{localize label}} + {{/with}} + {{#unless @root.hasHealing}} + {{#if damageTypes}} + {{#each damageTypes as | type | }} + {{#with (lookup @root.config.GENERAL.damageTypes type)}} + + {{/with}} + {{/each}} + {{/if}} + {{/unless}} + +
+
+ + +
+ {{/each}} + {{#if damageOptions.groupAttack}}
{{localize "DAGGERHEART.ACTIONS.Settings.groupAttack.label"}} @@ -32,45 +59,6 @@
{{/if}} - - {{#if @root.damageFormula}} - {{#with @root.damageFormula}} -
- {{localize "DAGGERHEART.GENERAL.formula"}}: {{roll.formula}} - - {{localize "DAGGERHEART.GENERAL.damage"}} - {{#if damageTypes}} - {{#each damageTypes as | type | }} - {{#with (lookup @root.config.GENERAL.damageTypes type)}} - - {{/with}} - {{/each}} - {{/if}} - -
-
- - -
- {{/with}} - {{/if}} - - {{#each @root.resourceFormulas}} -
- {{localize "DAGGERHEART.GENERAL.formula"}}: {{roll.formula}} - - {{#with (lookup @root.config.GENERAL.healingTypes applyTo)}} - {{localize label}} - {{/with}} - -
-
- -
- {{/each}} {{#unless (empty @root.modifiers)}}
@@ -88,7 +76,6 @@ {{/each}}
{{/unless}} -
{{#if directDamage}} - {{/if}} + +
{{else if (eq item.system.resource.type 'diceValue')}}
diff --git a/templates/sheets/global/tabs/tab-description.hbs b/templates/sheets/global/tabs/tab-description.hbs index 3fdf1a93..71995a51 100755 --- a/templates/sheets/global/tabs/tab-description.hbs +++ b/templates/sheets/global/tabs/tab-description.hbs @@ -1,25 +1,11 @@ -
-
- {{formInput systemFields.description value=document.system.description enriched=enrichedDescription toggled=true}} - {{#if (and systemFields.gmNotes @root.user.isGM)}} -
- {{#if enrichedGMNotes}} -
{{localize "DAGGERHEART.ITEMS.FIELDS.gmNotes.label"}}
- {{/if}} - {{{enrichedGMNotes}}} -
- {{/if}} -
- {{#if (and showAttribution document.system.attribution.artist)}} - - {{/if}} +
+ {{formInput systemFields.description value=document.system.description enriched=enrichedDescription toggled=true}} + + {{#if (and showAttribution document.system.attribution.artist)}} + + {{/if}}
\ No newline at end of file diff --git a/templates/sheets/items/weapon/settings.hbs b/templates/sheets/items/weapon/settings.hbs index fbdc1e44..c9805e7c 100644 --- a/templates/sheets/items/weapon/settings.hbs +++ b/templates/sheets/items/weapon/settings.hbs @@ -18,26 +18,26 @@
- {{#with systemFields.attack.fields.damage.fields.main.fields as | fields | }} - {{#with ../document.system.attack.damage.main as | source | }} - {{localize "DAGGERHEART.GENERAL.damage"}} - {{localize "DAGGERHEART.ACTIONS.Config.general.customFormula"}} - {{formInput fields.value.fields.custom.fields.enabled value=source.value.custom.enabled name="system.attack.damage.main.value.custom.enabled"}} - {{#if source.value.custom.enabled}} - {{localize "DAGGERHEART.ACTIONS.Config.general.formula"}} - {{formInput fields.value.fields.custom.fields.formula value=source.value.custom.formula name="system.attack.damage.main.value.custom.formula"}} - {{else}} - {{localize "DAGGERHEART.GENERAL.Dice.single"}} - {{formInput fields.value.fields.dice value=source.value.dice name="system.attack.damage.main.value.dice"}} - {{localize "DAGGERHEART.GENERAL.bonus"}} - {{formInput fields.value.fields.bonus value=source.value.bonus name="system.attack.damage.main.value.bonus" localize=true}} - {{/if}} - {{localize "DAGGERHEART.GENERAL.type"}} - {{formInput fields.type value=source.type name="system.attack.damage.main.type" localize=true}} - {{localize "DAGGERHEART.CONFIG.DamageType.direct.name"}} - {{formInput @root.systemFields.attack.fields.damage.fields.main.fields.direct value=@root.document.system.attack.damage.main.direct name="system.attack.damage.main.direct" localize=true}} - - {{/with}} + {{#with systemFields.attack.fields.damage.fields.parts.element.fields as | fields | }} + {{#with ../document.system.attack.damage.parts.hitPoints as | source | }} + {{localize "DAGGERHEART.GENERAL.damage"}} + {{localize "DAGGERHEART.ACTIONS.Config.general.customFormula"}} + {{formInput fields.value.fields.custom.fields.enabled value=source.value.custom.enabled name="system.attack.damage.parts.hitPoints.value.custom.enabled"}} + {{#if source.value.custom.enabled}} + {{localize "DAGGERHEART.ACTIONS.Config.general.formula"}} + {{formInput fields.value.fields.custom.fields.formula value=source.value.custom.formula name="system.attack.damage.parts.hitPoints.value.custom.formula"}} + {{else}} + {{localize "DAGGERHEART.GENERAL.Dice.single"}} + {{formInput fields.value.fields.dice value=source.value.dice name="system.attack.damage.parts.hitPoints.value.dice"}} + {{localize "DAGGERHEART.GENERAL.bonus"}} + {{formInput fields.value.fields.bonus value=source.value.bonus name="system.attack.damage.parts.hitPoints.value.bonus" localize=true}} + {{/if}} + {{localize "DAGGERHEART.GENERAL.type"}} + {{formInput fields.type value=source.type name="system.attack.damage.parts.hitPoints.type" localize=true}} + {{localize "DAGGERHEART.CONFIG.DamageType.direct.name"}} + {{formInput @root.systemFields.attack.fields.damage.fields.direct value=@root.document.system.attack.damage.direct name="system.attack.damage.direct" localize=true}} + + {{/with}} {{/with}}
diff --git a/templates/ui/chat/action.hbs b/templates/ui/chat/action.hbs index d9ceb417..51840363 100644 --- a/templates/ui/chat/action.hbs +++ b/templates/ui/chat/action.hbs @@ -16,10 +16,10 @@ {{#each action.summon}}
- - + +
- # {{this.quantity}} + # {{this.rolledCount}}
{{/each}}
diff --git a/templates/ui/chat/chat-message.hbs b/templates/ui/chat/chat-message.hbs index 92490cec..87ecce39 100644 --- a/templates/ui/chat/chat-message.hbs +++ b/templates/ui/chat/chat-message.hbs @@ -1,18 +1,23 @@
  • -
    - -
    - {{#if message.title}} -

    {{message.title}}

    - {{else if (not actor.name)}} -

    {{author.name}}

    - {{else}} -

    {{alias}}

    - {{/if}} - + +
    + {{#if message.title}} +

    {{message.title}}

    +
    {{alias}} {{#if author.isGM}}(GM){{/if}}
    + {{else}} + {{#unless actor.name}} +

    {{author.name}}

    + {{else}} +

    {{alias}}

    +
    {{author.name}}
    + {{/unless}} + {{/if}} +
    +
    +
    diff --git a/templates/ui/chat/parts/button-part.hbs b/templates/ui/chat/parts/button-part.hbs index 6dc39804..6bc5f372 100644 --- a/templates/ui/chat/parts/button-part.hbs +++ b/templates/ui/chat/parts/button-part.hbs @@ -1,40 +1,18 @@
    -
    - {{#if areas.length}}{{/if}} - {{#if hasDamage}} - {{#if damage.active}} - - {{else}} - - {{/if}} - {{/if}} - {{#if hasHealing}} - {{#if damage.active}} - - {{else}} - - {{/if}} - {{/if}} - {{#if (and hasEffect)}}{{/if}} -
    - - {{#if parent.system.hasReload}} -
    - -
    - {{#unless reloadCheckValue}} - {{localize "DAGGERHEART.ACTIONS.Reload.notRolled"}} - {{else}} - {{ifThen parent.system.reloadCheckFailed (localize "DAGGERHEART.ACTIONS.Reload.checkFailed") (localize "DAGGERHEART.ACTIONS.Reload.checkPassed")}} - {{/unless}} -
    -
    + {{#if areas.length}}{{/if}} + {{#if hasDamage}} + {{#unless (empty damage)}} + + {{else}} + + {{/unless}} {{/if}} + {{#if hasHealing}} + {{#unless (empty damage)}} + + {{else}} + + {{/unless}} + {{/if}} + {{#if (and hasEffect)}}{{/if}}
    \ No newline at end of file diff --git a/templates/ui/chat/parts/damage-part.hbs b/templates/ui/chat/parts/damage-part.hbs index 7d6d983d..45b09b72 100644 --- a/templates/ui/chat/parts/damage-part.hbs +++ b/templates/ui/chat/parts/damage-part.hbs @@ -1,4 +1,4 @@ -
    +
    {{#if hasHealing}} @@ -10,79 +10,60 @@
    - {{#if damage.main}} - {{> formula roll=damage.main label=(localize "DAGGERHEART.GENERAL.damage") }} - {{/if}} - {{#each damage.resources as | roll index | }} - {{> formula roll=roll label=(ifThen ../hasHealing (localize (concat 'DAGGERHEART.CONFIG.HealingType.' index '.name')) (localize (concat 'DAGGERHEART.CONFIG.HealingType.' index '.inChatRoll'))) }} + {{#each damage as | roll index | }} +
    {{localize (concat 'DAGGERHEART.CONFIG.HealingType.' index '.inChatRoll')}}: {{total}}
    {{/each}}
    - {{#if damage.main}} - {{> damage label=(localize "DAGGERHEART.GENERAL.damage") roll=damage.main isDirect=isDirect }} - {{/if}} - - {{#each damage.resources as | roll index | }} - {{> damage - label=(ifThen ../hasHealing (localize (concat 'DAGGERHEART.CONFIG.HealingType.' index '.name')) (localize (concat 'DAGGERHEART.CONFIG.HealingType.' index '.inChatRoll'))) - roll=roll - isResource=true - }} + {{#each damage as | roll index | }} +
    + + {{#if ../hasHealing}}{{localize (concat 'DAGGERHEART.CONFIG.HealingType.' index '.name')}}{{else}}{{localize (concat 'DAGGERHEART.CONFIG.HealingType.' index '.inChatRoll')}}{{/if}}
    {{localize "DAGGERHEART.GENERAL.total"}}: {{roll.total}}
    {{#if (and (eq index "hitPoints") ../isDirect)}}
    {{localize "DAGGERHEART.CONFIG.DamageType.direct.short"}}
    {{/if}} +
    + {{#each roll.parts}} + {{#if (and (not @root.hasHealing) damageTypes.length)}} + + {{/if}} +
    + {{#if dice.length}} + {{#each dice}} + {{#each results}} + {{#unless discarded}} +
    +
    + {{#if hasRerolls}}{{/if}} + {{result}} +
    +
    + {{/unless}} + {{/each}} + {{/each}} + {{#if modifierTotal}} +
    +
    {{modifierTotal}}
    +
    + {{/if}} + {{else}} +
    +
    {{total}}
    +
    + {{/if}} +
    + {{/each}} +
    {{/each}}
    -
    - -{{#*inline "formula"}} -
    {{label}}: {{roll.total}}
    -{{/inline}} - -{{#*inline "damage"}} -
    - - {{label}} -
    {{localize "DAGGERHEART.GENERAL.total"}}: {{roll.total}}
    {{#if isDirect}}
    {{localize "DAGGERHEART.CONFIG.DamageType.direct.short"}}
    {{/if}} -
    - {{#if (and (not @root.hasHealing) roll.options.damageTypes.length)}} - - {{/if}} -
    - {{#if roll.dice.length}} - {{#each roll.dice}} - {{#each results}} - {{#if active}} -
    -
    - {{#if hasRerolls}}{{/if}} - {{result}} -
    -
    - {{/if}} - {{/each}} - {{/each}} - - {{#if roll.modifierTotal}} -
    -
    {{roll.modifierTotal}}
    -
    - {{/if}} - {{else}} -
    -
    {{roll.total}}
    -
    - {{/if}} -
    -
    -{{/inline}} \ No newline at end of file +
    \ No newline at end of file diff --git a/templates/ui/chat/roll.hbs b/templates/ui/chat/roll.hbs index 362aeadd..c7b17b21 100644 --- a/templates/ui/chat/roll.hbs +++ b/templates/ui/chat/roll.hbs @@ -1,6 +1,5 @@
    {{title}}
    - {{#if actionDescription}}{{> 'systems/daggerheart/templates/ui/chat/parts/description-part.hbs'}}{{/if}} {{#if hasRoll}}
    {{localize "Result"}}
    diff --git a/templates/ui/fearTracker.hbs b/templates/ui/fearTracker.hbs index deae762a..6832ab90 100644 --- a/templates/ui/fearTracker.hbs +++ b/templates/ui/fearTracker.hbs @@ -1,48 +1,16 @@
    -
    - {{#if isFree}} -
    -
    - -
    - {{localize 'DAGGERHEART.GENERAL.fear'}} -
    - {{/if}} - +
    {{#if (eq display 'token')}} -
    - {{#times max}} - - - - {{/times}} -
    + {{#times max}} + + {{/times}} {{/if}} {{#if (eq display 'bar')}} + {{#if isGM}}
    -
    {{/if}}
    - -

    {{current}} / {{max}}

    + {{current}}/{{max}}
    - {{#if isGM}} -
    - - -
    - {{/if}} - {{/if}} - - {{#if isFree}} - - - + {{#if isGM}}
    +
    {{/if}} {{/if}}
    \ No newline at end of file diff --git a/templates/ui/tooltip/attack.hbs b/templates/ui/tooltip/attack.hbs index 903c13a5..8e4a1bb0 100644 --- a/templates/ui/tooltip/attack.hbs +++ b/templates/ui/tooltip/attack.hbs @@ -23,7 +23,7 @@
    {{/if}}
    - {{{damageFormula attack}}} {{{damageSymbols attack.damage.main}}} + {{{damageFormula attack}}} {{{damageSymbols attack.damage.parts}}}
    {{#if description}} diff --git a/templates/ui/tooltip/weapon.hbs b/templates/ui/tooltip/weapon.hbs index 10889015..4adb9c46 100644 --- a/templates/ui/tooltip/weapon.hbs +++ b/templates/ui/tooltip/weapon.hbs @@ -23,7 +23,7 @@ {{/with}}
    - {{{damageFormula item.system.attack}}} {{{damageSymbols item.system.attack.damage.main}}} + {{{damageFormula item.system.attack}}} {{{damageSymbols item.system.attack.damage.parts}}}
    {{#if description}}