diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index b472f383..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "js/ts.preferGoToSourceDefinition": true -} \ No newline at end of file diff --git a/assets/icons/documents/actors/capybara.svg b/assets/icons/documents/actors/capybara.svg index 8fbe2c06..90deb64a 100644 --- a/assets/icons/documents/actors/capybara.svg +++ b/assets/icons/documents/actors/capybara.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/icons/documents/actors/dark-squad.svg b/assets/icons/documents/actors/dark-squad.svg index 39ca794a..f21b4c5b 100644 --- a/assets/icons/documents/actors/dark-squad.svg +++ b/assets/icons/documents/actors/dark-squad.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/icons/documents/actors/dragon-head.svg b/assets/icons/documents/actors/dragon-head.svg index 324ffbbb..d9e008f5 100644 --- a/assets/icons/documents/actors/dragon-head.svg +++ b/assets/icons/documents/actors/dragon-head.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/icons/documents/actors/drama-masks.svg b/assets/icons/documents/actors/drama-masks.svg index 98b9b68b..84307da0 100644 --- a/assets/icons/documents/actors/drama-masks.svg +++ b/assets/icons/documents/actors/drama-masks.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/icons/documents/actors/forest.svg b/assets/icons/documents/actors/forest.svg index fcdb8a9d..8f7117e8 100644 --- a/assets/icons/documents/actors/forest.svg +++ b/assets/icons/documents/actors/forest.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/icons/documents/items/battered-axe.svg b/assets/icons/documents/items/battered-axe.svg index 436884bd..5d7be27d 100644 --- a/assets/icons/documents/items/battered-axe.svg +++ b/assets/icons/documents/items/battered-axe.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/icons/documents/items/card-play.svg b/assets/icons/documents/items/card-play.svg index 5c29fc43..587cb1c1 100644 --- a/assets/icons/documents/items/card-play.svg +++ b/assets/icons/documents/items/card-play.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/icons/documents/items/chest-armor.svg b/assets/icons/documents/items/chest-armor.svg index 03b82d48..2cef80a6 100644 --- a/assets/icons/documents/items/chest-armor.svg +++ b/assets/icons/documents/items/chest-armor.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/icons/documents/items/family-tree.svg b/assets/icons/documents/items/family-tree.svg index 031f23da..d95c935d 100644 --- a/assets/icons/documents/items/family-tree.svg +++ b/assets/icons/documents/items/family-tree.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/icons/documents/items/laurel-crown.svg b/assets/icons/documents/items/laurel-crown.svg index 38dbcd04..34a54d2a 100644 --- a/assets/icons/documents/items/laurel-crown.svg +++ b/assets/icons/documents/items/laurel-crown.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/icons/documents/items/laurels.svg b/assets/icons/documents/items/laurels.svg index ae7398ec..2c3cdf63 100644 --- a/assets/icons/documents/items/laurels.svg +++ b/assets/icons/documents/items/laurels.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/icons/documents/items/open-treasure-chest.svg b/assets/icons/documents/items/open-treasure-chest.svg index d66399f4..172a8003 100644 --- a/assets/icons/documents/items/open-treasure-chest.svg +++ b/assets/icons/documents/items/open-treasure-chest.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/icons/documents/items/round-potion.svg b/assets/icons/documents/items/round-potion.svg index 0c235732..7f981914 100644 --- a/assets/icons/documents/items/round-potion.svg +++ b/assets/icons/documents/items/round-potion.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/icons/documents/items/stars-stack.svg b/assets/icons/documents/items/stars-stack.svg index 2d446683..19c197f6 100644 --- a/assets/icons/documents/items/stars-stack.svg +++ b/assets/icons/documents/items/stars-stack.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/icons/documents/items/village.svg b/assets/icons/documents/items/village.svg index b7c776a1..c28d742b 100644 --- a/assets/icons/documents/items/village.svg +++ b/assets/icons/documents/items/village.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/icons/documents/items/wolf-head.svg b/assets/icons/documents/items/wolf-head.svg index 7e57bbad..2be500c1 100644 --- a/assets/icons/documents/items/wolf-head.svg +++ b/assets/icons/documents/items/wolf-head.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/daggerheart.d.ts b/daggerheart.d.ts index 1641faa8..7ff7fd59 100644 --- a/daggerheart.d.ts +++ b/daggerheart.d.ts @@ -2,7 +2,6 @@ import '@client/global.mjs'; import '@common/global.mjs'; import '@common/primitives/global.mjs'; import Canvas from '@client/canvas/board.mjs'; -import { ResourceUpdateMap } from './module/data/action/baseAction.mjs'; // Foundry's use of `Object.assign(globalThis) means many globally available objects are not read as such // This declare global hopefully fixes that @@ -40,43 +39,4 @@ declare global { const Collection: foundry.utils.Collection; const FormDataExtended: foundry.applications.ux.FormDataExtended; const TextEditor: foundry.applications.ux.TextEditor; - - /** - * Data used to build rolls such as duality rolls. The definition is incomplete and likely incorrect. - * Objects will often accept a Partial and spit out a non-partial. Those that are not guaranteed should be marked optional. - */ - interface RollConfig { - // unverified, check which ones are used and optional/not optional - event: Event; - title: string; - roll: { - modifier: number; - simple: boolean; - type: string; - difficulty: number; - }; - hasDamage: boolean; - hasEffect: boolean; - hasRoll: boolean; - chatMessage: { - template: string; - mute: boolean; - }; - targets: object; - costs: object; - - // verified - source?: { - /** uuid of the actor this roll is coming from */ - actor: string; - }; - /** Roll data associated with the actor or item */ - data: object; - resourceUpdates: ResourceUpdateMap; - hooks: string[]; - dialog: { - configure: boolean; - }; - damageOptions: object; - } } diff --git a/eslint.config.mjs b/eslint.config.mjs index 74141cc8..3c9b8fd9 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -90,26 +90,12 @@ export default defineConfig([ }, rules: { 'no-undef': 'error', - 'no-unused-vars': [ - 'error', - { - args: 'none', - destructuredArrayIgnorePattern: '^_', - varsIgnorePattern: '^_[A-Z]', - ignoreRestSiblings: true - } - ], + // 'no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }], ...stylisticRules } }, { files: ['**/*.ts'], - extends: [js.configs.recommended, tseslint.configs.recommended], - plugins: { - '@stylistic': stylistic - }, - rules: { - ...stylisticRules - } + extends: [js.configs.recommended, tseslint.configs.recommended] } ]); diff --git a/jsconfig.json b/jsconfig.json index 42c4b404..a0d51d0b 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -8,7 +8,7 @@ } }, "exclude": ["node_modules", "**/node_modules/*"], - "include": ["daggerheart.mjs", "foundry/client/client.mjs", "daggerheart.d.ts", "module/**/*.d.ts"], + "include": ["daggerheart.mjs", "foundry/client/client.mjs", "daggerheart.d.ts"], "typeAcquisition": { "include": ["jquery"] } diff --git a/lang/en.json b/lang/en.json index a4a5edf0..9f16828e 100755 --- a/lang/en.json +++ b/lang/en.json @@ -484,6 +484,7 @@ "startFormula": "Randomized Start Value Formula", "currentCountdownCurrent": "Current: {value}", "currentCountdownStart": "Start: {value}", + "category": "Category", "progressionType": "Progression", "decreasing": "Decreasing", "looping": "Looping", @@ -1161,7 +1162,7 @@ "autoAppliedByLabel": "Max Stress" } }, - "CountdownProgressType": { + "CountdownType": { "actionRoll": "Action Roll", "characterAttack": "Character Attack", "characterSpotlight": "Character Spotlight", @@ -1169,10 +1170,6 @@ "fear": "Fear", "spotlight": "Spotlight" }, - "CountdownType": { - "encounter": { "label": "Short Term", "shortLabel": "Short" }, - "narrative": { "label": "Long Term", "shortLabel": "Long" } - }, "DaggerheartDiceAnimationEvents": { "critical": { "name": "Critical" }, "higher": { "name": "Highest Roll" } @@ -1451,7 +1448,7 @@ }, "triggerType": "Trigger Type", "triggeringActorType": "Triggering Actor Type", - "triggerError": "{trigger} trigger on {item}({itemUuid}) failed for {actor}. It's probably configured wrong." + "triggerError": "{trigger} trigger failed for {actor}. It's probably configured wrong." }, "WeaponFeature": { "barrier": { diff --git a/module/applications/dialogs/d20RollDialog.mjs b/module/applications/dialogs/d20RollDialog.mjs index 048797d8..e9140b56 100644 --- a/module/applications/dialogs/d20RollDialog.mjs +++ b/module/applications/dialogs/d20RollDialog.mjs @@ -160,7 +160,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio } if (rest.hasOwnProperty('trait')) { this.config.roll.trait = rest.trait; - if (!this.config.source.item && this.config.roll.trait) + if (!this.config.source.item) this.config.title = game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', { ability: game.i18n.localize(abilities[this.config.roll.trait]?.label) }); @@ -225,6 +225,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio } static async submitRoll(event) { + event.preventDefault(); await this.close({ submitted: true }); } diff --git a/module/applications/dialogs/damageReductionDialog.mjs b/module/applications/dialogs/damageReductionDialog.mjs index 8cb026f0..e5108e34 100644 --- a/module/applications/dialogs/damageReductionDialog.mjs +++ b/module/applications/dialogs/damageReductionDialog.mjs @@ -176,6 +176,7 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap } static updateData(event, _, formData) { + const form = foundry.utils.expandObject(formData.object); this.render(true); } diff --git a/module/applications/dialogs/groupRollDialog.mjs b/module/applications/dialogs/groupRollDialog.mjs index ebc80d39..7196d848 100644 --- a/module/applications/dialogs/groupRollDialog.mjs +++ b/module/applications/dialogs/groupRollDialog.mjs @@ -1,5 +1,4 @@ import { ResourceUpdateMap } from '../../data/action/baseAction.mjs'; -import { shouldUseHopeFearAutomation } from '../../helpers/utils.mjs'; import { emitGMUpdate, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs'; import Party from '../sheets/actors/party.mjs'; @@ -481,23 +480,20 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat await cls.create(msgData); - /* Handle resource updates for the finished GroupRoll */ - if (shouldUseHopeFearAutomation({ gmAsPlayer: true })) { - const resourceMap = new ResourceUpdateMap(actor); - if (totalRoll.isCritical) { - resourceMap.addResources([ - { key: 'stress', value: -1 }, - { key: 'hope', value: 1 } - ]); - } else if (totalRoll.withHope) { - resourceMap.addResources([{ key: 'hope', value: 1 }]); - } else { - resourceMap.addResources([{ key: 'fear', value: 1 }]); - } - - resourceMap.updateResources(); + const resourceMap = new ResourceUpdateMap(actor); + if (totalRoll.isCritical) { + resourceMap.addResources([ + { key: 'stress', value: -1, total: 1 }, + { key: 'hope', value: 1, total: 1 } + ]); + } else if (totalRoll.withHope) { + resourceMap.addResources([{ key: 'hope', value: 1, total: 1 }]); + } else { + resourceMap.addResources([{ key: 'fear', value: 1, total: 1 }]); } + resourceMap.updateResources(); + /* Fin */ this.cancelRoll({ confirm: false }); } diff --git a/module/applications/dialogs/tagTeamDialog.mjs b/module/applications/dialogs/tagTeamDialog.mjs index 19869a00..b2ce0258 100644 --- a/module/applications/dialogs/tagTeamDialog.mjs +++ b/module/applications/dialogs/tagTeamDialog.mjs @@ -1,6 +1,5 @@ -import { ResourceUpdateMap } from '../../data/action/baseAction.mjs'; import { MemberData } from '../../data/tagTeamData.mjs'; -import { getCritDamageBonus, shouldUseHopeFearAutomation } from '../../helpers/utils.mjs'; +import { getCritDamageBonus } from '../../helpers/utils.mjs'; import { emitGMUpdate, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs'; import Party from '../sheets/actors/party.mjs'; @@ -10,7 +9,6 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio constructor(party) { super({ id: `TagTeamDialog-${party.id}` }); - this.usesTagTeamHopeCost = true; this.party = party; this.partyMembers = party.system.partyMembers .filter(x => Party.DICE_ROLL_ACTOR_TYPES.includes(x.type)) @@ -22,9 +20,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio owned: member.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER) })); - this.initiator = { cost: - this.party.system.schema.fields.tagTeam.fields.initiator.fields.cost.initial - }; + this.initiator = { cost: 3 }; this.openForAllPlayers = true; this.tabGroups.application = Object.keys(party.system.tagTeam.members).length @@ -98,13 +94,9 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio ?.addEventListener('input', this.updateInitiatorMemberField.bind(this)); htmlElement - .querySelector('.initiator-cost-input') + .querySelector('.initiator-cost-field') ?.addEventListener('input', this.updateInitiatorCostField.bind(this)); - htmlElement - .querySelector('.initiator-cost-enabled-checkbox') - ?.addEventListener('change', this.toggleInitiatorCostEnabled.bind(this)); - htmlElement .querySelector('.openforall-field') ?.addEventListener('change', this.updateOpenForAllField.bind(this)); @@ -164,7 +156,6 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio .map(x => ({ value: x.id, label: x.name })); partContext.initiatorDisabled = !selectedMembers.length; partContext.openForAllPlayers = this.openForAllPlayers; - partContext.usesTagTeamHopeCost = this.usesTagTeamHopeCost; break; case 'tagTeamRoll': @@ -406,13 +397,6 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio this.render(); } - toggleInitiatorCostEnabled(_event) { - this.usesTagTeamHopeCost = !this.usesTagTeamHopeCost; - this.initiator.cost = this.usesTagTeamHopeCost ? - this.party.system.schema.fields.tagTeam.fields.initiator.fields.cost.initial : 0; - this.render(); - } - updateOpenForAllField(event) { this.openForAllPlayers = event.target.checked; this.render(); @@ -768,37 +752,32 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio /* Handle resource updates from the finished TagTeamRoll */ const tagTeamData = this.party.system.tagTeam; - - const actorResourceMaps = Object.keys(tagTeamData.members).reduce((acc, key) => { - acc[key] = new ResourceUpdateMap(game.actors.get(key)); - return acc; - }, {}); - - if (shouldUseHopeFearAutomation({ gmAsPlayer: true })) { - const fearResourceMap = actorResourceMaps[tagTeamData.initiator.memberId]; - for (const memberId in tagTeamData.members) { - const resourceMap = actorResourceMaps[memberId]; - if (finalRoll.isCritical) { - resourceMap.addResources([ - { key: 'stress', value: -1, enabled: true }, - { key: 'hope', value: 1, enabled: true } - ]); - } else if (finalRoll.withHope) { - resourceMap.addResources([{ key: 'hope', value: 1, enabled: true }]); - } else if (finalRoll.withFear) { - fearResourceMap.addResources([{ key: 'fear', value: 1, enabled: true }]); - } + const fearUpdate = { key: 'fear', value: null, total: null, enabled: true }; + for (let memberId in tagTeamData.members) { + const resourceUpdates = []; + const rollGivesHope = finalRoll.isCritical || finalRoll.withHope; + if (memberId === tagTeamData.initiator.memberId) { + const value = tagTeamData.initiator.cost + ? rollGivesHope + ? 1 - tagTeamData.initiator.cost + : -tagTeamData.initiator.cost + : 1; + resourceUpdates.push({ key: 'hope', value: value, total: -value, enabled: true }); + } else if (rollGivesHope) { + resourceUpdates.push({ key: 'hope', value: 1, total: -1, enabled: true }); + } + if (finalRoll.isCritical) resourceUpdates.push({ key: 'stress', value: -1, total: 1, enabled: true }); + if (finalRoll.withFear) { + fearUpdate.value = fearUpdate.value === null ? 1 : fearUpdate.value + 1; + fearUpdate.total = fearUpdate.total === null ? -1 : fearUpdate.total - 1; } - } - /* Even with Hope/Fear automation off, the hope cost of performing the TagTeamRoll can still optionally be subtracted */ - if (tagTeamData.initiator.cost) { - const resourceMap = actorResourceMaps[tagTeamData.initiator.memberId]; - resourceMap.addResources([{ key: 'hope', value: -tagTeamData.initiator.cost, enabled: true }]); + game.actors.get(memberId).modifyResource(resourceUpdates); } - for (const resourceMap of Object.values(actorResourceMaps)) - resourceMap.updateResources(); + if (fearUpdate.value) { + mainActor.modifyResource([fearUpdate]); + } /* Fin */ this.cancelRoll({ confirm: false }); diff --git a/module/applications/levelup/levelup.mjs b/module/applications/levelup/levelup.mjs index f40609a4..cafc5c89 100644 --- a/module/applications/levelup/levelup.mjs +++ b/module/applications/levelup/levelup.mjs @@ -1,3 +1,4 @@ +import { abilities, subclassFeatureLabels } from '../../config/actorConfig.mjs'; import { getDeleteKeys, tagifyElement } from '../../helpers/utils.mjs'; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; diff --git a/module/applications/settings/appearanceSettings.mjs b/module/applications/settings/appearanceSettings.mjs index f2defffb..9de9e752 100644 --- a/module/applications/settings/appearanceSettings.mjs +++ b/module/applications/settings/appearanceSettings.mjs @@ -1,13 +1,12 @@ +import DhAppearance from '../../data/settings/Appearance.mjs'; import { getDiceSoNicePreset } from '../../config/generalConfig.mjs'; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; /** - * @import DhAppearance from '../../data/settings/Appearance.mjs'; * @import {ApplicationClickAction} from "@client/applications/_types.mjs" */ -/** Settings menu for appearance settings */ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(ApplicationV2) { /**@inheritdoc */ static DEFAULT_OPTIONS = { diff --git a/module/applications/sheets-configs/adversary-settings.mjs b/module/applications/sheets-configs/adversary-settings.mjs index ff3f3039..57405675 100644 --- a/module/applications/sheets-configs/adversary-settings.mjs +++ b/module/applications/sheets-configs/adversary-settings.mjs @@ -10,7 +10,11 @@ export default class DHAdversarySettings extends DHBaseActorSettings { actions: { addExperience: DHAdversarySettings.#addExperience, removeExperience: DHAdversarySettings.#removeExperience - } + }, + dragDrop: [ + { dragSelector: null, dropSelector: '.tab.features' }, + { dragSelector: '.feature-item', dropSelector: null } + ] }; /**@override */ @@ -34,8 +38,7 @@ export default class DHAdversarySettings extends DHBaseActorSettings { }, features: { id: 'features', - template: 'systems/daggerheart/templates/sheets-settings/adversary-settings/features.hbs', - scrollable: [''] + template: 'systems/daggerheart/templates/sheets-settings/adversary-settings/features.hbs' } }; @@ -51,16 +54,12 @@ export default class DHAdversarySettings extends DHBaseActorSettings { async _prepareContext(options) { const context = await super._prepareContext(options); - // Get feature groups. Uncategorized go to actions - const featureFormsTypes = ['passive', 'action', 'reaction']; - const features = this.document.system.features.sort((a, b) => a.sort - b.sort); - const featureGroups = featureFormsTypes.map(t => ({ - featureForm: t, - label: _loc(CONFIG.DH.ITEM.featureForm[t]), - features: features.filter(f => f.system.featureForm === t) - })); - featureGroups[1].features.push(...features.filter(f => !featureFormsTypes.includes(f.system.featureForm))); - context.featureGroups = featureGroups; + const featureForms = ['passive', 'action', 'reaction']; + context.features = context.document.system.features.sort((a, b) => + a.system.featureForm !== b.system.featureForm + ? featureForms.indexOf(a.system.featureForm) - featureForms.indexOf(b.system.featureForm) + : a.sort - b.sort + ); return context; } @@ -98,4 +97,32 @@ export default class DHAdversarySettings extends DHBaseActorSettings { await this.actor.update({ [`system.experiences.${target.dataset.experience}`]: _del }); } + + async _onDragStart(event) { + const featureItem = event.currentTarget.closest('.feature-item'); + + if (featureItem) { + const feature = this.actor.items.get(featureItem.id); + const featureData = { type: 'Item', uuid: feature.uuid, fromInternal: true }; + event.dataTransfer.setData('text/plain', JSON.stringify(featureData)); + event.dataTransfer.setDragImage(featureItem.querySelector('img'), 60, 0); + } + } + + async _onDrop(event) { + event.stopPropagation(); + const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event); + + const item = await fromUuid(data.uuid); + if (item?.type === 'feature') { + if (data.fromInternal && item.parent?.uuid === this.actor.uuid) { + return; + } + + const itemData = item.toObject(); + delete itemData._id; + + await this.actor.createEmbeddedDocuments('Item', [itemData]); + } + } } diff --git a/module/applications/sheets-configs/environment-settings.mjs b/module/applications/sheets-configs/environment-settings.mjs index 954d6381..6d74f9c6 100644 --- a/module/applications/sheets-configs/environment-settings.mjs +++ b/module/applications/sheets-configs/environment-settings.mjs @@ -15,7 +15,7 @@ export default class DHEnvironmentSettings extends DHBaseActorSettings { dragDrop: [ { dragSelector: null, dropSelector: '.category-container' }, { dragSelector: null, dropSelector: '.tab.features' }, - { dragSelector: '.feature-item, .inventory-item[data-type="adversary"]', dropSelector: null } + { dragSelector: '.feature-item', dropSelector: null } ] }; @@ -32,13 +32,11 @@ export default class DHEnvironmentSettings extends DHBaseActorSettings { }, features: { id: 'features', - template: 'systems/daggerheart/templates/sheets-settings/environment-settings/features.hbs', - scrollable: [''] + template: 'systems/daggerheart/templates/sheets-settings/environment-settings/features.hbs' }, adversaries: { id: 'adversaries', - template: 'systems/daggerheart/templates/sheets-settings/environment-settings/adversaries.hbs', - scrollable: [''] + template: 'systems/daggerheart/templates/sheets-settings/environment-settings/adversaries.hbs' } }; @@ -54,16 +52,12 @@ export default class DHEnvironmentSettings extends DHBaseActorSettings { async _prepareContext(options) { const context = await super._prepareContext(options); - // Get feature groups. Uncategorized go to actions - const featureFormsTypes = ['passive', 'action', 'reaction']; - const features = this.document.system.features.sort((a, b) => a.sort - b.sort); - const featureGroups = featureFormsTypes.map(t => ({ - featureForm: t, - label: _loc(CONFIG.DH.ITEM.featureForm[t]), - features: features.filter(f => f.system.featureForm === t) - })); - featureGroups[1].features.push(...features.filter(f => !featureFormsTypes.includes(f.system.featureForm))); - context.featureGroups = featureGroups; + const featureForms = ['passive', 'action', 'reaction']; + context.features = context.document.system.features.sort((a, b) => + a.system.featureForm !== b.system.featureForm + ? featureForms.indexOf(a.system.featureForm) - featureForms.indexOf(b.system.featureForm) + : a.sort - b.sort + ); return context; } @@ -116,30 +110,33 @@ export default class DHEnvironmentSettings extends DHBaseActorSettings { } async _onDragStart(event) { - const element = event.currentTarget.closest('.inventory-item[data-type=adversary]'); - if (element) { - const adversaryData = { type: 'Actor', uuid: element.dataset.itemUuid }; - event.dataTransfer.setData('text/plain', JSON.stringify(adversaryData)); - event.dataTransfer.setDragImage(element, 60, 0); - } else { - return super._onDragStart(event); + const featureItem = event.currentTarget.closest('.feature-item'); + + if (featureItem) { + const feature = this.actor.items.get(featureItem.id); + const featureData = { type: 'Item', uuid: feature.uuid, fromInternal: true }; + event.dataTransfer.setData('text/plain', JSON.stringify(featureData)); + event.dataTransfer.setDragImage(featureItem.querySelector('img'), 60, 0); } } async _onDrop(event) { event.stopPropagation(); const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event); - const doc = await fromUuid(data.uuid); - if (doc?.type === 'adversary' && event.target.closest('.category-container')) { + const item = await fromUuid(data.uuid); + if (data.fromInternal && item?.parent?.uuid === this.actor.uuid) return; + + if (item.type === 'adversary' && event.target.closest('.category-container')) { const target = event.target.closest('.category-container'); const path = `system.potentialAdversaries.${target.dataset.potentialAdversary}.adversaries`; const current = foundry.utils.getProperty(this.actor, path).map(x => x.uuid); - if (!current.includes(doc.uuid)) { - await this.actor.update({ [path]: [...current, doc.uuid] }); - } - return; + await this.actor.update({ + [path]: [...current, item.uuid] + }); + this.render(); + } else if (item.type === 'feature' && event.target.closest('.tab.features')) { + await this.actor.createEmbeddedDocuments('Item', [item]); + this.render(); } - - return super._onDrop(event); } } diff --git a/module/applications/sheets-configs/npc-settings.mjs b/module/applications/sheets-configs/npc-settings.mjs index 33cbc270..c187877c 100644 --- a/module/applications/sheets-configs/npc-settings.mjs +++ b/module/applications/sheets-configs/npc-settings.mjs @@ -27,8 +27,7 @@ export default class DHNPCSettings extends DHBaseActorSettings { }, features: { id: 'features', - template: 'systems/daggerheart/templates/sheets-settings/npc-settings/features.hbs', - scrollable: [''] + template: 'systems/daggerheart/templates/sheets-settings/npc-settings/features.hbs' } }; @@ -44,17 +43,43 @@ export default class DHNPCSettings extends DHBaseActorSettings { async _prepareContext(options) { const context = await super._prepareContext(options); - // Get feature groups. Uncategorized go to actions - const featureFormsTypes = ['passive', 'action', 'reaction']; - const features = this.document.system.features.sort((a, b) => a.sort - b.sort); - const featureGroups = featureFormsTypes.map(t => ({ - featureForm: t, - label: _loc(CONFIG.DH.ITEM.featureForm[t]), - features: features.filter(f => f.system.featureForm === t) - })); - featureGroups[1].features.push(...features.filter(f => !featureFormsTypes.includes(f.system.featureForm))); - context.featureGroups = featureGroups; + const featureForms = ['passive', 'action', 'reaction']; + context.features = context.document.system.features.sort((a, b) => + a.system.featureForm !== b.system.featureForm + ? featureForms.indexOf(a.system.featureForm) - featureForms.indexOf(b.system.featureForm) + : a.sort - b.sort + ); return context; } + + /* -------------------------------------------- */ + + async _onDragStart(event) { + const featureItem = event.currentTarget.closest('.feature-item'); + + if (featureItem) { + const feature = this.actor.items.get(featureItem.id); + const featureData = { type: 'Item', uuid: feature.uuid, fromInternal: true }; + event.dataTransfer.setData('text/plain', JSON.stringify(featureData)); + event.dataTransfer.setDragImage(featureItem.querySelector('img'), 60, 0); + } + } + + async _onDrop(event) { + event.stopPropagation(); + const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event); + + const item = await fromUuid(data.uuid); + if (item?.type === 'feature') { + if (data.fromInternal && item.parent?.uuid === this.actor.uuid) { + return; + } + + const itemData = item.toObject(); + delete itemData._id; + + await this.actor.createEmbeddedDocuments('Item', [itemData]); + } + } } diff --git a/module/applications/sheets/actors/_types.d.ts b/module/applications/sheets/actors/_types.d.ts deleted file mode 100644 index 89adc57b..00000000 --- a/module/applications/sheets/actors/_types.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -import DhCompanion from '../../../data/actor/companion.mjs'; -import DhpActor from '../../../documents/actor.mjs'; - -declare module './companion.mjs' { - export default interface DhCompanionSheet { - actor: DhpActor; - } -} diff --git a/module/applications/sheets/actors/adversary.mjs b/module/applications/sheets/actors/adversary.mjs index 85380392..c8d5a299 100644 --- a/module/applications/sheets/actors/adversary.mjs +++ b/module/applications/sheets/actors/adversary.mjs @@ -103,7 +103,7 @@ export default class AdversarySheet extends DHBaseActorSheet { context.resources.stress.emptyPips = context.resources.stress.max < maxResource ? maxResource - context.resources.stress.max : 0; - const featureForms = Object.keys(CONFIG.DH.ITEM.featureForm); + const featureForms = ['passive', 'action', 'reaction']; context.features = this.document.system.features.sort((a, b) => a.system.featureForm !== b.system.featureForm ? featureForms.indexOf(a.system.featureForm) - featureForms.indexOf(b.system.featureForm) @@ -131,15 +131,6 @@ export default class AdversarySheet extends DHBaseActorSheet { return context; } - /** @inheritdoc */ - _prepareTabs(group) { - const result = super._prepareTabs(group); - if (group === 'primary') { - result.notes.empty = !this.document.system.notes?.trim(); - } - return result; - } - /**@inheritdoc */ _attachPartListeners(partId, htmlElement, options) { super._attachPartListeners(partId, htmlElement, options); diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index 3a60e7ca..f0f8326f 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -809,7 +809,7 @@ export default class CharacterSheet extends DHBaseActorSheet { /* This could be avoided by baking config.costs into config.resourceUpdates. Didn't feel like messing with it at the time */ const costResources = - result.costs?.filter(x => x.enabled).map(cost => ({ ...cost, value: -cost.value })) || + result.costs?.filter(x => x.enabled).map(cost => ({ ...cost, value: -cost.value, total: -cost.total })) || {}; result.resourceUpdates.addResources(costResources); await result.resourceUpdates.updateResources(); diff --git a/module/applications/sheets/actors/environment.mjs b/module/applications/sheets/actors/environment.mjs index 60fb9071..f8ff74a6 100644 --- a/module/applications/sheets/actors/environment.mjs +++ b/module/applications/sheets/actors/environment.mjs @@ -72,22 +72,13 @@ export default class DhpEnvironment extends DHBaseActorSheet { return applicationOptions; } - /** @inheritdoc */ - _prepareTabs(group) { - const result = super._prepareTabs(group); - if (group === 'primary') { - result.potentialAdversaries.empty = foundry.utils.isEmpty(this.document.system.potentialAdversaries); - result.notes.empty = !this.document.system.notes?.trim(); - } - return result; - } - /**@inheritdoc */ async _preparePartContext(partId, context, options) { context = await super._preparePartContext(partId, context, options); switch (partId) { case 'header': await this._prepareHeaderContext(context, options); + break; case 'features': await this._prepareFeaturesContext(context, options); diff --git a/module/applications/sheets/api/actor-setting.mjs b/module/applications/sheets/api/actor-setting.mjs index fa434034..738f7002 100644 --- a/module/applications/sheets/api/actor-setting.mjs +++ b/module/applications/sheets/api/actor-setting.mjs @@ -1,15 +1,13 @@ import DHApplicationMixin from './application-mixin.mjs'; -const { ActorSheetV2 } = foundry.applications.sheets; +const { DocumentSheetV2 } = foundry.applications.api; -/** - * @typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction - */ +/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */ /** * Base settings sheet for Daggerheart actors. - * @extends {DHApplicationMixin} + * @extends {DHApplicationMixin} */ -export default class DHBaseActorSettings extends DHApplicationMixin(ActorSheetV2) { +export default class DHBaseActorSettings extends DHApplicationMixin(DocumentSheetV2) { /**@inheritdoc */ static DEFAULT_OPTIONS = { classes: ['dialog'], @@ -25,7 +23,7 @@ export default class DHBaseActorSettings extends DHApplicationMixin(ActorSheetV2 }, dragDrop: [ { dragSelector: null, dropSelector: '.tab.features' }, - { dragSelector: '.feature-item, .inventory-item[data-type="feature"]', dropSelector: null } + { dragSelector: '.feature-item', dropSelector: null } ] }; @@ -36,7 +34,7 @@ export default class DHBaseActorSettings extends DHApplicationMixin(ActorSheetV2 return options; } - /** @returns {foundry.documents.Actor} */ + /**@returns {foundry.documents.Actor} */ get actor() { return this.document; } @@ -75,32 +73,4 @@ export default class DHBaseActorSettings extends DHApplicationMixin(ActorSheetV2 return context; } - - async _onDragStart(event) { - const featureItemEl = event.currentTarget.closest('.feature-item, .inventory-item[data-type="feature"]'); - const feature = this.actor.items.get(featureItemEl?.dataset.itemId); - if (feature && event.target.closest('.tab.features')) { - const featureData = { ...feature.toDragData(), fromInternal: true }; - event.dataTransfer.setData('text/plain', JSON.stringify(featureData)); - event.dataTransfer.setDragImage(featureItemEl.querySelector('img'), 60, 0); - } - } - - async _onDrop(event) { - event.stopPropagation(); - const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event); - const item = await fromUuid(data.uuid); - if (item?.type === 'feature') { - if (data.fromInternal && item.parent?.uuid === this.actor.uuid) { - return super._onDrop(event); - } - - const itemData = item.toObject(); - delete itemData._id; - await this.actor.createEmbeddedDocuments('Item', [itemData]); - } - } - - /** Setting sheets do not auto extend */ - async _autoExpandDescriptions() {} } diff --git a/module/applications/sheets/api/application-mixin.mjs b/module/applications/sheets/api/application-mixin.mjs index 98f38f03..752dc80b 100644 --- a/module/applications/sheets/api/application-mixin.mjs +++ b/module/applications/sheets/api/application-mixin.mjs @@ -4,7 +4,6 @@ import { getDocFromElement, getDocFromElementSync, tagifyElement } from '../../. const typeSettingsMap = { character: 'extendCharacterDescriptions', adversary: 'extendAdversaryDescriptions', - npc: 'extendAdversaryDescriptions', environment: 'extendEnvironmentDescriptions', ancestry: 'extendItemDescriptions', community: 'extendItemDescriptions', @@ -263,7 +262,7 @@ export default function DHApplicationMixin(Base) { if (!!this.options.contextMenus.length) this._createContextMenus(); - this._autoExpandDescriptions(context); + this.#autoExtendDescriptions(context); } /** @inheritDoc */ @@ -382,35 +381,12 @@ export default function DHApplicationMixin(Base) { * @protected */ _onDrop(event) { - // Potentially handle subclasses that dont descend from actor/item sheet. + // Fallback to super, but note that config sheets don't have this option + // We still need this to avoid setting apps having issues event.stopPropagation(); return super._onDrop?.(event); } - /** @inheritdoc */ - _onSortItem(event, item) { - // If we are dragging a feature past its allowed feature form, put it in the front or in the back - const doc = this.actor.items.get(item.id); - const dropTargetEl = event.target.closest('[data-item-id]'); - const dropTarget = this.actor.items.get(dropTargetEl?.dataset.itemId); - if (doc?.type === 'feature' && dropTarget?.type === 'feature' && doc.system.featureForm !== dropTarget.system.featureForm) { - const siblings = this.actor.itemTypes.feature - .filter(f => f.system.featureForm === doc.system.featureForm) - .sort((a, b) => a.sort - b.sort); - if (siblings.length > 1) { - const featureForms = Object.keys(CONFIG.DH.ITEM.featureForm); - const thisFeatureIdx = featureForms.indexOf(doc.system.featureForm); - const targetFeatureIdx = featureForms.indexOf(dropTarget.system.featureForm); - const target = targetFeatureIdx < thisFeatureIdx ? siblings[0] : siblings.at(-1); - const sortUpdates = foundry.utils.performIntegerSort(doc, { target, siblings }); - const updateData = sortUpdates.map(u => ({ ...u.update, _id: u.target._id })); - return this.actor.updateEmbeddedDocuments('Item', updateData); - } - } - - return super._onSortItem?.(event, item); - } - /* -------------------------------------------- */ /* Context Menu */ /* -------------------------------------------- */ @@ -518,7 +494,7 @@ export default function DHApplicationMixin(Base) { const doc = await getDocFromElement(target), action = doc?.system?.attack ?? doc; const config = action.prepareConfig(event); - config.effects = await game.system.api.data.actions.actionsTypes.base.getActionRelevantEffects( + config.effects = await game.system.api.data.actions.actionsTypes.base.getEffects( this.document, doc ); @@ -631,9 +607,8 @@ export default function DHApplicationMixin(Base) { /** * Extend inventory description when enabled in settings. * @returns {Promise} - * @protected */ - async _autoExpandDescriptions(context) { + async #autoExtendDescriptions(context) { const inventoryItems = this.element.querySelectorAll('.inventory-item[data-item-uuid]'); for (const el of inventoryItems) { // Get the doc uuid from the element @@ -736,7 +711,7 @@ export default function DHApplicationMixin(Base) { * @type {ApplicationClickAction} */ static async #onCreateDoc(event, target) { - const { documentClass, type, inVault, disabled, featureForm } = target.dataset; + const { documentClass, type, inVault, disabled } = target.dataset; const parentIsItem = this.document.documentName === 'Item'; const featureOnCharacter = this.document.parent?.type === 'character' && type === 'feature'; const parent = featureOnCharacter @@ -754,7 +729,6 @@ export default function DHApplicationMixin(Base) { identifier: this.document.system.isMulticlass ? 'multiclass' : null }; } - if (featureForm) systemData.featureForm = featureForm; const cls = type === 'action' ? game.system.api.models.actions.actionsTypes.base : getDocumentClass(documentClass); diff --git a/module/applications/sheets/api/base-actor.mjs b/module/applications/sheets/api/base-actor.mjs index 007b641b..027dd397 100644 --- a/module/applications/sheets/api/base-actor.mjs +++ b/module/applications/sheets/api/base-actor.mjs @@ -1,12 +1,10 @@ import { getDocFromElement, itemIsIdentical } from '../../../helpers/utils.mjs'; +import DHBaseActorSettings from './actor-setting.mjs'; import DHApplicationMixin from './application-mixin.mjs'; const { ActorSheetV2 } = foundry.applications.sheets; -/** - * @import DHBaseActorSettings from './actor-setting.mjs'; - * @typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction - */ +/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */ /** * A base actor sheet extending {@link ActorSheetV2} via {@link DHApplicationMixin} @@ -212,7 +210,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) { const doc = await getDocFromElement(target), action = doc?.system?.attack ?? doc; const config = action.prepareConfig(event); - config.effects = await game.system.api.data.actions.actionsTypes.base.getActionRelevantEffects( + config.effects = await game.system.api.data.actions.actionsTypes.base.getEffects( this.document, doc ); diff --git a/module/applications/sidebar/tabs/actorDirectory.mjs b/module/applications/sidebar/tabs/actorDirectory.mjs index 4d00afa2..a6f48b54 100644 --- a/module/applications/sidebar/tabs/actorDirectory.mjs +++ b/module/applications/sidebar/tabs/actorDirectory.mjs @@ -1,6 +1,6 @@ export default class DhActorDirectory extends foundry.applications.sidebar.tabs.ActorDirectory { static DEFAULT_OPTIONS = { - renderUpdateKeys: ['system.levelData.level.current', 'system.partner', 'system.tier', 'system.type'] + renderUpdateKeys: ['system.levelData.level.current', 'system.partner', 'system.tier'] }; static _entryPartial = 'systems/daggerheart/templates/ui/sidebar/actor-document-partial.hbs'; diff --git a/module/applications/ui/countdownEdit.mjs b/module/applications/ui/countdownEdit.mjs index 1dbc56be..5974e1f2 100644 --- a/module/applications/ui/countdownEdit.mjs +++ b/module/applications/ui/countdownEdit.mjs @@ -35,7 +35,7 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio static PARTS = { countdowns: { - template: 'systems/daggerheart/templates/ui/countdowns/countdown-edit.hbs', + template: 'systems/daggerheart/templates/ui/countdown-edit.hbs', scrollable: ['.expanded-view', '.edit-content'] } }; @@ -45,7 +45,7 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio context.isGM = game.user.isGM; context.ownershipDefaultOptions = CONFIG.DH.GENERAL.basicOwnershiplevels; context.defaultOwnership = this.data.defaultOwnership; - context.countdownTypes = CONFIG.DH.GENERAL.countdownTypes; + context.countdownBaseTypes = CONFIG.DH.GENERAL.countdownBaseTypes; context.countdownProgressionTypes = CONFIG.DH.GENERAL.countdownProgressionTypes; context.countdownLoopingTypes = CONFIG.DH.GENERAL.countdownLoopingTypes; context.hideNewCountdowns = this.hideNewCountdowns; @@ -62,7 +62,7 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio const randomizeValid = !new Roll(countdown.progress.startFormula ?? '').isDeterministic; acc[key] = { ...countdown, - typeName: game.i18n.localize(CONFIG.DH.GENERAL.countdownTypes[countdown.type].label), + typeName: game.i18n.localize(CONFIG.DH.GENERAL.countdownBaseTypes[countdown.type].label), progress: { ...countdown.progress, typeName: game.i18n.localize( diff --git a/module/applications/ui/countdowns.mjs b/module/applications/ui/countdowns.mjs index 5cf79100..2a2a113e 100644 --- a/module/applications/ui/countdowns.mjs +++ b/module/applications/ui/countdowns.mjs @@ -11,14 +11,9 @@ 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(); } @@ -37,7 +32,6 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application }, actions: { toggleViewMode: DhCountdowns.#onToggleViewMode, - toggleCountdownTypes: DhCountdowns.#onToggleCountdownTypes, editCountdowns: DhCountdowns.#onEditCountdowns, loopCountdown: DhCountdowns.#onLoopCountdown, decreaseCountdown: (_, target) => this.editCountdown(false, target), @@ -54,20 +48,11 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application static PARTS = { resources: { root: true, - template: 'systems/daggerheart/templates/ui/countdowns/countdowns-view.hbs' + template: 'systems/daggerheart/templates/ui/countdowns.hbs' } }; - /** - * Returns all visible countdown types - * @returns {string[]} - */ - get visibleCountdownTypes() { - const { encounter, narrative } = CONFIG.DH.GENERAL.countdownTypes; - return game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.countdownTypeModes) - ?? [encounter.id, narrative.id]; - } - + /**@inheritdoc */ async _renderFrame(options) { const frame = await super._renderFrame(options); @@ -80,57 +65,6 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application return frame; } - - async _onRender(context, options) { - await super._onRender(context, options); - - /* Handle rendering/hiding/positioning of the countdown UI */ - this.element.hidden = !game.user.isGM && this.#getCountdowns().length === 0; - if (options?.force) { - document.getElementById('ui-right-column-1')?.appendChild(this.element); - } - - this.previousCountdownData = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns) - .countdowns; - - /* Handle animations to draw attention to countdown values changing */ - const typesToAnimate = new Set(); - for (const countdownKey of this.changedCountdownsForAnimation) { - const shimmerAnimation = [ - { backgroundPositionX: '98%' }, - { backgroundPositionX: '0%' } - ]; - const shimmerTiming = { - duration: 1000, - iterations: 1 - }; - - const element = this.element.querySelector(`.countdown-container[data-countdown="${countdownKey}"]`); - element?.animate(shimmerAnimation, shimmerTiming); - - const countdown = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns) - .countdowns[countdownKey]; - if (!this.visibleCountdownTypes.includes(countdown?.type)) - typesToAnimate.add(countdown.type); - } - - for (const type of typesToAnimate) { - const pulseAnimation = [ - { boxShadow: '0 0 1px 1px var(--golden)' }, - { boxShadow: '0 0 2px 2px var(--golden)' } - ]; - const pulseTiming = { - duration: 1000, - iterations: 3 - }; - - 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 */ #getCountdowns() { const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns); @@ -142,10 +76,16 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application 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); + /** @override */ + async _prepareContext(options) { + const context = await super._prepareContext(options); + context.isGM = game.user.isGM; - return this.#getCountdowns().reduce((acc, { key, countdown, ownership }) => { + context.iconOnly = + game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.countdownMode) === + CONFIG.DH.GENERAL.countdownAppMode.iconOnly; + const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns); + context.countdowns = this.#getCountdowns().reduce((acc, { key, countdown, ownership }) => { const playersWithAccess = game.users.reduce((acc, user) => { const ownership = DhCountdowns.#getPlayerOwnership(user, setting, countdown); if (!user.isGM && ownership && ownership !== CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE) { @@ -168,7 +108,7 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application !countdownEditable || (isLooping && (countdown.progress.current > 0 || countdown.progress.start === '0')); - acc[countdown.type][key] = { + acc[key] = { ...countdown, editable: countdownEditable, noPlayerAccess: nonGmPlayers.length && playersWithAccess.length === 0, @@ -177,38 +117,7 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application loopTooltip: isLooping && game.i18n.localize(loopTooltip) }; return acc; - }, Object.keys(CONFIG.DH.GENERAL.countdownTypes).reduce((acc, key) => { - acc[key] = {}; - return acc; - }, {})); - } - - /** @override */ - async _prepareContext(options) { - const context = await super._prepareContext(options); - context.isGM = game.user.isGM; - - context.iconOnly = - game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.countdownMode) - === CONFIG.DH.GENERAL.countdownAppMode.iconOnly; - - context.userCountdownTypes = this.visibleCountdownTypes; - - context.typeToggles = - Object.values(CONFIG.DH.GENERAL.countdownTypes).map(type => ({ - type: type.id, - label: game.i18n.localize(type.shortLabel), - active: context.userCountdownTypes.includes(type.id) - })); - - context.countdowns = this._getCountdownData(); - context.countdownTypesWithVisibleEntries = this.#getCountdowns().reduce((acc, data) => { - if (context.userCountdownTypes.includes(data.countdown.type) && !acc.includes(data.countdown.type)) - acc.push(data.countdown.type); - - return acc; - }, []); - + }, {}); return context; } @@ -238,7 +147,6 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application return true; } - /** @this {DhCountdowns} */ static async #onToggleViewMode() { const currentMode = game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.countdownMode); const appMode = CONFIG.DH.GENERAL.countdownAppMode; @@ -250,24 +158,10 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application this.render(); } - /** @this {DhCountdowns} */ - static async #onToggleCountdownTypes(event, target) { - const currentTypes = this.visibleCountdownTypes; - const { type } = target.dataset; - const newTypes = event.shiftKey ? - [type] : - currentTypes.includes(type) ? currentTypes.filter(x => x !== type) : [...currentTypes, type]; - await game.user.setFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.countdownTypeModes, newTypes); - - this.render(); - } - - /** @this {DhCountdowns} */ static async #onEditCountdowns() { new game.system.api.applications.ui.CountdownEdit().render(true); } - /** @this {DhCountdowns} */ static async #onLoopCountdown(_, target) { if (!DhCountdowns.canPerformEdit()) return; @@ -342,31 +236,29 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application * Sends updates of the countdowns to the GM player. Since this is asynchronous, be sure to * update all the countdowns at the same time. * - * @param {...(string | { type: string; undo?: boolean })} progressTypes Countdowns to be updated + * @param {...any} progressTypes Countdowns to be updated */ static async updateCountdowns(...progressTypes) { - progressTypes = progressTypes.map(p => typeof p === 'string' ? { type: p } : p); const { countdownAutomation } = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation); if (!countdownAutomation) return; const countdownSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns); const updatedCountdowns = Object.keys(countdownSetting.countdowns).reduce((acc, key) => { const countdown = countdownSetting.countdowns[key]; - const progressData = progressTypes.find(x => x.type === countdown.progress.type); - if (progressData && countdown.progress.current > 0) { - acc[key] = { value: progressData.undo ? 1 : -1 }; + if (progressTypes.indexOf(countdown.progress.type) !== -1 && countdown.progress.current > 0) { + acc.push(key); } return acc; - }, {}); + }, []); const countdownData = countdownSetting.toObject(); const settings = { ...countdownData, countdowns: Object.keys(countdownData.countdowns).reduce((acc, key) => { const countdown = foundry.utils.deepClone(countdownData.countdowns[key]); - if (updatedCountdowns[key]) { - countdown.progress.current += updatedCountdowns[key].value; + if (updatedCountdowns.includes(key)) { + countdown.progress.current -= 1; } acc[key] = countdown; @@ -377,4 +269,12 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application refreshType: RefreshType.Countdown }); } + + async _onRender(context, options) { + await super._onRender(context, options); + this.element.hidden = !game.user.isGM && this.#getCountdowns().length === 0; + if (options?.force) { + document.getElementById('ui-right-column-1')?.appendChild(this.element); + } + } } diff --git a/module/config/flagsConfig.mjs b/module/config/flagsConfig.mjs index cb05bd72..817ac89d 100644 --- a/module/config/flagsConfig.mjs +++ b/module/config/flagsConfig.mjs @@ -24,8 +24,7 @@ export const itemAttachmentSource = 'attachmentSource'; export const userFlags = { welcomeMessage: 'welcome-message', - countdownMode: 'countdown-mode', - countdownTypeModes: 'countdown-type-modes' + countdownMode: 'countdown-mode' }; export const combatToggle = 'combat-toggle-origin'; diff --git a/module/config/generalConfig.mjs b/module/config/generalConfig.mjs index 188efafb..6fc85ec5 100644 --- a/module/config/generalConfig.mjs +++ b/module/config/generalConfig.mjs @@ -867,27 +867,27 @@ export const abilityCosts = { export const countdownProgressionTypes = { actionRoll: { id: 'actionRoll', - label: 'DAGGERHEART.CONFIG.CountdownProgressType.actionRoll' + label: 'DAGGERHEART.CONFIG.CountdownType.actionRoll' }, characterAttack: { id: 'characterAttack', - label: 'DAGGERHEART.CONFIG.CountdownProgressType.characterAttack' + label: 'DAGGERHEART.CONFIG.CountdownType.characterAttack' }, characterSpotlight: { id: 'characterSpotlight', - label: 'DAGGERHEART.CONFIG.CountdownProgressType.characterSpotlight' + label: 'DAGGERHEART.CONFIG.CountdownType.characterSpotlight' }, custom: { id: 'custom', - label: 'DAGGERHEART.CONFIG.CountdownProgressType.custom' + label: 'DAGGERHEART.CONFIG.CountdownType.custom' }, fear: { id: 'fear', - label: 'DAGGERHEART.CONFIG.CountdownProgressType.fear' + label: 'DAGGERHEART.CONFIG.CountdownType.fear' }, spotlight: { id: 'spotlight', - label: 'DAGGERHEART.CONFIG.CountdownProgressType.spotlight' + label: 'DAGGERHEART.CONFIG.CountdownType.spotlight' } }; export const rollTypes = { @@ -935,6 +935,17 @@ export const simpleOwnershiplevels = { ...basicOwnershiplevels }; +export const countdownBaseTypes = { + narrative: { + id: 'narrative', + label: 'DAGGERHEART.APPLICATIONS.Countdown.types.narrative' + }, + encounter: { + id: 'encounter', + label: 'DAGGERHEART.APPLICATIONS.Countdown.types.encounter' + } +}; + export const countdownLoopingTypes = { noLooping: { id: 'noLooping', @@ -959,19 +970,6 @@ export const countdownAppMode = { iconOnly: 'icon-only' }; -export const countdownTypes = { - encounter: { - id: 'encounter', - label: 'DAGGERHEART.CONFIG.CountdownType.encounter.label', - shortLabel: 'DAGGERHEART.CONFIG.CountdownType.encounter.shortLabel' - }, - narrative: { - id: 'narrative', - label: 'DAGGERHEART.CONFIG.CountdownType.narrative.label', - shortLabel: 'DAGGERHEART.CONFIG.CountdownType.narrative.shortLabel' - } -}; - export const sceneRangeMeasurementSetting = { disable: { id: 'disable', diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index f568436e..c71f5ef9 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -228,8 +228,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel let config = this.prepareConfig(event, configOptions); if (!config) return; - config.effects = - await game.system.api.data.actions.actionsTypes.base.getActionRelevantEffects(this.actor, this.item); + config.effects = await game.system.api.data.actions.actionsTypes.base.getEffects(this.actor, this.item); if (Hooks.call(`${CONFIG.DH.id}.preUseAction`, this, config) === false) return; @@ -334,43 +333,27 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel } /** - * Get the all potentially applicable effects on the actor for the action's RollDialog + * Get the all potentially applicable effects on the actor * @param {DHActor} actor The actor performing the action * @param {DHItem|DhActor} effectParent The parent of the effect * @returns {DhActiveEffect[]} */ - static async getActionRelevantEffects(actor, effectParent) { + static async getEffects(actor, effectParent) { if (!actor) return []; - // Changes on weapon effects are not typically only applicable to show in the roll dialog for the weapon itself - // The exemptions to this rule are listed below - const weaponTransferredEffectKeys = [ - 'system.bonuses.roll.spellcast.bonus' - ]; - - return Array.from(await actor.allApplicableEffects({ noTransferArmor: true, noSelfArmor: true })).reduce( - (acc, effect) => { - const effectData = effect.toObject(); - /* Effects on weapons only ever apply for the weapon itself, with a few defined exceptions */ + return Array.from(await actor.allApplicableEffects({ noTransferArmor: true, noSelfArmor: true })).filter( + effect => { + /* Effects on weapons only ever apply for the weapon itself */ if (effect.parent.type === 'weapon') { /* Unless they're secondary - then they apply only to other primary weapons */ if (effect.parent.system.secondary) { - if (effectParent?.type !== 'weapon' || effectParent?.system.secondary) { - effectData.system.changes = - effectData.system.changes.filter(x => weaponTransferredEffectKeys.includes(x.key)); - } - } else if (effectParent?.id !== effect.parent.id) { - effectData.system.changes = - effectData.system.changes.filter(x => weaponTransferredEffectKeys.includes(x.key)); - } + if (effectParent?.type !== 'weapon' || effectParent?.system.secondary) return false; + } else if (effectParent?.id !== effect.parent.id) return false; } - if (!effect.isSuppressed) { - acc.push(effectData); - } - - return acc; - }, []); + return !effect.isSuppressed; + } + ); } /** @@ -476,7 +459,8 @@ export class ResourceUpdateMap extends Map { } else if (!existing?.clear) { this.set(resource.key, { ...existing, - value: existing.value + (resource.value ?? 0) + value: existing.value + (resource.value ?? 0), + total: existing.total + (resource.total ?? 0) }); } } diff --git a/module/data/actor/_types.d.ts b/module/data/actor/_types.d.ts deleted file mode 100644 index fec6e638..00000000 --- a/module/data/actor/_types.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -import DhpActor from '../../documents/actor.mjs' -import DhCharacter from './character.mjs'; - -declare module './base.mjs' { - export default interface BaseDataActor { - parent: DhpActor; - } -} - -declare module './companion.mjs' { - export default interface DhCompanion { - partner: DhpActor; - } -} diff --git a/module/data/actor/base.mjs b/module/data/actor/base.mjs index 995708eb..9efbe7d7 100644 --- a/module/data/actor/base.mjs +++ b/module/data/actor/base.mjs @@ -1,13 +1,9 @@ +import DHBaseActorSettings from '../../applications/sheets/api/actor-setting.mjs'; +import DHItem from '../../documents/item.mjs'; import { createShallowProxy, getScrollTextData } from '../../helpers/utils.mjs'; const fields = foundry.data.fields; -/** - * @import DHItem from '../../documents/item.mjs'; - * @import DHBaseActorSettings from '../../applications/sheets/api/actor-setting.mjs'; - */ - -/** Function to generate resistance fields for damage types */ const resistanceField = (resistanceLabel, immunityLabel, reductionLabel) => new fields.SchemaField({ resistance: new fields.BooleanField({ @@ -100,8 +96,6 @@ export const commonActorRules = (extendedData = { damageReduction: {}, attack: { * @property {Boolean} isNPC - This data model represents a NPC? * @property {typeof DHBaseActorSettings} settingSheet - The sheet class used to render the settings UI for this actor type. */ - -/** Base actor type data model for all actors in Daggerheart */ export default class BaseDataActor extends foundry.abstract.TypeDataModel { /** @returns {ActorDataModelMetadata}*/ static get metadata() { diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index 3b12da6f..0cae662f 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -315,12 +315,7 @@ export default class DhCharacter extends DhCreature { label: 'DAGGERHEART.ACTORS.Character.defaultDisadvantageDice' }) }) - }), - /** Accumulated armor score from all sources */ - armorScore: new fields.SchemaField({ - value: new fields.NumberField(), - max: new fields.NumberField() - }, { persisted: false }) + }) }; } @@ -416,8 +411,8 @@ export default class DhCharacter extends DhCreature { get domainCards() { const domainCards = this.parent.items.filter(x => x.type === 'domainCard'); - const loadout = domainCards.filter(x => !x.system.inVault).sort((a, b) => a.sort - b.sort); - const vault = domainCards.filter(x => x.system.inVault).sort((a, b) => a.sort - b.sort); + const loadout = domainCards.filter(x => !x.system.inVault); + const vault = domainCards.filter(x => x.system.inVault); return { loadout: loadout, diff --git a/module/data/countdowns.mjs b/module/data/countdowns.mjs index 8e55ed31..54971d34 100644 --- a/module/data/countdowns.mjs +++ b/module/data/countdowns.mjs @@ -13,21 +13,6 @@ export default class DhCountdowns extends foundry.abstract.DataModel { }) }; } - - handleChange() { - const previousCountdowns = foundry.ui.countdowns.previousCountdownData; - const changedCountdowns = Object.entries(this.countdowns).reduce((acc, [key, countdown]) => { - const previousCountdown = previousCountdowns[key]; - if (!previousCountdown || (previousCountdown.progress.current !== countdown.progress.current)) { - acc.push(key); - } - - return acc; - }, []); - - for (const countdownKey of changedCountdowns) - foundry.ui.countdowns.changedCountdownsForAnimation.add(countdownKey); - } } export class DhCountdown extends foundry.abstract.DataModel { @@ -36,8 +21,7 @@ export class DhCountdown extends foundry.abstract.DataModel { return { type: new fields.StringField({ required: true, - choices: CONFIG.DH.GENERAL.countdownTypes, - initial: CONFIG.DH.GENERAL.countdownTypes.encounter.id, + choices: CONFIG.DH.GENERAL.countdownBaseTypes, label: 'DAGGERHEART.GENERAL.type' }), name: new fields.StringField({ @@ -101,7 +85,7 @@ export class DhCountdown extends foundry.abstract.DataModel { : undefined; return { - type: type ?? CONFIG.DH.GENERAL.countdownTypes.encounter.id, + type: type ?? CONFIG.DH.GENERAL.countdownBaseTypes.narrative.id, name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Countdown.newCountdown'), img: 'icons/magic/time/hourglass-yellow-green.webp', ownership: ownership, diff --git a/module/data/fields/action/countdownField.mjs b/module/data/fields/action/countdownField.mjs index 53e132c9..96d9dd91 100644 --- a/module/data/fields/action/countdownField.mjs +++ b/module/data/fields/action/countdownField.mjs @@ -8,8 +8,8 @@ export default class CountdownField extends fields.ArrayField { ...game.system.api.data.countdowns.DhCountdown.defineSchema(), type: new fields.StringField({ required: true, - choices: CONFIG.DH.GENERAL.countdownTypes, - initial: CONFIG.DH.GENERAL.countdownTypes.encounter.id, + choices: CONFIG.DH.GENERAL.countdownBaseTypes, + initial: CONFIG.DH.GENERAL.countdownBaseTypes.encounter.id, label: 'DAGGERHEART.GENERAL.type' }), name: new fields.StringField({ diff --git a/module/data/fields/formulaField.mjs b/module/data/fields/formulaField.mjs index c465bec3..85922f1f 100644 --- a/module/data/fields/formulaField.mjs +++ b/module/data/fields/formulaField.mjs @@ -39,7 +39,7 @@ export default class FormulaField extends foundry.data.fields.StringField { let roll = null; try { roll = new Roll(value.replace(/@([a-z.0-9_-]+)/gi, '1')); - } catch { + } catch (_) { roll = new Roll(value.replace(/@([a-z.0-9_-]+)/gi, 'd6')); } roll.evaluateSync({ strict: false }); diff --git a/module/data/item/_types.d.ts b/module/data/item/_types.d.ts deleted file mode 100644 index 6004e13b..00000000 --- a/module/data/item/_types.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -import DHItem from '../../documents/item.mjs'; - -declare module './base.mjs' { - export default interface BaseDataItem { - parent: DHItem; - actor: DhpActor; - } -} \ No newline at end of file diff --git a/module/data/registeredTriggers.mjs b/module/data/registeredTriggers.mjs index 64e7f93e..ab86351c 100644 --- a/module/data/registeredTriggers.mjs +++ b/module/data/registeredTriggers.mjs @@ -149,15 +149,12 @@ export default class RegisteredTriggers extends Map { const result = await command(...args); if (result?.updates?.length) updates.push(...result.updates); - } catch { - const item = await foundry.utils.fromUuid(itemUuid); + } catch (_) { const triggerName = game.i18n.localize(triggerData.label); - console.error( + ui.notifications.error( game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerError', { trigger: triggerName, - actor: currentActor?.name, - item: item?.name ?? '', - itemUuid: item?.uuid ?? '' + actor: currentActor?.name }) ); } diff --git a/module/dice/dhRoll.mjs b/module/dice/dhRoll.mjs index 1c6ec829..c28db98f 100644 --- a/module/dice/dhRoll.mjs +++ b/module/dice/dhRoll.mjs @@ -23,10 +23,6 @@ export default class DHRoll extends Roll { static DefaultDialog = D20RollDialog; - /** - * @param {Partial} config - * @returns {Promise} - */ static async build(config = {}, message = {}) { const roll = await this.buildConfigure(config, message); if (!roll) return; @@ -38,10 +34,6 @@ export default class DHRoll extends Roll { return config; } - /** - * @param {Partial} config - * @returns {Promise} - */ static async buildConfigure(config = {}, message = {}) { config.hooks = [...this.getHooks(), '']; config.dialog ??= {}; diff --git a/module/dice/dualityRoll.mjs b/module/dice/dualityRoll.mjs index e2d967a3..1cfed094 100644 --- a/module/dice/dualityRoll.mjs +++ b/module/dice/dualityRoll.mjs @@ -1,6 +1,6 @@ import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs'; import D20Roll from './d20Roll.mjs'; -import { parseRallyDice, setDiceSoNiceForDualityRoll, shouldUseHopeFearAutomation } from '../helpers/utils.mjs'; +import { parseRallyDice, setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs'; import { getDiceSoNicePresets } from '../config/generalConfig.mjs'; import { updateResourcesForDualityReroll } from './helpers.mjs'; @@ -312,9 +312,11 @@ export default class DualityRoll extends D20Roll { } static async addDualityResourceUpdates(config) { + const automationSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation); + const hopeFearAutomation = automationSettings.hopeFear; if ( !config.source?.actor || - !shouldUseHopeFearAutomation() || + (game.user.isGM ? !hopeFearAutomation.gm : !hopeFearAutomation.players) || config.actionType === 'reaction' || config.skips?.resources ) @@ -332,15 +334,15 @@ export default class DualityRoll extends D20Roll { const fear = (config.roll.result.duality === -1 ? 1 : 0) - (config.rerolledRoll.result.duality === -1 ? 1 : 0); - if (hope !== 0) updates.push({ key: 'hope', value: hope, enabled: true }); - if (stress !== 0) updates.push({ key: 'stress', value: -1 * stress, enabled: true }); - if (fear !== 0) updates.push({ key: 'fear', value: fear, enabled: true }); + if (hope !== 0) updates.push({ key: 'hope', value: hope, total: -1 * hope, enabled: true }); + if (stress !== 0) updates.push({ key: 'stress', value: -1 * stress, total: stress, enabled: true }); + if (fear !== 0) updates.push({ key: 'fear', value: fear, total: -1 * fear, enabled: true }); } } else { if (config.roll.isCritical || config.roll.result.duality === 1) - updates.push({ key: 'hope', value: 1, enabled: true }); - if (config.roll.isCritical) updates.push({ key: 'stress', value: -1, enabled: true }); - if (config.roll.result.duality === -1) updates.push({ key: 'fear', value: 1, enabled: true }); + updates.push({ key: 'hope', value: 1, total: -1, enabled: true }); + if (config.roll.isCritical) updates.push({ key: 'stress', value: -1, total: 1, enabled: true }); + if (config.roll.result.duality === -1) updates.push({ key: 'fear', value: 1, total: -1, enabled: true }); } if (updates.length) { diff --git a/module/dice/helpers.mjs b/module/dice/helpers.mjs index d03970d0..35adb8b7 100644 --- a/module/dice/helpers.mjs +++ b/module/dice/helpers.mjs @@ -1,28 +1,19 @@ import { ResourceUpdateMap } from '../data/action/baseAction.mjs'; export function updateResourcesForDualityReroll(oldDuality, newDuality, actor) { + const { hopeFear } = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation); + if (game.user.isGM ? !hopeFear.gm : !hopeFear.players) return; + + const updates = []; const hope = (newDuality >= 0 ? 1 : 0) - (oldDuality >= 0 ? 1 : 0); const stress = (newDuality === 0 ? 1 : 0) - (oldDuality === 0 ? 1 : 0); const fear = (newDuality === -1 ? 1 : 0) - (oldDuality === -1 ? 1 : 0); - const { hopeFear, countdownAutomation } = - game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation); + if (hope !== 0) updates.push({ key: 'hope', value: hope, total: -1 * hope, enabled: true }); + if (stress !== 0) updates.push({ key: 'stress', value: -1 * stress, total: stress, enabled: true }); + if (fear !== 0) updates.push({ key: 'fear', value: fear, total: -1 * fear, enabled: true }); - if (game.user.isGM ? hopeFear.gm : hopeFear.players) { - const updates = []; - if (hope !== 0) updates.push({ key: 'hope', value: hope, enabled: true }); - if (stress !== 0) updates.push({ key: 'stress', value: -1 * stress, enabled: true }); - if (fear !== 0) updates.push({ key: 'fear', value: fear, enabled: true }) - - const resourceUpdates = new ResourceUpdateMap(actor); - resourceUpdates.addResources(updates); - resourceUpdates.updateResources(); - } - - if (countdownAutomation && fear !== 0) { - game.system.api.applications.ui.DhCountdowns.updateCountdowns({ - type: CONFIG.DH.GENERAL.countdownProgressionTypes.fear.id, - undo: fear === 1 ? false : true - }); - } + const resourceUpdates = new ResourceUpdateMap(actor); + resourceUpdates.addResources(updates); + resourceUpdates.updateResources(); } diff --git a/module/documents/_types.d.ts b/module/documents/_types.d.ts deleted file mode 100644 index a94d6395..00000000 --- a/module/documents/_types.d.ts +++ /dev/null @@ -1,22 +0,0 @@ -import BaseDataActor from '../data/actor/base.mjs' -import DHItem from './item.mjs'; -import BaseDataItem from '../data/item/base.mjs'; -import DhActiveEffect from './activeEffect.mjs'; -import EmbeddedCollection from '@common/abstract/embedded-collection.mjs'; - -declare module './actor.mjs' { - export default interface DhpActor { - system: T; - items: EmbeddedCollection; - effects: EmbeddedCollection; - } -} - -declare module './item.mjs' { - export default interface DHItem { - parent: DhpActor; - actor: DhpActor; - system: T; - effects: EmbeddedCollection; - } -} \ No newline at end of file diff --git a/module/documents/activeEffect.mjs b/module/documents/activeEffect.mjs index 0e7f5d1e..3518210b 100644 --- a/module/documents/activeEffect.mjs +++ b/module/documents/activeEffect.mjs @@ -212,7 +212,7 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect { try { const evl = new Function('sandbox', `with (sandbox) { return ${expression}}`); result = evl(Roll.MATH_PROXY); - } catch { + } catch (err) { return expression; } diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index 8ef64f65..fb10435f 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -531,7 +531,21 @@ export default class DhpActor extends Actor { } /** - * @param {Partial} config + * @param {object} config + * @param {Event} config.event + * @param {string} config.title + * @param {object} config.roll + * @param {number} config.roll.modifier + * @param {boolean} [config.roll.simple=false] + * @param {string} [config.roll.type] + * @param {number} [config.roll.difficulty] + * @param {boolean} [config.hasDamage] + * @param {boolean} [config.hasEffect] + * @param {object} [config.chatMessage] + * @param {string} config.chatMessage.template + * @param {boolean} [config.chatMessage.mute] + * @param {object} [config.targets] + * @param {object} [config.costs] */ async diceRoll(config) { config.source = { ...(config.source ?? {}), actor: this.uuid }; @@ -549,7 +563,7 @@ export default class DhpActor extends Actor { headerTitle: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', { ability: abilityLabel }), - effects: await game.system.api.data.actions.actionsTypes.base.getActionRelevantEffects(this), + effects: await game.system.api.data.actions.actionsTypes.base.getEffects(this), roll: { trait: trait, type: 'trait' diff --git a/module/documents/chatMessage.mjs b/module/documents/chatMessage.mjs index b555dfca..480f8c69 100644 --- a/module/documents/chatMessage.mjs +++ b/module/documents/chatMessage.mjs @@ -167,7 +167,7 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage { if (this.system.action) { const actor = await foundry.utils.fromUuid(config.source.actor); const item = actor?.items.get(config.source.item) ?? null; - config.effects = await game.system.api.data.actions.actionsTypes.base.getActionRelevantEffects(actor, item); + config.effects = await game.system.api.data.actions.actionsTypes.base.getEffects(actor, item); await this.system.action.workflow.get('damage')?.execute(config, this._id, true); } } diff --git a/module/enrichers/FateRollEnricher.mjs b/module/enrichers/FateRollEnricher.mjs index 8513ed94..c82bbcb2 100644 --- a/module/enrichers/FateRollEnricher.mjs +++ b/module/enrichers/FateRollEnricher.mjs @@ -49,7 +49,7 @@ export const renderFateButton = async event => { const fateTypeData = getFateTypeData(button.dataset?.fatetype); if (!fateTypeData) ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.fateTypeParsing')); - const { value: fateType } = fateTypeData; + const { value: fateType, label: fateTypeLabel } = fateTypeData; await enrichedFateRoll( { diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index 6467edd7..90a5c29d 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -318,7 +318,7 @@ export function getDocFromElementSync(element) { const target = element.closest('[data-item-uuid]'); try { return foundry.utils.fromUuidSync(target.dataset.itemUuid) ?? null; - } catch { + } catch (_) { return null; } } @@ -377,7 +377,7 @@ export const itemAbleRollParse = (value, actor, item) => { try { return Roll.replaceFormulaData(slicedValue, rollData); - } catch { + } catch (_) { return ''; } }; @@ -885,8 +885,3 @@ export async function triggerChatRollFx(rolls, options = { whisper: false, blind foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice }); } } - -export function shouldUseHopeFearAutomation(options = { gmAsPlayer: true }) { - const { hopeFear } = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation); - return (!game.user.isGM || options.gmAsPlayer) ? hopeFear.players : hopeFear.gm; -} \ No newline at end of file diff --git a/module/systemRegistration/handlebars.mjs b/module/systemRegistration/handlebars.mjs index bf315358..1b08e0ad 100644 --- a/module/systemRegistration/handlebars.mjs +++ b/module/systemRegistration/handlebars.mjs @@ -50,7 +50,6 @@ export const preloadHandlebarsTemplates = async function () { 'systems/daggerheart/templates/ui/chat/parts/target-part.hbs', 'systems/daggerheart/templates/ui/chat/parts/button-part.hbs', 'systems/daggerheart/templates/ui/itemBrowser/itemContainer.hbs', - 'systems/daggerheart/templates/ui/countdowns/parts/countdowns.hbs', 'systems/daggerheart/templates/scene/dh-config.hbs', 'systems/daggerheart/templates/settings/appearance-settings/diceSoNiceTab.hbs', 'systems/daggerheart/templates/sheets/activeEffect/typeChanges/armorChange.hbs' diff --git a/module/systemRegistration/migrations.mjs b/module/systemRegistration/migrations.mjs index ec546c92..b718a127 100644 --- a/module/systemRegistration/migrations.mjs +++ b/module/systemRegistration/migrations.mjs @@ -178,8 +178,8 @@ export async function runMigrations() { await countdownSettings.updateSource({ countdowns: { - ...getCountdowns(countdownSettings.narrative, 'narrative'), - ...getCountdowns(countdownSettings.encounter, 'encounter') + ...getCountdowns(countdownSettings.narrative, CONFIG.DH.GENERAL.countdownBaseTypes.narrative.id), + ...getCountdowns(countdownSettings.encounter, CONFIG.DH.GENERAL.countdownBaseTypes.encounter.id) } }); await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, countdownSettings); diff --git a/module/systemRegistration/settings.mjs b/module/systemRegistration/settings.mjs index 1a985274..a66323c7 100644 --- a/module/systemRegistration/settings.mjs +++ b/module/systemRegistration/settings.mjs @@ -199,10 +199,7 @@ const registerNonConfigSettings = () => { game.settings.register(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, { scope: 'world', config: false, - type: DhCountdowns, - onChange: value => { - value.handleChange(); - } + type: DhCountdowns }); game.settings.register(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.CompendiumBrowserSettings, { diff --git a/styles/less/dialog/tag-team-dialog/initialization.less b/styles/less/dialog/tag-team-dialog/initialization.less index e79ed65c..d6f7ad29 100644 --- a/styles/less/dialog/tag-team-dialog/initialization.less +++ b/styles/less/dialog/tag-team-dialog/initialization.less @@ -88,21 +88,9 @@ grid-template-columns: 1fr 1fr; gap: 8px; - .inactive { + &.inactive { opacity: 0.4; } - - .initiator-cost-fields { - display: flex; - flex-direction: column; - align-items: flex-start; - - .initiator-cost-inputs { - display: grid; - grid-template-columns: auto 1fr; - align-items: center; - } - } } footer { diff --git a/styles/less/sheets/actors/actor-sheet-shared.less b/styles/less/sheets/actors/actor-sheet-shared.less index 89617103..b3eb0469 100644 --- a/styles/less/sheets/actors/actor-sheet-shared.less +++ b/styles/less/sheets/actors/actor-sheet-shared.less @@ -37,10 +37,6 @@ color: @color-text-subtle; } - .window-header > .attribution-header-label { - margin-right: var(--spacer-4); - } - .tab.inventory { .gold-section { display: grid; diff --git a/styles/less/sheets/actors/adversary/header.less b/styles/less/sheets/actors/adversary/header.less index 549a219d..1e5e4fa5 100644 --- a/styles/less/sheets/actors/adversary/header.less +++ b/styles/less/sheets/actors/adversary/header.less @@ -3,21 +3,16 @@ .application.sheet.daggerheart.actor.dh-style.adversary { .adversary-header-sheet { + padding: 0 15px; padding-top: var(--header-height); width: 100%; - > *:not(line-div, .tab-navigation) { - padding-left: 15px; - padding-right: 15px; - } - .name-row { display: flex; gap: 5px; align-items: center; justify-content: space-between; - padding-top: var(--spacer-4); - padding-bottom: var(--spacer-4); + padding: 8px 0; flex: 1; h1 { @@ -39,8 +34,8 @@ .tags { display: flex; - gap: 8px; - padding-bottom: var(--spacer-12); + gap: 10px; + padding-bottom: 8px; .tag { display: flex; @@ -49,7 +44,8 @@ justify-content: center; align-items: center; padding: 3px 5px; - font: var(--font-size-12) @font-body; + font-size: var(--font-size-12); + font: @font-body; background: light-dark(@dark-15, @beige-15); border: 1px solid light-dark(@dark, @beige); @@ -68,16 +64,8 @@ .adversary-info { display: flex; flex-direction: column; - gap: var(--spacer-8); - padding-top: var(--spacer-12); - padding-bottom: var(--spacer-12); - } - - .tab-navigation { - margin-top: 0; - button[data-action="openSettings"] { - margin-right: 12px; - } + gap: 12px; + padding: 16px 0; } } } diff --git a/styles/less/ui/countdown/countdown.less b/styles/less/ui/countdown/countdown.less index 96e01ffd..63e539ba 100644 --- a/styles/less/ui/countdown/countdown.less +++ b/styles/less/ui/countdown/countdown.less @@ -22,6 +22,7 @@ pointer-events: all; align-self: flex-end; transition: 0.3s right ease-in-out; + display: flex; flex-direction: column; @@ -64,31 +65,12 @@ padding: 0 0.5rem; overflow: hidden; font-size: var(--font-size-13); - .window-title { font-family: @font-body; - } - - - .header-type-toggles { flex: 1; - display: flex; - align-items: center; - gap: 4px; - - .header-type { - flex: 1; - border: 1px solid @beige; - border-radius: 12px; - padding: 2px 4px; - text-align: center; - background-color: @dark-blue; - color: @beige; - - &.inactive { - opacity: 0.4; - } - } + } + .header-control + .header-control { + margin-left: 8px; } } @@ -100,23 +82,9 @@ overflow: auto; max-height: 312px; - .countdown-category-container { - display: flex; - flex-direction: column; - gap: 8px; - - &.hidden { - display: none; - } - } - .countdown-container { display: flex; width: 100%; - border-radius: 6px; - background: linear-gradient(-45deg, transparent 30%, light-dark(@dark-blue-40, @golden-40) 35%, transparent 40%); - background-size: 300%; - background-position-x: 100%; &.icon-only { gap: 8px; @@ -137,7 +105,6 @@ display: flex; align-items: center; gap: 16px; - border-radius: 6px; img { width: 2.75rem; diff --git a/styles/less/ui/game-pause/game-pause.less b/styles/less/ui/game-pause/game-pause.less index daf9a1e4..60d08bec 100644 --- a/styles/less/ui/game-pause/game-pause.less +++ b/styles/less/ui/game-pause/game-pause.less @@ -1,34 +1,23 @@ @import '../../utils/mixin.less'; #pause.dh-style { - animation: none; - - img { - width: 125px; - height: 125px; - } - figcaption { - --base-shadow: drop-shadow(2px 2px 2px black); position: absolute; margin-top: 24px; animation: pause-pulse 3s ease-in-out infinite; - font-size: var(--font-size-30); - letter-spacing: 0.18em; - filter: var(--base-shadow); } @keyframes pause-pulse { 0% { - filter: var(--base-shadow) drop-shadow(0 0 5px @secondary-blue); + filter: drop-shadow(0 0 5px @secondary-blue); } 50% { - filter: var(--base-shadow) drop-shadow(0 0 5px @golden-secondary); + filter: drop-shadow(0 0 5px @golden); } 100% { - filter: var(--base-shadow) drop-shadow(0 0 5px @secondary-blue); + filter: drop-shadow(0 0 5px @secondary-blue); } } } \ No newline at end of file diff --git a/styles/less/utils/fonts.less b/styles/less/utils/fonts.less index 72ee1a85..07da3389 100755 --- a/styles/less/utils/fonts.less +++ b/styles/less/utils/fonts.less @@ -10,7 +10,6 @@ --font-size-8: 0.5rem; --font-size-9: 0.5625rem; --font-size-22: 1.375rem; - --font-size-30: 1.875rem; } @font-title: ~"var(--dh-font-title, 'Cinzel Decorative'), serif"; diff --git a/system.json b/system.json index 08693074..f5e13a62 100644 --- a/system.json +++ b/system.json @@ -2,7 +2,7 @@ "id": "daggerheart", "title": "Daggerheart", "description": "An unofficial implementation of the Daggerheart system", - "version": "2.4.0", + "version": "2.3.4", "compatibility": { "minimum": "14.364", "verified": "14.364", @@ -10,7 +10,7 @@ }, "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.4.0/system.zip", + "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.3.4/system.zip", "authors": [ { "name": "WBHarry" diff --git a/templates/dialogs/tagTeamDialog/initialization.hbs b/templates/dialogs/tagTeamDialog/initialization.hbs index 40491e1b..0b92e68e 100644 --- a/templates/dialogs/tagTeamDialog/initialization.hbs +++ b/templates/dialogs/tagTeamDialog/initialization.hbs @@ -26,16 +26,11 @@ -
+
-
- - -
+
+ +
diff --git a/templates/sheets-settings/adversary-settings/features.hbs b/templates/sheets-settings/adversary-settings/features.hbs index 1cab8cfe..2f2f5f47 100644 --- a/templates/sheets-settings/adversary-settings/features.hbs +++ b/templates/sheets-settings/adversary-settings/features.hbs @@ -3,31 +3,27 @@ data-tab='{{tabs.features.id}}' data-group='{{tabs.features.group}}' > - {{#each featureGroups as |group|}} -
- - {{group.label}} - - - - -
    - {{#each group.features as |feature|}} - {{> 'daggerheart.inventory-item' - item=feature - type='feature' - actorType=@root.document.type - hideTags=true - hideContextMenu=true - hideResources=true - showActions=false - hideTooltip=true - }} - {{/each}} -
-
- {{/each}} -
- {{localize "DAGGERHEART.GENERAL.dropFeaturesHere"}} -
+ +
+ {{localize tabs.features.label}} +
    + {{#each @root.features as |feature|}} +
  • + +
    + {{feature.name}} +
    +
    + + +
    +
  • + {{/each}} +
+
+ {{localize "DAGGERHEART.GENERAL.dropFeaturesHere"}} +
+
\ No newline at end of file diff --git a/templates/sheets-settings/environment-settings/features.hbs b/templates/sheets-settings/environment-settings/features.hbs index 1cab8cfe..579fe74e 100644 --- a/templates/sheets-settings/environment-settings/features.hbs +++ b/templates/sheets-settings/environment-settings/features.hbs @@ -3,31 +3,24 @@ data-tab='{{tabs.features.id}}' data-group='{{tabs.features.group}}' > - {{#each featureGroups as |group|}} -
- - {{group.label}} - - - - -
    - {{#each group.features as |feature|}} - {{> 'daggerheart.inventory-item' - item=feature - type='feature' - actorType=@root.document.type - hideTags=true - hideContextMenu=true - hideResources=true - showActions=false - hideTooltip=true - }} - {{/each}} -
-
- {{/each}} -
- {{localize "DAGGERHEART.GENERAL.dropFeaturesHere"}} -
+ +
+ {{localize tabs.features.label}} +
    + {{#each @root.features as |feature|}} +
  • + +
    + {{feature.name}} +
    +
    + + +
    +
  • + {{/each}} +
+
\ No newline at end of file diff --git a/templates/sheets-settings/npc-settings/features.hbs b/templates/sheets-settings/npc-settings/features.hbs index 1cab8cfe..2f2f5f47 100644 --- a/templates/sheets-settings/npc-settings/features.hbs +++ b/templates/sheets-settings/npc-settings/features.hbs @@ -3,31 +3,27 @@ data-tab='{{tabs.features.id}}' data-group='{{tabs.features.group}}' > - {{#each featureGroups as |group|}} -
- - {{group.label}} - - - - -
    - {{#each group.features as |feature|}} - {{> 'daggerheart.inventory-item' - item=feature - type='feature' - actorType=@root.document.type - hideTags=true - hideContextMenu=true - hideResources=true - showActions=false - hideTooltip=true - }} - {{/each}} -
-
- {{/each}} -
- {{localize "DAGGERHEART.GENERAL.dropFeaturesHere"}} -
+ +
+ {{localize tabs.features.label}} +
    + {{#each @root.features as |feature|}} +
  • + +
    + {{feature.name}} +
    +
    + + +
    +
  • + {{/each}} +
+
+ {{localize "DAGGERHEART.GENERAL.dropFeaturesHere"}} +
+
\ No newline at end of file diff --git a/templates/sheets/global/partials/domain-card-item.hbs b/templates/sheets/global/partials/domain-card-item.hbs index ca584e57..54e44e64 100644 --- a/templates/sheets/global/partials/domain-card-item.hbs +++ b/templates/sheets/global/partials/domain-card-item.hbs @@ -1,4 +1,4 @@ -
  • +
  • {{item.system.recallCost}} diff --git a/templates/sheets/global/partials/inventory-item-V2.hbs b/templates/sheets/global/partials/inventory-item-V2.hbs index f7d22a30..523e9304 100644 --- a/templates/sheets/global/partials/inventory-item-V2.hbs +++ b/templates/sheets/global/partials/inventory-item-V2.hbs @@ -27,17 +27,17 @@ Parameters: >
    {{!-- Image --}} -
    - - {{#if (and item.usable (ne showActions false))}} - {{#if @root.isNPC}} - d20 - {{else}} - 2d12 - {{/if}} +
    + + {{#if item.usable}} + {{#if @root.isNPC}} + d20 + {{else}} + 2d12 {{/if}} + {{/if}}
    {{!-- Name & Tags --}} @@ -46,10 +46,10 @@ Parameters: {{localize item.name}} {{#unless (or noExtensible (not item.system.description))}}{{/unless}} {{!-- Tags Start --}} - {{#if (not hideTags)}} + {{#if (not ../hideTags)}} {{#> "systems/daggerheart/templates/sheets/global/partials/item-tags.hbs" item}} {{#if (eq ../type 'feature')}} - {{#if (and system.featureForm (ne @root.document.type "character"))}} + {{#if (and system.featureForm (or (eq @root.document.type "adversary") (eq @root.document.type "environment")))}}
    {{localize (concat "DAGGERHEART.CONFIG.FeatureForm." system.featureForm)}}
    diff --git a/templates/ui/countdowns/countdown-edit.hbs b/templates/ui/countdown-edit.hbs similarity index 97% rename from templates/ui/countdowns/countdown-edit.hbs rename to templates/ui/countdown-edit.hbs index eebefe11..6b7d22d4 100644 --- a/templates/ui/countdowns/countdown-edit.hbs +++ b/templates/ui/countdown-edit.hbs @@ -78,9 +78,9 @@
    - +
    diff --git a/templates/ui/countdowns/parts/countdowns.hbs b/templates/ui/countdowns.hbs similarity index 70% rename from templates/ui/countdowns/parts/countdowns.hbs rename to templates/ui/countdowns.hbs index 4f0ee086..4a77dfd7 100644 --- a/templates/ui/countdowns/parts/countdowns.hbs +++ b/templates/ui/countdowns.hbs @@ -1,11 +1,23 @@ -{{#each countdowns as | category key |}} -
    - {{#each category as |countdown id|}} -
    +
    +
    + + {{localize "DAGGERHEART.UI.Countdowns.title"}} + {{#if isGM}} + + + + {{/if}} + + + +
    +
    + {{#each countdowns as | countdown id |}} +
    - +
    - {{#unless @root.iconOnly}} + {{#unless ../iconOnly}}
    {{countdown.name}}
    {{/unless}}
    @@ -17,7 +29,7 @@ {{#if countdown.editable}}{{/if}}
    - {{#if (not @root.iconOnly)}} + {{#if (not ../iconOnly)}} {{#if (and countdown.noPlayerAccess @root.isGM)}} {{/if}} @@ -41,4 +53,4 @@
    {{/each}}
    -{{/each}} \ No newline at end of file +
    \ No newline at end of file diff --git a/templates/ui/countdowns/countdowns-view.hbs b/templates/ui/countdowns/countdowns-view.hbs deleted file mode 100644 index 7727c4b6..00000000 --- a/templates/ui/countdowns/countdowns-view.hbs +++ /dev/null @@ -1,24 +0,0 @@ -
    -
    - - {{#unless iconOnly}} - {{localize "DAGGERHEART.UI.Countdowns.title"}} - {{/unless}} -
    - {{#each typeToggles as |type|}} - {{localize type.label}} - {{/each}} -
    - {{#if isGM}} - - - - {{/if}} - - - -
    -
    - {{> "systems/daggerheart/templates/ui/countdowns/parts/countdowns.hbs" }} -
    -
    \ No newline at end of file