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..99b6baeb 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", @@ -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..131ef10f 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 }; @@ -148,24 +145,14 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel { * @param {object} [options] - Options that modify the styling of the rendered template. { headerStyle: undefined|'none'|'large' } * @returns {Promise} */ - 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/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..944f0eec 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++) { 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/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..edc02f9a 100755 --- a/styles/less/global/elements.less +++ b/styles/less/global/elements.less @@ -595,57 +595,31 @@ 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; + padding-left: 3px; } 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; - + position: relative; + section.secret { + background-color: @red-10; + padding: 0; + margin-top: 0.375rem; + &.revealed { + background-color: @green-10; + } + p { + margin: 0.5rem 0; + } + } button.reveal { - --button-size: 1rem; - height: var(--button-size); + --button-size: 0.875rem; position: absolute; margin: auto; left: 0; right: 0; width: min-content; padding: 1px 8px 0 8px; - bottom: calc(100% - 0.4375rem - 1px); + bottom: calc(100% - 0.4375rem - 2px); background-color: var(--dh-window-button-color-bg); // todo: find a better var name border-color: var(--color-secret-border); @@ -653,7 +627,6 @@ font-size: var(--font-size-10); user-select: none; text-transform: uppercase; - white-space: nowrap; visibility: hidden; } @@ -662,20 +635,6 @@ 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; - } - } } .system-daggerheart { @@ -896,8 +855,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..d942133c 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; - } } } 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/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..c31136ad 100644 --- a/styles/less/ui/chat/ability-use.less +++ b/styles/less/ui/chat/ability-use.less @@ -133,11 +133,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/mixin.less b/styles/less/utils/mixin.less index 429fb3ef..fb70d0a3 100644 --- a/styles/less/utils/mixin.less +++ b/styles/less/utils/mixin.less @@ -226,7 +226,7 @@ ul, ol { - margin: 0.5rem 0; + margin: 1rem 0; padding: 0 0 0 1.25rem; li { diff --git a/system.json b/system.json index 0aafe3f5..37242137 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.3", "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.3/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}}